This is an automated email from the ASF dual-hosted git repository.
emkornfield pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/parquet-testing.git
The following commit(s) were added to refs/heads/master by this push:
new d1f14a0 Example files for GEOMETRY and GEOGRAPHY logical type (#70)
d1f14a0 is described below
commit d1f14a06f800238b127b51fef6fa6b9feb15ab0b
Author: Dewey Dunnington <[email protected]>
AuthorDate: Wed Apr 30 13:25:51 2025 -0500
Example files for GEOMETRY and GEOGRAPHY logical type (#70)
* add some files
* slightly better crs representations for projjson
* maybe actually fix
* remove redundant files
* rewrite with crs keys that are less likely to collide
* remove previous files
* simpler example file
* fix WKB in file for emtpy multiX types
* add crs files
* add nan case
* rewrite geospatial.parquet with new empty/all null logic
* update Shapely stats calculator for new files
---
data/geospatial/README.md | 67 ++++++
data/geospatial/crs-arbitrary-value.parquet | Bin 0 -> 14867 bytes
data/geospatial/crs-default.parquet | Bin 0 -> 15944 bytes
data/geospatial/crs-gen.py | 163 ++++++++++++++
data/geospatial/crs-geography.parquet | Bin 0 -> 15903 bytes
data/geospatial/crs-projjson.parquet | Bin 0 -> 15417 bytes
data/geospatial/crs-srid.parquet | Bin 0 -> 12559 bytes
data/geospatial/geospatial-gen.py | 213 ++++++++++++++++++
data/geospatial/geospatial-with-nan.parquet | Bin 0 -> 1111 bytes
data/geospatial/geospatial.parquet | Bin 0 -> 48364 bytes
data/geospatial/geospatial.yaml | 332 ++++++++++++++++++++++++++++
11 files changed, 775 insertions(+)
diff --git a/data/geospatial/README.md b/data/geospatial/README.md
new file mode 100644
index 0000000..5798fc4
--- /dev/null
+++ b/data/geospatial/README.md
@@ -0,0 +1,67 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+# Geospatial Test Files
+
+
+These test files cover the main and corner case functionality of the
+[Parquet Geospatial
Types](https://github.com/apache/parquet-format/blob/master/Geospatial.md)
+GEOMETRY and GEOGRAPHY.
+
+- `geospatial.parquet`: Contains row groups with specific combinations of
+ geometry types to test statistics generation and geometry type coverage.
+ The file contains columns `group` (string identifier of the group name),
+ `wkt` (the human-readable well-known text representation of the geometry)
+ and `geometry` (a Parquet GEOMETRY column). A human-readable version of
+ the file is available in `geospatial.yaml`.
+
+- `geospatial-with-nan.parquet`: Contains a single row group with a GEOMETRY
+ column whose contents contains two valid geometries and one invalid
LINESTRING
+ whose coordinates contain a `NaN` value in all dimensions. Such a geometry is
+ not valid and the behaviour of it is not defined; however, implementations
should
+ not generate statistics that would prevent the other (valid) geometries in
the
+ column chunk from appearing in the case of predicate pushdown. Notably,
+ implementations should *not* generate statistics that contain `NaN` for this
case.
+
+ Note that POINT EMPTY is represented by convention in well-known binary as
+ a POINT whose coordinates are all `NaN`, which should be treated as a valid
+ (but empty) geometry.
+
+- `crs-default.parquet`: Contains a GEOMETRY column with the crs
+ omitted. This should be interpreted as OGC:CRS84 (i.e., longitude/latitude).
+
+- `crs-geography.parquet`: Contains a GEOGRAPHY column with the crs
+ omitted. This should be interpreted as OGC:CRS84 (i.e., longitude/latitude).
+
+- `crs-projjson.parquet`: Contains a GEOMETRY column with the crs parameter
+ set to `projjson:projjson_epsg_5070` and a metadata field with the key
+ `projjson_epsg_5070` and a value consisting of the appropriate PROJJSON
+ value for EPSG:5070.
+
+- `crs-srid.parquet`: Contains a GEOMETRY column with the crs parameter set
+ to `srid:5070`. The Parquet format does not mention the EPSG database in
+ any way, but otherwise out-of-context SRID values are commonly interpreted
+ as the corresponding EPSG:xxxx value. Producers of SRIDs may wish to
+ avoid valid EPSG:xxxx values where this is not the intended usage to minimize
+ the chances they will be misinterpreted by consumers who make this
assumption.
+
+- `crs-arbitrary-value.parquet`: Contains a GEOMETRY column with the crs
+ parameter set to an arbitrary string value. The Parquet format does not
+ restrict the value of the crs parameter and implementations may choose to
+ attempt interpreting the value or error.
diff --git a/data/geospatial/crs-arbitrary-value.parquet
b/data/geospatial/crs-arbitrary-value.parquet
new file mode 100644
index 0000000..622049c
Binary files /dev/null and b/data/geospatial/crs-arbitrary-value.parquet differ
diff --git a/data/geospatial/crs-default.parquet
b/data/geospatial/crs-default.parquet
new file mode 100644
index 0000000..280dc2c
Binary files /dev/null and b/data/geospatial/crs-default.parquet differ
diff --git a/data/geospatial/crs-gen.py b/data/geospatial/crs-gen.py
new file mode 100644
index 0000000..48f849e
--- /dev/null
+++ b/data/geospatial/crs-gen.py
@@ -0,0 +1,163 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from pathlib import Path
+
+import pyarrow as pa
+from pyarrow import parquet
+import pyproj
+import shapely
+
+HERE = Path(__file__).parent
+
+
+# Using Wyoming because it is the easiest state to inline into a Python file
+WYOMING_LOWRES = (
+ "POLYGON ((-111.0 45.0, -111.0 41.0, -104.0 41.0, -104.0 45.0, -111.0
45.0))"
+)
+
+# We densify the edges such that there is a point every 0.1 degrees to minimize
+# the effect of the edge algorithm and coordinate transformation.
+WYOMING_HIRES = shapely.from_wkt(WYOMING_LOWRES).segmentize(0.1).wkt
+
+
+class WkbType(pa.ExtensionType):
+ """Minimal geoarrow.wkb implementation"""
+
+ def __init__(self, crs=None, edges=None, *, storage_type=pa.binary(),
**kwargs):
+ self.crs = crs
+ self.edges = edges
+ super().__init__(storage_type, "geoarrow.wkb")
+
+ def __arrow_ext_serialize__(self):
+ obj = {"crs": self.crs, "edges": self.edges}
+ return json.dumps({k: v for k, v in obj.items() if v}).encode()
+
+ @classmethod
+ def __arrow_ext_deserialize__(cls, storage_type, serialized):
+ obj: dict = json.loads(serialized)
+ return WkbType(**obj, storage_type=storage_type)
+
+
+pa.register_extension_type(WkbType())
+
+
+def write_crs(type, geometry, name, col_name="geometry", metadata=None):
+ schema = pa.schema({"wkt": pa.utf8(), col_name: type})
+
+ with parquet.ParquetWriter(
+ HERE / name,
+ schema,
+ # Not sure if there's a way to write metadata without
+ # storing the Arrow schema
+ store_schema=metadata is not None,
+ compression="none",
+ ) as writer:
+ batch = pa.record_batch(
+ {
+ "wkt": [geometry.wkt],
+ col_name: type.wrap_array(pa.array([geometry.wkb])),
+ }
+ )
+ writer.write_batch(batch)
+
+ if metadata is not None:
+ writer.add_key_value_metadata(metadata)
+
+
+def write_crs_files():
+ # Create the Shapely geometry
+ geometry = shapely.from_wkt(WYOMING_HIRES)
+
+ # A general purpose coordinate system for the United States
+ crs_not_lonlat = pyproj.CRS("EPSG:5070")
+ transformer = pyproj.Transformer.from_crs(
+ "OGC:CRS84", crs_not_lonlat, always_xy=True
+ )
+ geometry_not_lonlat = shapely.transform(
+ geometry, transformer.transform, interleaved=False
+ )
+
+ # Write with the default CRS (i.e., lon/lat)
+ write_crs(WkbType(), geometry, "crs-default.parquet")
+
+ # Write a Geography column with the default CRS
+ write_crs(
+ WkbType(edges="spherical"),
+ geometry,
+ "crs-geography.parquet",
+ col_name="geography",
+ )
+
+ # Write a file with the projjson format in the specification
+ # and the appropriate metadata key
+ write_crs(
+ WkbType(crs="projjson:projjson_epsg_5070"),
+ geometry_not_lonlat,
+ "crs-projjson.parquet",
+ metadata={"projjson_epsg_5070": crs_not_lonlat.to_json()},
+ )
+
+ # Write a file with the srid format in the specification
+ write_crs(WkbType(crs="srid:5070"), geometry_not_lonlat,
"crs-srid.parquet")
+
+ # Write a file with an arbitrary value (theoretically allowed by the format
+ # and consumers may choose to error or attempt to interpret the value)
+ write_crs(
+ WkbType(crs=crs_not_lonlat.to_json_dict()),
+ geometry_not_lonlat,
+ "crs-arbitrary-value.parquet",
+ )
+
+
+def check_crs_schema(name, expected_col_type):
+ file = parquet.ParquetFile(HERE / name)
+
+ col = file.schema.column(1)
+ col_dict = json.loads(col.logical_type.to_json())
+ col_type = col_dict["Type"]
+ if col_type != expected_col_type:
+ raise ValueError(
+ f"Expected '{expected_col_type}' logical type but got '{col_type}'"
+ )
+
+
+def check_crs_crs(name, expected_crs):
+ expected_crs = pyproj.CRS(expected_crs)
+
+ file = parquet.ParquetFile(HERE / name, arrow_extensions_enabled=True)
+ ext_type = file.schema_arrow.field(1).type
+ actual_crs = pyproj.CRS(ext_type.crs)
+ if actual_crs != expected_crs:
+ raise ValueError(f"Expected '{expected_crs}' crs but got
'{actual_crs}'")
+
+
+def check_crs(name, expected_col_type, expected_crs):
+ check_crs_schema(name, expected_col_type)
+ check_crs_crs(name, expected_crs)
+
+
+if __name__ == "__main__":
+ write_crs_files()
+
+ check_crs("crs-default.parquet", "Geometry", "OGC:CRS84")
+ check_crs("crs-geography.parquet", "Geography", "OGC:CRS84")
+ check_crs("crs-projjson.parquet", "Geometry", "EPSG:5070")
+ check_crs("crs-srid.parquet", "Geometry", "EPSG:5070")
+ check_crs("crs-arbitrary-value.parquet", "Geometry", "EPSG:5070")
diff --git a/data/geospatial/crs-geography.parquet
b/data/geospatial/crs-geography.parquet
new file mode 100644
index 0000000..286e2ec
Binary files /dev/null and b/data/geospatial/crs-geography.parquet differ
diff --git a/data/geospatial/crs-projjson.parquet
b/data/geospatial/crs-projjson.parquet
new file mode 100644
index 0000000..8d694bc
Binary files /dev/null and b/data/geospatial/crs-projjson.parquet differ
diff --git a/data/geospatial/crs-srid.parquet b/data/geospatial/crs-srid.parquet
new file mode 100644
index 0000000..0b97a53
Binary files /dev/null and b/data/geospatial/crs-srid.parquet differ
diff --git a/data/geospatial/geospatial-gen.py
b/data/geospatial/geospatial-gen.py
new file mode 100644
index 0000000..3d3ee5a
--- /dev/null
+++ b/data/geospatial/geospatial-gen.py
@@ -0,0 +1,213 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import json
+from pathlib import Path
+import re
+
+import geoarrow.pyarrow as ga
+import numpy as np
+import pyarrow as pa
+from pyarrow import parquet
+import shapely
+import shapely.testing
+import yaml
+
+# M value support was added in Shapely 2.1.0
+if tuple(shapely.__version__.split(".")) < ("2", "1", "0"):
+ raise ImportError("shapely >= 2.1.0 is required")
+
+HERE = Path(__file__).parent
+
+# Shapely doesn't propagate the geometry type for MULTI* Z/M/ZM, so
+# we have to write our own geometry type detector to check statistics output
+GEOMETRY_TYPE_CODE = {
+ "POINT": 1,
+ "LINESTRING": 2,
+ "POLYGON": 3,
+ "MULTIPOINT": 4,
+ "MULTILINESTRING": 5,
+ "MULTIPOLYGON": 6,
+ "GEOMETRYCOLLECTION": 7,
+}
+
+DIMENSIONS_CODE = {None: 0, "Z": 1000, "M": 2000, "ZM": 3000}
+
+
+def geometry_type_code(wkt):
+ if wkt is None:
+ return None
+
+ geometry_type, _, dimensions = re.match(r"([A-Z]+)( ([ZM]+)?)?",
wkt).groups()
+ return GEOMETRY_TYPE_CODE[geometry_type] + DIMENSIONS_CODE[dimensions]
+
+
+def write_geospatial():
+ with open(HERE / "geospatial.yaml") as f:
+ examples = yaml.safe_load(f)
+
+ schema = pa.schema({"group": pa.utf8(), "wkt": pa.utf8(), "geometry":
ga.wkb()})
+
+ with parquet.ParquetWriter(
+ HERE / "geospatial.parquet",
+ schema,
+ store_schema=False,
+ compression="none",
+ ) as writer:
+ for group_name, geometries_wkt in examples.items():
+ # Unfortunately we can't use Shapely to generate the test WKB
+ # because of https://github.com/libgeos/geos/issues/888, so we use
+ # geoarrow.pyarrow.as_wkb() instead.
+ # geometries = shapely.from_wkt(geometries_wkt)
+ # wkbs = shapely.to_wkb(geometries, flavor="iso")
+ # wkb_array = ga.wkb().wrap_array(pa.array(wkbs, pa.binary()))
+ wkt_array = pa.array(geometries_wkt, pa.utf8())
+
+ batch = pa.record_batch(
+ {
+ "group": [group_name] * len(geometries_wkt),
+ "wkt": wkt_array,
+ "geometry": ga.as_wkb(wkt_array),
+ }
+ )
+
+ writer.write_batch(batch)
+
+
+def write_geospatial_with_nan():
+ geometries_wkt = [
+ "POINT ZM (10 20 30 40)",
+ "POINT ZM (50 60 70 80)",
+ "LINESTRING ZM (90 100 110 120, nan nan nan nan, 130 140 150 160)",
+ ]
+
+ schema = pa.schema({"group": pa.utf8(), "wkt": pa.utf8(), "geometry":
ga.wkb()})
+
+ with parquet.ParquetWriter(
+ HERE / "geospatial-with-nan.parquet",
+ schema,
+ store_schema=False,
+ compression="none",
+ ) as writer:
+ geometries = shapely.from_wkt(geometries_wkt)
+ wkbs = shapely.to_wkb(geometries, flavor="iso")
+ wkb_array = ga.wkb().wrap_array(pa.array(wkbs, pa.binary()))
+
+ batch = pa.record_batch(
+ {
+ "group": ["with-nan"] * len(geometries_wkt),
+ "wkt": geometries_wkt,
+ "geometry": wkb_array,
+ }
+ )
+
+ writer.write_batch(batch)
+
+
+def check_geospatial_schema(name):
+ file = parquet.ParquetFile(HERE / name)
+
+ col = file.schema.column(2)
+ col_dict = json.loads(col.logical_type.to_json())
+ col_type = col_dict["Type"]
+ if col_type != "Geometry":
+ raise ValueError(f"Expected 'Geometry' logical type but got
'{col_type}'")
+
+
+def check_geospatial_values(name):
+ tab = parquet.read_table(HERE / name, arrow_extensions_enabled=False)
+ geometries_from_wkt = shapely.from_wkt(tab["wkt"])
+ geometries_from_wkb = shapely.from_wkb(tab["geometry"])
+ shapely.testing.assert_geometries_equal(geometries_from_wkt,
geometries_from_wkb)
+
+
+def iso_type_code(shapely_type_id, has_z, has_m):
+ # item was null
+ if shapely_type_id < 0:
+ return None
+
+ # GEOS type ids are not quite WKB type ids
+ iso_type_id = shapely_type_id if shapely_type_id >= 3 else shapely_type_id
+ 1
+ iso_dimensions = (
+ 3000 if has_z and has_m else 2000 if has_m else 1000 if has_z else 0
+ )
+ return int(iso_dimensions + iso_type_id)
+
+
+def calc_stats_shapely(wkts):
+ geometries = shapely.from_wkt(wkts)
+
+ # Calculate the list of iso type codes
+ any_not_null = any(geom is not None for geom in geometries)
+ type_codes = set(geometry_type_code(wkt) for wkt in wkts)
+
+ # Calculate min/max ignoring nan values
+ coords = shapely.get_coordinates(geometries, include_z=True,
include_m=True)
+ coord_mins = [
+ None if np.isposinf(x) else float(x)
+ for x in np.nanmin(coords, 0, initial=np.inf)
+ ]
+ coord_maxes = [
+ None if np.isneginf(x) else float(x)
+ for x in np.nanmax(coords, 0, initial=-np.inf)
+ ]
+
+ # Assemble stats in the same format as returned by geo_statistics.to_dict()
+ stats = {}
+ stats["geospatial_types"] = list(
+ sorted(code for code in type_codes if code is not None)
+ ) if any_not_null else None
+ stats["xmin"], stats["ymin"], stats["zmin"], stats["mmin"] = coord_mins
+ stats["xmax"], stats["ymax"], stats["zmax"], stats["mmax"] = coord_maxes
+
+ return stats
+
+
+def check_batch_statistics(stats, wkts, group):
+ if stats is None:
+ raise ValueError(f"geo_statistics is missing for group {group}")
+
+ shapely_stats = calc_stats_shapely(wkts)
+ file_stats = stats.to_dict()
+ if file_stats != shapely_stats:
+ raise ValueError(
+ f"stats mismatch calculated:\n{shapely_stats}\nvs
file\n{file_stats}"
+ )
+
+
+def check_geospatial_statistics(name):
+ file = parquet.ParquetFile(HERE / name)
+ for i in range(file.num_row_groups):
+ batch = file.read_row_group(i)
+ column_metadata = file.metadata.row_group(i).column(2)
+ group = batch["group"][0].as_py()
+
+ check_batch_statistics(
+ column_metadata.geo_statistics, batch["wkt"].to_pylist(), group
+ )
+
+
+if __name__ == "__main__":
+ write_geospatial()
+ write_geospatial_with_nan()
+
+ check_geospatial_schema("geospatial.parquet")
+ check_geospatial_values("geospatial.parquet")
+ check_geospatial_statistics("geospatial.parquet")
+
+ check_geospatial_schema("geospatial-with-nan.parquet")
+ check_geospatial_statistics("geospatial-with-nan.parquet")
diff --git a/data/geospatial/geospatial-with-nan.parquet
b/data/geospatial/geospatial-with-nan.parquet
new file mode 100644
index 0000000..995139c
Binary files /dev/null and b/data/geospatial/geospatial-with-nan.parquet differ
diff --git a/data/geospatial/geospatial.parquet
b/data/geospatial/geospatial.parquet
new file mode 100644
index 0000000..eb5ed5c
Binary files /dev/null and b/data/geospatial/geospatial.parquet differ
diff --git a/data/geospatial/geospatial.yaml b/data/geospatial/geospatial.yaml
new file mode 100644
index 0000000..0ac5c96
--- /dev/null
+++ b/data/geospatial/geospatial.yaml
@@ -0,0 +1,332 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# These are the values used to generate the content of the geospatial.parquet
+# file, with each item here comprising a row group in the output. (See
+# README.md for further description of geospatial.parquet). Note that
+# Z values are always calculated as X + Y and M values are always calculated
+# as Z * Y for the purposes of this example data.
+
+# Contains one non-empty geometry of every geometry type/dimensions combination
+all:
+- POINT (30 10)
+- LINESTRING (30 10, 10 30, 40 40)
+- POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))
+- MULTIPOINT ((30 10))
+- MULTILINESTRING ((30 10, 10 30, 40 40))
+- MULTIPOLYGON (((30 10, 40 40, 20 40, 10 20, 30 10)))
+- GEOMETRYCOLLECTION (POINT (30 10), LINESTRING (30 10, 10 30, 40 40), POLYGON
((30
+ 10, 40 40, 20 40, 10 20, 30 10)), MULTIPOINT ((30 10)), MULTILINESTRING ((30
10,
+ 10 30, 40 40)), MULTIPOLYGON (((30 10, 40 40, 20 40, 10 20, 30 10))))
+- POINT Z (30 10 40)
+- LINESTRING Z (30 10 40, 10 30 40, 40 40 80)
+- POLYGON Z ((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40))
+- MULTIPOINT Z ((30 10 40))
+- MULTILINESTRING Z ((30 10 40, 10 30 40, 40 40 80))
+- MULTIPOLYGON Z (((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40)))
+- GEOMETRYCOLLECTION Z (POINT Z (30 10 40), LINESTRING Z (30 10 40, 10 30 40,
40 40
+ 80), POLYGON Z ((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40)),
MULTIPOINT
+ Z ((30 10 40)), MULTILINESTRING Z ((30 10 40, 10 30 40, 40 40 80)),
MULTIPOLYGON
+ Z (((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40))))
+- POINT M (30 10 300)
+- LINESTRING M (30 10 300, 10 30 300, 40 40 1600)
+- POLYGON M ((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300))
+- MULTIPOINT M ((30 10 300))
+- MULTILINESTRING M ((30 10 300, 10 30 300, 40 40 1600))
+- MULTIPOLYGON M (((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300)))
+- GEOMETRYCOLLECTION M (POINT M (30 10 300), LINESTRING M (30 10 300, 10 30
300, 40
+ 40 1600), POLYGON M ((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10
300)),
+ MULTIPOINT M ((30 10 300)), MULTILINESTRING M ((30 10 300, 10 30 300, 40 40
1600)),
+ MULTIPOLYGON M (((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300))))
+- POINT ZM (30 10 40 300)
+- LINESTRING ZM (30 10 40 300, 10 30 40 300, 40 40 80 1600)
+- POLYGON ZM ((30 10 40 300, 40 40 80 1600, 20 40 60 800, 10 20 30 200, 30 10
40 300))
+- MULTIPOINT ZM ((30 10 40 300))
+- MULTILINESTRING ZM ((30 10 40 300, 10 30 40 300, 40 40 80 1600))
+- MULTIPOLYGON ZM (((30 10 40 300, 40 40 80 1600, 20 40 60 800, 10 20 30 200,
30 10
+ 40 300)))
+- GEOMETRYCOLLECTION ZM (POINT ZM (30 10 40 300), LINESTRING ZM (30 10 40 300,
10
+ 30 40 300, 40 40 80 1600), POLYGON ZM ((30 10 40 300, 40 40 80 1600, 20 40
60 800,
+ 10 20 30 200, 30 10 40 300)), MULTIPOINT ZM ((30 10 40 300)),
MULTILINESTRING ZM
+ ((30 10 40 300, 10 30 40 300, 40 40 80 1600)), MULTIPOLYGON ZM (((30 10 40
300,
+ 40 40 80 1600, 20 40 60 800, 10 20 30 200, 30 10 40 300))))
+
+# Contains one empty geometry of every geometry type/dimensions combination
+empty-geometries:
+- POINT EMPTY
+- LINESTRING EMPTY
+- POLYGON EMPTY
+- MULTIPOINT EMPTY
+- MULTILINESTRING EMPTY
+- MULTIPOLYGON EMPTY
+- GEOMETRYCOLLECTION EMPTY
+- POINT Z EMPTY
+- LINESTRING Z EMPTY
+- POLYGON Z EMPTY
+- MULTIPOINT Z EMPTY
+- MULTILINESTRING Z EMPTY
+- MULTIPOLYGON Z EMPTY
+- GEOMETRYCOLLECTION Z EMPTY
+- POINT M EMPTY
+- LINESTRING M EMPTY
+- POLYGON M EMPTY
+- MULTIPOINT M EMPTY
+- MULTILINESTRING M EMPTY
+- MULTIPOLYGON M EMPTY
+- GEOMETRYCOLLECTION M EMPTY
+- POINT ZM EMPTY
+- LINESTRING ZM EMPTY
+- POLYGON ZM EMPTY
+- MULTIPOINT ZM EMPTY
+- MULTILINESTRING ZM EMPTY
+- MULTIPOLYGON ZM EMPTY
+- GEOMETRYCOLLECTION ZM EMPTY
+
+# Contains only null values
+null-geometries:
+- null
+- null
+- null
+- null
+
+# Individual row groups for each geometry type/dimensions combination.
+# Each contains at least two non-empty items, a null, and an EMPTY.
+point:
+- POINT (30 10)
+- POINT (40 20)
+- null
+- POINT EMPTY
+
+linestring:
+- LINESTRING (30 10, 10 30, 40 40)
+- LINESTRING (40 20, 20 40, 50 50)
+- null
+- LINESTRING EMPTY
+
+polygon:
+- POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))
+- POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))
+- null
+- POLYGON EMPTY
+
+multipoint:
+- MULTIPOINT ((30 10))
+- MULTIPOINT ((10 40), (40 30), (20 20), (30 10))
+- null
+- MULTIPOINT EMPTY
+
+multilinestring:
+- MULTILINESTRING ((30 10, 10 30, 40 40))
+- MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))
+- null
+- MULTILINESTRING EMPTY
+
+multipolygon:
+- MULTIPOLYGON (((30 10, 40 40, 20 40, 10 20, 30 10)))
+- MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15
5)))
+- MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5,
45 20,
+ 20 35), (30 20, 20 15, 20 25, 30 20)))
+- null
+- MULTIPOLYGON EMPTY
+
+geometrycollection:
+- GEOMETRYCOLLECTION (POINT (30 10))
+- GEOMETRYCOLLECTION (LINESTRING (30 10, 10 30, 40 40))
+- GEOMETRYCOLLECTION (POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)))
+- GEOMETRYCOLLECTION (MULTIPOINT ((30 10)))
+- GEOMETRYCOLLECTION (MULTILINESTRING ((30 10, 10 30, 40 40)))
+- GEOMETRYCOLLECTION (MULTIPOLYGON (((30 10, 40 40, 20 40, 10 20, 30 10))))
+- GEOMETRYCOLLECTION (POINT (30 10), LINESTRING (30 10, 10 30, 40 40), POLYGON
((30
+ 10, 40 40, 20 40, 10 20, 30 10)), MULTIPOINT ((30 10)), MULTILINESTRING ((30
10,
+ 10 30, 40 40)), MULTIPOLYGON (((30 10, 40 40, 20 40, 10 20, 30 10))))
+- null
+- GEOMETRYCOLLECTION EMPTY
+
+point-z:
+- POINT Z (30 10 40)
+- POINT Z (40 20 60)
+- null
+- POINT Z EMPTY
+
+linestring-z:
+- LINESTRING Z (30 10 40, 10 30 40, 40 40 80)
+- LINESTRING Z (40 20 60, 20 40 60, 50 50 100)
+- null
+- LINESTRING Z EMPTY
+
+polygon-z:
+- POLYGON Z ((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40))
+- POLYGON Z ((35 10 45, 45 45 90, 15 40 55, 10 20 30, 35 10 45), (20 30 50, 35
35
+ 70, 30 20 50, 20 30 50))
+- null
+- POLYGON Z EMPTY
+
+multipoint-z:
+- MULTIPOINT Z ((30 10 40))
+- MULTIPOINT Z ((10 40 50), (40 30 70), (20 20 40), (30 10 40))
+- null
+- MULTIPOINT Z EMPTY
+
+multilinestring-z:
+- MULTILINESTRING Z ((30 10 40, 10 30 40, 40 40 80))
+- MULTILINESTRING Z ((10 10 20, 20 20 40, 10 40 50), (40 40 80, 30 30 60, 40
20 60,
+ 30 10 40))
+- null
+- MULTILINESTRING Z EMPTY
+
+multipolygon-z:
+- MULTIPOLYGON Z (((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40)))
+- MULTIPOLYGON Z (((30 20 50, 45 40 85, 10 40 50, 30 20 50)), ((15 5 20, 40 10
50,
+ 10 20 30, 5 10 15, 15 5 20)))
+- MULTIPOLYGON Z (((40 40 80, 20 45 65, 45 30 75, 40 40 80)), ((20 35 55, 10
30 40,
+ 10 10 20, 30 5 35, 45 20 65, 20 35 55), (30 20 50, 20 15 35, 20 25 45, 30 20
50)))
+- null
+- MULTIPOLYGON Z EMPTY
+
+geometrycollection-z:
+- GEOMETRYCOLLECTION Z (POINT Z (30 10 40))
+- GEOMETRYCOLLECTION Z (LINESTRING Z (30 10 40, 10 30 40, 40 40 80))
+- GEOMETRYCOLLECTION Z (POLYGON Z ((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30
10
+ 40)))
+- GEOMETRYCOLLECTION Z (MULTIPOINT Z ((30 10 40)))
+- GEOMETRYCOLLECTION Z (MULTILINESTRING Z ((30 10 40, 10 30 40, 40 40 80)))
+- GEOMETRYCOLLECTION Z (MULTIPOLYGON Z (((30 10 40, 40 40 80, 20 40 60, 10 20
30,
+ 30 10 40))))
+- GEOMETRYCOLLECTION Z (POINT Z (30 10 40), LINESTRING Z (30 10 40, 10 30 40,
40 40
+ 80), POLYGON Z ((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40)),
MULTIPOINT
+ Z ((30 10 40)), MULTILINESTRING Z ((30 10 40, 10 30 40, 40 40 80)),
MULTIPOLYGON
+ Z (((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40))))
+- null
+- GEOMETRYCOLLECTION Z EMPTY
+
+point-m:
+- POINT M (30 10 300)
+- POINT M (40 20 800)
+- null
+- POINT M EMPTY
+
+linestring-m:
+- LINESTRING M (30 10 300, 10 30 300, 40 40 1600)
+- LINESTRING M (40 20 800, 20 40 800, 50 50 2500)
+- null
+- LINESTRING M EMPTY
+
+polygon-m:
+- POLYGON M ((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300))
+- POLYGON M ((35 10 350, 45 45 2025, 15 40 600, 10 20 200, 35 10 350), (20 30
600,
+ 35 35 1225, 30 20 600, 20 30 600))
+- null
+- POLYGON M EMPTY
+
+multipoint-m:
+- MULTIPOINT M ((30 10 300))
+- MULTIPOINT M ((10 40 400), (40 30 1200), (20 20 400), (30 10 300))
+- null
+- MULTIPOINT M EMPTY
+
+multilinestring-m:
+- MULTILINESTRING M ((30 10 300, 10 30 300, 40 40 1600))
+- MULTILINESTRING M ((10 10 100, 20 20 400, 10 40 400), (40 40 1600, 30 30
900, 40
+ 20 800, 30 10 300))
+- null
+- MULTILINESTRING M EMPTY
+
+multipolygon-m:
+- MULTIPOLYGON M (((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300)))
+- MULTIPOLYGON M (((30 20 600, 45 40 1800, 10 40 400, 30 20 600)), ((15 5 75,
40 10
+ 400, 10 20 200, 5 10 50, 15 5 75)))
+- MULTIPOLYGON M (((40 40 1600, 20 45 900, 45 30 1350, 40 40 1600)), ((20 35
700,
+ 10 30 300, 10 10 100, 30 5 150, 45 20 900, 20 35 700), (30 20 600, 20 15
300, 20
+ 25 500, 30 20 600)))
+- null
+- MULTIPOLYGON M EMPTY
+
+geometrycollection-m:
+- GEOMETRYCOLLECTION M (POINT M (30 10 300))
+- GEOMETRYCOLLECTION M (LINESTRING M (30 10 300, 10 30 300, 40 40 1600))
+- GEOMETRYCOLLECTION M (POLYGON M ((30 10 300, 40 40 1600, 20 40 800, 10 20
200, 30
+ 10 300)))
+- GEOMETRYCOLLECTION M (MULTIPOINT M ((30 10 300)))
+- GEOMETRYCOLLECTION M (MULTILINESTRING M ((30 10 300, 10 30 300, 40 40 1600)))
+- GEOMETRYCOLLECTION M (MULTIPOLYGON M (((30 10 300, 40 40 1600, 20 40 800, 10
20
+ 200, 30 10 300))))
+- GEOMETRYCOLLECTION M (POINT M (30 10 300), LINESTRING M (30 10 300, 10 30
300, 40
+ 40 1600), POLYGON M ((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10
300)),
+ MULTIPOINT M ((30 10 300)), MULTILINESTRING M ((30 10 300, 10 30 300, 40 40
1600)),
+ MULTIPOLYGON M (((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300))))
+- null
+- GEOMETRYCOLLECTION M EMPTY
+
+point-zm:
+- POINT ZM (30 10 40 300)
+- POINT ZM (40 20 60 800)
+- null
+- POINT ZM EMPTY
+
+linestring-zm:
+- LINESTRING ZM (30 10 40 300, 10 30 40 300, 40 40 80 1600)
+- LINESTRING ZM (40 20 60 800, 20 40 60 800, 50 50 100 2500)
+- null
+- LINESTRING ZM EMPTY
+
+polygon-zm:
+- POLYGON ZM ((30 10 40 300, 40 40 80 1600, 20 40 60 800, 10 20 30 200, 30 10
40 300))
+- POLYGON ZM ((35 10 45 350, 45 45 90 2025, 15 40 55 600, 10 20 30 200, 35 10
45 350),
+ (20 30 50 600, 35 35 70 1225, 30 20 50 600, 20 30 50 600))
+- null
+- POLYGON ZM EMPTY
+multipoint-zm:
+- MULTIPOINT ZM ((30 10 40 300))
+- MULTIPOINT ZM ((10 40 50 400), (40 30 70 1200), (20 20 40 400), (30 10 40
300))
+- null
+- MULTIPOINT ZM EMPTY
+
+multilinestring-zm:
+- MULTILINESTRING ZM ((30 10 40 300, 10 30 40 300, 40 40 80 1600))
+- MULTILINESTRING ZM ((10 10 20 100, 20 20 40 400, 10 40 50 400), (40 40 80
1600,
+ 30 30 60 900, 40 20 60 800, 30 10 40 300))
+- null
+- MULTILINESTRING ZM EMPTY
+
+multipolygon-zm:
+- MULTIPOLYGON ZM (((30 10 40 300, 40 40 80 1600, 20 40 60 800, 10 20 30 200,
30 10
+ 40 300)))
+- MULTIPOLYGON ZM (((30 20 50 600, 45 40 85 1800, 10 40 50 400, 30 20 50
600)), ((15
+ 5 20 75, 40 10 50 400, 10 20 30 200, 5 10 15 50, 15 5 20 75)))
+- MULTIPOLYGON ZM (((40 40 80 1600, 20 45 65 900, 45 30 75 1350, 40 40 80
1600)),
+ ((20 35 55 700, 10 30 40 300, 10 10 20 100, 30 5 35 150, 45 20 65 900, 20 35
55
+ 700), (30 20 50 600, 20 15 35 300, 20 25 45 500, 30 20 50 600)))
+- null
+- MULTIPOLYGON ZM EMPTY
+
+geometrycollection-zm:
+- GEOMETRYCOLLECTION ZM (POINT ZM (30 10 40 300))
+- GEOMETRYCOLLECTION ZM (LINESTRING ZM (30 10 40 300, 10 30 40 300, 40 40 80
1600))
+- GEOMETRYCOLLECTION ZM (POLYGON ZM ((30 10 40 300, 40 40 80 1600, 20 40 60
800, 10
+ 20 30 200, 30 10 40 300)))
+- GEOMETRYCOLLECTION ZM (MULTIPOINT ZM ((30 10 40 300)))
+- GEOMETRYCOLLECTION ZM (MULTILINESTRING ZM ((30 10 40 300, 10 30 40 300, 40
40 80
+ 1600)))
+- GEOMETRYCOLLECTION ZM (MULTIPOLYGON ZM (((30 10 40 300, 40 40 80 1600, 20 40
60
+ 800, 10 20 30 200, 30 10 40 300))))
+- GEOMETRYCOLLECTION ZM (POINT ZM (30 10 40 300), LINESTRING ZM (30 10 40 300,
10
+ 30 40 300, 40 40 80 1600), POLYGON ZM ((30 10 40 300, 40 40 80 1600, 20 40
60 800,
+ 10 20 30 200, 30 10 40 300)), MULTIPOINT ZM ((30 10 40 300)),
MULTILINESTRING ZM
+ ((30 10 40 300, 10 30 40 300, 40 40 80 1600)), MULTIPOLYGON ZM (((30 10 40
300,
+ 40 40 80 1600, 20 40 60 800, 10 20 30 200, 30 10 40 300))))
+- null
+- GEOMETRYCOLLECTION ZM EMPTY