---
group: AI 使用指引
order: 20
reviewed_by:
  rd: Ronny
  pm:
---
# 選舉地圖：鄉鎮藍綠地圖製作指引

> 以「2024 年總統大選 × 新北市各鄉鎮得票分布」為例，示範如何整合歐噴資料庫的選票資料與行政區地理邊界，用 Leaflet 繪製 choropleth 地圖。

---

## 所需資料集

| 資料集 | 用途 |
|--------|------|
| [`tw.gov.cec~ref~election-event`](../../datasets/tw.gov.cec~ref~election-event/skill.md) | 確認選舉代碼 |
| [`tw.gov.cec~ref~candidates`](../../datasets/tw.gov.cec~ref~candidates/skill.md) | 取得候選人清單與政黨 |
| [`tw.gov.cec~txn~candidates-votes`](../../datasets/tw.gov.cec~txn~candidates-votes/skill.md) | 各鄉鎮得票數 |
| [`tw.openfun~entity~geo`](../../datasets/tw.openfun~entity~geo/skill.md) | 鄉鎮代碼清單 + GIS 邊界 polygon |

全部 API 均需 Bearer token。申請網址：https://data.openfun.tw/user

---

## 完整流程

### Step 1：確認選舉代碼

2024 年總統大選的選舉代碼為 `ELC-P0-16`（第 16 任總統副總統選舉，投票日 2024-01-13）。

可由 `tw.gov.cec~ref~election-event` 查詢確認：

```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://data.openfun.tw/api/v1/datasets/tw.gov.cec~ref~election-event/records/ELC-P0-16"
```

### Step 2：取得候選人清單與政黨

查詢 2024 總統大選的候選人，**過濾 `副手` 欄位為空（或 `N`）的記錄**，排除副總統候選人：

```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://data.openfun.tw/api/v1/datasets/tw.gov.cec~ref~candidates/records?選舉代碼=ELC-P0-16&per_page=20"
```

回傳結果中每筆包含 `候選人代碼`（如 `ELC-P0-16:00:2`）和 `政黨`（如 `民主進步黨`）。以 `候選人代碼` → `政黨` 建立對應表。

⚠️ **候選人號次依選舉而異，務必以 API 取得的實際資料為準，不可假設固定號次。**

### Step 3：取得新北市各鄉鎮代碼

新北市的縣市代碼為 `65000`：

```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://data.openfun.tw/api/v1/datasets/tw.openfun~entity~geo/records?level=town&county_id=65000&per_page=50"
```

回傳 29 筆，每筆的 `id` 為 8 碼鄉鎮代碼（如 `65000010` = 板橋區）、`full_name` 為中文名稱。

### Step 4：取得各鄉鎮的各候選人得票數

`candidates-votes` 的 `行政區代碼` 是 exact match，無法直接篩「某縣市全部鄉鎮」。做法是取全台灣鄉鎮層後 client-side 篩選：

```bash
# 2024 總統大選，全台灣所有鄉鎮層得票（約 1,100 筆）
# 若一頁裝不下，翻頁取完（用 page=2, page=3 ... 直到 total 取完）
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://data.openfun.tw/api/v1/datasets/tw.gov.cec~txn~candidates-votes/records?選舉代碼=ELC-P0-16&行政區層級=town&per_page=200"
```

取回後，以 `行政區代碼` 比對 Step 3 的代碼清單，篩出新北市 29 個鄉鎮的記錄（每鄉鎮有 3 筆，共 87 筆）。

### Step 5：計算各鄉鎮藍綠比例

對每個鄉鎮，整合所有候選人得票，計算各政黨得票率：

```python
# 以 Python 偽代碼示意
for town_id in 新北市鄉鎮代碼清單:
    records = [r for r in votes if r["行政區代碼"] == town_id]
    total = sum(r["得票數"] for r in records)

    # 依候選人代碼對應政黨
    party_votes = {}
    for r in records:
        party = candidate_party_map[r["候選人代碼"]]
        party_votes[party] = r["得票數"]

    dpp_pct = party_votes.get("民主進步黨", 0) / total
    kmt_pct = party_votes.get("中國國民黨", 0) / total
    winner = "dpp" if dpp_pct > kmt_pct else "kmt"
```

