這篇不是什麼新知,只是我第一次處理,覺得比看起來容易,希望改善「看起來比較難」情況。

村里邊界,右下文字部分要自己處理 mouse 事件

整個處理大致都被 GeoJSON 包裝好了

原本沒碰過 GeoJSON,自己想像要處理區塊框線、互動,以為要折磨很多事;但其實在 Leaflet 裡可以看成載入一個 GeoJSON 資源,再用 addTo(map) 掛上去就結束了。

建立與顯示:

const layer = Leaflet.geoJSON(data as GeoJsonObject, options);
layer.addTo(map);

外觀、互動行為等,可以用 options 去調,以下是一個 options 物件:

{
  style: () => ({
    fill: false,
  }),
  onEachFeature: function (feat: Feature, layer: Path) {
    layer.on('mouseover', function () {
      layer.setStyle({ fill: true });
    });
    layer.on('mouseout', function () {
      layer.setStyle({ fill: false });
    });
  },
}

要關閉圖層,就 remove 掉:

layer.removeFrom(map);


取得地圖資料

另一個問題是圖資怎麼來,首選當然是內政部的開放資料:
縣市界:鄉鎮市區界線(TWD97經緯度)、村(里)界:村里界圖(TWD97經緯度)

但要怎麼轉成 GeoJSON 格式?尋找現成整理好的,似乎都失連,或看起來有點年代,幸好裡面原理沒變,參考 jason2506/Taiwan.TopoJSON,它是用 mapshaper 將 .shp 轉為 .geojson。

具體指令例,縣市:

mapshaper raw/COUNTY_MOI_1090820.shp -simplify interval=400 \
  -filter 'COUNTYNAME !== "金門縣" && COUNTYNAME !== "連江縣"' \
  -rename-fields name=COUNTYNAME -filter-fields name \
  -o format=geojson precision=0.0001 \
  counties.geojson

村里:

mapshaper raw/VILLAGE_NLSC_1130807.shp -simplify interval=30 \
  -rename-fields id=VILLCODE \
  -verbose \
  -filter 'COUNTYNAME !== "金門縣" && COUNTYNAME !== "連江縣"' \
  -each 'name=TOWNNAME + " " + VILLNAME' -filter-fields id,name \
  -o format=geojson precision=0.0001 \
  villages.geojson

其中 -simplify interval=precision 參數可以控制產出的精細度,也決定了檔案大小。 上面範例出來的結果是 61.35 KB 和 7.21 MB,後者檔案很大,但其實掛在 Cloudflare R2,在 HTTP/2 和壓縮加持下傳輸量只有 1.7 MB,使用上是沒什麼問題的。