Skip to content

Commit

Permalink
Refactor geo_interface to use standard mixin. Fixes #96.
Browse files Browse the repository at this point in the history
  • Loading branch information
eseglem committed Feb 10, 2023
1 parent d08b037 commit ff0135a
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 63 deletions.
43 changes: 3 additions & 40 deletions geojson_pydantic/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
from pydantic import BaseModel, Field, validator
from pydantic.generics import GenericModel

from geojson_pydantic.geo_interface import GeoInterfaceMixin
from geojson_pydantic.geometries import Geometry, GeometryCollection
from geojson_pydantic.types import BBox

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


class Feature(GenericModel, Generic[Geom, Props]):
class Feature(GenericModel, Generic[Geom, Props], GeoInterfaceMixin):
"""Feature Model"""

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

return geometry

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

ref: https://gist.github.com/sgillies/2217756#__geo_interface
"""
geo: Dict[str, Any] = {
"type": self.type,
"geometry": self.geometry.__geo_interface__
if self.geometry is not None
else None,
"properties": self.properties,
}

if self.bbox:
geo["bbox"] = self.bbox

if self.id:
geo["id"] = self.id

return geo


class FeatureCollection(GenericModel, Generic[Geom, Props]):
class FeatureCollection(GenericModel, Generic[Geom, Props], GeoInterfaceMixin):
"""FeatureCollection Model"""

type: Literal["FeatureCollection"]
Expand All @@ -75,19 +54,3 @@ def __len__(self) -> int:
def __getitem__(self, index: int) -> Feature:
"""get feature at a given index"""
return self.features[index]

@property
def __geo_interface__(self) -> Dict[str, Any]:
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
ref: https://gist.github.com/sgillies/2217756#__geo_interface
"""
features: List[Dict[str, Any]] = []
for feat in self.features:
features.append(feat.__geo_interface__)

geo: Dict[str, Any] = {"type": self.type, "features": features}
if self.bbox:
geo["bbox"] = self.bbox

return geo
23 changes: 23 additions & 0 deletions geojson_pydantic/geo_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Mixin for __geo_interface__ on GeoJSON objects."""

from typing import Any, Dict, Protocol


class DictProtocol(Protocol):
"""Protocol for use as the type of self in the mixin."""

def dict(self, *, exclude_unset: bool, **args: Any) -> Dict[str, Any]:
"""Define a dict function so the mixin knows it exists."""
...


class GeoInterfaceMixin:
"""Mixin for __geo_interface__ on GeoJSON objects."""

@property
def __geo_interface__(self: DictProtocol) -> Dict[str, Any]:
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
ref: https://gist.github.com/sgillies/2217756#__geo_interface
"""
return self.dict(exclude_unset=True)
27 changes: 4 additions & 23 deletions geojson_pydantic/geometries.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""pydantic models for GeoJSON Geometry objects."""

import abc
from typing import Any, Dict, Iterator, List, Literal, Union
from typing import Any, Iterator, List, Literal, Union

from pydantic import BaseModel, Field, ValidationError, validator
from pydantic.error_wrappers import ErrorWrapper
from typing_extensions import Annotated

from geojson_pydantic.geo_interface import GeoInterfaceMixin
from geojson_pydantic.types import (
LinearRing,
LineStringCoords,
Expand Down Expand Up @@ -49,20 +50,12 @@ def _lines_has_z(lines: List[List[Position]]) -> bool:
)


class _GeometryBase(BaseModel, abc.ABC):
class _GeometryBase(BaseModel, abc.ABC, GeoInterfaceMixin):
"""Base class for geometry models"""

type: str
coordinates: Any

@property
def __geo_interface__(self) -> Dict[str, Any]:
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
ref: https://gist.github.com/sgillies/2217756#__geo_interface
"""
return {"type": self.type, "coordinates": self.coordinates}

@property
@abc.abstractmethod
def has_z(self) -> bool:
Expand Down Expand Up @@ -252,7 +245,7 @@ def check_closure(cls, coordinates: List) -> List:
]


class GeometryCollection(BaseModel):
class GeometryCollection(BaseModel, GeoInterfaceMixin):
"""GeometryCollection Model"""

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

@property
def __geo_interface__(self) -> Dict[str, Any]:
"""GeoJSON-like protocol for geo-spatial (GIS) vector data.
ref: https://gist.github.com/sgillies/2217756#__geo_interface
"""
geometries: List[Dict[str, Any]] = []
for geom in self.geometries:
geometries.append(geom.__geo_interface__)

return {"type": self.type, "geometries": self.geometries}


def parse_geometry_obj(obj: Any) -> Geometry:
"""
Expand Down

0 comments on commit ff0135a

Please sign in to comment.