fix(extract): Brücke braucht Nähe zu Wasser, nicht nur Ferne von Land
Fix-Runde 2 (Ruling 17). Nachmessung am echten Mosaik zeigte: 143 der 190 aus Fix-Runde 1 markierten Brücken (137.288 von 208.744 m² Fläche) hatten unter 10 % Wasser in ihrer Umgebung, darunter die beiden größten Stücke mit 2,4 % und 0,1 % Wasser. Das waren breite Kreuzungs- und Parkflächen an Land: ihre Mitte liegt allein deshalb weiter als LAND_REACH_PX von straßenfreiem Land entfernt, weil dort ringsum nur Straße ist – kein Bezug zu Wasser. Ein größeres LAND_REACH_PX heilt das nicht (ab 16 kippt der bestehende Test). Straßenstücke jenseits von "Land" zählen jetzt nur noch als Brücke, wenn sie zusätzlich innerhalb WATER_REACH_PX von Wasser liegen; neue Konstante WATER_REACH_PX = 8, am echten Mosaik kalibriert (6-10 ein stabiles Plateau bei ~40 Brücken, ab 14 bricht es weg und reißt wieder trockene Flächen mit). Regressionstest zuerst rot gesehen (test_wide_road_area_on_land_is_not_a_bridge), bestehende Tests unverändert und weiterhin grün. 17/17 Tests grün. Auf dem echten Mosaik: 475 Straßenstücke, 40 Brücken, größtes Brückenstück 12.588 m² – deckt sich mit den unabhängig ermittelten Referenzwerten der Prüfung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
47c3cb98da
commit
120cafd7bf
2 files changed
+35
-2
No files matched your search
@@ -72,9 +72,17 @@ def road_mask(labels: np.ndarray) -> np.ndarray:
|
||||
# zum künstlichen Test, dessen 15 Pixel breite Wasserlücke sonst zu knapp anläge.
|
||||
LAND_REACH_PX = 10
|
||||
|
||||
# Reichweite in Pixeln (= Metern) für die "nahe am Wasser"-Prüfung: fern von Land allein
|
||||
# macht noch keine Brücke (siehe unten). Am echten Mosaik gemessen (WATER_REACH_PX →
|
||||
# Brücken gesamt / davon "trocken" mit < 10 % Wasser im Umkreis: 6→40/1, 8→40/1,
|
||||
# 10→41/2, 14→72/17) ist 6 bis 10 ein stabiles Plateau, das ab 14 abbricht und wieder
|
||||
# trockene Flächen als Brücke mitreißt; 8 liegt in dessen Mitte.
|
||||
WATER_REACH_PX = 8
|
||||
|
||||
|
||||
def road_polygons(labels: np.ndarray) -> list[tuple[Polygon, bool]]:
|
||||
"""Straßenflächen, aufgeteilt in Stücke auf Land und Brückenstücke über Wasser.
|
||||
"""Straßenflächen, aufgeteilt in Stücke auf Land, Brückenstücke über Wasser und
|
||||
breite Flächen an Land, die nur zufällig weit von "Land" entfernt liegen.
|
||||
|
||||
"Land" ist hier die Geländeklasse ohne die Straße selbst, um LAND_REACH_PX nach
|
||||
außen gewachsen. Ohne den Ausschluss der Straße wäre das zirkulär: land_polygons()
|
||||
@@ -83,14 +91,27 @@ def road_polygons(labels: np.ndarray) -> list[tuple[Polygon, bool]]:
|
||||
Brücke auffallen. Der Wuchs sorgt dafür, dass eine Straße, die direkt an echtem
|
||||
Land beginnt, dort noch als "auf Land" erkannt wird, statt an ihrer eigenen Kante
|
||||
ins Leere zu greifen.
|
||||
|
||||
"Fern von Land" reicht als Brückenkriterium allein nicht aus: eine breite Kreuzungs-
|
||||
oder Parkfläche ist überall nur Straße, ihre Mitte liegt also zwangsläufig weiter als
|
||||
LAND_REACH_PX von straßenfreiem Land entfernt, obwohl gar kein Wasser in der Nähe
|
||||
ist. Ein Stück jenseits von "Land" zählt deshalb nur dann als Brücke, wenn es
|
||||
zusätzlich innerhalb WATER_REACH_PX von Wasser liegt; der Rest – fern von Land, aber
|
||||
auch fern von Wasser – bleibt "auf Land".
|
||||
"""
|
||||
solid_ground = ndi.binary_dilation(
|
||||
(labels != CLASSES["wasser"]) & ~road_mask(labels), disk(LAND_REACH_PX)
|
||||
)
|
||||
land = unary_union(mask_polygons(solid_ground, min_area=50, simplify=1.0))
|
||||
wet = unary_union(mask_polygons(
|
||||
ndi.binary_dilation(labels == CLASSES["wasser"], disk(WATER_REACH_PX)),
|
||||
min_area=50, simplify=1.0))
|
||||
out = []
|
||||
for poly in mask_polygons(road_mask(labels), min_area=20, simplify=0.8):
|
||||
for part, bridge in ((poly.intersection(land), False), (poly.difference(land), True)):
|
||||
off = poly.difference(land)
|
||||
for part, bridge in ((poly.intersection(land), False), # auf Land
|
||||
(off.intersection(wet), True), # über Wasser: Brücke
|
||||
(off.difference(wet), False)): # breite Fläche an Land
|
||||
for piece in getattr(part, "geoms", [part]):
|
||||
if piece.geom_type == "Polygon" and piece.area >= 20:
|
||||
out.append((piece, bridge))
|
||||
|
||||
@@ -44,3 +44,15 @@ def test_crossing_roads_yield_separate_centerlines():
|
||||
labels[55:65, :] = CLASSES["strasse"]
|
||||
labels[:, 55:65] = CLASSES["strasse"]
|
||||
assert len(centerlines(labels)) >= 3 # Arme werden an der Kreuzung getrennt
|
||||
|
||||
def test_wide_road_area_on_land_is_not_a_bridge():
|
||||
"""Eine breite Kreuzungsfläche ohne Wasser in der Nähe ist nie eine Brücke.
|
||||
|
||||
Ihre Mitte liegt weiter als LAND_REACH_PX von straßenfreiem Land entfernt, weil
|
||||
dort nur Straße ist — das allein darf sie nicht zur Brücke machen.
|
||||
"""
|
||||
labels = np.full((120, 120), CLASSES["land"], dtype=np.uint8)
|
||||
labels[30:90, 30:90] = CLASSES["strasse"] # 60x60 Straßenfläche, kein Wasser
|
||||
pieces = road_polygons(labels)
|
||||
assert pieces
|
||||
assert not any(bridge for _, bridge in pieces)
|
||||
Reference in new issue
Block a user