diff --git a/lib/linesweep.py b/lib/linesweep.py index 2e467c5..83c7fa3 100644 --- a/lib/linesweep.py +++ b/lib/linesweep.py @@ -2,6 +2,8 @@ from bisect import bisect_left, bisect_right,insort import unittest from collections import namedtuple import math +from shapely.geometry import LineString +from itertools import combinations def find_intersections(horizontal_segments, vertical_segments, epsilon=0.0): @@ -30,105 +32,36 @@ def find_intersections(horizontal_segments, vertical_segments, epsilon=0.0): return intersections +def find_all_intersections(segments): + # segments: List of ((x1, y1), (x2, y2)) + lines = [LineString([p1, p2]) for p1, p2 in segments] + intersections = set() + for l1, l2 in combinations(lines, 2): + if l1.intersects(l2): + point = l1.intersection(l2) + if point.geom_type == "Point": + intersections.add((point.x, point.y)) + elif point.geom_type == "MultiPoint": + for p in point.geoms: + intersections.add((p.x, p.y)) -Point = namedtuple('Point', ['x', 'y']) -Segment = namedtuple('Segment', ['p1', 'p2']) + return list(intersections) -def ccw(a, b, c): - return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x) +def find_touch_cut_close(segments): + # segments: Liste aus ((x1, y1), (x2,y2)) mit 1=Anfang, 2=Ende + verbindungen = [] + for i,(s1_start, s1_end) in enumerate(segments): + line1 = LineString([s1_start, s1_end]) + for j, ([s2_start, s2_end]) in enumerate(segments): + if i >= j: + continue + line2 = LineString([s2_start,s2_end]) -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) + if line1.distance(line2) < 0.5: #Toleranz 0,5 Hardcode + verbindungen.append((i,j,line1.intersection(line2))) + + return verbindungen @@ -175,53 +108,84 @@ def sektionen(segmente, tolerance): # self.assertEqual( len(b), 1) # self.assertEqual( [(0,0),(0,0)], b) -class TestScanLineAllgMethods(unittest.TestCase): - # def test_sweep(self): +# 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))] +# 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) +# a = find_all_intersections(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 - # """ +# 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))] +# 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) +# b = find_all_intersections(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 - # """ +# 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))] +# 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) +# b = find_all_intersections(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 - # """ +# 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))] +# 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) +# b = find_all_intersections(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 +# def test_shapely(self): +# # Beispiel +# segments = [ +# ((0, 0), (4, 4)), # Diagonal / +# ((0, 4), (4, 0)), # Diagonal \ +# ((2, -1), (2, 5)), # Vertikal +# ((-1, 2), (5, 2)) # Horizontal +# ] + +# result = find_all_intersections(segments) +# print(result) + +class TestShapely(unittest.TestCase): + def test_shapely_2_Stern(self): + # Beispiel !Stern mit gemeinsamen Schnittpunkt in (2,2) + segments = [ + ((0, 0), (4, 4)), # Diagonal / + ((0, 4), (4, 0)), # Diagonal \ + ((2, -1), (2, 5)), # Vertikal + ((-1, 2), (5, 2)) # Horizontal + ] + + result = find_touch_cut_close(segments) + self.assertEqual(len(result),1) + + def test_shapely_2_Rand(self): + #Test mit Strecken horizontal, vertikal, diagonal, Schnittpunkt u. Annaeherung + segments = [ + ((0, 0), (5, 0)), # Horizontale + ((0, 0), (0, 5)), # Vertikale mit Schnitt Horizontal in 0,0 + ((0.25, 2), (5, 2)), # Horizontale mit 0.25 Abstand zu vertikaler + ((0.25, 4), (4, 3)) # Diagonale \ mit 0.25 Abstand zu vertikaler und 1 zu horizontaler + ] + + result = find_touch_cut_close(segments) + self.assertEqual(len(result),3) + if __name__ == '__main__': unittest.main() \ No newline at end of file