From a01062f43d36112c00d5c007ac9d488c14d8fb0d Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:24:24 +0900 Subject: parks: アイコン・フィルタ・編集画面を更新し、公開を一時停止 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 遊具アイコンをSVGRepo素材に差し替え、施設種別をアイコン表示 - フィルタに種類・遊具・ピクニックを追加し、適用時の地図移動を停止 - 現在地ボタンを追跡トグルに変更し、初回以外は地図を動かさない - 編集画面を市内公園・周辺公園・公共施設の4タブに拡張 - 公園ページをドラフト化し、サイドバーとPWAの公開を停止 --- src/components/ParksTable.astro | 260 +++++++++++++++++++++------------------- 1 file changed, 135 insertions(+), 125 deletions(-) (limited to 'src/components/ParksTable.astro') diff --git a/src/components/ParksTable.astro b/src/components/ParksTable.astro index bea8ff2..65b8412 100644 --- a/src/components/ParksTable.astro +++ b/src/components/ParksTable.astro @@ -1,78 +1,9 @@ --- -// 小平市の公園一覧(/kodaira-data/parks/)を表示する。 -// データは src/data/kodaira-parks.json(市立332件+市立以外)。 +// 小平市の公園・周辺の公園・公共施設の一覧(/kodaira-data/parks/)を表示する。 +// データは src/data/parks-rows.ts で組み立てる。 // DOM を抑えるため、設備は1セルにまとめ、アイコンは CSS の擬似要素で描く。 // 絞り込み・並べ替え・カード表示は利用者の端末で実行する。 -import parksData from "@/data/kodaira-parks.json"; -import osmData from "@/data/parks-geojson"; -import nearbyData from "@/data/kodaira-nearby-parks.json"; - -type Park = Record; - -function nearbyCentroid(feature: any): [number, number] { - const g = feature.geometry; - const ring = g.type === "Polygon" ? g.coordinates[0] : g.coordinates[0][0]; - if (!ring?.length) return [0, 0]; - let lat = 0; - let lon = 0; - for (const [x, y] of ring) { - lon += x; - lat += y; - } - return [lat / ring.length, lon / ring.length]; -} - -// 市境500m以内の市外公園。小平市の公園と同じ表に「市外」バッジ付きで並べる。 -const nearbyRows: Park[] = ((nearbyData as any).features ?? []).map((f: any) => { - const p = f.properties || {}; - const [lat, lon] = nearbyCentroid(f); - const area = Math.round(Number(p.area_m2) || 0); - const name = String(p.name || "").trim(); - // 市外の公園も公園名で開く。市名が分かる場合は市名を付ける。 - const mapsQuery = name - ? p.city - ? `${p.city}${name}` - : name - : `${lat},${lon}`; - return { - "公園名": name, - "住所": p.city ? `${p.city}(市外)` : "市外", - "町名": "市外", - "面積㎡": area ? String(area) : "", - "500㎡以上": "", - "種別": "市外", - "南西部指定管理": "", - "トイレ": "", - "水飲み場": "", - "ベンチ・椅子": "", - "テーブル": "", - "パーゴラ": "", - "東屋": "", - "防災用具": "", - "ボール遊び": "", - "遊具": "", - "駐車場": "", - "その他設備": "", - "緯度": String(lat), - "経度": String(lon), - "備考": `市外・小平市境から約${Math.round(Number(p.distance_m) || 0)}m`, - "設置者": "市外", - "状態": "", - "地図キー": `${p.osm_type || ""}/${p.osm_id || ""}`, - "地図URL": `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(mapsQuery)}`, - }; -}); - -const parks = [...(parksData as Park[]), ...nearbyRows]; - -// OpenStreetMapにポリゴンがあり、地図から位置を表示できる公園名。 -// 市外の近隣公園も地図に表示しているので、同じように地図で見られる。 -const mappedNames = new Set([ - ...(osmData as any).features.map((f: any) => f.properties?.our_name).filter(Boolean), - ...((nearbyData as any).features ?? []) - .map((f: any) => String(f.properties?.name || "").trim()) - .filter(Boolean), -]); +import { parks, mappedNames, type Park } from "@/data/parks-rows"; const EMPTY = "—"; @@ -87,6 +18,12 @@ const TYPE_LABEL: Record = { 団地内公園: "団地内公園", 児童遊園: "児童遊園", 市外: "市外の近隣公園", + 地域センター: "地域センター", + 公民館: "公民館", + 児童館: "児童館", + 図書館: "図書館", + 市役所支所: "市役所支所", + 文化施設: "文化施設", }; const TYPE_CLASS: Record = { 街区: "parks-type--block", @@ -98,6 +35,30 @@ const TYPE_CLASS: Record = { 団地内公園: "parks-type--danchi", 児童遊園: "parks-type--jidou", 市外: "parks-type--green", + 地域センター: "parks-type--facility", + 公民館: "parks-type--facility", + 児童館: "parks-type--facility", + 図書館: "parks-type--facility", + 市役所支所: "parks-type--facility", + 文化施設: "parks-type--facility", +}; +// 種別は列幅を小さくするため、漢字1文字の記号で表示する。 +const TYPE_MARK: Record = { + 街区: "街", + 近隣: "近", + 運動: "運", + 広域: "広", + 広場: "場", + 緑地: "緑", + 団地内公園: "団", + 児童遊園: "児", + 市外: "外", + 地域センター: "📍", + 公民館: "🏛", + 児童館: "🧒", + 図書館: "📚", + 市役所支所: "🏢", + 文化施設: "🎭", }; // 市立以外の公園に付けるバッジ(設置者が空欄なら市立として扱う)。 @@ -125,6 +86,9 @@ function facilityBadges(p: Park): Badge[] { if (p["水飲み場"]) { badges.push({ kind: "water", label: "水飲み場", title: `水飲み場: ${p["水飲み場"]}` }); } + if (p["給水"]) { + badges.push({ kind: "water", label: "給水", title: `給水: ${p["給水"]}` }); + } if (p["遊具"]) { badges.push({ kind: "toy", label: "遊具", title: `遊具: ${p["遊具"]}` }); } @@ -150,12 +114,34 @@ function facTokens(p: Park): string { const tokens: string[] = []; if (p["トイレ"]) tokens.push("toilet"); if (p["水飲み場"]) tokens.push("water"); + if (p["給水"]) tokens.push("water"); if (p["遊具"]) tokens.push("toy"); + const play = p["遊具"] || ""; + if (play.includes("砂場")) tokens.push("sand"); + if (play.includes("ブランコ")) tokens.push("swing"); + if (play.includes("シーソー")) tokens.push("seesaw"); + if (play.includes("スプリング")) tokens.push("spring"); + if (play.includes("ジャングル")) tokens.push("jungle"); + if (play.includes("ヒコーキ") || play.includes("飛行機")) tokens.push("plane"); + if (play.includes("滑り台") || play.includes("滑台")) tokens.push("slide"); + if (play.includes("複合遊具")) tokens.push("complex"); + if (play.includes("ロープウェイ")) tokens.push("rope"); if (p["パーゴラ"]) tokens.push("pergola"); if (p["東屋"]) tokens.push("azumaya"); const ball = p["ボール遊び"]; if (ball && ball !== "禁止") tokens.push("ball"); if (p["南西部指定管理"]) tokens.push("cm"); + // ピクニック広場・芝生広場・テーブルのある公園。 + const picnicText = `${p["公園名"] || ""} ${p["種別"] || ""} ${p["その他設備"] || ""} ${p["備考"] || ""}`; + if ( + (p["テーブル"] || "").trim() || + p["種別"] === "広場" || + picnicText.includes("芝生") || + picnicText.includes("ピクニック") || + picnicText.includes("広場") + ) { + tokens.push("picnic"); + } return tokens.join(" "); } @@ -228,56 +214,32 @@ function searchIndex(p: Park): string { p["状態"], p["トイレ"], p["水飲み場"], + p["給水"], p["ボール遊び"], ...DETAIL_FIELDS.map((f) => p[f.key]), ] .join(" ") .toLowerCase(); } - -const towns = [...new Set(parks.map((p) => p["町名"]).filter(Boolean))].sort((a, b) => - a.localeCompare(b, "ja"), -); -const total = parks.length; ---
-
- - -
- 設備・管理で絞る - - - - - - - -
- {total}件 -
-

