hiroki0008 commited on
Commit
0b5b913
·
verified ·
1 Parent(s): b6b7d5c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +28 -27
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py (Folium + 無料タイル / File配信 / 発電設備区分で色分け)
2
  # pip install folium gradio pandas numpy requests openpyxl
3
 
4
  import os
@@ -141,16 +141,13 @@ _BASE_PALETTE = [
141
  ]
142
 
143
  def _build_category_palette(labels: pd.Series) -> dict:
144
- """カテゴリ→色 のマッピングを生成。カテゴリが多い場合はHSLで追加生成。"""
145
  labels = pd.Series(labels).astype(str).fillna("その他/不明").replace({"nan":"その他/不明","None":"その他/不明"})
146
  uniq = list(pd.unique(labels))
147
  mapping = {}
148
- # まず固定パレット
149
  for i, lab in enumerate(uniq):
150
  if i < len(_BASE_PALETTE):
151
  mapping[lab] = _BASE_PALETTE[i]
152
  else:
153
- # HSLで追加(色相ずらし)
154
  h = (i * 37) % 360
155
  mapping[lab] = f"hsl({h},70%,45%)"
156
  return mapping
@@ -181,22 +178,21 @@ def _build_folium_map_html(df_points: pd.DataFrame, base_name: str, cat_col: str
181
  center_lon = float(df_valid["lon"].median())
182
  zoom = 6
183
 
184
- # ベースマップ(複数切替)
185
  m = folium.Map(location=[center_lat, center_lon], zoom_start=zoom, control_scale=True, tiles=None)
186
  for name, url in TILE_CATALOG.items():
187
  folium.TileLayer(tiles=url, name=name, attr=f"© {name}", overlay=False, control=True, max_zoom=20).add_to(m)
188
 
189
- # カテゴリ列の用意
190
  if cat_col and (cat_col in df_points.columns):
191
  cats = df_points[cat_col].astype("string").fillna("その他/不明")
192
  else:
193
  cat_col = "(区分なし)"
194
  cats = pd.Series(["その他/不明"] * len(df_points))
195
 
196
- # パレット生成
197
  palette = _build_category_palette(cats)
198
 
199
- # マーカーのサイズ(CFで可変
200
  if "CF" in df_valid.columns and df_valid["CF"].notna().any():
201
  cf = df_valid["CF"].clip(lower=0)
202
  cf_norm = (cf - cf.min()) / (cf.max() - cf.min() + 1e-9)
@@ -214,7 +210,7 @@ def _build_folium_map_html(df_points: pd.DataFrame, base_name: str, cat_col: str
214
 
215
  popup_html = (
216
  f"<b>住所:</b> {addr}<br>"
217
- f"<b>CF:</b> {'' if pd.isna(cfv) else cfv}<br>"
218
  f"<b>{cat_col}:</b> {catv}"
219
  )
220
 
@@ -229,7 +225,7 @@ def _build_folium_map_html(df_points: pd.DataFrame, base_name: str, cat_col: str
229
  popup=folium.Popup(popup_html, max_width=320),
230
  ).add_to(m)
231
 
232
- # 凡例を追加
233
  legend_html = _build_legend_html(cat_col, palette)
234
  m.get_root().html.add_child(Element(legend_html))
235
 
@@ -238,14 +234,14 @@ def _build_folium_map_html(df_points: pd.DataFrame, base_name: str, cat_col: str
238
 
239
  def _rewrite_leaflet_cdn(html_text: str, host: str) -> str:
240
  """
241
- Folium が出力する Leaflet の CDN(通常 jsDelivr)を必要に応じて置換。
242
- SRI不整合を避けるため integrity/crossorigin を除去する
243
  """
244
  html_text = re.sub(r'\sintegrity="[^"]+"', "", html_text)
245
  html_text = re.sub(r'\scrossorigin="[^"]+"', "", html_text)
246
 
247
  if host == "jsdelivr":
248
- return html_text # 置換しない
249
  elif host == "cdnjs":
250
  html_text = html_text.replace(
251
  "https://cdn.jsdelivr.net/npm/leaflet@", "https://cdnjs.cloudflare.com/ajax/libs/leaflet/"
@@ -281,13 +277,13 @@ def _parse_indexer(x):
281
  def run(excel_file, sheet_name, header_row, address_col, power_col, category_col, use_inet, base_name, leaflet_cdn):
282
  # Excel 読み込み
283
  if excel_file is None or not hasattr(excel_file, "name"):
284
- table_df = pd.DataFrame(columns=["address_input", "CF", "lat", "lon", category_col or "発電設備区分"])
285
  return ("Excelファイルを指定してください。", table_df, "", None)
286
 
287
  try:
288
  df = pd.read_excel(excel_file.name, sheet_name=sheet_name, header=int(header_row))
289
  except Exception as e:
290
- empty_df = pd.DataFrame(columns=["address_input", "CF", "lat", "lon", category_col or "発電設備区分"])
291
  return (f"Excel の読み込みに失敗しました: {e}", empty_df, "", None)
292
 
293
  # 列参照(番号/名前の両対応)
@@ -299,7 +295,7 @@ def run(excel_file, sheet_name, header_row, address_col, power_col, category_col
299
  cat_series = df.iloc[:, category_col] if isinstance(category_col, int) else df[category_col]
300
  except Exception:
301
  cat_series = pd.Series([np.nan] * len(df))
302
- category_col = "発電設備区分" # 名称だけ維持
303
  else:
304
  cat_series = pd.Series([np.nan] * len(df))
305
  category_col = "発電設備区分"
@@ -307,26 +303,31 @@ def run(excel_file, sheet_name, header_row, address_col, power_col, category_col
307
  addresses = addr_series.astype(str).tolist()
308
  cfs = cf_series.tolist()
309
 
310
- # ジオコーディング
311
  geo_df = geocode_with_cache(addresses, cfs, use_internet=bool(use_inet))
312
- # 出力テーブル(区分列も付与)
 
313
  table_df = geo_df.copy()
314
  table_df[category_col] = cat_series.values
 
315
 
316
  # 地図HTML生成 → CDN書換 → 実ファイル保存 → File出力
317
  try:
318
- html_text = _build_folium_map_html(table_df, base_name=base_name, cat_col=category_col)
319
  html_text = _rewrite_leaflet_cdn(html_text, host=leaflet_cdn)
320
  map_file_path = _save_map_html_file(html_text)
321
 
322
  msg = (
323
  "✅ 地図HTMLを生成しました。下の **地図HTMLファイル** をクリックして新規タブで開いてください。\n"
324
- "色=「発電設備区分」(UIで列名変更可)、サイズ=CF任意列)です。"
325
  )
326
- info = f"ポイント数(有効座標): {int(table_df[['lat','lon']].dropna().shape[0])} / {len(table_df)}"
327
- return (msg, table_df[["address_input","CF","lat","lon",category_col]], info, map_file_path)
 
 
328
  except Exception as e:
329
- return (f"地図描画に失敗しました: {e}", table_df[["address_input","CF","lat","lon",category_col]], "", None)
 
330
 
331
  # ----------------------------
332
  # Gradio UI
@@ -335,7 +336,7 @@ with gr.Blocks(title="Excel住所 → Folium(無料タイル・File配信・
335
  gr.Markdown(
336
  "## Excelの住所を国土地理院APIでジオコーディング → Folium(Leaflet)で地図表示(無料タイル・Mapbox不要)\n"
337
  "- 地図は **実ファイル(.html)** として配信します(CSPが厳しい環境でもOK)。\n"
338
- "- 色=**発電設備区分**、サイズ=**CF**。タイル=地理院/OSM、CDNは必要に応じて切替できます。"
339
  )
340
 
341
  with gr.Row():
@@ -346,8 +347,8 @@ with gr.Blocks(title="Excel住所 → Folium(無料タイル・File配信・
346
  header_row = gr.Number(label="ヘッダー行番号(0始まり)", value=2, precision=0)
347
 
348
  with gr.Row():
349
- address_col = gr.Textbox(label="住所列(列名 or 0始まり列番号)", value="発電設備の所在地")
350
- power_col = gr.Textbox(label="CF列(任意:列名 or 0始まり列番号)", value="発電出力(kW)")
351
  category_col = gr.Textbox(label="区分列(色分け:列名 or 0始まり列番号)", value="発電設備区分")
352
 
353
  with gr.Row():
@@ -361,7 +362,7 @@ with gr.Blocks(title="Excel住所 → Folium(無料タイル・File配信・
361
  run_btn = gr.Button("描画")
362
 
363
  out_html = gr.HTML(label="案内メッセージ")
364
- out_table = gr.Dataframe(label="ジオコーディング結果(住所・CF・緯度・経度・区分)", wrap=True)
365
  out_info = gr.Textbox(label="メタ情報", lines=2)
366
  out_file = gr.File(label="地図HTMLファイル(クリックで開く/ダウンロード)")
367
 
 
1
+ # app.py (Folium + 無料タイル / File配信 / 発電設備区分で色分け / CF表示名を「発電出力(kW)」に統一)
2
  # pip install folium gradio pandas numpy requests openpyxl
3
 
4
  import os
 
141
  ]
142
 
143
  def _build_category_palette(labels: pd.Series) -> dict:
 
144
  labels = pd.Series(labels).astype(str).fillna("その他/不明").replace({"nan":"その他/不明","None":"その他/不明"})
145
  uniq = list(pd.unique(labels))
146
  mapping = {}
 
147
  for i, lab in enumerate(uniq):
148
  if i < len(_BASE_PALETTE):
149
  mapping[lab] = _BASE_PALETTE[i]
150
  else:
 
151
  h = (i * 37) % 360
152
  mapping[lab] = f"hsl({h},70%,45%)"
153
  return mapping
 
178
  center_lon = float(df_valid["lon"].median())
179
  zoom = 6
180
 
181
+ # ベースマップ
182
  m = folium.Map(location=[center_lat, center_lon], zoom_start=zoom, control_scale=True, tiles=None)
183
  for name, url in TILE_CATALOG.items():
184
  folium.TileLayer(tiles=url, name=name, attr=f"© {name}", overlay=False, control=True, max_zoom=20).add_to(m)
185
 
186
+ # カテゴリ列
187
  if cat_col and (cat_col in df_points.columns):
188
  cats = df_points[cat_col].astype("string").fillna("その他/不明")
189
  else:
190
  cat_col = "(区分なし)"
191
  cats = pd.Series(["その他/不明"] * len(df_points))
192
 
 
193
  palette = _build_category_palette(cats)
194
 
195
+ # マーカーのサイズ(内部的には CF を使用
196
  if "CF" in df_valid.columns and df_valid["CF"].notna().any():
197
  cf = df_valid["CF"].clip(lower=0)
198
  cf_norm = (cf - cf.min()) / (cf.max() - cf.min() + 1e-9)
 
210
 
211
  popup_html = (
212
  f"<b>住所:</b> {addr}<br>"
213
+ f"<b>発電出力(kW):</b> {'' if pd.isna(cfv) else cfv}<br>"
214
  f"<b>{cat_col}:</b> {catv}"
215
  )
216
 
 
225
  popup=folium.Popup(popup_html, max_width=320),
226
  ).add_to(m)
227
 
228
+ # 凡例
229
  legend_html = _build_legend_html(cat_col, palette)
230
  m.get_root().html.add_child(Element(legend_html))
231
 
 
234
 
235
  def _rewrite_leaflet_cdn(html_text: str, host: str) -> str:
236
  """
237
+ Folium が出力する Leaflet の CDN(通常 jsDelivr)を必要に応じて置換。
238
+ SRI不整合を避けるため integrity/crossorigin を除去。
239
  """
240
  html_text = re.sub(r'\sintegrity="[^"]+"', "", html_text)
241
  html_text = re.sub(r'\scrossorigin="[^"]+"', "", html_text)
242
 
243
  if host == "jsdelivr":
244
+ return html_text
245
  elif host == "cdnjs":
246
  html_text = html_text.replace(
247
  "https://cdn.jsdelivr.net/npm/leaflet@", "https://cdnjs.cloudflare.com/ajax/libs/leaflet/"
 
277
  def run(excel_file, sheet_name, header_row, address_col, power_col, category_col, use_inet, base_name, leaflet_cdn):
278
  # Excel 読み込み
279
  if excel_file is None or not hasattr(excel_file, "name"):
280
+ table_df = pd.DataFrame(columns=["address_input", "発電出力(kW)", "lat", "lon", category_col or "発電設備区分"])
281
  return ("Excelファイルを指定してください。", table_df, "", None)
282
 
283
  try:
284
  df = pd.read_excel(excel_file.name, sheet_name=sheet_name, header=int(header_row))
285
  except Exception as e:
286
+ empty_df = pd.DataFrame(columns=["address_input", "発電出力(kW)", "lat", "lon", category_col or "発電設備区分"])
287
  return (f"Excel の読み込みに失敗しました: {e}", empty_df, "", None)
288
 
289
  # 列参照(番号/名前の両対応)
 
295
  cat_series = df.iloc[:, category_col] if isinstance(category_col, int) else df[category_col]
296
  except Exception:
297
  cat_series = pd.Series([np.nan] * len(df))
298
+ category_col = "発電設備区分"
299
  else:
300
  cat_series = pd.Series([np.nan] * len(df))
301
  category_col = "発電設備区分"
 
303
  addresses = addr_series.astype(str).tolist()
304
  cfs = cf_series.tolist()
305
 
306
+ # ジオコーディング(内部列名は CF のまま)
307
  geo_df = geocode_with_cache(addresses, cfs, use_internet=bool(use_inet))
308
+
309
+ # 表示用に列名を日本語化(地図生成には内部の CF を使用するので別DataFrame)
310
  table_df = geo_df.copy()
311
  table_df[category_col] = cat_series.values
312
+ display_df = table_df.rename(columns={"CF": "発電出力(kW)"})
313
 
314
  # 地図HTML生成 → CDN書換 → 実ファイル保存 → File出力
315
  try:
316
+ html_text = _build_folium_map_html(table_df, base_name=base_name, cat_col=category_col) # ← CF列は内部名のまま使用
317
  html_text = _rewrite_leaflet_cdn(html_text, host=leaflet_cdn)
318
  map_file_path = _save_map_html_file(html_text)
319
 
320
  msg = (
321
  "✅ 地図HTMLを生成しました。下の **地図HTMLファイル** をクリックして新規タブで開いてください。\n"
322
+ "色=「発電設備区分」、サイズ=「発電出力kWです。"
323
  )
324
+ info = f"ポイント数(有効座標): {int(display_df[['lat','lon']].dropna().shape[0])} / {len(display_df)}"
325
+ # 表示テーブルの列順
326
+ disp_cols = ["address_input", "発電出力(kW)", "lat", "lon", category_col]
327
+ return (msg, display_df[disp_cols], info, map_file_path)
328
  except Exception as e:
329
+ disp_cols = ["address_input", "発電出力(kW)", "lat", "lon", category_col]
330
+ return (f"地図描画に失敗しました: {e}", display_df[disp_cols], "", None)
331
 
332
  # ----------------------------
333
  # Gradio UI
 
336
  gr.Markdown(
337
  "## Excelの住所を国土地理院APIでジオコーディング → Folium(Leaflet)で地図表示(無料タイル・Mapbox不要)\n"
338
  "- 地図は **実ファイル(.html)** として配信します(CSPが厳しい環境でもOK)。\n"
339
+ "- **色=発電設備区分、サイズ=発電出力(kW)**。タイル=地理院/OSM、CDNは必要に応じて切替できます。"
340
  )
341
 
342
  with gr.Row():
 
347
  header_row = gr.Number(label="ヘッダー行番号(0始まり)", value=2, precision=0)
348
 
349
  with gr.Row():
350
+ address_col = gr.Textbox(label="住所列(列名 or 0始まり列番号)", value="発電設備の所在地")
351
+ power_col = gr.Textbox(label="発電出力(kW)の列(列名 or 0始まり列番号)", value="発電出力(kW)")
352
  category_col = gr.Textbox(label="区分列(色分け:列名 or 0始まり列番号)", value="発電設備区分")
353
 
354
  with gr.Row():
 
362
  run_btn = gr.Button("描画")
363
 
364
  out_html = gr.HTML(label="案内メッセージ")
365
+ out_table = gr.Dataframe(label="ジオコーディング結果(住所・発電出力(kW)・緯度・経度・区分)", wrap=True)
366
  out_info = gr.Textbox(label="メタ情報", lines=2)
367
  out_file = gr.File(label="地図HTMLファイル(クリックで開く/ダウンロード)")
368