Files
gta6/tools/extract/tests/test_vectors.py
T
knsoandClaude Sonnet 5 47c3cb98da fix(extract): Brückenerkennung per Verschneidung, Mittellinien ohne Dopplung
Fix-Runde 1, drei Befunde:

- road_polygons: Straßennetz ist ein zusammenhängendes Polygon über den ganzen
  Ausschnitt; eine Fensterprüfung um seine Bounding-Box sieht daher zwangsläufig
  Wasser und meldet fast alles als Brücke. Ersetzt durch Verschneidung jedes
  Straßenpolygons mit der Landfläche (Ruling 13). Die wörtlich vorgegebene
  Landberechnung (unary_union(land_polygons(labels))) besteht die bestehenden
  Tests nicht: eine Straße ist nie "wasser", also zählt land_polygons() sie
  selbst als Land, und ein Damm quer durchs Meer überlappt dann mit sich selbst
  statt als Brücke aufzufallen (siehe Fix-Bericht für den Nachweis). Land wird
  stattdessen ohne die Straßenklasse berechnet und um LAND_REACH_PX gewachsen,
  damit eine Straße, die direkt an Land beginnt, dort noch erkannt wird. Der
  Radius wurde am echten Mosaik kalibriert: unter 8 px verkettet sich eine
  gewöhnliche Küstenstraße mangels Berührung mit "Land" erneut zu einem
  kartenweiten Brückenstück; 10 px behebt das mit Sicherheitsabstand zur
  Wasserlücke des bestehenden Tests.

- centerlines: eine Kontur um das ein Pixel breite Skelett lief auf beiden
  Seiten des Bandes hin und zurück und lieferte jede Linie doppelt; an
  Kreuzungen ergab das eine einzelne Schleife statt getrennter Äste (Ruling 14,
  neue Tests zuerst rot gesehen). Ersetzt durch einen Pixelgraphen (networkx):
  Skelettpixel als Knoten, 8er-Nachbarschaft als Kanten, Pfade zwischen Knoten
  mit Grad ungleich 2 je Zusammenhangskomponente, reine Ringe einmal umlaufen.

- mask_polygons: Polygon(ring).buffer(0) kann bei selbstschneidenden Konturen
  ein MultiPolygon liefern; das schlug später mit AttributeError fehl. Beim
  Einsortieren in outers/holes wird jetzt nur noch ein sauberes Polygon
  akzeptiert (Ruling 15).

16/16 Tests grün. Auf dem echten Mosaik: 469 Straßenstücke (190 Brücken,
größtes Brückenstück 26.294 m² auf einer lokalen Fläche von 643 x 564 m,
nicht mehr kartenweit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 15:07:46 +02:00

47 lines
2.0 KiB
Python

import numpy as np
from leonida.classify import CLASSES
from leonida.vectors import centerlines, land_polygons, mask_polygons, road_polygons
def test_square_becomes_one_polygon_with_right_area():
mask = np.zeros((100, 100), dtype=bool)
mask[20:60, 30:80] = True # 40 x 50 Pixel = 2000 m²
polys = mask_polygons(mask, min_area=10, simplify=1.0)
assert len(polys) == 1
assert abs(polys[0].area - 2000) < 60 # Vereinfachung darf etwas abweichen
def test_island_keeps_its_lagoon():
labels = np.full((80, 80), CLASSES["wasser"], dtype=np.uint8)
labels[10:70, 10:70] = CLASSES["land"]
labels[30:50, 30:50] = CLASSES["wasser"] # Lagune
polys = land_polygons(labels)
assert len(polys) == 1
assert len(polys[0].interiors) == 1
def test_road_over_water_is_flagged_as_bridge():
labels = np.full((60, 60), CLASSES["wasser"], dtype=np.uint8)
labels[0:10, :] = CLASSES["land"]
labels[25:31, :] = CLASSES["strasse"] # Damm quer durchs Wasser
roads = road_polygons(labels)
assert len(roads) == 1
assert roads[0][1] is True
def test_crossing_roads_form_a_single_polygon():
labels = np.full((60, 60), CLASSES["land"], dtype=np.uint8)
labels[28:32, :] = CLASSES["strasse"]
labels[:, 28:32] = CLASSES["strasse"]
roads = road_polygons(labels)
assert len(roads) == 1 # Kreuzung, kein Zerfall in vier Arme
def test_centerline_runs_once_along_a_straight_road():
labels = np.full((60, 300), CLASSES["land"], dtype=np.uint8)
labels[28:38, 50:250] = CLASSES["strasse"] # 200 lang, 10 breit
lines = centerlines(labels)
assert len(lines) == 1
assert 180 <= lines[0].length <= 215 # einmal entlang, nicht hin und zurück
def test_crossing_roads_yield_separate_centerlines():
labels = np.full((120, 120), CLASSES["land"], dtype=np.uint8)
labels[55:65, :] = CLASSES["strasse"]
labels[:, 55:65] = CLASSES["strasse"]
assert len(centerlines(labels)) >= 3 # Arme werden an der Kreuzung getrennt