aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/components/ParkMap.astro168
-rw-r--r--src/components/ParksFilters.astro9
2 files changed, 136 insertions, 41 deletions
diff --git a/src/components/ParkMap.astro b/src/components/ParkMap.astro
index c67b4b3..8e7f35c 100644
--- a/src/components/ParkMap.astro
+++ b/src/components/ParkMap.astro
@@ -33,14 +33,17 @@
border-radius: 0;
}
.park-map.is-maximized {
- position: fixed;
- inset: 0;
- width: 100vw;
- height: 100dvh;
- margin: 0;
- border: 0;
- border-radius: 0;
- z-index: 10000;
+ position: fixed !important;
+ inset: 0 !important;
+ width: auto !important;
+ height: auto !important;
+ max-width: none !important;
+ max-height: none !important;
+ margin: 0 !important;
+ border: 0 !important;
+ border-radius: 0 !important;
+ transform: none !important;
+ z-index: 2147483000 !important;
}
:global(body.park-map-maximized) {
overflow: hidden;
@@ -116,6 +119,42 @@
font-size: 0.78rem;
cursor: pointer;
}
+ :global(.park-popup) {
+ font-size: 0.82rem;
+ line-height: 1.5;
+ color: #1e293b;
+ }
+ :global(.park-popup strong) {
+ font-size: 0.95rem;
+ }
+ :global(.park-popup-owner) {
+ margin-left: 0.4rem;
+ font-size: 0.7rem;
+ color: #555;
+ }
+ :global(.park-popup-meta) {
+ margin-top: 0.15rem;
+ color: #444;
+ font-size: 0.78rem;
+ }
+ :global(.park-popup-fac) {
+ margin-top: 0.1rem;
+ color: #2e7d32;
+ font-size: 0.78rem;
+ }
+ :global(.park-popup-links) {
+ display: flex;
+ gap: 0.8rem;
+ margin-top: 0.35rem;
+ }
+ :global(.park-popup-links a) {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.2rem;
+ color: #0b57d0;
+ font-weight: 700;
+ }
+
:global(.park-name-marker) {
background: none;
border: 0;
@@ -124,7 +163,7 @@
position: absolute;
left: 0;
top: 0;
- transform: translate(-50%, -170%);
+ transform: translate(-50%, 60%);
background: rgba(255, 255, 255, 0.92);
color: #1b5e20;
font-size: 11px;
@@ -186,10 +225,12 @@
@keyframes park-blink-pulse {
0%,
100% {
- fill-opacity: 0.45;
+ fill-opacity: 0.6;
+ stroke-opacity: 1;
}
50% {
- fill-opacity: 0.12;
+ fill-opacity: 0.08;
+ stroke-opacity: 0.3;
}
}
@media (prefers-reduced-motion: reduce) {
@@ -838,7 +879,12 @@
window.setTimeout(() => map.invalidateSize(), 60);
window.setTimeout(() => map.invalidateSize(), 350);
};
+ let maxHome: { parent: Node; next: Node | null } | null = null;
const enterCssMax = () => {
+ if (!maxHome) {
+ maxHome = { parent: target.parentNode as Node, next: target.nextSibling };
+ document.body.appendChild(target);
+ }
target.classList.add("is-maximized");
document.body.classList.add("park-map-maximized");
update();
@@ -846,6 +892,10 @@
const exitCssMax = () => {
target.classList.remove("is-maximized");
document.body.classList.remove("park-map-maximized");
+ if (maxHome) {
+ maxHome.parent.insertBefore(target, maxHome.next);
+ maxHome = null;
+ }
update();
};
@@ -917,6 +967,40 @@
? "民間"
: owner;
+ const escapePopup = (value: string) =>
+ value.replace(/[&<>"']/g, (ch) =>
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch] as string,
+ );
+ const buildPopup = (ourName: string, osmName: string, owner = "", mapsQuery = "") => {
+ const rec = parkDataByName.get(ourName);
+ const name = escapePopup(ourName || osmName || "");
+ const addr = escapePopup(rec?.["住所"] || "");
+ const areaRaw = Number(rec?.["面積㎡"]);
+ const area =
+ Number.isFinite(areaRaw) && areaRaw > 0
+ ? `${areaRaw.toLocaleString("ja-JP")}㎡`
+ : "";
+ const type = escapePopup(rec?.["種別"] || "");
+ const facs = [
+ rec?.["トイレ"] ? "トイレ" : "",
+ rec?.["水飲み場"] ? "水飲み" : "",
+ rec?.["遊具"] ? "遊具" : "",
+ rec?.["シェルター等"] ? "東屋等" : "",
+ ]
+ .filter(Boolean)
+ .join("・");
+ const query = mapsQuery || (ourName ? `小平市${ourName}` : "");
+ const dest =
+ rec?.["緯度"] && rec?.["経度"]
+ ? `${rec["緯度"]},${rec["経度"]}`
+ : query;
+ const maps =
+ rec?.["地図URL"] ||
+ `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(query)}`;
+ const route = `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(dest)}&travelmode=walking`;
+ return `<div class="park-popup"><strong>${name}</strong>${owner ? `<span class="park-popup-owner">${escapePopup(owner)}</span>` : ""}${addr || area || type ? `<div class="park-popup-meta">${[addr, area, type].filter(Boolean).join(" ・ ")}</div>` : ""}${facs ? `<div class="park-popup-fac">${facs}</div>` : ""}<div class="park-popup-links"><a href="${maps}" target="_blank" rel="noopener">🗺Gマップ</a><a href="${route}" target="_blank" rel="noopener">🚶経路</a></div></div>`;
+ };
+
const filterName = (name: string, scroll = false) => {
document.dispatchEvent(
new CustomEvent("park:filter-name", { detail: { name, scroll } }),
@@ -1024,13 +1108,25 @@
const path = lyr.getElement?.();
if (path) path.setAttribute("data-park", ourName);
});
+ lyr.bindPopup(buildPopup(ourName, name, ownerByName.get(ourName) || ""), {
+ maxWidth: 300,
+ });
+ lyr.on("popupopen", () => {
+ lyr.setStyle(SELECTED_STYLE);
+ markSelected(lyr, true);
+ markBlink(lyr, false);
+ lyr.bringToFront?.();
+ });
+ lyr.on("popupclose", () => {
+ lyr.setStyle(MATCHED_STYLE);
+ markSelected(lyr, false);
+ markBlink(lyr, true);
+ });
lyr.on("click", (ev: any) => {
if (ev?.originalEvent) L.DomEvent.stopPropagation(ev.originalEvent);
- highlightOnly(ourName);
document.dispatchEvent(
new CustomEvent("park:select-row", { detail: { name: ourName } }),
);
- showSelected(ourName, name, true, ownerByName.get(ourName) || "");
});
} else {
// 一覧にない公園・緑地は表示だけ。クリックには反応させない。
@@ -1097,14 +1193,28 @@
const key = `${p.osm_type || ""}/${p.osm_id || ""}`;
lyr._isPoint = feature.geometry?.type === "Point";
if (name) nearbyEntries.push({ name, layer: lyr, city, key });
+ if (name) {
+ lyr.bindPopup(buildPopup(name, name, "", nearbyMapsQuery(name, city)), {
+ maxWidth: 300,
+ });
+ lyr.on("popupopen", () => {
+ lyr.setStyle(NEARBY_SELECTED_STYLE);
+ markSelected(lyr, true);
+ markBlink(lyr, false);
+ lyr.bringToFront?.();
+ });
+ lyr.on("popupclose", () => {
+ lyr.setStyle(nearbyBaseStyle(lyr));
+ markSelected(lyr, false);
+ markBlink(lyr, true);
+ });
+ }
lyr.on("click", (ev: any) => {
if (ev?.originalEvent) L.DomEvent.stopPropagation(ev.originalEvent);
if (!name) return;
- highlightOnly(name);
document.dispatchEvent(
new CustomEvent("park:select-row", { detail: { name } }),
);
- showSelected(name, name, true, city || "市外", nearbyMapsQuery(name, city));
});
},
}).addTo(map);
@@ -1339,12 +1449,12 @@
let facilityLabelsShown = false;
const updateFacilities = () => {
- if (map.getZoom() >= 17) {
+ if (map.getZoom() >= 16) {
if (!map.hasLayer(facilityLayer)) facilityLayer.addTo(map);
} else if (map.hasLayer(facilityLayer)) {
map.removeLayer(facilityLayer);
}
- if (map.getZoom() >= 17) {
+ if (map.getZoom() >= 16) {
if (!map.hasLayer(playLayer)) playLayer.addTo(map);
} else if (map.hasLayer(playLayer)) {
map.removeLayer(playLayer);
@@ -1431,27 +1541,16 @@
if (nearbyHit) {
const group = L.featureGroup([nearbyHit.layer]);
map.fitBounds(group.getBounds(), { padding: [40, 40], maxZoom: 17 });
- highlightOnly(name);
- showSelected(
- name,
- name,
- true,
- nearbyHit.city || "市外",
- nearbyMapsQuery(name, nearbyHit.city),
- );
+ nearbyHit.layer.openPopup?.();
return true;
}
}
const target = layersByName.get(name);
if (target) {
map.fitBounds(target.getBounds(), { padding: [40, 40], maxZoom: 17 });
- highlightOnly(name);
- showSelected(
- name,
- target._osmName || name,
- true,
- ownerByName.get(name) || "",
- );
+ const targetLayer =
+ typeof target.getLayers === "function" ? target.getLayers()[0] : target;
+ targetLayer?.openPopup?.();
return true;
}
const nearbyTargets = nearbyEntries
@@ -1460,10 +1559,7 @@
if (nearbyTargets.length) {
const group = L.featureGroup(nearbyTargets);
map.fitBounds(group.getBounds(), { padding: [40, 40], maxZoom: 17 });
- highlightOnly(name);
- const info = nearbyInfoByName.get(name);
- const city = String(info?.city || "").trim();
- showSelected(name, name, true, city || "市外", nearbyMapsQuery(name, city));
+ nearbyTargets[0].openPopup?.();
return true;
}
return false;
diff --git a/src/components/ParksFilters.astro b/src/components/ParksFilters.astro
index 172edf0..4890741 100644
--- a/src/components/ParksFilters.astro
+++ b/src/components/ParksFilters.astro
@@ -62,7 +62,6 @@ const facilityTypes = [
<span class="filter-icon" set:html={PARK_ICONS.picnic.svg} />緑地広場
</label>
<label><input type="checkbox" data-fac-filter value="clock" />⏰時計</label>
- <label><input type="checkbox" data-fac-filter value="toy" />遊具あり</label>
<label>
<input type="checkbox" data-fac-filter value="sand" />
<span class="filter-icon" set:html={PARK_ICONS.sand.svg} />砂場
@@ -95,10 +94,10 @@ const facilityTypes = [
<input type="checkbox" data-fac-filter value="complex" />
<span class="filter-icon" set:html={PARK_ICONS.playground.svg} />複合遊具
</label>
- <label><input type="checkbox" data-fac-filter value="basket" />バスケゴール</label>
- <label><input type="checkbox" data-fac-filter value="shelter" />東屋/シェルター</label>
- <label><input type="checkbox" data-fac-filter value="health" />健康遊具</label>
- <label><input type="checkbox" data-fac-filter value="special" />特殊遊具</label>
+ <label><input type="checkbox" data-fac-filter value="basket" />🏀バスケゴール</label>
+ <label><input type="checkbox" data-fac-filter value="shelter" />⛱東屋/シェルター</label>
+ <label><input type="checkbox" data-fac-filter value="health" />💪健康遊具</label>
+ <label><input type="checkbox" data-fac-filter value="special" />✨特殊遊具</label>
</fieldset>
<label class="parks-fire-toggle" title="消火栓・防火水槽を地図に表示する">
<input type="checkbox" data-show-fire />