之前在做地图可视化项目时,数据同学导出一份.geojson文件,前端页面加载后地图上什么都没有,浏览器控制台也没有任何报错。排查到最后发现,coordinates数组里混入了好几个空数组,导致部分要素的几何解析直接失败,地图层却“静默”吞掉了这处异常。
这类问题非常典型。GeoJSON 本身是纯数据格式,工具链只负责解析,不负责“判断数据对不对”。但数据一旦有问题,渲染端往往表现得很隐晦。直到我看到一个名为 GeoLint 的开源项目,思路一下就清晰了:它把 ESLint 的设计理念搬到了 GeoJSON 数据校验上,让静态检查、规则配置、问题报告标准化。这篇文章就来系统拆解 GeoLint 到底是什么、它的规则体系如何设计,以及如何把它接入实际项目。
无论你是前端开发者、GIS 工程师,还是日常处理地理数据的后端同学,这篇文章都能帮上忙。读完你不仅能跑通 GeoLint,还能理解规则背后“为什么要这么设计”,遇到复杂数据质量问题也更容易定位根因。
1. GeoLint 是什么:从 ESLint 到 GeoJSON 校验
1.1 一个典型的 GeoJSON 数据事故
很多人第一次接触 GeoJSON 是因为前端地图组件。比如使用 Leaflet、Mapbox GL JS 或 ECharts 的地图功能,通常需要加载一个 geojson 文件来定义区域轮廓或点标记。文件加载进来了,地图却可能白屏、少一块区域,或者弹出一堆莫名其妙的警告。
从经验来看,这类问题大多不是地图库的 bug,而是数据本身“半残废”:
type字段拼写错误,例如把Point写成Ponit;Feature对象缺少geometry或properties必填字段;- 二维坐标与三维坐标混用,
[116.39, 39.9]和[116.39, 39.9, 30]交替出现; - 多边形坐标环未闭合,首尾点不一致;
- 经纬度越界,比如经度写成 139.9,实际上已经超出 180。
这些错误不会导致 JSON 解析失败,因为它们在语法上是合法的 JSON,所以很难在加载阶段被发现。只有当地图渲染到一半,或者某些空间计算突然异常时,问题才露出马脚。
1.2 引入 linter 的思路
JavaScript 生态里有个成熟方案:ESLint。它不是用来检查“JavaScript 能不能运行”,而是检查“代码是否遵循团队约定的规范”。既然代码可以有 linter,那数据为什么不行?
GeoLint 正是带着这种思路诞生的。它面向 GeoJSON 文件,不是简单验证“是不是合法 JSON”,而是提供了一套类似 ESLint 的机制:
- 内置一批可开关的规则;
- 通过配置文件控制规则的开启与参数;
- 统一输出“文件、行列号、错误级别、错误信息”的报告;
- 支持自定义规则,方便团队沉淀自己的数据规范。
类比一下:如果JSON.parse是“语法检查”,那么 GeoLint 是“代码规范检查”。前者保证数据能被解析,后者保证数据符合业务要求、坐标系正确、结构完整、命名统一。
1.3 GeoLint、ESLint、linter 三者关系
很多同学对linter这个词不熟,这里做一个简单区分:
| 概念 | 说明 |
|---|---|
| linter | 泛指静态检查工具,可以检查代码、配置、数据文件 |
| ESLint | 专注于 JavaScript/TypeScript 的 linter,规则丰富、生态完善 |
| GeoLint | 借鉴 ESLint 设计理念,专门面向 GeoJSON 数据的 linter |
GeoLint 不是要替代 ESLint,而是把 ESLint 那一套成熟的“规则-配置-报告”模型迁移到地理信息数据领域。它解决的核心问题是:在数据进入渲染层或计算引擎之前,把结构问题和业务约束问题提前暴露出来。
2. GeoJSON 格式基础与质量风险
2.1 GeoJSON 的核心结构
GeoJSON 是基于 JSON 的地理数据编码格式,核心是GeoJSON Object和Geometry Object。常见的几种对象类型:
- 单个几何对象:
Point、LineString、Polygon; - 多几何对象:
MultiPoint、MultiLineString、MultiPolygon; - 要素对象:
Feature,由geometry和properties组成; - 要素集合:
FeatureCollection,是实际项目最常用的外层容器。
下面是一个标准Feature示例:
{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [116.39, 39.9] }, "properties": { "name": "示例点", "id": 1001 } }FeatureCollection则是把多个要素包起来:
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [ [116.39, 39.9], [121.47, 31.23] ] }, "properties": { "name": "北京-上海" } } ] }结构本身不复杂,但正因为结构灵活,不同来源、不同工具生成的数据才会千差万别,也容易混入各种“看起来没问题”的脏数据。
2.2 高频数据错误清单
结合实际项目经验,我整理了一份高频问题清单。这些场景在 geojson 数据格式的日常处理中非常常见:
| 错误类型 | 典型表现 | 后果 |
|---|---|---|
| 类型拼写错误 | "type": "Ponit" | 解析器无法识别几何类型 |
| 必需字段缺失 | Feature 缺少properties | 有些工具直接报错 |
| 坐标数组为空 | coordinates: [] | 几何无效,渲染空白 |
| 坐标维度混用 | 二维、三维坐标共存 | 空间计算异常 |
| 经纬度越界 | 经度 > 180 或纬度 > 90 | 点位漂移,显示错误位置 |
| 多边形环未闭合 | 首尾坐标点不一致 | 面积计算错误、渲染变形 |
| 要素没有属性 | properties: {} | 无法关联业务数据 |
其中坐标维度混用是大坑。一个LineString的前两个点是[x, y],第三个点却是[x, y, z],很多解析库默认按三维处理,结果后端空间索引直接混乱。
2.3 为什么人工审查不可靠
你可能会说,这些错误“肉眼都能看出来”。但实际项目里的 geojson 文件动辄成千上万行,人工检查根本不现实。前端拿到的 geojson 文件通常来自第三方数据平台或 GIS 工具导出,经过多次转换后,很难保证每一步都符合规范。
此外,很多需求是“结构合法但业务不合法”。比如一个区域轮廓数据,要求必须有adcode和name字段,但某个文件漏掉了adcode。这种问题不是通用工具能发现的,必须依赖可配置的规则引擎。这正是 GeoLint 这类 linter 最有价值的地方。
3. 像 ESLint 一样设计 GeoJSON 的 linter
3.1 ESLint 的核心设计理念
要理解 GeoLint 的架构,先看 ESLint 做了哪三件事:
- 解析源代码为 AST,拿到可遍历的语法树;
- 遍历 AST 并执行规则,每条规则关注特定的节点类型;
- 汇总报告,统一格式输出问题列表。
这里最值得借鉴的是“规则只关注自己关心的节点”。一条规则不需要理解整个文件,只需要在碰到某个节点时判断是否违规。这种设计让规则可以独立开发、独立测试、独立配置。
GeoLint 面对 GeoJSON 时也采取了类似思路:把 GeoJSON 文件解析成一棵对象树,规则可以选择监听Feature、Geometry、Point、FeatureCollection等节点类型。碰到一个要素,检查属性是否完整;碰到一个坐标数组,检查范围是否合法。各司其职,互不干扰。
3.2 GeoLint 的规则模型
一个典型的 GeoLint 规则可以拆成这样:
module.exports = { meta: { description: "检查坐标数组不能为空" }, create(context) { return { Point(node) { if (!node.coordinates || node.coordinates.length === 0) { context.report({ node, message: "Point 的 coordinates 不能为空" }); } } }; } };这里出现了规则的两个关键部分:
meta:规则的元信息,包括描述、是否可修复、文档地址;create:返回一个监听器对象,定义该规则关心哪些节点。
当一个 GeoJSON 对象被解析后,GeoLint 会遍历整棵对象树。遇到Point节点就调用上面对应的Point(node)函数,遇到FeatureCollection节点也会调用对应的监听函数。
这种设计最大的好处是,团队可以根据自己的业务追加规则。比如有的项目要求所有Feature都必须包含properties.name,那就可以写一条required-properties规则,而不是去改通用校验库。
3.3 配置文件的组织方式
ESLint 使用.eslintrc来管理规则,GeoLint 也采用了类似的配置形态。下面是一份示例.geolintrc.json:
{ "rules": { "geolint/no-empty-coordinates": "error", "geolint/geometry-type": [ "error", { "allowed": ["Point", "LineString", "Polygon"] } ], "geolint/required-properties": [ "error", { "required": ["name", "adcode"] } ] } }规则值为"off"、"warn"、"error"三档,对应 ESLint 的习惯:
off表示关闭;warn只警告,不影响命令退出码;error会作为错误输出,CI 中可以让流水线失败。
带参数时写成数组形式,第二项是规则参数对象。比如geometry-type规则允许你指定该文件里允许出现的几何类型,超出范围就报错。
4. GeoLint 实战:从安装到跑通第一个规则
4.1 环境准备
GeoLint 通常是基于 Node.js 的命令行工具,所以本地环境需要准备:
- Node.js 16 及以上版本;
- npm、yarn 或 pnpm 任意一种包管理器。
安装方式很简单,一般是通过 npm 全局或项目内安装:
npm install -g geolint如果是项目内安装,更推荐通过npx直接执行,避免污染全局环境:
npx geolint --init--init命令会生成一份默认的.geolintrc.json配置文件,方便从零开始。
这里需要说明:不同项目的 CLI 参数可能略有差异,具体以 GeoLint 项目 README 为准。本文以“ESLint 风格”的命令设计为例,重点展示使用思路。
4.2 准备一份待校验的 GeoJSON 文件
先创建一份带有典型问题的数据文件,路径为data/sample.geojson:
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [] }, "properties": { "name": "空坐标点" } }, { "type": "Feature", "geometry": { "type": "Ponit", "coordinates": [116.39, 39.9] }, "properties": { "name": "拼写错误点" } }, { "type": "Feature", "properties": { "name": "缺少 geometry" } } ] }这份文件里有三个问题:
- 第一个点的
coordinates是空数组; - 第二个点的
type拼写成了Ponit; - 第三个点缺少了
geometry字段。
这三个问题都是 GeoJSON 数据中非常典型的“静默故障”,不会导致 JSON 解析失败,但会影响地图渲染和空间计算。
4.3 编写配置文件
在项目根目录创建.geolintrc.json:
{ "rules": { "geolint/no-empty-coordinates": "error", "geolint/valid-geometry-type": "error", "geolint/feature-required-fields": "error" } }三条规则分别对应刚才的三个问题:
no-empty-coordinates:检查coordinates数组是否为空;valid-geometry-type:检查geometry.type是否为规范允许的类型;feature-required-fields:检查Feature是否包含geometry和properties。
4.4 命令行执行校验
在项目根目录执行:
npx geolint "data/**/*.geojson"预期输出会类似于:
data/sample.geojson 4:12 error coordinates must not be empty geolint/no-empty-coordinates 9:12 error invalid geometry type "Ponit" geolint/valid-geometry-type 16:6 error Feature must have "geometry" field geolint/feature-required-fields ✖ 3 problems (3 errors, 0 warnings)这种报告格式和 ESLint 非常接近:先显示文件名,再显示行列号和错误级别,最后是规则名。开发者在终端里扫一眼就能知道数据哪里出了问题,而不需要自己打开 JSON 一行一行核对。
4.5 自动修复与手动修复
部分规则支持自动修复,比如坐标精度格式化、统一Feature字段顺序等。执行:
npx geolint "data/**/*.geojson" --fix--fix会自动处理可修复的问题。但要注意,像“缺少 geometry”这种问题无法自动修复,因为工具不知道你原本想表达什么几何类型。这类问题必须回到数据生产端去补齐。
修复后的文件建议重新执行一次校验,确认问题清零:
npx geolint "data/**/*.geojson"5. 编写自定义规则:一个完整的例子
5.1 什么时候需要自定义规则
内置规则往往只解决通用问题,比如结构是否合法、坐标是否为空、类型是否拼写正确。但业务项目里的很多约束是“独有”的。举几个例子:
- 所有要素必须有
adcode字段,且必须是六位数字; - 点要素不能落在某些区域之外;
- 线要素的坐标数量不能少于 2 个;
- 多边形必须闭合;
- 属性字段命名必须统一为驼峰式。
这些都能用自定义规则实现。
5.2 规则接口设计
在 GeoLint 的设计中,自定义规则通常导出一个对象,包含meta和create。下面这条规则用来检查 Point 坐标是否越界:
// 文件路径:rules/no-invalid-coordinate-range.js module.exports = { meta: { description: "经纬度坐标必须在合法范围内", docs: { url: "https://example.com/rules/no-invalid-coordinate-range.md" } }, create(context) { return { Point(node) { if (!node.coordinates || node.coordinates.length < 2) { return; } const [lng, lat] = node.coordinates; if (lng < -180 || lng > 180 || lat < -90 || lat > 90) { context.report({ node, message: `坐标越界: [${lng}, ${lat}]` }); } } }; } };这里的Point(node)表示:遍历 GeoJSON 时,每当遇到一个type为Point的对象,就执行这个回调。
5.3 检查要素属性完整性
再来看一个更贴近业务的自定义规则:要求所有Feature对象必须包含properties.name和properties.adcode。
// 文件路径:rules/required-business-properties.js module.exports = { meta: { description: "要素必须包含业务属性 name 和 adcode" }, create(context) { return { Feature(node) { const properties = node.properties || {}; const requiredFields = ["name", "adcode"]; requiredFields.forEach((field) => { if (properties[field] === undefined) { context.report({ node, message: `Feature 缺少属性字段: ${field}` }); } }); } }; } };这种规则解决的是一个很现实的问题:数据文件下载下来后,结构是合法的,但业务字段缺失,导致后面做数据关联时一堆 null。提前在数据入库前拦截,成本最低。
5.4 把自定义规则加载进配置
自定义规则写好后,需要在配置文件中注册。假设规则文件放在项目rules/目录下,package.json中声明了geoJSON字段来指定规则目录,配置可以这样写:
{ "rules": { "geolint/no-invalid-coordinate-range": "error", "geolint/required-business-properties": "error" } }GeoLint 会自动扫描本地或全局规则目录,把no-invalid-coordinate-range这样的规则名映射到rules/no-invalid-coordinate-range.js文件。
属于团队的规范代码就可以沉淀下来。新成员加入时,只需要安装同一套配置文件,就能在本地获得一致的数据检查结果。
6. 在项目工程中集成 GeoLint
6.1 在 Node.js 数据脚本中调用
除了命令行,GeoLint 也可能作为 Node.js 模块被其他代码调用。例如在数据处理脚本里,先校验再入库:
// 文件路径:scripts/validate-data.js const geolint = require("geolint"); const geojsonData = require("../data/china.geojson"); const report = geolint.lint(geojsonData, { rules: { "geolint/no-empty-coordinates": "error", "geolint/required-business-properties": "error" } }); if (report.errorCount > 0) { console.error("数据校验未通过,终止入库"); console.error(report.output); process.exit(1); } console.log("数据校验通过");这种方式适合把 GeoLint 嵌入到“文件导入-清洗-入库”的完整流程中。数据进入业务系统前,先过一道 linter,不合格就直接拒绝写入。
6.2 接入 CI/CD 流水线
地理数据经常是团队协作产出,为了保证主分支上的 geojson 文件始终可用,可以在 CI 里加一道检查。以 GitHub Actions 为例,增加一个校验任务:
name: geojson-lint on: push: paths: - "data/**/*.geojson" jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm install -g geolint - run: geolint "data/**/*.geojson"这里利用了paths过滤,只有当data目录下的 geojson 文件变更时才触发校验。任何一位同事提交了带错误的数据文件,CI 就能立刻给出反馈,不会等到前端页面白屏才发现。
6.3 与 geojsonhint、JSON Schema 的配合
在 GeoJSON 校验这个领域,GeoLint 并不是唯一工具。已经有一些成熟方案,比如 Mapbox 的geojsonhint和通用 JSON Schema 校验器。它们和 GeoLint 并不冲突,而是互补:
geojsonhint更侧重于“是否符合 GeoJSON 规范”,偏向语法结构层面;- JSON Schema 可以校验字段类型、必填字段,但难以表达“坐标范围”“多边形闭合”这类空间规则;
- GeoLint 更接近 ESLint 的定位,支持规则开关和自定义业务校验。
建议的工程分层是:
- 先
JSON.parse保证可读; - 再用 geojsonhint 或 JSON Schema 做结构校验;
- 最后用 GeoLint 做业务规则校验。
这样三层校验各有侧重,既不会重复,也不会留下死角。
7. 常见问题与排查清单
7.1 高频问题表格
用 GeoLint 校验 geojson 文件时,遇到的报错大多可以归为下面几类:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 规则不生效 | 配置文件没有放在项目根目录 | 检查.geolintrc.json路径和文件名 |
| 自定义规则无法加载 | 规则文件没有导出对象,或目录配置错误 | 确认module.exports语法和规则目录 |
| coordinates 报错但 JSON 合法 | 坐标为空、维度不足、类型不是数字 | 回到数据生产端修复原始数据 |
| 多边形边界异常 | 环未闭合,或内环外环方向错误 | 使用空间库做闭合检查和拓扑修复 |
| 报告定位不准 | 数据是压缩成一行的大文件 | 使用编辑器或处理脚本格式化 geojson |
| CI 中使用报错 | 全局安装的 geolint 在 CI 环境不可用 | 改为 npm 依赖安装并配置 scripts |
7.2 从报错信息快速定位数据问题
当 GeoLint 输出data/sample.geojson报告时,行号和列号直接告诉你问题在哪个位置。这里建议遵循一个排查步骤:
- 先看规则名,确认是哪一类问题;
- 再打开对应文件的行号,找到具体对象;
- 在完整对象上下文里观察,而不仅仅是看一个坐标数组;
- 能自动修复的先跑
--fix; - 不能自动修复的,回到生成 GeoJSON 的源头修改。
如果一份 geojson 文件报错几十个,不要一个个手工改,优先找出数据生成脚本的 bug。数据源头的问题解决了,导出文件自然就干净了。
7.3 如何打开和预览 geojson 文件
前端开发中拿到 geojson 文件后,除了用编辑器直接看 JSON,也可以借助一些可视化工具快速观察数据是否符合预期:
- QGIS:最常用的开源 GIS 工具,可以直接加载 GeoJSON,查看要素和属性表;
- geojson.io:在线站点,拖入文件即可地图预览;
- VS Code:安装支持 GeoJSON 预览的扩展,能在编辑器里直接渲染;
- Leaflet / Mapbox 的简单页面:临时写个 HTML 页面加载文件。
不过要注意,这些工具适合“看大概”,不适合做严格的数据质量检查。批量、自动化、可配置的检查仍然要交给 GeoLint 这类 linter 去做。
8. 最佳实践与工程建议
8.1 校验策略:尽早、自动、可解释
数据质量问题的修复成本会随着链路向后传递成倍增长。文件在生产端错了,改原始数据最便宜;等入库之后再改,往往涉及清洗任务重跑;等前端上线后再发现,影响面就大了。
所以 GeoLint 的接入点应该尽量靠前。数据导出、文件上传、提交代码这三个环节各加一道校验,就能拦截绝大多数问题。
8.2 规则推荐基线
并不是规则开得越多越好。规则太多,会让团队陷入无休止的改数据,反而影响效率。这里推荐一个起步基线:
| 规则方向 | 推荐等级 |
|---|---|
| 合法 JSON 可解析 | 必须 |
| type 字段正确 | 必须 error |
| geometry 和 properties 存在 | 必须 error |
| coordinates 非空 | 必须 error |
| 坐标范围合法 | 建议 error |
| 多边形闭合 | 建议 error |
| 业务属性完整 | 业务自定义 |
先跑通前几条,再逐步增加业务规则。每一次新增规则,意味着团队对数据质量多一份共识,不要一上来就全量开启。
8.3 把规则文档化并纳入代码评审
linter 的价值不止于“拦截错误”,更在于“沉淀共识”。建议每个自定义规则都写好文档,说明:
- 为什么要设这条规则;
- 什么情况下会误报;
- 遇到合法但特殊的数据该怎么豁免。
在代码评审时,如果一条新规则导致大量现有数据文件报警,不要硬去改数据,先讨论规则本身是否合理。linter 是工具,不是目的;数据服务于业务,不应该反过来被规则绑架。
8.4 注意性能和边界场景
如果你在数据流水线中处理上百 MB 的 geojson 文件,建议在 GeoLint 之前先做格式精简和压缩。linter 本身是静态分析,不应该承担数据清洗的重量。
另外要注意边界场景:美国某地的经度范围、跨越 180 度经线的多边形、南极地区的坐标,都容易触犯简单的范围规则。规则实现时最好加上“允许特定数据源跳过检查”的机制,避免误伤合法数据。
9. 小结
GeoLint 的定位很有意思,它把前端工程化里已经成熟的 linter 理念移植到了地理数据领域。结构合法不代表数据可用,数据可用不代表符合业务规范。通过一套可配置、可扩展、可集成的规则体系,我们完全可以把 geojson 文件的质量检查自动化。
这篇文章从 GeoJSON 的常见数据问题出发,介绍了 GeoLint 的规则模型、配置方式、CLI 使用、自定义规则编写,以及 CI/CD 集成方案。核心要记住的几点:
- GeoLint 解决的不是“JSON 能不能解析”,而是“数据质量和业务约束是否达标”;
- 规则可以内置,也可以团队自定义,关键是沉淀自己的数据规范;
- 接入点越早越好,校验结果要可读、可解释;
- 实际项目中,建议把 GeoLint 与 geojsonhint、JSON Schema 组合使用,各管一层。
下一步可以尝试在自己的地图项目里引入一份配置文件,拿真实的 geojson 数据跑一遍校验,看看能揪出多少你之前没注意到的问题。数据质量问题越早暴露,后续的地图渲染、空间计算和数据可视化就越省心。