### Step 6：批次取得 29 個鄉鎮的地理邊界

```bash
# 逗號分隔 29 個鄉鎮代碼（此處以省略號代表）
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://data.openfun.tw/api/v1/geo/shapes/tw.openfun~entity~geo?ids=65000010,65000020,...,65000290"
```

回傳 GeoJSON FeatureCollection，每個 feature 的 `properties.record_id` 即為鄉鎮代碼，可直接與 Step 5 的資料 JOIN。

### Step 7：繪製 Leaflet choropleth 地圖

```html
<div id="map" style="height: 600px;"></div>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
var map = L.map('map').setView([25.0, 121.6], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors'
}).addTo(map);

// voteData: {行政區代碼 → {winner, dpp_pct, kmt_pct, town_name}}
var voteData = { /* Step 5 的計算結果 */ };

// featureCollection: Step 6 的 API 回傳值
L.geoJSON(featureCollection, {
  style: function(feature) {
    var rid = feature.properties.record_id;
    var d = voteData[rid];
    var margin = Math.abs(d.dpp_pct - d.kmt_pct);
    return {
      fillColor: d.winner === 'dpp' ? '#1b9b38' : '#0070c8',
      fillOpacity: 0.25 + 0.55 * margin,  // 差距越大顏色越深
      color: '#ffffff',
      weight: 1
    };
  },
  onEachFeature: function(feature, layer) {
    var rid = feature.properties.record_id;
    var d = voteData[rid];
    layer.bindTooltip(
      '<b>' + d.town_name + '</b><br>' +
      '綠（民進黨）：' + (d.dpp_pct * 100).toFixed(1) + '%<br>' +
      '藍（國民黨）：' + (d.kmt_pct * 100).toFixed(1) + '%'
    );
  }
}).addTo(map);
</script>
```

---

## 延伸應用

| 目標 | 調整方式 |
|------|---------|
| 擴展到全台灣 | Step 3 改取所有 22 縣市的鄉鎮；Step 4 不做縣市篩選 |
| 縣市層地圖 | `行政區層級=county`；GIS ids 換成 22 個縣市代碼（5 碼） |
| 村里層地圖 | `行政區層級=village`；注意 `合併票數=Y` 的記錄（見 candidates-votes skill.md） |
| 其他選舉 | 把 `ELC-P0-16` 換成對應的選舉代碼（查 `election-event` 資料集） |
| 加入得票率漸層 | 用 `dpp_pct` 數值直接映射到色彩漸層，而非二元藍綠 |

---

## 注意事項

1. **代碼格式一致**：`candidates-votes` 的 `行政區代碼`、`entity~geo` 的 `id`、GIS API 的 `record_id` 三者格式完全相同（鄉鎮 8 碼純數字），可直接 JOIN，不需格式轉換。
2. **副手要排除**：`candidates` 中 `副手=Y` 的記錄是副總統候選人，`candidates-votes` 裡他們有獨立的票數記錄，計算得票率時需排除，只保留正候選人。
3. **層級不可相加**：`candidates-votes` 的 `village`/`town`/`county`/`national` 是同一票的不同彙總粒度，繪製鄉鎮地圖只取 `town` 層。
4. **翻頁取完所有記錄**：全台 368 個鄉鎮 × 3 位候選人 ≈ 1,104 筆，`per_page=200` 需要約 6 頁。確認 `total` 欄位後翻頁取完，或用更大的 `per_page`（最大值見 API 文件）。
5. **授權標注**：使用此資料產出的地圖，需標注「資料來源：歐噴資料庫（data.openfun.tw）／中央選舉委員會／內政部」。