設備の情報は、市の資料などに基づきます。設備で絞ると、この情報がある公園だけが対象になります。

- +
+ + + + + + + @@ -293,13 +255,14 @@ const total = parks.length; {parks.map((park) => ( -
住所
+ {mappedNames.has(park["公園名"]) ? ( + ) : park["公式URL"] ? ( + {park["公園名"]} ) : ( park["公園名"] )} @@ -344,7 +315,7 @@ const total = parks.length; title={TYPE_LABEL[park["種別"]]} aria-label={TYPE_LABEL[park["種別"]]} > - {park["種別"]} + ) : ( EMPTY @@ -424,16 +395,35 @@ const total = parks.length; opacity: 1; } .parks-scroll { - overflow-x: auto; + max-height: 70vh; + overflow: auto; + overscroll-behavior: contain; -webkit-overflow-scrolling: touch; border: 1px solid var(--sl-color-hairline, #ddd); border-radius: 0.3rem; } .parks table { width: 100%; + min-width: 760px; + table-layout: fixed; border-collapse: collapse; font-size: 0.85rem; } + .parks col.col-name { + width: 24%; + } + .parks col.col-address { + width: 18%; + } + .parks col.col-area { + width: 11%; + } + .parks col.col-type { + width: 7%; + } + .parks col.col-facility { + width: 40%; + } .parks th, .parks td { border-bottom: 1px solid var(--sl-color-hairline, #ddd); @@ -481,6 +471,9 @@ const total = parks.length; } .parks .is-name { font-weight: 600; + white-space: normal; + overflow-wrap: anywhere; + line-height: 1.5; } .parks-owner { display: inline-block; @@ -521,6 +514,8 @@ const total = parks.length; white-space: nowrap; } .park-name-link { + display: inline-block; + max-width: 100%; padding: 0; border: 0; background: none; @@ -532,6 +527,8 @@ const total = parks.length; text-decoration: underline; text-decoration-color: var(--sl-color-gray-4); text-underline-offset: 2px; + white-space: normal; + overflow-wrap: anywhere; } .park-name-link:hover { color: var(--sl-color-accent); @@ -579,16 +576,15 @@ const total = parks.length; .parks-type { display: inline-flex; align-items: center; - gap: 0.3em; - font-size: 0.9em; - } - .parks-type::before { - content: ""; - width: 0.7em; - height: 0.7em; - border-radius: 50%; - background: var(--type-color, #888); - flex: none; + justify-content: center; + width: 1.5em; + height: 1.5em; + border-radius: 0.35em; + background: color-mix(in srgb, var(--type-color, #888) 18%, transparent); + color: var(--type-color, #888); + font-size: 0.8em; + font-weight: 700; + line-height: 1; } .parks-type--block { --type-color: #2e9e4f; @@ -614,6 +610,10 @@ const total = parks.length; .parks-type--jidou { --type-color: #d81b60; } + .parks-type--facility { + --type-color: #00838f; + font-size: 1em; + } .parks .is-facility { white-space: normal; min-width: 12rem; @@ -674,12 +674,15 @@ const total = parks.length;