ARTICLE DETAIL

资讯详情

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

各省市GeoJSON文件下载与可视化实战指南

各省市GeoJSON文件下载与可视化实战指南 简介这是一份覆盖中国全部省级行政区划的GeoJSON数据集合专为Web地图开发、GIS分析与前端可视化场景设计。资源内含34个JSON文件对应全国34个省级单位以Feature Collection形式组织每个文件包含对应省份的边界几何信息及名称、行政代码等属性可直接在JavaScript中调用也兼容turf.js、geopandas等常见地理处理工具。GeoJSON本身轻量易解析支持Polygon、MultiPolygon等几何类型可直接用于浏览器渲染或服务端空间计算。压缩包整体约709KB结构清晰适合需要快速获取省界数据、进行空间统计或构建全国地图应用的开发者使用。目前已有927人学习下载对于需要省市级地理边界数据、又不想自行从开放平台逐个爬取的用户这份打包好的数据能显著节省预处理时间也可作为基础底图与人口、交通、环境等专题数据叠加服务于智慧城市、区域规划等场景。1. 各省市 GeoJSON 文件下载数据齐了地图才不翻车做前端数据可视化或 GIS 分析时第一步往往不是写代码画图而是找一份能用的省市边界 GeoJSON。这个需求听着简单真动手才发现有的数据源边界是旧的有的坐标系不是 WGS84有的文件下载下来只有几 KB一解析就报错。我最早做省级地图时也以为下载几个 json 就行结果前半天全耗在修数据上。这篇笔记就围绕“geojson 各省市文件下载”这条主线讲清楚数据从哪来、怎么批量落地、下载后怎么校验和排错。适合用 ECharts、Leaflet 做行政区划可视化以及需要把 SHP 转 GeoJSON 的 GIS 从业者照着做。2. 从数据源到本地先选对 GeoJSON 下载渠道找数据源时我一般分两步先看有没有现成 GeoJSON没有再看 SHP 转换。现成 GeoJSON 里最常用的是阿里 DataV 的开放接口而国土、规划口径的数据往往只发 SHP就需要 GDAL 转一手。无论哪条路下载后都要做一次文件体检避免把坏数据直接喂给前端。2.1 阿里 DataV GeoJSON 数据源最省事的省市边界阿里 DataV 的 GeoJSON 接口没有鉴权、返回标准 FeatureCollection很多开源 ECharts 地图示例都在用。它的 URL 规则很简单按 6 位行政区划代码adcode组织全国https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json省级https://geo.datav.aliyun.com/areas_v3/bound/310000_full.json市级https://geo.datav.aliyun.com/areas_v3/bound/310100_full.json这里的full表示带几何边界的完整文件不带full的同路径 json 只有子区域列表没有几何。下载单个文件用 curl 就够了# 下载全省边界-L 跟随跳转--fail 让 404 不落盘 curl -L --fail --retry 3 \ -A Mozilla/5.0 (Windows NT 10.0; Win64; x64) \ -o 100000_full.json \ https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json # 确认文件不是错误页 ls -lh 100000_full.json head -c 200 100000_full.json逻辑说明-A带浏览器 UA避免被源站反爬策略拦掉head -c 200看前 200 字节如果看到{type说明是 JSON如果看到html说明被重定向到了错误页。--retry 3在网络抖动时自动重试但注意它不会对 404 重试所以还要配合--fail。选择这个渠道的理由是省事、层级全、直接给 WGS84 坐标。但它也有明显边界数据精度最高到县级乡镇街道边界经常缺失行政区划调整后更新有滞后。如果项目只需要省市级展示它可以作为首选要精确到乡镇街道就得换天地图开放服务或地方测绘部门数据。2.2 从 SHP 转 GeoJSON当数据源只有 Shapefile 时的处理很多规划、国土部门只发布 SHP这也是“shp 文件下载”在检索里一直很热的原因。SHP 是二进制多文件格式浏览器没法直接用需要转成 GeoJSON。常见做法是装 GDAL用ogr2ogr一行完成转换。注意 SHP 至少包含.shp、.dbf、.shx三个文件命令里写主文件名就行# 基础转换统一到 WGS84 坐标 ogr2ogr -f GeoJSON \ -t_srs EPSG:4326 \ -lco COORDINATE_PRECISION6 \ output.geojson input.shp # 属性中文乱码时强制按 GBK 读取 dbf ogr2ogr -f GeoJSON \ -t_srs EPSG:4326 \ --config SHAPE_ENCODING GBK \ -lco COORDINATE_PRECISION6 \ output.geojson input.shp逻辑说明-t_srs EPSG:4326把源坐标系转到经纬度很多国内测绘数据是 CGCS2000 或高斯投影不转直接出图会偏移几百米到几公里。COORDINATE_PRECISION6保留 6 位小数约 0.1 米精度文件体积能缩小一半以上。第二个命令里的--config SHAPE_ENCODING GBK是关键SHP 的 dbf 属性表常用 GBK 编码不指定的话转换后的中文属性全是乱码。参数说明源坐标系未知时先用ogrinfo -so -al input.shp查看字段块里的坐标信息。如果显示的是PROJCS或LOCAL_CS就要向数据提供方确认坐标系代码不能瞎转。转换后我会再用ogrinfo -al -so output.geojson统计要素数和源文件对比防止漏要素。2.3 下载后的文件校验看一眼 JSON 结构和坐标系不管从哪个渠道拿到文件先做 30 秒体检。我用 Python 检查type、features数量、几何类型和属性字段import json def check_geojson(path): with open(path, r, encodingutf-8) as f: data json.load(f) assert data[type] FeatureCollection, type 不是 FeatureCollection feats data[features] print(要素数:, len(feats)) geoms {f[geometry][type] for f in feats if f.get(geometry)} print(几何类型:, geoms) props feats[0][properties] print(属性字段:, list(props.keys()) if isinstance(props, dict) else props) print(bbox:, data.get(bbox, 无))逻辑说明先断言最外层类型再统计几何类型集合。如果出现GeometryCollection、LineString说明这不是纯面边界数据前端渲染要多写分支。打印属性字段是为了看清省份名称存在哪个键里后面做下钻和匹配要用。参数说明这是一个快速脚本严谨做法还要遍历所有 feature 检查geometry是否为空。县级数据里空几何比较多比例超过 5% 就要考虑换数据源。判断坐标系有一个土办法如果经纬度在 60140、1055 之间基本是 WGS84 或 GCJ02 这类经纬度坐标如果坐标变成几十万的大数那是投影坐标系必须用ogr2ogr -t_srs EPSG:4326转回。3. 用脚本批量下载省市县三级边界Python 实现与参数说明单个文件能下载只是开始真正要落地的是批量拉全国省市数据。这里我一般分三层做先写一个不依赖外部库的最小下载函数再套线程池和重试最后用 manifest 文件记录每个文件的哈希和来源。3.1 单文件下载的最小脚本requests 与超时重试import requests from pathlib import Path def download_geojson(adcode, out_dirdata, timeout(5, 20)): out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) url fhttps://geo.datav.aliyun.com/areas_v3/bound/{adcode}_full.json target out_dir / f{adcode}_full.json headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} # streamTrue 让响应体按块写入避免大文件撑爆内存 with requests.get(url, headersheaders, timeouttimeout, streamTrue) as r: r.raise_for_status() with open(target, wb) as f: for chunk in r.iter_content(chunk_size8192): if chunk: f.write(chunk) print(f{adcode} - {target} ({target.stat().st_size} bytes))逻辑说明streamTrue配合iter_content按 8KB 块写盘下载中断时不会把半份文件读到内存里才报错。timeout(5, 20)表示连接超时 5 秒、读取超时 20 秒只填一个数字是整体超时对慢网络不够精确。raise_for_status()在 HTTP 4xx/5xx 时直接抛异常避免把 404 错误页当成 JSON 存下来。参数说明adcode是 6 位行政区划代码out_dir会自动创建。如果源站对无 UA 的请求返回 403统一加浏览器 UA 即可。不需要动verifyFalse除非你明确知道是内网证书问题。3.2 批量下载全国省市数据并发、限速与断点续传import json import time import concurrent.futures as cf import requests def get_children(adcode100000): # 不带 full 的 json 返回子区域列表字段里有 adcode r requests.get( fhttps://geo.datav.aliyun.com/areas_v3/bound/{adcode}.json, timeout10 ) r.raise_for_status() data r.json() return [item[adcode] for item in data if adcode in item] def safe_download(adcode, retries3): for i in range(retries): try: download_geojson(adcode, out_dirdata) return adcode, True except Exception as e: print(adcode, 重试, i 1, e) time.sleep(2 ** i) # 指数退避 2s/4s/8s return adcode, False adcodes get_children(100000) # 全国 34 个省级 adcode with cf.ThreadPoolExecutor(max_workers4) as pool: results list(pool.map(safe_download, adcodes)) failed [r for r in results if not r[1]] print(失败列表:, failed)逻辑说明get_children先取省级列表拿到 34 个 adcode。safe_download包了一层重试异常时按 2 的指数退避等待避免失败后立即重试再次被打回。ThreadPoolExecutor(max_workers4)限住并发数对源站友好也避免大量小文件同时写盘时磁盘 IO 争抢。参数说明max_workers我一般用 46如果目标是市县一级总文件数上千可以提到 8但一定要配合限速。GeoJSON 单文件大部分在几百 KB 到几 MB瓶颈通常不在带宽而在源站限流策略。真要较真先单线程下载一个几十 MB 的文件测速再决定并发数。断点续传这里没有做因为阿里 DataV 不保证支持 Range 请求失败后整体重下最省事只有当你确认源站响应头里有Accept-Ranges: bytes时才有必要实现分块续传。3.3 文件落地后的目录组织与命名规范import json import hashlib from pathlib import Path from datetime import datetime def build_manifest(source_dirdata): records [] for path in sorted(Path(source_dir).glob(*.json)): if path.name manifest.json: continue raw path.read_bytes() records.append({ file: path.name, size: len(raw), sha256: hashlib.sha256(raw).hexdigest(), mtime: datetime.now().isoformat(timespecseconds) }) out Path(source_dir) / manifest.json out.write_text( json.dumps(records, ensure_asciiFalse, indent2), encodingutf-8 ) return out逻辑说明每次下载完一批把所有文件的字节数、SHA256 和写入时间记到manifest.json。下次更新时先读旧 manifest对比 sha256变了才重新下载。path.read_bytes()对小文件没问题但如果单个 GeoJSON 超过 50MB建议改成流式分块计算哈希。参数说明文件名不要用中文。中文名在部分 HTTP 服务里会触发多次转义跨平台解压也容易乱码。我习惯用{adcode}_full.json这种纯数字命名省份名称放在properties里前端要显示时再读属性。manifest.json要单独跳过不然下次扫描会把自己也记进去。4. GeoJSON 文件打不开、边界错乱、坐标不对下载后的 5 个常见坑数据下载完不代表结束真正让项目翻车的全是下载后的小问题。这一章集中写我踩过的坑每一条都按现象、原因、解决的顺序说。4.1 geojson 可以用 ArcGIS 打开吗先看坐标系与编码现象把一个 GeoJSON 拖进 ArcGIS Pro地图上是空白或者提示“无法读取数据源图层”有时候能显示但位置偏到海里。原因很多人搜“geojson 可以用 arcgis 打开吗”答案是“能但有前提”。ArcGIS 对 GeoJSON 的支持要求外层结构是标准 FeatureCollection坐标系要是 WGS84 地理坐标系且属性字段编码是 UTF-8。如果你下载的数据是 GCJ02 加密坐标或者 SHP 转出来的属性表是 GBK 且没带上编码声明ArcGIS 就会出现上述怪现象。解决先用 QGIS 打开同一个文件做交叉验证。如果 QGIS 正常而 ArcGIS 不正常多半是坐标系或编码问题。有一个通用修复命令# 用 GDAL 强制转成 ArcGIS 最认的标准结构 ogr2ogr -f GeoJSON \ -t_srs EPSG:4326 \ -lco COORDINATE_PRECISION6 \ -lco WRITE_BOMYES \ fixed.geojson broken.geojsonWRITE_BOMYES会在文件头加 BOMArcGIS 对带 BOM 的 UTF-8 识别更稳定。坐标系的修复不能靠猜先ogrinfo -so -al broken.geojson看定义再决定要不要转。4.2 县级边界合并后出现飞线/缺口现象下载了某省所有区县的 GeoJSON用前端库做 dissolve 合并成省级边界结果相邻两县之间有细缝放大看还有一块块小缺口更严重的是路径飞到很远的地方出现一条横跨屏幕的“飞线”。原因共享边界在两份区县数据里可能没有精确对齐坐标在小数点后两位就开始分叉。前端 dissolve 只按几何求并集不处理拓扑关系所以裂缝会直接暴露出来。飞线通常是某个多边形的坐标串里混入了错误坐标点或者文件本身下载不完整。解决用后端计算引擎合并比如 Shapely 的unary_union先做拓扑修复再导出from shapely.geometry import shape from shapely.ops import unary_union import json with open(counties.geojson, encodingutf-8) as f: data json.load(f) geoms [shape(feature[geometry]) for feature in data[features] if feature.get(geometry)] merged unary_union(geoms) # 用 buffer(0) 修复微小的自相交和裂缝 merged_fixed merged.buffer(0) print(合并后类型:, merged_fixed.geom_type)buffer(0)是一个经典技巧它会把小于容差的缝隙吸掉同时清理自相交但不改变坐标精度。合并前最好先检查每个要素is_valid把无效几何单独过滤出来避免坏点污染整体。4.3 文件下载不完整但没报错Content-Length 与 gzip 的坑现象一个市级 GeoJSON 应该有 400 个乡镇下载下来解析成功但数出来只有 380 个文件在浏览器里打开也正常就是少了几个边界。原因下载过程中连接被源站断开requests 默认在Content-Length对不上时会抛ChunkedEncodingError但如果源站返回的是Transfer-Encoding: chunked没有 Content-Length断流时可能只警告而不报错。还有一种情况是源站在网关层把 gzip 压缩后的字节数作为 Content-Length而客户端解压后文件大小对不上。解决下载后强制校验实际字节数与 Content-Length 不一致就重试import requests def download_with_length(url, target): with requests.get(url, streamTrue, timeout15) as r: r.raise_for_status() expected r.headers.get(Content-Length) total 0 with open(target, wb) as f: for chunk in r.iter_content(chunk_size8192): f.write(chunk) total len(chunk) if expected and total ! int(expected): raise IOError(f下载不完整: {total} ! {expected})注意如果服务器返回了Content-Encoding: gzip你可以把expected和total都取解压后的字节数对比或者直接把响应体的raw.read()拿来用。最简单的做法是校验失败就删掉重下宁可多费一次请求也不要留下一个残文件。4.4 数据源更新导致 URL 失效版本化与镜像现象半年前写好的下载脚本今天跑出来全是 404或者返回的 JSON 里多了一批新区划属性字段也变了。原因GeoJSON 数据源不是稳定契约阿里 DataV 从areas_v2换到areas_v3时旧 URL 批量失效区划调整后 adcode 也会变比如莱芜并入济南后相关代码就失效了。很多项目直接把在线 URL 写死在前端这是最脆弱的做法。解决下载到本地后做一次版本化快照。我在数据目录里放一个source.json记录下载时间、源 URL 和 adcode 列表另外把manifest.json里的 sha256 作为文件逻辑版本。更新时先跑 HEAD 请求看Last-Modified或ETag变了才重新下载。这样即使源站哪天删掉老版本本地镜像仍然可用。不要长期依赖在线 URL这是做数据项目的基本纪律。4.5 下载速度慢/被限流测速、重试与限流策略现象批量下载时前 10 个文件很快到第 50 个突然全部超时稍等一会又能下载但速度掉到几十 KB/s。原因大概率是源站对单一 IP 做了限流或者上了防火墙策略。GeoJSON 文件虽小但高并发短请求会触发网关的 rate limit。解决在脚本里加“下载文件测速”这一步先探底再跑量# 下载前先测一条大文件的速度-o /dev/null 丢弃内容 curl -o /dev/null -s -w 速度: %{speed_download} bytes/s\n \ https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json拿到基准速度后把并发线程数设置为一个保守值线程内再限制请求频率。我自己的习惯是单线程速度大于 500KB/s 时用 4 并发小于 100KB/s 时只开 1 并发同时把超时时间拉长。配合上一章的指数退避重试基本能熬过限流窗口。5. 把下载的 GeoJSON 用起来本地数据服务与可视化验证数据落地后下一步就是让前端能访问到。我从来不直接双击 index.html 看效果而是起一个本地 HTTP 服务顺便解决跨域问题。这样能尽早发现坐标系、字段命名和文件损坏的问题。5.1 用 Python 做本地 HTTP 服务供前端测试直接用python -m http.server虽然快但它的目录浏览在部分浏览器上有缓存而且不能自定义响应头。我一般写一个小脚本统一挂载数据目录并加 CORS 头from http.server import HTTPServer, SimpleHTTPRequestHandler import os os.chdir(data) # 数据目录里面放着 *_full.json class Handler(SimpleHTTPRequestHandler): def end_headers(self): # 给所有响应加 CORS 头前端跨域请求不会挂 self.send_header(Access-Control-Allow-Origin, *) super().end_headers() def log_message(self, fmt, *args): # 减少控制台噪音只记录访问路径 print(self.path) HTTPServer((127.0.0.1, 8000), Handler).serve_forever()逻辑说明os.chdir(data)让服务根目录切到数据目录URL 路径直接就是/100000_full.json。end_headers里先加Access-Control-Allow-Origin: *这样用 ECharts 或 Leaflet 从另一个端口调试时不会被浏览器 CORS 拦截。参数说明127.0.0.1只监听本机适合本地调试如果要把数据暴露到局域网给同事看改成0.0.0.0但要注意服务没有认证不要放到公网。8000端口如果被占用换成 8080 或 9000 都行。5.2 在 Leaflet 中快速验证数据是否可用!DOCTYPE html html langzh-CN head meta charsetUTF-8 title验证 GeoJSON/title link relstylesheet hrefhttps://unpkg.com/leaflet1.9.4/dist/leaflet.css script srchttps://unpkg.com/leaflet1.9.4/dist/leaflet.js/script /head body div idmap styleheight: 500px/div script fetch(http://127.0.0.1:8000/100000_full.json) .then(r r.json()) .then(data { const map L.map(map).fitBounds( L.geoJSON(data).getBounds() ); L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, { maxZoom: 12 }).addTo(map); L.geoJSON(data, { style: { color: #d00, weight: 1, fillOpacity: 0.2 }, onEachFeature: (feature, layer) { layer.bindPopup(feature.properties?.name || 无名称); } }).addTo(map); }); /script /body /html逻辑说明先用fetch拉取本地服务里的 GeoJSON再用L.geoJSON(data).getBounds()算出边界范围调用fitBounds让地图自动缩放。这个动作能立刻暴露坐标系问题如果数据是从投影坐标系错误转出来的地图会飞到一个奇怪的位置。onEachFeature里绑定弹出框顺手验证properties里能不能取到中文名称。参数说明这个页面必须通过 HTTP 访问不能直接file://打开否则 fetch 会被浏览器安全策略挡掉。Leaflet 用的是 OpenStreetMap 瓦片国内网络访问不稳定时可以换成其他瓦片源但这一步只是为了验证数据不要让它成为网络瓶颈。5.3 简化与压缩降低前端加载体积的实用技巧下载的 GeoJSON 精度很高直接塞给浏览器可能会让页面卡顿。以全国边界为例原始文件可能几十 MB简化到 5% 的顶点规模后视觉差异几乎看不出来。# 安装 mapshaper 后做占位最优简化 npx mapshaper 100000_full.json \ -simplify 5% keep-shapes \ -o 100000_full_simplified.json # 再看看简化后的文件大小 ls -lh 100000_full_simplified.json逻辑说明-simplify 5%意思是保留约 5% 的顶点keep-shapes保证简化时不会把多边形面积缩到零或直接删除小岛。mapshaper 的简化算法对边界保持做得比简单抽稀好适合前端可视化场景。参数说明5%不是固定值省级数据我用 10%市级用 5%乡镇街道用 20%。简化后务必用第 5.2 节的 Leaflet 页面重新验证一遍重点看沿海岛屿和跨行政区边界有没有粘连。这一步不要在第一版就开始做先拿着完整精度数据跑通流程最后再决定压缩比例。6. 最后一步给数据做版本管理与自动更新数据文件下到本地、验证通过后最容易被忽略的是更新机制。行政区划不是一成不变的每过一两年就有合并、撤县设区、乡镇撤并。我不会手动去刷新下载而是在数据目录里维护一个轻量级的source.json记录每个文件的来源和哈希import json, hashlib from pathlib import Path def verify_local_data(source_path): with open(source_path, encodingutf-8) as f: source json.load(f) for item in source[files]: path Path(item[path]) if not path.exists(): print(path, 缺失) continue digest hashlib.sha256(path.read_bytes()).hexdigest() if digest ! item[sha256]: print(path, 内容已变化) else: print(path, 一致)逻辑说明这个脚本不请求外部接口只做本地一致性检查。每次重新下载或手工替换文件后更新source.json里的 sha256 和下载时间。等到源站发布新版本时你只需要比较 remote 的 ETag 与记录里的时间就能知道哪些文件需要重下。我的习惯是把source.json和manifest.json一起提交到 Git 仓库。数据文件可能太大不进版本库但这两个 JSON 文件体积很小能帮你回溯“这个边界是哪天谁下的、当时的源 URL 是什么”。这个习惯让我少踩了很多次“数据悄悄过期”的坑也希望帮到你。本文还有配套的精品资源点击获取
返回列表