Kreisel-Separatorliste (list_trackids) im CSV-Export ergaenzt
Kreisel bekommen wie VF_n/GF_n ein Merkmal mit der Reihenfolge aller Separator-/BTMT-Beladung-/BTMT-Entladung-TeileIds entlang der Bahn, sortiert nach Winkel um die Kreiselachse (Start bei 0 Grad in Drehrichtung DREHRICHTUNG) - nutzt dieselbe Umlauf-Logik, die bisher nur die "Nachbarn"-Spalte gespeist hat. Merkmal-Schluessel einheitlich auf "list_trackids" umbenannt (vorher "Separatorlist" bei VF/GF). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+49
-14
@@ -74,6 +74,19 @@ STRECKEN_TEILEARTEN = {"ILS 2.0 Gefaellestrecke", "ILS 2.0 Strecke", "ILS 2.0 St
|
||||
OMNIFLO_TEILEARTEN = {"Omniflo Kurve", "Omniflo Weiche", "Omniflo Gerade"}
|
||||
SENSOR_TEILEARTEN = {"ILS 2.0 Separator", "ILS 2.0 Scanner"}
|
||||
|
||||
# Frei auf der Bahn platzierte Elemente, die im Kreisel-Umlauf
|
||||
# (compute_kreisel_umlauf) und im Kreisel-Merkmal "list_trackids" (export_csv.py
|
||||
# write_kreisel_separatorliste_merkmale) als eigene Punkte auftreten koennen -
|
||||
# wie ein Separator per Bounding-Box-Ueberschneidung einer Kreiselhaelfte/
|
||||
# einem Kreisel zugeordnet (siehe compute_sensor_zuordnung/compute_kreisel_
|
||||
# umlauf). BTMT-Beladung/-Entladung sind Stationen AUF der Strecke, keine
|
||||
# Sensoren - eigene Konstante statt Erweiterung von SENSOR_TEILEARTEN, damit
|
||||
# deren Sensor-spezifische Weiterverarbeitung (z.B. compute_scanner_nearest_
|
||||
# separator) unberuehrt bleibt.
|
||||
KREISEL_UMLAUF_TEILEARTEN = {
|
||||
"ILS 2.0 Separator", "ILS 2.0 BTMT Beladung", "ILS 2.0 BTMT Entladung", "ILS Weiche",
|
||||
}
|
||||
|
||||
# Nur "ILS 2.0 Kreisel" (echte Kreisel-Bloecke mit AN8/Antrieb + SP8/Spann-
|
||||
# station + ABSTAND-Attribut) werden links/rechts gesplittet - "ILS 2.0
|
||||
# Eckrad" hat keine dieser zwei Stationen und bleibt eine einzelne BBox.
|
||||
@@ -1380,10 +1393,11 @@ def compute_sep_kette(items, dbg=None):
|
||||
def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
touch_switches=None, dbg=None):
|
||||
"""Bringt fuer jeden echten Kreisel (KREISEL_SPLIT_TEILEART) alle dort
|
||||
liegenden Punkte - frei platzierte Separatoren (per compute_sensor_
|
||||
zuordnung bereits einer Kreiselhaelfte zugeordnet), die AS/ES-Enden
|
||||
aller andockenden VF_n/GF_n-Ketten (aus sep_kette, per BBox-Naehe wie
|
||||
compute_strecke_kreisel_schleus zugeordnet) UND die Kreisel-Kreisel-
|
||||
liegenden Punkte - frei platzierte Separatoren UND BTMT-Beladung/
|
||||
-Entladung (KREISEL_UMLAUF_TEILEARTEN, per BBox-Ueberschneidung
|
||||
wie compute_sensor_zuordnung einer Kreiselhaelfte zugeordnet), die AS/ES-
|
||||
Enden aller andockenden VF_n/GF_n-Ketten (aus sep_kette, per BBox-Naehe
|
||||
wie compute_strecke_kreisel_schleus zugeordnet) UND die Kreisel-Kreisel-
|
||||
Weichen (touch_switches, siehe compute_kreisel_touch_switches) - in eine
|
||||
geschlossene Umlauf-Reihenfolge (zirkulaer, siehe DREHRICHTUNG-Attribut).
|
||||
|
||||
@@ -1399,7 +1413,11 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
_kreisel_capsule) gemessen, 0 Grad = Kapsel-Achsrichtung. UZS
|
||||
(Uhrzeigersinn, Default) sortiert nach fallendem Winkel, GUZ nach
|
||||
steigendem - beides ergibt einen konsistenten Umlauf, die Achsrichtung
|
||||
selbst ist nur der Nullpunkt.
|
||||
selbst ist nur der Nullpunkt. Die zurueckgegebene Liste beginnt IMMER bei
|
||||
diesem Nullpunkt (kleinster Winkel >= 0 Grad in Umlaufrichtung) - das ist
|
||||
fuer die reine Vorgaenger-/Nachfolger-Ermittlung (zirkulaer) irrelevant,
|
||||
aber Voraussetzung fuer eine reproduzierbare, bei 0 Grad beginnende
|
||||
"list_trackids" (siehe export_csv.py write_kreisel_separatorliste_merkmale).
|
||||
|
||||
items = dieselbe Liste wie bei compute_neighbor_ids/compute_sensor_
|
||||
zuordnung. sep_kette = Rueckgabe von compute_sep_kette (fuer die AS/ES-
|
||||
@@ -1408,10 +1426,11 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
Umlauf).
|
||||
|
||||
Rueckgabe: dict {kreisel_teileid: [(punkt_key, vorgaenger_key,
|
||||
nachfolger_key), ...]} in Umlaufreihenfolge - punkt_key ist eine
|
||||
Separator-TeileId (freie Separatoren), ein sep_kette-Schluessel (AS/ES-
|
||||
Enden) oder ein touch_switches-"key" (Kreisel-Kreisel-Weiche). Kreisel
|
||||
ohne zugeordnete Punkte liefern eine leere Liste.
|
||||
nachfolger_key), ...]} in Umlaufreihenfolge, beginnend bei Winkel 0 Grad -
|
||||
punkt_key ist die TeileId eines freien Punkts (Separator oder BTMT-
|
||||
Beladung/-Entladung, siehe KREISEL_UMLAUF_TEILEARTEN), ein
|
||||
sep_kette-Schluessel (AS/ES-Enden) oder ein touch_switches-"key" (Kreisel-
|
||||
Kreisel-Weiche). Kreisel ohne zugeordnete Punkte liefern eine leere Liste.
|
||||
"""
|
||||
half_tol = tolerance_mm / 2.0
|
||||
kreisel = {}
|
||||
@@ -1451,7 +1470,7 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
punkte_je_kreisel = {tid: [] for tid in kreisel}
|
||||
|
||||
for item in items:
|
||||
if item.get("teileart", "") != "ILS 2.0 Separator":
|
||||
if item.get("teileart", "") not in KREISEL_UMLAUF_TEILEARTEN:
|
||||
continue
|
||||
bbox = item.get("_bbox")
|
||||
if not bbox:
|
||||
@@ -1495,11 +1514,27 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
acy = (cap["a"][1] + cap["b"][1]) / 2.0
|
||||
achs_winkel = math.atan2(cap["b"][1] - cap["a"][1], cap["b"][0] - cap["a"][0])
|
||||
|
||||
def _winkel(pxy):
|
||||
return math.atan2(pxy[2] - acy, pxy[1] - acx) - achs_winkel
|
||||
uzs = (drehrichtung or "UZS").strip().upper() != "GUZ"
|
||||
|
||||
reverse = (drehrichtung or "UZS").strip().upper() != "GUZ"
|
||||
sortiert = sorted(punkte, key=lambda p: _winkel(p), reverse=reverse)
|
||||
def _winkel(pxy):
|
||||
# Rohwinkel relativ zur Achsrichtung, auf [0, 2*pi) normiert IN
|
||||
# UMLAUFRICHTUNG. Vorher (reine Vorgaenger-/Nachfolger-Ermittlung
|
||||
# ohne Startpunkt-Anspruch) wurde UZS durch reverse=True auf dem
|
||||
# rohen mathematischen Winkel erreicht (= "absteigend sortieren");
|
||||
# absteigend sortieren auf theta ist aequivalent zu aufsteigend
|
||||
# sortieren auf -theta - darum wird HIER fuer UZS negiert (NICHT
|
||||
# fuer GUZ, das dem unveraenderten mathematischen Winkel bereits
|
||||
# aufsteigend entspricht). Ohne dieses Vorzeichen liefe der UZS-
|
||||
# Umlauf in die falsche (zu GUZ spiegelverkehrte) Richtung - siehe
|
||||
# tests/test_export_kreisel_umlauf.py::TestKreiselUmlaufStartetBeiNullGrad.
|
||||
# Die Normierung auf [0, 2*pi) sorgt zusaetzlich dafuer, dass
|
||||
# Index 0 nach dem Sortieren der kleinste Winkel >= 0 Grad ist
|
||||
# (der gewuenschte Start bei 0 Grad).
|
||||
roh = math.atan2(pxy[2] - acy, pxy[1] - acx) - achs_winkel
|
||||
roh = -roh if uzs else roh
|
||||
return roh % (2.0 * math.pi)
|
||||
|
||||
sortiert = sorted(punkte, key=lambda p: _winkel(p))
|
||||
n = len(sortiert)
|
||||
umlauf = []
|
||||
for i, p in enumerate(sortiert):
|
||||
|
||||
Reference in New Issue
Block a user