naohiro701 commited on
Commit
72b203f
·
verified ·
1 Parent(s): 066be59

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -269
app.py CHANGED
@@ -6,320 +6,209 @@ import pandas as pd
6
  import io
7
  from PIL import Image
8
  import numpy as np
9
- import time
10
- from osmnx.errors import InsufficientResponseError, EmptyOverpassResponse # 追加
11
 
12
  def meters_to_degrees(meters, lat):
 
 
 
13
  lat_deg = meters / 111320
14
  lon_deg = meters / (111320 * np.cos(np.radians(lat)))
15
  return lat_deg, lon_deg
16
 
17
- def create_artistic_map(
18
- lat, lon, distance, dpi, width, height, bg_color, greenery_color, water_color,
19
- building_colors, major_road_color, medium_road_color, minor_road_color,
20
- display_greenery, display_water, display_buildings,
21
- display_major_roads, display_medium_roads, display_minor_roads,
22
- progress_bar, time_placeholder
23
- ):
24
- start_time = time.time()
25
- current_step = 0
26
- total_steps = 2 # 基本のステップ数
27
-
28
- if display_greenery:
29
- total_steps += 1
30
- if display_water:
31
- total_steps += 1
32
- if display_buildings:
33
- total_steps += 1
34
- if display_major_roads or display_medium_roads or display_minor_roads:
35
- total_steps += 1
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  # 中心点の設定
38
  point = (lat, lon)
39
-
40
  # 距離をもとに緯度経度の幅と高さを計算
41
  lat_deg, lon_deg = meters_to_degrees(distance, lat)
42
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  # 描画設定
44
  fig, ax = plt.subplots(figsize=(width, height))
45
-
46
  # 背景色を設定
47
  fig.patch.set_facecolor(bg_color)
48
  ax.set_facecolor(bg_color)
49
-
50
- # 進捗を更新
51
- current_step += 1
52
- elapsed_time = time.time() - start_time
53
- estimated_total_time = (elapsed_time / current_step) * total_steps
54
- remaining_time = estimated_total_time - elapsed_time
55
- progress_percent = int((current_step / total_steps) * 100)
56
- progress_bar.progress(progress_percent)
57
- time_placeholder.text(f"残り時間: 約{int(remaining_time // 60)}分{int(remaining_time % 60)}秒")
58
 
59
  # 緑地の描画
60
- if display_greenery:
61
- # 緑地データ取得
62
- greenery_list = []
63
- greenery_tags = [
64
- {'leisure': 'park'},
65
- {'leisure': 'garden'},
66
- {'landuse': 'grass'},
67
- {'landuse': 'forest'},
68
- {'landuse': 'meadow'},
69
- {'natural': 'wood'},
70
- {'natural': 'scrub'},
71
- {'natural': 'grassland'},
72
- {'natural': 'heath'}
73
- ]
74
- for tags in greenery_tags:
75
- try:
76
- greenery_part = ox.features_from_point(point, tags=tags, dist=distance)
77
- if not greenery_part.empty:
78
- greenery_list.append(greenery_part)
79
- except (InsufficientResponseError, EmptyOverpassResponse):
80
- continue
81
- except Exception as e:
82
- st.warning(f"緑地データ取得中にエラーが発生しました: {e}")
83
- continue
84
- if greenery_list:
85
- greenery = pd.concat(greenery_list)
86
- greenery.plot(ax=ax, facecolor=greenery_color, edgecolor='none')
87
- else:
88
- st.warning("指定された範囲に緑地が見つかりませんでした。")
89
 
90
- # 進捗を更新
91
- current_step += 1
92
- elapsed_time = time.time() - start_time
93
- estimated_total_time = (elapsed_time / current_step) * total_steps
94
- remaining_time = estimated_total_time - elapsed_time
95
- progress_percent = int((current_step / total_steps) * 100)
96
- progress_bar.progress(progress_percent)
97
- time_placeholder.text(f"残り時間: 約{int(remaining_time // 60)}分{int(remaining_time % 60)}秒")
98
-
99
  # 水域の描画
