Updated SMHI API and improved the polygon contains logic

This commit is contained in:
2026-04-18 12:37:37 +02:00 Verified
parent 49e52d9613
commit 131b74daf9
5 changed files with 65 additions and 70 deletions
View File
+22 -58
View File
@@ -1,6 +1,7 @@
# tools/geometry.py
from dataclasses import dataclass
import math
@dataclass
@@ -68,8 +69,8 @@ class LineString:
return min(line.distance_to_point(point) for line in self.lineList)
def is_intersecting(self, point: Point) -> bool:
"""Checks if a given point lies exactly on the LineString."""
return self.distance_to(point) == 0.0
"""Checks if a given point lies exactly on the LineString (with tolerance)."""
return math.isclose(self.distance_to(point), 0.0, abs_tol=1e-9)
class Polygon:
@@ -78,72 +79,35 @@ class Polygon:
def __init__(self, points: list[list[float]]) -> None:
self.listPoints: list[Point] = [Point(p[0], p[1]) for p in points]
def on_line(self, l1: Line, p: Point) -> bool:
"""Checks if collinear point 'p' lies strictly on the line segment 'l1'."""
return min(l1.p1.x, l1.p2.x) <= p.x <= max(l1.p1.x, l1.p2.x) and min(
l1.p1.y, l1.p2.y
) <= p.y <= max(l1.p1.y, l1.p2.y)
def direction(self, a: Point, b: Point, c: Point) -> int:
"""
Finds the orientation of an ordered triplet (a, b, c).
Returns:
0 : Collinear
1 : Clockwise
2 : Counterclockwise
"""
val = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y)
if val == 0:
return 0
return 2 if val < 0 else 1
def is_intersect(self, l1: Line, l2: Line) -> bool:
"""Checks if line segment l1 intersects with line segment l2."""
dir1 = self.direction(l1.p1, l1.p2, l2.p1)
dir2 = self.direction(l1.p1, l1.p2, l2.p2)
dir3 = self.direction(l2.p1, l2.p2, l1.p1)
dir4 = self.direction(l2.p1, l2.p2, l1.p2)
# General case intersection
if dir1 != dir2 and dir3 != dir4:
return True
# Special collinear cases
if dir1 == 0 and self.on_line(l1, l2.p1):
return True
if dir2 == 0 and self.on_line(l1, l2.p2):
return True
if dir3 == 0 and self.on_line(l2, l1.p1):
return True
if dir4 == 0 and self.on_line(l2, l1.p2):
return True
return False
def contains(self, p: Point) -> bool:
"""
Determines if a point is strictly inside the Polygon using the Ray-Casting algorithm.
Draws a horizontal line to the right of the point and counts edge intersections.
Determines if a point is inside the Polygon using the Even-Odd rule.
Includes an explicit check for points lying exactly on the boundary edges.
"""
n = len(self.listPoints)
if n < 3:
return False
# Create a horizontal ray starting from the point and going infinitely right
exline = Line(p, Point(99999.0, p.y))
count = 0
inside = False
j = n - 1
for i in range(n):
side = Line(self.listPoints[i], self.listPoints[(i + 1) % n])
pi = self.listPoints[i]
pj = self.listPoints[j]
if self.is_intersect(side, exline):
# If the point is collinear with the side, check if it's strictly on the side
if self.direction(side.p1, p, side.p2) == 0:
return self.on_line(side, p)
count += 1
cross_product = (p.y - pi.y) * (pj.x - pi.x) - (p.x - pi.x) * (pj.y - pi.y)
if math.isclose(cross_product, 0.0, abs_tol=1e-9):
if min(pi.x, pj.x) <= p.x <= max(pi.x, pj.x) and min(
pi.y, pj.y
) <= p.y <= max(pi.y, pj.y):
return True
# If the number of intersections is odd, the point is inside the polygon
return bool(count & 1)
if ((pi.y > p.y) != (pj.y > p.y)) and (
p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x
):
inside = not inside
j = i
return inside
class MultiPolygon: