aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/fill-nearby-cities.py
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-25 21:29:16 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-25 21:29:16 +0900
commitfa73b6a9f36268986af68192f65978539f4d059c (patch)
tree6a4f27d54823d35da29f68ef37b93bba37f71cf2 /scripts/fill-nearby-cities.py
parenta01062f43d36112c00d5c007ac9d488c14d8fb0d (diff)
parks: シェルター等の統合と編集画面の刷新
パーゴラ・東屋を「シェルター等」に統合し、編集画面に確認チェック・遊具別入力・時計・選択行ハイライトを追加した。周辺公園と周辺施設の自治体名を補完し、公園データの文字化けを修正した。
Diffstat (limited to 'scripts/fill-nearby-cities.py')
-rw-r--r--scripts/fill-nearby-cities.py129
1 files changed, 129 insertions, 0 deletions
diff --git a/scripts/fill-nearby-cities.py b/scripts/fill-nearby-cities.py
new file mode 100644
index 0000000..9f2a087
--- /dev/null
+++ b/scripts/fill-nearby-cities.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python3
+"""周辺公園・周辺施設の city が空のとき、Nominatimで市区町村を補完する。
+
+使い方:
+ py -X utf8 scripts/fill-nearby-cities.py
+
+- src/data/kodaira-nearby-parks.json の properties.city
+- src/data/kodaira-nearby-facilities.json の city
+
+既に city が入っている行は変更しない。Nominatimの利用ポリシーに従い、
+逆ジオコーディングは1リクエストあたり約1.1秒待つ。
+"""
+import json
+import os
+import re
+import time
+import urllib.parse
+import urllib.request
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+PARKS = os.path.join(ROOT, "src", "data", "kodaira-nearby-parks.json")
+FACILITIES = os.path.join(ROOT, "src", "data", "kodaira-nearby-facilities.json")
+UA = "yasutakeyohei.com nearby city fill (https://yasutakeyohei.com/)"
+
+
+def centroid(feature):
+ geom = feature.get("geometry") or {}
+ if geom.get("type") == "Point":
+ lon, lat = geom["coordinates"][:2]
+ return lat, lon
+ if geom.get("type") == "Polygon":
+ ring = geom.get("coordinates", [[]])[0]
+ elif geom.get("type") == "MultiPolygon":
+ ring = geom.get("coordinates", [[[]]])[0][0]
+ else:
+ return None, None
+ if not ring:
+ return None, None
+ lon = sum(p[0] for p in ring) / len(ring)
+ lat = sum(p[1] for p in ring) / len(ring)
+ return lat, lon
+
+
+def city_from_text(text):
+ text = text or ""
+ # 「東村山市」のように市区町村名の中に「村」が含まれるため、市→町→村の順で探す。
+ for suffix in ("市", "町", "村"):
+ match = re.search(r"(?:東京都)?([^\s0-9]+?" + suffix + r")", text)
+ if match:
+ return match.group(1)
+ return ""
+
+
+def reverse_city(lat, lon):
+ params = urllib.parse.urlencode(
+ {
+ "format": "jsonv2",
+ "lat": lat,
+ "lon": lon,
+ "zoom": 10,
+ "addressdetails": 1,
+ "accept-language": "ja",
+ }
+ )
+ url = "https://nominatim.openstreetmap.org/reverse?" + params
+ req = urllib.request.Request(url, headers={"User-Agent": UA})
+ try:
+ with urllib.request.urlopen(req, timeout=30) as res:
+ data = json.load(res)
+ except Exception as exc: # noqa: BLE001
+ print(f" warning: reverse failed ({lat},{lon}): {exc}")
+ return ""
+ finally:
+ time.sleep(1.1)
+ address = data.get("address") or {}
+ for key in ("city", "town", "village", "municipality"):
+ value = (address.get(key) or "").strip()
+ if value:
+ return value
+ return ""
+
+
+def main():
+ parks_data = json.load(open(PARKS, encoding="utf-8"))
+ parks_changed = 0
+ for feature in parks_data.get("features", []):
+ props = feature.setdefault("properties", {})
+ if (props.get("city") or "").strip():
+ continue
+ lat, lon = centroid(feature)
+ if lat is None or lon is None:
+ continue
+ city = reverse_city(lat, lon)
+ if city:
+ props["city"] = city
+ parks_changed += 1
+ print(f" park: {props.get('name')} -> {city}")
+
+ if parks_changed:
+ with open(PARKS, "w", encoding="utf-8") as fh:
+ json.dump(parks_data, fh, ensure_ascii=False, separators=(",", ":"))
+ fh.write("\n")
+
+ facilities = json.load(open(FACILITIES, encoding="utf-8"))
+ facilities_changed = 0
+ for item in facilities:
+ if (item.get("city") or "").strip():
+ continue
+ city = city_from_text(item.get("address", "")) or city_from_text(item.get("name", ""))
+ if not city:
+ lat = item.get("lat")
+ lon = item.get("lon")
+ if lat is not None and lon is not None:
+ city = reverse_city(lat, lon)
+ if city:
+ item["city"] = city
+ facilities_changed += 1
+ print(f" facility: {item.get('name')} -> {city}")
+
+ if facilities_changed:
+ with open(FACILITIES, "w", encoding="utf-8") as fh:
+ json.dump(facilities, fh, ensure_ascii=False, indent=2)
+ fh.write("\n")
+
+ print(f"updated parks {parks_changed}, facilities {facilities_changed}")
+
+
+if __name__ == "__main__":
+ main()