100
- if display_water:
101
- # 水域データ取得
102
- water_list = []
103
- water_tags = [
104
- {'natural': 'water'},
105
- {'waterway': 'riverbank'},
106
- {'landuse': 'reservoir'},
107
- {'landuse': 'basin'},
108
- {'natural': 'wetland'}
109
- ]
110
- for tags in water_tags:
111
- try:
112
- water_part = ox.features_from_point(point, tags=tags, dist=distance)
113
- if not water_part.empty:
114
- water_list.append(water_part)
115
- except (InsufficientResponseError, EmptyOverpassResponse):
116
- continue
117
- except Exception as e:
118
- st.warning(f"水域データ取得中にエラーが発生しました: {e}")
119
- continue
120
- if water_list:
121
- water = pd.concat(water_list)
122
- water.plot(ax=ax, facecolor=water_color, edgecolor='none')
123
- else:
124
- st.warning("指定された範囲に水域が見つかりませんでした。")
125
 
126
- # 進捗を更新
127
- current_step += 1
128
- elapsed_time = time.time() - start_time
129
- estimated_total_time = (elapsed_time / current_step) * total_steps
130
- remaining_time = estimated_total_time - elapsed_time
131
- progress_percent = int((current_step / total_steps) * 100)
132
- progress_bar.progress(progress_percent)
133
- time_placeholder.text(f"残り時間: 約{int(remaining_time // 60)}分{int(remaining_time % 60)}秒")
134
-
135
  # 建物の描画
136
- if display_buildings:
137
- # 建物データ取得
138
- try:
139
- buildings = ox.features_from_point(point, tags={'building': True}, dist=distance)
140
- if not buildings.empty:
141
- if 'building' in buildings.columns:
142
- building_types = buildings['building'].unique()
143
- for i, building_type in enumerate(building_types):
144
- building_subset = buildings[buildings['building'] == building_type]
145
- building_color = building_colors[i % len(building_colors)]
146
- building_subset.plot(ax=ax, facecolor=building_color, edgecolor='none')
147
- else:
148
- # 'building'カラムがない場合、すべて同じ色で表示
149
- buildings.plot(ax=ax, facecolor=building_colors[0], edgecolor='none')
150
- else:
151
- st.warning("指定された範囲に建物が見つかりませんでした。")
152
- except (InsufficientResponseError, EmptyOverpassResponse):
153
- st.warning("指定された範囲に建物が見つかりませんでした。")
154
- except Exception as e:
155
- st.warning(f"建物データ取得中にエラーが発生しました: {e}")
156
 
157
- # 進捗を更新
158
- current_step += 1
159
- elapsed_time = time.time() - start_time
160
- estimated_total_time = (elapsed_time / current_step) * total_steps
161
- remaining_time = estimated_total_time - elapsed_time
162
- progress_percent = int((current_step / total_steps) * 100)
163
- progress_bar.progress(progress_percent)
164
- time_placeholder.text(f"残り時間: 約{int(remaining_time // 60)}分{int(remaining_time % 60)}秒")
165
-
166
  # 道路の描画
