GeoJSON 里的 properties 老坑人?行政区划属性字段的类型、命名与空值清坑指南
下载的行政区划 GeoJSON 里,properties 似乎是最不起眼的那段——坐标对不对才是大家关心的,属性嘛,用的时候读一眼就行。可跑过几次真实项目就发现,翻车往往不是几何,而是属性。今天把 GeoJSON 属性字段那些坑和清理办法摊开讲一遍。无论你是做 GeoJSON下载 后直接上 Leaflet,还是要喂给后端空间库,这几点都值得先花十分钟排查。
字段类型:数字被存成字符串,是大部分 bug 的源头
我从各类源站拿到过全国省市区县geojson数据下载回来的文件,第一件要确认的事就是每个字段到底是数字还是字符串。JSON 是弱类型,很多工具从 CSV、Excel、Shapefile 导出来时会偷懒:
"adcode": "110000"是字符串,"adcode": 110000是数字;- 人口、面积这类度量值,
"population": "2189307"看起来没问题,一旦要sum()就现原形。
行政区划代码尤其危险。.geojson(RFC 7946)里代码必须保留前导零,比如 510722(绵阳三台县)。如果被当成数字写入,前导零直接丢,变成 510722 看着一样,但遇到 11 开头的北京区县,"110000" 会变成 "11",join 全错。我写过一个极小的归一化函数:
// 行政区划代码统一转字符串并补齐 6 位,防御前导零丢失
function normCode(v) {
if (v == null) return null;
const s = String(v).trim();
// 兼容统计用 12 位与常规 6 位,统一取前 6 位
return s.length >= 6 ? s.slice(0, 6) : s.padStart(6, '0');
}
再配合一个表来对照常见字段的类型陷阱,判断你的数据该不该修:
| 字段 | 正确类型 | 常见错误 | 后果 |
|---|---|---|---|
| adcode / code | string | number | 前导零丢失,join 错 |
| name / 名称 | string | — | 重名、混用简繁体 |
| population / 面积 | number | string | 数值运算变拼接 |
| 中心点坐标 | array | string | 解析失败 |
| 可选描述字段 | null 或缺省 | 空串 " " | 渲染空标签 |
修的话不要手动一行行改,写个脚本遍历 features 批量归一化,改完再 JSON.stringify(result, null, 2) 重新落盘,顺手做一次几何合法性校验(几何不合法是另一篇的事,这里不展开)。下面这段把整份文件读进来、字段规整一遍再写回:
const fs = require('fs');
const gj = JSON.parse(fs.readFileSync('sichuan.geojson', 'utf8'));
for (const f of gj.features) {
const p = f.properties;
if (p.adcode) p.adcode = normCode(p.adcode);
if (typeof p.population === 'string') p.population = Number(p.population);
}
fs.writeFileSync('sichuan.clean.geojson', JSON.stringify(gj));
注意 normCode 里那行 s.slice(0, 6):如果上游混进了统计用 12 位代码,直接截前 6 位能救回一部分;但要是 6 位本身被截断过,那就只能回源头修了。所以脚本加个计数,打印有多少要素的 adcode 长度既不是 6 也不是 12,人工看一眼再决定。
字段命名:中文本地化与冗余属性
很多源数据为了让数据库看得懂,字段名用拼音:XZQDM、XZQMC、JCCLASS。直接拿去给前端渲染没问题,但你自己维护、给同事交接、或挂到别的地图场景时,拼音字段名可读性极差。我在做村级数据的时候就吃过亏:一套全村的数据里既有 village 又有 czmc,字段语义重复,后面接手的同事根本分不清哪个是村级名。
字段没统一还有一个隐患——跨文件 join 时对不上号。比如你要把一份统计报表按 乡镇代码 接回地理文件,但报表里叫 town_code,边界里叫 czdm,不写映射表就只能靠肉眼。建议在下发前统一定一套,并保留一份中英对照清单。
建议在下发前统一成一套命名,保留必选、砍掉冗余,properties 尽量精简。行政区划边界数据常见的精简原则是:
- 必留:
adcode、name、type(省/市/区县/乡镇/村)、parent(可选上层代码); - 可留:
center(预先算好的标注质心)、fullname(带省市区前缀的全称); - 尽量砍:内部 ID、排序位、拼音冗余字段、脚本临时变量。
属性越瘦,前端序列化、边缘缓存、按需加载全都跟着受益。毕竟全国省市区县区划边界数据下载下来是要给别人的应用消费的,字段干净才能少出兼容问题。字段名最好用英文 snake_case(如 full_name 而非 fullname),再在数据说明文档里贴一张中英对照,别让下游靠猜。
空值与缺失:别用空字符串占位
最让我头疼的是 null 的写法不统一:有的要素 "name": ""、有的干脆没有 name 这个 key、有的写 "population": null。三种写法在 JS 里的表现不一样,undefined、null、"" 在模板字符串拼标签时会渲染成不同的东西,String(feature.properties.name) 更是直接给你吐一个 "null"。
统一策略很简单:该有的字段必须有,缺失就不写那个 key;可选度量值缺则用 null,别用空串。渲染端再用可选链兜底:
const name = p?.name ?? '未命名';
const pop = p?.population ?? 0;
这样前端永远拿得到可用的默认值,不会因为某个要素属性不全而整片白屏。
小结
GeoJSON 的属性字段看着小,实则决定了 join 对不对、运算准不准、渲染稳不稳。做全国省市区县geojson数据下载分发时,把字段类型、命名、空值这三件小事在源头做干净,比事后在十几个消费方里各打一份补丁省事得多。至于全国村级geojson数据下载这类更细粒度的数据,字段规范更值得提前定好——村级要素动辄成千上万,改一处就等于重发一遍。
需要能直接用的分层数据,可以在 geojsoncn.com 上预览(免费),确认字段结构后再下载(导出 ¥1.99/次)。