技術實作
這頁寫給想知道怎麼做的人。所有效能數字都是在自己這台機器上量的。
整條流程
- 從政府資料開放平臺下載 zip(三年份合計約 104 MB)
- 用 Python 的 zipfile 串流讀取,不解壓縮到磁碟 —— 三年份解開來是 1.5 GB
- 去重、剔除假座標
- 建成 SQLite:一份 12 公尺細格(帶月份),加上第 7 到 11 級的預聚合表
- 成品 106 MB
為什麼不是每個縮放層級各存一份
第一版是那樣做的,12 個層級各存一份,單年資料就 180 MB。 改成「存一份細格,查詢時用整數除法即時併粗」之後,資料量變成三倍(一年變兩年八個月), 資料庫反而縮到 106 MB。低倍率(第 7 到 11 級)才另外預聚合, 因為那個範圍一次要掃的格子太多。
圖磚而不是 bbox
第一版的 API 是吃 bbox 的。問題是 bbox 只要差一個小數點就是一個新網址, 所以地圖平移一點點就會重抓整個畫面,而且瀏覽器和 CDN 都無法快取。實測一次回應 954 KB。
改成固定的圖磚網址之後:
- 初次載入 12 塊圖磚,195 KB
- 平移 200 像素 只抓新露出來的 3 塊,約 10 KB
- 再移回來 0 個請求,全部命中快取,14 毫秒
圖磚帶 Cache-Control: max-age=86400。資料一個月只更新兩次,快取一天很安全。
壓縮
GeoJSON 是高度重複的文字,壓縮效果非常好。同一份 1,044,568 bytes 的回應實測:
| gzip 級別 | 壓縮後 | 倍率 | 伺服器壓縮 | 瀏覽器解壓 |
|---|---|---|---|---|
| 1 | 69,132 | 15.1× | 1.8 ms | 1.28 ms |
| 4 | 58,194 | 17.9× | 2.6 ms | 1.25 ms |
| 6 | 53,255 | 19.6× | 4.3 ms | 1.19 ms |
| 9 | 50,967 | 20.5× | 10.4 ms | 1.27 ms |
解壓縮固定在 1.2 毫秒左右,跟級別無關 —— 那是瀏覽器裡的原生程式碼。 最後選 4 級:比 6 級只大 8%,但伺服器端快一倍。
前端
地圖用 MapLibre GL JS(Mapbox GL 的開源分支,BSD 授權)。 選它的理由是原生支援熱力圖圖層、GPU 渲染、而且不需要 API key 也不需要綁信用卡。
Google Maps 的 HeatmapLayer 在 2025 年 5 月宣告棄用、2026 年 5 月的 3.65 版正式移除, 官方建議改用第三方套件。而且 Google Maps 每次載入要計費,對一個免費工具來說是風險。
底圖用 OpenFreeMap 的向量圖磚,同樣不需要 API key、沒有用量限制。 熱力圖的權重在前端計算,因為圖磚必須是純資料才能被快取, 而每塊圖磚各自標準化會在磚的邊界上留下看得見的色階斷層。
後端
Flask + SQLite,跟站上其他工具跑在同一個容器裡,但完全不碰顯示卡 —— 沒有佇列、沒有配額、可以無限併發。
查詢實測:單一圖磚 8 到 82 毫秒,看縮放層級與範圍而定。