167
- if display_major_roads or display_medium_roads or display_minor_roads:
168
- try:
169
- # OSMデータ取得
170
- G = ox.graph_from_point(point, dist=distance, network_type='all')
171
- nodes, edges = ox.graph_to_gdfs(G)
172
- if not edges.empty and 'highway' in edges.columns:
173
- # 主要道路の描画
174
- if display_major_roads:
175
- major_roads = edges[edges['highway'].isin(['motorway', 'trunk', 'primary'])]
176
- if not major_roads.empty:
177
- major_roads.plot(ax=ax, linewidth=2, edgecolor=major_road_color)
178
- else:
179
- st.warning("指定された範囲に主要道路が見つかりませんでした。")
180
-
181
- # 中程度の道路の描画
182
- if display_medium_roads:
183
- medium_roads = edges[edges['highway'].isin(['secondary', 'tertiary'])]
184
- if not medium_roads.empty:
185
- medium_roads.plot(ax=ax, linewidth=1.5, edgecolor=medium_road_color)
186
- else:
187
- st.warning("指定された範囲に中程度の道路が見つかりませんでした。")
188
-
189
- # 小規模な道路の描画
190
- if display_minor_roads:
191
- minor_roads = edges[~edges['highway'].isin(['motorway', 'trunk', 'primary', 'secondary', 'tertiary'])]
192
- if not minor_roads.empty:
193
- minor_roads.plot(ax=ax, linewidth=1, edgecolor=minor_road_color)
194
- else:
195
- st.warning("指定された範囲に小規模な道路が見つかりませんでした。")
196
- else:
197
- st.warning("指定された範囲に道路データが見つかりませんでした。")
198
- except (InsufficientResponseError, EmptyOverpassResponse):
199
- st.warning("指定された範囲に道路データが見つかりませんでした。")
200
- except Exception as e:
201
- st.warning(f"道路データ取得中にエラーが発生しました: {e}")
202
 
203
- # 進捗を更新
204
- current_step += 1
205
- elapsed_time = time.time() - start_time
206
- estimated_total_time = (elapsed_time / current_step) * total_steps
207
- remaining_time = estimated_total_time - elapsed_time
208
- progress_percent = int((current_step / total_steps) * 100)
209
- progress_bar.progress(progress_percent)
210
- time_placeholder.text(f"残り時間: 約{int(remaining_time // 60)}分{int(remaining_time % 60)}秒")
211
-
212
  # 軸をオフに設定
213
  ax.axis('off')
214
-
215
  # 表示範囲を設定
216
  ax.set_xlim([lon - lon_deg, lon + lon_deg])
217
  ax.set_ylim([lat - lat_deg, lat + lat_deg])
218
 
219
- # 最終処理の進捗を更新
220
- current_step += 1
221
- progress_percent = int((current_step / total_steps) * 100)
222
- progress_bar.progress(progress_percent)
223
- time_placeholder.text("最終処理中...")
224
-
225
- # バッファに保存して、PIL画像として返す
226
  buf = io.BytesIO()
227
  plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=dpi)
228
  plt.close()
229
  buf.seek(0)
230
-
231
- # 進捗を100%に更新
232
- progress_bar.progress(100)
233
- time_placeholder.text("完了しました!")
234
-
235
  return Image.open(buf)
236
 
237
  # Streamlit UI
238
  st.title("カラフルな地図が作成できます")
239
 
240
- # デフォルト値を設定
241
- default_lat = 35.533283
242
- default_lon = 139.642000
243
- default_distance = 1.0 # キロメートル
244
-
245
  # ユーザー入力
246
- lat = st.number_input("緯度を入力してください", value=default_lat, format="%.6f")
247
- lon = st.number_input("経度を入力してください", value=default_lon, format="%.6f")
248
- distance = st.number_input("距離を選択(km)", value=default_distance, format="%.2f")
249
 
250
- # 画像設定
251
- dpi = st.number_input("画像のDPI(解像度)を選択", value=72, format="%d")
252
- width = st.slider("画像の幅(インチ)を選択", 5, 30, 20)
253
- height = st.slider("画像の高さ(インチ)を選択", 5, 30, 20)
254
-
255
- # 表示設定
256
- st.header("表示設定")
257
-
258
- # 背景色
259
- bg_color = st.color_picker("背景色を選択", "#FAF3E0")
260
-
261
- # 緑地の設定
262
- st.subheader("緑地の設定")
263
- display_greenery = st.checkbox("緑地を表示", value=True)
264
- if display_greenery:
265
- greenery_color = st.color_picker("緑地の色を選択", "#80C341")
266
- else:
267
- greenery_color = None
268
-
269
- # 水域の設定
270
- st.subheader("水域の設定")
271
- display_water = st.checkbox("水域を表示", value=True)
272
- if display_water:
273
- water_color = st.color_picker("水域の色を選択", "#45A6FF")
274
- else:
275
- water_color = None
276
-
277
- # 建物の設定
278
- st.subheader("建物の設定")
279
- display_buildings = st.checkbox("建物を表示", value=True)
280
- if display_buildings:
281
- default_building_colors = ['#FF6F61', '#FFAB73', '#FFA07A', '#FFD700', '#F08080']
282
- building_colors = [
283
- st.color_picker(f"建物の色 {i+1}", default_color)
284
- for i, default_color in enumerate(default_building_colors)
285
- ]
286
- else:
287
- building_colors = []
288
-
289
- # 道路の設定
290
- st.subheader("道路の設定")
291
- # 主要道路
292
- display_major_roads = st.checkbox("主要道路を表示", value=True)
293
- if display_major_roads:
294
- major_road_color = st.color_picker("主要道路の色を選択", "#FF6347")
295
  else:
