fix(extract): Kontraktion zieht jetzt alle anliegenden Kanten mit

_contract_short() ließ Kanten, die schon vorher am überlebenden Knoten
hingen, auf ihrer alten Koordinate stehen - nur die vom eingezogenen
Knoten umgehängten folgten dem neuen Mittelpunkt. Da Kontraktion beidseitig
Grad >= 3 voraussetzt, hat der überlebende Knoten danach immer mindestens
Grad 4, und _merge_chains() fasst nur Grad-2-Knoten an: Der Fehler konnte
sich nie von selbst heilen. Am heutigen Mosaik wirkungslos (die Funktion
greift dort kein einziges Mal), aber real für jeden Ausschnitt mit zwei
nahen Kreuzungen - genau der Fall, den die Funktion behandeln soll.

Dazu zwei Kleinigkeiten: _median_width() deckte den Kurzkanten-Fallback
(unter 2 * WIDTH_MARGIN) bisher nicht ab, und ein Kommentar in
_merge_chains() war ein Zeichen zu weit eingerückt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-17 01:52:19 +02:00
1 parent f809eedf71
commit 2d8f4e730c
2 files changed
+48 -3

No files matched your search

+42 -1
View File
@@ -4,7 +4,8 @@ from __future__ import annotations
import numpy as np
from leonida.coords import SLICE
from leonida.roads import (CLASS_WIDTH, Edge, Graph, Node, SPUR_MIN, clean, measure_widths)
from leonida.roads import (CLASS_WIDTH, CONTRACT_MAX, Edge, Graph, Node, SPUR_MIN, clean,
measure_widths)
def punkt(x: float, y: float) -> tuple[float, float]:
@@ -103,6 +104,20 @@ def test_klasse_ist_die_naechstliegende_regelbreite():
assert CLASS_WIDTH["hauptstrasse"] == 18.0
def test_kurze_kante_ohne_randregel_misst_sich_ganz():
# Kante unter 2 * WIDTH_MARGIN (16 m): "end <= start" bei der Randregel greift ins Leere -
# _median_width() muss dann auf die ganze Kante zurückfallen statt auf ein leeres Stück.
# Alle anderen Breitentests laufen mit Kanten ab 160 m und erreichen diesen Zweig nie.
distance = np.full((50, 50), 3.0) # halbe Breite 3 m -> 6 m
graph = Graph(nodes=[Node(punkt(SLICE["x0"] + 5, SLICE["y1"] - 20)),
Node(punkt(SLICE["x0"] + 15, SLICE["y1"] - 20))],
edges=[Edge(0, 1, gerade(punkt(SLICE["x0"] + 5, SLICE["y1"] - 20),
punkt(SLICE["x0"] + 15, SLICE["y1"] - 20)))])
out = measure_widths(graph, distance)
assert out.edges[0].width == 6.0
assert out.edges[0].klass == "gasse"
# Die folgenden zwei Fälle hat erst der Gegencheck am echten Mosaik gezeigt (Schritt 5): Kein
# synthetischer Test bildet sie ab, aber am echten Netz treten sie zuverlässig auf.
@@ -134,3 +149,29 @@ def test_schleife_an_einem_durchgangsknoten_bringt_clean_nicht_zum_absturz():
Edge(1, 1, ring)])
out = clean(graph)
assert any(e.a == e.b for e in out.edges), "die Schleife bleibt erhalten, statt clean() abstürzen zu lassen"
# Diesen Fall hat nicht das Mosaik gezeigt, sondern die Codeprüfung von Fixrunde 1: Die
# Kontraktion griff bisher an keiner Stelle im heutigen Netz, ihr Fehler blieb dort wirkungslos.
def test_kontraktion_zieht_alle_anliegenden_kanten_zum_neuen_knoten():
# Zwei Kreuzungen mit je Grad 3, 3 m auseinander (unter CONTRACT_MAX) - _contract_short()
# zieht sie zu einem Knoten in der Mitte zusammen. Nicht nur die zwei Kanten, die vorher am
# eingezogenen Knoten hingen, müssen dem neuen Punkt folgen, sondern auch die zwei, die schon
# vorher am überlebenden Knoten hingen: Der ist gerade selbst auf den Mittelpunkt gewandert.
assert 3 < CONTRACT_MAX
graph = Graph(nodes=[Node(punkt(0, 0)), Node(punkt(3, 0)),
Node(punkt(-50, 0)), Node(punkt(0, 50)),
Node(punkt(53, 0)), Node(punkt(3, 50))],
edges=[Edge(0, 1, gerade(punkt(0, 0), punkt(3, 0))),
Edge(0, 2, gerade(punkt(0, 0), punkt(-50, 0))),
Edge(0, 3, gerade(punkt(0, 0), punkt(0, 50))),
Edge(1, 4, gerade(punkt(3, 0), punkt(53, 0))),
Edge(1, 5, gerade(punkt(3, 0), punkt(3, 50)))])
out = clean(graph)
ziel = out.nodes[0].point
assert ziel == punkt(1.5, 0.0), "der überlebende Knoten steht auf dem Mittelpunkt beider Kreuzungen"
fehler = [(edge.a, edge.b, edge.points) for edge in out.edges
if (edge.a == 0 and edge.points[0] != ziel) or (edge.b == 0 and edge.points[-1] != ziel)]
assert not fehler, f"Kanten mit veralteter Koordinate am zusammengezogenen Knoten: {fehler}"