From a71727538f65ab4f10f5239550be82590f9d47d1 Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Sat, 24 Oct 2026 12:29:24 +0900 Subject: parks: 公園一覧と地図、公共施設の情報を更新 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 近隣公園のクリックを市内公園と同じ挙動にし、Googleマップを公園名で開く - 公共施設のトイレを○にし、ルネこだいらを追加 - 未取得だった施設の座標を追加し、再ジオコーディング時の上書きを防止 - 手入力ポリゴン、近隣公園、PWA、ライセンス表示を追加 --- src/components/FacilitiesTable.astro | 186 +++++++++++++++++++++++++ src/components/ParkMap.astro | 261 +++++++++++++++++++++++++++++++---- src/components/ParksTable.astro | 114 +++++++++++++-- src/components/PwaRegister.astro | 19 +++ 4 files changed, 535 insertions(+), 45 deletions(-) create mode 100644 src/components/FacilitiesTable.astro create mode 100644 src/components/PwaRegister.astro (limited to 'src/components') diff --git a/src/components/FacilitiesTable.astro b/src/components/FacilitiesTable.astro new file mode 100644 index 0000000..2974581 --- /dev/null +++ b/src/components/FacilitiesTable.astro @@ -0,0 +1,186 @@ +--- +// 地域センター・公民館・図書館・市役所支所・文化施設の一覧。 +// トイレは市の公共施設として基本的に利用できる。給水は公式ページに明記が少ないため「未確認」が多い。 +import facilitiesData from "@/data/kodaira-facilities.json"; + +type Facility = { + name: string; + type: string; + address: string; + hours: string; + closed: string; + toilet: string; + water: string; + note: string; + source: string; +}; + +const facilities = facilitiesData as Facility[]; +const types = ["地域センター", "公民館", "図書館", "市役所支所", "文化施設"]; +const mapsUrl = (name: string) => + `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`小平市${name}`)}`; +--- + +
| 施設名 | +種別 | +住所 | +開館時間 | +休館日 | +トイレ | +給水 | +備考 | +
|---|---|---|---|---|---|---|---|
| + {f.source ? ( + {f.name} + ) : ( + f.name + )} + | +{f.type} | ++ + {f.address || "—"} + + | +{f.hours || "未確認"} | +{f.closed || "未確認"} | +{f.toilet || "未確認"} | +{f.water || "未確認"} | +{f.note} | +
+ トイレは市の公共施設として基本的に利用できます(○)。給水は公式ページに明記が少ないため「未確認」が多いです。最新の情報は各施設の公式ページで確認してください。地図の位置は住所・名称からの概略です。 +
+
- 公園の範囲を赤で表示しています。地図を拡大すると見やすくなります。トイレと水飲み場のアイコンは、地図を拡大すると表示されます。OpenStreetMapに公園として登録されている範囲を表示しているため、すべての公園を網羅しているわけではありません。現在地は端末内でのみ利用し、サーバーには送信しません。地図データ © OpenStreetMapOpenStreetMap contributors',
}).addTo(map);
+ const FullscreenControl = L.Control.extend({
+ onAdd: () => {
+ const container = L.DomUtil.create("div", "leaflet-bar park-map-fullscreen");
+ const button = L.DomUtil.create("button", "", container);
+ button.type = "button";
+ button.title = "全画面表示";
+ button.setAttribute("aria-label", "全画面表示");
+ button.innerHTML = "⛶";
+ L.DomEvent.disableClickPropagation(container);
+ L.DomEvent.on(button, "click", (ev: Event) => {
+ L.DomEvent.stop(ev);
+ const target = map.getContainer();
+ if (!document.fullscreenElement) {
+ target.requestFullscreen?.().catch(() => {});
+ } else {
+ document.exitFullscreen?.().catch(() => {});
+ }
+ });
+ const update = () => {
+ const active = document.fullscreenElement === map.getContainer();
+ button.innerHTML = active ? "✕" : "⛶";
+ button.title = active ? "全画面を終了" : "全画面表示";
+ button.setAttribute("aria-label", active ? "全画面を終了" : "全画面表示");
+ map.invalidateSize();
+ };
+ document.addEventListener("fullscreenchange", update);
+ return container;
+ },
+ });
+ if (document.fullscreenEnabled) {
+ new FullscreenControl({ position: "topright" }).addTo(map);
+ }
+
const layersByName = new Map
${f.type}
${f.hours || ""}${f.closed ? `
休: ${f.closed}` : ""}
トイレ: ${f.toilet || "未確認"} / 給水: ${f.water || "未確認"}`,
+ )
+ .addTo(publicFacilityLayer);
+ });
+ publicFacilityLayer.addTo(map);
+
+ L.control
+ .layers(
+ null,
+ {
+ "市外の近い公園(500m以内)": nearbyLayer,
+ "公共施設": publicFacilityLayer,
+ },
+ { collapsed: false, position: "topright" },
+ )
+ .addTo(map);
+
// 拡大時だけ表示する設備アイコン(トイレ・水飲み場)
// 一覧と一致した公園のポリゴン内にだけ表示する。
const facilityLayer = L.layerGroup();
@@ -459,6 +608,7 @@
iconAnchor: [0, 0],
}),
riseOnHover: false,
+ interactive: false,
}).addTo(facilityLayer);
});
@@ -492,24 +642,59 @@
};
fitAll();
- const focusPark = (name: string) => {
+ const focusPark = (name: string, key = "") => {
+ if (key) {
+ const nearbyHit = nearbyEntries.find((entry) => entry.key === key);
+ if (nearbyHit) {
+ map.fitBounds(nearbyHit.layer.getBounds(), { padding: [40, 40], maxZoom: 17 });
+ showSelected(
+ name,
+ name,
+ true,
+ nearbyHit.city || "市外",
+ nearbyMapsQuery(name, nearbyHit.city),
+ );
+ return true;
+ }
+ }
const target = layersByName.get(name);
- if (!target) return false;
- map.fitBounds(target.getBounds(), { padding: [40, 40], maxZoom: 17 });
- showSelected(
- name,
- target._osmName || name,
- true,
- ownerByName.get(name) || "",
- );
- return true;
+ if (target) {
+ map.fitBounds(target.getBounds(), { padding: [40, 40], maxZoom: 17 });
+ showSelected(
+ name,
+ target._osmName || name,
+ true,
+ ownerByName.get(name) || "",
+ );
+ return true;
+ }
+ const nearbyTargets = nearbyEntries
+ .filter((entry) => entry.name === name)
+ .map((entry) => entry.layer);
+ if (nearbyTargets.length) {
+ const group = L.featureGroup(nearbyTargets);
+ map.fitBounds(group.getBounds(), { padding: [40, 40], maxZoom: 17 });
+ const info = nearbyInfoByName.get(name);
+ const city = String(info?.city || "").trim();
+ showSelected(name, name, true, city || "市外", nearbyMapsQuery(name, city));
+ return true;
+ }
+ return false;
};
document.addEventListener("park:focus", (e: Event) => {
- const name = (e as CustomEvent).detail?.name;
- if (!name || !layersByName.has(name)) return;
+ const detail = (e as CustomEvent).detail || {};
+ const name = detail.name;
+ if (!name) return;
+ const key = detail.key || "";
+ if (
+ !key &&
+ !layersByName.has(name) &&
+ !nearbyEntries.some((entry) => entry.name === name)
+ )
+ return;
el.scrollIntoView({ behavior: "smooth", block: "center" });
- focusPark(name);
+ focusPark(name, key);
});
document.addEventListener("park:filter", (e: Event) => {
@@ -532,6 +717,9 @@
lyr.setStyle(name ? MATCHED_STYLE : UNMATCHED_STYLE);
}
});
+ nearbyEntries.forEach(({ name, layer: lyr }) => {
+ lyr.setStyle(name && names.has(name) ? NEARBY_SELECTED_STYLE : NEARBY_STYLE);
+ });
return;
}
@@ -549,6 +737,19 @@
}
});
+ nearbyEntries.forEach(({ name, layer: lyr }) => {
+ if (!active) {
+ lyr.setStyle(NEARBY_STYLE);
+ return;
+ }
+ if (name && names.has(name)) {
+ lyr.setStyle(NEARBY_STYLE);
+ visible.push(lyr);
+ } else {
+ lyr.setStyle(NEARBY_DIM_STYLE);
+ }
+ });
+
if (active && visible.length && visible.length < 30) {
const bounds = L.featureGroup(visible).getBounds();
if (bounds.isValid()) {
diff --git a/src/components/ParksTable.astro b/src/components/ParksTable.astro
index 6f8fdbd..bea8ff2 100644
--- a/src/components/ParksTable.astro
+++ b/src/components/ParksTable.astro
@@ -5,16 +5,74 @@
// 絞り込み・並べ替え・カード表示は利用者の端末で実行する。
import parksData from "@/data/kodaira-parks.json";
import osmData from "@/data/parks-geojson";
+import nearbyData from "@/data/kodaira-nearby-parks.json";
type Park = Record