简介一套面向GIS开发、地图可视化及前端应用场景的世界国家/地区GeoJSON数据包数据源自Natural Earth并经过精心处理既适合小规模制图也适合嵌入各类GIS软件或自定义地图项目中可帮助开发者、数据分析师及可视化爱好者解决各国边界多边形数据分散、格式不一、难以获取的痛点。压缩包内文件总数超过2000个核心为3442个json格式地图文件每个文件对应一个国家、地区或次级行政区域如美国阿拉斯加部分区域另有少量md说明文档、gitignore与Unlicense许可证文件整体仅1.69MB便于下载与离线部署。目前已有3687人学习下载可与ECharts地图、Leaflet、GeoPandas、QGIS等常见工具配合使用直接作为数据源渲染或分析。读者获取后既能快速加载任意国家或地区的地图边界也能将其作为离线基础地理数据嵌入应用程序、数据大屏或空间分析流程免去联网请求与数据清洗的额外工作快速呈现清晰、美观的矢量地图效果。1. 世界国家 GeoJSON 大全带 (old) 的旧数据包反而是最快能跑起来的底图做地图可视化最折腾的不是写图表代码是找底图数据。搜世界国家 geojson 大全、找各国地图 json 数据下载经常搜出来要么不全要么字段结构跟地图库对不上调一晚上底图还是灰的。标题里这份带 (old) 后缀的数据包其实是社区里流传了很久的整理版本全世界国家边界统一成 GeoJSON 结构按国家拆分文件能被浏览器端地图库直接消费。它不会频繁变动用的人多、踩坑记录也好搜适合当通用底图而不是权威区划数据。顺着这条线我把解析、加载、排错、改造成底图资源的完整路径写出来。下面是给前端可视化开发者、用 Python 洗地理数据的分析师、以及在原型阶段需要免 token 底图的朋友看的实操笔记。2. 先拆开看一眼结构FeatureCollection 到底装了多少东西老规矩拿到任何数据文件第一件事不是直接渲染是把它打开看结构。GeoJSON 看起来就是个 JSON 文件但如果不懂它的三层嵌套骨架后面的匹配、切片、压缩全都得靠猜。这一章用最少的代码把结构摸清楚同时把属性字段这个最容易翻车的地方提前讲明白。2.1 三层嵌套的 JSON 骨架FeatureCollection、Feature 与 Geometry一份标准的世界 GeoJSON 数据包顶层通常是一个 FeatureCollection 对象里面只有一个关键数组 features。数组里每一个元素是一个 Feature代表一个国家每个 Feature 里又有两部分properties 存该国名称、代码等属性geometry 存这个国家边界对应的几何数据。用一段很短的 Python 就能看穿import json with open(world.json, encodingutf-8) as f: data json.load(f) print(顶层类型:, data[type]) # 预期输出 FeatureCollection print(要素数量:, len(data[features])) # 国家数量 first data[features][0] print(要素类型:, first[type]) # 预期输出 Feature print(属性字段:, first[properties]) print(几何类型:, first[geometry][type])这段代码的价值在于基准值。跑完你就知道这份数据包里到底有多少个要素、属性命名是什么风格、几何是 Polygon 还是 MultiPolygon。要素数量是个很好的经验指标世界国家数据包的 features 数量在 180 到 250 之间都算正常低于 150 基本说明有大量国家被遗漏高于 280 则大概率混入了非国家行政区划。读 properties 时要留个心眼老数据包的字段名大小写经常不一致同一包里面可能混着 name 和 NAMEiso_a3 和 ISO_A3。动手写任何固定字段的脚本之前先跑一遍上面的代码把真实的 key 打出来别上来就写死 properties[name]。2.2 属性字段不统一是常态name、ISO 代码怎么拿来做数据匹配properties 里真正决定你能不能把业务数据贴到地图上的只是两三个字段。最常见的是一段英文国家名通常叫 name 或 NAME以及 ISO 3166 标准里的两位、三位国家代码常见命名是 iso_a2、iso_a3。中文名不是必然存在有的老包带 name_zh有的根本没有。对于要跟业务数据做关联的场景我最推荐用三位代码做匹配键而不是英文名。原因是英文名在不同数据源里变体太多同一个国家可能同时存在全称写法和常见简称程序里写字符串精确匹配非常容易漏。三位代码是枚举值一个代码对应一个国家匹配失败时直接能看出是数据源问题还是业务表问题。如果业务侧只有中文国家名而 GeoJSON 只有英文名常规做法是做一张映射表# 映射表只覆盖业务数据里实际出现的国家按需扩充 name_en_to_zh { 某个国家的英文常用名: 这个国家的中文名, 另一个国家的英文全称: 对应中文名, }当待匹配国家只有几个时手写映射表最省事当国家数上百时建议用 iso_a3 代码直接关联业务表绕开翻译问题。不管用哪种方式都做一次反向校验把 GeoJSON 里所有英文名过一遍映射表看有多少 key 没被覆盖输出出来人工确认避免静默漏匹配。2.3 Polygon 与 MultiPolygon一个国家的边界为什么长成两副面孔细看 geometry.type 会发现有的国家是 Polygon有的是 MultiPolygon。原因很简单有海外领地、离岛或者飞地的国家一块连续的国界线根本描述不完必须拆成多个多边形。MultiPolygon 的 coordinates 比 Polygon 多一层嵌套——数组里装的是多个多边形每个多边形的第一个环是外边界后面的环是内边界也就是国中之国这类镂空结构。用一段代码统计所有要素类型能快速判断这份数据包是否混入了形状异常的数据from collections import Counter geom_types Counter() for feat in data[features]: g feat.get(geometry) geom_types[g[type] if g else None] 1 print(geom_types) # 常见输出: {Polygon: 50, MultiPolygon: 130}以及为 0 的 None写遍历坐标的脚本时必须同时兼容这两种类型。很多自己写过滤器的人只处理了 Polygon结果 MultiPolygon 国家的统计数据直接变成 0。处理几何的通用思路是先判断 type再按对应层数展开后面第 5 章的体检脚本会给出完整的展开写法。3. 把数据包接入渲染管线前端地图库加载与最小示例结构摸透了下一步是把 GeoJSON 真正渲染出来。浏览器端最常见的三个选择是专门做图表的地理组件、传统的 WebGIS 库以及依赖第三方密钥的商业地图 SDK。前两种开源、免密钥、文件丢进去就能出图原型阶段最省事第三种因为涉及密钥和商业授权我一般不会把它作为第一选择。下面用两个最小示例把前两种跑通。3.1 接入 EChartsregisterMap 注册全局地图如果你做的是大屏和报表大概率已经装了 ECharts 全家桶。给 ECharts 接入世界地图核心只有两个动作先用 fetch 拿到 JSON再调用 registerMap 把数据注册成一张名叫 world 的地图。fetch(./world.json) .then(res res.json()) .then(worldJson { echarts.registerMap(world, worldJson); const chart echarts.init(document.getElementById(mapContainer)); chart.setOption({ geo: { map: world, // 与 registerMap 的第一个参数保持一致 roam: true, // 开启缩放和平移 itemStyle: { areaColor: #eaeaea, borderColor: #888 } } }); });registerMap 第一个参数是给这张地图起的内部名字第二个参数是完整的 GeoJSON 对象。之后不管是 geo 组件还是系列里的 map 类型只要 map 属性写成同一个名字就能引用到这张底图。文件放本地时 fetch 的路径要跟实际部署路径对齐否则 Network 面板会先报一次 404然后再报一次 registerMap 未找到地图。3.2 接入 Leaflet用 L.geoJSON 把边界铺成可交互图层要的是可缩放、可点击、能叠加多个矢量图层的地图应用用 Leaflet 这类 WebGIS 库更顺手。Leaflet 的 L.geoJSON 构造器可以直接消费 GeoJSON 对象每个国家自动变成一个可交互矢量图层import L from leaflet; import leaflet/dist/leaflet.css; import worldJson from ./world.json; const map L.map(map).setView([30, 10], 2); L.tileLayer(https://{s}.你的瓦片服务域名/{z}/{x}/{y}.png, { maxZoom: 18 }).addTo(map); const geoLayer L.geoJSON(worldJson, { style: { color: #555, weight: 1, fillColor: #f0f0f0, fillOpacity: 0.8 }, onEachFeature: (feature, layer) { const p feature.properties || {}; layer.bindTooltip(String(p.name || p.NAME || )); } }).addTo(map);瓦片地址里的域名需要替换成你自己有权限的瓦片服务Leaflet 本身不生产底图只负责把 GeoJSON 边界叠加在经纬度对齐的底图上。onEachFeature 是给要素绑定交互的钩子这里用兜底写法读取 name 或 NAME就是为了应对老数据包字段大小写不统一的问题。3.3 加载白屏的根因顶点数量失控跑起来之后最常见的症状是页面白屏几秒随后浏览器卡死这是因为 GeoJSON 里顶点数量太多了。浏览器渲染地图不是画一个国名而是逐点构建路径全世界边界叠在一起几十万个坐标点对 Canvas 和内存都是实打实的压力。写一个统计顶点的函数预先评估压力function countVertices(geo) { let count 0; geo.features.forEach(f { const g f.geometry; if (!g) return; if (g.type Polygon) { g.coordinates.forEach(ring count ring.length); } else if (g.type MultiPolygon) { g.coordinates.forEach(poly poly.forEach(ring count ring.length)); } }); return count; } console.log(countVertices(worldJson));一般超过 20 万顶点就值得做简化超过 50 万基本必须简化。简化方向有两个降低坐标精度把 6 位小数压到 3 位以及用简化算法剔除弯曲度小但对视觉影响不大的中间点。第 5 章会说到相对省事的 TopoJSON 方案。4. 旧数据包避坑指南国名匹配、坐标偏移、边界过时的 5 条排查记录旧数据包能用和好用之间隔着几个经典坑。这些坑多数不是数据包本身坏了而是使用者默认它跟现代地图体系完全兼容。下面按现象、原因、解决的顺序写每一条都是我在实际项目里自己撞过的。4.1 国名对不上地图区域渲染出来全是空白现象ECharts 里设置 series.data 给每个国家配了数值地图也注册成功了但所有区域都不着色控制台没有任何报错。原因series 数据项里的 name 字段与 GeoJSON properties 里的国家名不一致差一个字母、多一个空格、中英文混用都会匹配失败。解决先确认 GeoJSON 里国家名字段的真实值再写一个映射关系把业务数据国家的名称转成 GeoJSON 里的拼写或者反过来。顺序上先打印 GeoJSON 里的所有 name 去重列表跟业务表对比别靠肉眼猜。4.2 边界整体偏移WGS84 坐标遇到火星坐标底图现象边界线加载出来了但整个国家的轮廓跟底图错开一段距离放大后更明显陆地区域跟真实地名对不上。原因数据包边界坐标是 WGS84 经纬度但你在用的底图服务返回的是 GCJ-02 加密坐标两个坐标系叠加必然错位。解决优先换用兼容 WGS84 的底图服务这是最干净的做法。不要在生产环境手动加一个看着差不多的偏移量不同区域偏移量不一样肉眼对齐只适合演示。4.3 高纬度国家图形异常投影坐标系被套了两层现象越靠近极地的国家形状越怪甚至整个图形被裁掉一块。原因GeoJSON 里的坐标本身是经纬度渲染时地图库又默认套了一层 Web Mercator 投影等于对已经投影过的数据做二次投影。解决确认数据坐标系是 EPSG:4326渲染端不要额外开启投影转换在 ECharts 的 geo 组件里不要给坐标数据再配投影参数保持直出。4.4 要素列表混入地区统计图里多出几个国家现象做一个各国数值排名的仪表盘发现图例里出现了一些不算国家的地区名称数值是空的排名也乱了。原因部分数据包为了完整性把非主权行政区划也装进了 features。解决用 iso_a3 字段过滤为空的三位代码要素直接剔除。过滤逻辑要写在读取数据之后、注册地图之前这样连渲染的力气都省了。4.5 数据包缺新国家边界过时怎么补救现象某些国家列表里没有近期新增的领土实体旧版数据包甚至沿用了旧国名展示出去跟新闻里对不上。原因这个数据包的快照时间早整理者后续没做增量更新这是所有 old 版本的共性。解决把这类数据包定位成底图而不是权威区划。缺哪个国家就从更新的数据源单独导出该国的 GeoJSON用第 5 章的切片逻辑生成单个文件再替换进原数据包的 features 里用补丁的方式更新而不是整体换包。5. 把数据包改造成自己的底图资源切片、压缩与体检作为一个会被反复使用的底图原始文件直接上线属于偷懒做法。用三个步骤把它改造成按需加载、体积可控、质量可查的工程资源这一步做完才能放心丢进生产环境。5.1 按 iso_a3 代码切出单个国家按需加载先降体积很多页面其实只需要某个区域或几个国家不必把全世界两三百个要素一次全灌进浏览器。按 iso_a3 做切片是最直接的按需加载方案const fs require(fs); const world JSON.parse(fs.readFileSync(world.json, utf-8)); const code process.argv[2]; // 目标国家的 iso_a3 代码 const target world.features.filter(f f.properties f.properties.iso_a3 code ); if (target.length 0) { console.error(未找到 iso_a3${code} 的要素先确认字段名与大小写); process.exit(1); } fs.writeFileSync( ${code}.json, JSON.stringify({ type: FeatureCollection, features: target }) ); console.log(已写出 ${code}.json);这段脚本用 Node 跑命令行传一个三位国家代码就能输出对应的单国 GeoJSON。切片后的单文件通常只有几十到几百 KB加载速度比全量包快一个量级。注意老数据包可能出现 iso_a3 和 ISO_A3 混用跑之前先打印一条看看字段真实名称。5.2 转成 TopoJSON共享边界只存一次体积直接减半两个相邻国家共享的那条边界在 GeoJSON 里会被完整存两遍全世界两百多个国家重复的坐标点非常多。TopoJSON 把共享边界提取成 arcs 索引只存一次体积通常能降 50% 以上。命令行转换很简单topojson -o world.topojson world.json转换前先确认顶点数量顶点越多压缩收益越大。如果你的环境里没有这个命令可以用在线转换服务输入 GeoJSON 输出 TopoJSON。浏览器端渲染时 TopoJSON 不能直接被地图库消费需要先用配套解析函数转回 GeoJSON 再交给渲染管线转换本身很快通常可以忽略不计。5.3 数据体检脚本检查坐标越界、空几何与重复代码不管是原包还是自己切出来的单国文件上线前都跑一遍体检。下面这个 Python 脚本覆盖了我平时最关心的几类问题import json with open(world.json, encodingutf-8) as f: data json.load(f) issues [] for i, feat in enumerate(data[features]): props feat.get(properties) or {} geom feat.get(geometry) if geom is None: issues.append(f#{i} 几何为空属性: {props}) continue if geom[type] not in (Polygon, MultiPolygon): issues.append(f#{i} 几何类型异常: {geom[type]}) continue coords [] if geom[type] Polygon: coords [c for ring in geom[coordinates] for c in ring] else: coords [c for poly in geom[coordinates] for ring in poly for c in ring] for lon, lat in coords: if not (-180 lon 180 and -90 lat 90): issues.append(f#{i} 坐标越界: ({lon}, {lat})) break print(f共检查 {len(data[features])} 个要素发现 {len(issues)} 个问题) for line in issues[:20]: print(line)检查项包括四类几何对象为空、几何类型不在预期集合内、经纬度越界、以及通过统计字段时发现的重复 iso_a3 代码。跑完输出的问题列表如果是 0这份数据才算达到上线最低标准。6. 发布前跑一道综合健康检查几十秒省一次返工切好的底图不要直接提交上线我会在发布脚本里加一道健康检查把前面分散的检查合并成一个通过/不通过的结论。核心指标就四个格式合法、要素数量在合理区间、顶点总数在预算内、重复代码为 0。function healthCheck(geo) { if (!geo || geo.type ! FeatureCollection || !Array.isArray(geo.features)) { return { pass: false, reason: 不是标准 FeatureCollection }; } const features geo.features; const vertexCount countVertices(geo); // 复用第 3 章的统计函数 const codes features .map(f (f.properties || {}).iso_a3) .filter(Boolean); const report { pass: true, featureCount: features.length, vertexCount, duplicateCodes: codes.filter((c, i) codes.indexOf(c) ! i) }; if (features.length 100) { report.pass false; report.reason 要素数量偏少疑似数据被截断; } if (vertexCount 200000) { report.pass false; report.reason 顶点过多需要先做简化或转 TopoJSON; } if (report.duplicateCodes.length 0) { report.pass false; report.reason 存在重复国家代码匹配时会串数据; } return report; }20 万的顶点阈值来自第 3 章的经验值如果你的渲染目标主要是一张静态图可以放宽到 50 万要做高频缩放交互建议压到 10 万以内。阈值写成常量而不是散落在代码里后续换数据包时只需要改一处。我的习惯是任何一份新拿到的 GeoJSON 都先跑一遍这个函数再决定它值不值得进项目。毕竟数据格式烂掉的项目后面每一步都在填坑十几秒的体检能省一次大的返工这笔账怎么算都划算。希望帮到你。本文还有配套的精品资源点击获取