> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> GeoJSON FeatureCollection 文档的输入和输出格式：输入时，为每个要素生成一行数据，包含 id、geometry 和 properties 列；输出时，每行一个要素。

# GeoJSON

| 输入 | 输出 | 别名 |
| -- | -- | -- |
| ✔  | ✔  |    |

<div id="description">
  ## 说明
</div>

[GeoJSON](https://geojson.org/) 数据以单个 [`FeatureCollection`](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3) 文档的形式进行交换，ClickHouse 会将其映射到三列——`id`、`geometry` 和 `properties`——每个 `Feature` 对应一组。[读取](#reading-data)文档时，每个 `Feature` 会生成一行；[写入](#writing-data)时，每一行会生成一个 `Feature`。

<div id="reading-data">
  ## 读取数据
</div>

读取 `FeatureCollection` 时，每个要素都会生成一行，并采用以下固定 schema：

| 列            | 类型                 | 描述                                                                                       |
| ------------ | ------------------ | ---------------------------------------------------------------------------------------- |
| `id`         | `Nullable(String)` | 要素的 `id` 成员 (JSON 字符串或数字) 会以文本形式存储；如果 `id` 不存在或为 `null`，则为 `NULL`；而显式的空字符串 id 会保留为 `''`。 |
| `geometry`   | `Geometry`         | 要素的几何数据，存储为 `Geometry` Variant 类型。                                                       |
| `properties` | `Nullable(JSON)`   | 要素的 `properties` 对象，存储为半结构化的 `JSON` 列。显式的 `"properties": null` 会保留为 `NULL`。              |

每个几何数据都存储在 ClickHouse 的 `Geometry` 类型中 (即一种 `Variant`) 。支持的 GeoJSON 几何类型包括 `Point`、`LineString`、`MultiLineString`、`Polygon` 和 `MultiPolygon`。另外两种 GeoJSON 几何类型 `GeometryCollection` 和 `MultiPoint` 无法由 `Geometry` 类型表示；默认情况下，如果将其读入 `geometry` 列，会引发异常，但也可以改为插入 `NULL`——参见下文的[处理不支持的几何类型](#unsupported-geometry)。默认情况下，只有当要素的几何数据是显式的 JSON `null` 时，`geometry` 列才为 `NULL`；在 `input_format_geojson_unsupported_geometry_handling = 'null'` 下，不支持的几何类型也会记为 `NULL`。

系统会验证文档的结构：顶层 `type` 必须是 `FeatureCollection`，并且 `features` 中的每个元素都必须具有 `type` `Feature`。默认情况下，坐标必须满足 GeoJSON 的形态不变量——`LineString` (以及 `MultiLineString` 中的每一条线) 必须至少包含两个点，而 `Polygon` 的 Ring (以及 `MultiPolygon` 中的每个 Ring) 必须闭合且至少包含四个点 (参见[几何校验](#geometry-validation)) 。格式错误的文档会被拒绝，而不会静默加载。

键的顺序很灵活：顶层 `type` 可以出现在 `features` 数组之前或之后，在几何对象内部，`coordinates` 也可以出现在 `type` 之前或之后。

schema 推断会返回上述固定 schema，因此 `DESCRIBE` 和 `SELECT ... FROM format(...)` 无需表定义即可工作。

给定以下 GeoJSON 文件 `london.geojson`，其中包含多种几何类型：

```json theme={null}
{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "id": "1",
            "geometry": {"type": "Point", "coordinates": [-0.0761, 51.5081]},
            "properties": {"name": "Tower of London", "feature_type": "landmark", "year_built": 1078}
        },
        {
            "type": "Feature",
            "id": "2",
            "geometry": {
                "type": "LineString",
                "coordinates": [[-0.2500, 51.4700], [-0.1800, 51.4900], [-0.1200, 51.5060], [-0.0700, 51.5050], [0.0000, 51.5100]]
            },
            "properties": {"name": "River Thames", "feature_type": "river", "length_km": 346}
        },
        {
            "type": "Feature",
            "id": "3",
            "geometry": {
                "type": "Polygon",
                "coordinates": [[[-0.1880, 51.5074], [-0.1533, 51.5074], [-0.1533, 51.5153], [-0.1880, 51.5153], [-0.1880, 51.5074]]]
            },
            "properties": {"name": "Hyde Park", "feature_type": "park", "area_km2": 1.42}
        }
    ]
}
```

我们可以查询该文件并查看几何类型：

```sql title="Query" theme={null}
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson', GeoJSON);
```

```response title="Response" theme={null}
┌─id─┬─name────────────┬─geo_type───┐
│ 1  │ Tower of London │ Point      │
│ 2  │ River Thames    │ LineString │
│ 3  │ Hyde Park       │ Polygon    │
└────┴─────────────────┴────────────┘
```

文件扩展名 `.geojson` 会被自动检测到，因此可以省略 `format` 参数：

```sql title="Query" theme={null}
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson');
```

我们可以使用 `variantType` 来判断每个 Geometry 对象的实际类型：

```sql title="Query" theme={null}
SELECT properties.name AS name, geometry, variantType(geometry)
FROM file('london.geojson', GeoJSON);
```

```response title="Response" theme={null}
Row 1:
──────
name:                  Tower of London
geometry:              (-0.0761,51.5081)
variantType(geometry): Point

Row 2:
──────
name:                  River Thames
geometry:              [(-0.25,51.47),(-0.18,51.49),(-0.12,51.506),(-0.07,51.505),(0,51.51)]
variantType(geometry): LineString

Row 3:
──────
name:                  Hyde Park
geometry:              [[(-0.188,51.5074),(-0.1533,51.5074),(-0.1533,51.5153),(-0.188,51.5153),(-0.188,51.5074)]]
variantType(geometry): Polygon
```

我们还可以这样提取底层数据：

```sql title="Query" theme={null}
SELECT properties.name AS name, variantType(geometry), geometry.Point, geometry.LineString, geometry.Polygon
FROM file('london.geojson', GeoJSON);
```

```response title="Response" theme={null}
Row 1:
──────
name:                  Tower of London
variantType(geometry): Point
geometry.Point:        (-0.0761,51.5081)
geometry.LineString:   []
geometry.Polygon:      []

Row 2:
──────
name:                  River Thames
variantType(geometry): LineString
geometry.Point:        (0,0)
geometry.LineString:   [(-0.25,51.47),(-0.18,51.49),(-0.12,51.506),(-0.07,51.505),(0,51.51)]
geometry.Polygon:      []

Row 3:
──────
name:                  Hyde Park
variantType(geometry): Polygon
geometry.Point:        (0,0)
geometry.LineString:   []
geometry.Polygon:      [[(-0.188,51.5074),(-0.1533,51.5074),(-0.1533,51.5153),(-0.188,51.5153),(-0.188,51.5074)]]
```

访问 `Geometry` 子列时，如果该行存储的是该类型，则返回该值；否则返回该类型的默认值——`Point` 为 `(0,0)`，基于数组的类型为 `[]`——因此请使用 `variantType(geometry)` 来判断当前设置的是哪种类型。

我们也可以将 GeoJSON 数据摄取到表中：

```sql title="Query" theme={null}
CREATE TABLE london
(
    id           String,
    geometry     Geometry,
    properties   Nullable(JSON),
    name         String MATERIALIZED properties.name,
    feature_type String MATERIALIZED properties.feature_type
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO london
SELECT id, geometry, properties
FROM file('london.geojson', GeoJSON);
```

然后按要素类型进行查询：

```sql title="Query" theme={null}
SELECT name, feature_type, variantType(geometry) AS geo_type
FROM london
ORDER BY id;
```

```response title="Response" theme={null}
┌─name────────────┬─feature_type─┬─geo_type───┐
│ Tower of London │ landmark     │ Point      │
│ River Thames    │ river        │ LineString │
│ Hyde Park       │ park         │ Polygon    │
└─────────────────┴──────────────┴────────────┘
```

无需表定义，我们也可以推断 GeoJSON 数据的 schema：

```sql title="Query" theme={null}
DESCRIBE format(GeoJSON, '{"type":"FeatureCollection","features":[]}');
```

```response title="Response" theme={null}
┌─name───────┬─type─────────────┐
│ id         │ Nullable(String) │
│ geometry   │ Geometry         │
│ properties │ Nullable(JSON)   │
└────────────┴──────────────────┘
```

<div id="unsupported-geometry">
  ### 处理不支持的几何类型
</div>

某些有效的 GeoJSON 几何类型 — 例如 `GeometryCollection` 和 `MultiPoint` — 无法用 ClickHouse 的 `Geometry` 类型表示。你可以使用 `input_format_geojson_unsupported_geometry_handling` 设置，控制当这类几何对象必须存储到 `geometry` 列中时应如何处理。可能的值为：

* `'throw'` — 抛出异常 (默认)
* `'null'` — 为 `geometry` 列插入 `NULL` 值并继续解析

这种处理方式仅在读取 `geometry` 列时适用。当 `geometry` 不是请求输出的列时 (例如 `SELECT id FROM ...`) ，不受支持的几何对象仍会验证其格式是否正确，但不会触发该处理方式——既不会抛出异常，也不会插入 `NULL`，因为不会将任何几何值 materialize。

<div id="reading-limitations">
  ### 限制
</div>

读取时只能反映固定 schema 所能容纳的内容，因此部分 GeoJSON 信息无法保留：

* 只会生成 `id`、`geometry` 和 `properties`；其他文档结构不会作为列公开。
* 位置的第三个 (高程) 坐标以及其后的所有坐标都会被丢弃——位置将变为 `[longitude, latitude]`。
* `bbox` 和外部成员 (例如顶层的 `name` 或 `crs`，或 `Feature` 内部的额外成员) 都会被忽略。
* 数值型 `id` 会以文本形式存储，因此字符串与数字的区别会丢失；缺失或为 `null` 的 `id` 会变为 `NULL`。
* `GeometryCollection` 和 `MultiPoint` 无法表示——请参阅[处理不支持的几何类型](#unsupported-geometry)。

<div id="writing-data">
  ## 写入数据
</div>

写入结果集时会生成一个 GeoJSON [`FeatureCollection`](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3)，其中每一行对应一个 `Feature`。

结果中的列会按如下方式映射到各个 `Feature`：

| Feature member | Built from                       | Notes                                                                                                                                                       |
| -------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`         | —                                | 始终为 `"Feature"`。                                                                                                                                            |
| `geometry`     | the single geometry-typed column | 必须且只能有一个几何类型列，否则查询会被拒绝。`NULL` 几何值会写为 `null`。                                                                                                                |
| `id`           | a column named `id`              | 值为 `NULL` 时会省略。`String` 列会写为 JSON 字符串，数值列会写为 JSON 数值。                                                                                                       |
| `properties`   | all remaining columns            | 如果有且仅有一个名为 `properties` 的列，且其类型为对象类 (`JSON`、`Map` 或具名 `Tuple`) ，则会直接将其写为 `properties` 对象，而不是再嵌套在 `properties` 键下。否则，其余每一列都会成为一个属性，属性名为列名 (如果没有其余列，则写为空对象) 。 |

几何类型列可以是 `Geometry` Variant，也可以是某种具体的 Geo 类型；每种类型都会映射为相应的 GeoJSON 几何类型：

| ClickHouse type   | GeoJSON `"type"`                      |
| ----------------- | ------------------------------------- |
| `Point`           | `Point`                               |
| `LineString`      | `LineString`                          |
| `MultiLineString` | `MultiLineString`                     |
| `Polygon`         | `Polygon`                             |
| `MultiPolygon`    | `MultiPolygon`                        |
| `Ring`            | `Polygon` (a single ring)             |
| `Geometry`        | the active variant's type (or `null`) |

`Ring` 不是 GeoJSON 几何类型——[linear ring](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6) 是 `Polygon` 的一个组成部分——因此，`Ring` 值会写为只包含单个环的 `Polygon`。

<div id="writing-examples">
  ### 示例
</div>

继续以上文[创建](#reading-data)的 `london` 表为例，导出普通属性列时，会将除 `id` 和 `geometry` 之外的每一列都转换为一个属性：

```sql title="Query" theme={null}
SELECT id, geometry, name, feature_type
FROM london
ORDER BY id
FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"type":"FeatureCollection","features":[{"type":"Feature","id":"1","geometry":{"type":"Point","coordinates":[-0.0761,51.5081]},"properties":{"name":"Tower of London","feature_type":"landmark"}},{"type":"Feature","id":"2","geometry":{"type":"LineString","coordinates":[[-0.25,51.47],[-0.18,51.49],[-0.12,51.506],[-0.07,51.505],[0,51.51]]},"properties":{"name":"River Thames","feature_type":"river"}},{"type":"Feature","id":"3","geometry":{"type":"Polygon","coordinates":[[[-0.188,51.5074],[-0.1533,51.5074],[-0.1533,51.5153],[-0.188,51.5153],[-0.188,51.5074]]]},"properties":{"name":"Hyde Park","feature_type":"park"}}]}
```

由于名为 `properties` 的唯一对象类型列会被直接写出，因此，读取 GeoJSON 文件并直接原样写回时会重现该文档 (为该文件推断出的列是 `id`、`geometry` 和 `properties`) ：

```sql title="Query" theme={null}
SELECT * FROM file('london.geojson', GeoJSON) FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"type":"FeatureCollection","features":[{"type":"Feature","id":"1","geometry":{"type":"Point","coordinates":[-0.0761,51.5081]},"properties":{"feature_type":"landmark","name":"Tower of London","year_built":1078}},{"type":"Feature","id":"2","geometry":{"type":"LineString","coordinates":[[-0.25,51.47],[-0.18,51.49],[-0.12,51.506],[-0.07,51.505],[0,51.51]]},"properties":{"feature_type":"river","length_km":346,"name":"River Thames"}},{"type":"Feature","id":"3","geometry":{"type":"Polygon","coordinates":[[[-0.188,51.5074],[-0.1533,51.5074],[-0.1533,51.5153],[-0.188,51.5153],[-0.188,51.5074]]]},"properties":{"area_km2":1.42,"feature_type":"park","name":"Hyde Park"}}]}
```

数值型 `id` 列会以 JSON 数值形式写入 (如果 `Nullable` 的 `id` 为 `NULL`，则会被完全省略) ：

```sql title="Query" theme={null}
SELECT 42 AS id, (-0.1276, 51.5072)::Point AS geometry FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"type":"FeatureCollection","features":[{"type":"Feature","id":42,"geometry":{"type":"Point","coordinates":[-0.1276,51.5072]},"properties":{}}]}
```

`Ring` 可写作单环 `Polygon`：

```sql title="Query" theme={null}
SELECT [(0., 0.), (10., 0.), (10., 10.), (0., 0.)]::Ring AS geometry FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,0]]]},"properties":{}}]}
```

<div id="writing-to-a-file">
  ### 写入文件
</div>

使用 `INTO OUTFILE` 可将客户端中的数据写出为 GeoJSON 文件：

```sql title="Query" theme={null}
SELECT id, geometry, properties
FROM london
ORDER BY id
INTO OUTFILE 'london_export.geojson'
FORMAT GeoJSON;
```

服务器本身可以使用 `file` 表函数写入该文件 (`.geojson` 扩展名会自动选择格式) ：

```sql title="Query" theme={null}
INSERT INTO FUNCTION file('london_export.geojson', GeoJSON)
SELECT id, geometry, properties FROM london;
```

<div id="reading-limitations">
  ### 限制
</div>

<Note>
  ClickHouse 的 geo types 不包含坐标参考系统，因此输出会假定坐标已是 WGS84 经纬度，并按 `[longitude, latitude]` 顺序排列，这也是 [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946#section-4) 的要求。不会执行重投影或坐标轴交换，因此投影坐标——或以 `(latitude, longitude)` 形式存储的数据——会生成结构有效但不符合规范的 GeoJSON。
</Note>

输出只反映 ClickHouse 中实际存储的内容：

* 读取时丢失的信息——位置高程、`bbox`、外部成员，以及 `id` 的字符串/数字类型区别——都无法恢复；请参见[读取限制](#reading-limitations)。
* 坐标从 `Float64` 值写出时，会使用其可无损往返的最短表示形式。
* 直接取自 `JSON` 列的 `properties` 对象，会按 `JSON` 类型的规范键顺序输出，这可能与输入顺序不同。

几何对象会严格按存储内容写出——坐标顺序和绕向都会被保留。默认情况下，写出时会强制校验 GeoJSON 几何形状的有效性 (参见[几何校验](#geometry-validation)) ：如果某个几何对象不是有效的 GeoJSON 形状，例如只有一个点的 `LineString`，或未闭合的 `Polygon` Ring，则会被拒绝，以确保写出的文档能够被正确读回。将 `format_geojson_validate_geometry = 0` 设为 0 后，则会按原样输出这类几何对象，从而生成结构有效但不符合规范的 GeoJSON。无论是否这样设置，都不会强制执行右手法则 (绕向) 不变式，并且会保留 `null` 与空 `properties` 对象之间的区别。

<div id="geometry-validation">
  ## 几何校验
</div>

设置 `format_geojson_validate_geometry` 用于控制该格式在读写两个方向上是否都强制遵循 [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1) 的几何形态规则。该设置默认启用。

启用后，违反 GeoJSON 形态规则的几何体会被拒绝：点数少于两个的 `LineString` (或 `MultiLineString` 中的一条线) ；点数少于四个，或首尾点不同 (即未闭合 Ring) 的 `Polygon` 或 `MultiPolygon` 的 Ring；以及空的 `MultiLineString`、`Polygon` 或 `MultiPolygon`。读取此类文档和写入此类 ClickHouse 值时适用的规则完全相同，因此写出的文档始终都能再读回来。

禁用后，这些形态规则在两个方向上都不会强制执行：退化几何体会按原样读取，也会按原样写出。这样一来，那些不是有效 GeoJSON 几何体的 ClickHouse 几何值也可以通过该格式往返转换，但代价是生成的文档将不是有效的 GeoJSON。

这种校验仅限于结构层面：只检查点数和 Ring 是否闭合。它不会检查形状在几何意义上的正确性，因此结构有效但几何上退化的几何体在两个方向上都会被接受——例如面积为零的多边形、自相交的 Ring，或者孔 (内 Ring) 位于外 Ring 之外的多边形。同样，多边形 Ring 的右手法则 (绕向) 也从不会被强制要求。

有一项检查独立于该设置：非有限坐标 (`NaN`、`Inf`) 始终会被拒绝，因为它们无法表示为 JSON 数字。
