ARTICLE DETAIL

资讯详情

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

世界国家GeoJSON从结构到体积优化:数据大屏地图数据落地指南

世界国家GeoJSON从结构到体积优化:数据大屏地图数据落地指南 简介这份世界地图地理数据合集收录了全球各国的地图 JSON 文件面向需要快速获取国家边界与地理属性的前端开发者、GIS 分析人员及数据可视化爱好者。压缩包共包含 2000 个文件主要为 JSON 与 GeoJSON 格式整体大小约 2.1MB内附全球汇总文件以及多国独立地图数据如中国、美国、俄罗斯等文件命名清晰便于按国家或地区直接调用。数据遵循 GeoJSON 标准包含国家边界多边形、属性信息与 WGS84 坐标系统可在 Leaflet、Mapbox 等主流地图库中加载并渲染交互式世界地图也可解析属性字段完成国家面积测算、区域统计、邻国关系分析等地理空间处理。已有 4729 人学习使用适合作为地图可视化开发、GIS 教学、前端项目练习及相关科研项目的基础数据源尤其适合需要在离线环境中快速搭建地图服务的场景。 做海外业务的数据大屏时我最先找的往往不是图表库而是一份能用的世界地图 GeoJSON。所谓世界国家 GeoJSON本质上是把每个国家的边界坐标揉进 JSON 数组里的数据文件前端地图库拿过去就能直接渲染。这份数据听着简单真用起来全是细节坐标顺序、属性字段、边界画法、文件体积每一项都能让你在检查的时候返工。这篇文章就按我平时的落地顺序讲从数据结构开始再到下载转换、清洗简化、踩坑记录最后给一个压体积的收尾技巧。2. 读懂 GeoJSON 结构FeatureCollection、坐标顺序与闭合环2.1 为什么说 GeoJSON 只是 JSON 的一种约定JSON 是通用数据格式数组、对象、字符串都能装GeoJSON 则是用 JSON 表达地理要素的一套约定。一个完整的 GeoJSON 文件最外层是 FeatureCollection里面是多个 Feature每个 Feature 必须包含 type、properties、geometry 三个部分。很多新手拿到 json 文件后直接用 JSON.parse 解析结果发现数据结构和想象中对不上就是因为没先分清 geo 语义。下面是最小可用的 Feature 结构{ type: Feature, properties: { name: Japan, iso_a2: JP }, geometry: { type: MultiPolygon, coordinates: [ [ [ [139.0, 35.0], [140.0, 35.0], [140.0, 36.0], [139.0, 36.0], [139.0, 35.0] ] ] ] } }coordinates 里装的是经纬度坐标点每一个点是 [经度, 纬度] 的 JSON 数组形式。geometry.type 可以是 Point、LineString、Polygon、MultiPolygon 四种基本类型国家边界几乎全是 Polygon 或 MultiPolygon。写代码时最容易踩的就是层级数错Polygon 的 coordinates 是三维数组第一层是环第二层是点MultiPolygon 是四维数组最外层多个多边形每个多边形再套环。提示坐标顺序永远是 [经度, 纬度]不是 [纬度, 经度]。这是 GeoJSON 规范里最容易让人翻车的一条。2.2 典型属性字段name、name_en 与 ISO 编码世界各国边界 GeoJSON 的来源不同properties 字段差异很大。Natural Earth 风格的字段常是大写NAME、NAME_EN、ISO_A2、ISO_A3国内一些在线下载站给的是小写name、name_en、iso_a2、adcode。字段名不一致直接导致后期代码写死 key 时拿不到值。我拿到一份新数据第一件事是先把所有字段名拉出来看import json with open(world.geojson, encodingutf-8) as f: data json.load(f) for feat in data[features]: props feat[properties] print(props.keys()) print(props.get(name) or props.get(NAME), props.get(iso_a2) or props.get(ISO_A2)) break这段逻辑很简单只打印第一个 Feature 的字段名和取值先确认字段命名风格再写后续代码。实际项目中数据源基本不会统一所以建议一开始就做字段归一化见第四章。iso_a2 这类编码比中文名更稳定因为中文名在不同地区叫法不同而两字母编码是全球通用约定拆分“各国地图 json 数据”时我用它当文件名。2.3 坐标顺序与闭合环两个必查的细节坐标顺序问题前面提了这里给一个可执行检查。闭合环指的是 Polygon 的环首尾点必须相同不然渲染时会出现一道莫名其妙的裂缝。很多公开数据里会缺这个要求必须自己补。def fix_rings(feature): 确保 Polygon 的每个环都是闭合的 geom feature[geometry] if geom[type] Polygon: rings [geom[coordinates]] elif geom[type] MultiPolygon: rings geom[coordinates] else: return feature for poly in rings: for ring in poly: if ring[0] ! ring[-1]: ring.append(ring[0]) return feature这段代码的一个关键点是遍历层级Polygon 的 coordinates 里直接是环MultiPolygon 的 coordinates 里先是一层多边形再往下才是环。处理时先统一包一层再迭代。如果不做这个处理有些渲染引擎会把没闭合的边界直接丢弃导致某个国家只剩半块领土这个现象排查起来非常费劲。第二个细节是环的方向。GeoJSON 规范建议外环逆时针、内环顺时针实际渲染引擎大多不强制但 QGIS 和 PostGIS 这类工具对方向敏感。如果你后期要把 GeoJSON 导入数据库最好先统一方向。3. 世界国家 GeoJSON 从哪来下载、转换与按国家拆分3.1 常见数据源怎么选从在线下载到 Natural Earth数据源的选择直接影响坐标精度、属性完整度和后续工作量。我常用的来源主要是这几类数据源原始格式坐标基准精度优点注意点DataV.GeoAtlas 在线下载GeoJSONWGS84中中文属性齐全按国家/省/市直接取 json边界画法适合国内项目上线前仍要核对Natural EarthSHPWGS84高字段规范全球边界数据完整需要 ogr2ogr 转换属性是英文大写OpenStreetMap 关系导出PBF / OSMWGS84高边界更新快需要裁剪和处理关系表成本高geojson.xyzGeoJSONWGS84中大文件切片按需取属性字段不统一需自清理国内项目我一般优先从 DataV.GeoAtlas 这类在线服务取数因为它自带符合国内审图习惯的边界画法。做全球分析或学术项目时Natural Earth 是更稳妥的选择因为它字段规范且更新历史清晰。OSM 虽然数据最细但把它导出成干净的国界 GeoJSON需要处理关系relations成本最高。注意不同平台对同一区域的边界画法会不一致尤其表现在岛屿、飞地和一些跨境区域。这不是某个数据源的 bug而是各个平台采用的出版规范不同。业务上线前一定要以业务所在地区的合规地图规范为准不要自己拿两套数据拼接。3.2 用 ogr2ogr 把 SHP 转成 GeoJSON一行命令与 4 个参数Natural Earth 下载下来是 SHP 格式前端地图库直接读不了 SHP必须转成 GeoJSON。最常用的工具是 GDAL 自带的 ogr2ogr一条命令就够ogr2ogr -f GeoJSON \ -t_srs EPSG:4326 \ -lco COORDINATE_PRECISION6 \ world.geojson ne_110m_admin_0_countries.shp四个参数说明一下-f 指定输出格式为 GeoJSON-t_srs 把坐标系强制转成 EPSG:4326也就是 WGS84 经纬度避免数据源里夹杂投影坐标-lco COORDINATE_PRECISION6 控制坐标保留 6 位小数约 0.1 米精度展示足够体积也能压住最后分别写输出文件和输入 SHP 路径。如果你只需要某一个国家可以在转换时用 SQL 过滤ogr2ogr -f GeoJSON \ -sql SELECT * FROM ne_110m_admin_0_countries WHERE ISO_A3 CHN \ -t_srs EPSG:4326 \ china.geojson ne_110m_admin_0_countries.shp注意 -sql 里的表名是 SHP 文件名去掉后缀的名称同时字段名要用数据源里真实存在的否则会报“no such column”。这个命令在 Windows 下没有直接命令行环境时可以用 QGIS 自带的工具箱替代道理一样。3.3 json 转 shp 的逆转换为什么不建议用在线网站反向转换在项目里也常遇到比如老 GIS 系统只认 SHP或者要给 GeoServer 发数据。命令同样简单ogr2ogr -f ESRI Shapefile world.shp world.geojson很多人在网上找“json 转 shp 网站”图个省事。但我不建议把国界数据往在线转换工具里传原因有两个一是数据量大的时候在线工具容易超时二是国界数据涉及合规边界在本地完成转换更可控。本地装 GDAL 其实不难Linux 上 apt 安装 gdal-binmacOS 用 brewWindows 装 OSGeo4W 或者直接用 QGIS血泪经验告诉我环境装一次后面能省大量时间。3.4 按国家拆分 JSON一个脚本批量产出各国地图 json标题里说的“各国地图 json 数据”是项目里最常见的需求前端按国家加载而不是一次性加载整个世界地图。我习惯用 Python 脚本按 ISO 编码把大文件拆成单个国家的 FeatureCollectionimport json from pathlib import Path with open(world.geojson, encodingutf-8) as f: data json.load(f) out_dir Path(countries) out_dir.mkdir(exist_okTrue) for feat in data[features]: props feat[properties] code props.get(iso_a2) or props.get(ISO_A2) name props.get(name) or props.get(NAME) if not code: continue file_name f{code}.json single { type: FeatureCollection, features: [feat] } (out_dir / file_name).write_text( json.dumps(single, ensure_asciiFalse), encodingutf-8 )这段脚本有两个关键点。一是文件命名用 iso_a2 而不是中文名因为中文名在 URL 里要编码且不同系统叫法不一二是 ensure_asciiFalse 保证中文属性直接落盘为 UTF-8否则会变成一串 \uXXXX 的转义既难读又难排查。拆完以后前端可以只加载需要的国家文件页面首屏能快一大截。4. 用 Python 清洗各国地图 JSON字段归一化与精度压缩4.1 用 Python 做数据体检先看数量、类型和缺失拿到一份新数据先别急着渲染花两分钟做个体检能避免后面的连带返工。import json data json.load(open(world.geojson, encodingutf-8)) print(feature 数量:, len(data[features])) missing_name [ f[properties] for f in data[features] if not f[properties].get(name) and not f[properties].get(NAME) ] print(缺少 name 的要素:, len(missing_name)) types {} for f in data[features]: t f[geometry][type] types[t] types.get(t, 0) 1 print(几何类型分布:, types)数量检查能快速发现数据是否被截断全球国家加地区一般在 200 个左右如果只有几十个八成是数据源不完整。几何类型分布则帮你判断是否需要同时处理 Polygon 和 MultiPolygon很多项目只写了 Polygon 路径遇到 MultiPolygon 就变成黑洞。这一阶段不要追求复杂的面积计算用 shapely 更可靠自己手写经纬度面积公式容易在环嵌套上翻车。4.2 字段归一化把不同来源的属性名统一成一套数据源用的字段名五花八门有的叫 NAME有的叫 name有的给中文名有的给英文名。我一般先做一次字段映射rename_map { NAME: name, NAME_EN: name_en, ISO_A2: iso_a2, ISO_A3: iso_a3, } for feat in data[features]: props feat[properties] for old_key, new_key in rename_map.items(): if old_key in props and new_key not in props: props[new_key] props.pop(old_key)这里有个细节只有当 new_key 不存在时才覆盖避免数据源里同时有大写和小写字段时互相打架。字段归一化做完后续所有代码只认一套 key拆分、检索、接口输出都会省心很多。如果前端要显示中文名再单独维护一个映射表把 iso_a2 映射到中文国家名不要依赖数据源自带的中文字段因为它的命名习惯你控制不了。4.3 坐标精度压缩保留几位小数能省多少体积完整的世界国界 GeoJSON 动辄几十 MB其中大部分体积是坐标小数位撑起来的。精度和体积的取舍有一套对应关系小数位数约对应误差适用场景6 位约 0.1 米分析、入库4 位约 11 米城市级展示2 位约 1.1 千米世界地图概览实际项目里我做世界地图展示常用 4 位小数既看不出边界变形又能把体积压到一个可接受的范围。用递归函数压缩坐标PRECISION 4 # 展示用空间分析建议保留 5 或 6 def compress_coords(coords): if isinstance(coords, (int, float)): return round(coords, PRECISION) return [compress_coords(c) for c in coords] for feat in data[features]: feat[geometry][coordinates] compress_coords(feat[geometry][coordinates])递归处理的好处是不用关心具体是 Polygon 还是 MultiPolygon只要坐标是嵌套数组逐层往下压缩就行。注意这个函数对整数、浮点都做了 round不会有类型问题。压缩后文件体积通常会下降 60% 以上渲染流畅度区别很明显。4.4 用 mapshaper 简化边界百分比与参数怎么选只压缩小数位还不够边界本身有大量细节节点展示尺度根本用不到。mapshaper 是专门做地理数据简化的工具一条命令就能完成npx mapshaper world.geojson \ -simplify 15% \ -keep-shapes \ -clean \ -o world_simple.geojson-simplify 15% 表示保留约 15% 的坐标点比例越小文件越小但边界越粗糙-keep-shapes 防止面积较小的国家在简化过程中被完全删掉-clean 修复简化后产生的自相交多边形。做世界地图时我会先用 15% 试渲染如果边界还能辨识再往下压如果岛屿明显变形就调到 25%这个参数没有绝对标准按渲染效果定。mapshaper 是 Node 工具系统里有 Node 环境就可以用 npx 直接跑。不想装 Node 就用 QGIS 的简化工具参数含义是一样的。5. 世界国家 GeoJSON 的高频避坑记录编码、边界画法与性能问题5.1 名称乱码成问号文件编码不是 UTF-8现象地图渲染正常但国家名全部是“?”或乱码。原因数据源文件是 GBK 或 ANSI 编码而前端解析时按 UTF-8 读。解决用 Python 强制读入再按 UTF-8 写回with open(world.geojson, encodinggbk, errorsignore) as f: content f.read() with open(world_utf8.geojson, w, encodingutf-8) as f: f.write(content)注意 errorsignore 会丢字符如果乱码太多说明原文件不是 GBK 而是其他编码先换编码再转。5.2 部分国家渲染成黑洞只处理了 Polygon没处理 MultiPolygon现象Leaflet 或 ECharts 加载后大多数国家正常个别国家完全不显示或者整块区域涂成深色。原因该国家的几何类型是 MultiPolygon而代码里只处理了 Polygon 路径。解决判断 geometry.type统一处理。最稳妥的办法是用递归函数处理 coordinates不区分类型参考 4.3 的 compress_coords 写法。如果一个国家由多个不相连的多边形组成比如岛国渲染后出现多个碎片区域是正常的不是 bug。5.3 浏览器加载后卡死高精度数据没做简化现象页面打开后地图要转圈很久放大缩小明显掉帧右上角内存占用一路飙高。原因全球 GeoJSON 几十 MB浏览器要解析大量坐标点直接塞给渲染引擎。解决先做坐标精度压缩再做 mapshaper 简化。如果还卡就按国家拆分首屏只加载当前需要显示的区域其他区域用懒加载。5.4 边界线与地图服务对不上多数据源混用没有后悔药现象自己下了一份国界数据底图用的某个在线地图服务两边边界线明显错位尤其在小比例尺下看着像版图漂移。原因数据来自不同规范对边界、岛屿、飞地的处理不完全一致。解决业务正式上线前不要自己拼接多个来源的边界。国内项目优先使用符合当地审图规范的地图数据整库采用同一套数据源。别等画完所有交互再回头看边界返工成本太高。5.5 地图跑到海里去坐标顺序写反或坐标系没统一现象整个国家轮廓跑到非洲附近的海里或者图形严重变形。原因一是经纬度顺序写反数据源存的是 [纬度, 经度]二是数据源实际是投影坐标系被当成经纬度用了。解决先检查坐标范围。经纬度经度范围在 -180 到 180纬度在 -90 到 90超出这个范围说明坐标不是 WGS84 经纬度def check_lonlat(coords): if isinstance(coords, (int, float)): return -180 coords 180 return all(check_lonlat(c) for c in coords)如果确认是顺序反了可以用递归函数交换前两位。如果是投影坐标回到 ogr2ogr用 -t_srs EPSG:4326 重新转换不要手动改数值。6. 用 TopoJSON 和懒加载让世界地图秒开6.1 转 TopoJSON复用边界以后体积减少多少GeoJSON 的问题在于相邻国家会重复存储同一条边界而 TopoJSON 会把共享边界只存一次体积能再压掉一截。转换命令npx geo2topo countriesworld_simple.geojson world_topojson.json转换后的 TopoJSON 不能直接喂给地图库浏览器端需要用 topojson-client 还原成 GeoJSONnpm i topojson-clientimport { feature } from topojson-client; fetch(world_topojson.json) .then(response response.json()) .then(topo { const geojson feature(topo, topo.objects.countries); map.addSource(countries, { type: geojson, data: geojson }); });这段逻辑的核心是 fetch 拿 TopoJSON再用 feature 方法还原出标准 GeoJSON。TopoJSON 适合传输GeoJSON 适合渲染两者不冲突。我一般会把完整版、简化版、TopoJSON 版各留一份按场景切换。6.2 前端按需加载先把国家编码拿到再取对应文件按国家拆分后的文件配合前端懒加载能大幅提升首屏速度。我的做法是维护一个轻量索引文件包含所有国家的 iso_a2 编码和名称首屏只加载这个索引用户进入某个国家时再请求对应 jsonasync function loadCountry(code, chartInstance) { const res await fetch(/countries/${code}.json); const geojson await res.json(); echarts.registerMap(country, geojson); chartInstance.setOption({ geo: { map: country, roam: true } }); }transform 成按需加载后世界地图页面的初始请求体积能控制在几百 KB 级别而不是一上来就拉几十 MB。多写一个 Promise 缓存防止重复请求同一个国家文件。6.3 上线前验证检查坐标范围和要素数量压完体积、拆完文件上线前还要做一次整体验证。最笨但有效的方法是检查要素数量和坐标范围import json from pathlib import Path files list(Path(countries).glob(*.json)) print(国家文件数量:, len(files)) total 0 for fp in files: data json.loads(fp.read_text(encodingutf-8)) total len(data[features]) print(要素总数:, total)如果要素总数和原始数据对不上说明拆分时丢了要素。另一种验证是算面积总和全球陆地面积约 1.49 亿平方公里如果算出来差了一个数量级那基本是坐标或几何类型出了问题。面积计算直接用 shapely 的 area 方法不要自己写球面公式。这几年折腾世界地图数据我的习惯是先看体积再看编码先用工具压一遍再谈样式坐标范围检查永远放在第一次渲染之前。顺序反了后面全是补救。希望帮到你。本文还有配套的精品资源点击获取
返回列表