| 输入 | 输出 | 别名 |
|---|---|---|
| ✔ | ✔ |
说明
GeoJSON 数据以单个 FeatureCollection 文档的形式进行交换,ClickHouse 会将其映射到三列——id、geometry 和 properties——每个 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 几何类型包括 Point、MultiPoint、LineString、MultiLineString、Polygon 和 MultiPolygon。剩余的 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,因此 DESCRIBE 和 SELECT ... 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}
}
]
}我们可以查询该文件并查看几何类型:
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson', GeoJSON);┌─id─┬─name────────────┬─geo_type───┐
│ 1 │ Tower of London │ Point │
│ 2 │ River Thames │ LineString │
│ 3 │ Hyde Park │ Polygon │
└────┴─────────────────┴────────────┘文件扩展名 .geojson 会被自动识别,因此可以省略格式参数:
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson');我们可以使用 variantType 来判断每个 Geometry 对象的实际类型:
SELECT properties.name AS name, geometry, variantType(geometry)
FROM file('london.geojson', GeoJSON);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我们还可以这样提取底层数据:
SELECT properties.name AS name, variantType(geometry), geometry.Point, geometry.LineString, geometry.Polygon
FROM file('london.geojson', GeoJSON);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 数据摄取到表中:
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);然后按要素类型进行查询:
SELECT name, feature_type, variantType(geometry) AS geo_type
FROM london
ORDER BY id;┌─name────────────┬─feature_type─┬─geo_type───┐
│ Tower of London │ landmark │ Point │
│ River Thames │ river │ LineString │
│ Hyde Park │ park │ Polygon │
└─────────────────┴──────────────┴────────────┘无需表定义,我们也可以推断 GeoJSON 数据的 schema:
DESCRIBE format(GeoJSON, '{"type":"FeatureCollection","features":[]}');┌─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 信息无法保留:
- 只会生成
id、geometry和properties;其他文档结构不会作为列公开。 - 位置的第三个 (高程) 坐标以及其后的所有坐标都会被丢弃——位置将变为
[longitude, latitude]。 bbox和外部成员 (例如顶层的name或crs,或Feature内部的额外成员) 都会被忽略。- 数值型
id会以文本形式存储,因此字符串与数字的区别会丢失;缺失或为null的id会变为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 的列,且其类型为对象类 (JSON、Map 或具名 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 ring 是 Polygon 的一个组成部分——因此,Ring 值会写为只包含单个环的 Polygon。
示例
继续以上文创建的 london 表为例,导出普通属性列时,会将除 id 和 geometry 之外的每一列都转换为一个属性:
SELECT id, geometry, name, feature_type
FROM london
ORDER BY id
FORMAT GeoJSON;{"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) :
SELECT * FROM file('london.geojson', GeoJSON) FORMAT GeoJSON;{"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,则会被完全省略) :
SELECT 42 AS id, (-0.1276, 51.5072)::Point AS geometry FORMAT GeoJSON;{"type":"FeatureCollection","features":[{"type":"Feature","id":42,"geometry":{"type":"Point","coordinates":[-0.1276,51.5072]},"properties":{}}]}Ring 可写作单环 Polygon:
SELECT [(0., 0.), (10., 0.), (10., 10.), (0., 0.)]::Ring AS geometry FORMAT GeoJSON;{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,0]]]},"properties":{}}]}写入文件
使用 INTO OUTFILE 可将客户端中的数据写出为 GeoJSON 文件:
SELECT id, geometry, properties
FROM london
ORDER BY id
INTO OUTFILE 'london_export.geojson'
FORMAT GeoJSON;服务器本身可以使用 file 表函数写入该文件 (.geojson 扩展名会自动选择格式) :
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) 的 Polygon 或 MultiPolygon 的 Ring;以及空的 MultiLineString、Polygon 或 MultiPolygon。读取此类文档和写入此类 ClickHouse 值时适用的规则完全相同,因此写出的文档始终都能再读回来。
禁用后,这些形态规则在两个方向上都不会强制执行:退化几何体会按原样读取,也会按原样写出。这样一来,那些不是有效 GeoJSON 几何体的 ClickHouse 几何值也可以通过该格式往返转换,但代价是生成的文档将不是有效的 GeoJSON。
这种校验仅限于结构层面:只检查点数和 Ring 是否闭合。它不会检查形状在几何意义上的正确性,因此结构有效但几何上退化的几何体在两个方向上都会被接受——例如面积为零的多边形、自相交的 Ring,或者孔 (内 Ring) 位于外 Ring 之外的多边形。同样,多边形 Ring 的右手法则 (绕向) 也从不会被强制要求。
有一项检查独立于该设置:非有限坐标 (NaN、Inf) 始终会被拒绝,因为它们无法表示为 JSON 数字。