sumolib.geomhelper
1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo 2# Copyright (C) 2013-2026 German Aerospace Center (DLR) and others. 3# This program and the accompanying materials are made available under the 4# terms of the Eclipse Public License 2.0 which is available at 5# https://www.eclipse.org/legal/epl-2.0/ 6# This Source Code may also be made available under the following Secondary 7# Licenses when the conditions for such availability set forth in the Eclipse 8# Public License 2.0 are satisfied: GNU General Public License, version 2 9# or later which is available at 10# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html 11# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later 12 13# @file geomhelper.py 14# @author Daniel Krajzewicz 15# @author Jakob Erdmann 16# @author Michael Behrisch 17# @author Matthias Schwamborn 18# @date 2013-02-25 19 20from __future__ import absolute_import 21import math 22import sys 23 24INVALID_DISTANCE = -1 25 26# back-ported from python 3 for backward compatibility 27# https://www.python.org/dev/peps/pep-0485/#proposed-implementation 28 29 30def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): 31 return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) 32 33 34def distance(p1, p2): 35 dx = p1[0] - p2[0] 36 dy = p1[1] - p2[1] 37 return math.sqrt(dx * dx + dy * dy) 38 39 40def polyLength(polygon): 41 return sum([distance(a, b) for a, b in zip(polygon[:-1], polygon[1:])]) 42 43 44def addToBoundingBox(coordList, bbox=None): 45 if bbox is None: 46 minX = 1e400 47 minY = 1e400 48 maxX = -1e400 49 maxY = -1e400 50 else: 51 minX, minY, maxX, maxY = bbox 52 for x, y in coordList: 53 minX = min(x, minX) 54 minY = min(y, minY) 55 maxX = max(x, maxX) 56 maxY = max(y, maxY) 57 return minX, minY, maxX, maxY 58 59 60def isLeft(point, line_start, line_end): 61 return ((line_end[0] - line_start[0]) * (point[1] - line_start[1]) 62 < (point[0] - line_start[0]) * (line_end[1] - line_start[1])) 63 64 65def lineOffsetWithMinimumDistanceToPoint(point, line_start, line_end, perpendicular=False): 66 """Return the offset from line (line_start, line_end) where the distance to 67 point is minimal""" 68 d = distance(line_start, line_end) 69 u = ((point[0] - line_start[0]) * (line_end[0] - line_start[0]) 70 + (point[1] - line_start[1]) * (line_end[1] - line_start[1])) 71 if d == 0. or u < 0. or u > d * d: 72 if perpendicular: 73 return INVALID_DISTANCE 74 if u < 0.: 75 return 0. 76 return d 77 return u / d 78 79 80def polygonOffsetAndDistanceToPoint(point, polygon, perpendicular=False): 81 """Return the offset and the distance from the polygon start where the distance to the point is minimal""" 82 p = point 83 s = polygon 84 seen = 0 85 minDist = 1e400 86 minOffset = INVALID_DISTANCE 87 for i in range(len(s) - 1): 88 pos = lineOffsetWithMinimumDistanceToPoint( 89 p, s[i], s[i + 1], perpendicular) 90 dist = minDist if pos == INVALID_DISTANCE else distance( 91 p, positionAtOffset(s[i], s[i + 1], pos)) 92 if dist < minDist: 93 minDist = dist 94 minOffset = pos + seen 95 if perpendicular and i != 0 and pos == INVALID_DISTANCE: 96 # even if perpendicular is set we still need to check the distance 97 # to the inner points 98 cornerDist = distance(p, s[i]) 99 if cornerDist < minDist: 100 pos1 = lineOffsetWithMinimumDistanceToPoint( 101 p, s[i - 1], s[i], False) 102 pos2 = lineOffsetWithMinimumDistanceToPoint( 103 p, s[i], s[i + 1], False) 104 if pos1 == distance(s[i - 1], s[i]) and pos2 == 0.: 105 minOffset = seen 106 minDist = cornerDist 107 seen += distance(s[i], s[i + 1]) 108 return minOffset, minDist 109 110 111def polygonOffsetWithMinimumDistanceToPoint(point, polygon, perpendicular=False): 112 """Return the offset from the polygon start where the distance to the point is minimal""" 113 return polygonOffsetAndDistanceToPoint(point, polygon, perpendicular)[0] 114 115 116def distancePointToLine(point, line_start, line_end, perpendicular=False): 117 """Return the minimum distance between point and the line (line_start, line_end)""" 118 p1 = line_start 119 p2 = line_end 120 offset = lineOffsetWithMinimumDistanceToPoint( 121 point, line_start, line_end, perpendicular) 122 if offset == INVALID_DISTANCE: 123 return INVALID_DISTANCE 124 if offset == 0: 125 return distance(point, p1) 126 u = offset / distance(line_start, line_end) 127 intersection = (p1[0] + u * (p2[0] - p1[0]), p1[1] + u * (p2[1] - p1[1])) 128 return distance(point, intersection) 129 130 131def distancePointToPolygon(point, polygon, perpendicular=False): 132 """Return the minimum distance between point and polygon""" 133 p = point 134 s = polygon 135 minDist = None 136 for i in range(0, len(s) - 1): 137 dist = distancePointToLine(p, s[i], s[i + 1], perpendicular) 138 if dist == INVALID_DISTANCE and perpendicular and i != 0: 139 # distance to inner corner 140 dist = distance(point, s[i]) 141 if dist != INVALID_DISTANCE: 142 if minDist is None or dist < minDist: 143 minDist = dist 144 if minDist is not None: 145 return minDist 146 else: 147 return INVALID_DISTANCE 148 149 150def positionAtOffset(p1, p2, offset): 151 if isclose(offset, 0.): # for pathological cases with dist == 0 and offset == 0 152 return p1 153 154 dist = distance(p1, p2) 155 156 if isclose(dist, offset): 157 return p2 158 159 if offset > dist: 160 return None 161 162 return (p1[0] + (p2[0] - p1[0]) * (offset / dist), p1[1] + (p2[1] - p1[1]) * (offset / dist)) 163 164 165def indexAtShapeOffset(shape, offset): 166 """Returns the index of the shape segment which contains the offset and 167 the cumulated length of the shape up to the start point of the segment. 168 If the offset is less or equal to 0, it returns (0, 0.) If the offset is 169 larger than the shape length it returns (None, length of the shape)""" 170 seenLength = 0. 171 curr = shape[0] 172 for idx, p in enumerate(shape[1:]): 173 nextLength = distance(curr, p) 174 if seenLength + nextLength > offset: 175 return idx, seenLength 176 seenLength += nextLength 177 curr = p 178 return None, seenLength 179 180 181def positionAtShapeOffset(shape, offset): 182 idx, seen = indexAtShapeOffset(shape, offset) 183 if idx is None: 184 return shape[-1] 185 return positionAtOffset(shape[idx], shape[idx + 1], offset - seen) 186 187 188def rotationAtShapeOffset(shape, offset): 189 idx, _ = indexAtShapeOffset(shape, offset) 190 if idx is None: 191 return None 192 return math.atan2(shape[idx + 1][1] - shape[idx][1], shape[idx + 1][0] - shape[idx][0]) 193 194 195def angle2D(p1, p2): 196 theta1 = math.atan2(p1[1], p1[0]) 197 theta2 = math.atan2(p2[1], p2[0]) 198 dtheta = theta2 - theta1 199 while dtheta > math.pi: 200 dtheta -= 2.0 * math.pi 201 while dtheta < -math.pi: 202 dtheta += 2.0 * math.pi 203 return dtheta 204 205 206def angleTo2D(p1, p2): 207 return math.atan2(p2[1] - p1[1], p2[0] - p1[0]) 208 209 210def naviDegree(rad): 211 return normalizeAngle(math.degrees(math.pi / 2. - rad), 0, 360, 360) 212 213 214def fromNaviDegree(degrees): 215 return math.pi / 2. - math.radians(degrees) 216 217 218def normalizeAngle(a, lower, upper, circle): 219 while a < lower: 220 a = a + circle 221 while a > upper: 222 a = a - circle 223 return a 224 225 226def minAngleDegreeDiff(d1, d2): 227 return min(normalizeAngle(d1 - d2, 0, 360, 360), 228 normalizeAngle(d2 - d1, 0, 360, 360)) 229 230 231def isWithin(pos, shape): 232 """Returns whether the given pos coordinate is inside the polygon shape defined in anticlockwise order.""" 233 angle = 0. 234 for i in range(0, len(shape) - 1): 235 p1 = ((shape[i][0] - pos[0]), (shape[i][1] - pos[1])) 236 p2 = ((shape[i + 1][0] - pos[0]), (shape[i + 1][1] - pos[1])) 237 angle = angle + angle2D(p1, p2) 238 i = len(shape) - 1 239 p1 = ((shape[i][0] - pos[0]), (shape[i][1] - pos[1])) 240 p2 = ((shape[0][0] - pos[0]), (shape[0][1] - pos[1])) 241 angle = angle + angle2D(p1, p2) 242 return math.fabs(angle) >= math.pi 243 244 245def sideOffset(fromPos, toPos, amount): 246 scale = amount / distance(fromPos, toPos) 247 return (scale * (fromPos[1] - toPos[1]), 248 scale * (toPos[0] - fromPos[0])) 249 250 251def sub(a, b): 252 return (a[0] - b[0], a[1] - b[1]) 253 254 255def add(a, b): 256 return (a[0] + b[0], a[1] + b[1]) 257 258 259def mul(a, x): 260 return (a[0] * x, a[1] * x) 261 262 263def dotProduct(a, b): 264 return a[0] * b[0] + a[1] * b[1] 265 266 267def crossProduct2D(a, b): 268 return a[0] * b[1] - a[1] * b[0] 269 270 271def orthoIntersection(a, b): 272 c = add(a, b) 273 quot = dotProduct(c, a) 274 if abs(quot) > 0.001: 275 return mul(mul(c, dotProduct(a, a)), 1 / quot) 276 else: 277 return None 278 279 280def rotateAround2D(p, rad, origin): 281 s = math.sin(rad) 282 c = math.cos(rad) 283 tmp = sub(p, origin) 284 tmp2 = [tmp[0] * c - tmp[1] * s, 285 tmp[0] * s + tmp[1] * c] 286 return add(tmp2, origin) 287 288 289def length(a): 290 return math.sqrt(dotProduct(a, a)) 291 292 293def norm(a): 294 return mul(a, 1 / length(a)) 295 296 297def narrow(fromPos, pos, toPos, amount): 298 """detect narrow turns which cannot be shifted regularly""" 299 a = sub(toPos, pos) 300 b = sub(pos, fromPos) 301 c = add(a, b) 302 dPac = dotProduct(a, c) 303 if dPac == 0: 304 return True 305 x = dotProduct(a, a) * length(c) / dPac 306 return x < amount 307 308 309def line2boundary(shape, width): 310 """expand center line by width/2 on both sides to obtain a (closed) boundary shape 311 (i.e. for an edge or lane) 312 """ 313 left = move2side(shape, width / 2) 314 right = move2side(shape, -width / 2) 315 316 return left + list(reversed(right)) + [left[0]] 317 318 319def move2side(shape, amount): 320 shape = [s for i, s in enumerate(shape) if i == 0 or shape[i-1] != s] 321 if len(shape) < 2: 322 return shape 323 if polyLength(shape) == 0: 324 return shape 325 result = [] 326 for i, pos in enumerate(shape): 327 if i == 0: 328 fromPos = pos 329 toPos = shape[i + 1] 330 if fromPos != toPos: 331 result.append(sub(fromPos, sideOffset(fromPos, toPos, amount))) 332 elif i == len(shape) - 1: 333 fromPos = shape[i - 1] 334 toPos = pos 335 if fromPos != toPos: 336 result.append(sub(toPos, sideOffset(fromPos, toPos, amount))) 337 else: 338 fromPos = shape[i - 1] 339 toPos = shape[i + 1] 340 # check for narrow turns 341 if narrow(fromPos, pos, toPos, amount): 342 # print("narrow at i=%s pos=%s" % (i, pos)) 343 pass 344 else: 345 a = sideOffset(fromPos, pos, -amount) 346 b = sideOffset(pos, toPos, -amount) 347 c = orthoIntersection(a, b) 348 if c is not None: 349 pos2 = add(pos, c) 350 else: 351 extend = norm(sub(pos, fromPos)) 352 pos2 = add(pos, mul(extend, amount)) 353 result.append(pos2) 354 # print("move2side", amount) 355 # print(shape) 356 # print(result) 357 # print() 358 return result 359 360 361def isClosedPolygon(polygon): 362 return (len(polygon) >= 2) and (polygon[0] == polygon[-1]) 363 364 365def splitPolygonAtLengths2D(polygon, lengths): 366 """ 367 Returns the polygon segments split at the given 2D-lengths. 368 """ 369 if (len(polygon) <= 1 or len(lengths) == 0): 370 return [polygon] 371 offsets = [offset for offset in sorted(lengths) if offset > 0.0 and offset < polyLength(polygon)] 372 ret = [] 373 seenLength = 0 374 curr = polygon[0] 375 polygonIndex = 0 376 for offset in offsets: 377 currSlice = [curr] 378 while polygonIndex < len(polygon) - 1: 379 p = polygon[polygonIndex + 1] 380 if offset < seenLength + distance(curr, p): 381 splitPos = positionAtOffset(curr, p, offset - seenLength) 382 if not isclose(distance(currSlice[-1], splitPos), 0): 383 currSlice.append(splitPos) 384 seenLength += distance(curr, splitPos) 385 curr = splitPos 386 break 387 else: 388 if not isclose(distance(currSlice[-1], p), 0): 389 currSlice.append(p) 390 seenLength += distance(curr, p) 391 curr = p 392 polygonIndex += 1 393 ret.append(currSlice) 394 if polygonIndex < len(polygon) - 1: 395 finalSlice = [curr] + polygon[polygonIndex + 1:] 396 ret.append(finalSlice) 397 return ret 398 399 400def intersectsAtLengths2D(polygon1, polygon2): 401 """ 402 Returns the 2D-length from polygon1's start to all intersections between polygon1 and polygon2. 403 """ 404 ret = [] 405 if (len(polygon1) == 0 or len(polygon2) == 0): 406 return ret 407 polygon1Length = polyLength(polygon1) 408 for j in range(len(polygon2) - 1): 409 p21 = polygon2[j] 410 p22 = polygon2[j + 1] 411 pos = 0.0 412 for i in range(len(polygon1) - 1): 413 p11 = polygon1[i] 414 p12 = polygon1[i + 1] 415 pIntersection = [0.0, 0.0] 416 if intersectsLineSegment(p11, p12, p21, p22, 0.0, pIntersection, True): 417 for k in range(0, len(pIntersection), 2): 418 length = pos + distance(p11, (pIntersection[k], pIntersection[k + 1])) 419 # special case for closed polygons 420 if isClosedPolygon(polygon1) and isclose(length, polygon1Length): 421 length = 0.0 422 # check for duplicate 423 isDuplicate = False 424 for result in ret: 425 if isclose(length, result): 426 isDuplicate = True 427 break 428 if not isDuplicate: 429 ret.append(length) 430 pos += distance(p11, p12) 431 return ret 432 433 434def intersectsPolygon(polygon1, polygon2): 435 """ 436 Returns whether the polygons intersect on at least one of their segments. 437 """ 438 if (len(polygon1) < 2 or len(polygon2) < 2): 439 return False 440 for i in range(len(polygon1) - 1): 441 p11 = polygon1[i] 442 p12 = polygon1[i + 1] 443 for j in range(len(polygon2) - 1): 444 p21 = polygon2[j] 445 p22 = polygon2[j + 1] 446 if intersectsLineSegment(p11, p12, p21, p22): 447 return True 448 return False 449 450 451# @see src/utils/geom/PositionVector::intersects(p11, p12, p21, p22 ...) 452def intersectsLineSegment(p11, p12, p21, p22, withinDist=0.0, pIntersection=None, storeEndPointsIfCoincident=False): 453 """ 454 Returns whether the line segments defined by Line p11,p12 and Line p21,p22 intersect. 455 If not set to 'None', 'pIntersection' serves as a storage for the intersection point(s). 456 Parameter 'storeEndPointsIfCoincident' is an option for storing the endpoints of the 457 line segment defined by the intersecting set of line1 and line2 if applicable. 458 """ 459 eps = sys.float_info.epsilon 460 # dy2 * dx1 - dx2 * dy1 461 denominator = (p22[1] - p21[1]) * (p12[0] - p11[0]) - (p22[0] - p21[0]) * (p12[1] - p11[1]) 462 # dx2 * (p11.y - p21.y) - dy2 * (p11.x - p21.x) 463 numera = (p22[0] - p21[0]) * (p11[1] - p21[1]) - (p22[1] - p21[1]) * (p11[0] - p21[0]) 464 # dx1 * (p11.y - p21.y) - dy1 * (p11.x - p21.x) 465 numerb = (p12[0] - p11[0]) * (p11[1] - p21[1]) - (p12[1] - p11[1]) * (p11[0] - p21[0]) 466 # Are the lines coincident? 467 if (math.fabs(numera) < eps and math.fabs(numerb) < eps and math.fabs(denominator) < eps): 468 a1 = 0.0 469 a2 = 0.0 470 a3 = 0.0 471 a4 = 0.0 472 a = -1e12 473 isVertical = (p11[0] == p12[0]) 474 if not isVertical: 475 # line1 and line2 are not vertical 476 a1 = p11[0] if p11[0] < p12[0] else p12[0] 477 a2 = p12[0] if p11[0] < p12[0] else p11[0] 478 a3 = p21[0] if p21[0] < p22[0] else p22[0] 479 a4 = p22[0] if p21[0] < p22[0] else p21[0] 480 else: 481 # line1 and line2 are vertical 482 a1 = p11[1] if p11[1] < p12[1] else p12[1] 483 a2 = p12[1] if p11[1] < p12[1] else p11[1] 484 a3 = p21[1] if p21[1] < p22[1] else p22[1] 485 a4 = p22[1] if p21[1] < p22[1] else p21[1] 486 if a1 <= a3 and a3 <= a2: 487 # one endpoint of line2 lies on line1 488 if a4 <= a2: 489 # line2 is a subset of line1 490 a = (a3 + a4) / 2.0 491 if storeEndPointsIfCoincident and pIntersection is not None: 492 pIntersection[0] = p21[0] 493 pIntersection[1] = p21[1] 494 pIntersection.append(p22[0]) 495 pIntersection.append(p22[1]) 496 return True 497 else: 498 # the other endpoint of line2 lies beyond line1 499 a = (a3 + a2) / 2.0 500 if storeEndPointsIfCoincident and pIntersection is not None: 501 if not isVertical: 502 pIntersection[0] = a3 503 pIntersection[1] = p21[1] if p21[0] < p22[0] else p22[1] 504 pIntersection.append(a2) 505 pIntersection.append(p12[1] if p11[0] < p12[0] else p11[1]) 506 else: 507 pIntersection[0] = p21[0] if p21[1] < p22[1] else p22[0] 508 pIntersection[1] = a3 509 pIntersection.append(p12[0] if p11[1] < p12[1] else p11[0]) 510 pIntersection.append(a2) 511 return True 512 if a3 <= a1 and a1 <= a4: 513 # one endpoint of line1 lies on line2 514 if a2 <= a4: 515 # line1 is a subset of line2 516 a = (a1 + a2) / 2.0 517 if storeEndPointsIfCoincident and pIntersection is not None: 518 pIntersection[0] = p11[0] 519 pIntersection[1] = p11[1] 520 pIntersection.append(p12[0]) 521 pIntersection.append(p12[1]) 522 return True 523 else: 524 # the other endpoint of line1 lies beyond line2 525 a = (a1 + a4) / 2.0 526 if storeEndPointsIfCoincident and pIntersection is not None: 527 if not isVertical: 528 pIntersection[0] = a1 529 pIntersection[1] = p11[1] if p11[0] < p12[0] else p12[1] 530 pIntersection.append(a4) 531 pIntersection.append(p22[1] if p21[0] < p22[0] else p21[1]) 532 else: 533 pIntersection[0] = p11[0] if p11[1] < p12[1] else p12[0] 534 pIntersection[1] = a1 535 pIntersection.append(p22[0] if p21[1] < p22[1] else p21[0]) 536 pIntersection.append(a4) 537 return True 538 if a != -1e12: 539 if pIntersection is not None: 540 if not isVertical: 541 # line1 and line2 are not vertical 542 mu = (a - p11[0]) / (p12[0] - p11[0]) 543 x = a 544 y = p11[1] + mu * (p12[1] - p11[1]) 545 else: 546 # line1 and line2 are vertical 547 x = p11[0] 548 y = a 549 if p12[1] == p11[1]: 550 mu = 0 551 else: 552 mu = (a - p11[1]) / (p12[1] - p11[1]) 553 pIntersection[0] = x 554 pIntersection[1] = y 555 return True 556 return False 557 # Are the lines parallel? 558 if math.fabs(denominator) < eps: 559 return False 560 # Is the intersection along the segments? 561 mua = numera / denominator 562 # Reduce rounding errors for lines ending in the same point 563 if math.fabs(p12[0] - p22[0]) < eps and math.fabs(p12[1] - p22[1]) < eps: 564 mua = 1.0 565 else: 566 offseta = withinDist / distance(p11, p12) 567 offsetb = withinDist / distance(p21, p22) 568 mub = numerb / denominator 569 if (mua < -offseta or mua > 1 + offseta or mub < -offsetb or mub > 1 + offsetb): 570 return False 571 if pIntersection is not None: 572 x = p11[0] + mua * (p12[0] - p11[0]) 573 y = p11[1] + mua * (p12[1] - p11[1]) 574 mu = mua 575 pIntersection[0] = x 576 pIntersection[1] = y 577 return True 578 579 580def debugPrint(polygon, polyID): 581 """print polygon in a way that can be processed with debug2poly.py""" 582 print("%s=%s" % (polyID, ' '.join(['%s,%s' % p for p in polygon])))
45def addToBoundingBox(coordList, bbox=None): 46 if bbox is None: 47 minX = 1e400 48 minY = 1e400 49 maxX = -1e400 50 maxY = -1e400 51 else: 52 minX, minY, maxX, maxY = bbox 53 for x, y in coordList: 54 minX = min(x, minX) 55 minY = min(y, minY) 56 maxX = max(x, maxX) 57 maxY = max(y, maxY) 58 return minX, minY, maxX, maxY
66def lineOffsetWithMinimumDistanceToPoint(point, line_start, line_end, perpendicular=False): 67 """Return the offset from line (line_start, line_end) where the distance to 68 point is minimal""" 69 d = distance(line_start, line_end) 70 u = ((point[0] - line_start[0]) * (line_end[0] - line_start[0]) 71 + (point[1] - line_start[1]) * (line_end[1] - line_start[1])) 72 if d == 0. or u < 0. or u > d * d: 73 if perpendicular: 74 return INVALID_DISTANCE 75 if u < 0.: 76 return 0. 77 return d 78 return u / d
Return the offset from line (line_start, line_end) where the distance to point is minimal
81def polygonOffsetAndDistanceToPoint(point, polygon, perpendicular=False): 82 """Return the offset and the distance from the polygon start where the distance to the point is minimal""" 83 p = point 84 s = polygon 85 seen = 0 86 minDist = 1e400 87 minOffset = INVALID_DISTANCE 88 for i in range(len(s) - 1): 89 pos = lineOffsetWithMinimumDistanceToPoint( 90 p, s[i], s[i + 1], perpendicular) 91 dist = minDist if pos == INVALID_DISTANCE else distance( 92 p, positionAtOffset(s[i], s[i + 1], pos)) 93 if dist < minDist: 94 minDist = dist 95 minOffset = pos + seen 96 if perpendicular and i != 0 and pos == INVALID_DISTANCE: 97 # even if perpendicular is set we still need to check the distance 98 # to the inner points 99 cornerDist = distance(p, s[i]) 100 if cornerDist < minDist: 101 pos1 = lineOffsetWithMinimumDistanceToPoint( 102 p, s[i - 1], s[i], False) 103 pos2 = lineOffsetWithMinimumDistanceToPoint( 104 p, s[i], s[i + 1], False) 105 if pos1 == distance(s[i - 1], s[i]) and pos2 == 0.: 106 minOffset = seen 107 minDist = cornerDist 108 seen += distance(s[i], s[i + 1]) 109 return minOffset, minDist
Return the offset and the distance from the polygon start where the distance to the point is minimal
112def polygonOffsetWithMinimumDistanceToPoint(point, polygon, perpendicular=False): 113 """Return the offset from the polygon start where the distance to the point is minimal""" 114 return polygonOffsetAndDistanceToPoint(point, polygon, perpendicular)[0]
Return the offset from the polygon start where the distance to the point is minimal
117def distancePointToLine(point, line_start, line_end, perpendicular=False): 118 """Return the minimum distance between point and the line (line_start, line_end)""" 119 p1 = line_start 120 p2 = line_end 121 offset = lineOffsetWithMinimumDistanceToPoint( 122 point, line_start, line_end, perpendicular) 123 if offset == INVALID_DISTANCE: 124 return INVALID_DISTANCE 125 if offset == 0: 126 return distance(point, p1) 127 u = offset / distance(line_start, line_end) 128 intersection = (p1[0] + u * (p2[0] - p1[0]), p1[1] + u * (p2[1] - p1[1])) 129 return distance(point, intersection)
Return the minimum distance between point and the line (line_start, line_end)
132def distancePointToPolygon(point, polygon, perpendicular=False): 133 """Return the minimum distance between point and polygon""" 134 p = point 135 s = polygon 136 minDist = None 137 for i in range(0, len(s) - 1): 138 dist = distancePointToLine(p, s[i], s[i + 1], perpendicular) 139 if dist == INVALID_DISTANCE and perpendicular and i != 0: 140 # distance to inner corner 141 dist = distance(point, s[i]) 142 if dist != INVALID_DISTANCE: 143 if minDist is None or dist < minDist: 144 minDist = dist 145 if minDist is not None: 146 return minDist 147 else: 148 return INVALID_DISTANCE
Return the minimum distance between point and polygon
151def positionAtOffset(p1, p2, offset): 152 if isclose(offset, 0.): # for pathological cases with dist == 0 and offset == 0 153 return p1 154 155 dist = distance(p1, p2) 156 157 if isclose(dist, offset): 158 return p2 159 160 if offset > dist: 161 return None 162 163 return (p1[0] + (p2[0] - p1[0]) * (offset / dist), p1[1] + (p2[1] - p1[1]) * (offset / dist))
166def indexAtShapeOffset(shape, offset): 167 """Returns the index of the shape segment which contains the offset and 168 the cumulated length of the shape up to the start point of the segment. 169 If the offset is less or equal to 0, it returns (0, 0.) If the offset is 170 larger than the shape length it returns (None, length of the shape)""" 171 seenLength = 0. 172 curr = shape[0] 173 for idx, p in enumerate(shape[1:]): 174 nextLength = distance(curr, p) 175 if seenLength + nextLength > offset: 176 return idx, seenLength 177 seenLength += nextLength 178 curr = p 179 return None, seenLength
Returns the index of the shape segment which contains the offset and the cumulated length of the shape up to the start point of the segment. If the offset is less or equal to 0, it returns (0, 0.) If the offset is larger than the shape length it returns (None, length of the shape)
232def isWithin(pos, shape): 233 """Returns whether the given pos coordinate is inside the polygon shape defined in anticlockwise order.""" 234 angle = 0. 235 for i in range(0, len(shape) - 1): 236 p1 = ((shape[i][0] - pos[0]), (shape[i][1] - pos[1])) 237 p2 = ((shape[i + 1][0] - pos[0]), (shape[i + 1][1] - pos[1])) 238 angle = angle + angle2D(p1, p2) 239 i = len(shape) - 1 240 p1 = ((shape[i][0] - pos[0]), (shape[i][1] - pos[1])) 241 p2 = ((shape[0][0] - pos[0]), (shape[0][1] - pos[1])) 242 angle = angle + angle2D(p1, p2) 243 return math.fabs(angle) >= math.pi
Returns whether the given pos coordinate is inside the polygon shape defined in anticlockwise order.
298def narrow(fromPos, pos, toPos, amount): 299 """detect narrow turns which cannot be shifted regularly""" 300 a = sub(toPos, pos) 301 b = sub(pos, fromPos) 302 c = add(a, b) 303 dPac = dotProduct(a, c) 304 if dPac == 0: 305 return True 306 x = dotProduct(a, a) * length(c) / dPac 307 return x < amount
detect narrow turns which cannot be shifted regularly
310def line2boundary(shape, width): 311 """expand center line by width/2 on both sides to obtain a (closed) boundary shape 312 (i.e. for an edge or lane) 313 """ 314 left = move2side(shape, width / 2) 315 right = move2side(shape, -width / 2) 316 317 return left + list(reversed(right)) + [left[0]]
expand center line by width/2 on both sides to obtain a (closed) boundary shape (i.e. for an edge or lane)
320def move2side(shape, amount): 321 shape = [s for i, s in enumerate(shape) if i == 0 or shape[i-1] != s] 322 if len(shape) < 2: 323 return shape 324 if polyLength(shape) == 0: 325 return shape 326 result = [] 327 for i, pos in enumerate(shape): 328 if i == 0: 329 fromPos = pos 330 toPos = shape[i + 1] 331 if fromPos != toPos: 332 result.append(sub(fromPos, sideOffset(fromPos, toPos, amount))) 333 elif i == len(shape) - 1: 334 fromPos = shape[i - 1] 335 toPos = pos 336 if fromPos != toPos: 337 result.append(sub(toPos, sideOffset(fromPos, toPos, amount))) 338 else: 339 fromPos = shape[i - 1] 340 toPos = shape[i + 1] 341 # check for narrow turns 342 if narrow(fromPos, pos, toPos, amount): 343 # print("narrow at i=%s pos=%s" % (i, pos)) 344 pass 345 else: 346 a = sideOffset(fromPos, pos, -amount) 347 b = sideOffset(pos, toPos, -amount) 348 c = orthoIntersection(a, b) 349 if c is not None: 350 pos2 = add(pos, c) 351 else: 352 extend = norm(sub(pos, fromPos)) 353 pos2 = add(pos, mul(extend, amount)) 354 result.append(pos2) 355 # print("move2side", amount) 356 # print(shape) 357 # print(result) 358 # print() 359 return result
366def splitPolygonAtLengths2D(polygon, lengths): 367 """ 368 Returns the polygon segments split at the given 2D-lengths. 369 """ 370 if (len(polygon) <= 1 or len(lengths) == 0): 371 return [polygon] 372 offsets = [offset for offset in sorted(lengths) if offset > 0.0 and offset < polyLength(polygon)] 373 ret = [] 374 seenLength = 0 375 curr = polygon[0] 376 polygonIndex = 0 377 for offset in offsets: 378 currSlice = [curr] 379 while polygonIndex < len(polygon) - 1: 380 p = polygon[polygonIndex + 1] 381 if offset < seenLength + distance(curr, p): 382 splitPos = positionAtOffset(curr, p, offset - seenLength) 383 if not isclose(distance(currSlice[-1], splitPos), 0): 384 currSlice.append(splitPos) 385 seenLength += distance(curr, splitPos) 386 curr = splitPos 387 break 388 else: 389 if not isclose(distance(currSlice[-1], p), 0): 390 currSlice.append(p) 391 seenLength += distance(curr, p) 392 curr = p 393 polygonIndex += 1 394 ret.append(currSlice) 395 if polygonIndex < len(polygon) - 1: 396 finalSlice = [curr] + polygon[polygonIndex + 1:] 397 ret.append(finalSlice) 398 return ret
Returns the polygon segments split at the given 2D-lengths.
401def intersectsAtLengths2D(polygon1, polygon2): 402 """ 403 Returns the 2D-length from polygon1's start to all intersections between polygon1 and polygon2. 404 """ 405 ret = [] 406 if (len(polygon1) == 0 or len(polygon2) == 0): 407 return ret 408 polygon1Length = polyLength(polygon1) 409 for j in range(len(polygon2) - 1): 410 p21 = polygon2[j] 411 p22 = polygon2[j + 1] 412 pos = 0.0 413 for i in range(len(polygon1) - 1): 414 p11 = polygon1[i] 415 p12 = polygon1[i + 1] 416 pIntersection = [0.0, 0.0] 417 if intersectsLineSegment(p11, p12, p21, p22, 0.0, pIntersection, True): 418 for k in range(0, len(pIntersection), 2): 419 length = pos + distance(p11, (pIntersection[k], pIntersection[k + 1])) 420 # special case for closed polygons 421 if isClosedPolygon(polygon1) and isclose(length, polygon1Length): 422 length = 0.0 423 # check for duplicate 424 isDuplicate = False 425 for result in ret: 426 if isclose(length, result): 427 isDuplicate = True 428 break 429 if not isDuplicate: 430 ret.append(length) 431 pos += distance(p11, p12) 432 return ret
Returns the 2D-length from polygon1's start to all intersections between polygon1 and polygon2.
435def intersectsPolygon(polygon1, polygon2): 436 """ 437 Returns whether the polygons intersect on at least one of their segments. 438 """ 439 if (len(polygon1) < 2 or len(polygon2) < 2): 440 return False 441 for i in range(len(polygon1) - 1): 442 p11 = polygon1[i] 443 p12 = polygon1[i + 1] 444 for j in range(len(polygon2) - 1): 445 p21 = polygon2[j] 446 p22 = polygon2[j + 1] 447 if intersectsLineSegment(p11, p12, p21, p22): 448 return True 449 return False
Returns whether the polygons intersect on at least one of their segments.
453def intersectsLineSegment(p11, p12, p21, p22, withinDist=0.0, pIntersection=None, storeEndPointsIfCoincident=False): 454 """ 455 Returns whether the line segments defined by Line p11,p12 and Line p21,p22 intersect. 456 If not set to 'None', 'pIntersection' serves as a storage for the intersection point(s). 457 Parameter 'storeEndPointsIfCoincident' is an option for storing the endpoints of the 458 line segment defined by the intersecting set of line1 and line2 if applicable. 459 """ 460 eps = sys.float_info.epsilon 461 # dy2 * dx1 - dx2 * dy1 462 denominator = (p22[1] - p21[1]) * (p12[0] - p11[0]) - (p22[0] - p21[0]) * (p12[1] - p11[1]) 463 # dx2 * (p11.y - p21.y) - dy2 * (p11.x - p21.x) 464 numera = (p22[0] - p21[0]) * (p11[1] - p21[1]) - (p22[1] - p21[1]) * (p11[0] - p21[0]) 465 # dx1 * (p11.y - p21.y) - dy1 * (p11.x - p21.x) 466 numerb = (p12[0] - p11[0]) * (p11[1] - p21[1]) - (p12[1] - p11[1]) * (p11[0] - p21[0]) 467 # Are the lines coincident? 468 if (math.fabs(numera) < eps and math.fabs(numerb) < eps and math.fabs(denominator) < eps): 469 a1 = 0.0 470 a2 = 0.0 471 a3 = 0.0 472 a4 = 0.0 473 a = -1e12 474 isVertical = (p11[0] == p12[0]) 475 if not isVertical: 476 # line1 and line2 are not vertical 477 a1 = p11[0] if p11[0] < p12[0] else p12[0] 478 a2 = p12[0] if p11[0] < p12[0] else p11[0] 479 a3 = p21[0] if p21[0] < p22[0] else p22[0] 480 a4 = p22[0] if p21[0] < p22[0] else p21[0] 481 else: 482 # line1 and line2 are vertical 483 a1 = p11[1] if p11[1] < p12[1] else p12[1] 484 a2 = p12[1] if p11[1] < p12[1] else p11[1] 485 a3 = p21[1] if p21[1] < p22[1] else p22[1] 486 a4 = p22[1] if p21[1] < p22[1] else p21[1] 487 if a1 <= a3 and a3 <= a2: 488 # one endpoint of line2 lies on line1 489 if a4 <= a2: 490 # line2 is a subset of line1 491 a = (a3 + a4) / 2.0 492 if storeEndPointsIfCoincident and pIntersection is not None: 493 pIntersection[0] = p21[0] 494 pIntersection[1] = p21[1] 495 pIntersection.append(p22[0]) 496 pIntersection.append(p22[1]) 497 return True 498 else: 499 # the other endpoint of line2 lies beyond line1 500 a = (a3 + a2) / 2.0 501 if storeEndPointsIfCoincident and pIntersection is not None: 502 if not isVertical: 503 pIntersection[0] = a3 504 pIntersection[1] = p21[1] if p21[0] < p22[0] else p22[1] 505 pIntersection.append(a2) 506 pIntersection.append(p12[1] if p11[0] < p12[0] else p11[1]) 507 else: 508 pIntersection[0] = p21[0] if p21[1] < p22[1] else p22[0] 509 pIntersection[1] = a3 510 pIntersection.append(p12[0] if p11[1] < p12[1] else p11[0]) 511 pIntersection.append(a2) 512 return True 513 if a3 <= a1 and a1 <= a4: 514 # one endpoint of line1 lies on line2 515 if a2 <= a4: 516 # line1 is a subset of line2 517 a = (a1 + a2) / 2.0 518 if storeEndPointsIfCoincident and pIntersection is not None: 519 pIntersection[0] = p11[0] 520 pIntersection[1] = p11[1] 521 pIntersection.append(p12[0]) 522 pIntersection.append(p12[1]) 523 return True 524 else: 525 # the other endpoint of line1 lies beyond line2 526 a = (a1 + a4) / 2.0 527 if storeEndPointsIfCoincident and pIntersection is not None: 528 if not isVertical: 529 pIntersection[0] = a1 530 pIntersection[1] = p11[1] if p11[0] < p12[0] else p12[1] 531 pIntersection.append(a4) 532 pIntersection.append(p22[1] if p21[0] < p22[0] else p21[1]) 533 else: 534 pIntersection[0] = p11[0] if p11[1] < p12[1] else p12[0] 535 pIntersection[1] = a1 536 pIntersection.append(p22[0] if p21[1] < p22[1] else p21[0]) 537 pIntersection.append(a4) 538 return True 539 if a != -1e12: 540 if pIntersection is not None: 541 if not isVertical: 542 # line1 and line2 are not vertical 543 mu = (a - p11[0]) / (p12[0] - p11[0]) 544 x = a 545 y = p11[1] + mu * (p12[1] - p11[1]) 546 else: 547 # line1 and line2 are vertical 548 x = p11[0] 549 y = a 550 if p12[1] == p11[1]: 551 mu = 0 552 else: 553 mu = (a - p11[1]) / (p12[1] - p11[1]) 554 pIntersection[0] = x 555 pIntersection[1] = y 556 return True 557 return False 558 # Are the lines parallel? 559 if math.fabs(denominator) < eps: 560 return False 561 # Is the intersection along the segments? 562 mua = numera / denominator 563 # Reduce rounding errors for lines ending in the same point 564 if math.fabs(p12[0] - p22[0]) < eps and math.fabs(p12[1] - p22[1]) < eps: 565 mua = 1.0 566 else: 567 offseta = withinDist / distance(p11, p12) 568 offsetb = withinDist / distance(p21, p22) 569 mub = numerb / denominator 570 if (mua < -offseta or mua > 1 + offseta or mub < -offsetb or mub > 1 + offsetb): 571 return False 572 if pIntersection is not None: 573 x = p11[0] + mua * (p12[0] - p11[0]) 574 y = p11[1] + mua * (p12[1] - p11[1]) 575 mu = mua 576 pIntersection[0] = x 577 pIntersection[1] = y 578 return True
Returns whether the line segments defined by Line p11,p12 and Line p21,p22 intersect. If not set to 'None', 'pIntersection' serves as a storage for the intersection point(s). Parameter 'storeEndPointsIfCoincident' is an option for storing the endpoints of the line segment defined by the intersecting set of line1 and line2 if applicable.
581def debugPrint(polygon, polyID): 582 """print polygon in a way that can be processed with debug2poly.py""" 583 print("%s=%s" % (polyID, ' '.join(['%s,%s' % p for p in polygon])))
print polygon in a way that can be processed with debug2poly.py