fix(extract): cut_edges() bildet keine Schleifen mehr, Straßen kreuzen das Netz statt es zu überlagern

Fixrunde 1 zu Task 11, zwei von der Koordination gefundene und am echten Netz nachgemessene
Fehler, beide durch die Senkung von MIN_CENTERLINE (15 m) auf MIN_EDGE (5 m) erst möglich:

1. cut_edges() hängte beide Enden eines Schnittstücks über attach() an - dessen 8-m-Umkreis fand
   bei einem Reststück zwischen 5 und 8 m Länge für beide Enden denselben ursprünglichen Knoten
   und machte aus dem geraden Stummel eine Schleife. section.py hält jeden Knoten ab Grad 3 für
   eine Kreuzung; an einem Sackgassenende wären dort Kreuzungsfläche, Zebrastreifen und
   Haltelinie entstanden. Die neue _cut_end() sucht nicht mehr im Umkreis, sondern nur noch nach
   exakter Übereinstimmung mit einem der beiden ursprünglichen Kantenenden.

2. apply_street() hängte die Jefferson Avenue nur mit einem attach()-Paar an ihre beiden Enden.
   Am echten Netz liegt ihr Nordende 23,2 m von jedem Knoten entfernt, aber die Straße kreuzt auf
   dem Weg zu ihrem Südende (6,6 m von Knoten 561) die 11th Street mittendrin, ohne dass dort ein
   Knoten entsteht - zwei Fahrbahnen hätten sich unverbunden überlagert. Die neue insert_edge()
   bricht jede gekreuzte Kante am Kreuzungspunkt auf und teilt sich dort einen Knoten mit der
   neuen Straße (mit attach() für die Knotenwahl, wie gehabt); apply_street() benutzt sie jetzt
   statt des attach()-Paars.

Am echten Netz (1.537 Knoten, 1.790 Kanten vor Flamingo Park) bestätigt: Jefferson Avenue bricht
jetzt korrekt an der 11th Street auf (neuer Knoten, Grad 4) und hängt am Südende weiterhin an
Knoten 561 (Grad 3 -> 4); die Meldezeile und die Sperrflächen-Warnung blieben wörtlich. Die
Schleifenzahl im Gesamtnetz sank von 196 auf 193 (die Park-Anlage schneidet alte Schleifen im
Parkgebiet weg), keine der verbliebenen 193 entstand neu - jede hat ein Vor-Park-Gegenstück im
5-m-Umkreis. Keine Kante unter 1 m, weder vorher noch nachher.

Neue Tests: test_roads_cut.py (Schleifenregression), test_roads_insert.py (drei Szenarien:
Kreuzung in der Mitte, Anhängen an einen Grad-3-Knoten, Kreuzungspunkt nahe einem vorhandenen
Knoten), test_park_street.py (Knotengrad an beiden Kreuzungen der Park-Zufahrtsstraße).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-17 17:26:42 +02:00
1 parent 8bc8ca19fc
commit 1f09e7b1d9
5 files changed
+166 -11

No files matched your search

+7 -7
View File
@@ -17,7 +17,7 @@ from shapely.geometry import LineString, Point, Polygon
from shapely.ops import unary_union
from .heights import PALM_CLEARANCE, PALM_KERB, palm_points
from .roads import CLASS_WIDTH, Edge, Graph, attach, cut_edges
from .roads import CLASS_WIDTH, Graph, cut_edges, insert_edge
class ParkError(ValueError):
@@ -497,15 +497,15 @@ def apply_street(world: World, street: Street) -> World:
kept.append((piece, bridge))
roads = kept + [(piece, False) for piece in _polygons(unary_union(joined), MIN_ROAD_AREA)]
# Die Gasse verschwindet auch aus dem Graphen, und die Jefferson Avenue kommt als Kante
# dazu - mit attach() an die Querstraße gehängt, damit dort eine echte Kreuzung entsteht
# statt eines freien Endes. Die Klasse folgt der Breite aus der Parkdatei wie bei jeder
# Die Gasse verschwindet auch aus dem Graphen, und die Jefferson Avenue kommt als Kante dazu.
# insert_edge() statt eines reinen attach()-Paars: Die Avenue endet nicht zwangsläufig an
# einer Kreuzung, sie kann eine Querstraße auch mittendrin schneiden (am echten Netz die
# 11th Street) - dort muss ein echter Knoten entstehen, sonst läge die neue Fahrbahn nur
# unverbunden über der alten. Die Klasse folgt der Breite aus der Parkdatei wie bei jeder
# anderen Kante auch.
graph = cut_edges(world.graph, cut)
klass = min(CLASS_WIDTH, key=lambda name: abs(CLASS_WIDTH[name] - street.width))
graph.edges.append(Edge(a=attach(graph, street.start), b=attach(graph, street.end),
points=[tuple(street.start), tuple(street.end)],
width=street.width, klass=klass))
graph = insert_edge(graph, [tuple(street.start), tuple(street.end)], street.width, klass)
freed = unary_union(removed + [cut]).difference(corridor)
covered = [b[0] for b in buildings if b[0].intersects(freed)] + \