☰
GeoJSON 实战避坑:坐标约束、内网编辑器与 ArcGIS 转换
2026/10/28 4:41:56 网站建设 项目流程

1. 一行坐标数组撑起的格式:GeoJSON 的对象模型与硬性约束

第一次接触 GeoJSON 的人,大多会觉得它简单到没什么可讲的——不就是个 JSON 嘛,塞几个坐标进去就完事了。我在前几个项目里也是这么想的,直到有一次把一批面数据导进系统,地图上看着正常,一做空间统计就发现面积少了将近一半,最后查出来是内环方向反了、被某个库当成外环重复计算。从那以后我对这个格式的态度就变了:它的语法确实简单,但它的语义约束藏在规范文档的角落里,不踩一次坑基本记不住。

GeoJSON 本质上是一种用 JSON 表达地理要素的交换格式,最新一版规范把它固定成七种几何类型加三种容器类型,坐标系强制为 WGS84 经纬度。它出现的目的很直接:让地理数据能像普通 JSON 一样被 HTTP 接口传、被前端直接画、被轻量脚本处理,而不需要带着一套完整的空间数据库。所以它特别适合中小体量的数据交换、前后端联调、快速可视化、原型验证。反过来,如果你要做几十万要素的渲染、要做复杂的拓扑运算、要保存圆弧和弧段,它就力不从心了。

下面这几节,我把这个格式里真正会咬人的地方拆开讲。

1.1 三种容器加七种几何体,嵌套规则其实很死

GeoJSON 的所有对象都在一个封闭的类型集合里。顶层可以是一个FeatureCollection,也可以是单个Feature,甚至可以直接放一个裸的几何对象。但无论从哪层进,最终落到geometry字段上的type,只能是下面这七种之一。

类型坐标结构常见用途
Point[lon, lat]点位标注、POI
MultiPoint[[lon, lat], ...]同质多点集合
LineString[[lon, lat], ...]道路、轨迹、管线
MultiLineString[[[lon, lat], ...], ...]多段线、河流分叉
Polygon[[[lon, lat], ...], ...]面状要素、行政区
MultiPolygon[[[[lon, lat], ...], ...], ...]多部件面、岛屿群
GeometryCollection{"geometries": [...]}异质几何混合

这里有个很容易被忽略的点:GeometryCollection里面的元素可以是任意几何类型,但不能是另一个GeometryCollection。我见过有人为了表达"一个要素包含两个不相连的区块和一个中心点",在GeometryCollection里再套一层GeometryCollection,本地某些库能读,但丢到别的解析器里直接抛错。正确做法是把它们拆成同一个FeatureCollection里的多个Feature,用相同的业务主键关联起来,或者用MultiPolygon加一个独立的 Point 要素。

另一个常见问题是把Feature的字段名写错。规范的字段是type、geometry、properties、id,其中geometry可以为null(表示这个要素只有属性没有位置,比如待定位的地址)。我见过把geometry写成geom或者geojson的,自己在测试环境跑得好好的,一对接就全挂——因为解析器找不到geometry字段,会把整个要素当作无效数据跳过,而且是静默跳过,不报错。

1.2 坐标顺序、环方向、闭合:三个藏得最深的硬约束

第一个是坐标顺序。GeoJSON 规定是[经度, 纬度],也就是[x, y],和数学上的习惯一致,但和很多人脑子里"先纬度后经度"的直觉相反。这个错误的后果分两种情况:在北纬 90 度以北或者南纬 90 度以南的区域(比如经度写成 116,纬度写成 39 之后颠倒),纬度值超过 ±90,多数解析器会直接判定非法;而在低纬度区域,两个值都落在合法范围内,程序照单全收,点位就静静地偏移到别的地方去了。后者才可怕,因为不报错。

提示:拿到一批陌生来源的 GeoJSON,先抽几个点看coordinates第一位和第二位的数值范围。如果第一位普遍在 -90 到 90 之间而第二位大于 90,基本可以确认顺序写反了。

