Source code for jupedsim.geometry_utils

# Copyright © 2012-2024 Forschungszentrum Jülich GmbH
# SPDX-License-Identifier: LGPL-3.0-or-later
from typing import Any, List, Optional, Tuple

import shapely

import jupedsim.native as py_jps
from jupedsim.geometry import Geometry


[docs]class GeometryError(Exception): """Class reflecting errors when creating JuPedSim geometry objects.""" def __init__(self, message) -> None: """Create GeometryError with the given message. Args: message: Error message """ self.message = message
def _geometry_from_wkt(wkt_input: str) -> Geometry: geometry_collection = None try: wkt_type = shapely.from_wkt(wkt_input) except Exception as exc: raise GeometryError( f"Could not create geometry objects from the given WKT: " f"{wkt_input}. See following error message:\n{exc}" ) from exc if isinstance(wkt_type, shapely.GeometryCollection): geometry_collection = wkt_type else: try: geometry_collection = shapely.GeometryCollection([wkt_type]) except Exception as exc: raise GeometryError( f"Could not create a geometry collection from the given WKT: " f"{wkt_input}. See following error message:\n{exc}" ) from exc polygons = _polygons_from_geometry_collection(geometry_collection) return Geometry(_internal_build_geometry(polygons)) def _geometry_from_shapely( geometry_input: ( shapely.Polygon | shapely.MultiPolygon | shapely.GeometryCollection | shapely.MultiPoint ), ) -> Geometry: polygons = _polygons_from_geometry_collection( shapely.GeometryCollection([geometry_input]) ) return Geometry(_internal_build_geometry(polygons)) def _geometry_from_coordinates( coordinates: List[Tuple], *, excluded_areas: Optional[List[Tuple]] = None ) -> Geometry: polygon = shapely.Polygon(coordinates, holes=excluded_areas) return Geometry(_internal_build_geometry([polygon])) def _polygons_from_geometry_collection( geometry_collection: shapely.GeometryCollection, ) -> List[shapely.Polygon]: def _polygons_from_multi_polygon( multi_polygon: shapely.MultiPolygon, ) -> List[shapely.Polygon]: result = [] for polygon in multi_polygon.geoms: result += _polygons_from_polygon(polygon) return result def _polygons_from_linear_ring( linear_ring: shapely.LinearRing, ) -> List[shapely.Polygon]: return _polygons_from_polygon(shapely.Polygon(linear_ring)) def _polygons_from_polygon( polygon: shapely.Polygon, ) -> List[shapely.Polygon]: return [polygon] polygons = [] for geo in geometry_collection.geoms: if shapely.get_type_id(geo) == shapely.GeometryType.GEOMETRYCOLLECTION: polygons += _polygons_from_geometry_collection(geo) elif shapely.get_type_id(geo) == shapely.GeometryType.MULTIPOLYGON: polygons += _polygons_from_multi_polygon(geo) elif shapely.get_type_id(geo) == shapely.GeometryType.LINEARRING: polygons += _polygons_from_linear_ring(geo) elif shapely.get_type_id(geo) == shapely.GeometryType.POLYGON: polygons += _polygons_from_polygon(geo) else: raise GeometryError( f"Unexpected geometry type found in GeometryCollection: " f"{geo.geom_type}. Only Polygon types are allowed." ) return polygons def _internal_build_geometry( polygons: List[shapely.Polygon], ) -> py_jps.Geometry: geo_builder = py_jps.GeometryBuilder() for polygon in polygons: geo_builder.add_accessible_area(polygon.exterior.coords[:-1]) for hole in polygon.interiors: geo_builder.exclude_from_accessible_area(hole.coords[:-1]) return geo_builder.build()
[docs]def build_geometry( geometry: ( list[tuple[float, float]] | shapely.GeometryCollection | shapely.Polygon | shapely.MultiPolygon | shapely.MultiPoint | str ), **kwargs: Any, ) -> Geometry: """Create a :class:`~jupedsim.geometry.Geometry` from different input representations. .. note :: The geometric data supplied need to form a single "simple" polygon with holes. In case the input contains multiple polygons this must hold true for the union of all polygons. Arguments: geometry: Data to create the geometry out of. Data may be supplied as: * list of 2d points describing the outer boundary, holes may be added with use of `excluded_areas` kw-argument * :class:`~shapely.GeometryCollection` consisting only out of :class:`Polygons <shapely.Polygon>`, :class:`MultiPolygons <shapely.MultiPolygon>` and :class:`MultiPoints <shapely.MultiPoint>` * :class:`~shapely.MultiPolygon` * :class:`~shapely.Polygon` * :class:`~shapely.MultiPoint` forming a "simple" polygon when points are interpreted as linear ring without repetition of the start/end point. * str with a valid Well Known Text. In this format the same WKT types as mentioned for the shapely types are supported: GEOMETRYCOLLETION, MULTIPOLYGON, POLYGON, MULTIPOINT. The same restrictions as mentioned for the shapely types apply. Keyword Arguments: excluded_areas: describes exclusions from the walkable area. Only use this argument if `geometry` was provided as list[tuple[float, float]]. """ if isinstance(geometry, str): return _geometry_from_wkt(geometry) elif ( isinstance(geometry, shapely.GeometryCollection) or isinstance(geometry, shapely.Polygon) or isinstance(geometry, shapely.MultiPolygon) or isinstance(geometry, shapely.MultiPoint) ): return _geometry_from_shapely(geometry) else: return _geometry_from_coordinates( geometry, excluded_areas=kwargs.get("excluded_areas") )