fix(extract): set_precision statt Koordinatenrundung gegen selbstschneidende Ringe
_rings() rundete bislang jede Koordinate unabhängig auf 2 Stellen, nach normalize(). Das kann zwei Ringpunkte so verschieben, dass sich der Ring selbst schneidet, obwohl das Ausgangspolygon gültig war - Ursache der acht neuen ungültigen Polygone in den ausgelieferten Chunks (carriageway 3, land 2, sidewalk 2, surfaces 1). shapely.set_precision(poly, 0.01) läuft jetzt vor normalize() und vor der Rundung: Es ist eine echte Geometrieoperation (GEOS hält das Ergebnis mit mode="valid_output" aktiv gültig) statt eines blinden round() je Zahl. Gegen alle 29.037 Polygone der committeten (bereits einmal falsch gerundeten) Chunks probeweise gerechnet: kein Absturz, 8 der 9 ungültigen Polygone (die neunte ist die bekannte, von diesem Zweig unabhängige Vorbelastung in "paths", chunk_10_6, schon vor diesem Zweig ungültig) werden gültig, Flächenänderung insgesamt -24,35 m² von 11.711.354 m² (-0,00021 %). Zwei Sonderfälle brauchten eine eigene Behandlung, die die Zeilen im Befund nicht erwähnen, ohne die aber der Vier-Minuten-Lauf hätte abbrechen oder eine falsche Fläche ausliefern können: - An einer Engstelle unter 1 cm kann set_precision ein Polygon in mehrere Teile spalten (3 von 29.037 Fällen, größter abgeschnittener Teil 25,9 m²) - poly.exterior auf einer MultiPolygon wäre ein AttributeError gewesen. Es bleibt jetzt das größere Teilstück. - Ganz vereinzelt lehnt GEOS das Schnappen mit einer TopologyException ab (in der Stichprobe nur an einem bereits vorher ungültigen Polygon); dann bleibt das Polygon ungerundet, und die alte Rundung je Koordinate greift wie vor diesem Befund, statt den Lauf abzubrechen. Zwei Tests, beide ohne die Korrektur rot: ein valides Polygon, dessen unabhängig gerundete Ecken sich kreuzen (Beispiel selbst nachgerechnet, nicht nur behauptet), und eine Hantelform mit einem Steg unter 1 cm, die am unkorrigierten _rings() unter der Rundung selbst ungültig wird statt sich sauber zu teilen.
This commit is contained in:
1 parent
83140f89e1
commit
a397892847
2 files changed
+74
-2
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from shapely.geometry import Polygon
|
||||
from leonida.emit import chunk_payload, split_by_chunk, split_by_chunk_all
|
||||
from leonida.emit import _rings, chunk_payload, split_by_chunk, split_by_chunk_all
|
||||
|
||||
BIG = Polygon([(-1000, 2500), (-700, 2500), (-700, 2200), (-1000, 2200)]) # Chunk (0,0) und Nachbarn
|
||||
HEIGHT = {"height": 9.0, "source": "district"}
|
||||
@@ -53,6 +54,52 @@ def test_polygon_holes_survive_into_the_payload():
|
||||
assert max(x for x, _ in exterior) > max(x for x, _ in hole)
|
||||
assert all(0 <= x <= 250 and 0 <= z <= 250 for x, z in hole)
|
||||
|
||||
def test_rings_bleiben_gueltig_wenn_koordinatenweises_runden_selbstschnitte_erzeugen_wuerde():
|
||||
"""Befund 5 der Fixwelle: _rings() rundete bislang jede Koordinate unabhaengig auf 2 Stellen,
|
||||
NACH normalize(). Das kann zwei Ringpunkte so verschieben, dass sich der Ring selbst
|
||||
schneidet, obwohl das Ausgangspolygon gueltig war. Am echten Netz betraf das 8 von 144
|
||||
Chunks (carriageway 3, land 2, sidewalk 2, surfaces 1) - je zwei ungueltige Polygone, alle
|
||||
neu in diesem Zweig, Ursache 2-Stellen-Rundung statt einer echten Geometrieaenderung.
|
||||
|
||||
Das folgende Polygon ist gueltig; unabhaengig gerundete Ecken (die alte Methode) kreuzen
|
||||
sich trotzdem - der Befund ist damit am Beispiel nachvollzogen, nicht nur behauptet.
|
||||
set_precision() vor normalize() (die neue Methode) haelt das Ergebnis gueltig, weil es eine
|
||||
echte Geometrieoperation ist statt eines blinden round() je Zahl.
|
||||
"""
|
||||
poly = Polygon([(16.083798, 6.586106), (20.804728, 8.005357),
|
||||
(18.203748, 0.906454), (18.305129, 1.203848)])
|
||||
assert poly.is_valid
|
||||
naiv = Polygon([(round(x, 2), round(y, 2)) for x, y in poly.exterior.coords])
|
||||
assert not naiv.is_valid, "das Beispiel muss den Befund tatsaechlich zeigen"
|
||||
|
||||
rings = _rings(poly, 0, 0)
|
||||
ergebnis = Polygon(rings[0], rings[1:])
|
||||
assert ergebnis.is_valid
|
||||
assert ergebnis.area == pytest.approx(poly.area, rel=0.01)
|
||||
|
||||
|
||||
def test_rings_behaelt_nur_das_groessere_teilstueck_an_einer_engstelle_unter_einem_zentimeter():
|
||||
"""set_precision() kann ein Polygon mit einem Steg unter 1 cm Breite beim Schnappen auf das
|
||||
1-cm-Raster in zwei Teile spalten (an einer Stichprobe der ausgelieferten Daten: 3 von
|
||||
29.037 Polygonen, groesster abgeschnittener Teil 25,9 m²). _rings() liefert aber immer genau
|
||||
ein Ringset - eine MultiPolygon ungeprueft an poly.exterior gereicht waere ein
|
||||
AttributeError. Hier bleibt das groessere der beiden Teilstuecke.
|
||||
|
||||
Am unkorrigierten _rings() (kein set_precision) kollabiert derselbe Steg unter der
|
||||
Koordinatenrundung auf exakt dieselbe Zeile und macht das EINE Ergebnis-Polygon ungueltig,
|
||||
statt es sauber zu teilen - auch das ist also gegen den Befund rot.
|
||||
"""
|
||||
hantel = Polygon([
|
||||
(0.0, 0.0), (10.0, 0.0), (10.0, 4.997), (20.0, 4.997), (20.0, 2.0), (26.0, 2.0), (26.0, 8.0),
|
||||
(20.0, 8.0), (20.0, 5.003), (10.0, 5.003), (10.0, 10.0), (0.0, 10.0),
|
||||
])
|
||||
assert hantel.is_valid and hantel.area == pytest.approx(136.06)
|
||||
rings = _rings(hantel, 0, 0) # darf nicht mit AttributeError abstuerzen
|
||||
ergebnis = Polygon(rings[0], rings[1:])
|
||||
assert ergebnis.is_valid
|
||||
assert ergebnis.area == pytest.approx(100.0, abs=0.5), "das groessere der beiden Teilstuecke"
|
||||
|
||||
|
||||
def test_the_same_geometry_in_another_order_gives_the_same_output():
|
||||
"""Die Ausgabe darf nicht am Rechenweg haengen, nur an der Geometrie.
|
||||
|
||||
|
||||
Reference in new issue
Block a user