Skip to content

Commit

Permalink
Merge pull request #480 from ungarj/geometry_creation_fix
Browse files Browse the repository at this point in the history
add fs kwarg to makedirs(); extend IndexedFeatures capability to find object geometry
  • Loading branch information
ungarj committed Sep 12, 2022
2 parents 23e95c6 + 5dd30c8 commit c83fbb6
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 27 deletions.
4 changes: 4 additions & 0 deletions mapchete/errors.py
Expand Up @@ -55,3 +55,7 @@ class NoTaskGeometry(TypeError):

class ReprojectionFailed(RuntimeError):
"""Raised when geometry cannot be reprojected."""


class NoGeoError(AttributeError):
"""Raised when object does not contain geographic information."""
4 changes: 2 additions & 2 deletions mapchete/io/_path.py
Expand Up @@ -90,15 +90,15 @@ def relative_path(path=None, base_dir=None):
return os.path.relpath(path, base_dir)


def makedirs(path):
def makedirs(path, fs=None):
"""
Silently create all subdirectories of path if path is local.
Parameters
----------
path : path
"""
fs = fs_from_path(path)
fs = fs or fs_from_path(path)
# create parent directories on local filesystems
if fs.protocol == "file":
fs.makedirs(path, exist_ok=True)
Expand Down
54 changes: 32 additions & 22 deletions mapchete/io/vector.py
Expand Up @@ -12,10 +12,11 @@
from shapely.geometry import box, mapping, shape
from shapely.errors import TopologicalError
from tilematrix import clip_geometry_to_srs_bounds
from tilematrix._funcs import Bounds
from itertools import chain
import warnings

from mapchete.errors import GeometryTypeError, MapcheteIOError
from mapchete.errors import NoGeoError, MapcheteIOError
from mapchete.io._misc import MAPCHETE_IO_RETRY_SETTINGS
from mapchete.io._path import fs_from_path, path_exists, makedirs, copy
from mapchete.io._geometry_operations import (
Expand Down Expand Up @@ -405,9 +406,13 @@ def __init__(self, features, index="rtree", allow_non_geo_objects=False, crs=Non
else:
id_ = self._get_feature_id(feature)
self._items[id_] = feature
bounds = self._get_feature_bounds(
feature, allow_non_geo_objects=allow_non_geo_objects
)
try:
bounds = object_bounds(feature)
except NoGeoError:
if allow_non_geo_objects:
bounds = None
else:
raise
if bounds is None:
self._non_geo_items.add(id_)
else:
Expand Down Expand Up @@ -481,24 +486,29 @@ def _get_feature_id(self, feature):
except TypeError:
raise TypeError("features need to have an id or have to be hashable")

def _get_feature_bounds(self, feature, allow_non_geo_objects=False):
try:
if hasattr(feature, "bounds"):
return validate_bounds(feature.bounds)
elif hasattr(feature, "__geo_interface__"):
return validate_bounds(shape(feature).bounds)
elif feature.get("bounds"):
return validate_bounds(feature["bounds"])
elif feature.get("geometry"):
return validate_bounds(to_shape(feature["geometry"]).bounds)
else:
raise TypeError("no bounds")
except Exception as exc:
if allow_non_geo_objects:
return None
else:
logger.exception(exc)
raise TypeError(f"cannot determine bounds from feature: {feature}")

def object_bounds(obj) -> Bounds:
"""
Determine geographic bounds from object if available.
"""
try:
if hasattr(obj, "bounds"):
return validate_bounds(obj.bounds)
elif hasattr(obj, "__geo_interface__"):
return validate_bounds(shape(obj).bounds)
elif hasattr(obj, "geometry"):
return validate_bounds(to_shape(obj.geometry).bounds)
elif hasattr(obj, "bbox"):
return validate_bounds(obj.bbox)
elif obj.get("bounds"):
return validate_bounds(obj["bounds"])
elif obj.get("geometry"):
return validate_bounds(to_shape(obj["geometry"]).bounds)
else:
raise TypeError("no bounds")
except Exception as exc:
logger.exception(exc)
raise NoGeoError(f"cannot determine bounds from object: {obj}") from exc


def convert_vector(inp, out, overwrite=False, exists_ok=True, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion mapchete/validate.py
Expand Up @@ -74,7 +74,7 @@ def validate_zoom(zoom):
return zoom


def validate_bounds(bounds):
def validate_bounds(bounds) -> Bounds:
"""
Return validated bounds.
Expand Down
74 changes: 72 additions & 2 deletions test/test_io_vector.py
Expand Up @@ -8,7 +8,12 @@
from shapely.geometry import shape, box, Polygon, MultiPolygon, LineString, mapping

from mapchete.config import MapcheteConfig
from mapchete.errors import GeometryTypeError, MapcheteIOError, ReprojectionFailed
from mapchete.errors import (
GeometryTypeError,
MapcheteIOError,
NoGeoError,
ReprojectionFailed,
)
from mapchete.io.vector import (
read_vector_window,
reproject_geometry,
Expand All @@ -19,6 +24,7 @@
convert_vector,
IndexedFeatures,
bounds_intersect,
object_bounds,
)
from mapchete.tile import BufferedTilePyramid

Expand Down Expand Up @@ -381,7 +387,7 @@ class Foo:

# no bounds
feature = {"properties": {"foo": "bar"}, "id": 0}
with pytest.raises(TypeError):
with pytest.raises(NoGeoError):
IndexedFeatures([feature])


Expand Down Expand Up @@ -502,3 +508,67 @@ def test_reproject_from_crs_wkt():
dst_crs = "EPSG:4326"
with pytest.raises(ReprojectionFailed):
reproject_geometry(geom, src_crs, dst_crs).is_valid


def test_object_bounds_attr_bounds():
# if hasattr(obj, "bounds"):
# return validate_bounds(obj.bounds)
control = (0, 1, 2, 3)

class Foo:
bounds = control

assert object_bounds(Foo()) == (0, 1, 2, 3)


def test_object_bounds_geo_interface():
# elif hasattr(obj, "__geo_interface__"):
# return validate_bounds(shape(obj).bounds)
control = (0, 1, 2, 3)

class Foo:
__geo_interface__ = mapping(box(*control))

assert object_bounds(Foo()) == (0, 1, 2, 3)


def test_object_bounds_attr_geometry():
# elif hasattr(obj, "geometry"):
# return validate_bounds(to_shape(obj.geometry).bounds)
control = (0, 1, 2, 3)

class Foo:
geometry = mapping(box(*control))

assert object_bounds(Foo()) == (0, 1, 2, 3)


def test_object_bounds_attr_bbox():
# elif hasattr(obj, "bbox"):
# return validate_bounds(obj.bbox)
control = (0, 1, 2, 3)

class Foo:
bbox = control

assert object_bounds(Foo()) == (0, 1, 2, 3)


def test_object_bounds_key_bbox():
# elif obj.get("bounds"):
# return validate_bounds(obj["bounds"])
control = (0, 1, 2, 3)

foo = {"bounds": control}

assert object_bounds(foo) == (0, 1, 2, 3)


def test_object_bounds_key_geometry():
# elif obj.get("geometry"):
# return validate_bounds(to_shape(obj["geometry"]).bounds)
control = (0, 1, 2, 3)

foo = {"geometry": mapping(box(*control))}

assert object_bounds(foo) == (0, 1, 2, 3)

0 comments on commit c83fbb6

Please sign in to comment.