熊出沒警報地圖:用 Leaflet 把 846 筆通報變成看得懂的地圖

2025年11月7日

熊出沒警報地圖畫面

為什麼做這個專案

2025 年秋天,日本東北地區的熊出沒事件大幅增加,新聞幾乎每天都有目擊與傷人的消息。看著一則則文字報導,很難想像「到底有多密集、集中在哪裡」。於是花了幾天時間,把這些通報資料放到地圖上,讓分布一眼就能看懂。

這次整理的資料涵蓋 2025/11/02~11/05 共 846 筆通報,其中 743 筆位於秋田縣。物種以亞洲黑熊(ツキノワグマ)為大宗,共 835 筆;另有少量野豬與日本鹿。通報類型包含目擊、痕跡、食害,以及 4 起人身傷害。


技術架構

項目使用技術
框架Next.js 16(App Router)+ TypeScript
地圖Leaflet + react-leaflet,底圖使用 OpenStreetMap
標記聚合react-leaflet-cluster
狀態管理Zustand
動畫Framer Motion
樣式Tailwind CSS,支援深色模式
部署Vercel

資料以靜態 JSON 載入,每筆包含時間、通報類型、城市、地址、物種、數量、描述與經緯度。


主要功能

標記聚合:讓 846 個點不再擠成一團

846 個標記直接畫在地圖上會完全重疊,看不出任何資訊。這裡用 react-leaflet-cluster 依縮放層級自動聚合,並自訂聚合圓圈的樣式:數量越多,圓圈越大。

iconCreateFunction={(cluster) => {
  const count = cluster.getChildCount();
  const size = count < 10 ? 40 : count < 100 ? 50 : 60;
  return L.divIcon({
    html: `<div class="cluster">${count}</div>`,
    iconSize: [size, size],
  });
}}

縮小時看得到整體熱區,放大後才展開成單一事件,閱讀的層次就出來了。

同一地址的點:加一點隨機偏移

不少通報的經緯度完全相同(同一個村落、同一條路),即使放大到最大也會疊在一起點不到。解法是在渲染時加上極小的隨機偏移(jitter),讓重疊的點稍微散開,又不影響對位置的判讀。

物種圖示與事件細節

熊、野豬、鹿各有不同圖示。點擊標記會跳出卡片,顯示時間、城市、地址、數量和現場描述。

篩選:日期區間與物種

左側面板可以設定觀測期間,以及勾選要顯示的物種。篩選狀態放在 Zustand store 裡,地圖只負責讀取篩選後的結果。

原始資料的物種名稱是日文,介面則顯示中文,所以篩選時做了一層中日對照:

(sp === "亞洲黑熊" && s.species?.includes("ツキノワグマ")) ||
(sp === "野豬" && s.species?.includes("イノシシ")) ||
(sp === "梅花鹿" && s.species?.includes("シカ"))

在手機上,篩選面板可以收合,把畫面留給地圖。


部署時踩到的坑

本機開發一切正常,推上 Vercel 卻連續 build 失敗。原因都出在 TypeScript 型別檢查:

  • JSON 裡的 id、count、經緯度,有的是字串、有的是數字,型別定義太嚴格就會報錯,最後放寬成 string | number
  • Leaflet 的型別套件 @types/leaflet 沒裝,本機有快取所以沒發現
  • react-leaflet-cluster 的 iconCreateFunction 參數型別對不上

教訓是:推上去之前,先在本機跑一次 next build。next dev 不做完整的型別檢查,很多錯誤只會在 production build 才出現。


小結

這是一個規模不大、但很完整的資訊視覺化練習:把一堆文字通報,轉換成可以縮放、篩選、點擊查看的地圖。過程中最有感的是,資料量一大,「怎麼呈現」比「能不能呈現」更重要:聚合、偏移、篩選,都是為了讓人看得懂。

🌐 線上 Demo | 💻 GitHub