將各縣市門牌座標資料轉換為掃街路線,以最近鄰居演算法規劃路徑,產生可在地圖上瀏覽的 GeoJSON 路線檔案。
raw/*.csv, *.xlsx
│
▼
┌─────────────┐
│ 1. 讀取與正規化 │ 自動偵測編碼 (UTF-8/BIG5/CP950),統一不同欄位名稱
└──────┬──────┘
▼
┌─────────────┐
│ 2. 去重複 │ 以「街路段+地區+巷+弄+號」為鍵,每個地址只保留一筆
└──────┬──────┘
▼
┌─────────────┐
│ 3. 座標轉換 │ TWD97 (EPSG:3826) → WGS84 (EPSG:4326)
└──────┬──────┘
▼
┌─────────────┐
│ 4. 路徑規劃 │ KDTree 加速的最近鄰居 TSP,依鄉鎮市區分組
└──────┬──────┘
▼
┌─────────────┐
│ 5. 線段簡化 │ Douglas-Peucker 演算法,ε=0.00005° (約 5 公尺)
└──────┬──────┘
▼
┌─────────────┐
│ 6. 分段輸出 │ 每段 ≤ 15 km(自行車約一小時),輸出 GeoJSON
└──────┬──────┘
▼
output/<city>/<district_code>.geojson
各縣市的原始資料欄位名稱不一致,程式透過 COLUMN_MAP 字典統一對應:
| 正規化欄位 | 原始欄位變體 |
|---|---|
| district | 鄉鎮市區代碼、areacode、districtCode |
| street | 街、路段、街路段、街或路段、街_路段、street、road、section、街(路段) |
| number | 號、號樓、number、houseNumber |
| x | 橫座標、橫坐標、TWD97橫坐標、x_3826、coordinateX |
| y | 縱座標、縱坐標、TWD97縱坐標、y_3826、coordinateY |
| address | 地址(新竹市為合併欄位,無獨立街路段) |
檔案編碼依序嘗試 UTF-8-SIG → BIG5 → CP950,若全部失敗則以 errors='replace' 模式重試(處理編碼混亂的檔案如新竹縣)。
多檔案城市(如雲林 yunlin_1.xlsx ~ yunlin_3.xlsx)會自動合併。
同一門牌號(截斷至「號」字)的多筆資料(如不同樓層)只保留第一筆座標,避免路線在同一棟建築上重複繞行。
原始資料使用 TWD97 二度分帶(EPSG:3826)投影座標。程式實作完整的橫麥卡托逆轉換公式,將橫座標 (E) 與縱座標 (N) 轉為 WGS84 經緯度,中央經線 121°E,尺度因子 0.9999。
每個鄉鎮市區的門牌點位獨立規劃路線:
- 以所有點位的 TWD97 座標建立 KDTree
- 從第一個點出發,每次查詢最近的 32 個鄰居,選擇尚未拜訪的最近點
- 若 32 個鄰居都已拜訪,退回暴力搜尋剩餘點位
- 重複直到所有點位都被拜訪
此為貪婪近似解,非最佳解,但對數萬至數十萬點位的規模能在數秒內完成。
路徑規劃後,許多相鄰門牌座標非常接近(同一條巷子內間距僅數公尺),在地圖上顯示時不需要這麼高的精度。
使用 Douglas-Peucker 演算法移除「偏離直線不超過 ε」的中間點:
- ε = 0.00005°,在台灣緯度約等於 5 公尺
- 使用迭代(非遞迴)實作,避免大量點位時的遞迴深度限制
- 比較時使用距離平方避免開根號,提升效能
典型壓縮率:都市密集區保留 2035%,鄉村區保留 5075%。整體輸出從 120 MB 降至 43 MB。
簡化後的路線以 Haversine 公式計算累積距離,每累積 15 km 切割為一段(自行車時速 15 km/h 約一小時的路程)。每段至少包含 2 個點位。
輸出為 GeoJSON FeatureCollection,每個 Feature 為一段 LineString,屬性包含:
district:鄉鎮市區代碼segment:段序號total_segments:該區總段數km:該段距離(公里)
map_cover/
├── raw/ # 原始門牌座標資料 (868 MB)
│ ├── taipei.csv
│ ├── kaohsiung.csv
│ ├── yunlin_1.xlsx # 多檔案會自動合併
│ ├── yunlin_2.xlsx
│ ├── yunlin_3.xlsx
│ └── ... # 共 19 個檔案,涵蓋 17 個縣市
├── scripts/
│ ├── generate_all.py # 產生掃街路線 GeoJSON
│ └── generate_csv.py # 產生統一格式 CSV
├── data/ # 統一格式 CSV (generate_csv.py 產出)
│ ├── taipei/
│ │ ├── 63000010.csv
│ │ └── ...
│ └── .../
├── docs/ # 網頁與產出資料(可直接部署為靜態網站)
│ ├── index.html # 地圖瀏覽介面
│ └── output/ # 產出的 GeoJSON (43 MB, 330 個檔案)
│ ├── taipei/
│ │ ├── 63000010.geojson # 松山區
│ │ ├── 63000020.geojson # 信義區
│ │ └── ...
│ ├── kaohsiung/
│ │ └── ...
│ └── .../
└── README.md
pip install numpy scipy openpyxl
python3 scripts/generate_all.py處理 17 個縣市約需 2~3 分鐘,產出寫入 docs/output/。
python3 scripts/generate_csv.py將各縣市不同格式的原始資料統一為 UTF-8 CSV,依鄉鎮市區分檔輸出至 data/<city>/<district_code>.csv。
統一欄位:city_code, district_code, village, neighbor, street, area, lane, alley, number, address, x, y
此步驟不做去重複或座標轉換,保留原始資料完整內容。
以任意 HTTP 伺服器開啟 docs/ 目錄:
cd docs
python3 -m http.server 8000
# 瀏覽 http://localhost:8000介面功能:
- 左側面板選擇縣市與行政區
- 搜尋欄支援縣市名與區域名
- 點擊路線段落或使用底部導覽列切換段落
- 每段路線顯示起點(綠色)、終點(紅色)及行進方向箭頭
- 支援 URL hash 直連,如
#tainan/6700100
| 縣市 | 行政區數 | 原始筆數 |
|---|---|---|
| 臺北市 | 12 | 1,154,424 |
| 新北市 | 29 | 1,982,870 |
| 桃園市 | 13 | 1,081,316 |
| 新竹市 | 3 | 210,396 |
| 新竹縣 | 13 | 262,762 |
| 苗栗縣 | 18 | 226,617 |
| 臺中市 | 29 | 1,292,650 |
| 彰化縣 | 26 | 467,023 |
| 雲林縣 | 20 | 283,980 |
| 嘉義縣 | 18 | 198,697 |
| 臺南市 | 37 | 847,103 |
| 高雄市 | 38 | 1,275,343 |
| 屏東縣 | 33 | 327,457 |
| 臺東縣 | 16 | 93,671 |
| 花蓮縣 | 13 | 151,358 |
| 澎湖縣 | 6 | 40,609 |
| 金門縣 | 6 | 32,005 |
- Python 3.8+
- NumPy
- SciPy(KDTree)
- openpyxl(讀取 xlsx)
- Leaflet 1.9.4(前端地圖,由 CDN 載入)