Source code for turf.polygon_tangents._polygon_tangents

from typing import Dict, List, Sequence, TypeVar, Union

from turf.bbox import bbox
from turf.explode import explode
from turf.nearest_point import nearest_point

from turf.helpers import (
    all_geometry_types,
    FeatureCollection,
    MultiPolygon,
    Point,
    Polygon,
)
from turf.helpers import feature, feature_collection, geometry, point, polygon
from turf.helpers import Feature, FeatureCollection, Geometry
from turf.invariant import get_coords_from_features
from turf.utils.error_codes import error_code_messages
from turf.utils.exceptions import InvalidInput

GeoJson = TypeVar("GeoJson", Dict, Feature, FeatureCollection, Geometry)
PointFeature = TypeVar("PointFeature", Dict, Point, Sequence)
PolygonFeature = TypeVar("PolygonFeature", Dict, Polygon, MultiPolygon)


[docs]def polygon_tangents( start_point: PointFeature, polygon: PolygonFeature ) -> FeatureCollection: """ Finds the tangents of a {Polygon or(MultiPolygon} from a {Point}. more: http://geomalgorithms.com/a15-_tangents.html :param point: point [lng, lat] or Point feature to calculate the tangent points from :param polygon: polygon to get tangents from :return: Feature Collection containing the two tangent points """ point_coord = get_coords_from_features(start_point, ("Point",)) polygon_coords = get_coords_from_features(polygon, ("Polygon", "MultiPolygon")) try: geometry_type = polygon.get("geometry").get("type") except AttributeError: try: geometry_type = polygon.get("type") except AttributeError: raise InvalidInput( error_code_messages["InvalidGeometry"](["Polygon", "MultiPolygon"]) ) box = bbox(polygon) near_point_index = 0 near_point = False # If the point lies inside the polygon bbox then it's a bit more complicated # points lying inside a polygon can reflex angles on concave polygons if ( (point_coord[0] > box[0]) and (point_coord[0] < box[2]) and (point_coord[1] > box[1]) and (point_coord[1] < box[3]) ): near_point = nearest_point(start_point, explode(polygon)) near_point_index = near_point["properties"]["featureIndex"] if geometry_type == "Polygon": tangents = process_polygon( polygon_coords, point_coord, near_point, near_point_index ) # bruteforce approach # calculate both tangents for each polygon # define all tangents as a new polygon and calculate tangetns out of those coordinates elif geometry_type == "MultiPolygon": multi_tangents = [] for polygon_coord in polygon_coords: tangents = process_polygon( polygon_coord, point_coord, near_point, near_point_index ) multi_tangents.extend(tangents) tangents = process_polygon( [multi_tangents], point_coord, near_point, near_point_index ) r_tangents = tangents[0] l_tangents = tangents[1] return feature_collection([point(r_tangents), point(l_tangents)])
def process_polygon( polygon_coords: Sequence, point_coord: Sequence, near_point: Union[Point, None], near_point_index: int, ) -> Sequence: """ Prepares a polygon to calculate the tangents :param polygon_coords: point [lng, lat] or Point feature to calculate the tangent points from :param point_coord: point [lng, lat] :param near_point: nearest point [lng, lat] on polygon towards view point in case the view point lies inside the polygon :param near_point_index: index of neareast point on polygon in case the view point lies inside the polygon :return: List of tangents coordinates [upper and lower] """ r_tangents = polygon_coords[0][near_point_index] l_tangents = polygon_coords[0][0] if near_point: if near_point["geometry"]["coordinates"][1] < point_coord[1]: l_tangents = polygon_coords[0][near_point_index] tangents = calculate_tangents( polygon_coords[0], point_coord, r_tangents, l_tangents, ) return tangents def calculate_tangents( poly_coords: Sequence, point_coord: Sequence, r_tangents: Sequence, l_tangents: Sequence, ) -> Sequence: """ Calculate the upper and lower tangents from the polygon by comparison with each neighbor coordinate in the polygon :param polygon_coords: point [lng, lat] or Point feature to calculate the tangent points from :param point_coord: point [lng, lat] :param r_tangents: current rightmost tangents point [lng, lat] of the polygon :param l_tangents: current leftmost tangents point [lng, lat] of the polygon :return: List of tangents coordinates [upper and lower] """ edge_next = None edge_prev = is_left(poly_coords[0], poly_coords[len(poly_coords) - 1], point_coord,) for i in range(1, len(poly_coords) + 1): current_poly_coord = poly_coords[i - 1] if i == len(poly_coords): next_poly_coord = poly_coords[0] else: next_poly_coord = poly_coords[i] edge_next = is_left(current_poly_coord, next_poly_coord, point_coord) if (edge_prev <= 0) and (edge_next > 0): if not is_below(point_coord, current_poly_coord, r_tangents): r_tangents = current_poly_coord elif (edge_prev > 0) and (edge_next <= 0): if not is_above(point_coord, current_poly_coord, l_tangents): l_tangents = current_poly_coord edge_prev = edge_next return [r_tangents, l_tangents] def is_above(point1: Sequence, point2: Sequence, point3: Sequence) -> bool: """ Checks if point 1 is above point2 and point 3 :param geojson: geojson or Feature :param geojson_types: string of the supposed feature tpye :return: Feature or FeatureCollection of the incoming geojson """ return is_left(point1, point2, point3) > 0 def is_below(point1: Sequence, point2: Sequence, point3: Sequence) -> bool: """ Checks if point 1 is below point2 and point 3 :param geojson: geojson or Feature :param geojson_types: string of the supposed feature tpye :return: Feature or FeatureCollection of the incoming geojson """ return is_left(point1, point2, point3) < 0 def is_left(point1: Sequence, point2: Sequence, point3: Sequence) -> float: """ Calculates if point 1 is left of point2 and point 3 :param geojson: geojson or Feature :param geojson_types: string of the supposed feature tpye :return: Feature or FeatureCollection of the incoming geojson """ return (point2[0] - point1[0]) * (point3[1] - point1[1]) - ( point3[0] - point1[0] ) * (point2[1] - point1[1])