feat(extract): Flamingo Park hängt seine Straße an den Graphen
World.centerlines wird zu World.graph: apply_street() und apply_park() schneiden und verlängern jetzt den Straßengraphen statt einer losen Liste von Mittellinien, mit den neuen roads.cut_edges()/attach() als Gegenstück zum bestehenden Schneiden der Straßenflächen. apply_park() schneidet dabei einmal am Parkviereck (nicht je Sperrfläche - das schneidet tatsächlich nichts, was der Parkumriss nicht schon geschnitten hätte). check_corners() bleibt unverändert gegen world.roads (den Korridor) messen, mit einem erklärenden Docstring-Absatz und einer Regressionswache dafür in den Tests. MIN_CENTERLINE (15 m) entfällt zugunsten von MIN_EDGE (5 m): Der alte Grund - Laternen wählten ihre Straßenseite über den Listenindex - fällt weg, sobald eine spätere Aufgabe die Seite aus der Geometrie bestimmt. test_park_street.py dokumentiert das jetzt: die beiden 11-m-Enden der geschnittenen Gasse bleiben deshalb stehen, statt wie vorher zu verschwinden. Beim Nachrechnen der Prüfkörper aus dem Auftrag fielen zwei echte Fehler auf: Der Reste-unter- Mindestlänge-Test schnitt ursprünglich bis übers Streckenende hinaus, sodass der Rest schon geometrisch verschwand und der MIN_EDGE-Filter nie geprüft wurde - jetzt schneidet er innerhalb der Strecke, sodass tatsächlich ein zu kurzes Stück verworfen wird. Und die neue Korridor-Regressionswache verglich (12-7)/2 > 2.5, was mit den echten Maßen immer falsch ist (2.5 > 2.5); korrigiert auf >=, denn die Halbdifferenz trifft die Toleranz exakt. Ausserdem musste eine Test-Fixture in test_park_apply.py von einem 5-m-Reststück (exakt an der neuen MIN_EDGE-Grenze, hätte also entgegen der ursprünglichen Testabsicht überlebt) auf 3 m verkürzt werden, damit der Test weiterhin prüft, dass echte Kurzreste wegfallen. run.py bleibt unangetastet: Es ist bereits durch eine frühere Aufgabe gebrochen und wird von der nächsten Aufgabe repariert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8283109ceb
commit
8bc8ca19fc
5 files changed
+162
-23
No files matched your search
@@ -18,7 +18,7 @@ from dataclasses import dataclass, field
|
||||
import networkx as nx
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
from shapely.geometry import LineString
|
||||
from shapely.geometry import LineString, Polygon
|
||||
from skimage.morphology import disk
|
||||
|
||||
from .coords import SLICE
|
||||
@@ -280,6 +280,50 @@ def measure_widths(graph: Graph, distance: np.ndarray) -> Graph:
|
||||
return graph
|
||||
|
||||
|
||||
# Kürzere Reste nach einem Schnitt sind Stummel, die nur eine Kreuzung vortäuschen würden.
|
||||
MIN_EDGE = 5.0
|
||||
|
||||
|
||||
def cut_edges(graph: Graph, cut: Polygon) -> Graph:
|
||||
"""Alles, was in der Fläche liegt, aus den Kanten herausschneiden.
|
||||
|
||||
Gegenstück zum Schneiden der Straßenflächen: Wo Flamingo Park die alte Gasse entfernt, darf
|
||||
im Graphen an derselben Stelle keine Kante mehr stehen - sonst führte dort weiterhin eine
|
||||
Fahrbahn durch den Park, nur ohne die Straßenfläche, die sie einmal begründet hat.
|
||||
"""
|
||||
out = Graph(nodes=list(graph.nodes), edges=[])
|
||||
for edge in graph.edges:
|
||||
line = LineString(edge.points)
|
||||
if not line.intersects(cut):
|
||||
out.edges.append(edge)
|
||||
continue
|
||||
rest = line.difference(cut)
|
||||
for piece in getattr(rest, "geoms", [rest]):
|
||||
if piece.geom_type != "LineString" or piece.length < MIN_EDGE:
|
||||
continue
|
||||
points = [(round(x, 2), round(y, 2)) for x, y in piece.coords]
|
||||
out.edges.append(Edge(a=attach(out, points[0]), b=attach(out, points[-1]),
|
||||
points=points, width=edge.width, klass=edge.klass))
|
||||
return _renumber(out)
|
||||
|
||||
|
||||
def attach(graph: Graph, point: tuple[float, float], radius: float = JUNCTION_RADIUS) -> int:
|
||||
"""Knoten-ID an dieser Stelle: der nächste innerhalb von radius, sonst ein neuer.
|
||||
|
||||
Damit hängt sich eine von Hand ergänzte Straße (die Jefferson Avenue) an die Querstraße an,
|
||||
statt als freies Ende danebenzuliegen und ohne Kreuzung zu enden.
|
||||
"""
|
||||
best, best_distance = None, radius
|
||||
for k, node in enumerate(graph.nodes):
|
||||
distance = ((node.point[0] - point[0]) ** 2 + (node.point[1] - point[1]) ** 2) ** 0.5
|
||||
if distance <= best_distance:
|
||||
best, best_distance = k, distance
|
||||
if best is not None:
|
||||
return best
|
||||
graph.nodes.append(Node((float(point[0]), float(point[1]))))
|
||||
return len(graph.nodes) - 1
|
||||
|
||||
|
||||
def _median_width(line: LineString, distance: np.ndarray) -> float:
|
||||
start, end = WIDTH_MARGIN, line.length - WIDTH_MARGIN
|
||||
if end <= start:
|
||||
|
||||
Reference in new issue
Block a user