From 13411424775106de42a847e816b983457fb2aa6b Mon Sep 17 00:00:00 2001 From: lertlmaier Date: Wed, 14 May 2025 10:58:53 +0200 Subject: [PATCH] Neue Funktionen und neue Klassen zur Rack Definition und Sortierung der Punkte auf dem Rack --- lib/linesweep_circle.py | 156 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 6 deletions(-) diff --git a/lib/linesweep_circle.py b/lib/linesweep_circle.py index d00317d..7cc6e3c 100644 --- a/lib/linesweep_circle.py +++ b/lib/linesweep_circle.py @@ -2,6 +2,8 @@ import json from shapely.geometry import LineString, Point from shapely.ops import nearest_points import unittest +from collections import defaultdict +import bisect class NodeIDs(): def __init__(self, points=[]): @@ -39,12 +41,109 @@ class NodeIDs(): ret.append(c) return ret +class RackIDs(): + def __init__(self, racks=dict()): + self._point2rack = dict() + self._rack2begend = dict() + self.add_racks(racks) + + + def add_rack(self, beg:Point, end:Point, name): #Hier wird Rack nur mit Anfang und Ende hinzugefügt -> wie macht man Zwischenpunkte? + if beg in self._point2rack: + self._point2rack[beg].append(name) + else: + self._point2rack[beg] = [name] + if end in self._point2rack: + self._point2rack[end].append(name) + else: + self._point2rack[end] = [name] + + self._rack2begend[name] = [beg, end] #Anfangs und Endpunkte zu Rack Namen merken + + def add_racks(self, racks:dict): + for name,v in racks.items(): + if len(v) != 2: + raise AttributeError + self.add_rack(v[0], v[1], name) + + + def add_point_to_rack(self, point:Point, name:str): + pass + + def get_racks_from_point(self, point:Point) -> list[str]: + return self._point2rack[point] + + def get_points_from_rack(self, name:str) -> list[Point]: + ''' Gibt zu Namen von Rack zugehörige Punkte aus und sortiert Punkte''' + ret = list() + pin = PointIndex2D() + for p, l_racks in self._point2rack.items(): + if name in l_racks: + ret.append(p) + pin.add_points(ret) + ret_sorted = list() + [pa, pe] = self._rack2begend[name] + if self.rack_is_horizontal(name): + ret_sorted = pin.get_sorted_by_x() + else: + ret_sorted = pin.get_sorted_by_y() + return ret_sorted + + def rack_is_horizontal(self, name): + [pa, pe] = self._rack2begend[name] + if pa.y == pe.y: + return True + else: + return False + + + +class PointIndex2D: + def __init__(self): + self._points_by_x = [] # [(x, y)] + self._points_by_y = [] # [(y, x)] + + def add_point(self, x, y): + bisect.insort(self._points_by_x, (x, y)) + bisect.insort(self._points_by_y, (y, x)) + + def add_points(self, points:list[Point]): + for p in points: + self.add_point(p.x, p.y) + + def query_box(self, x1, x2, y1, y2): + # Suche nach x-Grenzen + ix1 = bisect.bisect_left(self._points_by_x, (x1, -float('inf'))) + ix2 = bisect.bisect_right(self._points_by_x, (x2, float('inf'))) + candidates = self._points_by_x[ix1:ix2] + + # Filtere nach y + ret = list() + for (x,y) in candidates: + if y1 <= y <= y2: + ret.append(Point(x,y)) + return ret + + def get_sorted_by_x(self): + # Sortiere nach x + ret = list() + for (x,y) in self._points_by_x: + ret.append(Point(x,y)) + return ret + + def get_sorted_by_y(self): + # Sortiere nach y + ret = list() + for (y,x) in self._points_by_y: + ret.append(Point(x,y)) + return ret + 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" def rack_segmentation(racks): - ''' Racks werden zu LineString konvertiert. Racks bestehend aus Polylinine werden in einzelne Segmente zerlegt.''' + ''' Racks werden zu LineString konvertiert. Racks bestehend aus Polylinine werden in einzelne Segmente zerlegt und in Liste gesammelt.''' rack_segments = [] for rack_id, nodes in racks.items(): # Sortiere Node_1, Node_2, ... @@ -58,8 +157,8 @@ def rack_segmentation(racks): return(rack_segments) -def rack_endpoints(rack_segments): - ''' Endpunkte der Racks werden in Points konvertiert''' +def find_rack_endpoints(rack_segments): + ''' Endpunkte der Racks-Segmente werden in Points konvertiert und in Liste gesammelt''' segment_endpoints = [] for rack_id, idx, line in rack_segments: for pt in [line.coords[0], line.coords[1]]: @@ -97,8 +196,13 @@ def increase_circle(tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_ break radius += tol_step + + # === 3. Verbindungen suchen === def search_connections(rack_segments, segment_endpoints, tol, tol_step): + ''' Aus Rack Segmenten und Endpunkten der Racks wird unter Berücksichtigung von Toleranz naheliegende Endpunkte gefunden. + Zuerst echte Schnittpunkte und im Anschluss via Kreissuche neheliegende Punkte und deren gepinnte Berührpunkte + ''' verbindungen = [] endpoint_pinned = [] @@ -116,7 +220,6 @@ def search_connections(rack_segments, segment_endpoints, tol, tol_step): #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: @@ -174,13 +277,31 @@ def search_connections(rack_segments, segment_endpoints, tol, tol_step): d_racks_segments[key] = coords # aktualisieren - #Dict erstellen, dass alle Punkte die an einem Rack anschließen + #Dict erstellen, dass alle Punkte die an einem Rack anschließen speichert 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] + + d_rack_to_points = dict() #neues Dict für Rack_id - Idx: Alle Punkte auf dem Rack + + for key, coords in d_racks_segments.items(): # Erst Anfangs und Endpunkt aus d_racks_segments holen + # coords = [start_point end_point] + d_rack_to_points[key] = coords.copy() + + for key, point in d_rack_conn_points.items(): # Dann aus d_rack_conn_points alle verbindungspunkte holen und dazu speichern + to_rack = key.split(" + ")[0] + if to_rack in d_rack_to_points: + d_rack_to_points[to_rack].extend(point) + + for key in d_rack_to_points: + unique_points = list({(pt.x, pt.y): pt for pt in d_rack_to_points[key]}.values()) + d_rack_to_points[key] = unique_points + + + return [d_racks_segments, d_rack_conn_points] @@ -205,7 +326,7 @@ class TestLinesweep(unittest.TestCase): # === 2. Alle Endpunkte sammeln === ''' Alle Endpunkte aller Racks als Point gespeichert, um shapely funktionen verwenden zu können''' - segment_endpoints = rack_endpoints(rack_segments) + segment_endpoints = find_rack_endpoints(rack_segments) d_racks_segments, d_rack_conn_points = search_connections(rack_segments, segment_endpoints, tol, tol_step) @@ -221,6 +342,29 @@ class TestLinesweep(unittest.TestCase): log_res = to_json(res_rack_seg) self.assertEqual(d_racks_segments, res_rack_seg) + def test_ids_to_point(self): + allids = NodeIDs(nodes) + for k,v in d_racks_segments.items(): + allids.add_points(v) + + for k,v in d_rack_conn_points: + allids.add_point(v) + + def test_ids_to_point(self): + + res_rack_seg = {'Rack_1-0': [Point(1, 0), Point(5, 6)], + 'Rack_2-0': [Point(1, 8), Point(1, 0)], + 'Rack_2-1': [Point(0, 10), Point(5, 10)]} + + + allids = RackIDs(res_rack_seg) + + self.assertEqual(allids.get_racks_from_point(Point(1, 0)), ["Rack_1-0", "Rack_2-0"]) + self.assertEqual(allids.get_racks_from_point(Point(5, 6)), ["Rack_1-0"]) + self.assertEqual(allids.get_points_from_rack("Rack_2-0"), [Point(1, 0), Point(1, 8)]) + + + if __name__ == '__main__': unittest.main() \ No newline at end of file