TRO flow svg file can be created.
This commit is contained in:
+106
-5
@@ -769,8 +769,23 @@ def _nearest_node(station: Element, nodes: dict[str, Node]) -> str | None:
|
||||
return best
|
||||
|
||||
|
||||
def _nearest_kreisel(station: Element, elements: list[Element]) -> Element | None:
|
||||
"""Naechstgelegener Kreisel zur Station (2D-Abstand zur Kreisel-Achsposition)."""
|
||||
if station.x is None or station.y is None:
|
||||
return None
|
||||
best, best_d = None, None
|
||||
for el in elements:
|
||||
if not el.is_circle or el.x is None or el.y is None:
|
||||
continue
|
||||
d = (el.x - station.x) ** 2 + (el.y - station.y) ** 2
|
||||
if best_d is None or d < best_d:
|
||||
best, best_d = el, d
|
||||
return best
|
||||
|
||||
|
||||
def _attach_stations(
|
||||
elements: list[Element], nodes: dict[str, Node], warnings: list[str]
|
||||
elements: list[Element], by_id: dict[str, Element], nodes: dict[str, Node],
|
||||
warnings: list[str]
|
||||
) -> list[Edge]:
|
||||
"""
|
||||
Ein-/Ausgabestationen (BTMT Be-/Entladung) mit dem Fluss verbinden.
|
||||
@@ -778,7 +793,11 @@ def _attach_stations(
|
||||
Eingang (Beladung) ist Quelle: Kante Station -> Ziel.
|
||||
Ausgang (Entladung) ist Senke: Kante Ziel -> Station.
|
||||
Ziel kommt aus den 'Nachbarn' des Exports; fehlen sie (dxfmakros exportiert
|
||||
fuer BTMT derzeit keine), wird das naechstgelegene Objekt per Abstand genommen.
|
||||
fuer BTMT derzeit keine), haengen beide baulich direkt am naechstgelegenen
|
||||
Kreisel, auf dessen Eingangs- bzw. Ausgangsbahn (siehe _weiche_lane_roles):
|
||||
UZS -> Eingang Bahn R / Ausgang Bahn L, GUZ -> Eingang Bahn L / Ausgang
|
||||
Bahn R. Nur wenn kein Kreisel mit bekannter Drehrichtung gefunden wird,
|
||||
greift ersatzweise die naechstgelegene Bahn/Strecke per Abstand.
|
||||
"""
|
||||
edges: list[Edge] = []
|
||||
for element in elements:
|
||||
@@ -796,6 +815,18 @@ def _attach_stations(
|
||||
else:
|
||||
partners += [f"{base}-{L}" for L in (LANE_LEFT, LANE_RIGHT)
|
||||
if f"{base}-{L}" in nodes]
|
||||
if not partners and (element.is_entry or element.is_exit):
|
||||
kreisel = _nearest_kreisel(element, elements)
|
||||
roles = _weiche_lane_roles(kreisel.teile_id, by_id, nodes) if kreisel else None
|
||||
if roles:
|
||||
eingang, ausgang = roles
|
||||
lane, role = (eingang, "Eingangsbahn") if element.is_entry else (ausgang, "Ausgangsbahn")
|
||||
partners = [lane]
|
||||
warnings.append(
|
||||
f"{element.describe()}: keine Nachbarn im Export - "
|
||||
f"an Kreisel {kreisel.teile_id} Bahn {lane.split('-')[-1]} "
|
||||
f"angebunden ({role}, Drehrichtung {kreisel.rotation})"
|
||||
)
|
||||
if not partners:
|
||||
nearest = _nearest_node(element, nodes)
|
||||
if nearest:
|
||||
@@ -827,6 +858,30 @@ def _kreisel_lane_node(kreisel_base: str, strecke: Element, nodes: dict[str, Nod
|
||||
return kreisel_base if kreisel_base in nodes else None
|
||||
|
||||
|
||||
def _weiche_lane_roles(kreisel_base: str, by_id: dict[str, Element], nodes: dict[str, Node]) -> tuple[str, str] | None:
|
||||
"""Eingangs-/Ausgangsbahn eines Kreisels an einer Kreisel<->Kreisel-Weiche.
|
||||
|
||||
Ein Kreisel ist intern ein Umlauf zwischen seinen zwei Bahnen (Merkmal
|
||||
"Drehrichtung"); am Beruehrpunkt mit dem anderen Kreisel liegt daher
|
||||
zwingend die eine Bahn als Eingang (freies Ende vor dem internen Umlauf)
|
||||
und die andere als Ausgang (freies Ende danach):
|
||||
UZS (Umlauf R -> L) -> Eingang R, Ausgang L
|
||||
GUZ (Umlauf L -> R) -> Eingang L, Ausgang R
|
||||
None, wenn die Drehrichtung fehlt/unbekannt ist oder eine Bahn im Graph fehlt.
|
||||
"""
|
||||
element = by_id.get(kreisel_base)
|
||||
if element is None or not element.is_circle:
|
||||
return None
|
||||
left, right = f"{kreisel_base}-{LANE_LEFT}", f"{kreisel_base}-{LANE_RIGHT}"
|
||||
if left not in nodes or right not in nodes:
|
||||
return None
|
||||
if element.rotation == ROTATION_CW:
|
||||
return right, left
|
||||
if element.rotation == ROTATION_CCW:
|
||||
return left, right
|
||||
return None
|
||||
|
||||
|
||||
def _apply_transfers(
|
||||
elements: list[Element], by_id: dict[str, Element],
|
||||
nodes: dict[str, Node], edges: list[Edge], warnings: list[str]
|
||||
@@ -836,7 +891,10 @@ def _apply_transfers(
|
||||
|
||||
Ausschleuselement: Kreisel -> Strecke (Streckenanfang).
|
||||
Einschleuselement: Strecke -> Kreisel (Streckenende).
|
||||
Weiche: Kreisel<->Kreisel als Weiche markieren.
|
||||
Weiche: Kreisel<->Kreisel, Bahn und Richtung aus der Drehrichtung
|
||||
beider Kreisel (siehe _weiche_lane_roles); ohne bekannte
|
||||
Drehrichtung bleibt die Verbindung unbestimmt, wird aber
|
||||
weiterhin als Weiche markiert.
|
||||
Die so definierten Kanten sind massgeblich und ersetzen die aus Hoehe/
|
||||
Nachbarn abgeleitete Richtung fuer genau diese Knotenpaare.
|
||||
"""
|
||||
@@ -844,6 +902,7 @@ def _apply_transfers(
|
||||
# ein = Auslauf (Strecke->Kreisel, Ende)
|
||||
per_strecke: dict[str, dict[str, str]] = {}
|
||||
weiche_pairs: list[set[str]] = []
|
||||
weiche_ab: list[tuple[str, str]] = []
|
||||
for el in elements:
|
||||
if el.kind in ("Ausschleus", "Einschleus"):
|
||||
k = str(_merkmal(el.merkmale, "Kreisel") or "").strip()
|
||||
@@ -856,6 +915,28 @@ def _apply_transfers(
|
||||
b = str(_merkmal(el.merkmale, "Kreisel B") or "").strip()
|
||||
if a and b:
|
||||
weiche_pairs.append({a, b})
|
||||
weiche_ab.append((a, b))
|
||||
|
||||
# Weiche zwischen zwei Kreiseln: Bahn und Richtung deterministisch aus der
|
||||
# Drehrichtung beider Kreisel ableiten (Ausgang der einen Bahn -> Eingang
|
||||
# der anderen, in beiden Richtungen) statt aus dem rohen Nachbarn-Kreuzprodukt,
|
||||
# das links/rechts nicht unterscheiden kann.
|
||||
weiche_edges: list[Edge] = []
|
||||
weiche_resolved: set[frozenset] = set()
|
||||
for a, b in weiche_ab:
|
||||
roles_a = _weiche_lane_roles(a, by_id, nodes)
|
||||
roles_b = _weiche_lane_roles(b, by_id, nodes)
|
||||
if roles_a is None or roles_b is None:
|
||||
warnings.append(
|
||||
f"Weiche {a}<->{b}: Drehrichtung mindestens eines Kreisels unbekannt - "
|
||||
f"Bahnzuordnung nicht bestimmbar, Verbindung bleibt unbestimmt"
|
||||
)
|
||||
continue
|
||||
in_a, out_a = roles_a
|
||||
in_b, out_b = roles_b
|
||||
weiche_edges.append(Edge(out_a, in_b, EDGE_FLOW, "Weiche"))
|
||||
weiche_edges.append(Edge(out_b, in_a, EDGE_FLOW, "Weiche"))
|
||||
weiche_resolved.add(frozenset({a, b}))
|
||||
|
||||
directed: dict[frozenset, tuple[str, str]] = {}
|
||||
for s, roles in per_strecke.items():
|
||||
@@ -885,9 +966,13 @@ def _apply_transfers(
|
||||
# einem dritten Kreisel liegt) ist ein Falschtreffer und wird verworfen.
|
||||
covered = set(per_strecke)
|
||||
result: list[Edge] = []
|
||||
superseded: set[tuple[str, str]] = set()
|
||||
for e in edges:
|
||||
if frozenset({e.src, e.dst}) in directed:
|
||||
continue # wird gerichtet neu gesetzt
|
||||
if frozenset({e.src.split("-")[0], e.dst.split("-")[0]}) in weiche_resolved:
|
||||
superseded.add((e.src, e.dst))
|
||||
continue # wird durch die Weiche-Kanten ersetzt
|
||||
ends = {e.src, e.dst}
|
||||
strecke_end = ends & covered
|
||||
others = ends - strecke_end
|
||||
@@ -902,7 +987,23 @@ def _apply_transfers(
|
||||
result.append(e)
|
||||
for (src, dst, note) in directed.values():
|
||||
result.append(Edge(src, dst, EDGE_FLOW, note))
|
||||
# Kreisel<->Kreisel-Kanten, die eine Weiche verbinden, markieren
|
||||
result.extend(weiche_edges)
|
||||
# Die anfaengliche Hoehen-basierte Ausrichtung (_orient) kann eine
|
||||
# Kreisel<->Kreisel-Verbindung nie bestimmen und meldet sie immer als
|
||||
# "beidseitig gezeichnet" - das stimmt nicht mehr, sobald die Weiche sie
|
||||
# gerade durch obenstehende Kanten ersetzt hat. Die dadurch ueberholte
|
||||
# Meldung wird entfernt.
|
||||
if superseded:
|
||||
stale = {
|
||||
f"Verbindung {a} <-> {b}: Richtung nicht bestimmbar "
|
||||
f"(kein Transportobjekt beteiligt) - beidseitig gezeichnet"
|
||||
for src, dst in superseded for a, b in ((src, dst), (dst, src))
|
||||
}
|
||||
warnings[:] = [w for w in warnings if w not in stale]
|
||||
# Kreisel<->Kreisel-Kanten, die eine Weiche verbinden, markieren (deckt vor
|
||||
# allem die Faelle ab, die mangels Drehrichtung nicht gerichtet aufgeloest
|
||||
# werden konnten - die aufgeloesten Weiche-Kanten tragen die Markierung
|
||||
# bereits).
|
||||
for e in result:
|
||||
if {e.src.split("-")[0], e.dst.split("-")[0]} in weiche_pairs:
|
||||
e.note = "Weiche"
|
||||
@@ -949,7 +1050,7 @@ def build_graph(elements: list[Element], source: str, warnings: list[str]) -> Gr
|
||||
edges.append(Edge(right, left, EDGE_CIRCLE, "unbestimmt"))
|
||||
|
||||
# Ein-/Ausgabestationen (BTMT Be-/Entladung) als Quelle/Senke anbinden
|
||||
edges.extend(_attach_stations(elements, nodes, warnings))
|
||||
edges.extend(_attach_stations(elements, by_id, nodes, warnings))
|
||||
|
||||
# Kreisel<->Strecke gerichtet aus den Transferelementen (massgeblich)
|
||||
edges = _apply_transfers(elements, by_id, nodes, edges, warnings)
|
||||
|
||||
Reference in New Issue
Block a user