资讯动态

全国乡镇行政区划JSON数据实战:坐标系、加载与避坑指南

发布时间:2026/10/14 12:36:10 来源:尧图企业网站定制
简介这是一份面向GIS开发者、城市规划与数据分析人员的地理数据资源包以JSON格式收录全国乡镇级地图数据可用于地图应用开发、人口分布分析、交通规划与灾害风险评估等场景。压缩包共2000个文件其中1997个为json文件承载各乡镇行政边界、经纬度坐标与人口等地理信息另有3个txt文件作辅助说明整体约553.27MB目录按区域与行政边界组织便于按需检索调用。目前已有507人学习下载。借助这些数据读者可结合QGIS、ArcGIS或Python的geopandas、shapely等工具开展边界可视化、人口密度展示与交通网络建模为精细化地图服务与空间决策提供基础支撑。1. 乡镇级行政区划 JSON 数据从“找不到”到“能跑通”的那份底图做数据可视化或者区域分析的朋友大概率遇到过这种场景省级、市级的 GeoJSON 一搜一大把可一旦把镜头推到乡镇一级能直接拿来用的 JSON 就突然断档了。要么是数据残缺要么是坐标系对不上要么干脆就是一张图片没法转成矢量。这份“全国乡镇地图 json.zip”解决的正是这个断层——它把全国乡镇级别的行政区划边界整理成了 JSON 格式可以直接喂给 ECharts、D3、Mapbox 或者做空间统计的 Python 脚本。适合谁做区域热力图的前端、跑乡镇级空间聚合的数据分析师、以及需要给业务系统嵌入行政区划底图的后端。它不是什么高深算法但属于那种“没有它就得自己爬半个月”的基础设施型资源。下面我按拿到压缩包之后的真实操作顺序把坐标系、字段结构、加载方式和几个容易翻车的点拆开讲。2. 先搞懂 JSON 里的坐标系和字段别等地图飘到海里才发现2.1 坐标系是第一道坎WGS84、GCJ02、BD09 到底怎么认拿到任何行政区划 JSON第一件事不是急着往地图上贴而是确认坐标系。国内常见的三种WGS84 是 GPS 原始坐标GCJ02 是国测局加密后的“火星坐标”BD09 是百度在 GCJ02 基础上又偏移一次的坐标。三者之间的偏差在乡镇级别可能达到几百米视觉上就是整个乡镇轮廓平移出去一截。怎么判断手里的 JSON 是哪种最土但最有效的办法是取一个你熟悉的乡镇边界点比如某个镇政府所在地的经纬度和公开地图上看到的做对比。如果纬度差在 0.001 到 0.005 度之间大概率是 GCJ02 或 BD09如果基本吻合就是 WGS84。另一个线索是看坐标数值范围BD09 的经度通常会比 WGS84 大零点几度。常见做法是如果这份数据要叠加在高德或腾讯地图上必须转成 GCJ02叠加在百度地图上转 BD09做纯后端空间计算不涉及底图保持 WGS84 最干净。转换公式网上有成熟实现但注意批量转换时不要逐点调用在线 API几万个乡镇边界点会把配额跑光。# 以 WGS84 转 GCJ02 为例批量处理乡镇边界坐标 import json import math def wgs84_to_gcj02(lng, lat): WGS84 转 GCJ02单点转换 a 6378245.0 ee 0.00669342162296594323 dlat transform_lat(lng - 105.0, lat - 35.0) dlng transform_lng(lng - 105.0, lat - 35.0) radlat lat / 180.0 * math.pi magic math.sin(radlat) magic 1 - ee * magic * magic sqrtmagic math.sqrt(magic) dlat (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi) dlng (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi) return lng dlng, lat dlat def transform_lat(x, y): ret -100.0 2.0 * x 3.0 * y 0.2 * y * y 0.1 * x * y 0.2 * math.sqrt(abs(x)) ret (20.0 * math.sin(6.0 * x * math.pi) 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(y * math.pi) 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0 ret (160.0 * math.sin(y / 12.0 * math.pi) 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0 return ret def transform_lng(x, y): ret 300.0 x 2.0 * y 0.1 * x * x 0.1 * x * y 0.1 * math.sqrt(abs(x)) ret (20.0 * math.sin(6.0 * x * math.pi) 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(x * math.pi) 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0 ret (150.0 * math.sin(x / 12.0 * math.pi) 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0 return ret # 假设 json 结构是 FeatureCollection with open(townships_wgs84.json, r, encodingutf-8) as f: data json.load(f) for feature in data[features]: geom feature[geometry] if geom[type] Polygon: coords geom[coordinates] for ring in coords: for point in ring: point[0], point[1] wgs84_to_gcj02(point[0], point[1]) elif geom[type] MultiPolygon: for polygon in geom[coordinates]: for ring in polygon: for point in ring: point[0], point[1] wgs84_to_gcj02(point[0], point[1]) with open(townships_gcj02.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse)这段代码的逻辑是遍历 GeoJSON 的每个几何体对 Polygon 和 MultiPolygon 两种类型分别处理坐标环。参数上唯一需要改的是输入输出文件名以及如果你拿到的是 BD09 需要先转回 GCJ02 再转 WGS84。注意ensure_asciiFalse必须加否则中文乡镇名会变成 Unicode 转义序列后续按名称匹配时容易出问题。2.2 字段结构properties 里哪些字段真正有用乡镇级 JSON 的 properties 字段通常包含行政区划代码、名称、上级代码、层级等。但不同来源的数据字段命名差异很大有的用name有的用NAME有的用town_name。拿到之后先跑一遍字段探查别假设字段名。import json with open(townships.json, r, encodingutf-8) as f: data json.load(f) # 看第一个 feature 的 properties 有哪些 key sample data[features][0][properties] print(字段列表:, list(sample.keys())) print(示例值:, sample) # 统计所有 feature 的字段一致性 all_keys set() for feat in data[features]: all_keys.update(feat[properties].keys()) print(全量字段:, all_keys)常见做法是保留adcode行政区划代码作为主键因为名称可能重复比如多个省份都有“城关镇”。adcode是六位或九位数字乡镇级通常是九位。如果数据里没有adcode只有名称那在做跨表关联时会非常痛苦建议先补上。另外注意properties里可能嵌套了center或centroid字段表示乡镇中心点。这个点在做标注或聚合时很有用但要注意它和边界坐标的坐标系是否一致。我见过一份数据边界是 GCJ02中心点却是 WGS84结果标注全部偏移。3. 把 JSON 加载到 ECharts 和 Python两条最常用的落地路径3.1 ECharts 加载乡镇级 GeoJSONregisterMap 的正确姿势前端可视化最常用的就是 ECharts。乡镇级数据量比省级大很多一个地级市下面可能有一两百个乡镇全国加起来四万多个。直接全量加载会卡通常做法是按需加载某个市或某个县的乡镇。// 按需加载某市的乡镇边界 import * as echarts from echarts; async function loadTownshipMap(cityAdcode) { // 假设乡镇 JSON 按城市代码拆分成多个文件 const response await fetch(/static/townships/${cityAdcode}.json); const geoJson await response.json(); // 注册地图名字自定义 echarts.registerMap(township_${cityAdcode}, geoJson); const chart echarts.init(document.getElementById(map)); chart.setOption({ series: [{ type: map, map: township_${cityAdcode}, roam: true, label: { show: true, fontSize: 10, formatter: (params) params.name }, itemStyle: { areaColor: #f0f0f0, borderColor: #999, borderWidth: 0.5 }, emphasis: { itemStyle: { areaColor: #ffd700 } } }] }); } loadTownshipMap(350200); // 示例城市代码逻辑说明registerMap的第二个参数必须是标准 GeoJSONfeatures数组里每个元素的geometry和properties都要完整。参数上roam: true允许缩放拖拽乡镇级别必须开否则看不清。label.fontSize建议 10 以下不然乡镇名会重叠成一团。如果加载后地图不显示先检查geometry.type是不是Polygon或MultiPolygon有些数据源会写成MultiPolygon但坐标层级少一层ECharts 会静默失败。另一个坑是name字段。ECharts 默认用properties.name作为区域名称如果你的数据里叫NAME或town需要在registerMap之前手动映射或者用nameProperty指定。我一般会在数据预处理阶段统一改成name省得前端到处改。3.2 Python 侧用 GeoPandas 做空间聚合和面积计算如果要做乡镇级的人口密度、经济指标聚合Python 的 GeoPandas 是首选。它可以直接读 JSON转成 GeoDataFrame然后做空间连接、面积计算、裁剪。import geopandas as gpd import pandas as pd # 读取乡镇边界 townships gpd.read_file(townships.json) # 查看坐标系如果是 None 需要手动指定 print(原始 CRS:, townships.crs) # 假设原始是 WGS84 经纬度 townships townships.set_crs(epsg4326, allow_overrideTrue) # 转成投影坐标系算面积比如 Albers 等面积投影 townships_proj townships.to_crs(epsg3857) # Web Mercator面积有变形但常用 townships[area_km2] townships_proj.geometry.area / 1e6 # 和业务数据表按 adcode 关联 df_business pd.read_csv(business_data.csv) # 包含 adcode 和指标 merged townships.merge(df_business, onadcode, howleft) # 计算密度 merged[density] merged[population] / merged[area_km2] # 导出为 GeoJSON 供前端使用 merged.to_file(townships_enriched.json, driverGeoJSON)参数说明set_crs里的epsg:4326是 WGS84 经纬度allow_overrideTrue用于覆盖可能错误的默认 CRS。to_crs(epsg:3857)是 Web Mercator算面积不如 Albers 准但胜在通用。如果要做精确面积统计建议用epsg:4527或对应地区的 Albers 投影。merge时注意adcode的数据类型要一致一边是字符串一边是整数会匹配不上这是血泪经验。提示GeoPandas 读 JSON 时如果文件很大全国乡镇可能上百 MB内存会吃紧。可以先用ogr2ogr按省份裁剪或者用fiona的过滤功能只读某个省。4. 避坑与排查乡镇级 JSON 最容易翻车的五个地方4.1 现象地图整体偏移几百米 → 原因坐标系混用 → 解决统一转 GCJ02 或 WGS84这是最高频的问题。表现是乡镇边界和底图路网对不上整体平移。原因通常是数据是 WGS84底图是 GCJ02或者反过来。解决方式是在加载前统一转换。如果只是做纯数据展示不叠加底图可以忽略但一旦叠加高德/腾讯底图就必须转。4.2 现象部分乡镇缺失或边界断裂 → 原因数据源本身有空洞 → 解决用上级行政区划做差集补全有些免费数据源在乡镇级别会有缺失尤其是偏远地区。表现是某个县下面少了几个乡镇或者边界之间有缝隙。解决方式是用该县的边界减去已有乡镇的并集得到空洞区域再手动补。如果空洞太大建议换数据源。4.3 现象ECharts 加载后白屏无报错 → 原因GeoJSON 格式不标准 → 解决用 geojsonlint 校验ECharts 对 GeoJSON 的容错性一般如果coordinates层级不对或者type拼写错误它会静默失败。常见的是MultiPolygon的坐标层级应该是四层数组有些数据写成三层。用在线 geojsonlint 或者 Python 的geojson库校验一遍能省很多调试时间。4.4 现象按名称匹配数据时大量丢失 → 原因乡镇重名或名称带后缀 → 解决用 adcode 做主键“城关镇”“新城镇”这种名字在全国重复率极高。如果业务数据只有名称没有 adcode匹配时会一对多。解决方式是尽量获取 adcode或者用“省市县乡镇”四级名称拼接做联合主键。另外注意名称里的“镇”“乡”“街道”后缀是否一致有的数据带后缀有的不带。4.5 现象文件太大导致浏览器卡死 → 原因全量加载全国乡镇 → 解决按需拆分或简化边界全国乡镇 JSON 可能几十 MB直接丢给浏览器会卡。解决方式是按省或市拆分成多个文件前端按需请求。另一个方式是做边界简化用mapshaper或topojson减少点数乡镇级别简化到 10% 精度视觉上几乎看不出差别但体积能降一个数量级。# 用 mapshaper 简化边界并拆分 mapshaper townships.json -simplify 10% keep-shapes -split city_adcode -o formatgeojson这条命令的逻辑是保留 10% 的顶点keep-shapes防止小乡镇被简化没-split按城市代码拆分成多个文件。参数上10%可以调整乡镇级别建议 5% 到 15% 之间。5. 进阶技巧用 TopoJSON 压缩和按需加载把全国乡镇塞进一个页面如果你真的需要在一个页面里展示全国乡镇直接加载 GeoJSON 基本没戏。这时候 TopoJSON 是更优解。它把共享边界只存一次体积能比 GeoJSON 小 60% 到 80%。配合按需加载和边界简化全国乡镇也能跑起来。import * as topojson from topojson-client; import * as echarts from echarts; async function loadNationalTownships() { const response await fetch(/static/townships_topo.json); const topology await response.json(); // 把 TopoJSON 转成 GeoJSON const geojson topojson.feature(topology, topology.objects.townships); echarts.registerMap(national_townships, geojson); const chart echarts.init(document.getElementById(map)); chart.setOption({ series: [{ type: map, map: national_townships, roam: true, scaleLimit: { min: 1, max: 20 }, label: { show: false }, // 全国级别不显示标签太密 itemStyle: { areaColor: #e7e7e7, borderColor: #bbb, borderWidth: 0.3 } }] }); }逻辑说明topojson.feature把拓扑结构还原成标准 GeoJSONtopology.objects.townships是你在生成 TopoJSON 时定义的名称。参数上scaleLimit.max: 20允许放大到看清乡镇label.show: false在全国视角下必须关掉否则渲染几万个标签直接卡死。用户放大到一定级别后再动态显示标签这个可以用georoam事件监听缩放级别来实现。生成 TopoJSON 用geo2topo命令# 先生成 TopoJSON再简化 geo2topo townshipstownships.json townships_topo.json toposimplify -s 0.01 -f townships_topo.json -o townships_topo_simplified.json-s 0.01是简化阈值数值越大简化越狠。乡镇级别建议 0.001 到 0.01 之间根据实际视觉效果调。另一个技巧是结合adcode做按需加载。用户点击某个省时只请求该省的乡镇 TopoJSON而不是全国。这样首屏加载时间能从十几秒降到一两秒。我一般会在数据预处理阶段按省拆好前端维护一个adcode到文件路径的映射表。注意TopoJSON 的objects名称必须和生成时一致否则topojson.feature会返回 undefined。另外 TopoJSON 的坐标系继承自原始 GeoJSON转换前先确认坐标系。从那以后我每次拿到新的行政区划 JSON都强制走一遍“坐标系确认 → 字段探查 → 格式校验 → 按需拆分”的流程不再直接往地图上贴。这套流程帮我省掉了至少三次通宵排查偏移问题的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