diff --git a/lib/linesweep.py b/lib/linesweep.py index b9c4663..2e467c5 100644 --- a/lib/linesweep.py +++ b/lib/linesweep.py @@ -1,5 +1,7 @@ -from bisect import bisect_left, bisect_right +from bisect import bisect_left, bisect_right,insort import unittest +from collections import namedtuple +import math def find_intersections(horizontal_segments, vertical_segments, epsilon=0.0): @@ -27,22 +29,199 @@ def find_intersections(horizontal_segments, vertical_segments, epsilon=0.0): return intersections -class TestStringMethods(unittest.TestCase): - def test_sweep(self): - horizontal = [((1, 2), (5, 2)), ((3, 4), (7, 4))] - vertical = [((4, 1), (4, 5)), ((6, 3), (6, 6))] - a = find_intersections(horizontal, vertical) - self.assertEqual( [(4,2),(4,4),(6,4)], a) - def test_tol(self): - horizontal = [((0, 0), (10, 0))] - vertical = [((4, 0.6), (4, 5)), ((6, 0.2), (6, 6)),((8,-0.2),(8, 4))] +Point = namedtuple('Point', ['x', 'y']) +Segment = namedtuple('Segment', ['p1', 'p2']) - b = find_intersections(horizontal, vertical, 0.5) - self.assertEqual( length(b), 2) +def ccw(a, b, c): + return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x) +def intersect(s1, s2, tol=1e-8): + """Erweiterte Schnittprüfung mit Toleranz.""" + # Klassische Schnitttest + a, b = s1.p1, s1.p2 + c, d = s2.p1, s2.p2 + if ccw(a, c, d) != ccw(b, c, d) and ccw(a, b, c) != ccw(a, b, d): + return True + + # Zusätzliche Toleranzprüfung: Mindestabstand der Segmente + return segment_distance(s1, s2) < tol + +def point_distance(p1, p2): + return math.hypot(p1.x - p2.x, p1.y - p2.y) + +def point_segment_distance(p, seg): + """Abstand eines Punkts zu einem Segment.""" + x, y = p.x, p.y + x1, y1 = seg.p1.x, seg.p1.y + x2, y2 = seg.p2.x, seg.p2.y + + dx, dy = x2 - x1, y2 - y1 + if dx == dy == 0: + return point_distance(p, seg.p1) + + t = max(0, min(1, ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy))) + nearest = Point(x1 + t * dx, y1 + t * dy) + return point_distance(p, nearest) + +def segment_distance(s1, s2): + """Kleinster Abstand zweier Segmente.""" + return min( + point_segment_distance(s1.p1, s2), + point_segment_distance(s1.p2, s2), + point_segment_distance(s2.p1, s1), + point_segment_distance(s2.p2, s1), + ) + +def segment_cmp_x(segment, x): + p1, p2 = segment.p1, segment.p2 + if p1.x == p2.x: + return min(p1.y, p2.y) + slope = (p2.y - p1.y) / (p2.x - p1.x) + y = p1.y + slope * (x - p1.x) + return y + +def schnittpunkttest(segments, tolerance=1e-8): + events = [] + for seg in segments: + left = seg.p1 if seg.p1.x < seg.p2.x else seg.p2 + right = seg.p2 if seg.p1.x < seg.p2.x else seg.p1 + events.append((left.x, True, seg)) + events.append((right.x, False, seg)) + + events.sort() + L = [] + + for x, is_left, seg in events: + key = lambda s: segment_cmp_x(s, x) + idx = bisect_left(L, seg, key=key) + + if is_left: + insort(L, seg, key=key) + idx = bisect_left(L, seg, key=key) + pred = L[idx - 1] if idx > 0 else None + succ = L[idx + 1] if idx < len(L) - 1 else None + + if (pred and intersect(pred, seg, tolerance)) or (succ and intersect(succ, seg, tolerance)): + #print("ja") + return + else: + idx = bisect_left(L, seg, key=key) + if idx < len(L) and L[idx] == seg: + pred = L[idx - 1] if idx > 0 else None + succ = L[idx + 1] if idx < len(L) - 1 else None + L.pop(idx) + if pred and succ and intersect(pred, succ, tolerance): + #print("ja") + return + + #print("nein") + +def sektionen(segmente, tolerance): + laenge = len(segmente) + lseg = list() + for r1 in range(laenge): + s1 = segmente[r1] + p1 = Point(s1[0][0],s1[0][1]) + p2 = Point(s1[1][0],s1[1][1]) + seg1 = Segment( p1, p2 ) + lseg.append(seg1) + schnittpunkttest(lseg, tolerance) + + + + +# class TestSweepMethods(unittest.TestCase): + +# def test_sweep(self): +# horizontal = [((1, 2), (5, 2)), ((3, 4), (7, 4))] +# vertical = [((4, 1), (4, 5)), ((6, 3), (6, 6))] + +# a = find_intersections(horizontal, vertical) +# self.assertEqual( [(4,2),(4,4),(6,4)], a) + +# def test_tol_hor(self): +# """Testet die Anzahl der Schnittpunkte bei gegebener Toleranz +# schnittpunkt zwischen gegebener horizontalen und zu kurzen vertikalen +# """ +# horizontal = [((0, 0), (10, 0))] +# vertical = [((4, 0.6), (4, 5)), ((6, 0.2), (6, 6)),((8,-0.2),(8, 4))] + +# b = find_intersections(horizontal, vertical, 0.5) +# self.assertEqual( len(b), 2) +# self.assertEqual( [(6,0),(8,0)], b) + +# def test_tol_ver(self): +# """Testet die Anzahl der Schnittpunkte bei gegebener Toleranz +# schnittpunkt zwischen gegebener vertikalen und zu kurzen horizontalen +# """ +# horizontal = [((0.2, 0), (9.8, 0))] +# vertical = [((0, 0), (0, 10)), ((10, 0), (10, 10))] + +# b = find_intersections(horizontal, vertical, 0.5) +# self.assertEqual( len(b), 2) +# self.assertEqual( [(6,0),(8,0)], b) + +# def test_tol_diag(self): +# """Testet die Anzahl der Schnittpunkte bei gegebener Toleranz +# Horizontale mit Diagonalen +# """ +# horizontal = [((0, 0), (5, 0))] +# diagonal = [((0, -5), (10, 5))] + +# b = find_intersections(horizontal, diagonal, 0.5) +# self.assertEqual( len(b), 1) +# self.assertEqual( [(0,0),(0,0)], b) + +class TestScanLineAllgMethods(unittest.TestCase): + # def test_sweep(self): + + # seks = [((1, 2), (5, 2)), ((3, 4), (7, 4)), ((4, 1), (4, 5)), ((6, 3), (6, 6))] + + # a = sektionen(seks) + # self.assertEqual( [(4,2),(4,4),(6,4)], a) + + # def test_tol_hor(self): + # """Testet die Anzahl der Schnittpunkte bei gegebener Toleranz + # schnittpunkt zwischen gegebener horizontalen und zu kurzen vertikalen + # """ + + # seks = [((0, 0), (10, 0)), ((4, 0.6), (4, 5)), ((6, 0.2), (6, 6)),((8,-0.2),(8, 4))] + + # b = sektionen(seks, 0.5) + # self.assertEqual( len(b), 2) + # self.assertEqual( [(6,0),(8,0)], b) + + # def test_tol_ver(self): + # """Testet die Anzahl der Schnittpunkte bei gegebener Toleranz + # schnittpunkt zwischen gegebener vertikalen und zu kurzen horizontalen + # """ + + # seks = [((0.2, 0), (9.8, 0)), ((0, 0), (0, 10)), ((10, 0), (10, 10))] + + # b = sektionen(seks, 0.5) + # self.assertEqual( len(b), 2) + # self.assertEqual( [(6,0),(8,0)], b) + + # def test_tol_diag(self): + # """Testet die Anzahl der Schnittpunkte bei gegebener Toleranz + # Horizontale mit Diagonalen + # """ + + # seks = [((0, 0), (5, 0)), ((0, -5), (10, 5))] + + # b = sektionen(seks, 0.5) + # self.assertEqual( len(b), 1) + # self.assertEqual( [(6,0),(8,0)], b) + + def test_chatgpt(self): + s1 = Segment(Point(0, 0), Point(4, 4)) # Diagonale + s2 = Segment(Point(0, 4), Point(4, 0)) # Kreuzt s1 → Schnitt + s3 = Segment(Point(5, 5), Point(6, 6)) # Weit entfernt, kein Schnitt + s4 = Segment(Point(1, 1), Point(1, 3)) # Schneidet s1 → weiterer Schnitt + schnittpunkttest([s1, s2, s3, s4], tolerance=1e-8) # Ausgabe: ja if __name__ == '__main__': unittest.main() \ No newline at end of file