第二个是环的方向。规范采用右手规则,要求Polygon的外环逆时针、内环(洞)顺时针。这一条在规范里同时写了一句"解析实现不应当因为方向错误而拒绝数据",导致很多库选择不校验。但问题在于,一部分做面积计算、栅格化、空间求交的底层库是靠方向判断哪个是外环的,方向反了它们会把洞当外环、把外环当洞,算出来的面积能差出一个数量级。我上面提到的面积少一半,就是这个原因。

第三个是闭合。LineString不要求首尾相同,但Polygon的每一个环都必须首尾坐标完全一致。这里的"完全一致"包括小数位,[116.400000, 39.900000]和[116.4, 39.9]在数值上相等,序列化后字符串不同,某些严格比对字符串的实现会判定未闭合。用代码生成环的时候,最好的办法是直接把第一个点 append 到末尾,而不是手写一遍首点坐标。

1.3 properties 能装什么,不能装什么

properties是个自由字典,理论上任何 JSON 值都能放,但实际工具链的支持度差异很大。我总结下来是这样:字符串、数字、布尔、null这四种走遍天下都不怕;数组和嵌套对象在多数前端渲染库里会被直接忽略或者转成字符串;日期时间一定要用 ISO 8601 字符串,别用时间戳数字,因为一旦跨系统,时间戳的单位(秒还是毫秒)就没人说得清了。

还有个隐蔽的坑是大整数精度。JavaScript 的Number是双精度浮点,安全整数上限是 2 的 53 次方减一。我见过订单号、设备号这种 18 位以上的整数直接作为属性值写进 GeoJSON,前端解析出来最后几位就变了,变成另一个 ID。解决办法很简单:这类标识一律当字符串存,别为了省事用数字。

id成员是要素级别的标识,规范要求是字符串或数字。它和properties里的业务主键不是一回事,但有相当多的工具(尤其是空间数据库导入工具)会把id映射成要素 ID。如果你的数据里id有重复,导入时可能出现覆盖。我的习惯是:id只放全局唯一的、机器生成的短标识,业务主键老老实实放properties里。

2. 把 geojson.io 搬到内网:从源码构建到瓦片自给的完整链路

在线版的 geojson.io 我用过很长时间,拖个文件进去、画两笔、右边实时出 JSON,联调阶段效率极高。但真到项目上就会遇到几个现实问题:数据不能出内网(客户数据和涉密要素根本不允许上传到任何外部服务)、网络环境不稳定导致底图加载卡顿、以及某些配置项需要定制(比如默认坐标系提示、默认字段模板)。所以给团队搭一个私有可用的编辑器,是很划算的一次投入。

这一节我按实际搭过的顺序讲,从环境准备到能跑起来,再到真正好用的那几处定制。

2.1 先想清楚为什么要自建,而不是直接用在线版

自建的核心动机有三个,按重要性排序:数据不出网、底图可控、可定制。

第一条不用多解释,只要涉及矢量边界的业务数据,多数单位的信息安全要求就是禁止上传到任何一个外部在线服务。第二条更实际:在线版默认用的底图服务在国内网络下经常加载缓慢甚至瓦片缺失,而自建之后你可以把它指到内网的瓦片服务上,加载速度取决于你自己的带宽,稳定得多。第三条是长期收益,比如给业务同事用的版本,可以把"导出时必须选择 WGS84"这类提示直接写进界面,减少后面转换阶段的返工。

需要提前说明的是:这套东西解决的是"编辑与预览",它不是一个完整的 GIS 平台。别指望它能做投影变换、批量拓扑检查、符号化出图,它的定位就是轻量的数据查看器加手绘编辑器。想清楚这个边界,投入产出比才算得过来。

2.2 构建环境:版本锁定与内存参数是两处必调的地方

整个项目基于 Node 生态构建,源码从公共代码托管平台取下来。我这边的经验是:Node 版本一定锁死,用 16 或者 18 的 LTS 都行,但别用最新的大版本。理由很具体——这类项目往往依赖一些带原生编译的老包(典型的是老的 Sass 编译器和图像处理库),新 Node 上编译经常直接失败,报一堆node-gyp的错,排查起来极其痛苦。

