ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

世界国家GeoJSON离线数据包实战:从加载到坐标转换的完整指南

世界国家GeoJSON离线数据包实战:从加载到坐标转换的完整指南

简介:包含世界各国地理边界与属性数据的GeoJSON资源包,面向GIS开发者、前端可视化工程师及地理信息学习者,适用于交互式地图展示、空间数据分析和GIS教学。资源共2000个文件,以json/geojson格式为主,压缩包仅2.1MB,体积小巧,便于分发与加载。已有4731人学习下载,是社区认可度较高的地理数据集合。数据遵循WGS84坐标系,每个文件对应一个国家或地区的多边形边界,并携带国家名称、首都等属性字段,可直接在Leaflet、Mapbox GL JS、OpenLayers等地图库中渲染,也可用于面积计算、区域查询、地理编码等多种开发任务。内容包含完整的世界国家边界数据,目录清晰,覆盖全球各主要国家与地区,无论是快速构建世界地图看板,还是开展区域统计分析,这套数据都能直接复用,是GIS入门与地图开发的实用资源,也是教学与科研中常用的基础数据集。

1. 世界国家GeoJSON数据包:离线地图背后的“边界档案”

做可视化的时候,世界地图永远是最先想要的那张底图,但真正动手才发现:GeoJSON 数据不是随手能拿到的。在线地图服务要配 token,某些平台请求频率卡得死死的,更别提内网环境根本没有外网请求通道。于是这份“世界地图、世界国家 geojson、各国地图 json 数据”的下载资源就成了刚需——一份包含全球各国边界的 GeoJSON 集合,解压之后即可离线加载,适合 ECharts、Leaflet、D3、Power BI 以及各类数据大屏的后端渲染场景。它的核心价值在于“可复用”和“不依赖外部请求”,有了它,世界地图就不再是黑匣子,边界、国名、区域编码都摊开在你眼前。适合正在做地理可视化、需要按国家聚合展示指标的开发者和数据分析师,也适合学生用来做地图相关的课程设计。

2. 解析这份世界地图数据:目录结构、坐标系与真实用途

2.1 数据从哪来:一眼认出“Natural Earth 系”特征

拿到任何一份世界国家 GeoJSON 数据包,第一件事不是解压,而是先判断数据源头。市面上流传的“世界地图 json 数据”,绝大多数都脱胎于 Natural Earth 的 Admin 0 国家边界数据集,也就是“国家的行政 0 级边界”。这份资源大概率也是这个体系:每个国家一个 Polygon 或 MultiPolygon,properties 里带着国名、ISO 代码、大洲、人口估算值等字段。

怎么判断确实是 Natural Earth 系?看 properties 字段。典型的特征是包含NAME、NAME_EN、CONTINENT、ISO_A3、POP_EST这类键名。ISO_A3是三字母国家代码,比如CHN、USA、BRA,这是做数据匹配的黄金字段,比中文名和英文名都稳定。CONTINENT直接标了大洲归属,后续做“按大洲聚合”或者“过滤出亚洲国家”的操作时,这个字段比自己去数国家省事得多。

还有一个隐藏特征:Natural Earth 数据集的格陵兰被划在北美洲名下,法属圭亚那被划在南美洲名下,这与大多数人脑中的地理分区不完全一样。后面做标注、做筛选时,如果发现某些“奇怪”归属,大概率不是资源错了,而是数据源本来就按这个标准划分。

2.2 解压后先做什么:核对目录结构与字段清单

我一般会把 GeoJSON 下载包解压后,先做一个“摸底三步”,避免用到一半才发现字段对不上。

第一步,看文件目录。常见的组织方式是三种:一份全世界的单文件(例如world.geojson),按大洲拆分(asia.geojson、europe.geojson这样的命名),以及按单个国家拆分(china.geojson、japan.geojson)。如果三种都有,这份资源就算比较完整了。按大洲拆的文件适合做区域大屏,单独一个国家的文件适合做国家下钻。

第二步,检查 JSON 合法性。GeoJSON 本质就是 JSON,用 jq 或者 Python 都能快速做语法体检。

python -c "import json; data=json.load(open('world.geojson','r',encoding='utf-8')); print(len(data['features']), data['features'][0]['properties'])"

