aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/update-nearby-facilities.py
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-25 12:24:24 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-25 12:24:24 +0900
commita01062f43d36112c00d5c007ac9d488c14d8fb0d (patch)
treeb99ddac9928e02b9cd07d372fa42842a348bfb61 /scripts/update-nearby-facilities.py
parenta71727538f65ab4f10f5239550be82590f9d47d1 (diff)
parks: アイコン・フィルタ・編集画面を更新し、公開を一時停止
- 遊具アイコンをSVGRepo素材に差し替え、施設種別をアイコン表示 - フィルタに種類・遊具・ピクニックを追加し、適用時の地図移動を停止 - 現在地ボタンを追跡トグルに変更し、初回以外は地図を動かさない - 編集画面を市内公園・周辺公園・公共施設の4タブに拡張 - 公園ページをドラフト化し、サイドバーとPWAの公開を停止
Diffstat (limited to 'scripts/update-nearby-facilities.py')
-rw-r--r--scripts/update-nearby-facilities.py179
1 files changed, 179 insertions, 0 deletions
diff --git a/scripts/update-nearby-facilities.py b/scripts/update-nearby-facilities.py
new file mode 100644
index 0000000..dcef5d6
--- /dev/null
+++ b/scripts/update-nearby-facilities.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+"""小平市の境界から500m以内にある近隣市の公共施設をOpenStreetMapから抽出する。
+
+使い方:
+ py -X utf8 scripts/update-nearby-facilities.py
+
+出力:
+ src/data/kodaira-nearby-facilities.json
+
+対象: amenity=library / community_centre / townhall / arts_centre / theatre
+小平市内の施設は kodaira-facilities.json 側にあるため除外する。
+"""
+import importlib.util
+import json
+import os
+import urllib.parse
+import urllib.request
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+OUT = os.path.join(ROOT, "src", "data", "kodaira-nearby-facilities.json")
+MAX_DIST_M = 500.0
+BBOX = (35.68, 139.39, 35.76, 139.56)
+AMENITIES = "library|community_centre|townhall|arts_centre|theatre"
+EXCLUDE_NAME_PARTS = ("付属図書館", "コミュニティ サロン")
+
+ENDPOINTS = [
+ "https://overpass-api.de/api/interpreter",
+ "https://overpass.kumi.systems/api/interpreter",
+ "https://overpass.private.coffee/api/interpreter",
+]
+
+
+def load_module(path, name):
+ spec = importlib.util.spec_from_file_location(name, path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+def fetch_overpass():
+ south, west, north, east = BBOX
+ query = (
+ "[out:json][timeout:180];"
+ "("
+ f'node["amenity"~"{AMENITIES}"]({south},{west},{north},{east});'
+ f'way["amenity"~"{AMENITIES}"]({south},{west},{north},{east});'
+ f'relation["amenity"~"{AMENITIES}"]({south},{west},{north},{east});'
+ ");"
+ "out tags center;"
+ )
+ last_err = None
+ for endpoint in ENDPOINTS:
+ for attempt in range(2):
+ try:
+ req = urllib.request.Request(
+ endpoint,
+ data=query.encode("utf-8"),
+ headers={"User-Agent": "kodaira-nearby-facilities/1.0 (yasutakeyohei.com)"},
+ )
+ with urllib.request.urlopen(req, timeout=240) as res:
+ data = json.load(res)
+ if "elements" not in data:
+ raise ValueError("レスポンスに elements がありません")
+ print(f" {endpoint} から {len(data['elements'])} 件取得")
+ return data["elements"]
+ except Exception as exc: # noqa: BLE001
+ last_err = exc
+ print(f" {endpoint} 失敗 ({attempt + 1}/2): {exc}")
+ raise SystemExit(f"Overpass APIの取得に失敗しました(出力は作成していません): {last_err}")
+
+
+def facility_type(tags):
+ amenity = tags.get("amenity", "")
+ name = tags.get("name", "")
+ if amenity == "library":
+ return "図書館"
+ if amenity == "community_centre":
+ return "児童館" if "児童館" in name else "公民館"
+ if amenity == "townhall":
+ return "市役所支所"
+ if amenity in ("arts_centre", "theatre"):
+ return "文化施設"
+ return ""
+
+
+def address_of(tags):
+ full = (tags.get("addr:full") or "").strip()
+ if full:
+ return full
+ city = (tags.get("addr:city") or "").strip()
+ neighbourhood = (
+ tags.get("addr:neighbourhood")
+ or tags.get("addr:suburb")
+ or tags.get("addr:quarter")
+ or ""
+ ).strip()
+ street = (tags.get("addr:street") or "").strip()
+ block = (tags.get("addr:block_number") or "").strip()
+ number = (tags.get("addr:housenumber") or "").strip()
+ if block and number:
+ lot = f"{block}-{number}"
+ else:
+ lot = block or number
+ if street:
+ return f"{city}{street}{lot}"
+ if neighbourhood:
+ return f"{city}{neighbourhood}{lot}"
+ # 町名がない番地だけでは住所にならないため、市区町村だけにする。
+ return city
+
+
+def main():
+ print("Overpass API から近隣施設を取得中...")
+ elements = fetch_overpass()
+
+ np = load_module(os.path.join(ROOT, "scripts", "update-nearby-parks.py"), "nearby_parks")
+ boundary_ll = np.load_boundary()
+ lons = [lon for polygon in boundary_ll for ring in polygon for lon, _ in ring]
+ lats = [lat for polygon in boundary_ll for ring in polygon for _, lat in ring]
+ to_xy = np.projector(sum(lats) / len(lats), sum(lons) / len(lons))
+ boundary_xy = [
+ [[to_xy(lon, lat) for lon, lat in ring] for ring in polygon]
+ for polygon in boundary_ll
+ ]
+ boundary = np.BoundaryIndex(boundary_xy)
+
+ features = []
+ for el in elements:
+ tags = el.get("tags") or {}
+ name = (tags.get("name") or "").strip()
+ if not name:
+ continue
+ if any(part in name for part in EXCLUDE_NAME_PARTS):
+ continue
+ kind = facility_type(tags)
+ if not kind:
+ continue
+ center = el.get("center") or {}
+ lat = el.get("lat", center.get("lat"))
+ lon = el.get("lon", center.get("lon"))
+ if lat is None or lon is None:
+ continue
+ x, y = to_xy(lon, lat)
+ if boundary.contains(x, y):
+ continue
+ distance = boundary.min_distance([(x, y)])
+ if distance > MAX_DIST_M:
+ continue
+ features.append(
+ {
+ "name": name,
+ "type": kind,
+ "address": address_of(tags),
+ "hours": (tags.get("opening_hours") or "").strip(),
+ "closed": "",
+ "toilet": "未確認",
+ "water": "未確認",
+ "note": f"市外・小平市境から約{int(round(distance))}m(OpenStreetMap)",
+ "source": f"https://www.openstreetmap.org/{el.get('type')}/{el.get('id')}",
+ "lat": lat,
+ "lon": lon,
+ "geocode": "OSM",
+ "city": (tags.get("addr:city") or "").strip(),
+ "distance_m": int(round(distance)),
+ "osm_type": el.get("type"),
+ "osm_id": el.get("id"),
+ "nearby": True,
+ }
+ )
+
+ features.sort(key=lambda f: (f["distance_m"], f["name"]))
+ with open(OUT, "w", encoding="utf-8") as fh:
+ json.dump(features, fh, ensure_ascii=False, indent=2)
+ fh.write("\n")
+ print(f"nearby facilities {len(features)} -> {OUT}")
+
+
+if __name__ == "__main__":
+ main()