Featured image of post 長野市の避難場所マップを作ってみた

長野市の避難場所マップを作ってみた

長野市がオープンデータとして公開している避難場所一覧に緯度経度が付いていたのでマップにしてみました。 https://nagano-hinan.kuma-emon.com で使えます。
※ このマップは参考情報として提供しています。内容の正確性・最新性は保証しません。実際の避難行動は長野市や行政機関の公式情報・指示に従ってください。

長野市内の避難場所・指定避難所を地図上に表示しています。
マークをタップすると施設名・住所・対応している災害種別・留意事項を確認できます。

主な機能は次の通り。

  • 災害種別での絞り込み(洪水・土砂・地震・火事の AND フィルター)
  • 「指定避難所のみ」フィルター
  • GPS で現在地を取得して最寄りの避難場所を表示
  • Google マップへのルート案内連携

フィルター

画面上部のフィルターバーに「洪水」「土砂」「地震」「火事」のボタンがあり、選択した条件をすべて満たす避難場所だけを表示します。

たとえば「洪水」と「地震」を同時に選ぶと、洪水にも地震にも対応している避難場所だけが残ります。

ポップアップの見方

避難場所のマークをタップすると、以下の情報が表示されます。

  • 施設名と住所
  • 災害種別バッジ(対応している種別が色分けで表示)
  • 留意事項(「グラウンド除く」など適用条件が限定されている場合のみ表示)
  • 地区名(長野市内のどの地区に属するか)
  • 「Google マップでルートを見る」ボタン

現在地

右下の現在地ボタンを押すと、GPS でいまいる場所を取得します。

GPS が使えない場合や、別の場所を確認したいときは、地図上に現在地を手動で置くこともできます。

  • PC:右クリック
  • スマホ:長押し(約 0.6 秒)

Google マップ連携

施設のポップアップにある「Google マップでルートを見る」ボタンをタップすると、目的地がその施設に設定された状態で Google マップが開きます。
現在地を設定している場合は、出発地も引き継がれます。

データ

使用しているデータは、長野市がオープンデータとして公開 している「避難場所一覧」(CC BY 4.0 )です。

すべて緯度経度が付与されていたので、住所から座標を変換する処理などせずに使えました。

広告

実装について

静的 HTML ファイル 1 枚+データファイル(JavaScript)という構成で、サーバーサイドの処理はありません。

地図ライブラリとタイル

地図の描画には Leaflet.js を使いました。
地図タイルは MapTilerjp-gsi-standard スタイルです。国土地理院の地図データを基盤にしているので、日本語地名や道路が含まれていて見やすいです。

L.tileLayer('https://api.maptiler.com/maps/jp-gsi-standard/{z}/{x}/{y}.png?key=YOUR_API_KEY', {
  maxZoom: 20,
  tileSize: 512,
  zoomOffset: -1,
}).addTo(map);

tileSize: 512zoomOffset: -1 は、MapTiler の jp-gsi-standard が返す 512 px タイルを Leaflet 既定の 256 px タイル前提で正しく表示するための補正です。タイルが 2 倍のサイズなのでズームレベルを 1 段下げて辻褄を合わせています。

最寄りへのズーム

現在地ボタンを押したとき、最寄りの 2 件が画面内に収まるよう、ズームを自動で計算するようにしました。

const nearest = EVACUATION_DATA
  .map(p => ({ p, d: map.distance(latlng, [p.lat, p.lng]) }))
  .sort((a, b) => a.d - b.d)
  .slice(0, 2);
const radiusMeters = nearest[nearest.length - 1].d;
map.fitBounds(latlng.toBounds(radiusMeters * 2), { padding: [48, 48] });

現在地から全件との距離を計算して近い順に並べ、2 番目(遠い方)までの距離を半径にして表示範囲を決めています。

スマホの長押し

スマホでの長押しは、タッチ開始から 0.6 秒後に現在地を設定するタイマーを走らせる仕組みです。
ピンチ操作はタイマーをすぐキャンセルし、スクロールについては 10 px 以上指が動いたときにキャンセルするようにしました。

mapContainer.addEventListener('touchmove', (e) => {
  if (!longPressTimer) return;
  const t = e.touches[0];
  const dx = t.clientX - longPressStart.x;
  const dy = t.clientY - longPressStart.y;
  if (dx * dx + dy * dy > 100) cancelLongPress(); // 10px 以上の移動でキャンセル
}, { passive: true });

dx * dx + dy * dy > 100 は 10 px の移動距離を表す比較です。
指を止めているつもりでも多少ずれるため、閾値を設けないと誤って長押しがキャンセルされてしまいます。

改訂3版JavaScript本格入門 (Amazon)
コメントを送る
広告
Hugo で構築されています。  /  Theme Stack designed by Jimmy
本サイトに記載されている会社名・製品名などは、各社の商標または登録商標です。