diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-29 14:44:19 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-29 14:44:19 +0900 |
| commit | 17f6009bfc6589d43e10233040b125675d24c5c8 (patch) | |
| tree | 8f6606e4b5b086e3e61792a6965b27867bed5c18 | |
| parent | cf5468ffaef1b43e5cc529af2d072f1f08af9ba4 (diff) | |
parks: 複数ポリゴンとOSMポリゴンの置き換えに対応
同じ公園名の複数ポリゴンをまとめてズーム・アイコン表示する。import-manual-parks.py に --override-name を追加し、手入力ポリゴンでOSMを置き換えられるようにした。草地広場・多目的広場を一覧の検索・ピクニック絞り込みに反映。
| -rw-r--r-- | scripts/import-manual-parks.py | 48 | ||||
| -rw-r--r-- | scripts/make-park-qgis-kit.py | 10 | ||||
| -rw-r--r-- | src/components/ParkMap.astro | 41 | ||||
| -rw-r--r-- | src/components/ParksTable.astro | 8 | ||||
| -rw-r--r-- | src/data/parks-geojson.ts | 19 |
5 files changed, 111 insertions, 15 deletions
diff --git a/scripts/import-manual-parks.py b/scripts/import-manual-parks.py index 7eb3f1c..1061148 100644 --- a/scripts/import-manual-parks.py +++ b/scripts/import-manual-parks.py @@ -199,9 +199,17 @@ def main(): metavar="公園名", help="指定した公園だけ、OSM側にポリゴンがあっても取り込む(複数指定可)", ) + ap.add_argument( + "--override-name", + action="append", + default=[], + metavar="公園名", + help="手入力ポリゴンでOSM側のポリゴンを置き換える(複数指定可)", + ) ap.add_argument("--update-parks", action="store_true", help="kodaira-parks.json の空の緯度・経度を重心で埋める") args = ap.parse_args() force_names = set(args.force_name or []) + override_names = set(args.override_name or []) if not os.path.exists(args.input): sys.exit(f"入力が見つかりません: {args.input}") @@ -241,6 +249,12 @@ def main(): osm_areas[name] = osm_areas.get(name, 0.0) + area_m2(f["geometry"]) manual_features = manual.get("features") or [] manual_names = {f["properties"].get("our_name") for f in manual_features} + existing_override = {} + for f in manual_features: + p = f.get("properties") or {} + name = p.get("our_name") + if name and str(p.get("override", "")).lower() in ("1", "true"): + existing_override[name] = True errors = [] warnings = [] @@ -321,8 +335,21 @@ def main(): ) continue + override = ( + str(props.get("override", "")).lower() in ("1", "true") + or name in override_names + or existing_override.get(name, False) + ) resolved.append( - {"name": name, "method": method, "geom": geom, "area": area, "lat": lat, "lon": lon} + { + "name": name, + "method": method, + "geom": geom, + "area": area, + "lat": lat, + "lon": lon, + "override": override, + } ) if errors and not args.allow_partial: @@ -365,17 +392,20 @@ def main(): for item in items: geom = item["geom"] geom["coordinates"] = round_coords(geom["coordinates"]) + properties = { + "name": "", + "our_name": name, + "matched": True, + "osm_type": "manual", + "osm_id": osm_id, + "leisure": "park", + } + if item.get("override"): + properties["override"] = True new_features.append( { "type": "Feature", - "properties": { - "name": "", - "our_name": name, - "matched": True, - "osm_type": "manual", - "osm_id": osm_id, - "leisure": "park", - }, + "properties": properties, "geometry": geom, } ) diff --git a/scripts/make-park-qgis-kit.py b/scripts/make-park-qgis-kit.py index b84630e..ce0e2be 100644 --- a/scripts/make-park-qgis-kit.py +++ b/scripts/make-park-qgis-kit.py @@ -293,6 +293,16 @@ py -X utf8 scripts/import-manual-parks.py reference/qgis/manual-parks.geojson `--dry-run` で公園名・面積・台帳面積との倍率・警告を確認してから本実行してください。 台帳面積の 0.7〜1.5 倍を外れると警告が出ます。 + +OSM側にすでにポリゴンがある公園を手入力で置き換える場合(例: 道路で2つに分かれている公園)は、 +同じ `our_name` で複数のポリゴンを描いたうえで、`--force-name` と `--override-name` を付けます。 + +```bash +py -X utf8 scripts/import-manual-parks.py reference/qgis/manual-parks.geojson --force-name いずみ公園 --override-name いずみ公園 --dry-run +py -X utf8 scripts/import-manual-parks.py reference/qgis/manual-parks.geojson --force-name いずみ公園 --override-name いずみ公園 +``` + +`--override-name` を付けた公園は、OSM由来のポリゴンの代わりに手入力ポリゴンだけを地図に表示します。 市外の近隣公園は `py -X utf8 scripts/update-nearby-parks.py` で取り込みます。 取り込み後に `npm run build` で地図と表を確認します。 diff --git a/src/components/ParkMap.astro b/src/components/ParkMap.astro index 7ca85ab..ac85c0e 100644 --- a/src/components/ParkMap.astro +++ b/src/components/ParkMap.astro @@ -629,7 +629,21 @@ lyr._parkName = ourName; lyr._osmName = name; allLayers.push(lyr); - if (ourName) layersByName.set(ourName, lyr); + if (ourName) { + const existing = layersByName.get(ourName); + if (!existing) { + layersByName.set(ourName, lyr); + } else if ( + typeof existing.getLayers === "function" && + typeof existing.addLayer === "function" + ) { + existing.addLayer(lyr); + } else { + const group = L.featureGroup([existing, lyr]); + group._osmName = existing._osmName; + layersByName.set(ourName, group); + } + } if (ourName) { lyr.on("add", () => { @@ -760,6 +774,25 @@ addFacilities(nearbyFacilities as any[], true); publicFacilityLayer.addTo(map); + // 道路で分かれた公園など、同じ公園に複数のポリゴンがある場合は + // いちばん大きいポリゴンの中心にアイコンを置く。 + const centerOfLayer = (lyr: any) => { + if (lyr && typeof lyr.getLayers === "function") { + let best: any = null; + let bestArea = -1; + for (const child of lyr.getLayers()) { + const b = child.getBounds(); + const area = (b.getNorth() - b.getSouth()) * (b.getEast() - b.getWest()); + if (area > bestArea) { + bestArea = area; + best = child; + } + } + if (best) return best.getBounds().getCenter(); + } + return lyr.getBounds().getCenter(); + }; + // 拡大時だけ表示する設備アイコン(トイレ・水飲み場) // 一覧と一致した公園のポリゴン内にだけ表示する。 const facilityLayer = L.layerGroup(); @@ -778,7 +811,7 @@ ); } const clock = (p["時計"] || "").trim(); - if (clock === "○" || clock === "あり" || clock === "有") { + if (clock === "○" || clock === "あり" || clock === "有" || clock === "1") { icons.push( '<span class="park-fac park-fac-clock" title="時計あり" aria-label="時計あり">⏰</span>', ); @@ -795,7 +828,7 @@ ); } if (!icons.length) return; - const center = lyr.getBounds().getCenter(); + const center = centerOfLayer(lyr); L.marker([center.lat, center.lng], { icon: L.divIcon({ className: "park-fac-markers", @@ -854,7 +887,7 @@ ); } if (!icons.length) return; - const center = lyr.getBounds().getCenter(); + const center = centerOfLayer(lyr); L.marker([center.lat, center.lng], { icon: L.divIcon({ className: "park-play-markers", diff --git a/src/components/ParksTable.astro b/src/components/ParksTable.astro index f0864d6..17841cc 100644 --- a/src/components/ParksTable.astro +++ b/src/components/ParksTable.astro @@ -123,13 +123,15 @@ function facTokens(p: Park): string { const ball = p["ボール遊び"]; if (ball && ball !== "禁止") tokens.push("ball"); const clock = (p["時計"] || "").trim(); - if (clock === "○" || clock === "あり" || clock === "有") tokens.push("clock"); + if (clock === "○" || clock === "あり" || clock === "有" || clock === "1") tokens.push("clock"); if (p["南西部指定管理"]) tokens.push("cm"); // ピクニック広場・芝生広場・テーブルのある公園。 const picnicText = `${p["公園名"] || ""} ${p["種別"] || ""} ${p["その他設備"] || ""} ${p["備考"] || ""}`; if ( (p["テーブル"] || "").trim() || p["種別"] === "広場" || + p["草地広場"] || + p["多目的広場"] || picnicText.includes("芝生") || picnicText.includes("ピクニック") || picnicText.includes("広場") @@ -145,6 +147,8 @@ const DETAIL_FIELDS: { key: string; icon: string }[] = [ { key: "ベンチ・椅子", icon: "🪑" }, { key: "テーブル", icon: "🍽" }, { key: "シェルター等", icon: "⛱" }, + { key: "草地広場", icon: "🌿" }, + { key: "多目的広場", icon: "🏟" }, { key: "防災用具", icon: "🧯" }, { key: "遊具", icon: "🎠" }, { key: "時計", icon: "⏰" }, @@ -211,6 +215,8 @@ function searchIndex(p: Park): string { p["トイレ"], p["水飲み場"], p["給水"], + p["草地広場"] ? "草地広場" : "", + p["多目的広場"] ? "多目的広場" : "", p["ボール遊び"], ...DETAIL_FIELDS.map((f) => p[f.key]), ] diff --git a/src/data/parks-geojson.ts b/src/data/parks-geojson.ts index e12d297..9fd9d4f 100644 --- a/src/data/parks-geojson.ts +++ b/src/data/parks-geojson.ts @@ -20,6 +20,23 @@ function isHiddenUnlisted(props: any): boolean { const osmFeatures = ((osm as any).features ?? []).filter( (f: any) => !isHiddenUnlisted(f.properties), ); -const features = [...osmFeatures, ...((manual as any).features ?? [])]; +const manualFeatures = (manual as any).features ?? []; + +// 手入力ポリゴンでOSM側の形を置き換える公園(例: 道路で分かれた公園)。 +// QGISの属性 override=true、または import-manual-parks.py の --override-name で付く。 +const overrideNames = new Set( + manualFeatures + .filter((f: any) => { + const value = f?.properties?.override; + return value === true || value === 1 || value === "true" || value === "1"; + }) + .map((f: any) => f?.properties?.our_name) + .filter(Boolean), +); + +const features = [ + ...osmFeatures.filter((f: any) => !overrideNames.has(f.properties?.our_name)), + ...manualFeatures, +]; export default { type: "FeatureCollection", features }; |
