import numpy as np import pytest from shapely.geometry import Polygon from leonida.buildings import building_polygons, regularize from leonida.classify import CLASSES def test_rough_rectangle_snaps_to_rectangle(): poly = Polygon([(0, 0), (10.4, 0.3), (10.1, 20.2), (-0.2, 19.8)]) out = regularize(poly) assert len(out.exterior.coords) == 5 # geschlossenes Rechteck assert abs(out.area - 205) < 25 @pytest.mark.filterwarnings("ignore:.*oriented_envelope.*:RuntimeWarning") def test_l_shape_is_kept_not_boxed(): """out.minimum_rotated_rectangle (im Assert unten) loest fuer dieses achsen- parallele Ergebnispolygon eine harmlose Shapely/GEOS-RuntimeWarning aus (Winkelterm wird 0/0, das Rechteck bleibt korrekt) - siehe Befund C, Ruling 25. Nur dieser eine Test filtert sie; die Suite tut es nicht mehr global.""" poly = Polygon([(0, 0), (30, 0), (30, 10), (10, 10), (10, 30), (0, 30)]) out = regularize(poly) assert out.area < 0.8 * out.minimum_rotated_rectangle.area def test_tiny_polygon_is_dropped(): assert regularize(Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])) is None def test_two_buildings_are_separated(): labels = np.full((60, 60), CLASSES["stadt"], dtype=np.uint8) labels[10:25, 10:25] = CLASSES["gebaeude"] labels[35:50, 35:50] = CLASSES["gebaeude"] assert len(building_polygons(labels)) == 2 def test_small_staircase_building_is_not_dropped(): """regularize() darf nicht doppelt vereinfachen (Ruling 28). mask_polygons() liefert Kandidaten bereits mit 0,5 m simplify und mindestens MIN_AREA. Ein Grundriss mit gestuftem (Treppenrand-)Eck, der die Rechteckquote von 0,85 verfehlt, wurde bisher ein zweites Mal - und gröber, mit 1,0 m - vereinfacht und damit unter MIN_AREA gedrückt, obwohl er die 0,5-m-Vereinfachung klar übersteht. An zwei gegenüberliegenden Ecken eines 4x4-Blocks fehlt je ein Gebäudepixel, wie bei einer gefasten Ecke auf dem echten Mosaik. """ labels = np.full((40, 40), CLASSES["stadt"], dtype=np.uint8) block = np.array([ [1, 1, 1, 0], [1, 1, 1, 1], [1, 1, 1, 1], [0, 1, 1, 1], ], dtype=bool) labels[10:14, 10:14][block] = CLASSES["gebaeude"] assert len(building_polygons(labels)) == 1 def test_speculative_flag_matches_pixel_class(): """building_polygons() markiert nur aus 'spekulativ' (rot) entstandene Grundrisse als spekulativ; aus 'gebaeude' (grau) entstandene bleiben False.""" labels = np.full((60, 60), CLASSES["stadt"], dtype=np.uint8) labels[10:25, 10:25] = CLASSES["gebaeude"] # grau, kleinere Spalten -> kleineres Welt-x labels[35:50, 35:50] = CLASSES["spekulativ"] # rot, groessere Spalten -> groesseres Welt-x result = building_polygons(labels) assert len(result) == 2 result.sort(key=lambda item: item[0].centroid.x) (_, spec_from_gebaeude), (_, spec_from_spekulativ) = result assert spec_from_gebaeude is False assert spec_from_spekulativ is True