构建过程大致如下。假设代码已经解压到本地目录:

# 检查版本,务必和项目文档要求一致 node -v npm -v # 安装依赖,建议用国内镜像源加速 npm install --registry=https://registry.npmmirror.com # 启动开发服务 npm start

到npm start这一步,最容易撞上的是内存溢出。这类前端项目构建时 Webpack 会吃掉不少内存,机器内存小一点就会看到JavaScript heap out of memory。解决办法是给 Node 放大堆上限:

# Linux / macOS NODE_OPTIONS=--max-old-space-size=4096 npm start # Windows PowerShell $env:NODE_OPTIONS="--max-old-space-size=4096"; npm start

4096 是 MB,也就是 4G。如果机器有 16G 内存,给到 8192 也完全没问题,构建快一些。

开发模式跑通之后,还需要出一个静态产物用于内网部署:

npm run build

产物一般在dist或build目录下,扔到任意一个静态文件服务(Nginx 就行)上就能访问。这里有个细节:如果是部署在子路径下(比如http://内网地址/geojson/),前端路由和静态资源路径需要单独配置,否则会出现空白页、请求 404 的情况。最省事的办法是给它单独分配一个域名或者端口,别塞在子路径里。

提示:npm install阶段如果卡在某个包上下载不动,先别急着换源,看看是不是这个包本身需要编译。很多时候换个 Node 小版本就通了,比折腾镜像源快得多。

2.3 底图替换:从默认服务切到自建瓦片

这是自建过程中最关键的一步,做完之后体验会有质的提升。

项目的底图配置集中在一个样式文件里,本质是一份描述"瓦片从哪来、图层怎么叠、颜色怎么配"的 JSON。要做的事情就是把里面所有指向外部服务的 URL 换成自己的瓦片服务地址。

如果是完全离线的内网环境,需要自己准备瓦片。常用的做法是用矢量瓦片或者栅格瓦片两种:

  • 栅格瓦片(常见的是 XYZ 金字塔):直接把一张张图片按z/x/y的目录结构切好,交给 Nginx 提供静态服务,最省心。缺点是体积大、不能换配色。
  • 矢量瓦片:体积小、样式灵活、支持任意缩放,但需要一套瓦片服务来生成和分发,搭建成本略高。

我一般建议中小项目先用栅格瓦片,够用且不容易出问题。切图的话,用现成的开源工具按区域裁剪就行,注意切图时指定的缩放级别范围别贪多,z=0到z=14在全国范围下已经是相当可观的磁盘占用了。

样式文件里的关键位置长这样(简化示意):

{ "version": 8, "sources": { "base": { "type": "raster", "tiles": ["http://内网瓦片服务/tiles/{z}/{x}/{y}.png"], "tileSize": 256 } }, "layers": [ { "id": "base", "type": "raster", "source": "base" } ] }

改完记得清浏览器缓存,样式文件通常会被强缓存,改完不生效的情况十有八九是缓存问题。

另外要留意跨域。如果瓦片服务和编辑器不在同一个域名端口下,瓦片服务必须带Access-Control-Allow-Origin响应头,否则控制台会刷一片红色错误,地图白屏但代码没报错——这种"看起来哪儿都对就是不出图"的情况,九成是跨域。

2.4 中文字体缺失和保存接口这两处定制

底图换好之后,地图能出来了,但往往还是一堆小毛病。

第一个是中文标注不显示或者显示成方块。原因是矢量瓦片里的文字用字体栈渲染,自建服务如果没有提供对应的中文字体包(常见的格式是切片后的字形文件),中文就画不出来。解决思路是往瓦片服务里补中文字体切片,或者干脆在样式里把中文字段的字体名指到一个已存在的字体配置上。这个问题在栅格瓦片方案下不存在,因为文字已经在切图时烤进图片里了。

第二个是保存和加载接口。在线版会提供"打开文件"、"保存到某处"这类交互,背后依赖外部服务。内网部署时这些接口要么不可用、要么会指向外面,最好直接在配置里关掉,只保留本地文件的上传下载。这样界面干净,也不会有人点了一个按钮之后卡在那里等超时。

我实际踩过的一个坑是:文件上传的默认大小限制。业务同事传了一批几十兆的 GeoJSON 进来,浏览器直接卡死,页面像没响应一样。后来在服务配置里把请求体上限调到 50MB 以上才正常。但更好的方案是提前告诉他"超过 20MB 的数据别用编辑器打开",因为就算传上去了,渲染也撑不住。

3. 坐标系错位才是真凶:ArcGIS 与 GeoJSON 之间的基准面账

数据在 ArcGIS 里看着好好的,导出成 GeoJSON 之后位置就偏了,这是我在群里被问得最多的一类问题。绝大多数情况下,问题不在转换工具,而在坐标系这件事本身没对齐。这一节把这个账算清楚。

3.1 WGS84 和 CGCS2000 之间的米级差距

GeoJSON 规范咬死了一件事:坐标必须是 WGS84 下的经纬度,单位是度,不做任何投影。也就是说,一个合法的 GeoJSON 文件里,[116.4, 39.9]这两个数就默认是 WGS84 的大地坐标,没有商量的余地。

而国内业务数据常用的坐标系里,CGCS2000(EPSG:4490)和 WGS84 在定义上非常接近,厘米级到米级的差异,实际项目中多数场景可以直接当作一致处理。但更早的北京54、西安80 就完全不一样了,它们和 WGS84 之间存在明确的基准面差异,需要一个地区性的转换参数(俗称七参数或者四参数)才能准确换算。

所以转换前必须先回答一个问题:源数据到底在哪个坐标系下?

如果源数据本身没有带坐标系定义(比如一个手工拼的 Shapefile,.prj文件丢了),那就只能靠"看位置对不对"来判断,非常被动。我的做法是找几个已知的、可以在地图上精确定位的地物(比如某个路口、某栋建筑的角点),把转换后的位置和实际位置比一比,误差在几米内说明基准面接近,误差几百米说明用错了基准面,误差几万米说明多半是投影坐标被当成经纬度了。

3.2 crs 字段:一个已经被删掉、但还活在数据里的历史包袱

早年的 GeoJSON 规范允许在顶层写一个crs成员,用来说明坐标参考系,比如:

{ "type": "FeatureCollection", "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3857" } }, "features": [ ... ] }

