list_trackids aus Merkmalen in eigene CSV-Spalte "TrackIds" verschoben

Statt eines JSON-Merkmals bekommen VF_n/GF_n/Kreisel jetzt eine eigene
CSV-Spalte "TrackIds" (nach "Nachbarn", vor "Fehler") mit der Reihenfolge
der Separator-/BTMT-/AS-ES-/Weichen-TeileIds entlang der Strecke bzw. um
den Kreisel - analog zur bestehenden "Nachbarn"-Spalte statt versteckt im
Merkmale-JSON. Referenz-CSV fuer den Omniflo-Test an den neuen Header
angepasst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 15:07:31 +02:00
parent 3c0900a02b
commit 28dc3b4301
5 changed files with 158 additions and 146 deletions
+33 -24
View File
@@ -41,7 +41,7 @@ Aufruf:
CSV-Format:
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;
Insertpoint;K1;K2;K3;K4;Nachbarn;Fehler;Warnungen;Merkmale
Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale
Position und Boundingbox stammen aus der von export.lsp (csv:get-bbox, per
vla-getboundingbox) ermittelten Bounding-Box je Block:
@@ -68,6 +68,13 @@ das trennt bloss ueberlappende BBoxen von echten Anschluessen (Geraden fuehren
dafuer beim Export synthetisierte K1/K2, siehe csv:gerade-k-kos-strings).
Toleranzen und (Fallback-)Rastergroesse konfigurierbar ueber [Nachbarschaft]
in cfg/export.cfg.
TrackIds = kommaseparierte TeileId-Liste der Separatoren/BTMT-Beladung/
BTMT-Entladung (und bei einem Kreisel zusaetzlich der AS/ES-Streckenenden
und Kreisel-Kreisel-Weichen) entlang einer VF_n/GF_n-Strecke bzw. rund um
einen Kreisel, in Baureihenfolge (Strecke) bzw. Umlaufreihenfolge ab Winkel
0 Grad (Kreisel, siehe export_neighbors.compute_kreisel_umlauf) - nur bei
VF_n/GF_n/Kreisel vorhanden, sonst leer. Siehe write_separatorliste_
merkmale/write_kreisel_separatorliste_merkmale.
Fehler = "unverbunden" (keine Partner, aber mindestens einer noetig),
"nur ein Partner" (ein Partner, aber mindestens zwei noetig) oder leer
(Mindestanzahl erreicht bzw. TeileArt wird nicht geprueft). Kreisel/Eckrad/
@@ -250,11 +257,11 @@ def build_variofoerderer_merkmale(block):
export_raw.json-Dateien (z.B. tests/reference/export_raw.json) und noch
nicht neu aufgebaute Bloecke in der Zeichnung weiterhin Werte liefern.
Das Merkmal "list_trackids" (Reihenfolge der Separator-TeileIds entlang
der Strecke) steht hier NICHT drin - es kommt erst nach der sepliste-
Aufloesung dazu (siehe write_separatorliste_merkmale in process_blocks),
weil an dieser Stelle die x/y-Koordinaten aus der sepliste-XDATA noch
nicht auf reale Separator-TeileIds aufgeloest sind.
Die CSV-Spalte "TrackIds" (item["trackids"], Reihenfolge der Separator-
TeileIds entlang der Strecke) steht hier NICHT drin - sie kommt erst nach
der sepliste-Aufloesung dazu (siehe write_separatorliste_merkmale in
process_blocks), weil an dieser Stelle die x/y-Koordinaten aus der
sepliste-XDATA noch nicht auf reale Separator-TeileIds aufgeloest sind.
"""
attribs = block.get("attribs", {})
@@ -844,9 +851,10 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
def write_separatorliste_merkmale(items, sep_kette_by_key, dbg=None):
"""Traegt in jede Strecke (VF_n/GF_n mit "sepliste") das Merkmal
"list_trackids" ein: die TeileIds ihrer Separatoren in Baureihenfolge,
kommagetrennt (analog zu "Zuordnung"/"Nachbarn"), z.B. "0052, 0053, 0054".
"""Traegt in jede Strecke (VF_n/GF_n mit "sepliste") die eigene CSV-Spalte
"TrackIds" (item["trackids"]) ein: die TeileIds ihrer Separatoren in
Baureihenfolge, kommagetrennt (analog zur "Nachbarn"-Spalte, ebenfalls
ein Top-Level-Item-Feld statt eines Merkmals), z.B. "0052, 0053, 0054".
Quelle ist die rohe "sepliste" des Items (AS/SEP/ES mit x/y, bereits in
Baureihenfolge - siehe compute_sep_kette) statt sep_kette selbst: dessen
@@ -875,24 +883,24 @@ def write_separatorliste_merkmale(items, sep_kette_by_key, dbg=None):
sep_item = sep_kette_by_key.get(key)
if sep_item is None:
if dbg:
dbg(f" list_trackids {key}: kein aufgeloestes Separator-Item "
dbg(f" TrackIds {key}: kein aufgeloestes Separator-Item "
f"- uebersprungen")
continue
separator_ids.append(sep_item.get("teileid", ""))
if separator_ids:
item["merkmale"]["list_trackids"] = ", ".join(separator_ids)
item["trackids"] = ", ".join(separator_ids)
def write_kreisel_separatorliste_merkmale(items, kreisel_umlauf, sep_kette_by_key,
weiche_by_key, dbg=None):
"""Traegt in jeden Kreisel ("ILS 2.0 Kreisel") das Merkmal "list_trackids"
ein (gleicher Schluessel wie bei VF_n/GF_n, siehe write_separatorliste_
merkmale): die TeileIds aller auf ihm liegenden freien Punkte (Separator,
BTMT Beladung/Entladung - siehe KREISEL_UMLAUF_TEILEARTEN),
andockenden AS/ES-Streckenenden und Kreisel-Kreisel-Weichen, in
Umlaufreihenfolge ab Winkel 0 Grad (siehe compute_kreisel_umlauf - die
Liste beginnt dort bereits bei 0 Grad in Drehrichtung DREHRICHTUNG),
kommagetrennt.
"""Traegt in jeden Kreisel ("ILS 2.0 Kreisel") die eigene CSV-Spalte
"TrackIds" (item["trackids"], gleiches Feld wie bei VF_n/GF_n, siehe
write_separatorliste_merkmale) ein: die TeileIds aller auf ihm liegenden
freien Punkte (Separator, BTMT Beladung/Entladung - siehe
KREISEL_UMLAUF_TEILEARTEN), andockenden AS/ES-Streckenenden und Kreisel-
Kreisel-Weichen, in Umlaufreihenfolge ab Winkel 0 Grad (siehe
compute_kreisel_umlauf - die Liste beginnt dort bereits bei 0 Grad in
Drehrichtung DREHRICHTUNG), kommagetrennt.
Quelle ist kreisel_umlauf (export_neighbors.compute_kreisel_umlauf), das
fuer jeden Kreisel bereits alle Punkte (freie Punkte, AS/ES-Enden,
@@ -920,12 +928,12 @@ def write_kreisel_separatorliste_merkmale(items, kreisel_umlauf, sep_kette_by_ke
item = alle_schluessel.get(punkt_key)
if item is None:
if dbg:
dbg(f" list_trackids Kreisel {tid}: kein Item fuer "
dbg(f" TrackIds Kreisel {tid}: kein Item fuer "
f"Umlauf-Punkt {punkt_key!r} - uebersprungen")
continue
teileids.append(item.get("teileid", ""))
if teileids:
kreisel_item["merkmale"]["list_trackids"] = ", ".join(teileids)
kreisel_item["trackids"] = ", ".join(teileids)
def process_blocks(blocks, lookup, dbg=None):
@@ -1325,14 +1333,14 @@ def process_blocks(blocks, lookup, dbg=None):
map_separator_kette_items(
items, sep_kette, sep_kette_by_key=sep_kette_by_key, dbg=dbg)
# Merkmal "list_trackids" je Strecke (Reihenfolge der SEP-TeileIds aus der
# Spalte "TrackIds" je Strecke (Reihenfolge der SEP-TeileIds aus der
# sepliste, x/y bereits oben auf das reale Separator-Item aufgeloest).
write_separatorliste_merkmale(items, sep_kette_by_key, dbg=dbg)
resolve_umlauf_und_sepliste_nachbarn(
items, kreisel_umlauf, sep_kette, sep_kette_by_key, weiche_by_key)
# Merkmal "list_trackids" je Kreisel: dieselben Umlauf-Punkte (freie
# Spalte "TrackIds" je Kreisel: dieselben Umlauf-Punkte (freie
# Separatoren, AS/ES-Enden, Kreisel-Kreisel-Weichen), die oben schon die
# "Nachbarn"-Spalte gespeist haben, jetzt als eine einzige, bei 0 Grad
# beginnende Reihenfolge auf den Kreisel selbst geschrieben (analog zu
@@ -1380,6 +1388,7 @@ def format_csv_line(item):
f';"{csv_quote(item["k3"])}"'
f';"{csv_quote(item["k4"])}"'
f';"{csv_quote(item["nachbarn"])}"'
f';"{csv_quote(item.get("trackids", ""))}"'
f';"{csv_quote(item["fehler"])}"'
f';"{csv_quote(item.get("warnung", ""))}"'
f';{merkmale_json}'
@@ -1438,7 +1447,7 @@ def main():
print(f"[export_csv] Kollisions-Debuglog geschrieben: {NEIGHBOR_DEBUG_TARGET}")
header = ("Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;"
"Insertpoint;K1;K2;K3;K4;Nachbarn;Fehler;Warnungen;Merkmale")
"Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale")
with open(output_csv, "w", encoding="utf-8") as f:
f.write(header + "\n")
for item in items: