ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GeoJSON省市边界文件从下载到上屏:校验、简化与可视化全攻略

GeoJSON省市边界文件从下载到上屏:校验、简化与可视化全攻略 简介覆盖全国省份与省市级行政区划边界的GeoJSON数据文件集合面向Web前端开发者、GIS从业者及需要在地图中展示行政区域、做空间分析的人群。RAR压缩包内共34个JSON文件每个文件对应一个省级行政编码包含省界边界、几何形状及省份名称、行政代码等属性信息总大小约709KB可直接用于ECharts、Leaflet等框架的行政区划地图渲染与空间查询。GeoJSON基于JSON标准可在JavaScript中直接操作也可配合turf.js、geopandas、shapely等工具进行投影转换、几何运算、缓冲分析与空间统计是行政边界类地理数据的常用交换格式。目前已有927人学习下载。借助这些文件用户可以快速构建全国地理可视化、按省份着色展示人口或经济发展指标或进行距离、面积等空间计算省去自行爬取并整理行政边界数据的繁琐过程为智慧城市、交通规划、环境研究等场景直接提供基础地理底图。1. 拿到各省市GeoJSON边界文件之后从下载到上屏的完整落地方案做Web端地图可视化时最卡的往往不是图表逻辑而是边界数据从哪来、拿到的文件能不能直接用。这份资源是一套按省、市两级整理的GeoJSON边界文件覆盖全国主要行政区划解压后可以直接喂给Leaflet、Mapbox、ECharts或任意支持GeoJSON的渲染引擎。我最初拿到它的想法很直接省去逐个站点找数据、统一坐标系、补adcode字段的时间。本文会按真实使用顺序拆解——文件结构、格式选型、合法性校验、加载展示、常见坑点最后给一段能直接跑的进阶脚本。2. GeoJSON边界数据的选型与内部结构为什么不用shp2.1 GeoJSON里到底存了什么Feature、Geometry与properties一个标准的GeoJSON文件由FeatureCollection包裹每个Feature代表一个行政区域。代码块里这段就是典型结构{ type: FeatureCollection, features: [ { type: Feature, properties: { adcode: 110000, name: 北京市 }, geometry: { type: MultiPolygon, coordinates: [ [ [[116.2, 40.1], [116.5, 40.3], [116.8, 40.5]] ] ] } } ] }逻辑上properties存行政属性geometry存边界坐标。这套文件的properties里至少带adcode和name两个字段这是它比很多网上散落的资料好用的根本原因。adcode就是行政区划代码北京是110000郑州是410100稳定且唯一不存在“简称还是全称”的争议。MultiPolygon的coordinates是四层嵌套要素级多边形、外环、线段、坐标点。解析的时候如果不小心把层级减了一两层画出来的图形就是歪的。2.2 为什么这批数据选GeoJSON而不是shp做Web端地图shp文件不能直接塞进浏览器。想用shp要么在服务端用GDAL解析要么在前端引shpjs这类第三方库去读二进制还要处理dbf编码、坐标偏移、属性表对应关系工程成本高出一截。GeoJSON是纯文本JSON.parse就能读后端下发、前端渲染、数据库存储都方便。常见的“shp文件下载”放在GIS场景里确实是主流但Web可视化场景下GeoJSON的通用性更好。这套资源统一用WGS84坐标系也就是GPS原始坐标系不是高德、百度那种做了偏移的GCJ-02或BD-09。这一点从文件里坐标能看出来WGS84的北京市中心经度在116.4左右而GCJ-02会偏到116.6附近。地图服务用Web墨卡托投影WGS84坐标叠加后不会出现边界整体平移的诡异现象。2.3 省市两级文件粒度怎么对应业务下载后打开目录会发现文件有两种粒度省级一个文件市级一个文件命名规则常见有两种一种直接用adcode数字例如110000.json、410100.json另一种用拼音比如beijing.json、zhengzhou.json。无论哪种前两位数字或拼音前缀都能对回省级单位。实际项目里我的用法是全国视角加载省级文件下钻时再动态加载对应省份的市级文件。省市级文件之间坐标范围是重叠的——省级文件里也包含市级边界坐标只是粒度更粗。直接替换不会出现空白区域。3. 下载与校验GeoJSON拿到手后的第一步该做什么3.1 下载后先看目录与完整性文件下载完成后先不要急着解压导入第一步是核对字节数。如果下载平台给了md5值直接本地算一遍没有md5的话至少对比一下压缩包大小是否和页面标注一致。常见做法是先做一个单文件完整性检查再全面解压。这一步花一分钟能挡掉百分之八十的后续诡异问题。如果你用的是jupyter环境下载文件默认会落在notebook当前工作目录不是浏览器默认的“下载”文件夹。很多人在jupyter里下载完就找不到文件其实打开左侧文件树就排在你的ipynb旁边。3.2 用Python做格式与adcode校验拿到文件后我习惯先跑一段校验脚本把格式问题、字段问题、坐标越界问题一次扫出来import json from pathlib import Path def check_geojson(path): with open(path, r, encodingutf-8) as f: data json.load(f) assert data[type] FeatureCollection, f不是FeatureCollection: {path} features data[features] print(f{path}: {len(features)} 个feature) for feat in features: geom feat[geometry] if geom[type] not in (Polygon, MultiPolygon): # 部分文件可能混入点、线做兼容 continue props feat[properties] assert adcode in props, fadcode缺失: {props} adcode str(props[adcode]) assert adcode.isdigit() and len(adcode) 6, fadcode异常: {props} return data if __name__ __main__: for fp in sorted(Path(geo).rglob(*.json)): check_geojson(fp)逻辑说明json.load直接解析如果文件下载不完整或JSON格式损坏会在这一步直接抛异常快速定位到坏文件。断言adcode必须是6位数字是为了后续和业务表join时不会出现varchar和int的匹配坑。3.3 坐标范围检查边界线为什么飞到国外边界数据里最常见的问题是坐标参考系混用。文件里大部分用WGS84但偶尔会有个别文件是GCJ-02或者干脆是错的坐标。判断方法很简单遍历所有坐标点做范围断言。def iter_coords(geometry): if geometry[type] Polygon: for ring in geometry[coordinates]: yield from ring elif geometry[type] MultiPolygon: for poly in geometry[coordinates]: for ring in poly: yield from ring def check_bounds(data): for feat in data[features]: for coord in iter_coords(feat[geometry]): lon, lat coord[0], coord[1] assert -180 lon 180, f经度越界: {lon} assert -90 lat 90, f纬度越界: {lat}参数说明以全国省级边界为例经度正常浮动范围在73到135之间纬度在3到53之间。如果跑出来一个坐标是102.7, 31.2那就是正常的如果出现35.8, -23.4说明该文件使用的坐标系或者数据源有问题直接单独处理不建议混在整套文件里批量导入。这种越界文件我遇到过两次一次是源站数据更新时坐标串了行另一次是某市文件里混进了一个岛屿的坐标。提前扫一遍后面地图上就不会出现一个点莫名跳到大西洋的情况。4. 把GeoJSON真正用起来Leaflet加载、边界简化与adcode关联数据4.1 Leaflet加载本地GeoJSON三分钟跑通这套数据前端加载最直接的方案是Leaflet加原生geoJSON方法。下面这段代码是完整可运行的模板import L from leaflet import provinceData from ./geo/province.json const map L.map(map).setView([34.6, 105], 5) L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, { maxZoom: 18 }).addTo(map) L.geoJSON(provinceData, { style: { color: #2c3e50, weight: 1, fillColor: #f0f4c3, fillOpacity: 0.3 }, onEachFeature: (feature, layer) { const name feature.properties.name || feature.properties.adcode layer.bindTooltip(name) layer.on(click, () { console.log(选中区域adcode:, feature.properties.adcode) }) } }).addTo(map)参数说明weight设为1而不是默认的3因为省界坐标点密集线宽大会让边界糊在一起。onEachFeature里取name做tooltip取adcode做点击回调这样业务表格筛选和地图交互共用一个主键。注意这里是ES Module写法vite或webpack打包工具都能直接处理json import。4.2 性能问题边界太精细时用mapshaper做简化省级边界文件动辄几百KB市级合并文件更是可以到MB级。前端渲染卡顿的根源是多边形顶点过多而不是数据量本身。这里我不推荐在前端写算法做抽稀正确做法是离线处理一次常见工具是mapshaper。它内部用的拓扑感知简化算法比盲目做Douglas-Peucker更聪明能保住相邻多边形的公共边不被割裂。npm install -g mapshaper mapshaper geo/province.json -simplify 10% keep-shapes -o formatgeojson output/province-simplify.json命令说明simplify 10%代表保留原始顶点数的10%keep-shapes参数确保简化后多边形不会碎掉、不会出现空洞。如果只做全国缩放级别5%都够用做到城市级别下钻建议30%以上。简化后文件的体积通常能降到原来的十分之一渲染流畅度提升明显。这套资源配合mapshaper使用可以灵活控制每个缩放级别的数据粒度不必在业务代码里做动态抽稀。4.3 行政区划码adcode数据关联的关键钥匙做数据地图很多时候边界只是底图真正要展示的是业务数据落在哪些区域。这时adcode是连接两套数据的主键。后端业务表往往只有“郑州市”这种字符串名称而边界文件里写的是“郑州市”带不带“市”字就是个坑。我见过一次同学用name做关联结果一半区域匹配不上最后逐个人工核对痛苦不堪。用adcode就没有这个问题。先把边界文件里的adcode与name抽成映射表def build_adcode_mapping(data): mapping {} for feat in data[features]: props feat[properties] mapping[str(props[adcode])] props[name] return mapping # 使用示例 mapping build_adcode_mapping(province_data) print(mapping[410100]) # 郑州市业务侧也按adcode聚合统计前端渲染前用映射表把adcode翻译成中文名称。这比在SQL里写一堆CASE WHEN要干净得多。这个映射表建议保存为独立json供前后端复用避免每次启动都重新解析整个边界文件。5. 常见问题排查与避坑打不开、线漂移、卡顿的四个真实场景5.1 现象文件拖进ArcGIS显示无法识别或要素空白资源下载后第一个问题往往是“geojson可以用arcgis打开吗”。直接用老版本ArcGIS打开GeoJSON很可能报错或内容空白。原因在于ArcGIS对GeoJSON支持偏弱尤其是基于早期ArcMap版本对UTF-8编码和深层嵌套坐标的识别不稳定。解决方法是换用QGIS在QGIS里打开确认数据无误后另存为shapefile再给ArcGIS使用。我自己现在的习惯是数据清洗统一走QGIS避免在ArcGIS里反复试错。5.2 现象边界整体往东北方向偏移甚至飞到海上这一条对应的是坐标系漂移。如果你拿到的文件里北京市中心坐标约在116.6左右而地图底图是Web墨卡托那说明数据是GCJ-02坐标系俗称“火星坐标”。原因是国内测绘法规下公开商业地图普遍使用加密偏移坐标直接从高德、百度等平台导出的数据通常自带偏移。解决方法是写一个坐标转换函数把GCJ-02批量转回WGS84。这个转换算法网上有开源实现核心思路是做一次迭代反算偏移量。我建议不要自己在业务代码里来回转用一次把它清洗干净并另存为WGS84文件后续使用不再有隐患。5.3 现象浏览器加载全国省市级文件直接卡死这个坑很多人会踩。全国省界约几千个顶点市界约数万个顶点如果直接加载市级合并文件FPS会掉到个位数。原因是浏览器渲染路径太多每个顶点都要参与投影计算和canvas绘制。解决思路分两步第一步用mapshaper做如4.2节描述的简化第二部是分区域懒加载全国视角只加载省级文件下钻进入某省再加载该省市级文件。懒加载代码不算复杂用Leaflet的zoom事件判断当前缩放级别低于某阈值就移除市级图层只显示省级边界。资源里的省市两级文件划分对懒加载特别友好不需要自己再去切割数据。5.4 现象下载文件打不开提示JSON parse error或者文件只有几KB这通常是下载中断或者浏览器保存了错误的页面。浏览器下载大文件时偶发断流尤其网络不稳时.json文件会截断成半截json.load直接失败。解决方法是先看下载文件的大小和源站标注是否一致如果一致还是报错再检查文件开头是不是被存成了HTML页面把下载请求直接打开看返回内容第一行是{还是!DOCTYPE。如果是后者说明下载地址需要带referer或header这时候换curl或wget加参数重下。下载完成后用md5校验是成本最低的后悔药强烈建议保留源站校验值做一次比对。6. 进阶把GeoJSON转成TopoJSON并合并市级文件减少体积与请求数6.1 TopoJSON压缩公共边界只存一次GeoJSON的先天缺点是相邻行政区共有的边界会重复存储两遍一个省和邻省的交界线在两边各自存了一次完整坐标。TopoJSON把弧线提取出来每条弧线只存一次再让多个多边形引用它体积能再缩小50%以上。转换工具还是mapshapermapshaper geo/province.json -simplify 5% -o formattopojson output/province-topo.json运行完成后会生成一份.topo.json文件。前端运行时需要先引入topojson-client将其转换回GeoJSON再交给Leaflet。要注意的是TopoJSON的properties信息会保留但如果你在下钻时需要按adcode过滤转换回来后依然能取到不需要额外处理。这个格式适合后端接口返回给前端压缩体积越小首屏加载越快。6.2 用Python合并全市级文件一份代码生成全省边界进阶需求往往是已有省级轮廓但希望展示省内各地市的颜色分级此时需要一个包含全省所有地市边界的FeatureCollection。如果这套资源是省、市分开的可以按adcode前缀批量合并。河南省的市级adcode全部以41开头import json import glob features [] for path in sorted(glob.glob(geo/41*.json)): with open(path, r, encodingutf-8) as f: data json.load(f) features.extend(data[features]) print(f合并 {path}: {len(data[features])} 个feature) merged { type: FeatureCollection, features: features } with open(henan-all.json, w, encodingutf-8) as f: json.dump(merged, f, ensure_asciiFalse)参数说明glob.glob(geo/41*.json)匹配所有以41开头的JSON文件对应河南省全部地级市。合并时要注意每个文件里的feature直接extend到总列表不需要重新组装geometry。如果合并后前端渲染tooltip取不到名字检查每个子文件的properties字段名是否一致这套资源里名称字段统一但如果你混入其他来源的文件字段名可能从name变成nazhangName这一步最容易翻车。从那以后我每次拿到新的边界文件都会强制走一遍完整流程先跑adcode与坐标范围校验再进mapshaper简化最后用TopoJSON做一次体积压缩才放进工程里用。这个习惯帮我在后续很多个可视化项目里省掉了排查数据问题的加班时间。希望帮到你也祝你的地图项目一次上屏。本文还有配套的精品资源点击获取
返回列表