如何将 GeoJSON 转换为 JSON(剥离几何数据,保留属性)
GeoJSON 到底包含什么(以及为什么它通常超出了你的需求)
GeoJSON 本质上就是 JSON,但其结构非常特定,而且通常很臃肿。每个文件通常会将你的数据包裹在一个 `FeatureCollection` 中,它包含一个 `Feature` 对象数组。而每个 `Feature` 又有两个主要部分:`geometry` 和 `properties`。`geometry` 块是所有空间数据的存放地——坐标数组、像 Point 或 Polygon 这样的形状类型,以及可能长达数千行的复杂坐标环。`properties` 块才是你通常关心的东西:与每个形状绑定的实际属性数据,比如名称、ID、人口数量或时间戳。 当你需要将数据传递给一个不懂‘地图’的系统时,这种结构就成了问题。想象一下,你从一个 GIS 工具中导出了全美所有 4200 个郡县边界的数据集。生成的 GeoJSON 文件可能轻松达到 18 MB,其中每个郡县的多边形都由数千个坐标对定义。如果你的目标只是在一个报告或 API 中使用郡县名称、FIPS 代码和人口数量,那你相当于拖着 95% 的无用数据。这些几何数据纯属噪音。更糟糕的是,一些解析器会直接拒绝文件,因为它们不认识地理空间相关的键。对于这些非空间用途,剥离几何数据并不是丢失数据,而是一种明智的数据准备工作。
结构差异:GeoJSON 与普通 JSON
我们来具体看看这个转换过程。下面是一个你可能开始时会用到的最简 GeoJSON `Feature`: { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-87.6298, 41.8781] }, "properties": { "city": "Chicago", "population": 2696555, "timezone": "America/Chicago" } } 剥离几何数据后,你的目标就是 `properties` 键中那个干净、简单的 JSON 对象: { "city": "Chicago", "population": 2696555, "timezone": "America/Chicago" } 当你的输入是一个 `FeatureCollection`(一个包含多个要素的文件)时,目标是生成一个只包含每个要素中属性对象的 JSON 数组: [ { "city": "Chicago", "population": 2696555, "timezone": "America/Chicago" }, { "city": "Houston", "population": 2304580, "timezone": "America/Chicago" } ] 这正是大多数 API、电子表格导入工具和数据库加载器所能理解的格式。任何调试过失败 API 调用的人都懂,解析器因为遇到像 `geometry` 或 `type` 这样意料之外的顶级键而出错,是多么令人沮丧。最终的结果是一个干净的 JSON 数组,几乎可以与任何工具配合使用。一个关键细节:如果一个要素的 `properties` 值为 `null` 怎么办?这在 GeoJSON 中是有效的。一个好的转换器会为该要素生成一个空对象 `{}`,而不是崩溃,或者更糟地,悄悄跳过它。粗制滥造的转换器经常在这种测试中失败。
使用 CocoConvert 将 GeoJSON 转换为 JSON
如果你想要一个快速、无需代码的解决方案,CocoConvert 的 GeoJSON 到 JSON 转换器(位于 /convert/geojson-to-json)正是为此设计的。过程很简单。你上传你的 `.geojson` 或 `.json` 文件(它两种扩展名都接受,因为 GeoJSON 常常也用 `.json`),选择你的选项,然后下载精简后的结果。 这个转换器默认情况下处理得非常‘干净利落’。它会找到 `features` 数组,从每一个要素中抓取 `properties` 对象,然后将它们组合成一个干净的 JSON 数组。`geometry` 键、`type` 键以及 `FeatureCollection` 包装器都会被丢弃。如果你的文件只包含一个 `Feature` 而不是一个集合,输出将是一个单独的 JSON 对象,而不是数组。 免费版 CocoConvert 可以处理高达 50 MB 的文件,这对于大多数少于 10000 个要素的数据集来说已经足够了。但如果你有一个像全州道路网这样的大文件(可能轻松超过 200 MB),你就需要使用命令行工具了,我们接下来会讲到。在隐私方面,转换器在服务器上处理文件,但在你的会话结束后不会存储你的数据——如果你的属性包含个人标识符等敏感信息,这是一个重要的细节。 默认情况下,下载的文件扩展名为 `.json`。设置面板能让你进行更多控制,比如指定自定义文件名,或者获取缩进的、人类可读的输出,而不是压缩成一行的格式。
命令行替代方案:处理大文件或实现自动化
当你处理超过 50 MB 的文件或需要在脚本中自动化此转换时,命令行就是你的好朋友。你有两个主要的选择武器:`jq` 和 Python 的内置 `json` 模块。 对于纯粹的 JSON 转换,`jq` 在速度和简洁性上无人能及: jq '[.features[].properties]' input.geojson > output.json 这行命令的作用和网页工具完全一样:它遍历每个要素,提取出 `properties` 对象,然后将结果包裹在一个 JSON 数组中。`jq` 可以在任何主流操作系统上轻松安装(比如在 macOS 上用 `brew install jq`,或在 Debian/Ubuntu 上用 `apt install jq`),并且可以毫不费力地处理 G 级别大小的文件,因为它采用流式处理数据,而不是将所有数据加载到内存中。 如果你的要素 `id` 很重要(在 GeoJSON 中,它与 `properties` 同级,而不是在内部),你可以将它合并进去: jq '[.features[] | {id: .id} + .properties]' input.geojson > output.json 当你需要更复杂的逻辑时,Python 就是答案: import json with open('input.geojson') as f: gj = json.load(f) result = [feature['properties'] for feature in gj['features']] with open('output.json', 'w') as f: json.dump(result, f, indent=2) 这个短小的脚本让你在写入输出之前拥有完全的控制权,可以筛选要素、重命名键或处理奇怪的边缘情况。最棒的是,`json` 模块是 Python 标准库的一部分,所以无需安装任何额外的东西。 我的建议很直接:当你只有一个文件,想在 30 秒内不动代码就拿到结果时,用 CocoConvert。当你要自动化数据管道或处理成百上千个文件时,用 `jq` 或 Python。
处理那些会让简陋转换器崩溃的边缘情况
现实世界中的 GeoJSON 通常比规范里的干净示例要混乱得多。以下是可能让一个简陋转换器出问题的常见陷阱。 **空几何体 (Null geometry):** 你会经常遇到 `"geometry": null` 的要素。这些是完全有效的,通常代表那些就是没有位置数据的记录。一个健壮的转换器必须仍然提取它们的属性,而不是丢弃整个要素。前面展示的 `jq` 和 Python 方法都能正确处理这一点。 **嵌套属性 (Nested properties):** `properties` 对象本身可以包含嵌套的 JSON 对象,比如 `"properties": {"address": {"street": "Main St"}}`。剥离几何数据并不会扁平化这些嵌套结构;它们会按原样保留。如果你需要一个完全扁平的结构(例如,为了生成 CSV),那是一个需要你另外执行的独立转换。 **不一致的键 (Inconsistent keys):** 在一个集合中,有些要素有 `"name"` 键而另一些没有,这种情况很常见。生成的 JSON 数组将只是包含不同形状的对象。这是有效的 JSON,但可能会让强类型系统出问题。CocoConvert 会忠实地提取已有的数据,它不会尝试为你规范化模式。 **`GeometryCollection`:** 有些文件使用 `GeometryCollection` 类型,其结构与标准的 `FeatureCollection` 不同。许多工具,包括 CocoConvert,都期望输入是 `FeatureCollection`,如果遇到顶层是 `GeometryCollection` 可能会失败。 **编码问题 (Encoding problems):** 这是一个典型的 GIS 数据难题。GeoJSON 规范强制要求使用 UTF-8 编码,没有例外。但从旧软件导出的文件有时可能包含 Latin-1 或 Windows-1252 字符。这会导致解析错误。在尝试转换之前,你必须先用像 `iconv` 这样的工具修复上游的编码问题。
什么时候应该保留几何数据(并采用不同的转换方式)
虽然剥离几何数据对于纯数据工作流来说很棒,但有时这绝对是错误的做法。有很多时候你需要保留空间数据,只是形式不同而已。 如果你要将数据提供给像 Leaflet 或 MapboxGL 这样的网页地图库,请停下。不要转换任何东西。这两个库都原生支持 GeoJSON,所以转换为普通 JSON 会移除它们绘制地图所必需的坐标数据。 有时候你需要坐标,只是形式不同——比如一个扁平的 `{lat, lng, name}` 对象数组,用于自定义图表。那是一项重塑数据的任务,而不是剥离几何数据。`jq` 对此非常完美: jq '[.features[] | {lat: .geometry.coordinates[1], lng: .geometry.coordinates[0], name: .properties.name}]' input.geojson > output.json 这里要特别注意坐标顺序。GeoJSON 严格规定为 `[经度, 纬度]`,即 (x, y)。这和许多人以及系统的预期正好相反。搞错这一点是手动处理 GeoJSON 坐标时最常见的错误,它会导致一个既滑稽又令人沮丧的后果:你的数据点会跑到错误的半球去。 如果你的目标是将 GeoJSON 转换为另一种地理空间格式,如 TopoJSON、Shapefile 或 KML,你需要一个不同的工具。CocoConvert 不做这些保留几何数据的转换。为此,你应该使用专门构建的工具,比如命令行强者 `ogr2ogr`(来自 GDAL)或优秀的 Mapshaper 网页工具。它们才是完成那项工作的正确工具。 我们位于 /convert/geojson-to-json 的 GeoJSON 到 JSON 工具只专注于一项任务:提取属性。它只做这一件事,并且做得很好。
在下游使用前验证你的输出
在你将崭新的 JSON 文件输入下游系统之前,花两分钟验证一下。这个简单的步骤可以让你在后面省去数小时调试诡异故障的时间。 用文本编辑器打开文件看看。顶层是一个数组吗(以 `[` 开头)?每个元素是一个对象吗(以 `{` 开头)?最关键的是,你看到任何 `geometry` 或 `type` 键了吗?不应该有。快速搜索字符串 ‘coordinates’ 应该找不到任何匹配项。 接下来,检查记录数量。如果你的输入 GeoJSON 有 847 个要素,那么你的输出 JSON 数组也必须正好有 847 个对象。如果数字不匹配,说明转换器丢弃了要素,很可能是因为输入格式错误或对 null 值的处理不当。 现在,抽查一下数据本身。将新 JSON 中第一个、最后一个以及中间随机一个记录的属性值与原始 GeoJSON 文件进行比较。如果名称、ID 和数字都对得上,你就可以确信转换是干净的。 对于自动化管道,使用 JSONSchema。像用于 Node.js 的 `ajv` 或用于 Python 的 `jsonschema` 等工具,可以让你以编程方式验证数组中的每个对象是否具有你期望的键和数据类型。这对于任何定期在变化数据上运行的流程来说至关重要。 最后还有一件事:如果数据要进入数据库,在导入后对数据表运行一次 `COUNT` 查询。行数是否与你预期的相符?这个 30 秒的检查是确认整个链条——从转换到导入——都完美运行的最终方法。