Klasse Anlage erstellt und mit Methoden versehen. Unittests duetlich vereinfacht

This commit is contained in:
2025-05-14 17:52:29 +02:00
parent db8e8d351e
commit 81b5c55254
+189 -151
View File
@@ -5,102 +5,7 @@ import unittest
from collections import defaultdict from collections import defaultdict
import bisect import bisect
class NodeIDs(): class PointSorter:
def __init__(self, points=[]):
self._counter = 0
self._cord2id = dict()
self._id2cord = dict()
self.add_points(points)
def add_point(self, point:Point):
self._counter += 1
self._cord2id[f"{point.x} {point.y}"] = self._counter
self._id2cord[f"{self._counter}"] = point
def add_points(self, points):
for p in points:
self.add_point(p)
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
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):
if point in self._point2rack:
self._point2rack[point].append(name)
else:
self._point2rack[point] = [name]
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): def __init__(self):
self._points_by_x = [] # [(x, y)] self._points_by_x = [] # [(x, y)]
self._points_by_y = [] # [(y, x)] self._points_by_y = [] # [(y, x)]
@@ -140,12 +45,151 @@ class PointIndex2D:
ret.append(Point(x,y)) ret.append(Point(x,y))
return ret return ret
def to_json(d, pretty: bool = True) -> str: 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" return json.dumps(d, indent=2 if pretty else None, default=str) #ensure_ascii false für darstellung von "ue"
def rack_segmentation(racks): class NodeIDs():
''' Racks werden zu LineString konvertiert. Racks bestehend aus Polylinine werden in einzelne Segmente zerlegt und in Liste gesammelt.''' def __init__(self, points=[]):
self._counter = 0
self._cord2id = dict()
self._id2cord = dict()
self.add_points(points)
def add_point(self, point:Point):
self._counter += 1
self._cord2id[f"{point.x} {point.y}"] = self._counter
self._id2cord[f"{self._counter}"] = point
def add_points(self, points):
for p in points:
self.add_point(p)
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
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 get_racks_borders(self) -> dict:
''' Gibt Rack nur mit Anfangs und Endpunkt zurück.
{Rack_1_0: "Point(0, 0), Point(0,15)", ... }
'''
return self._rack2begend
def get_racks_from_all_points(self) -> dict:
''' Gibt zu einem Punkt, diejenigen Racks zurück, auf denen der Punkt liegt.
{Point(0, 0): ["Rack_1-0", "Rack_2-0", ...]}
'''
return self._point2rack
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):
if point in self._point2rack:
self._point2rack[point].append(name)
else:
self._point2rack[point] = [name]
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 = PointSorter()
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 Anlage():
def __init__(self, ):
self._points = PointSorter()
self._racks = RackIDs()
self._nodeids = NodeIDs()
self._sensors = dict()
self._sensor_onpoints = dict()
def set_racks(self, racks:dict[str, list[Point]]):
return self._racks.add_racks(racks)
def get_racks(self) -> dict:
return self._racks._point2rack
def add_point_to_rack(self, point:Point, rname:str):
return self._racks.add_point_to_rack(point, rname)
def get_points_from_rack(self, rname:str):
return self._racks.get_points_from_rack(rname)
def add_sensor(self, sname: str, pos:Point):
self._sensors[sname] = pos
def add_sensors(self, sensors:dict):
for sname,pos in sensors.items():
self.add_sensor(sname, pos)
def connect_sensors_to_racks(self):
for sname, pos in self._sensors.items():
rack_borders = self._racks.get_racks_borders()
onpoint, rack_name = self.find_nearest_rack_from_sensor(2, 0.5, pos, rack_borders)
self._sensor_onpoints[sname] = (onpoint, rack_name)
self.add_point_to_rack(onpoint, rack_name)
return self._sensor_onpoints
def rack_segmentation(self, racks:dict):
''' Racks werden zu LineString konvertiert. Racks bestehend aus Polylinine werden in einzelne Segmente zerlegt und in Liste gesammelt.
'''
rack_segments = [] rack_segments = []
for rack_id, nodes in racks.items(): for rack_id, nodes in racks.items():
# Sortiere Node_1, Node_2, ... # Sortiere Node_1, Node_2, ...
@@ -159,7 +203,7 @@ def rack_segmentation(racks):
return(rack_segments) return(rack_segments)
def find_rack_endpoints(rack_segments): def find_rack_endpoints(self, rack_segments):
''' Endpunkte der Racks-Segmente werden in Points konvertiert und in Liste gesammelt''' ''' Endpunkte der Racks-Segmente werden in Points konvertiert und in Liste gesammelt'''
segment_endpoints = [] segment_endpoints = []
for rack_id, idx, line in rack_segments: for rack_id, idx, line in rack_segments:
@@ -168,7 +212,7 @@ def find_rack_endpoints(rack_segments):
return(segment_endpoints) return(segment_endpoints)
def increase_circle(tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_idx, verbindungen, endpoint_pinned): def increase_circle(self, tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_idx, verbindungen, endpoint_pinned):
''' vergrößere Kreis bis Schnittpunkt mit Rack entsteht. ''' vergrößere Kreis bis Schnittpunkt mit Rack entsteht.
Argumente: Argumente:
@@ -198,7 +242,7 @@ def increase_circle(tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_
break break
radius += tol_step radius += tol_step
def find_nearest_rack_from_sensor(max_dist, coarse_step, sensor:Point, racks:dict) -> tuple[Point, str]: def find_nearest_rack_from_sensor(self, max_dist, coarse_step, sensor:Point, racks:dict) -> tuple[Point, str]:
# 1. grobe Kandidatensuche # 1. grobe Kandidatensuche
candidate_lines = [] candidate_lines = []
radius = coarse_step radius = coarse_step
@@ -232,11 +276,7 @@ def find_nearest_rack_from_sensor(max_dist, coarse_step, sensor:Point, racks:dic
return (nearest_point, r_best) return (nearest_point, r_best)
def search_connections(self, rack_segments, segment_endpoints, tol, 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. ''' 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 Zuerst echte Schnittpunkte und im Anschluss via Kreissuche neheliegende Punkte und deren gepinnte Berührpunkte
''' '''
@@ -271,26 +311,8 @@ def search_connections(rack_segments, segment_endpoints, tol, tol_step):
dist = line.distance(pt) dist = line.distance(pt)
if dist < tol: if dist < tol:
increase_circle(tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_idx, verbindungen, endpoint_pinned) self.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") #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))
# # 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 === # === Endpunkte aktualisieren ===
# Dict erstellen, dass mit dem Key "Rack_id - index" dahinter die Koordinaten von Anfang und Endpunkt speichert # Dict erstellen, dass mit dem Key "Rack_id - index" dahinter die Koordinaten von Anfang und Endpunkt speichert
@@ -344,28 +366,50 @@ def search_connections(rack_segments, segment_endpoints, tol, tol_step):
class TestLinesweep(unittest.TestCase): 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): def test_linesweep(self):
# === Konfiguration === # === Konfiguration ===
tol = 200 tol = 200
tol_step = 10 tol_step = 10
racks_json = self.data["racks"] #Suchen nach Racks in gesamter Json-Übergabe racks_json_str= '''{
"Rack_1": {
"Node_1": [ 4946.5, 15774.4 ],
"Node_2": [ 4946.5, 3879.4 ]
},
"Rack_2": {
"Node_1": [ 0.1, 57.6 ],
"Node_2": [ 0.1, 3777.6 ],
"Node_3": [ 14755.1, 3777.6 ]
},
"Rack_3": {
"Node_1": [ 185.1, 15865.5 ],
"Node_2": [ 12450.7, 15865.5 ] },
"Rack_4": {
"Node_1": [ 2866.6, 15774.4 ],
"Node_2": [ 2866.6, 3880.4 ]
},
"Rack_5": {
"Node_1": [ 8866.1, 15774.4 ],
"Node_2": [ 8866.1, 3878.4 ]
}}'''
racks_json = json.loads(racks_json_str)
an = Anlage()
# === 1. Racks in Segmente zerlegen === # === 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)''' ''' 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) rack_segments = an.rack_segmentation(racks_json)
# === 2. Alle Endpunkte sammeln === # === 2. Alle Endpunkte sammeln ===
''' Alle Endpunkte aller Racks als Point gespeichert, um shapely funktionen verwenden zu können''' ''' Alle Endpunkte aller Racks als Point gespeichert, um shapely funktionen verwenden zu können'''
segment_endpoints = find_rack_endpoints(rack_segments) segment_endpoints = an.find_rack_endpoints(rack_segments)
d_racks_segments, d_rack_conn_points = search_connections(rack_segments, segment_endpoints, tol, tol_step) d_racks_segments, d_rack_conn_points = an.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)], res_rack_seg = {'Rack_1-0': [Point(4946.5, 15865.5), Point(4946.5, 3777.6)],
@@ -379,14 +423,6 @@ class TestLinesweep(unittest.TestCase):
log_res = to_json(res_rack_seg) log_res = to_json(res_rack_seg)
self.assertEqual(d_racks_segments, 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): def test_ids_to_point(self):
res_rack_seg = {'Rack_1-0': [Point(1, 0), Point(5, 6)], res_rack_seg = {'Rack_1-0': [Point(1, 0), Point(5, 6)],
@@ -414,6 +450,7 @@ class TestLinesweep(unittest.TestCase):
def test_add_sensor(self): def test_add_sensor(self):
rack_segs = {'Rack_1-0': [Point(0, 0), Point(0, 10)], rack_segs = {'Rack_1-0': [Point(0, 0), Point(0, 10)],
'Rack_2-0': [Point(10, -2), Point(10, 5)], 'Rack_2-0': [Point(10, -2), Point(10, 5)],
'Rack_2-1': [Point(0, 3), Point(10, 3)]} 'Rack_2-1': [Point(0, 3), Point(10, 3)]}
@@ -422,17 +459,18 @@ class TestLinesweep(unittest.TestCase):
'Sens_2': Point(2, 4), 'Sens_2': Point(2, 4),
'Sens_3': Point(9, 2)} 'Sens_3': Point(9, 2)}
point2rack = RackIDs(rack_segs)
sensor_points = {} an = Anlage()
for s, p in sensors.items(): point2rack = an.set_racks(rack_segs)
onpoint, rack_name = find_nearest_rack_from_sensor(2, 0.5, p, rack_segs) an.add_sensors(sensors)
sensor_points[s] = ( onpoint, rack_name)
point2rack.add_point_to_rack(onpoint, rack_name)
plist = point2rack.get_points_from_rack("Rack_1-0") plist1 = an.get_points_from_rack("Rack_1-0")
self.assertEqual(plist, [Point(0, 0), Point(0,1), Point(0, 10)]) an.connect_sensors_to_racks()
plist2 = an.get_points_from_rack("Rack_1-0")
self.assertEqual(plist1, [Point(0, 0), Point(0, 10)])
self.assertEqual(plist2, [Point(0, 0), Point(0,1), Point(0, 10)])