import numpy as np
from ..utils import import_optional_dependency
from ..utils.geometry import Collection, Shape
geojson = import_optional_dependency("geojson", errors="silent")
geojson_classes = {}
if geojson is not None:
geojson_classes = {
"polygon": geojson.Polygon,
"multipolygon": geojson.MultiPolygon,
"point": geojson.Point,
"multipoint": geojson.MultiPoint,
"linestring": geojson.LineString,
"multilinestring": geojson.MultiLineString,
}
shape_types = {
"multipolygon": "MultiPolygon",
"polygon": "Polygon",
"point": "Point",
"multipoint": "MultiPoint",
"linestring": "LineString",
"multilinestring": "MultiLineString",
}
[docs]class GeoSpatialUtil:
"""
Geospatial utils are a unifying method to provide conversion between
commonly used geospatial input types
Parameters
----------
obj : geospatial object
obj can accept any of the following objects:
shapefile.Shape object
flopy.utils.geometry objects
list of vertices
geojson geometry objects
shapely.geometry objects
shapetype : str
shapetype is required when a list of vertices is supplied for obj
"""
def __init__(self, obj, shapetype=None):
self.__shapefile = import_optional_dependency(
"shapefile", errors="silent"
)
self.__obj = obj
self.__geo_interface = {}
self._geojson = None
self._shapely = None
self._shape = None
self._flopy_geometry = None
self._points = None
self.__shapetype = None
if shapetype is not None:
shapetype = shapetype.lower()
if isinstance(obj, (Shape, Collection)):
geo_interface = obj.__geo_interface__
if geo_interface["type"] == "GeometryCollection":
raise TypeError("GeometryCollections are not supported")
self.__geo_interface = geo_interface
elif isinstance(obj, (np.ndarray, list, tuple)):
if shapetype is None or shapetype not in shape_types:
err = "shapetype must be one of the following: " + " , ".join(
geojson_classes.keys()
)
raise AssertionError(err)
self.__geo_interface = {
"type": shape_types[shapetype],
"coordinates": list(obj),
}
if self.__shapefile is not None:
if isinstance(obj, self.__shapefile.Shape):
self.__geo_interface = self.__obj.__geo_interface__
if geojson is not None:
if isinstance(obj, geojson.Feature):
self.__geo_interface = {
"type": obj.geometry.type,
"coordinates": obj.geometry.coordinates,
}
elif isinstance(
obj,
(
geojson.Point,
geojson.MultiPoint,
geojson.Polygon,
geojson.MultiPolygon,
geojson.LineString,
geojson.MultiLineString,
),
):
self.__geo_interface = {
"type": obj.type,
"coordinates": obj.coordinates,
}
shapely_geo = import_optional_dependency("shapely.geometry")
if shapely_geo is not None:
if isinstance(
obj,
(
shapely_geo.Point,
shapely_geo.MultiPoint,
shapely_geo.Polygon,
shapely_geo.MultiPolygon,
shapely_geo.LineString,
shapely_geo.MultiLineString,
),
):
self.__geo_interface = obj.__geo_interface__
if not self.__geo_interface:
raise AssertionError(
f"Reader is not installed for collection type: {type(obj)}"
)
@property
def __geo_interface__(self):
"""
Geojson standard representation of a geometry
Returns
-------
dict
"""
return self.__geo_interface
@property
def shapetype(self):
"""
Shapetype string for a geometry
Returns
-------
str
"""
if self.__shapetype is None:
self.__shapetype = self.__geo_interface["type"]
return self.__shapetype
@property
def points(self):
"""
Returns a list of vertices to the user
Returns
-------
list
"""
if self._points is None:
self._points = self.__geo_interface["coordinates"]
return self._points
@property
def shapely(self):
"""
Returns a shapely.geometry object to the user
Returns
-------
shapely.geometry.<shape>
"""
shapely_geo = import_optional_dependency("shapely.geometry")
self._shapely = shapely_geo.shape(self.__geo_interface)
return self._shapely
@property
def geojson(self):
"""
Returns a geojson object to the user
Returns
-------
geojson.<shape>
"""
import_optional_dependency("geojson")
cls = geojson_classes[self.__geo_interface["type"].lower()]
self._geojson = cls(self.__geo_interface["coordinates"])
return self._geojson
@property
def shape(self):
"""
Returns a shapefile.Shape object to the user
Returns
-------
shapefile.shape
"""
if self.__shapefile is not None:
if self._shape is None:
self._shape = self.__shapefile.Shape._from_geojson(
self.__geo_interface
)
return self._shape
@property
def flopy_geometry(self):
"""
Returns a flopy geometry object to the user
Returns
-------
flopy.utils.geometry.<Shape>
"""
if self._flopy_geometry is None:
self._flopy_geometry = Shape.from_geojson(self.__geo_interface)
return self._flopy_geometry
[docs]class GeoSpatialCollection:
"""
The GeoSpatialCollection class allows a user to convert between
Collection objects from common geospatial libraries.
Parameters
----------
obj : collection object
obj can accept the following types
str : shapefile name
shapefile.Reader object
list of [shapefile.Shape, shapefile.Shape,]
shapefile.Shapes object
flopy.utils.geometry.Collection object
list of [flopy.utils.geometry, ...] objects
geojson.GeometryCollection object
geojson.FeatureCollection object
shapely.GeometryCollection object
list of [[vertices], ...]
shapetype : list
optional list of shapetypes that is required when vertices are
supplied to the class as the obj parameter
"""
def __init__(self, obj, shapetype=None):
self.__shapefile = import_optional_dependency(
"shapefile", errors="silent"
)
self.__obj = obj
self.__collection = []
self._geojson = None
self._shapely = None
self._shape = None
self._flopy_geometry = None
self._points = None
self.__shapetype = None
if isinstance(obj, Collection):
for shape in obj:
self.__collection.append(GeoSpatialUtil(shape))
elif isinstance(obj, (np.ndarray, list, tuple)):
if isinstance(obj[0], (Shape, Collection)):
for shape in obj:
self.__collection.append(GeoSpatialUtil(shape))
elif self.__shapefile is not None:
if isinstance(obj[0], self.__shapefile.Shape):
for shape in obj:
self.__collection.append(GeoSpatialUtil(shape))
if not self.__collection:
if shapetype is None:
err = "a list of shapetypes must be provided"
raise AssertionError(err)
elif isinstance(shapetype, str):
shapetype = [shapetype] * len(obj)
for ix, geom in enumerate(obj):
self.__collection.append(
GeoSpatialUtil(geom, shapetype[ix])
)
elif self.__shapefile is not None:
if isinstance(obj, str):
with self.__shapefile.Reader(obj) as r:
for shape in r.shapes():
self.__collection.append(GeoSpatialUtil(shape))
elif isinstance(obj, self.__shapefile.Reader):
for shape in obj.shapes():
self.__collection.append(GeoSpatialUtil(shape))
elif isinstance(obj, self.__shapefile.Shapes):
for shape in obj:
self.__collection.append(GeoSpatialUtil(shape))
if geojson is not None:
if isinstance(
obj,
(
geojson.GeometryCollection,
geojson.FeatureCollection,
geojson.MultiLineString,
geojson.MultiPoint,
geojson.MultiPolygon,
),
):
for geom in obj.geometries:
self.__collection.append(GeoSpatialUtil(geom))
shapely_geo = import_optional_dependency("shapely.geometry")
if shapely_geo is not None:
if isinstance(
obj,
(
shapely_geo.collection.GeometryCollection,
shapely_geo.MultiPoint,
shapely_geo.MultiLineString,
shapely_geo.MultiPolygon,
),
):
for geom in obj.geoms:
self.__collection.append(GeoSpatialUtil(geom))
if not self.__collection:
raise AssertionError(
f"Reader is not installed for collection type: {type(obj)}"
)
def __iter__(self):
"""
Iterator method that allows the user to get a list of GeoSpatialUtil
objects from the GeoSpatialCollection object
Returns
-------
GeoSpatialUtil
"""
yield from self.__collection
@property
def shapetype(self):
"""
Returns a list of shapetypes to the user
Returns
-------
list of str
"""
if self.__shapetype is None:
self.__shapetype = [i.shapetype for i in self.__collection]
return self.__shapetype
@property
def points(self):
"""
Property returns a multidimensional list of vertices
Returns
-------
list of vertices
"""
if self._points is None:
self._points = [i.points for i in self.__collection]
return self._points
@property
def shapely(self):
"""
Property that returns a shapely.geometry.collection.GeometryCollection
object to the user
Returns
-------
shapely.geometry.collection.GeometryCollection object
"""
shapely_geo = import_optional_dependency("shapely.geometry")
self._shapely = shapely_geo.collection.GeometryCollection(
[i.shapely for i in self.__collection]
)
return self._shapely
@property
def geojson(self):
"""
Property that returns a geojson.GeometryCollection object to the user
Returns
-------
geojson.GeometryCollection
"""
geojson = import_optional_dependency("geojson")
self._geojson = geojson.GeometryCollection(
[i.geojson for i in self.__collection]
)
return self._geojson
@property
def shape(self):
"""
Property that returns a shapefile.Shapes object
Returns
-------
shapefile.Shapes object
"""
if self._shape is None:
self._shape = self.__shapefile.Shapes()
for geom in self.__collection:
self._shape.append(geom.shape)
return self._shape
@property
def flopy_geometry(self):
"""
Property that returns a flopy.util.geometry.Collection object
Returns
-------
flopy.util.geometry.Collectionnos object
"""
if self._flopy_geometry is None:
self._flopy_geometry = Collection(
[i.flopy_geometry for i in self.__collection]
)
return self._flopy_geometry