296
- major_road_color = None
297
-
298
- # 中程度の道路
299
- display_medium_roads = st.checkbox("中程度の道路を表示", value=True)
300
- if display_medium_roads:
301
- medium_road_color = st.color_picker("中程度の道路の色を選択", "#FF4500")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  else:
303
- medium_road_color = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
- # 小規模な道路
306
- display_minor_roads = st.checkbox("小規模な道路を表示", value=True)
307
- if display_minor_roads:
308
- minor_road_color = st.color_picker("小規模な道路の色を選択", "#D2691E")
309
- else:
310
- minor_road_color = None
311
 
312
  # マップ生成ボタン
313
  if st.button("マップを生成"):
314
- # 進捗バーと残り時間のプレースホルダーを初期化
315
- progress_bar = st.progress(0)
316
- time_placeholder = st.empty()
317
- map_image = create_artistic_map(
318
- lat, lon, distance*1000, dpi, width, height,
319
- bg_color, greenery_color, water_color, building_colors,
320
- major_road_color, medium_road_color, minor_road_color,
321
- display_greenery, display_water, display_buildings,
322
- display_major_roads, display_medium_roads, display_minor_roads,
323
- progress_bar, time_placeholder
324
- )
325
- st.image(map_image, caption="生成されたアートマップ", use_column_width=True)
 
6
  import io
7
  from PIL import Image
8
  import numpy as np
9
+
10
+ ox.settings.use_cache = True # OSMnxのキャッシュを有効化
11
 
12
  def meters_to_degrees(meters, lat):
13
+ """
14
+ 距離(メートル)を緯度・経度の変化量(度)に変換する。
15
+ """
16
  lat_deg = meters / 111320
17
  lon_deg = meters / (111320 * np.cos(np.radians(lat)))
18
  return lat_deg, lon_deg
19
 
20
+ @st.cache_data
21
+ def fetch_osm_data(point, distance):
22
+ """
23
+ 指定した地点と距離に基づいて、OSMデータ(ノード、エッジ、フィーチャ)を取得する。
24
+ """
25
+ tags = {
26
+ 'building': True,
27
+ 'natural': ['water', 'wood'],
28
+ 'landuse': ['grass', 'forest'],
29
+ 'leisure': 'park'
30
+ }
31
+ # グラフデータの取得
32
+ G = ox.graph_from_point(point, dist=distance, network_type='all')
33
+ nodes, edges = ox.graph_to_gdfs(G)
34
+ # フィーチャデータの取得
35
+ features = ox.geometries_from_point(point, tags, dist=distance)
36
+ return nodes, edges, features
37
+
38
+ def create_artistic_map(lat, lon, distance, dpi, width, height, colors):
39
+ """
40
+ 指定されたパラメータに基づいて、カスタマイズ可能なアートマップ画像を作成する。
41
+ """
42
+ # カラー設定の取得
43
+ bg_color = colors['bg_color']
44
+ greenery_color = colors['greenery_color']
45
+ water_color = colors['water_color']
46
+ building_colors = colors['building_colors']
47
+ major_road_color = colors['major_road_color']
48
+ medium_road_color = colors['medium_road_color']
49
+ minor_road_color = colors['minor_road_color']
50
 
