世界国家GeoJSON大全:边界数据解析、可视化与优化实践

发布时间:2026/10/17 3:58:28
世界国家GeoJSON大全:边界数据解析、可视化与优化实践 简介这是由 Natural Earth 数据整理而来的世界国家/地区 GeoJSON 地图合集覆盖全球主要国家与地区面向 Web 开发、GIS 制图与数据可视化场景可帮助开发者在 ECharts、Leaflet、Mapbox 等常用地图库中快速加载并绘制国界与区域边界也能用于离线分析、区域检索和地图原型验证。压缩包大小约 1.69MB包含 2000 余个文件其中绝大部分为 JSON 格式的 GeoJSON 地图文件按国家/地区拆分覆盖多个独立边界另有少量 Markdown 说明文件与 Unlicense 许可文件便于查看数据说明和授权范围。该资源目前已有 3692 人学习浏览。整套数据在保障全球版图完整性的前提下兼顾了体积与加载效率开发者无需自行爬取或手工整理边界可开箱即用初学者也能通过具体文件理解 GeoJSON 的多边形结构、精度与投影差异是一份兼顾生产项目与教学自学的实用基础数据包。1. 你需要的“地图JSON”可能不是随便哪个JSON做地图可视化项目时我发现最大的阻碍往往不是图表库而是一份能直接用起来的边界数据。这份“世界国家geojson大全”把各国地图整理成 JSON 文件也就是地图JSON 格式能直接喂给 Leaflet、Mapbox、GeoPandas 等常见工具。它挂着 old 标记说明文件是历史快照不是持续更新版这意味着你可以用这份 geojson 大全把原型跑通但上线前必须自己核对时效性。适合正在做离线底图、区域统计、报表可视化的开发者与学生也适合想快速验证工具链的人拿去当素材。2. 这份世界国家 GeoJSON 合集先认清数据组织再决定怎么用拿到资源不要急着写代码画图先花十分钟把文件结构看清楚。同一份合集里各文件可能来自不同机器、不同导出工具结构参差不齐。这一章我会拆开 GeoJSON 的骨架把最容易引起解析问题的两个点单独拎出来。2.1 一个 Feature 就是一个国家边界从 FeatureCollection 到 MultiPolygonGeoJSON 的顶层类型通常是FeatureCollection里面features数组装着一个个国家。也有的文件直接把一个Feature当顶层程序里如果不兼容这种单数结构一读就崩。下面是一段演示数据我把一个国家简化成正方形陆地来展示结构{ type: FeatureCollection, name: fictio, crs: { type: name, properties: { name: EPSG:4326 } }, features: [ { type: Feature, properties: { name: 虚构A, iso_a2: FA }, geometry: { type: MultiPolygon, coordinates: [ [ [ [0, 0], [1, 0], [1, 1], [0, 1], [0, 0] ] ] ] } } ] }最外层crs是对坐标系的描述这里的EPSG:4326就等于说“文件里用的是经纬度”不是投影坐标。geometry.type是MultiPolygon意思是这个实体由多个多边形组成。很多国家的真实边界都由主陆地和离岛构成所以普遍使用 MultiPolygon很少用单数 Polygon。properties是附加信息这份合集里字段并不统一。我拆过几十个类似合集最常见的字段组合是字段名通常含义使用建议name国家/地区显示名用于展示但可能重复iso_a2 / iso_a3两字母/三字母代码用于做关联键更稳定id数字 ID文件内唯一跨文件未必唯一如果你的统计表里没有iso_a3宁可用nameid拼接成唯一键也不要只拿name做合并。原因很简单不同语言的文件里同一个国家的name可能写成完全不同的字符串。2.2 坐标系是个隐藏参数WGS84 与经纬度顺序GeoJSON 规范要求坐标是 WGS84 经纬度顺序为[经度, 纬度]。老数据的来源五花八门经常会有[纬度, 经度]的异类。判断方法非常简单看坐标数值范围检查项正常范围异常表现经度-180 180大于 180 说明顺序写反或用了米制纬度-90 90小于 -90 同理第一个坐标值通常不是 90 以内的小数若是则高度怀疑顺序错误举个例子一个正常点位是[118.7, 32.1]经度在前。如果文件里写的是[32.1, 118.7]大部分前端库会把它画到东经 32 度、北纬 118 度附近直接落到海里。最直接的验证方法是用 QGIS 或 GeoPandas 读一遍再叠一层官方底图肉眼对比边界位置。遇到写反的数据不要手动改坐标用 Shapely 的transform把每个点交换一下即可from shapely.ops import transform import geopandas as gpd def swap_xy(x, y, zNone): return (y, x, z) if z is not None else (y, x) world gpd.read_file(data/fictio.geojson) world[geometry] world.geometry.apply(lambda geom: transform(swap_xy, geom)) world.to_file(data/fictio_fixed.geojson, driverGeoJSON)这段代码只做了交换坐标的第一项和第二项对三维坐标也能处理。transform是 Shapely 的仿射变换工具会逐个顶点调用回调函数。注意lambda里对几何库的调用在数据量很大时会有点慢可以先拿一个文件测试耗时。2.3 动手前先跑一遍数据体检脚本与其逐个文件肉眼检查不如写一个小脚本把整个目录扫一遍。下面这段是我每次拆这类数据都会先跑的体检脚本import json import glob for path in glob.glob(geojson/*.geojson): with open(path, encodingutf-8-sig) as f: data json.load(f) feats data.get(features, []) if not feats: print(f{path}: 0 features可能不是 FeatureCollection) continue geom_types {} props_keys set() for feat in feats: gt feat[geometry][type] geom_types[gt] geom_types.get(gt, 0) 1 props_keys.update(feat[properties].keys()) print(f{path}: {len(feats)} features, {geom_types}, props{sorted(props_keys)})这段代码用glob.glob抓取目录下所有.geojson文件utf-8-sig编码读取可以自动吞掉 BOM 头。循环里统计了几何类型和 properties 字段集合运行后你一眼就能看到哪些文件是 Polygon哪些是 MultiPolygon哪些文件缺失了name字段。如果你看到某个文件 props 只有id说明它可能来自另一个导出工具后续处理要单独兼容。另外我还会在脚本里加一个坐标极值统计用于发现上面说的经纬度写反问题。可以把json.load换成geopandas.read_file然后直接取几何体的total_bounds来快速判断坐标范围是否合理。两种方式各有用途纯 JSON 脚本更快适合检查结构和编码GeoPandas 版本更贴合实际使用场景适合确认几何是否可被空间库直接读取。3. 从 JSON 到画面浏览器渲染与本地查询的两种路径数据体检通过后你会看到两种完全不同的使用场景一个是在网页上把国家边界画出来另一个是在本地做空间运算判断某个点落在哪个国家。两条路都要走通因为地图项目从示意图进入业务逻辑后这两种能力会在同一个应用里相遇。3.1 Leaflet 五分钟画出国家边界Leaflet 依然是我给轻量页面做地图的首选。下面是最简 HTML把data/fictio.geojson渲染成一张可交互地图!DOCTYPE html html langzh-CN head meta charsetutf-8 / link relstylesheet hrefhttps://unpkg.com/leaflet1.9.4/dist/leaflet.css / stylehtml, body, #map { height: 100%; margin: 0; }/style /head body div idmap/div script srchttps://unpkg.com/leaflet1.9.4/dist/leaflet.js/script script const map L.map(map).setView([0, 0], 2); L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, { maxZoom: 18 }).addTo(map); fetch(data/fictio.geojson) .then(res res.json()) .then(geojson { L.geoJSON(geojson, { style: { color: #444, weight: 1, fillColor: #f3c, fillOpacity: 0.2 }, onEachFeature: (feature, layer) { if (feature.properties?.name) { layer.bindTooltip(feature.properties.name); } } }).addTo(map); }) .catch(err console.error(加载失败, err)); /script /body /html这段代码分三层地图初始化、底图瓦片、GeoJSON 图层。setView([0, 0], 2)决定初始中心和缩放级别真正把边界文件加到地图上的是L.geoJSON。style里的weight: 1控制边界线粗细fillOpacity: 0.2控制填充透明度调成 0 就只剩线框。onEachFeature会在每个 Feature 转成图层后回调我在这里给每个国家绑了一个 tooltip鼠标悬停能显示名称。要提醒的是直接双击 HTML 文件打开会报 CORS 错误因为浏览器禁止从file://协议读本地 JSON。起一个静态服务就好python -m http.server 8000然后浏览器访问http://localhost:8000。这段命令会在当前目录架上 HTTP 服务不用装额外依赖。文件大的时候fetch 加载会把主线程卡住所以我在页面里加了.catch打日志方便判断是数据问题还是路径问题。3.2 用 GeoPandas 做“点在哪国”的空间查询网页端画完真正干活的是后端。GeoPandas 可以把 GeoJSON 读成 DataFrame 那样的结构再用空间关系做筛选。下面是一个最基本的命中测试import geopandas as gpd from shapely.geometry import Point world gpd.read_file(data/fictio.geojson) pt Point(0.5, 0.5) mask world.geometry.contains(pt) print(world.loc[mask, name].tolist() if mask.any() else 未命中)Point(0.5, 0.5)的两个参数是经度和纬度与 GeoJSON 的顺序保持一致。contains的语义是“面完全包含点”点在边界上时返回 False。如果你需要把边界上的点也算作命中改用covers。当你有大量点和大量国家要做匹配时千万别用双重 for 循环。最高效的姿势是空间连接cities gpd.GeoDataFrame( {city: [X城, Y城]}, geometry[Point(0.5, 0.5), Point(2.0, 2.0)], crsEPSG:4326 ) joined gpd.sjoin(cities, world, howleft, predicatewithin) print(joined[[city, name]])sjoin底层会先构建空间索引比逐点contains快一个数量级。predicatewithin表示点在面内也可以用intersects来做更宽松的匹配。空间连接得到的 DataFrame 会带有两个表的所有属性字段实际操作时注意name字段可能重复最好加上iso_a2一起带出来。3.3 性能问题直接丢给浏览器不如先简化GeoJSON 里的经纬度边界如果来自高精度采集一个文件动辄几十 MB浏览器渲染时先加载再重绘效果就是白屏几秒拖拽像幻灯片。这里的血泪教训是别让浏览器直接吞原文件先做简化。import geopandas as gpd world gpd.read_file(data/fictio.geojson) before world.geometry.area.sum() world[geometry] world.geometry.simplify(0.01, preserve_topologyTrue) after world.geometry.area.sum() print(f面积变化: {(after - before) / before * 100:.2f}%) world.to_file(data/fictio_simplified.geojson, driverGeoJSON)simplify是 Douglas-Peucker 算法的封装第一个参数0.01是容差单位是度约 1.1 公里容差越大删掉的顶点越多。第二个参数preserve_topologyTrue是关键它会尽量避免相邻面的边界互相穿透代价是速度慢一些但换来的结果更可靠。对于世界地图这种尺度我一般会把容差设为 0.005 到 0.01 之间对应 500 米到 1 公里的精度。面积损失可以接受体积却可能降一个量级。简化之后还要做一步按洲或者按国家文件分片让前端按需加载。这份资源本来就是“一国家一个文件”天然适合这种模式千万不要为了省请求把所有文件合到一起否则一次加载就把内存吃光了。4. 格式转换与二次加工TopoJSON、Shapefile、裁剪三条路一次说清GeoJSON 不是万能的。有的后端服务只接受 Shapefile有的前端地图为了压缩体积要转 TopoJSON还有的业务场景只想截取地图的一块局部区域。这一章把三条最常走的加工路线全部打通每条我都会给出可直接复制的命令或脚本。4.1 ogr2ogr 一行命令完成格式转换GDAL 自带ogr2ogr格式转换一般只用一行。GeoJSON 转 Shapefile 的命令ogr2ogr -f ESRI Shapefile output_dir fictio.geojson执行后output_dir会生成至少四个文件.shp存几何、.shx存索引、.dbf存属性、.prj存坐标系描述。手工从网上复制文件时经常只复制了.shp而丢掉其他后缀结果 GIS 软件里打不开。正确的分发方式是打包整个目录。如果目标坐标系不是 EPSG:4326比如要叠加 Web Mercator 底图加上投影参数ogr2ogr -f ESRI Shapefile -t_srs EPSG:3857 -lco ENCODINGUTF-8 output_dir fictio.geojson-t_srs指定输出坐标系-lco ENCODINGUTF-8让dbf属性表用 UTF-8 编码避免属性名为中文时在 ArcGIS 里变成乱码。GDAL 在 EPSG:4326 和 EPSG:3857 之间的转换会自动调用投影算法不需要你手工换算坐标。反向转换同样简单把 Shapefile 转回 GeoJSONogr2ogr -f GeoJSON fictio_back.geojson output_dir/fictio.shp这里要注意ogr2ogr对目录参数的处理第二个参数是输出文件第三个参数是要读取的数据源。如果数据源是一个目录它默认读该目录下的全部图层这会导致输出被塞进一个 GeoJSON。所以当目录里文件很多时我会显式指定.shp文件。4.2 TopoJSON 的空间优化原理与取舍TopoJSON 和 GeoJSON 最大的不同在于边界存储方式。GeoJSON 中相邻两个国家共用的边界是重复存储的TopoJSON 会把它们拆成弧段同一段弧段只存一次其他多边形按索引引用它。这种压缩方式对世界地图特别有效因为各国边界大量相邻体积通常能缩减一半以上。用命令行工具从 GeoJSON 生成 TopoJSONnpx topojson -o data/world.topojson --id-propertyiso_a2 -- data/fictio.geojson data/fictio_b.geojsonnpx会先临时安装topojson命令行工具。--id-propertyiso_a2指定properties.iso_a2作为每个 Feature 的唯一标识后面列输入文件。这个 ID 在之后从 TopoJSON 还原 GeoJSON 时很关键因为topojson-client是按id来匹配几何对象的。在浏览器里使用 TopoJSON 需要先把拓扑解码成 GeoJSON。Node 环境下用topojson-clientconst topojson require(topojson-client); const fs require(fs); const topology JSON.parse( fs.readFileSync(data/world.topojson, utf-8) ); const geoJson topojson.feature( topology, topology.objects.collection ); fs.writeFileSync(data/world_decoded.geojson, JSON.stringify(geoJson));topojson.feature会把 TopoJSON 的弧段引用还原成 GeoJSON 的 Polygon/MultiPolygon。topology.objects.collection中的collection是生成 TopoJSON 时默认的对象名如果你的命令没用--topology-name改过就是这个名。解码后的 GeoJSON 就能交给 Leaflet 或 GeoPandas 了。可以看出 TopoJSON 的优化是空间换时间体积更小但使用前必须解码且无法直接用 Shapely 读取。如果你的项目主要在 Python 侧做空间分析完全不需要转 TopoJSON直接保留 GeoJSON 反而省事。4.3 按矩形或行政区裁剪边界地图数据经常只需要局部。比如只关心经纬度 0 到 1、纬度 0 到 1 之间的区域用 GeoPandas 的clip切出来import geopandas as gpd from shapely.geometry import box world gpd.read_file(data/fictio.geojson) # minx, miny, maxx, maxy顺序是经度、纬度 region box(0, 0, 1, 1) clipped gpd.clip(world, region) print(f裁剪前 {len(world)} 个 Feature裁剪后 {len(clipped)} 个 Feature) clipped.to_file(data/region.geojson, driverGeoJSON)box生成的矩形是 Shapely 的 Polygongpd.clip会对每一个原始多边形求交集。落在矩形外的部分被裁掉与矩形相交的部分会产生新的顶点文件体积会略微增加。如果你希望保留原始边界形态先intersects筛选再取但简单裁剪已经能满足大部分区域统计需求。注意gpd.clip是较新版本 GeoPandas 才有的方法。旧版项目里我用gpd.overlay(world, region, howintersection)替代效果相同但更慢。还有一点box的坐标顺序是(minx, miny, maxx, maxy)也就是左下角和右上角。误写成(0, 1, 1, 0)这种写法会得到空结果因为矩形在 Y 轴上是倒的。这类错误不会报错只会返回空表所以打印保留数量是很有必要的验证步骤。5. 避坑手册从文件读不出来到地图上多一块陆地的真实翻车现场前面几章是顺利路线这一章写我实际拆数据时踩过的坑。每一条按“现象 → 原因 → 解决”组织几乎都能在你这台机器上复现。建议先通读再回看你手里的具体文件能省不少排查时间。5.1 文件打不开BOM、UTF-8 与 Excel 的惯性误解现象json.load一读就抛异常通常是Unexpected UTF-8 BOM或Invalid control character。明明文件在编辑器里看着是正常的为什么程序读不了原因数据作者在 Windows 上用记事本或 Excel 保存过文件写入了 BOM 头。Python 的utf-8解码器会把 BOM 当成一个不可见字符导致 JSON 解析第一行就失败。还有一部分文件被 Excel 改成了 UTF-16 编码直接按 UTF-8 读自然是乱码加报错。解决读取时一律用utf-8-sigwith open(path, encodingutf-8-sig) as f: data json.load(f)如果文件已经是 UTF-16先把原始字节读出来再用bytes.decode(utf-16)手动转一下。批量处理时任何“自动统一编码”的做法都不可靠我的做法是先把每个文件前三个字节打印出来看到EF BB BF就知道是 BOM看到FF FE就知道是 UTF-16 LE。这个前置检查十行以内就能写完却能省掉后面几小时的排查。5.2 经纬度写反所有图形全部横过来现象渲染出来的国家边界整体被旋转了 90 度有的直接出现在海里有的图形压扁看不出正常轮廓。原因GeoJSON 规范要求[经度, 纬度]但很多老工具生成的 GeoJSON 实际上是[纬度, 经度]。前端库按规范的顺序解析就把图形横过来了。解决先按前面第 2 章说的方法判断再用transform交换坐标。这里给出一个更通用的 Python 函数from shapely.ops import transform import geopandas as gpd def swap_xy(x, y, zNone): return (y, x, z) if z is not None else (y, x) world gpd.read_file(data/fictio.geojson) world[geometry] world.geometry.apply(lambda g: transform(swap_xy, g)) world.to_file(data/fictio_fixed.geojson, driverGeoJSON)transform的回调会传入坐标值函数返回交换后的新值。对 Point、LineString、Polygon 都有效不用关心几何类型。转换后重新打印total_bounds确认经度范围在 -180180、纬度范围在 -9090。如果还不对问题可能就不是顺序而是投影坐标系需要回到 ogr2ogr 转换。5.3 岛屿去哪了MultiPolygon 被拆坏现象地图上只画出了主体部分周边岛屿全不见了。放大缩小都找不到边界像被啃掉一块。原因这份合集中部分国家可能被拆成多个 Feature主陆是第一个岛屿是后面的几个。有的解析逻辑只读取features[0]岛屿自然被丢弃。另一种原因是几何体虽然是 MultiPolygon但代码在取 coordinates 时少剥了一层括号导致解析出的多边形是缺环的。解决不要假设一个 Feature 就是完整国家。渲染前用dissolve把相同属性字段的几何合并import geopandas as gpd world gpd.read_file(data/fictio.geojson) world world.dissolve(byname).reset_index() print(len(world))dissolve会把name相同的所有 Feature 的几何合并成一个 MultiPolygon自动把岛屿并回主陆。但这个操作依赖name唯一如果两个国家名字相同就会被错误合并。所以更稳的做法是dissolve(byiso_a2)。执行后随机抽查几个文件用 QGIS 打开看一眼确认岛屿没有丢失。5.4 边界争议同一个地方有两份数据现象两张地图叠在一起时同一区域出现两条不同的边界线中间形成一道空隙或重叠区。原因不同机构对边界的几何定义不同有的取行政图斑有的取简化后的线边界有的来自历史协议。两份数据如果不能互相对齐视觉上自然五花八门。解决项目不要混用多个来源的同一地区边界。如果必须对比就把每个来源标注成单独图层用不同颜色绘制不要试图用代码自动“统一”它们。你的任务是向用户展示差异而不是替某个来源解决边界问题。我在做类似项目时会为每个图层加一个 source 字段这样任何关于边界差异的疑问都能追溯到出处而不是在代码里被悄悄抹平。5.5 旧数据的地下坑边界早就变了现象用这份 old 合集做原型时发现某些区域名称与最新行政区划对不上甚至出现了几个已经不存在的历史边界。原因数据是历史快照代表的不是现状。边界每隔一段时间就会调整行政区代码也可能重排旧文件自然跟不上。解决先把这份资源定位成“demo 专用”正式项目要换有发布周期、带时间戳的数据源。拿到旧数据时在项目目录里放一个DATA_VERSION.txt记录“本次使用的是哪一版、哪一天打包的”。如果你的业务涉及行政区划不要只依赖国家层可以再叠加一级或二级行政区边界做校正。我从那以后做演示前都会先核对几个关键位置的边界再把旧数据交给用户避免把历史边界当成现状讲出来。6. 给这份边界数据做一次最小体检从合法性到体积优化前面把渲染、查询、转换、裁剪都过了一遍最后回到最容易被跳过的环节几何合法性检查和体积优化。这一步不做前面所有流程都可能被一条自相交的边界带偏。6.1 几何合法性检查is_valid 与 buffer(0) 修复拿到一份 GEOJSON 后第一件事不是画到地图上而是检查几何对象是否合法import geopandas as gpd world gpd.read_file(data/fictio.geojson) valid world.geometry.is_valid print(f有效 {valid.sum()} / {len(world)}无效 {len(world) - valid.sum()} 个) if (~valid).any(): world.loc[~valid].to_file(data/invalid.geojson, driverGeoJSON)is_valid由 Shapely 基于几何规则判断。最常见的非法情况是自相交也就是一条边穿过自身形成的环。它不会让读取报错但会使后续的intersects、contains结果变得不可靠。遇到无效几何我一般先用buffer(0)修复world[geometry] world.geometry.buffer(0)buffer(0)会用半径为零的缓冲重新构造几何相当于给每条边做一次“自动纠正”对大多数自相交问题有效。注意修复后面积可能会有微小变化所以下一步要打印面积变化确认在合理范围。6.2 简化阈值与面积损失对比把体积砍到 1/5 再上线简化不能只凭感觉定阈值我会用面积损失做标尺world gpd.read_file(data/fictio.geojson) before world.geometry.area.sum() world[geometry] world.geometry.simplify(0.005, preserve_topologyTrue) after world.geometry.area.sum() print(f面积变化: {(after - before) / before * 100:.2f}%) world.to_file(data/fictio_simplified.geojson, driverGeoJSON)0.005度大约相当于 550 米这个阈值在洲级尺度上几乎看不出变化但文件里冗余顶点会大幅减少。如果面积变化超过 3%把阈值调小如果变化为 0说明原文件顶点已经不多不用再简化。preserve_topologyTrue必须保留它能防止简化后的相邻边界互相穿插。除了简化还有一招把坐标精度从浮点小数点后 6 位降到 5 位体积能再小一点。这个操作适合不需要亚米级精度的场景比如地图缩放到州级视角时小数点后第 6 位的抖动根本看不见。从那以后我每次拿到任何一份边界数据包括这份世界国家 geojson 大全都会强制走一遍is_valid buffer simplify 面积损失对比再让数据进项目。流程不复杂但能避免后面在渲染、查询、转换里反复踩雷。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询