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

Reply via email to