这条命令会输出两样东西:要素总数和第一个要素的 properties。要素总数一般在 170 到 250 之间(取决于把争议地区、海外领地算作独立要素还是合并进主国),如果数字远小于 100,说明这份数据可能被过度简化了,不适合做精细的可视化。properties 的输出用来确认字段名,后面写匹配逻辑全靠它。

第三步,确认坐标系。多数世界地图 GeoJSON 都是 WGS-84 经纬度坐标(EPSG:4326),但有些“好心”的处理者会把数据转成墨卡托平面坐标,导致前端加载出来完全是乱的。判断方法很简单:看特征坐标绝对值,如果经度在 -180 到 180 之间、纬度在 -90 到 90 之间,就是经纬度;如果坐标变成了几百万的数字,那是投影坐标,需要做转换。

2.3 什么场景真正需要这份JSON,什么场景可以放弃

这份 GeoJSON 数据包最适合的场景是“自己掌握渲染全链路”:用原生 ECharts 或 Leaflet 实现的世界地图,按国家做数据下钻、按大洲做聚合展示、或者是纯离线环境下的大屏项目。它把地图的“边界数据”这个环节彻底本地化,所有样式都可以自己控制,不再依赖第三方平台是否开放地图服务。

反过来,有些场景不建议用这份资源。比如只是要一个背景底图,不在乎国家边界精确度,那直接引用在线地图服务既省流量又省心。再比如你的展示层级是“中国省级”,那应该找中国省级 GeoJSON 而不是这份世界数据,世界数据里省级边界通常不包含,硬拆只会得到粗糙结果。判断标准就一条:你需要的是“国家级别”的粒度,还是“国家以下”的粒度。这份资源的边界就到国家,粒度再细就不够了。

3. 把GeoJSON喂给地图组件:ECharts与Pyecharts实战

3.1 ECharts 加载离线世界地图:从注册地图到数据对齐

ECharts 在 4.x 时代还内置着世界地图,5.x 开始就把地图数据移除了,要求开发者自己注册。这一步反而让本地 GeoJSON 数据的价值凸显出来。用 ECharts 加载这份数据,核心动作就是registerMap。

fetch('world.geojson') .then(res => res.json()) .then(worldJson => { echarts.registerMap('world', worldJson); const chart = echarts.init(document.getElementById('map')); chart.setOption({ series: [{ type: 'map', map: 'world', roam: true, label: { show: false }, data: [ { name: 'China', value: 140 }, { name: 'United States of America', value: 120 } ] }] }); });

这里的registerMap('world', worldJson)是注册动作,第一个参数world是自定义的地图名,后面series里map: 'world'必须和注册名保持一致。data数组里的name字段是关键,它要与 GeoJSON 中properties.NAME_EN完全一致,差一个空格都匹配不上。上面例子里China和United States of America就是典型的标准写法,如果你的数据源字段带缩写或别名,要先做一次字段清洗。

如果 JSON 文件较大(超过 10MB),不建议用fetch一次性拉取,而是把数据放到本地 JS 文件里,用<script src="world.js"></script>的方式同步加载,再把解析后的 JSON 传给registerMap。这是地图应用最常见的前端优化手段。

3.2 Pyecharts 自定义地图:绕开默认图集

Python 生态里做地图图表,Pyecharts 是高频选择。但它自带的世界地图底图和国内常用的业务数据字段往往匹配不顺畅,而且内置图集更新缓慢。自己下载了 GeoJSON 之后,用法是先把数据转换成 Pyecharts 可识别的形式。

import json from pyecharts import options as opts from pyecharts.charts import Map with open('world.geojson', 'r', encoding='utf-8') as f: geo_data = json.load(f) region_data = [ {"name": "China", "value": 140}, {"name": "United States", "value": 120}, {"name": "Brazil", "value": 90}, ] c = Map() c.add("世界指标", region_data, maptype="world", is_roam=True) c.set_global_opts( title_opts=opts.TitleOpts(title="各国数据分布"), visualmap_opts=opts.VisualMapOpts(max_=150), ) c.render("world_map.html")