这套写法在新版规范里被移除了,规范明确要求不使用crs,一律视为 WGS84。问题在于,网上流传着大量老数据还带着这个字段,而且里面写的确实是投影坐标(比如 3857 的米制坐标)。

这时候会出现最坑的局面:读数据的工具忽略了crs字段,把米制数字当成度来处理。3857 的坐标动辄几百万,直接当地理坐标用,位置会飞到地图外面去,或者因为超出范围被判定非法。更隐蔽的情况是,某些解析器做了"聪明"的兜底,把超范围的值强行夹到合法区间,数据看起来有东西,位置全错。

所以拿到带crs的老数据,第一反应应该是拆开看一眼coordinates的数值量级:绝对值超过 180 的,基本可以确定不是经纬度。

3.3 3857 与 4326 混用导致的错位放大

Web 地图常用的墨卡托投影(EPSG:3857)和地理坐标(EPSG:4326)混用,是最常见也最容易迷惑人的一类错位。

它的迷惑之处在于:在赤道附近,两者的数值差异不算特别大,某些精度要求不高的场景下看起来"差不多";但越往高纬度走,纬度方向被拉伸得越厉害。同一个点,在 3857 下的 y 值可能是一千多万,而 4326 下的纬度只有几十。如果程序把前者当后者用,位置会错得离谱。

