台灣 ( "中華民國自由地區",含台、澎、金、馬 ) 縣市、鄉鎮、村里界圖。含前端繪圖函式 ( 基於 d3.js,v4 ~ v7 皆可 )
npm install --save pdmaptw
pdmaptw needs two globals at runtime: d3 and topojson ( topojson-client ).
It only uses d3.geoProjection / d3.geoPath / d3.select, which are identical from
d3 v4 through v7 — the released files are tested against v7.
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3"></script>
Colour scales are not used by the library itself; include them only if your own code needs them:
<script src="https://cdn.jsdelivr.net/npm/d3-scale-chromatic@3"></script>
include main js file:
<script src="path/to/dist/index.js>"></script>
include corresponding map files:
<script src="path/to/dist/county.map.js>"></script>
<script src="path/to/dist/town.map.js>"></script>
<script src="path/to/dist/village.map.js>"></script>
A single county is also available on its own, at both levels. These are much smaller than the national files ( a county's villages are 30 ~ 220KB against 1.8MB for all of them ), so load one of these instead when you only render one county:
<script src="path/to/dist/county/臺中市.map.js>"></script>
<script src="path/to/dist/county/臺中市.village.map.js>"></script>
Each map file registers itself under a type when it loads. Load them on demand if you let
the user switch levels — see web/static/sample.html.
Then, create map object:
var obj = new pdmaptw(opt);
obj.init().then(function() {
obj.fit();
});
root: container for this map. a CSS selector, or a DOM node — a plain<div>, an existing<svg>, or a<g>inside one. no extra dependency is needed for any of them.type: which map to draw. must match a map file that has been loaded.'county'/'town'/'village': the whole country at that level.'county/<縣市名>': the towns of one county, e.g.'county/臺中市'.'county/<縣市名>/town'is the same thing spelled out.'county/<縣市名>/village': the villages of one county.- county names here are as the government writes them —
臺中市, not台中市.
padding: padding in pixels used byfit(). defaults to 20.
init(): map initialization, include data fetching / path elements creating. return promise.fit(opt): fit map to the size of container. options:box: bounding box{width, height}for fix size hinting
scale(): the scale factor applied by the lastfit().choropleth(opt): fill the regions from a value lookup. returns the map object.data: an object keyed by region, e.g.{"63000": 270, "65000": 400}.key: which propertydatais keyed by —'code'( default ) or'name'.scale: a function turning a value into a colour. omit it to use the values indataas colours directly.empty: colour for regions absent fromdata. defaults to#eee.
The drawn <path> elements carry no fill of their own, so nothing is coloured until you
say so:
var map = new pdmaptw({root: '#map', type: 'county'});
map.init().then(function() {
map.choropleth({
data: {"63000": 270, "65000": 400, "64000": 277},
scale: d3.scaleSequential(d3.interpolateBlues).domain([0, 400])
});
map.fit();
});
choropleth() only sets fill; stroke, hover styling and anything else stays yours to do
on map.g.
hover: fired when user hovers on geographic paths. with parameters:- evt: event for mouseover.
- data: not null if mouseover path element of map. a geojson feature — see
Feature Properties for what its
propertiesholds.
projection(): return a d3js GeoProjection for 台澎金馬地區, as compact as possible. It moves 澎湖 / 金門 / 馬祖 / 釣魚臺 / 彭佳嶼 in towards the main island and clamps the result to the resulting box, so the map has no large empty corners. The same instance is shared by every map object.- It takes one argument, an array in
[lng, lat]order — longitude first, which is the GeoJSON order and the reverse of how coordinates are usually said out loud.pdmaptw.projection()([121.5645, 25.0338])is 台北 101. - It returns
[x, y]in the same coordinate space the<path>elements are drawn in, so anything you position with it lines up with the map and moves withfit()as long as you append it insidemap.g. Sizes do not scale with it, so divide a radius or a stroke width bymap.scale()to keep it constant on screen. - Because of the offsets above, feeding it a coordinate outside 台澎金馬 gives a point that is clamped into the box rather than a meaningful position.
- It takes one argument, an array in
normalize(str)- name normalization, e.g., replace '臺' with '台'.
After npm install, Fetch data and build:
npm run build
Alternatively, execute the script manually:
./fetch
./node_modules/.bin/lsc convert.ls
./node_modules/.bin/lsc filter.ls
./build
./node_modules/.bin/lsc verify.ls
Past releases are kept under archive/ — see archive/README.md. The government only
serves the current version of each dataset, so building over the old files is the only way
back to an earlier boundary set.
What the above commands do:
fetchdownloads and unzips the shp files intodownload/. The files live on tgos.tw and their names carry a release date that changes on every update, sofetchresolves the current url from the data.gov.tw dataset API rather than hardcoding it, and records the url it used indownload/<level>/source.txt.convert.lswill process all shp files and convert them to topojson.- tweak
mwandwfor tweaking topojson size. be sure to test in major browsers before using, escpecially windows firefox since we encountered an abnormal path before. - simplification can leave a ring degenerate or wound the wrong way, and d3-geo reads
such a ring as covering the whole sphere — one of them paints the entire map. the
cleanstep drops and re-winds those, so check its output if you changemw/w.
- tweak
filter.lswill generate separated county files, undersrc/topojson/county/.<county>.topo.jsonholds that county's towns and<county>.village.topo.jsonits villages.-n <county>limits it to one county and-l <level>to one level, which is useful while tuning.buildbuild the utility jstwmapfor frontend rendering.verify.lschecks what landed indist/, andnpm run buildfinishes by running it. It loads the built files the way a browser does and asserts the things that broke in past rebuilds: every feature carries a code of the right length, the codes nest, no geometry covers the whole sphere, the smallest district survived filtering, every county has both of its subfiles and they add up to the national totals, no file is unexpectedly large, andinit/fit/choropleth/hoverstill work in a jsdom page. Run it on its own withnpm run verify.tool/build.shwill process all shp files and convert them to geojson, topojson and sample svg.- for getting topojson, simply use
convert.lsdirectly.
- for getting topojson, simply use
For a sample usage in frontend:
npm start
the script will start a simple server and open the demo page automatically.
/sample.html on that server is a smaller, self-contained page — plain HTML with no build
step, kept at web/static/sample.html so it can be read as one file. It covers init(),
fit() including refit on resize, choropleth() keyed by code, the hover event,
loading a map file on demand when the level changes, and placing a marker from a
[lng, lat] pair with pdmaptw.projection().
Each feature drawn by init() carries:
code: the official 行政區代碼 from the source shapefile — 5 digits for a county ("63000"臺北市 ), 8 for a town ("63000030"臺北市大安區 ), 11 for a village ("63000030037"). A county code is a prefix of its town codes, which are a prefix of their village codes, so a coarser code is alwayscode.substring(0, 5)/code.substring(0, 8). Prefer this over the name when joining your own data.name: the composed Chinese name, normalized withpdmaptw.normalize— so"台北市","高雄市左營區","台東縣成功鎮", always in 台 form, never 臺. Town and village names include the county prefix; a bare"大安區"will not match.c/t/v: indices intometa.name, from whichnameis composed. These are an implementation detail of the file format.
meta ( the second half of each *.map.js, also released as <level>.meta.json ) is:
{
name: [ ...names, deduplicated across all levels... ],
source: {level: "town", file: "TOWN_MOI_1140318", date: "2025-03-18"}
}
meta.source records which government shapefile release the file was built from.
It is reachable as obj.lc.meta.source after init().
A handful of the smallest urban 里 collapse to nothing during simplification. They are
kept as features with an empty geometry, so a join on code still finds them — they just
draw no visible shape. Unnamed islets ( 未編定村里 ) are dropped entirely.
-
取得 shp files.
- 可以從政府開放資料平台取得. e.g.,
-
shp to geojson
- 使用 npm module: shapefile
- npm install shapefile
- shp2json ( -o )
- ( geojson 的格式說明? )
- 轉出的geojson 仍需做投影, 而這可以先做, 就不用 runtime 做. 使用 d3-geo-projection
- npm install d3-geo-projection
- geoproject 'd3.geoConicEqualArea().parallels([34, 40.5]).rotate([120, 0]).fitSize([960, 960], d)'
< \ - geoConicEqualArea 適用於北美加州, 我們可以自已換投影法. 參考
- 可以用 geo2svg ( from d3-geo-projection ) 先輸出範本 svg ( 但大概會非常大 ):
- geo2svg -w 960 -h 960 < >
- Data Join: 利用 ndjson 將 geojson 分 features 切成很多行, 方便後續處理
- npm install ndjson
- 切開: ndjson-split 'd.features' < >
- 轉換: ndjson-map 'd.id = d.properties.GEOID.slice(2), d' < >
- 串接: ndjson-cat ...
- join data: ndjson-join ...
- 轉回 geojson: ndjson-reduce
- 最佳化: 使用 topojson 格式.
- npm install topojson
- geo2topo -n tracts= >
- toposimplify -p 1 -f < >
- topoquantize 1e5 < >
- 合併行政區塊: 使用 topomerge ( in topojson package )
- topomerge -k 'd.id.slice(0,3)' counties=tracts < >
- 使用 npm module: shapefile
-
geojson to topojson
Source code: MIT