这里maptype="world"用的是 Pyecharts 默认注册的世界地图数据。如果你要改成自己下载的 GeoJSON,Pyecharts 提供的办法是使用Map项的maptype配合自定义地图注册表,或者干脆把 GeoJSON 转成JS语言文件后手动加载。常见做法是找echarts-map-js这类封装库,把world.geojson转成world.js放入项目,再由 Pyecharts 模板加载。我一般会建议:如果你的 Pyecharts 环境版本较老(0.5.x),直接改maptype不如升级到新版,用浏览器端registerMap更省事。

3.3 让业务字段与国名精确对上:name匹配的硬规则

地图渲染后最阴间的现象是:图表出来了,颜色块也有,但提示框里的国家名是英文,业务数据里存的是中文名,或者数据里写的US和 GeoJSON 里的United States对不上。这个问题的根源是 data 数组的name必须严格与 GeoJSON properties 中某个字段等价,ECharts 内部的匹配逻辑是字符串全等。

解决方式有两种。一种是把业务数据统一改成 GeoJSON 的NAME_EN值,这样最直接,但要求业务侧配合。另一种是在传数据前做一次映射,常见做法是维护一张 ISO 代码映射表,用ISO_A3作为中间键。

iso_to_name_en = { 'CHN': 'China', 'USA': 'United States of America', 'BRA': 'Brazil', } def convert_business_data(raw_records): converted = [] for rec in raw_records: iso3 = rec.get('country_code') name_en = iso_to_name_en.get(iso3) if name_en is None: print(f"[WARN] unknown ISO code: {iso3}, skip") continue converted.append({"name": name_en, "value": rec.get("value")}) return converted

这段脚本的核心思路是:业务数据里不管存的是CN、中国还是CHN,统一在进入图表前转成NAME_EN。输出日志里如果出现unknown ISO code,就要回头检查业务数据是不是真的有这个国家,还是国家代码用了旧标准。匹配逻辑做成独立函数还有个好处:后续数据源变化,只需要维护映射表,不用改渲染代码。

4. 坐标、格式与文件瘦身:让GeoJSON在前端不翻车

4.1 经纬度顺序与GCJ-02:“点位跑到海里”的两大根源

GeoJSON 的坐标数组顺序是[经度, 纬度],也就是[lon, lat]。这个顺序和很多人的习惯相反,日常说话都是“北纬 39.9、东经 116.4”,但写进 GeoJSON 时必须反过来。一个典型的错误是把[39.9, 116.4]当成坐标,结果中国地图上的标注点直接瞬移到北冰洋附近。

另一个大坑是坐标系混用。国内在线地图(高德、腾讯、百度)用的坐标系是 GCJ-02 甚至 BD-09,与 GeoJSON 数据集的 WGS-84 存在偏移。如果把来自国内地图服务的标注点坐标直接用到世界地图 GeoJSON 上,点位会有几百米到几公里的漂移,视觉上就是“点在国界外面”。

import math def gcj02_to_wgs84(lng, lat): a = 6378245.0 ee = 0.006693421622965943 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

这是 GCJ-02 转 WGS-84 的通用算法,核心参数ee是 WGS-84 椭球偏心率平方,a是长半轴。如果你的业务点位来自国内地图服务,统一在写入图表前做一次转换。反过来也一样:把 GeoJSON 里的边界坐标直接喂给高德地图 JS API 时,要转换成 GCJ-02。我建议在项目里封装一个坐标转换模块,所有点位进出地图组件都走这一个函数,别在业务代码里到处内联转换逻辑。

4.2 TopoJSON转GeoJSON:把压缩格式变回通用格式

有些下载包里提供的是.topojson后缀的文件,大小往往只有 GeoJSON 的一半甚至更少,因为它把相邻多边形的共享边界存成“弧段”,大幅去重。问题是 ECharts、D3 等通用渲染库不直接吃 TopoJSON,需要先转回 GeoJSON。

import topojson import json with open('world.topojson', 'r', encoding='utf-8') as f: topology = json.load(f) geo_data = topojson.Topology(topology).to_geojson() with open('world_from_topojson.geojson', 'w', encoding='utf-8') as f: json.dump(geo_data, f, ensure_ascii=False)