反向的情况同样存在:把经纬度直接塞进需要 3857 的接口,地图上会呈现出"所有要素挤在赤道附近一小块"的效果。这是因为经纬度的数值范围(经度 ±180、纬度 ±90)相对于 3857 的米制范围(全球约 ±20037508)太小了,视觉上就缩成了一团。

提示:快速判别一个坐标到底是哪种,看 y 值绝对值。小于 90 的大概率是纬度;在几千到两千万之间的大概率是 3857 的米制 y 值。

4. ArcGIS 侧的进出两条路:工具参数与字段损耗实录

坐标系搞明白之后,剩下的是操作层面的问题。ArcGIS 桌面端导出 GeoJSON 的路径不止一条,不同路径的参数差异会直接影响结果质量。

4.1 导出 GeoJSON 的参数组合

桌面端(ArcGIS Pro)里最直接的是地理处理工具箱里的要素转 JSON 工具,它有一个开关参数控制输出格式是 Esri JSON 还是 GeoJSON。要点是:

  1. 先投影,再导出。不要指望导出工具帮你转坐标系。虽然它有一个参数可以指定输出坐标,但行为在不同版本上不一致,稳妥的做法是先用投影工具把要素类转成 WGS84 地理坐标系,再走导出。
  2. 勾选"输出为 GeoJSON"。不勾的话,出来的是 Esri JSON,结构完全不同(几何部分用的是paths、rings这种字段名,坐标顺序也有差别),套 GeoJSON 解析器会直接失败。
  3. 控制小数位。默认精度可能不够,涉及小面积地块的时候,7 位小数(约 1 厘米)是比较安全的档位。

服务端的方式是走 REST 接口的查询参数,把输出格式指定为 geojson,同时显式带上输出坐标系为 4326:

# 示意,参数名以实际服务文档为准 .../query?where=1%3D1&outFields=*&f=geojson&outSR=4326

这个方式适合做增量同步,缺点是单次返回有条数上限,大批量需要分页拉取。

4.2 字段类型在转换过程中的塌陷

这是我最想强调的一节,因为它的后果是数据看起来完整的,但业务字段已经悄悄变形了。

原始字段类型转换后表现影响
日期 / 时间变成字符串,格式依版本而定下游按时间排序或比较会出错
双精度浮点可能被截断到 6 位小数精度要求高的坐标或量测值受损
长整型(18 位)变成数字后可能丢精度前端 JS 解析出错位
字段名含空格或中文保留原样,但部分工具不支持属性查询写不出来
二进制 / 大对象字段直接丢弃或转成空附件信息丢失
全局 ID / GUID转成字符串一般无影响,但注意大小写

我的处理原则是:导出前先做一次字段瘦身。只保留业务真正需要的字段,把日期统一预格式化成 ISO 字符串,把长 ID 预先转成文本类型,把字段名改成纯英文加下划线。这步花十分钟,能省掉后面几小时的排查。

另外一个小细节:ArcGIS 的要素类通常有一个对象 ID 字段,导出时它可能会被映射成 GeoJSON 的id成员,也可能只留在properties里,取决于版本和参数。如果你的业务依赖这个 ID 做关联,务必在导出后抽查几个要素确认它的落点。

4.3 从 GeoJSON 回到要素类

反向转换的路径是 JSON 转要素工具。它接受 GeoJSON 文件,但有几个限制需要注意。

第一,它只认.json后缀,.geojson后缀在某些版本上会被识别为未知格式。最省事的做法是先改后缀再导入。

第二,它不处理crs字段,一律按 WGS84 解读。如果源文件里带着投影坐标,导进去位置就错了,而且导入过程不会报错。

第三,几何类型必须纯净。同一个 GeoJSON 里混着 Point 和 Polygon,工具会尝试建成点要素类,面要素直接丢失。要么按类型拆文件,要么先统一成单一类型。

如果不想依赖桌面端,用开源的命令行转换工具也是很好的选择,脚本化程度更高:

