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/ParkMap.astro | 261 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 231 insertions(+), 30 deletions(-) (limited to 'src/components/ParkMap.astro') diff --git a/src/components/ParkMap.astro b/src/components/ParkMap.astro index 8537af2..1c75bee 100644 --- a/src/components/ParkMap.astro +++ b/src/components/ParkMap.astro @@ -13,12 +13,14 @@
- 公園の範囲を赤で表示しています。地図を拡大すると見やすくなります。トイレと水飲み場のアイコンは、地図を拡大すると表示されます。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()) {
--
cgit v1.3.1