这段脚本把 TopoJSON 对象转换为完整的 GeoJSON 结构。值得说的是topojson这个 Python 库与 Node 端的topojson-client行为一致:都会把arcs解算为坐标序列,输出MultiPolygon。如果你的环境没有 Python,也可以用 Node 命令npx topojson-client world.topojson -o world.geojson,效果相同。

注意转换后的 GeoJSON 坐标精度通常是原始精度,文件体积会变大很多。所以“TopoJSON 转 GeoJSON”之后,最好接一个简化步骤,把边界精度压到可视化可接受的级别。

4.3 简化边界:降精度、降要素,保住肉眼形状

世界地图文件动辄三五十兆,浏览器加载后交互掉帧是常事。核心优化思路有两个:降坐标精度、减少要素数量。降精度最直接的办法是用mapshaper,它是在线免费的,也支持命令行本地处理。

mapshaper world.geojson -simplify 10% keep-shapes -o format=geojson world_simplified.geojson

-simplify 10%表示保留原始点数的 10%,从几何层面大幅减少坐标密度;keep-shapes是保形参数,让简化后的小岛、海峡不消失。这条命令执行完后,文件体积通常能降到原来的 30% 以下。

如果不想引入 Node 环境,Python 里可以用shapely的simplify方法做类似操作,但拓扑关系保持不如 mapshaper 稳。我一般会把简化分两级:低精度版本用于世界总览,高精度版本用于单个国家下钻,两个文件分开存。这样既保住了大屏第一眼的效果,又不至于因为一个南太平洋小岛拖慢整张地图。

4.4 用脚本做体检:坏几何与重复国名一出生就拦下

数据包拿到手,先跑一遍体检脚本,能规避后续一半以上的渲染异常。体检的核心检查项有三个:几何合法性、要素是否重复、properties 字段是否完整。

from shapely.geometry import shape import json with open('world.geojson', 'r', encoding='utf-8') as f: data = json.load(f) problem_list = [] for idx, feat in enumerate(data['features']): geom = shape(feat['geometry']) props = feat['properties'] if not geom.is_valid: problem_list.append((idx, props.get('NAME_EN'), 'invalid geometry')) if 'ISO_A3' not in props or 'NAME_EN' not in props: problem_list.append((idx, props.get('NAME_EN'), 'missing fields')) seen = set() for idx, feat in enumerate(data['features']): name = feat['properties'].get('NAME_EN', '') if name in seen: problem_list.append((idx, name, 'duplicate name')) seen.add(name) print(f"total features: {len(data['features'])}") print(f"problems: {len(problem_list)}") for p in problem_list: print(p)

geom.is_valid是 shapely 自带的拓扑校验,专门检查多边形自相交、环断裂等问题。duplicate name的检查尤其重要,因为很多数据包把“法国本土”“法属圭亚那”“留尼汪”等海外领地拆成多个要素,但它们共享一个NAME_EN,如果不处理,渲染时会出现一个国家名对应多个多边形,提示框和点击事件都会打架。体检结果出来后,根据具体需求决定是合并海外领地还是保留原样。

5. 使用世界地图GeoJSON的五大坑:现象、原因与解法

5.1 地图加载出来却是“白板”:registerMap时机与异步陷阱

现象:数据配好了,setOption 也执行了,页面上一片空白,控制台不报错。
原因:registerMap是异步的,fetch还没返回数据时,setOption已经执行,此时 ECharts 内部没有注册到对应地图名,渲染自然为空。
解决:把setOption放进fetch.then回调里,确保地图注册完成后再设置配置。如果使用同步<script>引入地图 JS 文件,也要确认变量全局可用后再 init 图表。从那以后我每次写地图之前,都会先确认“注册完成”这个事件是否已经发生。

5.2 国家名对不上:数据字段与业务字段错位

现象:部分国家显示出来了,部分国家颜色块不出现,但提示框里可以看到地图自带的国家英文名。
原因:业务数据的name与 GeoJSON 的NAME_EN不一致,常见差异包括“United States” vs “United States of America”、“Russia” vs “Russian Federation”,以及拼写中含连字符的差异。
解决:不要靠肉眼匹配,统一用 ISO_A3 映射。写一节里提到的映射函数,保证所有业务数据在进入图表前被转换成合法地图名。或者反过来,把 GeoJSON 里所有的 NAME_EN 导出为清单,业务侧按清单改数据。优先前面那个方案,改动集中在代码里,可维护性更好。