# 转成 GeoJSON,强制 WGS84,启用 RFC7946 模式(自动修正环方向、去掉 crs) ogr2ogr -f GeoJSON output.json input.shp \ -t_srs EPSG:4326 \ -lco RFC7946=YES \ -lco COORDINATE_PRECISION=7

RFC7946=YES这个参数特别值得记住,它会自动处理环方向、坐标系声明这些容易出错的地方,等于把规范要求的约束在导出阶段就落实了。

5. 排错现场:五类高频报错的定位链路

排错这件事,光知道答案没用,得知道怎么一步步走到答案。这一节我把遇到过的几类问题按"现象→可能原因→验证方法→修复"的顺序写下来。

5.1 解析层:文件根本读不进去

现象是打开文件直接失败,或者控制台提示 JSON 解析错误。

第一查 BOM。Windows 下用某些编辑器保存的 UTF-8 文件会带一个不可见的字节序标记,JSON.parse遇到它会直接抛错。验证方法是用十六进制查看文件头,如果开头是EF BB BF就是它。修复很简单,用编辑器另存为"UTF-8 无 BOM",或者命令行处理一下:

# 去掉 BOM sed -i '1s/^\xEF\xBB\xBF//' data.geojson

第二查尾逗号。JSON 不允许数组或对象最后一个元素后面还有逗号,这是从 JavaScript 字面量习惯带过来的错误,手写文件特别容易出现。

第三查NaN和Infinity。这两个在 JavaScript 里是合法的值,在 JSON 里不是。数据里如果有空几何或者计算出错的位置,可能被写成NaN,浏览器读的时候报错位置往往指向文件末尾,非常误导。

5.2 几何层:文件能读,但地图上不对

现象是文件加载成功,要素数量也对,但位置或者形状不对。

如果位置整体偏移,回到第 3 节查坐标系。如果位置完全跑到地图外,查坐标顺序。如果形状异常(比如面变成一个奇怪的蝴蝶结),查环方向或者自相交。

自相交是个不太好发现的问题。一个外环自己跟自己交叉了,某些渲染器会把它画成一块奇怪的区域,某些空间库会直接拒绝。用 Python 快速查一遍:

import json from shapely.geometry import shape with open("data.geojson", encoding="utf-8") as f: gj = json.load(f) bad = [] for feat in gj["features"]: geom = feat.get("geometry") if not geom: continue try: g = shape(geom) if not g.is_valid: bad.append((feat.get("id"), g.geom_type, "invalid")) except Exception as e: bad.append((feat.get("id"), "parse_error", str(e))) print(f"共 {len(gj['features'])} 个要素,问题要素 {len(bad)} 个") for item in bad[:20]: print(item)

这段脚本能一次把空几何、几何类型错误、自相交、环未闭合等问题全扫出来,配合make_valid或者buffer(0)还能顺手修复。

5.3 渲染层:数据没错,但画不出来

这类问题最消耗时间,因为代码看起来没有任何问题。

第一个常见原因是图层顺序。面图层盖在点图层上面,点就看不见了;后加的图层盖住先加的,也可能造成"要素消失"的错觉。排查方法很土但有效:把某个图层的颜色改成高对比度的红色,看它到底在不在。

第二个是样式表达式。属性值是字符串"1"和数字1在多数样式表达式里不等价,用数字比较就永远匹配不上,图层的可见性规则不生效。我遇到过一次,字段里存的是"true"字符串,样式按布尔判断,结果整层要素都不显示。

第三个是坐标精度不足导致的细缝。相邻的两个面如果坐标被截断到 2 位小数,接缝处会出现肉眼可见的缝隙,放大后像一条白线。这不是渲染 bug,是数据精度问题,精度提到 6 到 7 位就消失了。

5.4 大文件:几十万要素直接卡死

浏览器渲染 GeoJSON 是很吃资源的。我的经验阈值是:两万要素以内基本流畅,五万开始明显变慢,十万以上大概率卡死,具体取决于几何复杂度和机器性能。

