Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for nested GeometryCollection with warnings. #111

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add support for nested GeometryCollection with warnings.
Closes #110.
  • Loading branch information
eseglem committed Mar 14, 2023
commit c98fbe0bad504b8465f4932b09d8727e69c07da5
26 changes: 23 additions & 3 deletions geojson_pydantic/geometries.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""pydantic models for GeoJSON Geometry objects."""
from __future__ import annotations

import abc
import warnings
from typing import Any, Iterator, List, Literal, Optional, Protocol, Union

from pydantic import BaseModel, Field, ValidationError, validator
Expand Down Expand Up @@ -241,18 +244,18 @@ class GeometryCollection(BaseModel, GeoInterfaceMixin):
"""GeometryCollection Model"""

type: Literal["GeometryCollection"]
geometries: List[Geometry]
geometries: List[Union[Geometry, GeometryCollection]]
bbox: Optional[BBox] = None

def __iter__(self) -> Iterator[Geometry]: # type: ignore [override]
def __iter__(self) -> Iterator[Union[Geometry, GeometryCollection]]: # type: ignore [override]
"""iterate over geometries"""
return iter(self.geometries)

def __len__(self) -> int:
"""return geometries length"""
return len(self.geometries)

def __getitem__(self, index: int) -> Geometry:
def __getitem__(self, index: int) -> Union[Geometry, GeometryCollection]:
"""get geometry at a given index"""
return self.geometries[index]

Expand All @@ -274,6 +277,23 @@ def wkt(self) -> str:
z = " Z " if "Z" in geometries else " "
return f"{self.type.upper()}{z}{geometries}"

@validator("geometries")
def check_geometries(cls, geometries: List) -> List:
"""Add warnings for conditions the spec does not explicitly forbid."""
if len(geometries) == 1:
warnings.warn(
"GeometryCollection should not be used for single geometries."
)
if any(geom.type == "GeometryCollection" for geom in geometries):
warnings.warn(
"GeometryCollection should not be used for nested GeometryCollections."
)
if len(set(geom.type for geom in geometries)) == 1:
warnings.warn(
"GeometryCollection should not be used for homogeneous collections."
)
return geometries


def parse_geometry_obj(obj: Any) -> Geometry:
"""
Expand Down
28 changes: 17 additions & 11 deletions tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,29 @@ class GenericProperties(BaseModel):
"size": randint(0, 1000),
}

coordinates = [
[
[13.38272, 52.46385],
[13.42786, 52.46385],
[13.42786, 52.48445],
[13.38272, 52.48445],
[13.38272, 52.46385],
]
]

polygon: Dict[str, Any] = {
"type": "Polygon",
"coordinates": [
[
[13.38272, 52.46385],
[13.42786, 52.46385],
[13.42786, 52.48445],
[13.38272, 52.48445],
[13.38272, 52.46385],
]
],
"coordinates": coordinates,
}

multipolygon: Dict[str, Any] = {
"type": "MultiPolygon",
"coordinates": [coordinates],
}

geom_collection: Dict[str, Any] = {
"type": "GeometryCollection",
"geometries": [polygon, polygon],
"geometries": [polygon, multipolygon],
}

test_feature: Dict[str, Any] = {
Expand Down Expand Up @@ -131,7 +138,6 @@ def test_generic_geometry_collection():
assert feature.properties.id == test_feature_geometry_collection["properties"]["id"]
assert type(feature.geometry) == GeometryCollection
assert feature.geometry.wkt.startswith("GEOMETRYCOLLECTION (POLYGON ")
assert feature.geometry.geometries[0].wkt == feature.geometry.geometries[1].wkt
assert type(feature.properties) == GenericProperties
assert hasattr(feature.properties, "id")

Expand Down
33 changes: 21 additions & 12 deletions tests/test_geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,33 +452,42 @@ def test_parse_geometry_obj_invalid_point():
def test_geometry_collection_iteration(coordinates):
"""test if geometry collection is iterable"""
polygon = Polygon(type="Polygon", coordinates=coordinates)
gc = GeometryCollection(type="GeometryCollection", geometries=[polygon, polygon])
multipolygon = MultiPolygon(type="MultiPolygon", coordinates=[coordinates])
gc = GeometryCollection(
type="GeometryCollection", geometries=[polygon, multipolygon]
)
assert hasattr(gc, "__geo_interface__")
assert_wkt_equivalence(gc)
iter(gc)


@pytest.mark.parametrize(
"polygon", [[[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (1.0, 2.0)]]]
"coordinates", [[[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (1.0, 2.0)]]]
)
def test_len_geometry_collection(polygon):
def test_len_geometry_collection(coordinates):
"""test if GeometryCollection return self leng"""
polygon = Polygon(type="Polygon", coordinates=polygon)
gc = GeometryCollection(type="GeometryCollection", geometries=[polygon, polygon])
polygon = Polygon(type="Polygon", coordinates=coordinates)
multipolygon = MultiPolygon(type="MultiPolygon", coordinates=[coordinates])
gc = GeometryCollection(
type="GeometryCollection", geometries=[polygon, multipolygon]
)
assert_wkt_equivalence(gc)
assert len(gc) == 2


@pytest.mark.parametrize(
"polygon", [[[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (1.0, 2.0)]]]
"coordinates", [[[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (1.0, 2.0)]]]
)
def test_getitem_geometry_collection(polygon):
"""test if GeometryCollection return self leng"""
polygon = Polygon(type="Polygon", coordinates=polygon)
gc = GeometryCollection(type="GeometryCollection", geometries=[polygon, polygon])
def test_getitem_geometry_collection(coordinates):
"""test if GeometryCollection is subscriptable"""
polygon = Polygon(type="Polygon", coordinates=coordinates)
multipolygon = MultiPolygon(type="MultiPolygon", coordinates=[coordinates])
gc = GeometryCollection(
type="GeometryCollection", geometries=[polygon, multipolygon]
)
assert_wkt_equivalence(gc)
item = gc[0]
assert item == gc[0]
assert polygon == gc[0]
assert multipolygon == gc[1]


def test_wkt_mixed_geometry_collection():
Expand Down