From 559a4690c8b108514ec3f0c1fc59db5880ee16d3 Mon Sep 17 00:00:00 2001 From: lertlmaier Date: Tue, 13 May 2025 13:48:49 +0200 Subject: [PATCH] Klassendeklaration in linesweep_circle. Umbau des graphbuild auf Stuktur mit unittests --- lib/graphbuild.py | 100 ++++++++++++++ lib/linesweep_circle.py | 289 +++++++++++++++++++++++++++------------- 2 files changed, 300 insertions(+), 89 deletions(-) create mode 100644 lib/graphbuild.py diff --git a/lib/graphbuild.py b/lib/graphbuild.py new file mode 100644 index 0000000..acdf390 --- /dev/null +++ b/lib/graphbuild.py @@ -0,0 +1,100 @@ +import networkx as nx +from shapely.geometry import Point +import matplotlib.pyplot as plt +import unittest +from linesweep_circle import NodeIDs + + + +def graphbuild(d_racks_segments, d_rack_conn_points): + # 2. Graph erstellen + G = nx.Graph() + + # 3. Knoten und Kanten aus d_racts_segments hinzufügen + for rack, points in d_racks_segments.items(): + start = points[0] + end = points[1] + G.add_node(start) # Knoten für Startpunkt + G.add_node(end) # Knoten für Endpunkt + G.add_edge(start, end) # Kante zwischen Start und Endpunkt + + # 4. Verbindungen aus d_rack_conn_points hinzufügen + for connection, points in d_rack_conn_points.items(): + for point in points: + # Wir fügen die Kante zwischen den Punkten der Verbindung hinzu + # Hinweis: Wir nehmen hier an, dass der Punkt von beiden Racks als Verbindungspunkt betrachtet wird + G.add_edge(point, point) # Verbindungspunkt als Kante + + + + return G + +class TestShapely(unittest.TestCase): + def test_graph(self): + d_racks_segments = { + 'Rack_1-0': [Point(4946.5, 15865.5), Point(4946.5, 3777.6)], + 'Rack_2-0': [Point(0.1, 57.6), Point(0.1, 3777.6)], + 'Rack_2-1': [Point(0.1, 3777.6), Point(14755.1, 3777.6)], + 'Rack_3-0': [Point(185.1, 15865.5), Point(12450.7, 15865.5)], + 'Rack_4-0': [Point(2866.6, 15865.5), Point(2866.6, 3777.6)], + 'Rack_5-0': [Point(8866.1, 15865.5), Point(8866.1, 3777.6)] + } + + d_rack_conn_points = { + 'Rack_2-0 + Rack_2-1': [Point(0.1, 3777.6)], + 'Rack_2-1 + Rack_1-0': [Point(4946.5, 3777.6)], + 'Rack_2-1 + Rack_4-0': [Point(2866.6, 3777.6)], + 'Rack_2-1 + Rack_5-0': [Point(8866.1, 3777.6)], + 'Rack_3-0 + Rack_1-0': [Point(4946.5, 15865.5)], + 'Rack_3-0 + Rack_4-0': [Point(2866.6, 15865.5)], + 'Rack_3-0 + Rack_5-0': [Point(8866.1, 15865.5)] + } + + G = graphbuild(d_racks_segments, d_rack_conn_points) + + #print(nx.has_path(G, Point(4946.5, 15865.5), Point(0.1, 57.6))) + + + # 5. Graph anzeigen + nodes_str = "[, , , ," \ + " , , , ," \ + " , , ]" + + edges_str = "[(, ), (," \ + " ), (, )," \ + " (, ), (, )," \ + " (, ), (, )," \ + " (, ), (, )," \ + " (, ), (, )," \ + " (, ), (, )]" + + nodes = G.nodes + + + + self.assertEqual(str(G.nodes), nodes_str) + self.assertEqual(str(G.edges), edges_str) + + allids = NodeIDs(nodes) + ids = allids.get_ids(nodes) + + + self.assertEqual(len(ids), len(nodes)) + + + + + # 6. Optional: Visualisierung des Graphen + # Dies ist ein sehr grundlegendes Plotten. Bei Bedarf könnte man es weiter anpassen. + pos = {node: (node.x, node.y) for node in G.nodes()} # Positionen der Knoten aus den Punkten + + # Plotten des Graphen + #nx.draw(G, pos, with_labels=False, node_size=10, font_size=8) + #plt.show() + + + + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/lib/linesweep_circle.py b/lib/linesweep_circle.py index 09bd138..d00317d 100644 --- a/lib/linesweep_circle.py +++ b/lib/linesweep_circle.py @@ -1,115 +1,226 @@ import json from shapely.geometry import LineString, Point from shapely.ops import nearest_points +import unittest -# === Konfiguration === -tol = 200 -tol_step = 10 +class NodeIDs(): + def __init__(self, points=[]): + self._counter = 0 + self._cord2id = dict() + self._id2cord = dict() + self.add_points(points) -# === Lade JSON-Daten === -with open("C:/10-Develop/kabellaengen/work/easy_positions.json", "r") as f: - data = json.load(f) + def add_point(self, point:Point): + self._counter += 1 + self._cord2id[f"{point.x} {point.y}"] = self._counter + self._id2cord[f"{self._counter}"] = point -racks_json = data["racks"] #Suchen nach Racks in gesamter Json-Übergabe + def add_points(self, points): + for p in points: + self.add_point(p) -# === 1. Racks in Segmente zerlegen === -''' Hier werden Racks, die aus "echter" Polylinie bestehen (mehrere Nodes, z.B. Rack 2 in easy.dxf) in einzelne Segmente zerlegt (Node1 -> Node2, Node2 -> Node3)''' -rack_segments = [] -for rack_id, nodes in racks_json.items(): - # Sortiere Node_1, Node_2, ... - sorted_keys = sorted(nodes.keys(), key=lambda k: int(k.split("_")[1])) - coords = [tuple(nodes[k]) for k in sorted_keys] + def get_id(self, point:Point) -> int: + return self._cord2id[f"{point.x} {point.y}"] + + def get_point(self, nid:int) -> Point: + return self._id2cord[f"{nid}"] + + def get_ids(self, points:list[Point]) -> list[int]: + ret = list() + for p in points: + nid = self.get_id(p) + ret.append(nid) + return ret + + def get_points(self, nids:list[int]) -> list[Point]: + ret = list() + for n in nids: + c = self.get_point(n) + ret.append(c) + return ret - for i in range(len(coords) - 1): - p1, p2 = coords[i], coords[i+1] - line = LineString([p1, p2]) - rack_segments.append((rack_id, i, line)) +def to_json(d, pretty: bool = True) -> str: + return json.dumps(d, indent=2 if pretty else None, default=str) #ensure_ascii false für darstellung von "ue" -# === 2. Alle Endpunkte sammeln === -''' Alle Endpunkte aller Racks als Point gespeichert, um shapely funktionen verwenden zu können''' -segment_endpoints = [] -for rack_id, idx, line in rack_segments: - for pt in [line.coords[0], line.coords[1]]: - segment_endpoints.append((rack_id, idx, Point(pt))) +def rack_segmentation(racks): + ''' Racks werden zu LineString konvertiert. Racks bestehend aus Polylinine werden in einzelne Segmente zerlegt.''' + rack_segments = [] + for rack_id, nodes in racks.items(): + # Sortiere Node_1, Node_2, ... + sorted_keys = sorted(nodes.keys(), key=lambda k: int(k.split("_")[1])) + coords = [tuple(nodes[k]) for k in sorted_keys] + + for i in range(len(coords) - 1): + p1, p2 = coords[i], coords[i+1] + line = LineString([p1, p2]) + rack_segments.append((rack_id, i, line)) + + return(rack_segments) +def rack_endpoints(rack_segments): + ''' Endpunkte der Racks werden in Points konvertiert''' + segment_endpoints = [] + for rack_id, idx, line in rack_segments: + for pt in [line.coords[0], line.coords[1]]: + segment_endpoints.append((rack_id, idx, Point(pt))) + + return(segment_endpoints) + +def increase_circle(tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_idx, verbindungen, endpoint_pinned): + ''' vergrößere Kreis bis Schnittpunkt mit Rack entsteht. + + Argumente: + tol, tol_step -- Toleranz und Schittweite + line -- linestring der entlang gelaufen wird + rack_id, idx -- Rack_id und index von dem linestring stammt + pt -- Punkt der Überprüft wird + other_rack_id, other_idx -- Rack zu welchem der zu untersuchende Punkt gehört + verbindungen -- Liste an die angefügt wird und die verbindungspunkte speichert + endpoint_pinned -- Liste, die Rack und index von dem untersuchten Punkt und den neuen angepinnten Punkt speichert + ''' + radius = tol_step + while radius <= tol: + circle = pt.buffer(radius) + if circle.intersects(line): + contact = circle.intersection(line) + if contact.geom_type == "Point": + nearest = contact + else: + nearest = nearest_points(pt, contact)[1] + #print(f" 🟡 Kreisberührung bei {nearest} mit {rack_id}_{idx}") + verbindungen.append((rack_id, idx, other_rack_id, other_idx, nearest)) + + # Füge verschobenen Endpunkt zu Liste hinzu. [Punkt gehört zu Rack_Nr, alter Punkt, neuer Punkt, gepinnt an Target_Rack] + endpoint_pinned.append((other_rack_id, other_idx, pt, nearest, rack_id)) + + break + radius += tol_step # === 3. Verbindungen suchen === -verbindungen = [] -endpoint_pinned = [] +def search_connections(rack_segments, segment_endpoints, tol, tol_step): + verbindungen = [] + endpoint_pinned = [] -# === A: Echte Schnittpunkte zwischen Linien finden === -''' Alle Segmente mit allen überprüfen, um echte SP zu finden''' -for i, (rack_id1, idx1, line1) in enumerate(rack_segments): - print(f"\n=== Prüfe {rack_id1}_{idx1} auf echte Schnittpunkte") - for j, (rack_id2, idx2, line2) in enumerate(rack_segments): - if i >= j: - continue # keine Duplikate / sich selbst + # === A: Echte Schnittpunkte zwischen Linien finden === + ''' Alle Segmente mit allen überprüfen, um echte SP zu finden''' + for i, (rack_id1, idx1, line1) in enumerate(rack_segments): + #print(f"\n=== Prüfe {rack_id1}_{idx1} auf echte Schnittpunkte") + for j, (rack_id2, idx2, line2) in enumerate(rack_segments): + if i >= j: + continue # keine Duplikate / sich selbst - if line1.intersects(line2): - inter = line1.intersection(line2) - if inter.geom_type == "Point": - print(f"✅ Exakter Schnittpunkt {inter} zwischen {rack_id1}_{idx1} und {rack_id2}_{idx2}") - verbindungen.append((rack_id1, idx1, rack_id2, idx2, inter)) + if line1.intersects(line2): + inter = line1.intersection(line2) + if inter.geom_type == "Point": + #print(f"✅ Exakter Schnittpunkt {inter} zwischen {rack_id1}_{idx1} und {rack_id2}_{idx2}") + verbindungen.append((rack_id1, idx1, rack_id2, idx2, inter)) -# === B: Näherungsweise Verbindung durch Toleranz-Kreise === -''' Entlanglaufen der Racks und Scan nach Endpunkten im Toleranzbereich''' -for rack_id, idx, line in rack_segments: - print(f"\n=== Prüfe {rack_id}_{idx1} auf Punkte im Toleranzbereich") - for other_rack_id, other_idx, pt in segment_endpoints: - if rack_id == other_rack_id: - continue # ignoriere eigene Endpunkte + # === B: Näherungsweise Verbindung durch Toleranz-Kreise === + ''' Entlanglaufen der Racks und Scan nach Endpunkten im Toleranzbereich''' + for rack_id, idx, line in rack_segments: + #print(f"\n=== Prüfe {rack_id}_{idx1} auf Punkte im Toleranzbereich") + for other_rack_id, other_idx, pt in segment_endpoints: + if rack_id == other_rack_id: + continue # ignoriere eigene Endpunkte - # Exakte Schnittpunkte ignorieren - if line.intersects(pt): - continue + # Exakte Schnittpunkte ignorieren + if line.intersects(pt): + continue - dist = line.distance(pt) - if dist < tol: - print(f"🔍 Punkt {pt} liegt {dist:.2f} von Linie {rack_id}_{idx} entfernt") + dist = line.distance(pt) + if dist < tol: + increase_circle(tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_idx, verbindungen, endpoint_pinned) + #print(f"🔍 Punkt {pt} liegt {dist:.2f} von Linie {rack_id}_{idx} entfernt") - radius = tol_step - while radius <= tol: - circle = pt.buffer(radius) - if circle.intersects(line): - contact = circle.intersection(line) - if contact.geom_type == "Point": - nearest = contact - else: - nearest = nearest_points(pt, contact)[1] - print(f" 🟡 Kreisberührung bei {nearest} mit {rack_id}_{idx}") - verbindungen.append((rack_id, idx, other_rack_id, other_idx, nearest)) + # radius = tol_step + # while radius <= tol: + # circle = pt.buffer(radius) + # if circle.intersects(line): + # contact = circle.intersection(line) + # if contact.geom_type == "Point": + # nearest = contact + # else: + # nearest = nearest_points(pt, contact)[1] + # #print(f" 🟡 Kreisberührung bei {nearest} mit {rack_id}_{idx}") + # verbindungen.append((rack_id, idx, other_rack_id, other_idx, nearest)) - # Füge verschobenen Endpunkt zu Liste hinzu. [Punkt gehört zu Rack_Nr, alter Punkt, neuer Punkt, gepinnt an Target_Rack] - endpoint_pinned.append((other_rack_id, other_idx, pt, nearest, rack_id)) - - break - radius += tol_step + # # Füge verschobenen Endpunkt zu Liste hinzu. [Punkt gehört zu Rack_Nr, alter Punkt, neuer Punkt, gepinnt an Target_Rack] + # endpoint_pinned.append((other_rack_id, other_idx, pt, nearest, rack_id)) + + # break + # radius += tol_step -# === Endpunkte aktualisieren === -# Dict erstellen, dass mit dem Key "Rack_id - index" dahinter die Koordinaten von Anfang und Endpunkt speichert -d_racks_segments = dict() + # === Endpunkte aktualisieren === + # Dict erstellen, dass mit dem Key "Rack_id - index" dahinter die Koordinaten von Anfang und Endpunkt speichert + d_racks_segments = dict() -for rack_id, idx, linestring in rack_segments: - key = f"{rack_id}-{idx}" - d_racks_segments[key] = [Point(linestring.coords[0]), Point(linestring.coords[1])] #Alle Racks in ihrer eingelesenen Form zum Dict hinzufügen + for rack_id, idx, linestring in rack_segments: + key = f"{rack_id}-{idx}" + d_racks_segments[key] = [Point(linestring.coords[0]), Point(linestring.coords[1])] #Alle Racks in ihrer eingelesenen Form zum Dict hinzufügen -for rack_id, idx, old_pt, new_pt, taget_rack in endpoint_pinned: #Durch verschobene Endpunkte laufen... - key = f"{rack_id}-{idx}" - coords = d_racks_segments.get(key) + for rack_id, idx, old_pt, new_pt, taget_rack in endpoint_pinned: #Durch verschobene Endpunkte laufen... + key = f"{rack_id}-{idx}" + coords = d_racks_segments.get(key) - if coords: #...und bei Übereinstimmung von Start oder Endkoordinate die ursprüngliche (eingelesene) mit der gepinnten überschreiben - # Vergleich mit Startpunkt - if Point(coords[0]).equals(old_pt): - coords[0] = Point(new_pt.x, new_pt.y) #.x bzw .y übergibt x bzw y Koordinate von Objekt POINT - # Vergleich mit Endpunkt - elif Point(coords[1]).equals(old_pt): - coords[1] = Point(new_pt.x, new_pt.y) + if coords: #...und bei Übereinstimmung von Start oder Endkoordinate die ursprüngliche (eingelesene) mit der gepinnten überschreiben + # Vergleich mit Startpunkt + if Point(coords[0]).equals(old_pt): + coords[0] = Point(new_pt.x, new_pt.y) #.x bzw .y übergibt x bzw y Koordinate von Objekt POINT + # Vergleich mit Endpunkt + elif Point(coords[1]).equals(old_pt): + coords[1] = Point(new_pt.x, new_pt.y) - d_racks_segments[key] = coords # aktualisieren - -# === Ausgabe-Verbindungen === -print("\n=== Gefundene Verbindungen ===") -for v in verbindungen: - print(v) + d_racks_segments[key] = coords # aktualisieren + + #Dict erstellen, dass alle Punkte die an einem Rack anschließen + d_rack_conn_points = dict() + + for conn_to_rack, conn_to_idx, conn_from_rack, conn_from_idx, conn_point in verbindungen: + key = f"{conn_to_rack}-{conn_to_idx} + {conn_from_rack}-{conn_from_idx}" + d_rack_conn_points[key] = [conn_point] + + return [d_racks_segments, d_rack_conn_points] + + + + +class TestLinesweep(unittest.TestCase): + def setUp(self): + # === Lade JSON-Daten === + with open("C:/10-Develop/kabellaengen/work/easy_positions.json", "r") as f: + self.data = json.load(f) + + def test_linesweep(self): + # === Konfiguration === + tol = 200 + tol_step = 10 + + racks_json = self.data["racks"] #Suchen nach Racks in gesamter Json-Übergabe + + # === 1. Racks in Segmente zerlegen === + ''' Hier werden Racks, die aus "echter" Polylinie bestehen (mehrere Nodes, z.B. Rack 2 in easy.dxf) in einzelne Segmente zerlegt (Node1 -> Node2, Node2 -> Node3)''' + rack_segments = rack_segmentation(racks_json) + + # === 2. Alle Endpunkte sammeln === + ''' Alle Endpunkte aller Racks als Point gespeichert, um shapely funktionen verwenden zu können''' + segment_endpoints = rack_endpoints(rack_segments) + + d_racks_segments, d_rack_conn_points = search_connections(rack_segments, segment_endpoints, tol, tol_step) + + + res_rack_seg = {'Rack_1-0': [Point(4946.5, 15865.5), Point(4946.5, 3777.6)], + 'Rack_2-0': [Point(0.1, 57.6), Point(0.1, 3777.6)], + 'Rack_2-1': [Point(0.1, 3777.6), Point(14755.1, 3777.6)], + 'Rack_3-0': [Point(185.1, 15865.5), Point(12450.7, 15865.5)], + 'Rack_4-0': [Point(2866.6, 15865.5), Point(2866.6, 3777.6)], + 'Rack_5-0': [Point(8866.1, 15865.5), Point(8866.1, 3777.6)] + } + + log_res = to_json(res_rack_seg) + self.assertEqual(d_racks_segments, res_rack_seg) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file