Path: blob/main/tools/contributed/sumopy/agilepy/lib_base/geometry.py
169689 views
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo1# Copyright (C) 2016-2025 German Aerospace Center (DLR) and others.2# SUMOPy module3# Copyright (C) 2012-2021 University of Bologna - DICAM4# This program and the accompanying materials are made available under the5# terms of the Eclipse Public License 2.0 which is available at6# https://www.eclipse.org/legal/epl-2.0/7# This Source Code may also be made available under the following Secondary8# Licenses when the conditions for such availability set forth in the Eclipse9# Public License 2.0 are satisfied: GNU General Public License, version 210# or later which is available at11# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html12# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later1314# @file geometry.py15# @author Joerg Schweizer16# @date 20121718import numpy as np19try:20from shapely.geometry import MultiPoint, Polygon, Point, LineString21IS_SHAPELY = True22except:23IS_SHAPELY = False242526# >>> from geometry import *27# >>> cv = [(0,0),(0,1),(0,2),(0,3),(3,3),(3,0)]28# >>> cc = [(0,0),(0,1),(1,1),(1,2),(0,2),(0,3),(3,3),(3,0)]29# >>>30# >>> p=Point(0.5,2)31# >>> policc=Polygon(cc)32# >>> policv=Polygon(cv)33# >>> is_point_in_polygon(p,cv, is_use_shapely = True)34# >>> pc = [0.5,2]35# >>> is_point_in_polygon(pc,cv, is_use_shapely = True)36# True37# >>> is_point_in_polygon(pc,cc, is_use_shapely = True)38# False39# >>> is_point_in_polygon(pc,cc, is_use_shapely = False)40# False41# >>> is_point_in_polygon(pc,cv, is_use_shapely = False)42# True4344def get_norm_2d(vertex3d):45# print 'get_norm_2d',vertex3d.shape46return np.sqrt(np.sum(vertex3d[:, :2]**2, 1))47# print ' r',r.shape48# return r495051def get_length_polylines(polylines):52# print 'get_length_polylines'53# v = np.array([[[0.0,0.0,0.0],[1,0.0,0.0]],[[1,0.0,0.0],[1,2,0.0]],[[1,2.0,0.0],[1,2,3.0]] ])54lengths = np.zeros(len(polylines), np.float)55i = 056for v in polylines:57# print ' v=\n',v,v.shape58# print ' v[:,0,:]\n',v[:,0,:]59# print ' v[:,1,:]\n',v[:,1,:]60lengths[i] = np.sum(np.sqrt(np.sum((v[:, 1, :]-v[:, 0, :])**2, 1)))61i += 162return lengths636465def get_length_polypoints(polylines):66# print 'get_length_polypoints'67# v = np.array([[[0.0,0.0,0.0],[1,0.0,0.0]],[[1,0.0,0.0],[1,2,0.0]],[[1,2.0,0.0],[1,2,3.0]] ])68lengths = np.zeros(len(polylines), np.float)69i = 070for v in polylines:71# print ' v=\n',v72a = np.array(v, np.float32)73# print ' a=\n',a,a.shape74lengths[i] = np.sum(np.sqrt(np.sum((a[1:, :]-a[:-1, :])**2, 1)))75i += 176return lengths777879def polypoints_to_polylines(polypoints):80linevertices = np.array([None]*len(polypoints), np.object) # np.zeros((0,2,3),np.float32)81#polyinds = []82#lineinds = []8384ind = 085ind_line = 086for polyline in polypoints:87# Important type conversion!!88v = np.zeros((2*len(polyline)-2, 3), np.float32)89v[0] = polyline[0]90v[-1] = polyline[-1]91if len(v) > 2:92v[1:-1] = np.repeat(polyline[1:-1], 2, 0)9394#n_lines = len(v)/295#polyinds += n_lines*[ind]96# lineinds.append(np.arange(ind_line,ind_line+n_lines))97#ind_line += n_lines9899linevertices[ind] = v.reshape((-1, 2, 3))100#linevertices = np.concatenate((linevertices, v.reshape((-1,2,3))))101102ind += 1103104return linevertices # , lineinds, polyinds105106107def get_vec_on_polyline_from_pos(polyline, pos, length, angle=0.0):108"""109Returns a vector ((x1,y1,z1),(x2,y2,z2))110where first coordinate is the point on the polyline at position pos111and the second coordinate is length meters ahead with an angle112with respect to the direction of the polyline.113114TODO: Attention angle not yet implemented115"""116pos_edge = 0.0117pos_edge_pre = 0.0118x1, y1, z1 = polyline[0]119120for j in xrange(1, len(polyline)):121x2, y2, z2 = polyline[j]122seglength = np.linalg.norm([x2-x1, y2-y1])123pos_edge += seglength124125if (pos >= pos_edge_pre) & (pos <= pos_edge):126127dxn = (x2-x1)/seglength128dyn = (y2-y1)/seglength129u1 = (pos-pos_edge_pre)130131u2 = (pos+length-pos_edge_pre)132return np.array([[x1 + u1 * dxn, y1 + u1 * dyn, z2], [x1 + u2 * dxn, y1 + u2 * dyn, z2]], np.float32)133134x1, y1 = x2, y2135pos_edge_pre = pos_edge136137x1, y1, z1 = polyline[-1]138dxn = (x2-x1)/seglength139dyn = (y2-y1)/seglength140u1 = (pos-pos_edge_pre)141u2 = (pos+length-pos_edge_pre)142return np.array([[x2, y2, z2], [x2 + u2 * dxn, y1 + u2 * dyn, z2]], np.float32)143144145def get_coord_on_polyline_from_pos(polyline, pos):146pos_edge = 0.0147pos_edge_pre = 0.0148x1, y1, z1 = polyline[0]149150for j in xrange(1, len(polyline)):151x2, y2, z2 = polyline[j]152length = np.linalg.norm([x2-x1, y2-y1])153pos_edge += length154155if (pos >= pos_edge_pre) & (pos <= pos_edge):156u = (pos-pos_edge_pre)/length157x = x1 + u * (x2-x1)158y = y1 + u * (y2-y1)159return np.array([x, y, z2], np.float32)160161x1, y1 = x2, y2162pos_edge_pre = pos_edge163164return np.array([x2, y2, z2], np.float32)165166167def get_coord_angle_on_polyline_from_pos(polyline, pos):168"""169Return coordinate and respective angle170at position pos on the polygon.171"""172pos_edge = 0.0173pos_edge_pre = 0.0174x1, y1, z1 = polyline[0]175176for j in xrange(1, len(polyline)):177x2, y2, z2 = polyline[j]178length = np.linalg.norm([x2-x1, y2-y1])179pos_edge += length180181if (pos >= pos_edge_pre) & (pos <= pos_edge):182u = (pos-pos_edge_pre)/length183dx = (x2-x1)184dy = (y2-y1)185x = x1 + u * dx186y = y1 + u * dy187return np.array([x, y, z2], np.float32), np.arctan2(dy, dx)188189dx = (x2-x1)190dy = (y2-y1)191x1, y1 = x2, y2192pos_edge_pre = pos_edge193194return np.array([x2, y2, z2], np.float32), np.arctan2(dy, dx)195196197def get_pos_on_polyline_from_coord(polyline, coord):198xc, yc, zc = coord199n_segs = len(polyline)200201d_min = 10.0**8202x_min = 0.0203y_min = 0.0204j_min = 0205p_min = 0.0206pos = 0.0207x1, y1, z1 = polyline[0]208for j in xrange(1, n_segs):209x2, y2, z2 = polyline[j]210d, xp, yp = shortest_dist(x1, y1, x2, y2, xc, yc)211# print ' x1,y1=(%d,%d)'%(x1,y1),',x2,y2=(%d,%d)'%(x2,y2),',xc,yc=(%d,%d)'%(xc,yc)212# print ' d,x,y=(%d,%d,%d)'%shortest_dist(x1,y1, x2,y2, xc,yc)213if d < d_min:214d_min = d215# print ' **d_min=',d_min,[xp,yp]216x_min = xp217y_min = yp218j_min = j219p_min = pos220# print ' pos',pos,[x2-x1,y2-y1],'p_min',p_min221pos += np.linalg.norm([x2-x1, y2-y1])222x1, y1 = x2, y2223224x1, y1, z1 = polyline[j_min-1]225return p_min+np.linalg.norm([x_min-x1, y_min-y1])226227228def shortest_dist(x1, y1, x2, y2, x3, y3): # x3,y3 is the point229"""230Shortest distance between point (x3,y3) and line (x1,y1-> x2,y2).231Returns distance and projected point on line.232"""233234px = x2-x1235py = y2-y1236237something = px*px + py*py238if something > 0:239u = ((x3 - x1) * px + (y3 - y1) * py) / float(something)240else:241u = 0242243# clip and return infinite distance if not on the line244if u > 1:245u = 1246# return 10.0**8,x1 + px,y1 + py247248elif u < 0:249u = 0250# return 10.0**8,x1 ,y1251252x = x1 + u * px253y = y1 + u * py254255dx = x - x3256dy = y - y3257258# Note: If the actual distance does not matter,259# if you only want to compare what this function260# returns to other results of this function, you261# can just return the squared distance instead262# (i.e. remove the sqrt) to gain a little performance263264dist = np.sqrt(dx*dx + dy*dy)265266return dist, x, y267268269def get_dist_point_to_segs(p, y1, x1, y2, x2, is_ending=True,270is_detect_initial=False,271is_detect_final=False,272#is_return_pos = False273):274"""275Minimum square distance between a Point p = (x,y) and a Line segments ,276where vectors x1, y1 are the first points and x2,y2 are the second points277of the line segments.278279If is_detect_initial is True then a point whose projection is beyond280the start of the segment will result in a NaN distance.281282If is_detect_final is True then a point whose projection is beyond283the end of the segment will result in a NaN distance.284285If is_return_pos then the position on the section is returned.286287Written by Paul Bourke, October 1988288http://astronomy.swin.edu.au/~pbourke/geometry/pointline/289290Rewritten in vectorial form by Joerg Schweizer291"""292293y3, x3 = p294295d = np.zeros(len(y1), dtype=np.float32)296297dx21 = (x2-x1)298dy21 = (y2-y1)299300lensq21 = dx21*dx21 + dy21*dy21301302# indexvector for all zero length lines303iz = (lensq21 == 0)304305dy = y3-y1[iz]306dx = x3-x1[iz]307308d[iz] = dx*dx + dy*dy309310lensq21[iz] = 1.0 # replace zeros with 1.0 to avoid div by zero error311312u = (x3-x1)*dx21 + (y3-y1)*dy21313u = u / lensq21 # normalize position314315x = x1 + u * dx21316y = y1 + u * dy21317318if is_ending:319if not is_detect_initial:320ie = u < 0321x[ie] = x1[ie]322y[ie] = y1[ie]323324if not is_detect_final:325ie = u > 1326x[ie] = x2[ie]327y[ie] = y2[ie]328329dx30 = x3-x330dy30 = y3-y331d[~iz] = (dx30*dx30 + dy30*dy30)[~iz]332333if is_detect_final:334d[iz | (u > 1)] = np.nan335336if is_detect_initial:337d[iz | (u < 0)] = np.nan338339return d340341342def is_inside_triangles(p, x1, y1, x2, y2, x3, y3):343"""344Returns a binary vector with True if point p is345inside a triangle.346x1,y1,x2,y2,x3,y3 are vectors with the 3 coordiantes of the triangles.347"""348alpha = ((y2 - y3)*(p[0] - x3) + (x3 - x2)*(p[1] - y3)) \349/ ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3))350351beta = ((y3 - y1)*(p[0] - x3) + (x1 - x3)*(p[1] - y3)) \352/ ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3))353354gamma = 1.0 - alpha - beta355return (alpha > 0) & (beta > 0) & (gamma > 0)356357# from http://www.arachnoid.com/area_irregular_polygon/index.html358359360def find_area_perim(array):361"""362Scalar!363"""364a = 0365p = 0366ox, oy = array[0]367for x, y in array[1:]:368a += (x*oy-y*ox)369p += abs((x-ox)+(y-oy)*1j)370ox, oy = x, y371return a/2, p372373374def find_area(array, is_use_shapely=True):375"""376Single polygon with 2D coordinates.377"""378# TODO: check, there are negative A!!!!379# print 'find_area',array380if len(array) >= 3:381if IS_SHAPELY & is_use_shapely:382return Polygon(np.array(array)[:, :2]).area383384else:385a = 0386ox, oy = array[0]387for x, y in array[1:]:388a += (x*oy-y*ox)389ox, oy = x, y390391# print ' =',np.abs(a/2)392return np.abs(a/2)393else:394return 0.0395396397def get_polygonarea_fast(x, y):398"""399Returns area in polygon represented by x,y vectors.400Attention: last point is not first point.401"""402# https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates403return 0.5*np.abs(np.dot(x, np.roll(y, 1))-np.dot(y, np.roll(x, 1)))404405406def is_point_in_polygon(point, poly, is_use_shapely=True):407"""408Scalar!409"""410if len(poly) >= 3:411if IS_SHAPELY & is_use_shapely:412pol = Polygon(np.array(poly)[:, :2])413return Point(point[:2]).within(pol)414else:415is_3d = len(point) == 3416417if is_3d:418x, y, z = point419p1x, p1y, p1z = poly[0]420else:421x, y = point422p1x, p1y = poly[0]423n = len(poly)424inside = False425426for i in range(n+1):427if is_3d:428p2x, p2y, p2z = poly[i % n]429else:430p2x, p2y = poly[i % n]431if y > min(p1y, p2y):432if y <= max(p1y, p2y):433if x <= max(p1x, p2x):434if p1y != p2y:435xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x436if p1x == p2x or x <= xints:437inside = not inside438p1x, p1y = p2x, p2y439440return inside441442else:443return False444445446def is_polyline_intersect_polygon(polyline, polygon, is_use_shapely=True, is_lineinterpolate=True):447if IS_SHAPELY & is_use_shapely:448poly = Polygon(np.array(polygon)[:, :2])449if is_lineinterpolate:450# requires that any line interpolation between 2 points451# intersects the polygon452return LineString(np.array(polyline)[:, :2]).intersects(poly)453else:454# requires that at least one point is inside the polygon455return MultiPoint(np.array(polyline)[:, :2]).intersects(poly)456457else:458# WARNING: this dows not work if no point of the polyline459# resides within the polygon460for p in polyline:461if is_point_in_polygon(p, polygon):462return True463return False464465466def is_polyline_in_polygon(polyline, polygon, is_use_shapely=True):467if IS_SHAPELY & is_use_shapely:468poly = Polygon(np.array(polygon)[:, :2])469return MultiPoint(np.array(polyline)[:, :2]).within(poly)470471else:472for p in polyline:473if not is_point_in_polygon(p, polygon):474return False475return True476477478def get_angles_perpendicular(shape):479"""480Returns the angle vector angles_perb which is perpendicular to the given shape.481The normalized482dxn = np.cos(angles_perb)483dyn = np.sin(angles_perb)484"""485486n_vert = len(shape)487# print 'get_laneshapes',_id,n_lanes,n_vert488489#width = self.widths_lanes_default[_id]490# print ' shape', shape ,len( shape)491v_ext_begin = (shape[0]-(shape[1]-shape[0])).reshape(1, 3)492v_ext_end = (shape[-1]+(shape[-1]-shape[-2])).reshape(1, 3)493494exshape = np.concatenate((v_ext_begin, shape, v_ext_end))[:, 0:2]495# print ' exshape', exshape,len( exshape)496vertex_delta_x = exshape[1:, 0]-exshape[0:-1, 0]497vertex_delta_y = exshape[1:, 1]-exshape[0:-1, 1]498499angles = np.arctan2(vertex_delta_y, vertex_delta_x)500#angles = np.mod(np.arctan2(vertex_delta_y,vertex_delta_x)+2*np.pi,2*np.pi)501#angles_perb = 0.5 *(angles[1:]+angles[0:-1])-np.pi/2502503angles1 = angles[1:]504angles2 = angles[0:-1]505ind_discont = (angles1 < -0.5*np.pi) & ((angles2 > 0.5*np.pi)) | (angles2 < -0.5*np.pi) & ((angles1 > 0.5*np.pi))506angle_sum = angles1+angles2507angle_sum[ind_discont] += 2*np.pi508509#angles = np.mod(np.arctan2(vertex_delta_y,vertex_delta_x)+2*np.pi,2*np.pi)510#angle_sum = angles[1:]+angles[0:-1]511#ind_discont = angle_sum>2*np.pi512#angle_sum[ind_discont] = angle_sum[ind_discont]-2*np.pi513return 0.5*angle_sum-np.pi/2514515516def rotate_vertices(vec_x, vec_y, alphas=None,517sin_alphas=None, cos_alphas=None,518is_array=False):519"""520Rotates all vertices around521"""522# print 'rotate_vertices',vec_x, vec_y523524if alphas is not None:525sin_alphas = np.sin(alphas)526cos_alphas = np.cos(alphas)527# print ' sin_alphas',sin_alphas528# print ' cos_alphas',cos_alphas529#deltas = vertices - offsets530#vertices_rot = np.zeros(vertices.shape, np.float32)531#n= size(vec_x)532vec_x_rot = vec_x*cos_alphas - vec_y*sin_alphas533vec_y_rot = vec_x*sin_alphas + vec_y*cos_alphas534# print ' vec_x_rot',vec_x_rot535# print ' vec_y_rot',vec_y_rot536if is_array:537# print ' concatenate', np.concatenate((vec_x_rot.reshape(-1,1), vec_y_rot.reshape(-1,1)),1)538return np.concatenate((vec_x_rot.reshape(-1, 1), vec_y_rot.reshape(-1, 1)), 1)539else:540return vec_x_rot, vec_y_rot541542543T = np.array([[0, -1], [1, 0]])544545546def line_intersect(a1, a2, b1, b2):547"""548Works for multiple points in each of the input arguments, i.e., a1, a2, b1, b2 can be Nx2 row arrays of 2D points.549https://stackoverflow.com/questions/3252194/numpy-and-line-intersections550"""551da = np.atleast_2d(a2 - a1)552db = np.atleast_2d(b2 - b1)553dp = np.atleast_2d(a1 - b1)554dap = np.dot(da, T)555denom = np.sum(dap * db, axis=1)556num = np.sum(dap * dp, axis=1)557return np.atleast_2d(num / denom).T * db + b1558559560def line_intersect2(a1, a2, b1, b2):561"""562Works for multiple points in each of the input arguments, i.e., a1, a2, b1, b2 can be Nx2 row arrays of 2D points.563https://stackoverflow.com/questions/3252194/numpy-and-line-intersections564Returns also a scale factor which indicates wheter the intersection565is in positive or negative direction of the b vector.566"""567da = np.atleast_2d(a2 - a1)568db = np.atleast_2d(b2 - b1)569dp = np.atleast_2d(a1 - b1)570dap = np.dot(da, T)571denom = np.sum(dap * db, axis=1)572num = np.sum(dap * dp, axis=1)573la = np.atleast_2d(num / denom).T574return la * db + b1, la575576577def get_diff_angle_clockwise(p1, p2):578"""579Returns the clockwise angle between vector5800 -> p1 and 0 -> p2581582p1=np.array([[x11,x11,x13,...],[y11,y12,y13,...]])583p2 =np.array([[x21,x21,x23,...],[y21,y22,y23,...]])584585"""586ang1 = np.arctan2(*p1[::-1])587ang2 = np.arctan2(*p2[::-1])588# return np.rad2deg((ang1 - ang2) % (2 * np.pi))589if hasattr(ang1, '__iter__'):590return anglediffs(ang1, ang2)591else:592return anglediff(ang1, ang2)593################################################################594# old595596597def anglediff(a1, a2):598"""Compute the smallest difference between two angle arrays.599Parameters600----------601a1, a2 : np.ndarray602The angle arrays to subtract603Returns604-------605out : np.ndarray606The difference between a1 and a2607"""608609dtheta = a1 - a2610while dtheta > np.pi:611dtheta -= 2.0*np.pi612while dtheta < -np.pi:613dtheta += 2.0*np.pi614615return dtheta616617618def anglediffs(a1, a2, deg=False):619"""Compute the smallest difference between two angle arrays.620Parameters621----------622a1, a2 : np.ndarray623The angle arrays to subtract624deg : bool (default=False)625Whether to compute the difference in degrees or radians626Returns627-------628out : np.ndarray629The difference between a1 and a2630"""631print 'anglediffs', a1, a2632return wrapanglediffs(a1 - a2, deg=deg)633634635def wrapanglediffs(diff, deg=False):636"""Given an array of angle differences, make sure that they lie637between -pi and pi.638Parameters639----------640diff : np.ndarray641The angle difference array642deg : bool (default=False)643Whether the angles are in degrees or radians644Returns645-------646out : np.ndarray647The updated angle differences648"""649650if deg:651base = 360652else:653base = np.pi * 2654655i = np.abs(diff) > (base / 2.0)656out = diff.copy()657out[i] -= np.sign(diff[i]) * base658return out659660# find indees where 2 arrays are identical661#idx = np.argwhere(np.diff(np.sign(f - g)) != 0).reshape(-1) + 0662663664def azimut_from_origin(p, origin=[0, 0]):665# determine the azimut angle relative to a 2D point666p = np.array(p) - np.array(origin)667if p[0] > 0 and p[1] > 0:668return np.arctan(p[0]/p[1])669if p[0] > 0 and p[1] < 0:670return np.arctan(p[0]/p[1])+np.pi671if p[0] < 0 and p[1] < 0:672return np.arctan(p[0]/p[1])+np.pi673if p[0] < 0 and p[1] > 0:674return np.arctan(p[0]/p[1])+2*np.pi675676677def angle2D(p1, p2):678theta1 = math.atan2(p1[1], p1[0])679theta2 = math.atan2(p2[1], p2[0])680#dtheta = theta2 - theta1;681# while dtheta > np.pi:682# dtheta -= 2.0*np.pi683# while dtheta < -np.pi:684# dtheta += 2.0*np.pi685return wrapanglediffs(theta2 - theta1)686687688def is_point_within_polygon(pos, shape):689angle = 0.690pos = np.array(pos, float)691shape = np.array(shape, float)692for i in range(0, len(shape)-1):693p1 = ((shape[i][0] - pos[0]), (shape[i][1] - pos[1]))694p2 = ((shape[i+1][0] - pos[0]), (shape[i+1][1] - pos[1]))695angle = angle + angle2D(p1, p2)696i = len(shape)-1697p1 = ((shape[i][0] - pos[0]), (shape[i][1] - pos[1]))698p2 = ((shape[0][0] - pos[0]), (shape[0][1] - pos[1]))699angle = angle + angle2D(p1, p2)700return math.fabs(angle) >= np.pi701702703if __name__ == '__main__':704a1 = 0.0705a2 = +0.1706print 'a1', a1/np.pi*180707print 'a2', a2/np.pi*180708print 'anglediff(a1, a2)', anglediff(a1, a2)/np.pi*180709710711