aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/fill-nearby-cities.py
blob: 9f2a087b9d522590ad846951245b5baa9bf50acd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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()