51
  # 中心点の設定
52
  point = (lat, lon)
53
+
54
  # 距離をもとに緯度経度の幅と高さを計算
55
  lat_deg, lon_deg = meters_to_degrees(distance, lat)
56
+
57
+ # OSMデータ取得
58
+ nodes, edges, features = fetch_osm_data(point, distance)
59
+
60
+ # 建物データ取得
61
+ buildings = features[features['building'].notnull()]
62
+
63
+ # 水域データ取得
64
+ water = features[features['natural'] == 'water']
65
+
66
+ # 緑地データ取得(公園、草地、森林など)
67
+ greenery = features[
68
+ (features['leisure'] == 'park') |
69
+ (features['landuse'].isin(['grass', 'forest'])) |
70
+ (features['natural'] == 'wood')
71
+ ]
72
+
73
  # 描画設定
74
  fig, ax = plt.subplots(figsize=(width, height))
75
+
76
  # 背景色を設定
77
  fig.patch.set_facecolor(bg_color)
78
  ax.set_facecolor(bg_color)
 
 
 
 
 
 
 
 
 
79
 
80
  # 緑地の描画
81
+ if not greenery.empty:
82
+ greenery.plot(ax=ax, facecolor=greenery_color, edgecolor='none')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
 
 
 
 
 
 
 
 
 
84
  # 水域の描画
85
+ if not water.empty:
86
+ water.plot(ax=ax, facecolor=water_color, edgecolor='none')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
 
 
 
 
 
 
 
 
 
88
  # 建物の描画
89
+ if not buildings.empty:
90
+ building_types = buildings['building'].unique()
91
+ for i, building_type in enumerate(building_types):
92
+ building_subset = buildings[buildings['building'] == building_type]
93
+ building_color = building_colors[i % len(building_colors)]
94
+ building_subset.plot(ax=ax, facecolor=building_color, edgecolor='none')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
 
 
 
 
 
 
 
 
 
96
  # 道路の描画
97
+ if not edges.empty and 'highway' in edges.columns:
98
+ # 主要道路の描画
99
+ major_roads = edges[edges['highway'].isin(['motorway', 'trunk', 'primary'])]
100
+ if not major_roads.empty:
101
+ major_roads.plot(ax=ax, linewidth=2, edgecolor=major_road_color)
102
+
103
+ # 中程度の道路の描画
104
+ medium_roads = edges[edges['highway'].isin(['secondary', 'tertiary'])]
105
+ if not medium_roads.empty:
106
+ medium_roads.plot(ax=ax, linewidth=1.5, edgecolor=medium_road_color)
107
+
108
+ # 小規模な道路の描画
109
+ minor_roads = edges[~edges['highway'].isin(['motorway', 'trunk', 'primary', 'secondary', 'tertiary'])]
110
+ if not minor_roads.empty:
111
+ minor_roads.plot(ax=ax, linewidth=1, edgecolor=minor_road_color)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
 
 
 
 
 
 
 
 
 
113
  # 軸をオフに設定
114
  ax.axis('off')
115
+
116
  # 表示範囲を設定
117
  ax.set_xlim([lon - lon_deg, lon + lon_deg])
118
  ax.set_ylim([lat - lat_deg, lat + lat_deg])
119
 
120
+ # 画像をバッファに保存
 
 
 
 
 
 
121
  buf = io.BytesIO()
122
  plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=dpi)
123
  plt.close()
124
  buf.seek(0)
 
 
 
 
 
125
  return Image.open(buf)
126
 
127
  # Streamlit UI
128
  st.title("カラフルな地図が作成できます")
129
 
 
 
 
 
 
130
  # ユーザー入力
131
+ location = st.text_input("場所を入力してください(住所、都市名など)")
 
 
132
 
