Source code for gefest.core.geometry.geometry_2d

from typing import Union
from uuid import uuid4

import numpy as np
from golem.utilities.data_structures import ensure_wrapped_in_sequence
from loguru import logger
from shapely import affinity, get_parts
from shapely.affinity import scale
from shapely.geometry import (
    GeometryCollection,
    LineString,
    MultiLineString,
    MultiPoint,
    MultiPolygon,
)
from shapely.geometry import Point as ShapelyPoint
from shapely.geometry import Polygon as ShapelyPolygon
from shapely.geometry import box, mapping
from shapely.ops import nearest_points, split

from gefest.core.geometry import Point, Polygon, Structure

from .geometry import Geometry


[docs] class Geometry2D(Geometry): """Overriding the geometry base class for 2D structures. The input receives information about the closeness of the polygon. Args: is_closed: ``True`` if the :obj:`Polygon` must have close borders (first Point is equal to the last one), otherwise ``False``. Default value is ``True`` """ is_closed: bool = True is_convex: bool = True
[docs] def get_length(self, polygon: Polygon): """Returns polygon perimeter.""" if len(polygon.points) <= 2: return 0 geom_polygon = LineString([ShapelyPoint(pt.x, pt.y) for pt in polygon]) return geom_polygon.length
[docs] def shapely_to_gefest(self, geom_in): """Converts any shapely object to GEFEST polygon.""" if isinstance(geom_in, ShapelyPolygon): return Polygon(self.get_coords(geom_in))
# add other shapely objects
[docs] def get_coords(self, poly: Union[ShapelyPolygon, LineString]) -> list[Point]: """The function for getting points. Args: poly: :obj:`Polygon` for processing Returns: all :obj:`Point` that :obj:`poly`contains """ if isinstance(poly, ShapelyPolygon): poly = poly.exterior points = [ Point(x, y) for x, y in zip( list(poly.coords.xy[0]), list(poly.coords.xy[1]), ) ] return points
[docs] def get_prohibited_geom( self, prohibited_area: Structure, buffer_size: float = 0.001, ) -> GeometryCollection: """Generates Shapely GeometryCollection from pohibited structure.""" geom_collection = [] for poly in prohibited_area.polygons: if poly[0] == poly[-1]: geom_collection.append(self._poly_to_shapely_poly(poly).buffer(buffer_size)) else: geom_collection.append(self._poly_to_shapely_line(poly).buffer(buffer_size)) return GeometryCollection(geom_collection)
[docs] def resize_poly( self, poly: Polygon, x_scale: float, y_scale: float, ) -> Polygon: """The function for rescaling polygons along each axis. Scaling occurs relative to the center of mass of the polygon. Args: poly: :obj:`Polygon` for processing x_scale: scale value for **x** axis y_scale: scale value for **y** axis Returns: scaled :obj:`poly` by ``(x,y)`` axes """ geom_polygon = self._poly_to_shapely_line(poly) rescaled_geom_polygon = affinity.scale( geom_polygon, x_scale, y_scale, ) rescaled_points = self.get_coords(rescaled_geom_polygon) rescaled_poly = Polygon( polygon_id=poly.id_, points=rescaled_points, ) return rescaled_poly
[docs] @logger.catch def get_angle( self, vector1: tuple[Point, Point], vector2: tuple[Point, Point], ) -> float: """Finds angle betwen two bectors.""" v1 = np.array([vector1[1].x - vector1[0].x, vector1[1].y - vector1[0].y]) v2 = np.array([vector2[1].x - vector2[0].x, vector2[1].y - vector2[0].y]) v1_u = v1 / np.linalg.norm(v1) v2_u = v2 / np.linalg.norm(v2) return np.rad2deg(np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0)))
[docs] def rotate_point( self, point: Point, origin: Point, angle: float, ) -> Polygon: """Rotates polygon by given angle.""" rotated = affinity.rotate( ShapelyPoint(point.x, point.y), angle, ShapelyPoint(origin.x, origin.y), ) return Point(rotated.x, rotated.y)
[docs] def rotate_poly( self, poly: Polygon, angle: float, ) -> Polygon: """Rotating polygon relative to the center of mass by a given angle. Args: poly: :obj:`Polygon` for processing. angle: value of degree rotation. Returns: rotated :obj:`poly`. """ geom_polygon = self._poly_to_shapely_line(poly) rotated_geom_polygon = affinity.rotate( geom_polygon, angle, 'center', ) rotated_points = self.get_coords(rotated_geom_polygon) rotated_poly = Polygon( polygon_i=poly.id_, points=rotated_points, ) return rotated_poly
[docs] def get_square(self, polygon: Polygon) -> float: """Recieving value of the area. Args: polygon: :obj:`Polygon` for processing. Returns: value of the :obj:`polygon` area. """ if len(polygon.points) <= 2: return 0 geom_polygon = ShapelyPolygon([self._pt_to_shapely_pt(pt) for pt in polygon]) return geom_polygon.area
[docs] def is_contain_point(self, poly: Polygon, point: Point) -> bool: """Checking if a point is inside a polygon. Args: poly: :obj:`Polygon` that explore point: :obj:`Point` for checking presence inside the :obj:`Polygon`. Returns: ``True`` if :obj:`point` is into :obj:`poly`, otherwise ``False``. """ geom_poly_allowed = ShapelyPolygon([self._pt_to_shapely_pt(pt) for pt in poly]) geom_pt = ShapelyPoint(point.x, point.y) return geom_poly_allowed.contains(geom_pt)
[docs] def nearest_point(self, point: Point, poly: Polygon) -> Point: """Calculating closest point between input point and polygon. Args: point: the :obj:`Point` that explore poly: the :obj:`Polygon` that explore Returns: nearest_correct_position :obj:`Point` from ``point`` among all points in the ``poly`` """ geom_poly = self._poly_to_shapely_line(poly) geom_point = ShapelyPoint(point.x, point.y) _, nearest_correct_position = nearest_points(geom_point, geom_poly) return Point(nearest_correct_position.x, nearest_correct_position.y)
[docs] def nearest_points(self, poly_1: Polygon, poly_2: Polygon) -> list[Point]: """Calculating closest point between two polygons. Args: poly_1: the first :obj:`Polygon` that explore poly_2: the second :obj:`Polygon` that explore Returns: the couple of :obj:`Point` where the first one from :obj:`poly_1` and the second one from :obj:`poly_2` """ geom_poly_1 = self._poly_to_shapely_line(poly_1) geom_poly_2 = self._poly_to_shapely_line(poly_2) _, nearest_correct_position = nearest_points( geom_poly_1, geom_poly_2, ) return nearest_correct_position
[docs] def get_convex(self, poly: Polygon) -> Polygon: """Obtaining a convex polygon to avoid intersections. Args: poly: :obj:`Polygon` for processing Returns: convex :obj:`Polygon` """ if len(poly.points) < 3: return poly geom_poly = self._poly_to_shapely_line(poly).convex_hull points = self.get_coords(geom_poly) polygon = Polygon(polygon_id='tmp', points=points) return polygon
[docs] def intersection_line_line(self, points1, points2, scale1, scale2): """Returns point of two lines intersection.""" a = scale(LineString([(p.x, p.y) for p in points1]), scale1, scale1) b = scale(LineString([(p.x, p.y) for p in points2]), scale2, scale2) intersection_point = a.intersection(b) if not intersection_point.is_empty: if isinstance(intersection_point, LineString): intersection_point = intersection_point.coords[0] intersection_point = Point(intersection_point[0], intersection_point[1]) else: intersection_point = Point(intersection_point.x, intersection_point.y) else: intersection_point = None return intersection_point
[docs] def intersection_poly_line(self, figure: Polygon, points: list[Point], scale_factor): """Returns points where line intersects polygon.""" if self.is_closed: figure = self._poly_to_shapely_poly(figure) else: figure = self._poly_to_shapely_line(figure) minx, miny, maxx, maxy = figure.bounds line = LineString([(p.x, p.y) for p in points]) line = scale(line, scale_factor) bounding_box = box(minx * 2, miny * 2, maxx * 2, maxy * 2) a = ShapelyPoint(line.boundary.bounds[:2]) b = ShapelyPoint(line.boundary.bounds[2:]) if a.x == b.x: # vertical line extended_line = LineString([(a.x, miny), (a.x, maxy)]) elif a.y == b.y: # horizonthal line extended_line = LineString([(minx, a.y), (maxx, a.y)]) else: # linear equation: y = k*x + m k = (b.y - a.y) / (b.x - a.x) m = a.y - k * a.x y0 = k * minx + m y1 = k * maxx + m x0 = (miny - m) / k x1 = (maxy - m) / k points_on_boundary_lines = [ ShapelyPoint(minx, y0), ShapelyPoint(maxx, y1), ShapelyPoint(x0, miny), ShapelyPoint(x1, maxy), ] points_sorted_by_distance = sorted(points_on_boundary_lines, key=bounding_box.distance) extended_line = LineString(points_sorted_by_distance[:2]) if extended_line.intersects(figure): interaction = extended_line.intersection(figure) if isinstance(interaction, ShapelyPoint): interaction = [Point(interaction.x, interaction.y)] elif isinstance(interaction, MultiPoint): interaction = [Point(p[0], p[1]) for p in interaction.geoms] else: interaction = [Point(p[0], p[1]) for p in interaction.coords] else: return None
[docs] def simplify(self, poly: Polygon, tolerance: float) -> Polygon: """Simplifies polyon.""" inp = poly if len(poly) < 3: return poly if self._poly_to_shapely_poly(poly).is_simple: poly = self._poly_to_shapely_poly(inp) compressed = poly.buffer(-tolerance, join_style='mitre') if not compressed.is_empty: poly = compressed.buffer(tolerance * 1.05, join_style='mitre') simplified = poly.simplify(tolerance) if isinstance(simplified, MultiPolygon): simplified = max(simplified.geoms, key=lambda p: p.area) if simplified.is_empty: poly = self._poly_to_shapely_poly(inp) compressed = poly.buffer(-tolerance, join_style='mitre') decompressed = compressed.buffer(tolerance * 1.1, join_style='mitre') intersected = decompressed.intersection(poly) simplified = intersected.simplify(tolerance) if isinstance(simplified, MultiPolygon): simplified = max(simplified.geoms, lambda p: p.area) out = Polygon([Point(p[0], p[1]) for p in simplified.exterior.coords]) else: simplified = self._poly_to_shapely_line(poly).convex_hull.simplify(tolerance) if simplified.is_empty: raise ValueError('Empty polygon produced') out = Polygon([Point(p[0], p[1]) for p in simplified.exterior.coords]) return out
[docs] def is_simple(self, poly: Polygon) -> bool: """Checks if poly is simple.""" return self._poly_to_shapely_poly(poly).is_simple
[docs] def get_random_point_in_shapey_geom(self, fig): """Returns random point from polygon of arbitrary shape shapely geometry.""" if fig.is_empty: raise ValueError('Unable to pick a point from an empty polygon.') if isinstance(fig, MultiPolygon): bds = [] for bound in list(fig.geoms): bds.extend(list(bound.exterior.coords)) minx, miny, maxx, maxy = LineString(bds).bounds else: minx, miny, maxx, maxy = fig.bounds x = np.random.uniform(minx, maxx, 1) y = np.random.uniform(miny, maxy, 1) while not fig.contains(ShapelyPoint(x, y)): x = np.random.uniform(minx, maxx, 1) y = np.random.uniform(miny, maxy, 1) return Point(x, y)
[docs] def get_random_point_in_poly(self, poly) -> Union[Point, None]: """Returns random point from polygon of arbitrary shape.""" minx, miny, maxx, maxy = poly.bounds if any(b != b for b in poly.bounds): raise ValueError('Unable to pick a point from empty an polygon.') # also can be used polar cords generator within circumscribed circle x = np.random.uniform(minx, maxx, 1) y = np.random.uniform(miny, maxy, 1) point = None for _ in range(100): x = np.random.uniform(minx, maxx, 1) y = np.random.uniform(miny, maxy, 1) if poly.contains(ShapelyPoint(x, y)): point = Point(x, y) break return point
[docs] def get_centroid(self, poly: Polygon) -> Point: """Getting a point that is the center of mass of the polygon. Args: poly: the :obj:`Polygon` that explore Returns: central :obj:`Point` of :obj:`poly` """ points = poly.points if len(points) < 3: points.append(points[0]) geom_poly = ShapelyPolygon([self._pt_to_shapely_pt(pt) for pt in points]) geom_point = geom_poly.centroid point = Point(geom_point.x, geom_point.y) return point
[docs] def intersects(self, structure: Structure) -> bool: """Function to check for any intersection in structure of polygons. Whole structure appears like shapely MultiLineString for which uses method is simple. Args: structure: the :obj:`Structure` that explore Returns: ``True`` if any :obj:`Polygon` in :obj:`structure` intersects with another one, otherwise - ``False`` """ multi_geom = MultiLineString( [self._poly_to_shapely_line(poly) for poly in structure.polygons], ) return multi_geom.is_simple
[docs] def contains(self, poly1: Polygon, poly2: Polygon) -> bool: """Checks if poly2 contains poly1.""" geom_polygon1 = self._poly_to_shapely_line(poly1) geom_polygon2 = ShapelyPolygon([self._pt_to_shapely_pt(pt) for pt in poly2]) is_contain = geom_polygon2.contains(geom_polygon1) return is_contain
[docs] def difference_polys(self, base_poly: Polygon, diff_polys: list[Polygon]): """Returns area of base_poly difference with diff_polys polygons.""" base_poly = self._poly_to_shapely_poly(base_poly) diff_polys = ensure_wrapped_in_sequence(diff_polys) diff_polys = [self._poly_to_shapely_poly for poly in diff_polys] for poly in diff_polys: base_poly = base_poly.difference(poly) if base_poly.is_empty: assert 'Empty difference' return Polygon([Point(p[0], p[1]) for p in base_poly.exterior.coords])
[docs] def intersection_polys(self, base_poly: Polygon, diff_polys: list[Polygon]): """Returns area of base_poly intersection with diff_polys polygons.""" base_poly = self._poly_to_shapely_poly(base_poly).convex_hull if isinstance(diff_polys, Polygon): diff_polys = [diff_polys] diff_polys = [self._poly_to_shapely_poly(poly).convex_hull for poly in diff_polys] for poly in diff_polys: base_poly = base_poly.intersection(poly) if base_poly.is_empty: assert 'Empty intersection' return Polygon([Point(p[0], p[1]) for p in base_poly.exterior.coords])
[docs] def intersects_poly(self, poly_1: Polygon, poly_2: Polygon) -> bool: """Intersection between two polygons. Args: poly_1: the first :obj:`Polygon` that explore poly_2: the second :obj:`Polygon` that explore Returns: ``True`` if the :obj:`poly_1` intersects with :obj:`poly_2`, otherwise - ``False`` """ geom_poly_1 = self._poly_to_shapely_line(poly_1) geom_poly_2 = self._poly_to_shapely_line(poly_2) return geom_poly_1.intersects(geom_poly_2)
[docs] def _poly_to_shapely_line(self, poly: Polygon) -> LineString: """Transform GEFEST Polygon to shapely non cycled LineString. Args: poly: Polygon Returns: LineString """ return LineString([self._pt_to_shapely_pt(pt) for pt in poly.points])
[docs] def _poly_to_shapely_poly(self, poly: Polygon) -> ShapelyPolygon: """Transform GEFEST Polygon to shapely Polygon. Args: poly: Polygon Returns: ShapelyPolygon """ return ShapelyPolygon([(pt.x, pt.y) for pt in poly.points])
[docs] def _pt_to_shapely_pt(self, pt: Point) -> ShapelyPoint: """Transform GEFEST Polygon to shapely Polygon. Args: poly: Point Returns: ShapelyPoint """ return ShapelyPoint(pt.x, pt.y)
[docs] def split_polygon(self, poly, line: tuple[Point, Point], scale_factor=1000) -> list: """Splits polygon by line. Returns: list: Produced parts. """ poly = ShapelyPolygon([(p.x, p.y) for p in poly]) line = LineString( [ (line[0].x, line[0].y), (line[1].x, line[1].y), ], ) line = scale( line, scale_factor, scale_factor, ) parts = get_parts(split(poly, line)).tolist() parts = [mapping(p)['coordinates'][0][:-1] for p in parts] return parts
[docs] def min_distance(self, obj_1, obj_2) -> float: """Finds smallest distance between two objects. Args: obj_1: the first :obj:`obj_1` that explore obj_2: the second :obj:`obj_2` that explore Returns: value of distance between the nearest points of the explored objects """ if isinstance(obj_1, Polygon): obj_1 = self._poly_to_shapely_line(obj_1) elif isinstance(obj_1, Point): obj_1 = self._pt_to_shapely_pt(obj_1) if isinstance(obj_2, Polygon): obj_2 = self._poly_to_shapely_line(obj_2) elif isinstance(obj_2, Point): obj_2 = self._pt_to_shapely_pt(obj_2) distance = obj_1.distance(obj_2) return distance
[docs] def centroid_distance(self, point: Point, poly: Polygon) -> Point: """Finds distance from point to polygon.""" geom_point = self._pt_to_shapely_pt(point) geom_poly = self._poly_to_shapely_line(poly) dist = geom_point.distance(geom_poly) return dist
def create_circle(struct: Structure) -> Structure: """Creates circle.""" geom = Geometry2D(is_closed=False) poly = struct.polygons[0] num_points = len(poly.points) radius = geom.get_length(struct.polygons[0]) / (2 * np.pi) x = [pt.x for pt in poly.points] y = [pt.y for pt in poly.points] center_x = round((max(x) + min(x)) / 2, 2) center_y = round((max(y) + min(y)) / 2, 2) theta = np.linspace(0, 2 * np.pi, num_points) a = radius * np.cos(theta) + center_x + 2.2 * radius b = radius * np.sin(theta) + center_y struct = Polygon( polygon_id=str(uuid4()), points=[(Point(x, y)) for x, y in zip(a, b)], ) return struct