fix(extract): Chunk-Ausgabe lauf-stabil, Eckenprüfung in beide Richtungen
emit.py normalisiert jede ausgegebene Fläche und sortiert die Listen nach der Ausgabestruktur selbst. unary_union() in apply_street() gab das kartenweite Straßenpolygon mit anderem Ringstartpunkt und an anderer Listenstelle zurück und änderte damit 88 Chunkdateien, in denen sich geometrisch nichts bewegt hatte (symmetrische Differenz 0,00 m²). Belagsarten und Mittellinien behalten ihre Reihenfolge: overview.py malt die Beläge in Listenreihenfolge übereinander, und vergeLampPoints() hängt an Reihenfolge und Laufrichtung der Mittellinien — Sortieren verschöbe dort 1.848 von 3.921 Laternen auf die andere Straßenseite. check_corners() misst gegen den Straßenrand statt gegen die Straßenfläche. Alle vier Ecken liegen in der Fahrbahn, ihr Flächenabstand ist 0,000 m; die alte Prüfung schlug deshalb nur an, wenn eine Straße sich zurückzieht. Eine nach innen verbreiterte Straße hätte apply_park() still auf das veraltete Viereck zurückgeschnitten. Die Parkwege werden am Parkviereck abgeschnitten — die runden Kappen von LineString.buffer() lagen mit 95,96 m² darüber hinaus, rund 41 m² davon sichtbar als Betonzungen auf den umgebenden Belägen. load_elements() prüft den OSM-Abzug so streng wie die Parkdatei, mit Dateiname statt nacktem KeyError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a3504df43d
commit
3c74202b5e
10 files changed
+333
-102
No files matched your search
@@ -279,7 +279,31 @@ class ParkLayout:
|
||||
|
||||
|
||||
def load_elements(path: Path) -> list[dict]:
|
||||
return json.loads(path.read_text(encoding="utf-8"))["elements"]
|
||||
"""Die Elemente des OSM-Abzugs, so streng geprüft wie die Parkdatei.
|
||||
|
||||
osm.py wird von Hand aufgerufen, der Abzug liegt also beliebig lange herum und kann
|
||||
abgeschnitten oder aus einer älteren Fassung des Skripts sein. Ohne diese Prüfung fällt das
|
||||
erst als nackter KeyError mitten in build_layout() auf — ohne Dateinamen und ohne den Hinweis,
|
||||
wie man den Abzug erneuert.
|
||||
"""
|
||||
def fail(message: str):
|
||||
raise ParkError(f"{path.name}: {message}")
|
||||
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as err:
|
||||
fail(f"kein gültiges JSON: {err}")
|
||||
elements = data.get("elements") if isinstance(data, dict) else None
|
||||
if not isinstance(elements, list):
|
||||
fail("Feld 'elements' fehlt oder ist keine Liste: cd tools/extract && uv run python osm.py")
|
||||
for n, element in enumerate(elements, start=1):
|
||||
where = f"elements[{n}]" if not isinstance(element, dict) else f"elements[{n}] (id {element.get('id')})"
|
||||
if not isinstance(element, dict) or not isinstance(element.get("tags"), dict):
|
||||
fail(f"{where} braucht eine Tabelle 'tags'")
|
||||
geometry = element.get("geometry")
|
||||
if not (isinstance(geometry, list) and all(_is_point(point) for point in geometry)):
|
||||
fail(f"{where} braucht 'geometry' als Liste von Punkten [Breite, Länge]")
|
||||
return elements
|
||||
|
||||
|
||||
def keepout_polygon(keepout: Keepout) -> Polygon:
|
||||
@@ -344,7 +368,12 @@ def build_layout(elements: list[dict], config, fit: ParkFit) -> ParkLayout:
|
||||
if footways:
|
||||
union = unary_union(footways)
|
||||
warnings += _keepout_warnings(union, config.keepouts, keepouts, "Parkweg")
|
||||
paths = _polygons(union.difference(blocked))
|
||||
# Am Parkviereck abschneiden: _extend() legt den letzten Stützpunkt genau auf die Kante,
|
||||
# die runde Kappe von LineString.buffer() setzt danach bis zu eine halbe Wegbreite (3 m)
|
||||
# darüber hinaus. Gemessen lagen so 95,96 m² Weg außerhalb des Vierecks, davon rund 41 m²
|
||||
# als Betonzungen sichtbar auf den umgebenden Belägen. Die gewollte Reichweite bis zur
|
||||
# Straßenkante geht dabei nicht verloren, weil _extend() ohnehin auf diese Kante zielt.
|
||||
paths = _polygons(union.difference(blocked).intersection(fit.game_quad))
|
||||
if service:
|
||||
service_union = unary_union(service)
|
||||
warnings += _keepout_warnings(service_union, config.keepouts, keepouts, "Zufahrt")
|
||||
@@ -487,13 +516,26 @@ def apply_street(world: World, street: Street) -> World:
|
||||
surfaces=surfaces, palms=palms)
|
||||
|
||||
|
||||
def check_corners(config, roads: list[tuple[Polygon, bool]], tolerance: float = 2.0) -> None:
|
||||
"""Jede Spielecke muss an einer Straße liegen, sonst hat sich die Extraktion geändert."""
|
||||
def check_corners(config, roads: list[tuple[Polygon, bool]], tolerance: float = 2.5) -> None:
|
||||
"""Jede Spielecke muss am Rand einer Straße liegen, sonst hat sich die Extraktion geändert.
|
||||
|
||||
Gemessen wird gegen den Rand, nicht gegen die Fläche: alle vier Ecken liegen heute innerhalb
|
||||
der Straßenfläche, ihr Flächenabstand ist 0,000 m. Eine Prüfung auf die Fläche schlägt deshalb
|
||||
nur an, wenn eine Straße sich zurückzieht. Verbreitert ein künftiger Extraktionslauf die
|
||||
Straßen nach innen, bliebe der Flächenabstand 0, und apply_park() schnitte die verbreiterte
|
||||
Straße still auf das veraltete Viereck zurück — genau der Fehlerfall, den diese Prüfung
|
||||
abfangen soll.
|
||||
|
||||
Am Referenzstand f32ea27 liegen die Ecken 0,45 m (sw), 0,14 m (se), 1,46 m (ne) und 1,14 m (nw)
|
||||
vom Straßenrand entfernt. 2,5 m Toleranz lässt der weitesten Ecke damit 1,04 m Luft, bevor die
|
||||
Prüfung grundlos abbricht, und schlägt umgekehrt an, sobald eine Straßenkante sich um mehr als
|
||||
ihren heutigen Abstand plus rund einen Meter verschiebt.
|
||||
"""
|
||||
for name, (x, y) in config.game.items():
|
||||
corner = Point(x, y)
|
||||
if not any(poly.distance(corner) <= tolerance for poly, bridge in roads if not bridge):
|
||||
raise ParkError(f"Spielecke {name} ({x:g} | {y:g}) liegt weiter als {tolerance:g} m von jeder Straße: "
|
||||
"Ecken in flamingo-park.toml neu messen")
|
||||
if not any(poly.boundary.distance(corner) <= tolerance for poly, bridge in roads if not bridge):
|
||||
raise ParkError(f"Spielecke {name} ({x:g} | {y:g}) liegt weiter als {tolerance:g} m vom Rand jeder "
|
||||
"Straße: Ecken in flamingo-park.toml neu messen")
|
||||
|
||||
|
||||
def apply_park(world: World, layout: ParkLayout, seed: int) -> World:
|
||||
|
||||
Reference in new issue
Block a user