5.3 国土区域“多一块”或“少一块”:邻国界与海外领地的处理

现象:渲染后某个国家区域明显多了一块小岛,或者某个国家在地图上缺了一个角。
原因:这份世界地图 GeoJSON 存在多处海外领地与主国分离的情况,例如法国的海外大区在地理上属于其它大洲,但在properties.CONTINENT里仍标着 Europe。造成“多一块”通常是把海外领地也画在当前国家色块下;造成“少一块”则是数据本身把某些争议区域归给了邻国。
解决:根据实际业务定义决定处理策略。如果做的是“国家主体一色块”的宏观大屏,就把海外领地要素合并到主国去;如果做的是“精确到地区”的数据挂接,反而应该保留独立要素。合并时可以用shapely.ops.unary_union,把同一国名的多边形几何合并成一个整体。

5.4 涉及本国区域的展示:官方数据源优先

现象:中国范围内的可视化,用这份数据渲染后,周边边界线条与常规认知存在差异,审核或领导评审阶段被提出质疑。
原因:国外公开的全球数据集属于“通用教学级别”,其国界与中国官方审图标准在部分区域存在差别,直接用于面向公众的产品就会出问题。
解决:只要可视化结果涉及中国区域的边界展示,就不要继续使用这份数据。国内的做法是优先采用具备官方审图号的数据源,或者使用各大国内地图服务平台提供的标准 GeoJSON/行政区划接口;这份世界地图 GeoJSON 只用于全球视野展示,把中国区域单独替换成官方数据源再合成。以后凡是涉中国区域的工程项目,我都会第一时间确认底图出处和审核要求,不带侥幸心理。

5.5 简化和合并过头:要素拓扑断裂引发渲染异常

现象:用 mapshaper 简化后,原来正常的国家边界在海峡、峡湾区域出现“锯齿缺角”,放大后还能看到多边形错位。
原因:-simplify比率设得太激进,或者没有加keep-shapes参数,导致细长型多边形被过度抽点,拓扑断裂。
解决:简化比率控制在 10%~30%,且务必加keep-shapes。简化后的文件要重新跑 4.4 的体检脚本,对比简化前后要素数量和几何合法性。如果某个国家的形状严重变形,就单独把该国要素从原始数据中提取出来,不参与全局简化。

6. 把多维数据钉在国土上:点归属匹配与地图校验脚本

有了世界地图 GeoJSON 之后,最常做的进阶操作不是画地图,而是“判断一个点落在哪个国家”。比如你有十万条交易记录,每条带经纬度,想把它们聚合成“国家维度的指标”,这时候不需要去各个国家查表,直接用 GeoJSON 的几何做空间判断即可。

from shapely.geometry import Point, shape import json with open('world.geojson', 'r', encoding='utf-8') as f: data = json.load(f) country_polys = [] for feat in data['features']: props = feat['properties'] geom = shape(feat['geometry']) country_polys.append((props.get('NAME_EN'), props.get('ISO_A3'), geom)) def locate_country(lng, lat): p = Point(lng, lat) for name, iso3, geom in country_polys: if geom.contains(p): return name, iso3 return None, None sample_point = (116.4, 39.9) print(locate_country(*sample_point))

这个脚本把 GeoJSON 里的多边形一次性加载进 shapely 的空间索引,之后每个点的判断都是几何包含关系计算。十万条记录跑下来,在普通笔记本上也就几秒钟。性能上要注意:如果点数量上百万,建议用rtree或geopandas.sjoin做空间连接,不要逐条循环。

这类空间操作的另一个衍生用途是“数据有效性验证”——把业务数据里的异常点位(比如在中国的边界数据里出现了一个落在海中央的坐标)直接揪出来。从那以后我每次拿到地图数据,都会强制走一遍“体检脚本 + 点归属验证”的流程,确认边界数据可用、点位归属符合常识,再进入正式开发流程。希望这份世界地图 GeoJSON 数据的使用经验,能帮你在可视化项目里少踩几个坑。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进