133
+ default_lat = 35.533283
134
+ default_lon = 139.642000
135
+ default_distance = 1000
136
+
137
+ if location:
138
+ try:
139
+ geocode_result = ox.geocode(location)
140
+ lat, lon = geocode_result[0], geocode_result[1]
141
+ st.write(f"緯度: {lat}, 経度: {lon}")
142
+ except Exception as e:
143
+ st.error("場所の取得に失敗しました。緯度と経度を直接入力してください。")
144
+ lat = st.number_input("緯度を入力してください", value=default_lat, format="%.6f")
145
+ lon = st.number_input("経度を入力してください", value=default_lon, format="%.6f")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  else:
147
+ lat = st.number_input("緯度を入力してください", value=default_lat, format="%.6f")
148
+ lon = st.number_input("経度を入力してください", value=default_lon, format="%.6f")
149
+
150
+ distance = st.slider("距離を選択(メートル)", 100, 5000, default_distance)
151
+
152
+ # カラーテーマの設定
153
+ color_themes = {
154
+ 'テーマ1': {
155
+ 'bg_color': '#FAF3E0',
156
+ 'greenery_color': '#80C341',
157
+ 'water_color': '#45A6FF',
158
+ 'building_colors': ['#FF6F61', '#FFAB73', '#FFA07A', '#FFD700', '#F08080'],
159
+ 'major_road_color': '#FF6347',
160
+ 'medium_road_color': '#FF4500',
161
+ 'minor_road_color': '#D2691E'
162
+ },
163
+ 'テーマ2': {
164
+ 'bg_color': '#FFFFFF',
165
+ 'greenery_color': '#00FF00',
166
+ 'water_color': '#0000FF',
167
+ 'building_colors': ['#A52A2A', '#8B0000', '#B22222', '#FF0000', '#FF6347'],
168
+ 'major_road_color': '#000000',
169
+ 'medium_road_color': '#2F4F4F',
170
+ 'minor_road_color': '#696969'
171
+ },
172
+ 'カスタム': None
173
+ }
174
+
175
+ theme_name = st.selectbox("カラーテーマを選択", list(color_themes.keys()))
176
+
177
+ if theme_name != 'カスタム':
178
+ colors = color_themes[theme_name]
179
  else:
180
+ # 色設定
181
+ with st.expander("カスタムカラーパレット"):
182
+ bg_color = st.color_picker("背景色を選択", "#FAF3E0")
183
+ greenery_color = st.color_picker("緑地の色を選択", "#80C341")
184
+ water_color = st.color_picker("水域の色を選択", "#45A6FF")
185
+ building_colors = [
186
+ st.color_picker(f"建物の色 {i+1}", default_color)
187
+ for i, default_color in enumerate(['#FF6F61', '#FFAB73', '#FFA07A', '#FFD700', '#F08080'])
188
+ ]
189
+ major_road_color = st.color_picker("主要道路の色を選択", "#FF6347")
190
+ medium_road_color = st.color_picker("中程度の道路の色を選択", "#FF4500")
191
+ minor_road_color = st.color_picker("小規模な道路の色を選択", "#D2691E")
192
+ colors = {
193
+ 'bg_color': bg_color,
194
+ 'greenery_color': greenery_color,
195
+ 'water_color': water_color,
196
+ 'building_colors': building_colors,
197
+ 'major_road_color': major_road_color,
198
+ 'medium_road_color': medium_road_color,
199
+ 'minor_road_color': minor_road_color
200
+ }
201
 
202
+ # 画像設定
203
+ with st.expander("画像設定(オプション)"):
204
+ dpi = st.slider("画像のDPI(解像度)を選択", 72, 600, 300)
205
+ width = st.slider("画像の幅(インチ)を選択", 5, 30, 20)
206
+ height = st.slider("画像の高さ(インチ)を選択", 5, 30, 20)
 
207
 
208
  # マップ生成ボタン
209
  if st.button("マップを生成"):
210
+ with st.spinner('マップを生成中...'):
211
+ map_image = create_artistic_map(
212
+ lat, lon, distance, dpi, width, height, colors
213
+ )
214
+ st.image(map_image, caption="生成されたアートマップ", use_column_width=True)