Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

GeoJSON

输入 输出 别名

说明

GeoJSON 数据以单个 FeatureCollection 文档的形式进行交换,ClickHouse 会将其映射到三列——idgeometryproperties——每个 Feature 对应一组。读取文档时,每个 Feature 会生成一行;写入时,每一行会生成一个 Feature

读取数据

读取 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 几何类型包括 PointMultiPointLineStringMultiLineStringPolygonMultiPolygon。剩余的 GeoJSON 几何类型 GeometryCollection 无法由 Geometry 类型表示;默认情况下,将其读入 geometry 列会引发异常,但可以改为插入 NULL —— 详见下文的处理不支持的几何类型。默认情况下,只有当要素的几何显式为 JSON null 时,geometry 列才为 NULL;在 input_format_geojson_unsupported_geometry_handling = 'null' 时,对于不支持的几何类型该列也将为 NULL

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

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

schema 推断会返回上述固定 schema,因此 DESCRIBESELECT ... FROM format(...) 无需表定义即可工作。

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

{
    "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}
        }
    ]
}

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

Querysql
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson', GeoJSON);
Responseresponse
┌─id─┬─name────────────┬─geo_type───┐
│ 1  │ Tower of London │ Point      │
│ 2  │ River Thames    │ LineString │
│ 3  │ Hyde Park       │ Polygon    │
└────┴─────────────────┴────────────┘

文件扩展名 .geojson 会被自动识别,因此可以省略格式参数:

Querysql
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson');

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

Querysql
SELECT properties.name AS name, geometry, variantType(geometry)
FROM file('london.geojson', GeoJSON);
Responseresponse
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

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

Querysql
SELECT properties.name AS name, variantType(geometry), geometry.Point, geometry.LineString, geometry.Polygon
FROM file('london.geojson', GeoJSON);
Responseresponse
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 数据摄取到表中:

Querysql
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);

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

Querysql
SELECT name, feature_type, variantType(geometry) AS geo_type
FROM london
ORDER BY id;
Responseresponse
┌─name────────────┬─feature_type─┬─geo_type───┐
│ Tower of London │ landmark     │ Point      │
│ River Thames    │ river        │ LineString │
│ Hyde Park       │ park         │ Polygon    │
└─────────────────┴──────────────┴────────────┘

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

Querysql
DESCRIBE format(GeoJSON, '{"type":"FeatureCollection","features":[]}');
Responseresponse
┌─name───────┬─type─────────────┐
│ id         │ Nullable(String) │
│ geometry   │ Geometry         │
│ properties │ Nullable(JSON)   │
└────────────┴──────────────────┘

处理不支持的几何类型

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

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

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

限制

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

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

写入数据

写入结果集时会生成一个 GeoJSON FeatureCollection,其中每一行对应一个 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 的列,且其类型为对象类 (JSONMap 或具名 Tuple) ,则会直接将其写为 properties 对象,而不是再嵌套在 properties 键下。否则,其余每一列都会成为一个属性,属性名为列名 (如果没有其余列,则写为空对象) 。

几何类型列可以是 Geometry Variant,也可以是某种具体的 Geo 类型;每种类型都会映射为相应的 GeoJSON 几何类型:

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

Ring 不是 GeoJSON 几何类型——linear ringPolygon 的一个组成部分——因此,Ring 值会写为只包含单个环的 Polygon

示例

继续以上文创建london 表为例,导出普通属性列时,会将除 idgeometry 之外的每一列都转换为一个属性:

Querysql
SELECT id, geometry, name, feature_type
FROM london
ORDER BY id
FORMAT GeoJSON;
Responseresponse
{"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 文件并直接原样写回时会重现该文档 (为该文件推断出的列是 idgeometryproperties) :

Querysql
SELECT * FROM file('london.geojson', GeoJSON) FORMAT GeoJSON;
Responseresponse
{"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 数值形式写入 (如果 NullableidNULL,则会被完全省略) :

Querysql
SELECT 42 AS id, (-0.1276, 51.5072)::Point AS geometry FORMAT GeoJSON;
Responseresponse
{"type":"FeatureCollection","features":[{"type":"Feature","id":42,"geometry":{"type":"Point","coordinates":[-0.1276,51.5072]},"properties":{}}]}

Ring 可写作单环 Polygon

Querysql
SELECT [(0., 0.), (10., 0.), (10., 10.), (0., 0.)]::Ring AS geometry FORMAT GeoJSON;
Responseresponse
{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,0]]]},"properties":{}}]}

写入文件

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

Querysql
SELECT id, geometry, properties
FROM london
ORDER BY id
INTO OUTFILE 'london_export.geojson'
FORMAT GeoJSON;

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

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

限制

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

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

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

几何校验

设置 format_geojson_validate_geometry 用于控制该格式在读写两个方向上是否都强制遵循 RFC 7946 的几何形态规则。该设置默认启用。

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

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

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

有一项检查独立于该设置:非有限坐标 (NaNInf) 始终会被拒绝,因为它们无法表示为 JSON 数字。

Navigation