fix(extract): insert_edge() knickt Kanten zu ihrem Knoten, verliert keine Kreuzung mehr bei Überlappung
Fixrunde 2 zu Task 11, zwei von der Koordination beim Bau von Gegenbeispielen gefundene Fehler in insert_edge() aus Fixrunde 1, einer davon schon in den echten Daten nachweisbar: 1. attach() darf einen bis zu JUNCTION_RADIUS (8 m) entfernten Knoten wiederverwenden, aber _add_piece() übernahm dessen ID in a/b, ohne den zugehörigen Endpunkt in points auf die Knotenkoordinate zu ziehen - die Kante hing dadurch an einem Punkt, an dem sie geometrisch gar nicht lag. Am echten Netz endete die Jefferson Avenue so 6,65 m neben Knoten 561, an dem sie laut Graph hängt; section.py baut die Kreuzungsarme in junction_arms() aus genau diesen Endpunkten. Behoben wie _contract_short() es seit Task 4 vormacht: den betroffenen Endpunkt durch die Knotenkoordinate ersetzen, die Kante bekommt dadurch einen kurzen Knick zum Knoten. Neue Invariante über den ganzen Graphen (test_roads_insert.py): points[0] == nodes[a].point und points[-1] == nodes[b].point für jede Kante. 2. _crossing_points() kannte nur Point und MultiPoint. Überlappt die neue Linie eine Bestandskante ein Stück weit und kreuzt sie danach an einer anderen Stelle wirklich, liefert intersection() eine GeometryCollection aus der Überlappung (LineString) und dem echten Kreuzungspunkt (Point) - die wurde bisher komplett verworfen, samt Kreuzungspunkt. Jetzt werden aus jeder Geometrie (Point, MultiPoint oder GeometryCollection) nur die Point-Anteile behalten. Festgehalten, nicht geändert: Kreuzt die neue Linie zwei verschiedene Kanten näher als JUNCTION_RADIUS zueinander, verschmelzen beide zu einem Knoten - dieselbe Regel, mit der _junction_clusters() beim Netzaufbau aus dem Skelett benachbarte Kreuzungspixel zusammenfasst. Mit Fix 1 knickt die zweite beteiligte Kante jetzt sauber zu diesem gemeinsamen Knoten statt knapp daneben zu enden. test_park_street.py zieht die Kreuzungsgrad-Zusicherung von ">= 3" auf "== 4" an, weil an einer echten Kreuzung zweier gespaltener Straßen genau das gilt. Am echten Netz nachgemessen: Die Jefferson Avenue endet jetzt exakt auf Knoten 561 (1551,67 | 332,67), nicht mehr 6,65 m daneben; die vier von insert_edge() neu geschriebenen Kanten haben 0,000000 m Lücke zu ihrem Knoten. Eine bereits vor Flamingo Park bestehende Rundungsdifferenz zwischen unrundeten Knotenkoordinaten aus _junction_clusters() und den auf 2 Dezimalstellen gerundeten Kantenpunkten aus clean() betrifft ~1.250 von 1.790 Kanten mit höchstens 7,1 mm Abstand, unverändert vorher wie nachher - das ist keine Folge dieser beiden Fixe und liegt außerhalb des heutigen Umfangs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f09e7b1d9
commit
bab83bce5f
3 files changed
+93
-10
No files matched your search
@@ -358,7 +358,11 @@ def insert_edge(graph: Graph, points: list[tuple[float, float]], width: float, k
|
||||
# Jede vorhandene Kante, die die neue Linie kreuzt, an den Kreuzungspunkten aufbrechen.
|
||||
# attach() entscheidet je Punkt, ob dort ein neuer Knoten entsteht oder ein vorhandener in
|
||||
# der Nähe genügt (Regel b) - dieselbe Funktion, die gleich auch die Enden der neuen Straße
|
||||
# anhängt, damit an einer Kreuzung nicht zwei Knoten wenige Meter auseinander liegen.
|
||||
# anhängt, damit an einer Kreuzung nicht zwei Knoten wenige Meter auseinander liegen. Kreuzt
|
||||
# die neue Linie zwei verschiedene Kanten weniger als JUNCTION_RADIUS auseinander, verschmilzt
|
||||
# attach() beide Punkte deshalb zu einem einzigen Knoten - genau die Regel, mit der schon
|
||||
# _junction_clusters() beim Aufbau aus dem Skelett benachbarte Kreuzungspixel zusammenfasst:
|
||||
# Alles innerhalb des Radius gilt als dieselbe Kreuzung, nicht als zwei dicht nebeneinander.
|
||||
crossings: list[tuple[float, float]] = []
|
||||
for edge in graph.edges:
|
||||
line = LineString(edge.points)
|
||||
@@ -379,25 +383,36 @@ def insert_edge(graph: Graph, points: list[tuple[float, float]], width: float, k
|
||||
|
||||
|
||||
def _crossing_points(a: LineString, b: LineString) -> list[tuple[float, float]]:
|
||||
"""Die Punkte, an denen sich zwei Linien berühren - deckungsgleiche Teilstücke (kein Punkt,
|
||||
sondern selbst eine Linie) sind ein Datenfehler und werden hier ignoriert, nicht aufgebrochen."""
|
||||
"""Die Punkte, an denen sich zwei Linien berühren.
|
||||
|
||||
Läuft die neue Linie ein Stück lang deckungsgleich mit einer Bestandskante und kreuzt sie
|
||||
danach woanders wirklich, liefert intersection() eine GeometryCollection aus dieser
|
||||
Überlappung (eine LineString) und dem echten Kreuzungspunkt (ein Point) - hier werden nur die
|
||||
Punkte daraus behalten. Die Überlappung selbst bleibt unberührt: Wo genau zwei deckungsgleiche
|
||||
Kanten sich trennen sollen, ist ein Datenfehler in den Eingabedaten, den diese Funktion nicht
|
||||
heilen kann.
|
||||
"""
|
||||
hit = a.intersection(b)
|
||||
if hit.geom_type == "Point":
|
||||
return [(hit.x, hit.y)]
|
||||
if hit.geom_type == "MultiPoint":
|
||||
return [(p.x, p.y) for p in hit.geoms]
|
||||
return []
|
||||
return [(g.x, g.y) for g in getattr(hit, "geoms", [hit]) if g.geom_type == "Point"]
|
||||
|
||||
|
||||
def _add_piece(out: Graph, line: LineString, start: float, end: float, width: float, klass: str) -> None:
|
||||
"""Das Teilstück von `line` zwischen zwei Stationen anhängen, wenn es lang genug ist und seine
|
||||
Enden nicht auf denselben Knoten fallen (Regel d) - attach() sucht dafür je Ende in `out`."""
|
||||
Enden nicht auf denselben Knoten fallen (Regel d) - attach() sucht dafür je Ende in `out`.
|
||||
|
||||
Die gefundenen Knoten-IDs ersetzen die eigenen Endpunkte in points: attach() darf einen bis zu
|
||||
JUNCTION_RADIUS entfernten Knoten wiederverwenden, und ohne diesen Knick hinge die Kante an
|
||||
einem Knoten, auf dem sie geometrisch gar nicht liegt - section.py baut seine Kreuzungsarme in
|
||||
junction_arms() aus genau diesen Endpunkten, nicht aus dem Knotenpunkt. _contract_short() zieht
|
||||
seit Task 4 aus demselben Grund die Kante zum verschobenen Knoten nach.
|
||||
"""
|
||||
if end - start < INSERT_MIN:
|
||||
return
|
||||
points = [(round(x, 2), round(y, 2)) for x, y in substring(line, start, end).coords]
|
||||
a, b = attach(out, points[0]), attach(out, points[-1])
|
||||
if a == b:
|
||||
return
|
||||
points = [out.nodes[a].point, *points[1:-1], out.nodes[b].point]
|
||||
out.edges.append(Edge(a=a, b=b, points=points, width=width, klass=klass))
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user