简介:一套世界地图及主要国家边界数据的 GeoJSON 文件合集,面向 Web 前端开发者、数据可视化爱好者及地理信息处理人员,解决在各项目中快速获取多国地图矢量数据的需要。压缩包共 25 个文件,其中 23 个为 JSON 格式的 GeoJSON 地图数据,涵盖美国、中国、俄罗斯、日本、德国、法国、英国、印度、巴西等主要国家,并附有世界地图数据;另含 2 个 MHT 格式的 D3.js 学习参考页面,便于结合可视化库理解地图绘制方法。包体整体约 11.31MB,数据较完整,用户可按需通过 mapshaper.org 在线工具简化文件体量。使用这些标准化的地理数据,可以快速实现地图区域的着色、数值统计、交互式热力等常见可视化效果,省去从零收集和清洗边界信息的麻烦。目前已有 6263 人学习下载,适合需要快速上手地理数据可视化、制作世界或国家等级地图的开发者直接取用。
1. 世界地图 GeoJson:为什么先处理数据,再谈地图渲染
做访问来源分布、销售大屏、区域下钻这类地图可视化时,最让人头疼的不是写 ECharts 配置,而是拿到一份「看起来没问题」的 GeoJson。你以为下载下来直接注册就能用,结果要么白屏,要么卡到浏览器崩溃,要么某个国家死活选不中——这些现象大半不是渲染代码的锅,而是 GeoJson 数据本身的结构、坐标层级和字段没被正确理解。这份资源里包含世界地图和多个主要国家边界的 JSON(GeoJson)数据,能直接用于地图注册、区域选中和分级着色。它的价值在于省去到处找边界数据的清洗时间,但真正把它用好,还得先弄清楚 GeoJson 是怎么组织的,以及它为什么动辄几十 MB。这篇文章就按我实际拆过的流程来讲:先看结构,再讲用法,最后把我踩过的坑一次说明白。
2. GeoJson 的骨架:FeatureCollection、坐标层级与数据膨胀
2.1 为什么一份 GeoJson 会大到加载白屏
GeoJson 本质上是一个 JSON 对象,没有二进制压缩,也没有索引结构。它里面每一个国家的边界,都是一串按顺序排列的经纬度坐标点。世界地图要表达多少个国家?少则几十个,多则两百多个;每个国家的外边界,少则几千个坐标点,多则几十万个坐标点。把这些全部用明文写在一个 JSON 文件里,体积自然会膨胀到几十 MB。
我拿到过一份看起来挺完整的世界 GeoJson,光是中国一个国家的边界就包含了两万多个坐标点,整个文件接近 50MB。直接丢给浏览器去解析,肯定卡。更隐蔽的问题是,许多下载站点只负责「转换」不负责「简化」,原数据可能来自高精度测绘数据,细到连一条河流的弯曲程度都记录了。那些对于地图展示不是必需的细节,统统成了渲染的负担。
所以在动手写页面之前,先要建立一条基本判断标准:如果一份 GeoJson 超过 10MB,通常就需要先做顶点简化,而不是直接进代码。减少顶点不只减小文件体积,还能让浏览器渲染时省掉大量不必要的路径计算。
2.2 从结构上看懂 GeoJson:FeatureCollection 和 Feature 的字段
GeoJson 的类型不多,最外层是 FeatureCollection,它主要包含一个features数组。数组里每一个元素是一个 Feature 对象,表示一个具体的地理实体——在这个资源里通常就是一个国家或地区。Feature 的关键字段是geometry和properties。先说 geometry,它里面定义了type(Polygon、MultiPolygon、Point 等)和coordinates(具体坐标数据)。properties 则是一组自定义属性,比如国家名称、ISO 国家码等。
我给出一个最小示例,方便看清层级关系:
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "示例国家A" }, "geometry": { "type": "Polygon", "coordinates": [ [ [116.3, 39.9], [117.2, 39.2], [117.7, 40.0], [116.3, 39.9] ] ] } } ] }这个示例里只有一条 Feature,coordinates 的第一层是一个数组,表示的是一个多边形边界。展开看,内部每个元素都是一个[经度, 纬度]的坐标点。最外层数组里的第一个数组,通常表示外边界;如果还有后续数组,则表示内部挖空的部分,比如湖泊或某个飞地边界。
明白这个层级后,在写解析脚本时就不会被「数组套数组」绕晕。properties里的字段名在不同数据源里差异很大,有的叫name,有的叫NAME,有的叫name_en,使用时务必先打印出来看一遍。直接写死feature.properties.name很常见,但字段名一旦不匹配,地图上就会显示空白标签。
2.3 坐标的第三层含义:Polygon 和 MultiPolygon
很多人第一次处理 GeoJson 时都会忽略geometry.type的影响。一个国家的边界,并不是只有一个 Polygon。像一些国土包含飞地、海外领地或群岛的国家,它的边界通常是由多个相互分离的多边形组成的,此时 GeoJson 会用 MultiPolygon 来表示。
这两者的坐标层级差了一层。Polygon 的 coordinates 长这样:[[[经度,纬度], [经度,纬度], ...]];MultiPolygon 则长这样:[[[[经度,纬度], [经度,纬度], ...], ...], ...]。也就是说,MultiPolygon 内部先按「区块」分了一层,每个区块再有自己的外环和内环。
在实际开发里,我见过有人把所有国家的坐标统一按geometry.coordinates[0]去取第一块,结果遇到 MultiPolygon 结构的国家,绘制出来就只剩一个局部形状,看着就像国家「缺了一块」。要避免这个坑,写脚本时就要先判断 type,再决定遍历哪一层,而不是统一处理。
用一小段 Python 脚本可以快速把资源里所有国家的类型和坐标段数统计出来:
# inspect_geojson.py —— 查看每个国家的几何类型与区块数量 import json import sys with open(sys.argv[1], encoding="utf-8") as f: data = json.load(f) total_points = 0 for feat in data["features"]: geo = feat["geometry"] coords = geo["coordinates"] if geo["type"] == "Polygon": total_points += sum(len(ring) for ring in coords) elif geo["type"] == "MultiPolygon": for poly in coords: total_points += sum(len(ring) for ring in poly) print(feat["properties"].get("name"), geo["type"], len(coords)) print("点总数:", total_points)运行方式是python inspect_geojson.py world.geojson。第一个参数是 GeoJson 文件路径,脚本会一行行打印每个国家的名称、几何类型和坐标区块数,最后统计全部点数。拿到这个输出,就能判断当前数据的「重量级」——点总数超过两百万的话,文件基本超过 20MB,必须做压缩再做渲染。
2.4 前置简化:mapshaper 命令与压缩预算
面对高精度的 GeoJson,主流做法不是自己在程序里写抽稀算法,而是先用 mapshaper 这类工具做顶点简化。mapshaper 是处理矢量地图数据的利器,支持命令行,也可以直接在网页版里拖拽操作。它内置了多种简化算法,其中 Douglas-Peucker(dp)算法在保留轮廓特征和压缩顶点数上表现比较均衡。
一条最常用的简化命令如下:
mapshaper world.geojson -simplify dp 0.3% keep-shapes -o format=geojson world_small.geojson参数含义拆开说:dp指定使用 Douglas-Peucker 算法;0.3%表示保留约 0.3% 的顶点,这是简化力度,数值越小简化幅度越大;keep-shapes是一个安全选项,避免某些小国家因为顶点被删掉太多而直接消失;-o后面是输出文件路径和格式。
简化力度怎么定?我一般按场景来:做世界地图全览时,简化到 0.5% 到 3% 之间,既能保住大陆轮廓,也能兼顾加载速度;做单国家大比例尺展示时,不能太狠,建议保留到 10% 左右,否则国界看起来会像锯齿。简化完之后,再用-info命令查看文件点数,确认压缩效果:
mapshaper world_small.geojson -info输出里会显示每个 feature 的顶点数,对比压缩前后的数字,就能确定简化力度是否合适。这个步骤建议放在清理坐标系之后、接入页面之前。
2.5 投影与坐标:EPSG:4326 是省事的基础
GeoJson 里存的是经纬度坐标,标准约定是 EPSG:4326,也就是 WGS84 坐标系。它的数值范围大致是经度 -180 到 180,纬度 -90 到 90,直接传给浏览器端的地图渲染库即可,不需要额外做投影转换。
但有些数据源提供的「GeoJson」实际上是已经被转换过的 Web 墨卡托坐标(EPSG:3857),坐标值动辄几百万,直接按经纬度渲染时地图会缩放到一个奇怪的位置,甚至出现在屏幕外。遇到这种情况,常见做法是用 mapshaper 做坐标回退:
mapshaper source.geojson -proj wgs84 -o format=geojson wgs84.geojson-proj wgs84会把投影强制转回到经纬度。我能给出的建议是:下载的 GeoJson 先做一次「坐标范围检查」,看看最大最小值是否落在经纬度合理区间。如果最小 X 是几百万,就不要去改渲染代码,先改数据。
3. 把 GeoJson 跑起来:ECharts 注册、单国家抽取与名称映射
3.1 registerMap 的正确姿势与参数说明
GeoJson 数据在 ECharts 里使用前,必须先调用registerMap完成注册。很多初学者在这个环节翻车,最常见的问题是注册名和配置项里的map字段不一致,导致图表报「Map not found」或者干脆白屏。
一个标准的地图初始化流程大致如下:
import * as echarts from 'echarts'; const chart = echarts.init(document.getElementById('mapContainer')); echarts.registerMap('world', worldJson); chart.setOption({ series: [{ type: 'map', map: 'world', roam: true, data: [ { name: '中国', value: 120 }, { name: '美国', value: 98 } ] }] });代码里,echarts.registerMap('world', worldJson)的第一个参数是注册别名,第二个参数是 GeoJson 对象。配置项里series.map的值必须和注册别名完全一致,这里是'world'。roam: true开启缩放和拖拽。data数组里的name要和 GeoJson 中properties.name对应上,只有这样对应区域的着色和数据值才能匹配。
参数细节上还有一点值得说明:如果 GeoJson 里的 name 是英文,而业务数据里是中文,那么两者直接做匹配永远对不上。此时要么改数据,要么在注册前给 GeoJson 的 properties.name 批量替换成业务用的名称。我更推荐后一种做法,因为数据层不太适合为中英文展示反复调整。
3.2 从世界地图中抽取指定国家:ISO 码脚本
整套世界地图 GeoJson 有时候太大,有的场景只需要展示某一个国家的局部区域,比如用户点选某个国家后,希望单独弹出一个该国家的地图。此时不需要去找单独的国家边界文件,直接从世界地图里把对应 Feature 抽出来即可。
一份世界 GeoJson 中,每个国家的 properties 通常会包含 ISO 3166-1 alpha-3 代码,比如 GBR 代表英国,CHN 代表中国,USA 代表美国。利用这个字段做匹配,是最稳定的抽国方式:
# extract_by_iso.py —— 按 ISO 3166-1 alpha-3 从世界地图中提取指定国家 import json def extract_country(src_path, iso, out_path): with open(src_path, encoding="utf-8") as f: data = json.load(f) feature = None for feat in data.get("features", []): props = feat.get("properties", {}) # 兼容不同字段名:ISO_A3 / iso_a3 if props.get("ISO_A3") == iso or props.get("iso_a3") == iso: feature = feat break if feature is None: raise ValueError(f"country not found: {iso}") output = {"type": "FeatureCollection", "features": [feature]} with open(out_path, "w", encoding="utf-8") as f: json.dump(output, f, ensure_ascii=False) print(f"extracted {iso} -> {out_path}") if __name__ == "__main__": # 用法: python extract_by_iso.py world.geojson GBR gb.geojson import sys extract_country(sys.argv[1], sys.argv[2], sys.argv[3])这段脚本接收三个参数:世界地图路径、ISO 代码、输出路径。脚本遍历所有 Feature,匹配 properties 里的ISO_A3或iso_a3字段,找到后把整个 Feature 放进新的 FeatureCollection 保存。这样抽取出来的单国地图没有多余数据,体积小,也省去了手动框选坐标的麻烦。
要注意的是,市面上的 GeoJson 中 ISO 字段有时不叫ISO_A3,可能叫ISO_A3_EH或ADM0_A3。抽国之前先跑一下 2.3 节的统计脚本,看完 properties 字段名再写匹配逻辑。我通常会在命令行里先执行一次python inspect_geojson.py world.geojson,确认字段名后,再执行抽国脚本,这样能少走很多弯路。
3.3 名称错位处理:用 ISO 码关联业务数据
国家名称错位是地图项目里一个非常隐蔽的坑。GeoJson 数据里的中文名称、英文名称、以及业务数据库里的名称往往不一致。比如 GeoJson 里叫United Kingdom,业务数据里写的是英国,直接在data数组里做 name 关联,就无法给英国区域赋值。
我把这类问题统称为「名称匹配黑匣子」——表面上看地图能渲染,但某个区域颜色不变,日志也不报错,排查起来很费劲。后来我改成一种比较稳的做法:不管 GeoJson 里的 name 是什么,只把 ISO 国家码作为统一关联键。
在实际处理中,可以先用一个映射对象把业务数据里的国家码转换成 GeoJson 里的 name,再交给 ECharts:
const isoToName = { 'GBR': '英国', 'CHN': '中国', 'USA': '美国' }; const mappedData = rawData.map(d => ({ name: isoToName[d.iso] || d.iso, value: d.value }));rawData是业务侧返回的数组,每个元素包含iso和value。isoToName负责把 ISO 码转成和 GeoJson 一致的名称。但要彻底避免名称不匹配,最稳妥的方式还是把 GeoJson 中所有 features 的 name 字段改为 ISO 码,而不是中文或英文名。因为 ISO 码在一个标准资源里是唯一的,不会出现「英国」和Britain的差异。改完之后,mappedData里的name也直接存 ISO 码,两边完全对齐。
4. 常见问题避坑:从白屏到国家错位的五条踩坑记录
4.1 白屏与「Map not found」
现象:控制台报错提示某个地图未注册,页面地图部分一片空白;或者地图能渲染,但点击缩放时没有任何反应。
原因:registerMap的注册名与配置项里的map字段不一致。比如注册时写的是registerMap('world', data),配置里却写成了map: 'worldMap'。ECharts 找不到对应的地图资源,干脆什么都不画。
解决:把两个名称统一为一个,最好用一个常量维护,避免手抖。我的做法是定义const MAP_NAME = 'world',注册时和配置时都用这个常量。如果是多语言切换场景,还要注意按语言注册不同名称的地图,不能互相覆盖。
排查白屏时,我一般会按「配置 → 数据 → 注册名 → 坐标」四步走:先确认配置项没有语法错误,再确认加载的 GeoJson 文件不是空的,再检查注册名是否一致,最后打开 GeoJson 看看坐标点是否在合理范围内。大多数白屏都能在四步内定位。
4.2 文件解析报错与 UTF-8 BOM
现象:用fetch加载 GeoJson 后,JSON.parse直接抛错;或者本地开发正常,部署到服务器后偶尔报解析失败。
原因:GeoJson 文件保存时带了 UTF-8 BOM 头,或者数据里混入了 HTML 转义实体。BOM 是文件开头几个不可见字节,JSON.parse不认;转义实体常见于从网页直接复制的内容,比如把双引号复制成了",导致 JSON 结构失效。
解决:写一个简单的修复脚本来统一清理这两种情况:
# fix_geojson.py —— 清理 BOM 与常见转义实体 import json import codecs def fix(path_in, path_out): raw = open(path_in, "rb").read() if raw.startswith(codecs.BOM_UTF8): raw = raw[len(codecs.BOM_UTF8):] text = raw.decode("utf-8") text = text.replace(""", '"').replace("&", "&").replace("'", "'") with open(path_out, "w", encoding="utf-8") as f: json.dump(json.loads(text), f, ensure_ascii=False) print("done") if __name__ == "__main__": import sys fix(sys.argv[1], sys.argv[2])fix函数先把文件以二进制方式读入,检测并去掉 BOM 头,再处理几个高频转义字符,最后用json.loads验证内容确实能正常解析。这一步相当于给数据上了一层保险,只要是合法 JSON,之后的加载就不会因为文件本身的问题翻车。
4.3 地图变形或坐标漂移:投影与经纬度分不清
现象:地图能渲染,但整个形状被压扁、拉长,或者显示位置完全不对,有些区域跑到屏幕外。
原因:数据源给的坐标不是经纬度,而是已经被投影成 planar 坐标(常见是 Web 墨卡托)。浏览器端的地图组件默认按经纬度处理,拿着几百万的坐标直接做映射,自然就「飞」出去了。
解决:先检查 GeoJson 中坐标数组的最小最大值。如果经度范围是 -180 到 180,纬度范围是 -90 到 90,说明是标准的 WGS84 经纬度,不需要处理。如果坐标数值达到好几万或几百万,就要用 mapshaper 把投影转回 WGS84:
mapshaper source.geojson -proj wgs84 -o format=geojson fixed.geojson还有一种少见但真实存在的情况:经度和纬度的数值被写反了。此时地图会出现水平翻转或者旋转 90 度的观感。常规做法是在转换脚本里把两个数值对调,但我更建议重新找一份数据源,因为手工修复几百个 MultiPolygon 的坐标顺序,迟早会漏。
4.4 移动端卡顿与数据体积:简化预算不是玄学
现象:开发时用桌面浏览器看起来流畅,部署到移动端后,缩小地图时明显掉帧,甚至直接白屏。
原因:移动端 GPU 和内存资源有限。全量 GeoJson 的顶点数量如果超过百万级,渲染路径计算量会急剧上升,尤其在每次缩放拖拽重新绘制时。
解决:回到第 2.4 节,用 mapshaper 给数据做前置简化。简化预算不是固定的,要按目标平台来定。桌面端可以宽松一些,保留 3% 左右的顶点;移动端建议压到 1% 以下,并为地图页面增加一个「初始缩放级别」的控制,让用户进入页面时不要带着全量顶点去做大范围渲染。
第 2.3 节里的统计脚本在这里能派上用场:如果压缩前点总数是 800 万,压缩后只有 20 万,移动端基本就不会卡了。我在项目里会把压缩后的点总数控制在 30 万以内,然后配合异步加载,等到地图即将展示时才拉取数据,进一步减少首屏压力。
4.5 国家名对不上业务数据:旧地名与字段名差异
现象:地图主体渲染正常,但某个国家区域始终不显示业务数据对应的颜色;有些区域点击后弹出的名称还是旧版地名或英文别名。
原因:GeoJson 的properties.name与业务数据中的国家名称表述不一致。比如一边存的是苏联这种旧名,一边存的是新国名;或者一边是Russia,一边是俄罗斯。ECharts 做 data 匹配时找不到对应项,就会跳过该区域,不报错也不提示。
解决:用第 3.3 节的 ISO 码映射思路,把关联键从「名称」换成「ISO 码」,展示名称单独用一张映射表控制。实际操作中,我先整理一份完整的「ISO 码 → 业务名称」映射表,再写一段逻辑,把 GeoJson 的 name 统一替换成 ISO 码,最后业务数据的 name 字段也转成 ISO 码。这样无论资源内部叫什么都无所谓,关联不会断。
下面是一张常用对照表,供整理数据时参考:
| ISO 3166-1 alpha-3 | GeoJson 常见 name | 业务展示名 |
|---|---|---|
| CHN | 中国 | 中国 |
| GBR | United Kingdom | 英国 |
| USA | United States of America | 美国 |
不管中文还是英文,只要关联键统一用 ISO 码,展示名就是纯展示层的事,想怎么改都行,不会再影响数据匹配。
4.6 数据质量核对表:拿到 GeoJson 后逐项检查
把前面几条坑汇总成一张核对表,每拿到一份新 GeoJson,我都会在项目里过一遍:
| 检查项 | 现象 | 建议做法 | 严重程度 |
|---|---|---|---|
| UTF-8 与 BOM | JSON.parse 报错 | 用 fix_geojson.py 清理 | 高 |
| 顶点数量 | 白屏 / 卡顿 | 用 mapshaper 简化 | 高 |
| name 字段缺失 | 地图无标签 | 检查 properties,补 name | 中 |
| ISO 字段命名 | 抽国失败 / 数据错位 | 先看字段名再匹配 | 中 |
| 坐标投影 | 地图变形 / 漂移 | 转回 WGS84 | 高 |
| MultiPolygon 多次嵌套 | 区块缺失 | 按 geometry.type 分层解析 | 中 |
这张表不是摆设,我建议把它当成数据接入的第一道工序。所有 GeoJson 在写页面代码之前,先按表里的六项逐一确认,能省掉后面至少一天的调试时间。
5. 一个兜底技巧:浏览器端边加载边精简
5.1 快速顶点采样函数
有些时候没法在命令行里跑 mapshaper,比如临时做一个原型 Demo 或者数据由后端接口动态返回。这种情况我准备了一个 JavaScript 函数,能在浏览器里直接对 GeoJson 做「应急式」顶点精简:
const simplifyGeoJson = (orig, ratio = 0.003) => { const copy = JSON.parse(JSON.stringify(orig)); copy.features.forEach((feat) => { const geo = feat.geometry; if (geo.type === 'Polygon') { geo.coordinates = geo.coordinates.map((ring) => ring.filter((_, idx) => idx % Math.round(1 / ratio) === 0) ); } else if (geo.type === 'MultiPolygon') { geo.coordinates = geo.coordinates.map((poly) => poly.map((ring) => ring.filter((_, idx) => idx % Math.round(1 / ratio) === 0)) ); } }); return copy; };ratio参数控制采样间隔。默认0.003表示大约每 334 个点保留 1 个。要保留更多细节就调大 ratio,比如0.01;要更精简就调成0.001。这个函数的好处是纯 JS 实现,不依赖外部工具,缺点是不如 mapshaper 的 Douglas-Peucker 算法聪明——它只是均匀抽点,可能会丢掉一些重要的拐点。
所以我的使用场景很明确:只在临时原型、接口数据演示时用。正式项目里我一定会回到 mapshaper,优先做有损但保形的拓扑简化。
5.2 最终推荐给数据入库的完整流程
从拿到一份新 GeoJson 到真正跑进页面,我推荐的完整流程是这样的:
- 用 fix_geojson.py 做 BOM 和转义清理,确保能正常解析。
- 用 inspect_geojson.py 打印国家名、几何类型、点总数,确认字段名和坐标范围。
- 用 mapshaper 做简化,压缩到目标平台可接受的文件体积。
- 用 extract_by_iso.py 抽出需要单独展示的国家,存成独立文件。
- 把 GeoJson 的 name 字段统一替换成 ISO 码,业务数据也按 ISO 码关联。
- 最后才进入 ECharts 的 registerMap 和 setOption。
这套流程看着繁琐,但每一步都是在消除一个真实的隐患。BOM 清理能避免解析报错,顶点简化能避免移动端卡顿,ISO 码统一能避免名称匹配黑匣子。我在一个项目上因为没有走这套流程,直接用了全量高精度世界地图,结果上线当天就收到用户反馈说地图缩放卡到没法操作。后来强制把数据先过一遍 mapshaper,再重新发布,问题立刻消失。
从那以后,我每次拿到新的 GeoJson 都强制走一遍这个流程,先处理数据,再写页面代码。希望帮到你。
本文还有配套的精品资源,点击获取