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
import bisect
class NodeIDs():
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:
class PointSorter:
def __init__(self):
self._points_by_x = [] # [(x, y)]
self._points_by_y = [] # [(y, x)]
@@ -140,12 +45,151 @@ class PointIndex2D:
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 und in Liste gesammelt.'''
class NodeIDs():
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 = []
for rack_id, nodes in racks.items():
# Sortiere Node_1, Node_2, ...
@@ -159,7 +203,7 @@ def rack_segmentation(racks):
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'''
segment_endpoints = []
for rack_id, idx, line in rack_segments:
@@ -168,7 +212,7 @@ def find_rack_endpoints(rack_segments):
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.
Argumente:
@@ -198,7 +242,7 @@ def increase_circle(tol, tol_step, line, pt, rack_id, idx, other_rack_id, other_
break
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
candidate_lines = []
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)
# === 3. Verbindungen suchen ===
def search_connections(rack_segments, segment_endpoints, tol, tol_step):
def search_connections(self, 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
'''
@@ -271,26 +311,8 @@ def search_connections(rack_segments, segment_endpoints, tol, tol_step):
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))
# # 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
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"
# === Endpunkte aktualisieren ===
# 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):
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
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 ===
''' 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 ===
''' 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)],
@@ -379,14 +423,6 @@ 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)],
@@ -414,6 +450,7 @@ class TestLinesweep(unittest.TestCase):
def test_add_sensor(self):
rack_segs = {'Rack_1-0': [Point(0, 0), Point(0, 10)],
'Rack_2-0': [Point(10, -2), Point(10, 5)],
'Rack_2-1': [Point(0, 3), Point(10, 3)]}
@@ -422,17 +459,18 @@ class TestLinesweep(unittest.TestCase):
'Sens_2': Point(2, 4),
'Sens_3': Point(9, 2)}
point2rack = RackIDs(rack_segs)
sensor_points = {}
for s, p in sensors.items():
onpoint, rack_name = find_nearest_rack_from_sensor(2, 0.5, p, rack_segs)
sensor_points[s] = ( onpoint, rack_name)
point2rack.add_point_to_rack(onpoint, rack_name)
an = Anlage()
point2rack = an.set_racks(rack_segs)
an.add_sensors(sensors)
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)])