feat(extract): Pipeline baut Straßen aus dem Graphen
run.py baut jetzt aus dem Straßengraphen (Tasks 3-11) statt aus den nachgezeichneten Straßenrändern: Fahrbahn, Bordstein, Gehweg und Restflächen aus build_street(), amerikanische Markierungen aus markings(). fit_to_carriageway() bringt Bodenplatte und Beläge auf dasselbe Loch wie die Fahrbahn und hält Palmen dabei von ihr fern - nach derselben Regel, mit der apply_street() sie schon von der Jefferson Avenue fernhält. split_by_chunk_all() bekommt dafür eine parametrisierbare Splittergrenze: Ein Strich der gestrichelten Linien ist 3 m x 10 cm = 0,3 m² und wäre an der bisherigen 0,5-m²-Grenze für Flächen verschwunden. Markierungen laufen deshalb über den neuen Helfer chunk_markings() mit eigener, kleinerer Grenze; alle anderen Aufrufer bleiben bei 0,5 m². Am echten Netz gemessen: 1.537 Knoten, 1.790 Kanten, 830 Bordsteinstücke, 171 Restflächen, 22.394 Markierungen, 56 Palmen an der Fahrbahn entfernt. Fahrbahn-, Gehweg- und Restflächen sowie die Bordsteinlänge treffen die vorab am echten Netz gemessenen Erwartungswerte auf 0,03-0,3 % genau. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
892558fc50
commit
2201be37bd
3 files changed
+181
-15
No files matched your search
+100
-11
@@ -1,22 +1,83 @@
|
||||
"""Erzeugt aus den Kacheln die Chunk-Dateien. Aufruf: uv run python run.py"""
|
||||
import json
|
||||
from collections import Counter
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from scipy import ndimage as ndi
|
||||
from shapely.geometry import LineString, Point
|
||||
from shapely.ops import unary_union
|
||||
|
||||
from leonida.annotations import load_annotations, mark_generated, remove_annotations
|
||||
from leonida.buildings import building_polygons
|
||||
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.heights import PALM_KERB, assign_heights, palm_points
|
||||
from leonida.marking import markings
|
||||
from leonida.park import World, apply_flamingo_park
|
||||
from leonida.roads import road_graph
|
||||
from leonida.section import SECTION
|
||||
from leonida.street import build_street, punch
|
||||
from leonida.tiles import COMMIT, download_tiles, load_mosaic
|
||||
from leonida.vectors import centerlines, land_polygons, mask_polygons, road_polygons
|
||||
from leonida.vectors import land_polygons, mask_polygons, road_mask, 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)}
|
||||
MARKING_MIN_AREA = 0.001 # m² — ein Strich ist 3 m × 10 cm = 0,3 m², die Flächengrenze 0,5 m² verwürfe ihn
|
||||
|
||||
|
||||
def _pieces(geometry) -> list:
|
||||
return [g for g in getattr(geometry, "geoms", [geometry]) if not g.is_empty]
|
||||
|
||||
|
||||
def _clip_lines(line, cell) -> list:
|
||||
"""Der Anteil einer Linie in diesem Chunk, in Stücken."""
|
||||
part = line.intersection(cell)
|
||||
return [g for g in _pieces(part) if g.geom_type == "LineString" and g.length > 0.5]
|
||||
|
||||
|
||||
def fit_to_carriageway(world: World, carriage) -> World:
|
||||
"""Die Welt an die gebaute Fahrbahn anpassen (Entwurf, Ablauf Schritte 3 und 7).
|
||||
|
||||
Bodenplatte und Beläge bekommen dasselbe Loch — fehlt es in einer Schicht, liegt dort ein Belag
|
||||
über der Mulde oder die Platte schließt sie. Palmen näher als PALM_KERB an der Fahrbahn fallen
|
||||
weg, nach derselben Regel, mit der apply_street() sie von der Jefferson Avenue fernhält:
|
||||
palm_points() hält nur Abstand zum gemessenen Korridor, und die Fahrbahn ragt an Kurven und
|
||||
Kreuzungen darüber hinaus.
|
||||
"""
|
||||
return replace(world,
|
||||
land=punch(world.land, carriage),
|
||||
surfaces=[(part, kind) for poly, kind in world.surfaces
|
||||
for part in punch([poly], carriage)],
|
||||
palms=[p for p in world.palms if carriage.distance(Point(p)) >= PALM_KERB])
|
||||
|
||||
|
||||
def chunk_markings(marks, i: int, j: int) -> list:
|
||||
"""Die Markierungen in Chunk (i, j) — an der Chunkgrenze geteilt, nicht verworfen."""
|
||||
return [(part, art) for poly, art in marks
|
||||
for part in split_by_chunk_all([poly], i, j, min_area=MARKING_MIN_AREA)]
|
||||
|
||||
|
||||
def _classified(street, graph) -> list:
|
||||
"""Jede Fahrbahnfläche mit der Klasse der breitesten Kante, deren Mittelpunkt in ihr liegt.
|
||||
|
||||
Vorsicht beim Lesen des Felds: carriageway_area() verschmilzt das Netz zu wenigen
|
||||
zusammenhängenden Teilen, und ein Teil trägt dann die breiteste Klasse aller Straßen darin. Am
|
||||
echten Netz sind das 5 von 213 Teilen, aber 91 % der Fahrbahnfläche tragen "boulevard". Über
|
||||
die einzelne Straße sagt das Feld also wenig — die Klasse je Straße steht in edges. Heute liest
|
||||
es kein Verbraucher; Browser und Minimap färben die Fahrbahn einheitlich.
|
||||
"""
|
||||
mitten = [(e.klass, LineString(e.points).interpolate(0.5, normalized=True)) for e in graph.edges]
|
||||
out = []
|
||||
for poly in _pieces(street.carriageway):
|
||||
treffer = [klass for klass, mitte in mitten if poly.contains(mitte)]
|
||||
klass = max(treffer, key=lambda k: SECTION[k].carriageway) if treffer else "wohnstrasse"
|
||||
out.append((poly, klass))
|
||||
return out
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -38,7 +99,12 @@ def main() -> None:
|
||||
|
||||
land = land_polygons(labels)
|
||||
roads = road_polygons(labels)
|
||||
lines = centerlines(labels)
|
||||
# Der Graph ersetzt die nachgezeichneten Straßenränder (Entwurf Teil 2). Die
|
||||
# Distanztransformation misst an jedem Straßenpixel den Abstand zum nächsten Nicht-Straßen-
|
||||
# pixel; doppelt genommen ist das die Korridorbreite an dieser Stelle.
|
||||
distance = ndi.distance_transform_edt(road_mask(labels))
|
||||
graph = road_graph(labels, distance)
|
||||
print(f"Straßennetz: {len(graph.nodes)} Knoten, {len(graph.edges)} Kanten")
|
||||
sand = mask_polygons(labels == CLASSES["sand"], 50, 1.0)
|
||||
parks = mask_polygons(labels == CLASSES["gruen"], 50, 1.0)
|
||||
urban = mask_polygons(labels == CLASSES["stadt"], 80, 1.5)
|
||||
@@ -72,25 +138,47 @@ def main() -> None:
|
||||
heights = assign_heights([p for p, _ in buildings], landmarks, districts, SEED)
|
||||
marked = mark_generated([p for p, _ in buildings], heights, removal.generated)
|
||||
print(f"Gebäude: {len(buildings)}, davon {marked} unter entfernten Beschriftungen ergänzt")
|
||||
palms = palm_points(sand, parks, lines, [p for p, _ in buildings], road_shapes,
|
||||
land, SEED)
|
||||
edge_lines = [LineString(e.points) for e in graph.edges]
|
||||
palms = palm_points(sand, parks, edge_lines, [p for p, _ in buildings], road_shapes, land, SEED)
|
||||
|
||||
world = World(land=land, surfaces=surfaces, roads=roads, centerlines=lines,
|
||||
world = World(land=land, surfaces=surfaces, roads=roads, graph=graph,
|
||||
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)
|
||||
|
||||
corridor = unary_union([p for p, bridge in world.roads if not bridge])
|
||||
bridge_polys = [p for p, bridge in world.roads if bridge]
|
||||
street = build_street(world.graph, corridor, bridge_polys)
|
||||
marks = markings(world.graph)
|
||||
palmen = len(world.palms)
|
||||
world = fit_to_carriageway(world, street.carriageway)
|
||||
arten = dict(sorted(Counter(art for _, art in marks).items()))
|
||||
print(f"Straßen: {len(street.kerb)} Bordsteinstücke, {len(street.lots)} Restflächen, "
|
||||
f"{len(marks)} Markierungen {arten}, {palmen - len(world.palms)} Palmen an der Fahrbahn entfernt")
|
||||
classified = _classified(street, world.graph) # einmal, nicht je Chunk: 5,8 s am echten Netz
|
||||
|
||||
payloads = []
|
||||
for j in range(12):
|
||||
for i in range(12):
|
||||
payloads.append(chunk_payload(
|
||||
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))],
|
||||
surfaces=[(part, kind) for (p, kind) in world.surfaces
|
||||
for part in split_by_chunk_all([p], i, j)],
|
||||
carriageway=[(part, klass) for poly, klass in classified
|
||||
for part in split_by_chunk_all([poly], i, j)],
|
||||
curb=[part for line in street.kerb
|
||||
for part in _clip_lines(line, chunk_box_of(i, j))],
|
||||
sidewalk=list(split_by_chunk_all(_pieces(street.sidewalk), i, j)),
|
||||
markings=chunk_markings(marks, i, j),
|
||||
lots=list(split_by_chunk_all(street.lots, i, j)),
|
||||
bridges=list(split_by_chunk_all(bridge_polys, i, j)),
|
||||
edges=[(part, e.klass, e.width, SECTION[e.klass].carriageway)
|
||||
for e in world.graph.edges
|
||||
for part in _clip_lines(LineString(e.points), 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)],
|
||||
))
|
||||
@@ -101,7 +189,8 @@ def main() -> None:
|
||||
"class_shares": shares,
|
||||
"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).",
|
||||
"Flamingo Park: © OpenStreetMap-Mitwirkende (ODbL). "
|
||||
"Bordstein und Gehweg: Just8 (Sketchfab, CC BY 4.0).",
|
||||
})
|
||||
print(render_map(OUT, OUT), "->", OUT)
|
||||
|
||||
|
||||
Reference in new issue
Block a user