尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GeoJSON数据包处理实战:格式精读、数据清洗与可视化避坑指南

GeoJSON数据包处理实战:格式精读、数据清洗与可视化避坑指南 简介这是一份经收集整理的 GeoJSON 地理数据合集面向 GIS 开发者、Web 前端地图应用及地理信息学习者可用于地图可视化、空间分析和数据交换测试。压缩包共 1102 个文件以 1100 个 JSON 文件为主覆盖世界地图及巴西、印度尼西亚、菲律宾、加拿大、德国等多国行政边界数据另附 1 个全国县级以上地名代码与经纬度 CSV 和 1 个说明文档整体约 156MB。数据既可直接在 Leaflet、Mapbox 等前端框架中渲染也可配合 geojson-vt、geojson-merge 等工具做切片与合并便于快速搭建演示地图或开展空间统计。资源中大量真实行政边界 GeoJSON尤其是全球及多国县级边界数据与地名坐标 CSV 能有效降低数据获取门槛对搭建分级统计地图、开展空间分析、完成课程作业都很有帮助。目前已有 584 人浏览学习值得需要现成地理数据的开发者与学习者下载使用。1. 这份 GeoJSON 数据包先别急着解压做地图可视化、GIS 分析或者前端渲染的人大概率都经历过这种场景从某个渠道搞到一个GeoJSON_data.zip解压一看里面几十个.geojson文件命名五花八门有的带坐标精度问题有的属性字段对不上更麻烦的是有些文件直接用文本编辑器打开全是乱码。这份「搜集来的 geojson 数据_GeoJSON_data.zip」就是典型的野生态数据包——没有文档、没有目录说明、坐标系不统一数据价值全靠自己挖。我拆完这份数据后最大的感受是它适合两类人一类是想拿现成数据做地图可视化又不想自己爬的另一类是刚接触 GeoJSON 想理解格式边界和常见坑的。这篇笔记会把我的拆包过程、处理脚本、参数设置和踩过的坑完整写出来。2. GeoJSON 格式精读FeatureCollection 与坐标体系是绕不开的两道门2.1 为什么几乎所有文件都长成一个结构打开任意一个 GeoJSON 文件第一眼看到的永远是type、features这两个键。绝大多数收集类数据包里的文件都是FeatureCollection结构这不是巧合而是 GeoJSON 规范里最通用的一种组织方式。一个 FeatureCollection 里挂着若干Feature每个 Feature 又有自己的geometry和properties。理解这个层级是后续所有处理的基础。{ type: FeatureCollection, features: [ { type: Feature, geometry: { type: Point, coordinates: [116.391, 39.907] }, properties: { name: 示例点, id: 1 } } ] }这段结构里最需要注意的是coordinates的嵌套方式。Point 是经纬度数组LineString 是坐标数组的数组Polygon 则要在外层再多套一层——因为一个面可能由多个环组成外环和内环都放在同一个数组里。这个嵌套层级是新手最容易搞错的地方后面写处理脚本时几乎所有 bug 都跟这个有关。2.2 坐标系、精度与属性字段拿到文件先做三件事任何 GeoJSON 数据包第一件事不是急着转格式而是先做体检。我拿到这份数据后的标准动作是三个检查坐标系、检查坐标精度、检查属性字段是否齐整。GeoJSON 规范默认使用 WGS84 坐标系也就是经纬度直接标注但很多爬虫或手工采集的数据会混入投影坐标最典型的特征是数值范围异常——比如经度出现 400 多万这种明显不合理的数字。精度问题同样隐蔽。有的数据源导出时把坐标保留了 6 位小数有的只保留 3 位更有的直接丢了小数位导致本该在同一条街上的点散落到几百米外。处理这类问题的通用做法是写一个 Python 脚本批量扫描所有文件先输出异常记录再决定是清洗还是弃用。import json import glob files glob.glob(./data/*.geojson) for f in files: with open(f, r, encodingutf-8) as fp: data json.load(fp) for feat in data[features]: coords feat[geometry][coordinates] # 递归取到最内层的坐标对 def walk(c): if isinstance(c[0], float): return [c] result [] for item in c: result.extend(walk(item)) return result pts walk(coords) for lon, lat in pts: if not (-180 lon 180 and -90 lat 90): print(f{f}: 坐标越界 {lon},{lat})这个脚本的核心逻辑是递归降维。GeoJSON 的 geometry 类型多样Point、MultiPoint、LineString、Polygon、MultiPolygon 的坐标嵌套层数完全不同用递归统一拍平成坐标对数组是最省事的方案。参数方面glob(./data/*.geojson)负责匹配目录下所有 geojson 文件encodingutf-8是必须的不少数据源用了 GBK 编码不指定编码直接读会抛异常。跑完一遍基本就能知道这份数据包里哪些文件能用、哪些文件需要重投影。3. 数据清洗与转换从乱糟糟到能用的标准处理管线3.1 属性表归一化让几十个文件的字段对齐收集来的数据包几乎不可能字段统一。这份数据里有的文件叫name有的叫title有的干脆没有名字字段只有id。做可视化时字段不一致会直接导致图层属性无法统一映射。我一般会写一个标准化脚本把所有 Feature 的properties里的字段名映射到统一 schema同时保留原始字段方便追溯。import json FIELD_MAP { name: name, title: name, 名称: name, id: source_id, ID: source_id } def normalize_properties(props): new_props {} for k, v in props.items(): target FIELD_MAP.get(k, k) new_props[target] v return new_props with open(raw.geojson, r, encodingutf-8) as f: data json.load(f) for feat in data[features]: feat[properties] normalize_properties(feat[properties]) with open(normalized.geojson, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)这段代码的重心在FIELD_MAP字典上。写映射表的原则是只把确认同义的字段合并不确定的宁可保留原名。ensure_asciiFalse这个参数容易被忽略不加的话中文会被转成\uXXXX文件体积变大不说后续人工排查也看不出来内容是什么。indent2是控制可读性的数据清洗完给人看或提交到 Git 时建议缩进格式化但如果是几 MB 以上的大文件格式化会显著增加体积生产环境直接用不缩进的输出更合适。3.2 几何修复自相交 Polygon 与重复顶点问题属性字段的问题好解决几何层面的问题才是真正的硬骨头。常见的几何错误包括多边形环没有闭合、自相交、重复顶点、经纬度顺序颠倒。其中经纬度顺序颠倒最隐蔽——有些数据源用的是纬度在前经度在后渲染时图形整个偏移到海洋里肉眼几乎无法直接判断必须靠范围检测或叠加底图验证。对于自相交和未闭合这类拓扑问题shapely 库提供了标准解法。实测中make_valid()函数能解决大部分情况但它不是银弹处理 MultiPolygon 时可能改变要素数量这点必须在处理日志里记录。from shapely.geometry import shape, mapping from shapely.validation import make_valid import json with open(polygons.geojson, r, encodingutf-8) as f: data json.load(f) fixed_count 0 for feat in data[features]: geom shape(feat[geometry]) if not geom.is_valid: fixed make_valid(geom) feat[geometry] mapping(fixed) fixed_count 1 print(f修复了 {fixed_count} 个无效几何) with open(polygons_fixed.geojson, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse)shape()把 GeoJSON 字典转成 shapely 几何对象is_valid做拓扑合法性校验make_valid执行修复mapping再转回 GeoJSON 结构。注意make_valid之后几何类型可能发生变化比如多边形被拆成 MultiPolygon这是正常现象渲染端需要支持 Multi 类型。如果项目里对要素边界要求严格修复后的人工抽检是绕不过去的。3.3 坐标抽稀与数据瘦身文件太大时的取舍数据包里有些 GeoJSON 文件动辄几十 MB前端加载直接卡死。常见做法是用 Douglas-Peucker 算法做抽稀保留主要形状的同时大幅减少顶点数。shapely 里simplify()的tolerance参数决定了抽稀力度单位与坐标系一致。from shapely.geometry import shape, mapping import json with open(big_data.geojson, r, encodingutf-8) as f: data json.load(f) # tolerance 0.001 约等于 100 米级别的简化 for feat in data[features]: geom shape(feat[geometry]) simplified geom.simplify(tolerance0.001, preserve_topologyTrue) feat[geometry] mapping(simplified)tolerance0.001的含义是简化后的折线偏离原始几何不超过 0.001 度换算到赤道附近大约是 111 米。这个值不是越小越好——太小起不到瘦身效果太大则边界细节全部丢失。动辄几十 MB 的文件建议从 0.001 开始试然后对比简化前后的文件大小找到兼顾体积和形状保真度的拐点。preserve_topologyTrue必须开启否则可能生成重叠或破碎的面虽然能继续渲染但叫什么就不好说了。4. 可视化落地从 GeoJSON 到地图展示的三种路径4.1 本地快速预览geojson.io 未必是最好的第一站很多人拿到 GeoJSON 习惯直接拖进 geojson.io 预览但数据量大时浏览器会卡死而且 geojson.io 的报错信息对数据问题几乎没有任何提示。我本地更常用的方案是用 Python 起一个轻量服务配合 Leaflet 做实时预览有报错也能精确到具体的 Feature。但更务实的第一步其实是先做格式校验——数据如果本来就坏了任何渲染端都不会给面子。校验的实用工具有geojson这个 Python 库的validate()函数能检测出coordinates缺失、类型错误等低级问题。校验通过之后再进渲染管线排错成本最低。这份数据包里有几个文件就是在这步被筛出来的——结构上合法但语义上坐标范围明显偏移这种问题渲染端根本看不出来。4.2 Leaflet 加载本地数据与样式映射Leaflet 是加载 GeoJSON 最轻量可行的方案。核心是通过L.geoJSON()把数据对象直接注入地图然后定义pointToLayer或style回调来控制渲染样式。属性字段统一化之后的好处在这时候体现出来——所有要素都能用同一个函数读取properties里的字段做映射。fetch(./data/normalized.geojson) .then(res res.json()) .then(data { L.geoJSON(data, { pointToLayer: (feature, latlng) { const size feature.properties.value || 5; return L.circleMarker(latlng, { radius: size, color: #d97a2b, weight: 1, fillOpacity: 0.6 }); }, onEachFeature: (feature, layer) { const props feature.properties; layer.bindPopup(b${props.name}/bbr数值: ${props.value}); } }).addTo(map); });这段代码里值得关注的是pointToLayer和onEachFeature两个回调。前者把 Point 要素渲染成circleMarker半径由properties.value动态决定——这就是属性字段归一化之后带来的直接收益否则每个文件都要写一套兼容逻辑。bindPopup里直接拼 HTML 字符串如果属性字段可能包含恶意脚本要做转义处理别把用户可控的数据直接往里扔。4.3 QGIS 导出为其他格式什么时候不推荐转 Shapefile数据最终要跟其他系统对接时格式转换是躲不开的。QGIS 的右键导出可以一键把 GeoJSON 转成 Shapefile、KML、CSV 等格式。但有一个坑务必要知道Shapefile 的属性字段名最长 10 个字符中文字段名基本必乱码而且单个文件大小上限 2GB。所以只要下游系统支持 GeoJSON最好不要转 Shapefile。数据包里有几个带时间属性的文件转 KML 倒是挺合适Google Earth 可以直接识别时间轴播放。转格式之前务必确认目标格式的坐标系要求。GeoJSON 默认 WGS84但 Shapefile 可能被要求转成 GCJ02 或 Web Mercator这一步在 QGIS 里通过「重投影图层」完成不在导出对话框里做。很多人在导出时才选坐标系结果导出后要素偏移返工成本远高于先投影再导出。5. 避坑专题解码这份数据包时踩过的 5 个实坑5.1 解压后文件是乱码现象解压后用文本编辑器打开中文地名全是乱码但英文正常。原因数据源采集时用了 GBK 或 GB18030 编码而 GeoJSON 规范要求 UTF-8导致编码错乱。解决用 Python 重新读文件并转换编码批量处理所有文件。with open(raw.geojson, r, encodinggbk, errorsreplace) as f: content f.read() with open(fixed.geojson, w, encodingutf-8) as f: f.write(content)errorsreplace是双刃剑它能保证脚本不中断但会把无法识别的字节替换成占位符后续需要人工核对。如果错误很多建议先打印前 100 行看原始内容确认实际编码再动手。5.2 坐标数值正常但渲染位置全在海里现象经纬度范围看起来正常但叠到地图上要素全部偏移到海里。原因坐标顺序写反了数据源把纬度当作经度输出。解决遍历所有坐标对统一交换位置。def swap_coords(coord_list): return [coord_list[1], coord_list[0]]这个问题的可怕之处在于不容易发现——单看数值完全在合法范围内只有叠底图才能确认。我现在的习惯是拿到数据先在全球地图上扫一遍全景看到要素分布跟预期不符立刻怀疑坐标顺序而不是投影。5.3 QGIS 打不开文件但不报错现象文件能被识别为 GeoJSON但加载后图层是空的。原因文件里存在非标准字段或 Feature 缺少geometry键。解决用脚本扫出geometry为空的 Feature单独存放剩余数据重新导出。这个坑最麻烦的地方在于QGIS 的静默失败让排查耗时很长。5.4 zip 包内混入伪加密文件现象解压到某个文件时提示需要密码但全包加密码文件又打不开。原因zip 的加密标志被错误设置文件实际没有加密——这就是常说的「伪加密」。解决先用 7-Zip 试打开如果能看到文件名但解压时提示错误说明是伪加密。用 Python 的zipfile库读取时可以忽略加密标志直接把内容抽出来。伪加密的处理本质是清除压缩包的加密标记位具体实现需要改 zip 的 end of central directory 结构普通用户建议直接换个解压工具。5.5 文件大小从 80MB 变成 10MB 后图形残缺现象抽稀后有些面变成了线或者出现尖锐凹角。原因simplify()的 tolerance 设得太大preserve_topology也没能完全兜住。解决降低 tolerance 值同时抽稀后跑一遍is_valid校验。这一步没法完全自动化形状复杂的数据必须人工抽检几个典型区域。6. 进阶用法写一个针对这份数据包的属性 Schema 校验器收集来的数据最大的问题不是几何错误而是属性不可预期。我做了一个小工具专门检查这份数据包里每个文件是否符合预设的 schema 要求不匹配的直接报出文件名和缺失字段。这个东西比单纯的手工检查靠谱得多适合数据包文件数量多、字段比较杂的场景。import json import glob SCHEMA { points.geojson: [name, value], lines.geojson: [name, length], polygons.geojson: [name, area] } def validate_schema(filename, schema_fields): with open(filename, r, encodingutf-8) as f: data json.load(f) missing set() for feat in data[features]: props feat.get(properties, {}) for field in schema_fields: if field not in props: missing.add(field) return missing for pattern, fields in SCHEMA.items(): for f in glob.glob(f./data/{pattern}): missing validate_schema(f, fields) if missing: print(f{f} 缺失字段: {missing}) else: print(f{f} 校验通过)这个校验器的核心是把「人肉看数据」变成「程序查数据」。SCHEMA字典是手动维护的但一旦定下来每次新拿数据包都能直接复用。字段缺失的原因通常是上游数据源结构变更如果校验报错频繁建议回头检查采集端。从那以后我每次处理新的 GeoJSON 数据包都会强制走一遍这套流程先解压数文件、跑编码扫描、做坐标范围检测、再看属性 schema最后才进可视化。整个过程能自动化就自动化省下的时间足够多调几版样式。希望这份拆包笔记能帮你少走我走过的弯路。本文还有配套的精品资源点击获取
返回列表