Skip to content

Commit fdebb3d

Browse files
committed
fix Z coord for GeometryCollection
1 parent d17cd18 commit fdebb3d

File tree

3 files changed

+65
-11
lines changed

3 files changed

+65
-11
lines changed

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,38 @@ Note: Minor version `0.X.0` update might break the API, It's recommended to pin
7373
_ = fe.features[0] # __getitem__
7474
```
7575

76+
* make sure `GeometryCollection` are homogeneous for Z coordinates
77+
78+
```python
79+
from geojson_pydantic.geometries import Point, LineString, GeometryCollection
80+
# Before
81+
GeometryCollection(
82+
type="GeometryCollection",
83+
geometries=[
84+
Point(type="Point", coordinates=[0, 0]), # 2D point
85+
LineString(
86+
type="LineString", coordinates=[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)] # 3D LineString
87+
),
88+
],
89+
)
90+
>>> GeometryCollection(bbox=None, type='GeometryCollection', geometries=[Point(bbox=None, type='Point', coordinates=Position3D(longitude=0.0, latitude=0.0, altitude=0.0)), LineString(bbox=None, type='LineString', coordinates=[Position3D(longitude=0.0, latitude=0.0, altitude=0.0), Position3D(longitude=1.0, latitude=1.0, altitude=1.0)])])
91+
92+
# Now
93+
GeometryCollection(
94+
type="GeometryCollection",
95+
geometries=[
96+
Point(type="Point", coordinates=[0, 0]), # 2D point
97+
LineString(
98+
type="LineString", coordinates=[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)] # 3D LineString
99+
),
100+
],
101+
)
102+
>>> ValidationError: 1 validation error for GeometryCollection
103+
geometries
104+
Value error, GeometryCollection cannot have mixed Z dimensionality. [type=value_error, input_value=[Point(bbox=None, type='P...de=1.0, altitude=1.0)])], input_type=list]
105+
For further information visit https://errors.pydantic.dev/2.11/v/value_error
106+
```
107+
76108
## [1.2.0] - 2024-12-19
77109

78110
* drop python 3.8 support

geojson_pydantic/geometries.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,11 @@ def wkt(self) -> str:
278278
z = " Z " if "Z" in geometries else " "
279279
return f"{self.type.upper()}{z}{geometries}"
280280

281+
@property
282+
def has_z(self) -> bool:
283+
"""Checks if any coordinates have a Z value."""
284+
return any(geom.has_z for geom in self.geometries)
285+
281286
@field_validator("geometries")
282287
def check_geometries(cls, geometries: List) -> List:
283288
"""Add warnings for conditions the spec does not explicitly forbid."""
@@ -299,6 +304,9 @@ def check_geometries(cls, geometries: List) -> List:
299304
stacklevel=1,
300305
)
301306

307+
if len({geom.has_z for geom in geometries}) == 2:
308+
raise ValueError("GeometryCollection cannot have mixed Z dimensionality.")
309+
302310
return geometries
303311

304312

tests/test_geometries.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -534,12 +534,14 @@ def test_getitem_geometry_collection(coordinates):
534534

535535
def test_wkt_mixed_geometry_collection():
536536
point = Point(type="Point", coordinates=(0.0, 0.0, 0.0))
537-
line_string = LineString(type="LineString", coordinates=[(0.0, 0.0), (1.0, 1.0)])
537+
line_string = LineString(
538+
type="LineString", coordinates=[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]
539+
)
538540
assert (
539541
GeometryCollection(
540542
type="GeometryCollection", geometries=[point, line_string]
541543
).wkt
542-
== "GEOMETRYCOLLECTION Z (POINT Z (0.0 0.0 0.0), LINESTRING (0.0 0.0, 1.0 1.0))"
544+
== "GEOMETRYCOLLECTION Z (POINT Z (0.0 0.0 0.0), LINESTRING Z (0.0 0.0 0.0, 1.0 1.0 1.0))"
543545
)
544546

545547

@@ -552,6 +554,9 @@ def test_wkt_empty_geometry_collection():
552554

553555
def test_geometry_collection_warnings():
554556
point = Point(type="Point", coordinates=(0.0, 0.0, 0.0))
557+
line_string_z = LineString(
558+
type="LineString", coordinates=[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]
559+
)
555560
line_string = LineString(type="LineString", coordinates=[(0.0, 0.0), (1.0, 1.0)])
556561

557562
# one geometry
@@ -575,18 +580,15 @@ def test_geometry_collection_warnings():
575580
type="GeometryCollection",
576581
geometries=[
577582
GeometryCollection(
578-
type="GeometryCollection", geometries=[point, line_string]
583+
type="GeometryCollection", geometries=[point, line_string_z]
579584
),
580585
point,
581586
],
582587
)
583588

584-
# homogeneous geometry
585-
with pytest.warns(
586-
UserWarning,
587-
match="GeometryCollection should not be used for homogeneous collections.",
588-
):
589-
GeometryCollection(type="GeometryCollection", geometries=[point, point])
589+
# homogeneous (Z) geometry
590+
with pytest.raises(ValidationError):
591+
GeometryCollection(type="GeometryCollection", geometries=[point, line_string])
590592

591593

592594
def test_polygon_from_bounds():
@@ -776,9 +778,9 @@ def test_wkt_empty_geometrycollection():
776778
"MULTIPOLYGON Z (((0.0 0.0 0.0, 1.0 1.0 0.0, 2.0 2.0 0.0, 3.0 3.0 0.0, 0.0 0.0 0.0)), ((1.0 1.0 0.0, 2.0 2.0 0.0, 3.0 3.0 0.0, 4.0 4.0 0.0, 1.0 1.0 0.0)))",
777779
"MULTIPOLYGON EMPTY",
778780
"GEOMETRYCOLLECTION (POINT (0.0 0.0))",
781+
"GEOMETRYCOLLECTION (POLYGON EMPTY, MULTIPOLYGON (((0.0 0.0, 1.0 1.0, 2.0 2.0, 3.0 3.0, 0.0 0.0))))",
779782
"GEOMETRYCOLLECTION (POINT (0.0 0.0), MULTIPOINT ((0.0 0.0), (1.0 1.0)))",
780-
"GEOMETRYCOLLECTION Z (POLYGON EMPTY, MULTIPOLYGON Z (((0.0 0.0 0.0, 1.0 1.0 0.0, 2.0 2.0 0.0, 3.0 3.0 0.0, 0.0 0.0 0.0))))",
781-
"GEOMETRYCOLLECTION Z (LINESTRING Z (0.0 0.0 0.0, 1.0 1.0 1.0, 2.0 2.0 2.0), MULTILINESTRING ((0.0 0.0, 1.0 1.0), (1.0 1.0, 2.0 2.0)))",
783+
"GEOMETRYCOLLECTION Z (POLYGON Z ((0.0 0.0 0.0, 1.0 1.0 0.0, 2.0 2.0 0.0, 3.0 3.0 0.0, 0.0 0.0 0.0)), MULTIPOLYGON Z (((0.0 0.0 0.0, 1.0 1.0 0.0, 2.0 2.0 0.0, 3.0 3.0 0.0, 0.0 0.0 0.0))))",
782784
"GEOMETRYCOLLECTION EMPTY",
783785
),
784786
)
@@ -843,10 +845,22 @@ def test_geometry_collection_serializer():
843845
LineString(type="LineString", coordinates=[(0.0, 0.0), (1.0, 1.0)]),
844846
],
845847
)
848+
assert not geom.has_z
846849
# bbox will be in the Dict
847850
assert "bbox" in geom.model_dump()
848851
assert "bbox" in geom.model_dump()["geometries"][0]
849852

853+
geom = GeometryCollection(
854+
type="GeometryCollection",
855+
geometries=[
856+
Point(type="Point", coordinates=[0, 0, 0]),
857+
LineString(
858+
type="LineString", coordinates=[(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]
859+
),
860+
],
861+
)
862+
assert geom.has_z
863+
850864
# bbox should not be in any Geometry nor at the top level
851865
geom_ser = json.loads(geom.model_dump_json())
852866
assert "bbox" not in geom_ser

0 commit comments

Comments
 (0)