from typing import Any, Sequence, Callable, Set, List, Union
from turf.helpers import (
Feature,
Point,
LineString,
MultiPoint,
MultiLineString,
Polygon,
MultiPolygon,
FeatureCollection,
get_input_dimensions,
)
from turf.utils.exceptions import InvalidInput
from turf.utils.error_codes import error_code_messages
from turf.utils.helpers import dimensions
allowed_types_points = ["Point", "MultiPoint"]
allowed_types_default = [
"Point",
"MultiPoint",
"LineString",
"MultiLineString",
"Polygon",
"MultiPolygon",
]
def _process_list_input(
allowed_types: Sequence,
input_value: Sequence,
callback: Callable,
raise_exception: bool = True,
) -> List:
"""
Retrieves the coords from lists, or raises an exception if invalid.
:param allowed_types: allowed Feature types
:param input_value: list input
:param callback: method to call recursively
:param raise_exception: if an exception should be raised or if it should be silent
:return: list with extracted coords
"""
dim = get_input_dimensions(input_value)
allowed_dimensions = {dimensions.get(type_, None) for type_ in allowed_types}
allowed_class_types = (
*[eval(allowed_type) for allowed_type in allowed_types],
dict,
)
if dim == 1 and all(isinstance(el, allowed_class_types) for el in input_value):
return list(map(lambda geom: callback(geom, allowed_types), input_value))
return _check_input_in_allowed_dimensions(
input_value, dim, allowed_types, allowed_dimensions, raise_exception
)
def _check_input_in_allowed_dimensions(
input_value: Sequence,
dim: int,
allowed_types: Sequence,
allowed_dimensions: Set,
raise_exception: bool = True,
) -> Union[List, Sequence]:
"""
:param input_value: list input
:param dim: number of dimensions of input
:param allowed_types: allowed Feature types
:param allowed_dimensions: allowed dimensions for specified allowed types
:param raise_exception: if an exception should be raised or if it should be silent
:return: list with extracted coords
"""
if dim in allowed_dimensions:
if dim == 1 and len(input_value) < 2:
raise InvalidInput(error_code_messages["InvalidPointInput"])
return input_value
else:
if raise_exception:
raise InvalidInput(
error_code_messages["InvalidGeometry"](
[type_ for type_ in allowed_types if type_ in allowed_types_default]
)
)
return []
[docs]def get_coords_from_geometry(
geometry: Any, allowed_types: Sequence = None, raise_exception: bool = True
) -> List:
"""
Retrieves coords from a given Geometry. Geometry must be a GeoJSON,
a Geometry object or a list of coordinates, otherwise it raises an exception.
:param geometry: Any input value(s)
:param allowed_types: allowed Feature types
:param raise_exception: if an exception should be raised or if it should be silent
:return: list with extracted coords
"""
if not allowed_types:
allowed_types = allowed_types_default
if isinstance(geometry, list):
return _process_list_input(
allowed_types, geometry, get_coords_from_geometry, raise_exception
)
if isinstance(geometry, (Feature, dict)):
if geometry.get("type") == "Feature":
return get_coords_from_geometry(geometry.get("geometry", {}), allowed_types)
allowed_class_types = [
*[eval(allowed_type) for allowed_type in allowed_types],
dict,
]
if any(
isinstance(geometry, allowed_type_class)
and geometry.get("type", "") in allowed_types
for allowed_type_class in allowed_class_types
):
return geometry.get("coordinates", [])
if raise_exception:
raise InvalidInput(error_code_messages["InvalidGeometry"](allowed_types))
else:
return []
[docs]def get_geometry_from_features(features: Any, allowed_types: Sequence = None) -> List:
"""
Retrieves Geometries from Features. Features must be a GeoJSON,
a Feature object or a list of coordinates, otherwise it raises an exception.
:param features: Any input value(s)
:param allowed_types: allowed Feature types
:return: list with extracted coords
"""
if not allowed_types:
allowed_types = allowed_types_default
if isinstance(features, list):
return _process_list_input(
[*allowed_types, "Feature"], features, get_geometry_from_features
)
if isinstance(features, (FeatureCollection, dict)):
if features.get("type") == "FeatureCollection":
return list(
map(
lambda feature: feature.get("geometry", {}),
features.get("features", []),
)
)
if isinstance(features, (Feature, dict)):
if features.get("type", "") == "Feature":
if features.get("geometry", {}).get("type", "") in allowed_types:
return features.get("geometry", {})
if isinstance(
features, (*[eval(allowed_type) for allowed_type in allowed_types], dict)
):
if features.get("type", "") in allowed_types:
return features
raise InvalidInput(error_code_messages["InvalidGeometry"](allowed_types))
[docs]def get_coords_from_features(features: Any, allowed_types: Sequence = None) -> List:
"""
Retrieves coords from Features. Features must be a GeoJSON,
a Feature object or a list of coordinates, otherwise it raises an exception.
:param features: Any input value(s)
:param allowed_types: allowed Feature types
:return: list with extracted coords
"""
if not allowed_types:
allowed_types = allowed_types_default
if isinstance(features, (FeatureCollection, dict)):
if features.get("type") == "FeatureCollection":
return list(
map(
lambda feature: get_coords_from_geometry(
feature.get("geometry", {}), allowed_types
),
features.get("features", []),
)
)
return get_coords_from_geometry(features, allowed_types)