feat(extract): Flamingo Park in run.py, Parkwege in der Chunk-Ausgabe
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ef9e6774a
commit
442f5b2ada
5 files changed
+64
-12
No files matched your search
@@ -39,7 +39,7 @@ def _rings(poly: Polygon, i: int, j: int) -> list[list[list[float]]]:
|
||||
for ring in (poly.exterior, *poly.interiors)]
|
||||
|
||||
|
||||
def chunk_payload(i, j, *, land, surfaces, roads, centerlines, buildings, palms) -> dict:
|
||||
def chunk_payload(i, j, *, land, surfaces, roads, centerlines, buildings, palms, paths=()) -> dict:
|
||||
ox = SLICE["x0"] + i * CHUNK_SIZE
|
||||
oy = SLICE["y1"] - j * CHUNK_SIZE
|
||||
return {
|
||||
@@ -52,6 +52,8 @@ def chunk_payload(i, j, *, land, surfaces, roads, centerlines, buildings, palms)
|
||||
"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],
|
||||
# Parkwege (Flamingo Park, docs/superpowers/specs/2026-09-15-flamingo-park-design.md), aufgebaut wie roads.
|
||||
"paths": [{"poly": _rings(p, i, j)} for p in paths],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -522,3 +522,17 @@ def apply_park(world: World, layout: ParkLayout, seed: int) -> World:
|
||||
palms += palm_points([], _polygons(lawn), [], [] if blocked.is_empty else [blocked], [], [], seed)
|
||||
return replace(world, roads=roads, centerlines=centerlines, surfaces=surfaces,
|
||||
buildings=buildings, palms=palms, paths=world.paths + layout.paths)
|
||||
|
||||
|
||||
def apply_flamingo_park(world: World, config_path: Path, seed: int) -> tuple[World, list[str]]:
|
||||
"""Alles für Flamingo Park in der Reihenfolge der Spec: Ecken prüfen, Jefferson Avenue, Park."""
|
||||
config = load_park(config_path)
|
||||
check_corners(config, world.roads)
|
||||
world = apply_street(world, config.street)
|
||||
fit = ParkFit(config)
|
||||
layout = build_layout(load_elements(config.osm_path), config, fit)
|
||||
world = apply_park(world, layout, seed)
|
||||
messages = [f"Warnung: {warning}" for warning in layout.warnings]
|
||||
messages.append(f"Flamingo Park: {len(layout.paths)} Wegflächen, {len(layout.surfaces)} Flächen, "
|
||||
f"{len(layout.buildings)} Gebäude, Jefferson Avenue {config.street.width:g} m breit")
|
||||
return world, messages
|
||||
+18
-8
@@ -8,12 +8,14 @@ from leonida.classify import CLASSES, class_shares, classify, drop_map_labels
|
||||
from leonida.emit import (chunk_box_of, chunk_payload, in_chunk, split_by_chunk_all,
|
||||
write_chunks, write_debug_png)
|
||||
from leonida.heights import assign_heights, palm_points
|
||||
from leonida.park import World, apply_flamingo_park
|
||||
from leonida.tiles import COMMIT, download_tiles, load_mosaic
|
||||
from leonida.vectors import centerlines, land_polygons, mask_polygons, road_polygons
|
||||
from overview import render_map
|
||||
|
||||
SEED = 20260912
|
||||
OUT = Path(__file__).resolve().parents[2] / "public" / "data" / "vice-a"
|
||||
PARK = Path("data/flamingo-park.toml")
|
||||
TOLERANCE = {"wasser": (0.30, 0.36), "strasse": (0.10, 0.14), "gebaeude": (0.09, 0.13)}
|
||||
|
||||
|
||||
@@ -73,25 +75,33 @@ def main() -> None:
|
||||
palms = palm_points(sand, parks, lines, [p for p, _ in buildings], road_shapes,
|
||||
land, SEED)
|
||||
|
||||
world = World(land=land, surfaces=surfaces, roads=roads, centerlines=lines,
|
||||
buildings=[(p, h, s) for (p, s), h in zip(buildings, heights)], palms=palms)
|
||||
world, messages = apply_flamingo_park(world, PARK, SEED)
|
||||
for message in messages:
|
||||
print(message)
|
||||
|
||||
payloads = []
|
||||
for j in range(12):
|
||||
for i in range(12):
|
||||
payloads.append(chunk_payload(
|
||||
i, j,
|
||||
land=list(split_by_chunk_all(land, i, j)),
|
||||
surfaces=[(part, kind) for (p, kind) in surfaces for part in split_by_chunk_all([p], i, j)],
|
||||
roads=[(part, b) for (p, b) in roads for part in split_by_chunk_all([p], i, j)],
|
||||
centerlines=[ln for ln in lines if ln.intersects(chunk_box_of(i, j))],
|
||||
buildings=[(p, h, s) for (p, s), h in zip(buildings, heights)
|
||||
if p.intersects(chunk_box_of(i, j))],
|
||||
palms=[pt for pt in palms if in_chunk(pt, i, j)],
|
||||
land=list(split_by_chunk_all(world.land, i, j)),
|
||||
surfaces=[(part, kind) for (p, kind) in world.surfaces for part in split_by_chunk_all([p], i, j)],
|
||||
roads=[(part, b) for (p, b) in world.roads for part in split_by_chunk_all([p], i, j)],
|
||||
centerlines=[ln for ln in world.centerlines if ln.intersects(chunk_box_of(i, j))],
|
||||
buildings=[(p, h, s) for (p, h, s) in world.buildings if p.intersects(chunk_box_of(i, j))],
|
||||
palms=[pt for pt in world.palms if in_chunk(pt, i, j)],
|
||||
paths=[part for p in world.paths for part in split_by_chunk_all([p], i, j)],
|
||||
))
|
||||
write_debug_png(Path("debug"), mosaic, payloads)
|
||||
write_chunks(OUT, payloads, {
|
||||
"slice": "vice-a", "source_commit": COMMIT, "seed": SEED,
|
||||
"chunk_size": 250, "chunks": [p["chunk"] for p in payloads],
|
||||
"class_shares": shares,
|
||||
"attribution": "Kartenkacheln: GTA VI Map (CC BY 4.0) — https://map.gtadb.org, YANIS v15. Wahrzeichen: rolux/gtamaplib-vc (Unlicense).",
|
||||
"attribution": "Kartenkacheln: GTA VI Map (CC BY 4.0) — https://map.gtadb.org, YANIS v15. "
|
||||
"Wahrzeichen: rolux/gtamaplib-vc (Unlicense). "
|
||||
"Flamingo Park: © OpenStreetMap-Mitwirkende (ODbL).",
|
||||
})
|
||||
print(render_map(OUT, OUT), "->", OUT)
|
||||
|
||||
|
||||
@@ -20,9 +20,18 @@ def test_coordinates_are_chunk_local_three_js():
|
||||
|
||||
def test_payload_matches_schema():
|
||||
payload = chunk_payload(2, 3, land=[], surfaces=[], roads=[], centerlines=[], buildings=[], palms=[])
|
||||
assert set(payload) == {"chunk", "origin", "land", "surfaces", "roads", "centerlines", "buildings", "palms"}
|
||||
assert set(payload) == {"chunk", "origin", "land", "surfaces", "roads", "centerlines", "buildings", "palms", "paths"}
|
||||
assert payload["chunk"] == [2, 3]
|
||||
|
||||
def test_paths_are_chunk_local_rings_like_roads():
|
||||
path = Polygon([(-990, 2490), (-980, 2490), (-980, 2480), (-990, 2480)])
|
||||
payload = chunk_payload(0, 0, land=[], surfaces=[], roads=[], centerlines=[], buildings=[], palms=[], paths=[path])
|
||||
(entry,) = payload["paths"]
|
||||
assert set(entry) == {"poly"}
|
||||
ring = entry["poly"][0]
|
||||
assert (min(x for x, _ in ring), max(x for x, _ in ring)) == (10, 20)
|
||||
assert (min(z for _, z in ring), max(z for _, z in ring)) == (10, 20)
|
||||
|
||||
def test_polygon_holes_survive_into_the_payload():
|
||||
"""Ein von Strassen umschlossener Block darf nicht zu Asphalt werden. An echten Daten
|
||||
gehen sonst 449.876 m2 Strassenloch und 208.657 m2 Stadtloch verloren."""
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from shapely.geometry import LineString, Point, box
|
||||
from shapely.geometry import LineString, Point, Polygon, box
|
||||
|
||||
from leonida.heights import PALM_CLEARANCE
|
||||
from leonida.park import ParkError, ParkLayout, World, apply_park, check_corners
|
||||
from leonida.park import ParkError, ParkLayout, World, apply_flamingo_park, apply_park, check_corners, load_park
|
||||
|
||||
DATA = Path(__file__).resolve().parents[1] / "data"
|
||||
|
||||
QUAD = box(0, 0, 100, 200)
|
||||
SERVICE = box(60, 0, 66, 30)
|
||||
@@ -75,3 +78,17 @@ def test_corners_must_lie_at_a_road():
|
||||
far = SimpleNamespace(game={"sw": (0.0, 0.0), "se": (100.0, 0.0), "ne": (100.0, 200.0), "nw": (30.0, 200.0)})
|
||||
with pytest.raises(ParkError, match="Spielecke nw"):
|
||||
check_corners(far, roads)
|
||||
|
||||
|
||||
def test_flamingo_park_goes_into_a_world_with_roads_around_the_park():
|
||||
config = load_park(DATA / "flamingo-park.toml")
|
||||
park = Polygon([config.game[key] for key in ("sw", "se", "ne", "nw")])
|
||||
ring = park.buffer(15, join_style="mitre").difference(park)
|
||||
start = World(land=[park.buffer(400)], surfaces=[], roads=[(ring, False)], centerlines=[], buildings=[], palms=[])
|
||||
result, messages = apply_flamingo_park(start, DATA / "flamingo-park.toml", seed=20260912)
|
||||
assert {"park", "field", "court", "pool", "parking", "sand"} <= {kind for _, kind in result.surfaces}
|
||||
assert len([b for b in result.buildings if b[1]["source"] == "osm"]) == 11
|
||||
assert result.paths and result.palms
|
||||
assert LineString([config.street.start, config.street.end]) in result.centerlines
|
||||
assert not any(message.startswith("Warnung") for message in messages)
|
||||
assert any(message.startswith("Flamingo Park:") for message in messages)
|
||||
Reference in new issue
Block a user