解决思路是换格式,而不是硬扛。几条路线:

  • 简化几何:用道格拉斯-普克算法降低顶点数量,面要素在缩放级别不高的时候完全没必要保留那么多点。
  • 转矢量瓦片:把 GeoJSON 切成mbtiles或者pmtiles,前端按需加载,几十万要素也能流畅。
  • 按范围分片:把数据按网格切成多个小文件,根据视野动态请求。

前两种是更根本的解法。第三种适合数据本身有天然分区(比如按行政区域),实现成本低但要处理边界处的重复。

5.5 高频问题速查表

现象最可能原因快速验证
打不开文件BOM / 尾逗号 / NaN十六进制看文件头
位置整体偏几百米基准面不一致比对已知地物
位置完全在地图外坐标顺序反了或投影坐标当经纬度看坐标数值量级
面形状怪异环方向错误 / 自相交用脚本校验is_valid
面积计算偏差大内环方向反了检查每个环的走向
属性查不到类型不匹配 / 字段名含特殊字符打印属性字典看实际类型
相邻面有白缝坐标精度被截断提高到 7 位小数
中文乱码编码不是 UTF-8检查文件编码声明
页面卡死要素数量过多统计features长度

6. 把校验做进流水线:从"每次手工查"到"自动挡"

前面讲的排查方法,单次用是有效的,但如果一个项目要反复导出、反复对接,每次都手工走一遍流程是不现实的。我后来的做法是把校验和规范化做成一个固定的小脚本,任何数据先进这道关卡再往下走。

6.1 一个够用的入库前校验脚本

这个脚本做的事情很朴素:读文件、检查顶层结构、遍历要素、统计几何类型、校验坐标范围和环闭合、输出报告。不依赖重型 GIS 库也能跑,装了 Shapely 就能多做一层几何有效性检查。

import json, sys from collections import Counter def check(path, lng_range=(-180, 180), lat_range=(-90, 90)): with open(path, encoding="utf-8-sig") as f: gj = json.load(f) assert gj.get("type") in ("FeatureCollection", "Feature", "GeometryCollection"), "顶层类型非法" feats = gj["features"] if gj["type"] == "FeatureCollection" else [gj] types = Counter() problems = [] for i, feat in enumerate(feats): geom = feat.get("geometry") if feat.get("type") == "Feature" else feat if geom is None: problems.append((i, "空几何")) continue gtype = geom.get("type") types[gtype] += 1 if gtype == "Polygon": for ring in geom["coordinates"]: if ring[0] != ring[-1]: problems.append((i, "环未闭合")) break print("几何类型分布:", dict(types)) print("问题数量:", len(problems)) for p in problems[:10]: print("样例:", p) return len(problems) == 0 if __name__ == "__main__": ok = check(sys.argv[1]) sys.exit(0 if ok else 1)

脚本返回非零退出码,直接就能挂到构建流程里,数据不合格就卡住不让进。这个做法看着简单,实际省下的返工时间非常可观——尤其是有多个数据供应方的时候,标准统一了,扯皮就少了。

6.2 格式选型:什么时候该放弃 GeoJSON

最后说一个心态问题。GeoJSON 好用,但它不是万能的,判断标准其实很清楚:

单文件在几十兆以内、要素在几万以内、以查看和轻量编辑为主、需要跨系统传递——用 GeoJSON,没有问题。

要素量到几十万、需要复杂空间运算、需要保存拓扑关系或圆弧、需要多用户并发编辑——用空间数据库或者瓦片格式,GeoJSON 只作为导出和交换的中间态。

我见过有团队硬拿 GeoJSON 当主存储,一个文件几百兆,每次读写都全量加载,最后不得不整体重构。这个弯路完全可以避开,代价只是前期多想十分钟。

另外还有个小建议:不管走哪条路,源数据永远保留一份原始坐标系下的备份。转换是有损的,尤其是精度和字段类型,转过去再转回来往往回不到原样。留一份底,出问题的时候才有退路。这是我踩过两次之后才养成的习惯,看着笨,但真的管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询