Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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>
|
| 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", "
|
| 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", "
|
| 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 |
-
"色=「発電設備区分」
|
| 325 |
)
|
| 326 |
-
info = f"ポイント数(有効座標): {int(
|
| 327 |
-
|
|
|
|
|
|
|
| 328 |
except Exception as e:
|
| 329 |
-
|
|
|
|
| 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 |
-
"-
|
| 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
|
| 350 |
-
power_col
|
| 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="ジオコーディング結果(住所・
|
| 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 |
|