简介:面向 Web 前端与数据可视化开发者,一份 GeoJson 数据包汇总了世界地图和主要国家的地理边界文件,可直接用于 D3.js、ECharts 等常见可视化库,省去逐个寻找和校验边界数据的繁琐过程。压缩包为 RAR 格式,共 25 个文件、体积 11.31MB,其中 23 个 JSON 分别对应世界及主要国家地图的边界坐标;另含 2 个 MHT 网页存档,内容是 D3.js 地图绘制的入门讲解,可作为加载 GeoJson 数据、配置投影和渲染路径的参考。原始数据精度较细、体积偏大,作者建议下载后使用 mapshaper.org 进行简化,按需降低坐标点密度,从而适配地图缩放、区域高亮或大数据量专题图等不同场景。目前已有 6263 人学习下载,覆盖欧洲、亚洲、美洲、非洲及大洋洲的多个主要国家,对快速搭建世界地图原型、制作国家轮廓动画或开展地理数据可视化练习都有直接帮助。
1. 世界地图 GeoJson:拿到就能用的边界数据,到底解决了什么问题
第一次做可视化大屏的时候,我花了一整晚在找世界地图的 GeoJson 数据:网上捞出来的要么是几十 MB 的精细版,浏览器直接卡死;要么国家名对不上业务表里的中文名,地图上死活渲染不出高亮。这份世界地图和主要国家的 JSON (GeoJson) 资源,解决的正是这个“数据落地”的问题——把世界地图和各主要国家的边界数据整理成套,直接用在前端可视化、数据大屏和 GIS 分析里,不用自己满世界找数据、处理边界、调坐标系。它适合前端开发、数据分析师、GIS 入门的人,哪怕是刚接触地图项目的新手,拿到文件后拖进项目里就能看到轮廓。
2. 读懂 GeoJson 结构:FeatureCollection、坐标系与文件组织方式
2.1 一个国家的边界文件里到底装了什么
GeoJson 本质上就是一段 JSON,顶层是一个 FeatureCollection 对象。你打开这份资源里任意一个国家的文件,看到的应该是这样的结构:
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "某国名称", "iso_a3": "XXX", "adcode": 123456 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [[102.0, 12.0], [102.1, 12.1], [102.2, 12.0]] ] ] } } ] }这段代码的核心在于 coordinates 的嵌套层级:MultiPolygon 比 Polygon 多一层数组。很多新手在这里踩坑——写渲染逻辑时用固定层级去取坐标,结果有的国家是 Polygon、有的国家是 MultiPolygon,取出来的数组要么是空的要么整个渲染崩掉。我一般会在读取数据后先判断 geometry.type,再走对应的解析分支。
2.2 为什么坐标精度比国界完整性更影响性能
这份资源里的坐标数据有讲究:明文 JSON 的经纬度小数点位数直接决定文件体积。一位小数点代表约 10 公里的精度,三位小数点约 100 米,六位小数点约 0.1 米。做世界地图时,国界精度到三位小数足够了,没必要上六位——六位小数点的全量世界地图体积可能达到几十 MB,前端解析加渲染的时间翻几倍,还不一定看得出区别。
资源里通常会对同版本数据做不同精度的拆分,比如“标准版”和“简化版”两个目录。简化版在小数点位数和顶点数两个维度做了裁剪,适合大范围展示;标准版保留更多细节,适合放大后看局部边界。我不会盲目追求高精度版本,而是按“最终展示比例尺”来选文件:整屏看全球用简化版,聚焦到某个大洲或某国用标准版。
2.3 这份资源的数据组织和命名约定
从文件命名规则就能看出作者的数据整理思路,常见做法是按“地图级别 + 区域名”来组织:
| 目录 / 文件 | 内容范围 | 用途 |
|---|---|---|
| world.json | 全世界国家边界,含国界线、岛屿 | 大屏默认展示、全球分布图 |
| asia.json / europe.json 等 | 按大洲拆分 | 区域切换、下钻 |
| 国家代码目录(如 xxx.json) | 单个国家边界与行政区划 | 国家维度分析 |
里面附带的中英文名称对照表,是直接写入 properties 还是独立 CSV,取决于资源版本。如果是独立 CSV,我会先看一眼 name 字段的命名规则:“name”通常存英文名,中文名可能在“name_zh”字段,也可能要自己映射。字段名不统一是很常见的事,用之前先用脚本扫一遍所有文件的 properties 键名,比一个个打开看省事得多。
3. 把 GeoJson 渲染到页面上:加载、注册与字段映射的三个步骤
3.1 在浏览器端用 ECharts 的 geo 组件直接渲染
如果你用的是 ECharts,拿到 GeoJson 后第一件事是注册地图,再通过 geo 或 map 系列引用。下面是一个最小可运行示例:
import * as echarts from 'echarts'; // 方式一:直接 fetch 本地 JSON 文件 fetch('./world.json') .then(res => res.json()) .then(geoJson => { // 注册地图,第二个参数就是 GeoJson 对象 echarts.registerMap('world', geoJson); const chart = echarts.init(document.getElementById('map-container')); chart.setOption({ geo: { map: 'world', roam: true, // 允许缩放和平移 itemStyle: { areaColor: '#f0f2f5', borderColor: '#999' } }, series: [] }); });这里有个关键点:registerMap 的第一个参数是地图名,第二个参数是 GeoJson 对象本身,不是 URL。如果你拿到的数据在文件里嵌套了一层(比如有{ "map": {...} }这种包装),要先解包再注册,否则 ECharts 拿不到 features 数组,地图上什么都画不出来。
3.2 在 Leaflet 里加载:注意坐标系与缩放层级
Leaflet 的 GeoJson 渲染路径略有不同,核心是用 L.geoJSON 把数据直接加到图层上:
const map = L.map('map').setView([20, 0], 2); // 先加一个底图层,避免黑背景 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18 }).addTo(map); fetch('./world.json') .then(res => res.json()) .then(geoJson => { const layer = L.geoJSON(geoJson, { style: { color: '#3388ff', weight: 1 }, onEachFeature: (feature, l) => { l.bindPopup(feature.properties.name); // 绑定名称弹窗 } }).addTo(map); // 自动缩放到数据边界范围 map.fitBounds(layer.getBounds()); });Leaflet 默认使用 WGS84 经纬度坐标,和这份资源一致,所以直接渲染不会出现位置偏移。但要注意:Leaflet 的 getBounds() 方法依赖坐标顺序正确排列,如果某个文件的坐标数组结构异常,fitBounds 会把地图缩到一个错误位置,表现为“地图飞到海里”。遇到这种情况先打印一笔坐标看看数据结构,别怀疑底图。
3.3 在 Node 端做数据预处理:合并文件、重命名字段
多数业务场景不会直接用原始文件,而是要按自己的业务数据结构做一次加工。我一般会在 Node 里写个预处理脚本,把多个 GeoJson 合并成一个、统一字段名:
const fs = require('fs'); const path = require('path'); // 读取某个目录下所有国家文件 const dir = './countries/'; const files = fs.readdirSync(dir).filter(f => f.endsWith('.json')); const features = []; for (const file of files) { const raw = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf-8')); const feature = raw.type === 'FeatureCollection' ? raw.features[0] : raw; // 统一字段:把 name_en 改成 name,把 adcode 补齐到业务需要的格式 feature.properties = { name: feature.properties.name_en || feature.properties.name, adcode: feature.properties.adcode || 0 }; features.push(feature); } // 合并为一张自定义地图 const merged = { type: 'FeatureCollection', features: features }; fs.writeFileSync('./merged.json', JSON.stringify(merged));这段代码解决两类问题:一是多文件合并,二是字段名不统一。写业务代码时,前端不用关心数据源里叫 name_en 还是叫 name_cn,统一成项目约定俗成的键名,后续渲染不用到处做判断。注意最后 JSON.stringify 的压缩等级:生产环境我建议在合并步骤一并压缩,去掉多余空格,体积能减少 20% 左右。
4. 边界数据避坑指南:坐标颠倒、文件膨胀与标签乱码的排查记录
4.1 现象:地图渲染出来是一条斜线或所有多边形堆在一起
原因:坐标数组的经纬度顺序反了。GeoJson 的标准顺序是 [经度, 纬度],也就是 [lon, lat],也就是先写经度再写纬度。如果心里想着“纬度放前面”,写出来就是 [lat, lon],渲染出来的图形会全部变形。
解决:写一个简单脚本批量检查并翻转坐标顺序。先读取文件,判断第一个顶点是否在有效经度范围(-180 到 180),如果不在就递归交换经纬度。要注意多边形可能嵌套多层的坐标数组,写递归工具函数处理最省事。
4.2 现象:某个国家的轮廓明显缺失,岛屿没了
原因:这个国家的主陆地边界在几何上是 Polygon,但它的小岛在 GeoJson 里被表达为 MultiPolygon 的一部分。如果渲染代码只按 Polygon 的层级取坐标,岛屿数据就全部丢掉了。
解决:渲染前统一把所有多边形转成 MultiPolygon 结构,或者按几何类型写两个分支。更省心的方案是直接对数据做归一化——把单一 Polygon 也包一层数组变成 MultiPolygon,这样渲染层只有一个分支逻辑,减少出错可能。
4.3 现象:页面加载地图时白屏几秒或卡死
原因:资源里的标准版数据本来就带完整边界细节,如果不做任何简化直接扔给浏览器,坐标点数可能高达几十万,解析和渲染都会卡。
解决:生产环境使用前,对 GeoJson 做一次抽稀处理。常见做法是用拓扑简化算法保留边界外形,把顶点数量压到原来的 20% 左右。我在下一章会写具体的简化命令和参数,这里先记住一个原则:展示范围越小,可以留下的细节越多;展示全世界的场景,简化阈值尽量调大。
4.4 现象:地图上的名称标签显示为乱码或空白
原因:文件里存的是 UTF-8 编码的中文,但页面没有声明正确的字符集,或者读取时被转成了 Latin-1。更常见的是:资源的 properties 里只有一个英文字段,前端代码却取的是中文的 name_zh 字段,取值为空,自然显示空白。
解决:先确认字段名,再确认字符集。浏览器端 fetch 默认按 UTF-8 解析,一般不会乱码,问题多半出在字段名上。我的排查顺序是先 console.log 打印 feature.properties 的全部键名,看有没有 name_zh、cn_name 这类字段,没有就用外部映射表补。中文字段缺失但英文名在场时,宁可自己做一个“英文名 → 中文名”的映射,也不要让前端显示空白。
5. 加工出生产级地图数据:抽稀简化与业务 id 对齐的实战做法
5.1 用 turf.js 做边界简化:tolerance 参数怎么定
V 级 GeoJson 直接上线会拖垮渲染性能,所以我基本都会先跑一遍简化。用的最多的工具是 turf.js 的 simplify 方法,它封装了 Douglas-Peucker 算法,按“最大偏差距离”控制顶点删除力度:
const turf = require('@turf/turf'); const fs = require('fs'); const raw = JSON.parse(fs.readFileSync('./world.json', 'utf-8')); const simplified = turf.simplify(raw, { tolerance: 0.01, // 单位是度,约 1 公里 highQuality: true // 用更慢的算法换取更少顶点 }); // 简化后统计对比 const beforeCount = JSON.stringify(raw).length; const afterCount = JSON.stringify(simplified).length; console.log(`体积变化: ${beforeCount} -> ${afterCount}`); console.log(`压缩率: ${(100 - afterCount / beforeCount * 100).toFixed(1)}%`); fs.writeFileSync('./world-simplified.json', JSON.stringify(simplified));tolerance 是最核心的参数,我说几个常用值供参考:全屏世界地图,0.01(约 1 公里偏差)基本看不出失真;聚焦大洲视角,0.001 保留更多细节;聚焦单个国家时,0.0005 以下才安全。这里有个血泪经验:不要一次把 tolerance 调到 0.05,那会让国界线直接变成锯齿多边形,特别难看。先跑三次小步进,观察每次的顶点数和体积变化,再决定收手阈值。
5.2 字段匹配:把 GeoJson 里的 name 与业务数据 id 对齐
数据要能高亮,核心是让地图文件的字段和你业务表里的键对齐。假设后端返回的业务数据结构是{ countryCode: 'XXX', value: 80 },而 GeoJson 里的字段是 name,需要手动做一次关联。
我常用的思路:写一个映射脚本,把 iso_a3(国际三字码)作为共同键,用它连接业务表和 GeoJson:
const businessData = [ { countryCode: 'CHN', value: 85 }, { countryCode: 'USA', value: 120 } ]; const geoJson = JSON.parse(fs.readFileSync('./world-simplified.json', 'utf-8')); // 以 iso_a3 为关联键,把业务数据写入 GeoJson 的 properties geoJson.features.forEach(f => { const code = f.properties.iso_a3; const matched = businessData.find(d => d.countryCode === code); f.properties.value = matched ? matched.value : 0; }); fs.writeFileSync('./world-with-data.json', JSON.stringify(geoJson));用 iso_a3 而不是中英文名做关联键,是避开大小写、空格、别名等问题的稳妥方案。如果你拿到的资源里没有 iso_a3,也可以用 adcode 或 FIPS 码做键,关键是先打印几个样本确认字段不缺失。字段值里有时会带多余空格,匹配不上时先做 trim 再比对。
5.3 导出为压缩后的 min 文件
生产部署时,我会把处理后的文件做一层 min 化:去掉所有不必要空格、换行、注释字符。这一步在 Node 里用手写正则不够稳妥,直接复用 JSON 序列化天然压缩的特性:
# 一行命令完成精简压缩,输出为无空格的 min 文件 node -e "const fs=require('fs');const d=JSON.parse(fs.readFileSync('world-with-data.json','utf-8'));fs.writeFileSync('world.min.json', JSON.stringify(JSON.stringify(d)))"这段命令等价于把 JSON 先解析再序列化一次,只留下必要的键和值。不过如果只是去掉空格,也可以考虑用 gzip 压缩,让服务器在传输层再压一次,前端拿到的体积会更小。本质和 min 化不冲突——一条数据流里两个环节都可以做,先 min 再 gzip 能实现最大压缩比。
6. 验收一份 GeoJson 是否可用:三分钟跑完的五个健康检查
拿到一份新的 GeoJson 文件,我不会直接丢进项目,而是先跑一组快速检查。以下是一个可反复使用的最小验收脚本,重点检查结构、坐标系、字段和边界完整性:
import json def validate_geojson(file_path): with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) # 检查1:顶层类型必须是 FeatureCollection assert data.get('type') == 'FeatureCollection', '顶层类型不是 FeatureCollection' # 检查2:features 非空,且每个 feature 都有 geometry features = data.get('features', []) assert len(features) > 0, 'features 为空' for f in features: assert 'geometry' in f and f['geometry']['type'], '存在缺少 geometry 的 feature' # 检查3:坐标纬度范围合法 for f in features: geom = f['geometry'] coords = json.dumps(geom['coordinates']) # 粗筛:如果纬度出现小于-90或大于90,坐标顺序很可能错了 assert '[-91' not in coords and '[91' not in coords, f"坐标范围异常: {f['properties']['name']}" print(f"检查通过: {len(features)} 个 feature, 类型正常, 坐标范围合法") validate_geojson('./world.json')除了脚本检查,我还会做两条人工检查:第一,打开文件看前 5 条 feature 的 properties 字段名是否统一,如果不统一,后续代码要写更多的条件判断,不如趁早清洗;第二,快速渲染一次,把比例缩放到最大,看国界线是否出现不正常的直线切割,如果有,说明简化过度了。这两条属于“肉眼验收”,脚本测不出来的问题往往都藏在这里。
从那以后,我每次拿到地图类的 GeoJson 数据,都强制走一遍这套流程:先扫描字段、再验证坐标顺序、接着跑简化、最后做渲染测试。这套习惯帮我少走了很多弯路,尤其是坐标系和 MultiPolygon 的坑,踩过一次就记住了。希望这份资源和这些经验帮到你。
本文还有配套的精品资源,点击获取