Source code for cityImage.angles

import math
import numpy as np
from math import sqrt
from shapely.geometry import Point, LineString, MultiLineString

"""
Readapted for LineStrings from Abhinav Ramakrishnan's post in https://stackoverflow.com/a/28261304/7375309.
"""  
def _dot(vA, vB):
    return vA[0]*vB[0]+vA[1]*vB[1]
        
[docs]def get_coord_angle(origin, distance, angle): """ The function returns the coordinates of the line starting from a tuple of coordinates, which forms with the y axis an angle in degree of a certain magnitude, given the distance from the origin. Parameters ---------- origin: tuple Tuple of coordinates. distance: float The distance from the origin coordinates. angle: float The desired angle. Returns: ---------- coords: tuple The resulting coordinates. """ (disp_x, disp_y) = (distance * math.sin(math.radians(angle)), distance * math.cos(math.radians(angle))) coord = (origin[0] + disp_x, origin[1] + disp_y) return coord
class Error(Exception): """Base class for other exceptions""" class AngleError(Error): """Raised when not-intersecting lines are provided for computing angles""" class Settings(): """ A class to store and compare the coordinates of two line geometries. Attributes ---------- x_originA: float The x-coordinate of the first point of the first line geometry. y_originA: float The y-coordinate of the first point of the first line geometry. x_secondA: float The x-coordinate of the second point of the first line geometry. y_secondA: float The y-coordinate of the second point of the first line geometry. x_destinationA: float The x-coordinate of the last point of the first line geometry. y_destinationA: float The y-coordinate of the last point of the first line geometry. x_second_lastA: float The x-coordinate of the second-last point of the first line geometry. y_second_lastA: float The y-coordinate of the second-last point of the first line geometry. x_originB: float The x-coordinate of the first point of the second line geometry. y_originB: float The y-coordinate of the first point of the second line geometry. x_secondB: float The x-coordinate of the second point of the second line geometry. y_secondB: float The y-coordinate of the second point of the second line geometry. x_destinationB: float The x-coordinate of the last point of the second line geometry. y_destinationB: float The y-coordinate of the last point of the second line geometry. x_second_lastB: float The x-coordinate of the second-last point of the second line geometry. y_second_lastB: float The y-coordinate of the second-last point of the second line geometry. lineA (Tuple): The coordinates of the first line used to calculate the angle. lineB (Tuple): The coordinates of the second linne used to calculate the angle. """ def set_coordinates(self, coords, prefix): """ Set the coordinates for a line. Parameters: ---------- coords: list A list of coordinates in the form [(x1, y1), (x2, y2), ...]. prefix: str A string prefix to be added to the variable names for the coordinates. For example, if "A" is passed as the prefix, the coordinates will be stored as self.x_originA, self.y_originA, etc. """ setattr(self, 'x_origin'+ prefix, float("{0:.10f}".format(coords[0][0]))) setattr(self, 'y_origin'+ prefix, float("{0:.10f}".format(coords[0][1]))) setattr(self, 'x_second'+ prefix, float("{0:.10f}".format(coords[1][0]))) setattr(self, 'y_second'+ prefix, float("{0:.10f}".format(coords[1][1]))) setattr(self, 'x_destination'+ prefix, float("{0:.10f}".format(coords[-1][0]))) setattr(self, 'y_destination'+ prefix, float("{0:.10f}".format(coords[-1][1]))) setattr(self, 'x_second_last'+ prefix, float("{0:.10f}".format(coords[-2][0]))) setattr(self, 'y_second_last'+ prefix, float("{0:.10f}".format(coords[-2][1]))) def set_conditions(self, calculation_type): """ Given a Setting object and a calculation type, this function returns the lines that will be used to compute the angle. Parameters ---------- Setting: object An object of the Setting class, which contains information about the lines. calculation_type: str One of: 'vectors', 'angular_change', 'deflection': - 'vectors': computes angle between vectors. - 'angular_change': computes angle of incidence between the two lines. - 'deflection': computes angle of incidence between the two lines, on the basis of the vertex in common and the second following(intermediate, if existing) vertexes forming each of the line. Raises ------ AngleError: If the lines do not have a common vertex. """ if calculation_type == "angular_change": if (self.x_destinationA, self.y_destinationA) == (self.x_destinationB, self.y_destinationB): self.lineA = ((self.x_second_lastA, self.y_second_lastA), (self.x_destinationA, self.y_destinationA)) self.lineB = ((self.x_destinationB, self.y_destinationB), (self.x_second_lastB, self.y_second_lastB)) elif (self.x_destinationA, self.y_destinationA) == (self.x_originB, self.y_originB): self.lineA = ((self.x_second_lastA, self.y_second_lastA), (self.x_destinationA, self.y_destinationA)) self.lineB = ((self.x_originB, self.y_originB), (self.x_secondB, self.y_secondB)) elif (self.x_originA, self.y_originA) == (self.x_originB, self.y_originB): self.lineA = ((self.x_secondA, self.y_secondA), (self.x_originA, self.y_originA)) self.lineB = ((self.x_originB, self.y_originB), (self.x_secondB, self.y_secondB)) elif (self.x_originA, self.y_originA) == (self.x_destinationB, self.y_destinationB): self.lineA = ((self.x_secondA, self.y_secondA), (self.x_originA, self.y_originA)) self.lineB = ((self.x_destinationB, self.y_destinationB), (self.x_second_lastB, self.y_second_lastB)) # no common vertex else: raise AngleError("The lines do not intersect! provide lines wich have a common vertex") # deflection on the entire lines elif calculation_type == "deflection": if (self.x_destinationA, self.y_destinationA) == (self.x_destinationB, self.y_destinationB): self.lineA = ((self.x_originA, self.y_originA), (self.x_destinationA, self.y_destinationA)) self.lineB = ((self.x_destinationB, self. y_destinationB), (self.x_originB, self.y_originB)) elif (self.x_destinationA, self.y_destinationA) == (self.x_originB, self.y_originB): self.lineA = ((self.x_originA, self.y_originA), (self.x_destinationA, self.y_destinationA)) self.lineB = ((self.x_originB, self.y_originB), (self.x_destinationB, self.y_destinationB)) elif (self.x_originA, self.y_originA) == (self.x_originB, self.y_originB): self.lineA = ((self.x_destinationA, self.y_destinationA), (self.x_originA, self.y_originA)) self.lineB = ((self.x_originB, self.y_originB), (self.x_destinationB, self.y_destinationB)) elif (self.x_originA, self.y_originA) == (self.x_destinationB, self.y_destinationB): self.lineA = ((self.x_destinationA, self.y_destinationA), (self.x_originA, self.y_originA)) self.lineB = ((self.x_destinationB, self.y_destinationB), (self.x_originB, self.y_originB)) # no common vertex else: raise AngleError("The lines do not intersect! provide lines wich have a common vertex") else: # calculation_type == "vectors" if (self.x_destinationA, self.y_destinationA) == (self.x_destinationB, self.y_destinationB): self.lineA = ((self.x_destinationA, self.y_destinationA), (self.x_originA, self.y_originA)) self.lineB = ((self.x_destinationB, self.y_destinationB), (self.x_originB, self.y_originB)) elif (self.x_destinationA, self.y_destinationA) == (self.x_originB, self.y_originB): self.lineA = ((self.x_destinationA, self.y_destinationA), (self.x_originA, self.y_originA)) self.lineB = ((self.x_originB, self.y_originB), (self.x_destinationB, self.y_destinationB)) elif (self.x_originA, self.y_originA) == (self.x_originB, self.y_originB): self.lineA = ((self.x_originA, self.y_originA), (self.x_destinationA, self.y_destinationA)) self.lineB = ((self.x_originB, self.y_originB), (self.x_destinationB, self.y_destinationB)) elif (self.x_originA, self.y_originA) == (self.x_destinationB, self.y_destinationB): self.lineA = ((self.x_originA, self.y_originA), (self.x_destinationA, self.y_destinationA)) self.lineB = ((self.x_destinationB, self.y_destinationB),(self.x_originB, self.y_originB)) # no common vertex else: raise AngleError("The lines do not intersect! provide lines wich have a common vertex") def __init__(self, coordsA, coordsB, calculation_type): """ Initializes the class with the coordinates of two line geometries. Args ---------- coordsA: list A list of coordinates of the first line geometry. coordsB: list A list of coordinates of the second line geometry. """ self.set_coordinates(coordsA, 'A') self.set_coordinates(coordsB, 'B') self.set_conditions(calculation_type)
[docs]def angle_line_geometries(line_geometryA, line_geometryB, degree = False, calculation_type = 'vector'): """ Given two LineStrings it computes the angle between them. Returns value in degrees or radians. Parameters ---------- line_geometryA: LineString The first line. line_geometryB: LineString The other line; it must share a vertex with line_geometryA. degree: boolean If True it returns value in degree, otherwise in radians. calculation_type: string One of: 'vectors', 'angular_change', 'deflection': -'vectors': computes angle between vectors. -'angular_change': computes angle of incidence between the two lines. -'deflection': computes angle of incidence between the two lines, on the basis of the vertex in common and the second following(intermediate, if existing) vertexes forming each of the line. Returns: ---------- angle: float The resulting angle in radians or degrees. """ valid_calculation_types = ['vectors', 'angular_change', 'deflection'] if not isinstance(line_geometryA, LineString) or not isinstance(line_geometryB, LineString): raise TypeError("Both input must be of type shapely.geometry.LineString") if calculation_type not in valid_calculation_types: raise ValueError(f"Invalid calculation type. Choose one of: {valid_calculation_types}.") # extracting coordinates and createing lines coordsA = list(line_geometryA.coords) coordsB = list(line_geometryB.coords) if len(coordsA) < 2 or len(coordsB) < 2: raise ValueError("Both LineString must have at least 2 coordinates") settings = Settings(coordsA, coordsB, calculation_type) lineA, lineB = settings.lineA, settings.lineB # Get nicer vector form vA = [(lineA[0][0]-lineA[1][0]), (lineA[0][1]-lineA[1][1])] vB = [(lineB[0][0]-lineB[1][0]), (lineB[0][1]-lineB[1][1])] try: # Get dot prod dot_prod = _dot(vA, vB) # Get magnitudes magA = _dot(vA, vA)**0.5 magB = _dot(vB, vB)**0.5 # Get cosine value cos_ = dot_prod/magA/magB # Get angle in radians and then convert to degrees angle_rad = math.acos(dot_prod/magB/magA) # Basically doing angle <- angle mod 360 angle_deg = math.degrees(angle_rad)%360 except: angle_deg = 0.0 angle_rad = 0.0 angle = angle_rad if degree: angle = angle_deg return angle