docs: Palmenpunkte in Task 5 korrigieren (Rulings 20-23)
Die erste Palme startet bei halber Teilung, weil die Mittellinien seit Task 4 an Kreuzungen getrennt sind und zwei gegenüberliegende Äste sonst zwei Palmen in denselben Punkt setzen. Der feste Versatz von 4 m weicht einer Suche vom Fahrbahnrand: am echten Mosaik stünden damit 60 % der Palmen auf der Fahrbahn. Dazu zwei Tests, die es bisher nicht gab, und die korrigierte Signatur im Interfaces-Block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
830e9f87e0
commit
11f6aa76fe
1 file changed
+74
-14
@@ -893,7 +893,7 @@ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>"
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `mask_polygons`, `centerlines` aus Task 4, `CLASSES` aus Task 3
|
||||
- Produces: `regularize(poly) -> Polygon | None`, `building_polygons(labels) -> list[tuple[Polygon, bool]]` (Polygon, `spekulativ`), `assign_heights(polys, landmarks, districts, seed) -> list[float]`, `height_sources(...) -> list[str]`, `palm_points(labels, lines, seed) -> list[tuple[float, float]]`
|
||||
- Produces: `regularize(poly) -> Polygon | None`, `building_polygons(labels) -> list[tuple[Polygon, bool]]` (Polygon, `spekulativ`), `assign_heights(polys, landmarks, districts, seed) -> list[float]`, `height_sources(...) -> list[str]`, `palm_points(sand_polys, park_polys, road_lines, building_polys, road_polys, seed) -> list[tuple[float, float]]`
|
||||
|
||||
- [ ] **Step 1: Den fehlschlagenden Test für Gebäude schreiben**
|
||||
|
||||
@@ -1068,34 +1068,89 @@ def assign_heights(polys: list[Polygon], landmarks: list[dict],
|
||||
Run: `cd tools/extract && uv run pytest -v`
|
||||
Expected: PASS, alle Tests
|
||||
|
||||
- [ ] **Step 9: Palmenpunkte ergänzen**
|
||||
- [ ] **Step 9: Den fehlschlagenden Test für Palmenpunkte schreiben**
|
||||
|
||||
An `tools/extract/tests/test_heights.py` anfügen:
|
||||
|
||||
```python
|
||||
from shapely.geometry import LineString, Point
|
||||
from leonida.heights import PALM_KERB, palm_points
|
||||
|
||||
def test_no_palm_stands_on_the_carriageway():
|
||||
"""Der Versatz muss vom Fahrbahnrand her gedacht werden, nicht von einem festen Wert.
|
||||
|
||||
Am echten Mosaik hat die Fahrbahn an den Mittellinienpunkten p50 5,4 m, p90 11,7 m
|
||||
und p95 14,0 m Halbbreite. Ein fester Versatz von 4 m setzt 60 % der Palmen auf die
|
||||
Fahrbahn, einer von 11 m immer noch 12,5 % – und auf der Hälfte der Straßen stünde
|
||||
die Palme dann im Nachbargebäude.
|
||||
"""
|
||||
road = Polygon([(0, -9), (200, -9), (200, 9), (0, 9)]) # 18 m breite Fahrbahn
|
||||
line = LineString([(0, 0), (200, 0)])
|
||||
sand = Polygon([(0, 20), (200, 20), (200, 40), (0, 40)])
|
||||
pts = palm_points([sand], [], [line], [], [road], seed=1)
|
||||
assert pts
|
||||
assert all(road.distance(Point(x, y)) >= PALM_KERB for x, y in pts)
|
||||
|
||||
def test_palms_do_not_pile_up_where_centerlines_meet():
|
||||
"""Mittellinien sind an Kreuzungen getrennt (Task 4). Ein Start bei Position 0 setzt
|
||||
auf zwei gegenüberliegenden Ästen zwei Palmen in genau denselben Punkt."""
|
||||
sand = Polygon([(-80, 20), (80, 20), (80, 40), (-80, 40)])
|
||||
arms = [LineString([(0, 0), (60, 0)]), LineString([(0, 0), (-60, 0)])]
|
||||
pts = palm_points([sand], [], arms, [], [], seed=1)
|
||||
assert pts
|
||||
assert len(set(pts)) == len(pts)
|
||||
```
|
||||
|
||||
- [ ] **Step 10: Palmenpunkte ergänzen**
|
||||
|
||||
An `leonida/heights.py` anfügen:
|
||||
|
||||
```python
|
||||
PALM_SPACING = 12.0 # Meter entlang der Mittellinie
|
||||
PALM_BEACH_RANGE = 40.0 # Meter Abstand zur nächsten Sandfläche
|
||||
PALM_CLEARANCE = 6.0 # Meter Abstand zu Gebäuden und Straßen im Park
|
||||
PALM_CLEARANCE = 6.0 # Meter Abstand zu Gebäuden
|
||||
PALM_KERB = 1.5 # Meter Abstand zum Fahrbahnrand
|
||||
# Versatzkandidaten von innen nach außen; genommen wird der erste, der Fahrbahn und
|
||||
# Gebäude freihält. Ein fester Wert geht nicht: die Fahrbahn ist an den Mittellinien-
|
||||
# punkten p50 5,4 m, p90 11,7 m, p95 14,0 m halbbreit (am echten Mosaik gemessen).
|
||||
PALM_VERGE = (6.0, 8.0, 10.0, 12.0, 14.0, 16.0)
|
||||
|
||||
|
||||
def _verge_point(line, p, side: str, roads, buildings):
|
||||
"""Erster Versatz von innen nach außen, der Fahrbahn und Gebäude freihält."""
|
||||
for dist in PALM_VERGE:
|
||||
off = line.parallel_offset(dist, side)
|
||||
if off.is_empty:
|
||||
continue
|
||||
q = off.interpolate(off.project(p))
|
||||
if roads is not None and q.distance(roads) < PALM_KERB:
|
||||
continue
|
||||
if buildings is not None and q.distance(buildings) < PALM_CLEARANCE:
|
||||
continue
|
||||
return [(round(q.x, 2), round(q.y, 2))]
|
||||
return []
|
||||
|
||||
|
||||
def palm_points(sand_polys, park_polys, road_lines, building_polys, road_polys, seed: int):
|
||||
"""Palmen entlang strandnaher Mittellinien und verstreut in Parks."""
|
||||
from shapely.ops import unary_union
|
||||
sand = unary_union(sand_polys) if sand_polys else None
|
||||
blocked = unary_union(list(building_polys) + list(road_polys)) if (building_polys or road_polys) else None
|
||||
roads = unary_union(list(road_polys)) if road_polys else None
|
||||
buildings = unary_union(list(building_polys)) if building_polys else None
|
||||
blocked = unary_union([g for g in (roads, buildings) if g is not None]) \
|
||||
if (roads is not None or buildings is not None) else None
|
||||
points = []
|
||||
for line in road_lines:
|
||||
if sand is None or line.distance(sand) > PALM_BEACH_RANGE:
|
||||
continue
|
||||
steps = max(1, int(line.length // PALM_SPACING))
|
||||
for k in range(steps + 1):
|
||||
p = line.interpolate(k * PALM_SPACING)
|
||||
for side in (-1, 1):
|
||||
off = line.parallel_offset(4.0 * side, "left" if side > 0 else "right")
|
||||
if off.is_empty:
|
||||
continue
|
||||
q = off.interpolate(off.project(p))
|
||||
points.append((round(q.x, 2), round(q.y, 2)))
|
||||
# Start bei halber Teilung: die Mittellinien sind an Kreuzungen getrennt, ein
|
||||
# Start bei 0 setzt je Ast eine Palme in denselben Punkt.
|
||||
pos = min(PALM_SPACING / 2, line.length / 2)
|
||||
while pos <= line.length:
|
||||
p = line.interpolate(pos)
|
||||
for side in ("left", "right"):
|
||||
points.extend(_verge_point(line, p, side, roads, buildings))
|
||||
pos += PALM_SPACING
|
||||
for park in park_polys:
|
||||
minx, miny, maxx, maxy = park.bounds
|
||||
n = int(park.area // 250)
|
||||
@@ -1108,7 +1163,12 @@ def palm_points(sand_polys, park_polys, road_lines, building_polys, road_polys,
|
||||
return points
|
||||
```
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
- [ ] **Step 11: Test laufen lassen, er muss bestehen**
|
||||
|
||||
Run: `cd tools/extract && uv run pytest -v`
|
||||
Expected: PASS, alle Tests
|
||||
|
||||
- [ ] **Step 12: Commit**
|
||||
|
||||
```bash
|
||||
git add tools/extract/leonida/buildings.py tools/extract/leonida/heights.py tools/extract/tests
|
||||
|
||||
Reference in new issue
Block a user