Skip to content

Commit

Permalink
Refactor geo_interface to use standard mixin. Fixes developmentseed#96.
Browse files Browse the repository at this point in the history
  • Loading branch information
eseglem committed Feb 6, 2023
1 parent 49af995 commit fb0d2a7
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,11 +1,12 @@
"""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, ValidationError, validator
from pydantic.error_wrappers import ErrorWrapper

from geojson_pydantic.geo_interface import GeoInterfaceMixin
from geojson_pydantic.types import (
LinearRing,
LineStringCoords,
Expand All @@ -32,20 +33,12 @@ def _lines_wtk_coordinates(lines: List[List[Position]]) -> str:
return ", ".join(f"({_position_list_wkt_coordinates(line)})" for line in lines)


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 _wkt_coordinates(self) -> str:
Expand Down Expand Up @@ -211,7 +204,7 @@ def _wkt_coordinates(self) -> str:
Geometry = Union[Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon]


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

type: Literal["GeometryCollection"]
Expand Down Expand Up @@ -244,18 +237,6 @@ def wkt(self) -> str:
"""Return the Well Known Text representation."""
return f"{self._wkt_type} ({self._wkt_coordinates})"

@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 fb0d2a7

Please sign in to comment.