Skip to content

Commit 45e758f

Browse files
Merge pull request #105 from eseglem/feature/geo-interface-mixin
Refactor geo_interface to use a standardized mixin.
2 parents d08b037 + d397a6e commit 45e758f

File tree

3 files changed

+30
-63
lines changed

3 files changed

+30
-63
lines changed

geojson_pydantic/features.py

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
from pydantic import BaseModel, Field, validator
66
from pydantic.generics import GenericModel
77

8+
from geojson_pydantic.geo_interface import GeoInterfaceMixin
89
from geojson_pydantic.geometries import Geometry, GeometryCollection
910
from geojson_pydantic.types import BBox
1011

1112
Props = TypeVar("Props", bound=Union[Dict[str, Any], BaseModel])
1213
Geom = TypeVar("Geom", bound=Union[Geometry, GeometryCollection])
1314

1415

15-
class Feature(GenericModel, Generic[Geom, Props]):
16+
class Feature(GenericModel, Generic[Geom, Props], GeoInterfaceMixin):
1617
"""Feature Model"""
1718

1819
type: Literal["Feature"]
@@ -34,30 +35,8 @@ def set_geometry(cls, geometry: Any) -> Any:
3435

3536
return geometry
3637

37-
@property
38-
def __geo_interface__(self) -> Dict[str, Any]:
39-
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
4038

41-
ref: https://gist.github.com/sgillies/2217756#__geo_interface
42-
"""
43-
geo: Dict[str, Any] = {
44-
"type": self.type,
45-
"geometry": self.geometry.__geo_interface__
46-
if self.geometry is not None
47-
else None,
48-
"properties": self.properties,
49-
}
50-
51-
if self.bbox:
52-
geo["bbox"] = self.bbox
53-
54-
if self.id:
55-
geo["id"] = self.id
56-
57-
return geo
58-
59-
60-
class FeatureCollection(GenericModel, Generic[Geom, Props]):
39+
class FeatureCollection(GenericModel, Generic[Geom, Props], GeoInterfaceMixin):
6140
"""FeatureCollection Model"""
6241

6342
type: Literal["FeatureCollection"]
@@ -75,19 +54,3 @@ def __len__(self) -> int:
7554
def __getitem__(self, index: int) -> Feature:
7655
"""get feature at a given index"""
7756
return self.features[index]
78-
79-
@property
80-
def __geo_interface__(self) -> Dict[str, Any]:
81-
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
82-
83-
ref: https://gist.github.com/sgillies/2217756#__geo_interface
84-
"""
85-
features: List[Dict[str, Any]] = []
86-
for feat in self.features:
87-
features.append(feat.__geo_interface__)
88-
89-
geo: Dict[str, Any] = {"type": self.type, "features": features}
90-
if self.bbox:
91-
geo["bbox"] = self.bbox
92-
93-
return geo

geojson_pydantic/geo_interface.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Mixin for __geo_interface__ on GeoJSON objects."""
2+
3+
from typing import Any, Dict, Protocol
4+
5+
6+
class _DictProtocol(Protocol):
7+
"""Protocol for use as the type of self in the mixin."""
8+
9+
def dict(self, *, exclude_unset: bool, **args: Any) -> Dict[str, Any]:
10+
"""Define a dict function so the mixin knows it exists."""
11+
...
12+
13+
14+
class GeoInterfaceMixin:
15+
"""Mixin for __geo_interface__ on GeoJSON objects."""
16+
17+
@property
18+
def __geo_interface__(self: _DictProtocol) -> Dict[str, Any]:
19+
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
20+
21+
ref: https://gist.github.com/sgillies/2217756#__geo_interface
22+
"""
23+
return self.dict(exclude_unset=True)

geojson_pydantic/geometries.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
"""pydantic models for GeoJSON Geometry objects."""
22

33
import abc
4-
from typing import Any, Dict, Iterator, List, Literal, Union
4+
from typing import Any, Iterator, List, Literal, Union
55

66
from pydantic import BaseModel, Field, ValidationError, validator
77
from pydantic.error_wrappers import ErrorWrapper
88
from typing_extensions import Annotated
99

10+
from geojson_pydantic.geo_interface import GeoInterfaceMixin
1011
from geojson_pydantic.types import (
1112
LinearRing,
1213
LineStringCoords,
@@ -49,20 +50,12 @@ def _lines_has_z(lines: List[List[Position]]) -> bool:
4950
)
5051

5152

52-
class _GeometryBase(BaseModel, abc.ABC):
53+
class _GeometryBase(BaseModel, abc.ABC, GeoInterfaceMixin):
5354
"""Base class for geometry models"""
5455

5556
type: str
5657
coordinates: Any
5758

58-
@property
59-
def __geo_interface__(self) -> Dict[str, Any]:
60-
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
61-
62-
ref: https://gist.github.com/sgillies/2217756#__geo_interface
63-
"""
64-
return {"type": self.type, "coordinates": self.coordinates}
65-
6659
@property
6760
@abc.abstractmethod
6861
def has_z(self) -> bool:
@@ -252,7 +245,7 @@ def check_closure(cls, coordinates: List) -> List:
252245
]
253246

254247

255-
class GeometryCollection(BaseModel):
248+
class GeometryCollection(BaseModel, GeoInterfaceMixin):
256249
"""GeometryCollection Model"""
257250

258251
type: Literal["GeometryCollection"]
@@ -289,18 +282,6 @@ def wkt(self) -> str:
289282
+ (f"({self._wkt_coordinates})" if self._wkt_coordinates else "EMPTY")
290283
)
291284

292-
@property
293-
def __geo_interface__(self) -> Dict[str, Any]:
294-
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
295-
296-
ref: https://gist.github.com/sgillies/2217756#__geo_interface
297-
"""
298-
geometries: List[Dict[str, Any]] = []
299-
for geom in self.geometries:
300-
geometries.append(geom.__geo_interface__)
301-
302-
return {"type": self.type, "geometries": self.geometries}
303-
304285

305286
def parse_geometry_obj(obj: Any) -> Geometry:
306287
"""

0 commit comments

Comments
 (0)