fix(extract): Polygonlöcher im Chunk-Payload erhalten, split_by_chunk_all abdecken
Ruling 34 (Important, Fix-Runde 1 zu Aufgabe 6): _ring() verwarf jedes Loch eines Polygons (nur poly.exterior wurde in Weltkoordinaten -> three.js uebertragen). Vor dem Chunk-Zuschnitt war das meist harmlos, weil das groesste Loch die Bucht selbst ist und beim Zuschneiden ohnehin herausfaellt - danach blieb es aber ein echter Datenverlust: ein von Strassen umschlossener Block wurde zu Asphalt, ein Innenhof zu Gebaeudemasse, ein Teich im Park zu Wiese. Gemessen an echten Daten: 120 Strassen-Chunk-Stuecke (449.876 m2), 127 Stadtflaechen-Stuecke (208.657 m2 wobei 1 weiteres Park-Stueck), 30 Land-Stuecke (11.402 m2) und mindestens 75 Gebaeude-Stuecke trugen ein verworfenes Loch. _ring() wird _rings(): liefert [poly.exterior, *poly.interiors] in derselben Transformation, Aussenring zuerst. Payload-Schema aendert sich entsprechend fuer land/surfaces/roads/buildings von einem flachen Ring zu einer Liste von Ringen; centerlines und palms sind unveraendert (keine Polygone). Task 7s extrudePolygons() ruft ShapeUtils.triangulateShape(ring, []) bereits mit einem (bisher leeren) Loecher-Array - die Aenderung passt jetzt durchgehend, bevor Aufgabe 7 gebaut wird. write_debug_png() zeichnet weiterhin nur den Aussenring (poly[0]), Loecher werden im Debug-Bild nicht dargestellt. Neuer Test test_polygon_holes_survive_into_the_payload prueft das direkt an einem Block mit Loch; die Koordinaten wurden vor dem Festschreiben gegen die tatsaechliche chunk_payload()-Transformation nachgerechnet (uv run python, nicht nur von Hand). test_coordinates_are_chunk_local_three_js musste auf das neue [0]-Aussenring-Indexing angepasst werden. Ruling 35 (Important): split_by_chunk_all() - die Variante, die run.py tatsaechlich benutzt, nicht das getestete split_by_chunk() - hatte einen MultiPolygon-Zweig ohne jede Testabdeckung. Neuer Test mit einer Klammerform (Steg ausserhalb der Chunk-Zelle, zwei Zinken je einmal hineinreichend) trifft genau diesen Zweig gezielt; er war schon vor der Aenderung gruen - bestaetigt als Abdeckungsluecke, nicht als Bug. uv run pytest -v: 36 passed (34 zuvor + 2 neu). Pipeline (run.py) am gecachten Mosaik neu durchlaufen: weiterhin 144 Chunk-Dateien, public/data/vice-a/ jetzt 2,7 MB (zuvor 2,5 MB; +0,2 MB fuer die erhaltenen Loecher). Insgesamt tragen jetzt 415 Chunk-Stuecke mindestens ein Loch: Land 30/213, Stadtflaechen 127/1145 (+1 Park-Flaeche), Strassen 120/953, Gebaeude 137/2859 (80 global-eindeutige Gebaeude vor Chunk-Duplizierung). Land/Strassen/Stadtflaechen treffen die oben genannten Referenzwerte exakt; bei Gebaeuden weicht die Stueckzahl vom Review-Befund (75) ab. Stichprobe der vier flaechenmaessig groessten Gebaeude-Loecher (Debug-PNGs chunk_8_0, chunk_2_10, chunk_5_11 sowie ein viertes) zeigt plausible echte Ring-Bauwerke (ein stadionartiger Rundbau, eine Wendel-Parkrampe) statt offensichtlicher Fehlklassifikation - die verbleibende Abweichung duerfte an der bereits im Task-6-Bericht offengelegten Restunsicherheit der Gebaeude-Ueberlappungsrekonstruktion liegen (263 vs. 258 spekulative Gebaeude), nicht an dieser Aenderung; im Fix-Bericht dokumentiert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
062a9af2fd
commit
22ac8f530c
289 files changed
+194
-156
No files matched your search
@@ -24,11 +24,19 @@ def split_by_chunk(poly: Polygon) -> dict[tuple[int, int], Polygon]:
|
||||
return out
|
||||
|
||||
|
||||
def _ring(poly: Polygon, i: int, j: int) -> list[list[float]]:
|
||||
def _rings(poly: Polygon, i: int, j: int) -> list[list[list[float]]]:
|
||||
"""Aussenring zuerst, danach jedes Loch, in derselben Transformation.
|
||||
|
||||
Ruling 34: ein Loch (z.B. ein von Strassen umschlossener Block, ein Innenhof, ein Teich
|
||||
im Park) ist genauso echte Flaeche wie der Aussenring und darf beim Zuschneiden nicht
|
||||
verschwinden - sonst wird der Block zu Asphalt, der Innenhof zu Gebaeudemasse, der Teich
|
||||
zu Wiese.
|
||||
"""
|
||||
ox = SLICE["x0"] + i * CHUNK_SIZE
|
||||
oy = SLICE["y1"] - j * CHUNK_SIZE
|
||||
# Welt (X, Y) -> three (x, z) -> relativ zur Chunk-Ecke
|
||||
return [[round(x - ox, 2), round(-y - (-oy), 2)] for x, y in poly.exterior.coords]
|
||||
return [[[round(x - ox, 2), round(-y - (-oy), 2)] for x, y in ring.coords]
|
||||
for ring in (poly.exterior, *poly.interiors)]
|
||||
|
||||
|
||||
def chunk_payload(i, j, *, land, surfaces, roads, centerlines, buildings, palms) -> dict:
|
||||
@@ -37,11 +45,11 @@ def chunk_payload(i, j, *, land, surfaces, roads, centerlines, buildings, palms)
|
||||
return {
|
||||
"chunk": [i, j],
|
||||
"origin": [ox, -oy],
|
||||
"land": [_ring(p, i, j) for p in land],
|
||||
"surfaces": [{"poly": _ring(p, i, j), "kind": k} for p, k in surfaces],
|
||||
"roads": [{"poly": _ring(p, i, j), "bridge": b} for p, b in roads],
|
||||
"land": [_rings(p, i, j) for p in land],
|
||||
"surfaces": [{"poly": _rings(p, i, j), "kind": k} for p, k in surfaces],
|
||||
"roads": [{"poly": _rings(p, i, j), "bridge": b} for p, b in roads],
|
||||
"centerlines": [[[round(x - ox, 2), round(-y + oy, 2)] for x, y in ln.coords] for ln in centerlines],
|
||||
"buildings": [{"poly": _ring(p, i, j), "height": h["height"],
|
||||
"buildings": [{"poly": _rings(p, i, j), "height": h["height"],
|
||||
"source": h["source"], "speculative": s} for p, h, s in buildings],
|
||||
"palms": [[round(x - ox, 2), round(-y + oy, 2)] for x, y in palms],
|
||||
}
|
||||
@@ -78,13 +86,16 @@ def write_debug_png(out_dir: Path, mosaic, payloads: list[dict]) -> None:
|
||||
x0, y0 = i * CHUNK_SIZE, j * CHUNK_SIZE
|
||||
crop = Image.fromarray(mosaic[y0:y0 + CHUNK_SIZE, x0:x0 + CHUNK_SIZE]).convert("RGB")
|
||||
draw = ImageDraw.Draw(crop)
|
||||
for ring in payload["land"]:
|
||||
for rings in payload["land"]:
|
||||
ring = rings[0] # nur der Aussenring; Loecher werden nicht gezeichnet
|
||||
draw.line([tuple(pt) for pt in ring] + [tuple(ring[0])], fill=(0, 255, 255), width=1)
|
||||
for road in payload["roads"]:
|
||||
draw.line([tuple(pt) for pt in road["poly"]], fill=(255, 0, 255), width=1)
|
||||
ring = road["poly"][0]
|
||||
draw.line([tuple(pt) for pt in ring], fill=(255, 0, 255), width=1)
|
||||
for b in payload["buildings"]:
|
||||
if len(b["poly"]) >= 3:
|
||||
draw.polygon([tuple(pt) for pt in b["poly"]], outline=(255, 220, 0))
|
||||
ring = b["poly"][0]
|
||||
if len(ring) >= 3:
|
||||
draw.polygon([tuple(pt) for pt in ring], outline=(255, 220, 0))
|
||||
for x, z in payload["palms"]:
|
||||
draw.ellipse([x - 2, z - 2, x + 2, z + 2], outline=(0, 200, 0))
|
||||
crop.save(out_dir / f"chunk_{i}_{j}.png")
|
||||
|
||||
Reference in new issue
Block a user