Source code for jupedsim.geometry_utils
# Copyright © 2012-2023 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")
        )