Files
gta6/tools/extract/tests/test_park_layout.py
T
knsoandClaude Sonnet 5 d756eccd77 fix(extract): Sperrflächen auch aus Zufahrtsflächen ausschneiden und davor warnen
Zufahrtsflächen (highway=service) wurden bisher ungeschnitten ausgegeben und nicht auf
Überschneidung mit Sperrflächen geprüft. Am echten Stand überlappte eine Zufahrt die
Sperrfläche "tribuene" um 42,17 m² ohne Schnitt und ohne Warnung. build_layout wendet
difference(blocked) jetzt auch auf Zufahrten an, die Warnschleife prüft Wege und
Zufahrten getrennt ("Parkweg schneidet ..." / "Zufahrt schneidet ...").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 07:20:41 +02:00

149 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from types import SimpleNamespace
import pytest
from shapely.geometry import Point
from leonida.park import (M_PER_DEG_LAT, M_PER_DEG_LON, REF_LAT, REF_LON, Keepout, ParkFit, build_layout,
building_height, keepout_polygon)
def latlon(x, y):
return REF_LAT + y / M_PER_DEG_LAT, REF_LON + x / M_PER_DEG_LON
def config(keepouts=()):
"""Echter Park 100 × 200 m, Spielpark 200 × 400 m ab (1000 | 1000), Anlage 50 m vom Westrand:
Welt-x = 1050 + x, Welt-y = 1000 + 2 · y."""
return SimpleNamespace(
real={"sw": latlon(0, 0), "se": latlon(100, 0), "nw": latlon(0, 200)},
game={"sw": (1000.0, 1000.0), "se": (1200.0, 1000.0), "ne": (1200.0, 1400.0), "nw": (1000.0, 1400.0)},
scale_u=1.0, offset_u=50.0, keepouts=tuple(keepouts),
main_width=6.0, path_width=3.0, service_width=6.0, default_height=4.0,
)
def way(tags, *points, id=1):
return {"id": id, "tags": tags, "geometry": [list(latlon(x, y)) for x, y in points]}
def square(tags, x, y, size=10, id=1):
return way(tags, (x, y), (x + size, y), (x + size, y + size), (x, y + size), (x, y), id=id)
def layout(elements, keepouts=()):
cfg = config(keepouts)
return build_layout(elements, cfg, ParkFit(cfg))
def test_footway_is_three_metres_wide_and_a_named_main_path_six():
(plain,) = layout([way({"highway": "footway"}, (20, 100), (80, 100))]).paths
assert plain.contains(Point(1100, 1201.4)) and not plain.contains(Point(1100, 1201.6))
(main,) = layout([way({"highway": "footway", "name": "Flamingo Park Path (13th St)"}, (20, 100), (80, 100))]).paths
assert main.contains(Point(1100, 1202.9)) and not main.contains(Point(1100, 1203.1))
def test_crossing_paths_become_one_surface():
result = layout([way({"highway": "footway"}, (20, 100), (80, 100), id=1),
way({"highway": "path"}, (50, 60), (50, 140), id=2)])
assert len(result.paths) == 1
def test_path_is_cut_at_the_real_park_edge():
(path,) = layout([way({"highway": "footway"}, (50, 150), (50, 260))]).paths
assert path.bounds[3] <= 1400 + 1.5 + 1e-6
def test_path_ending_near_the_west_edge_runs_on_to_the_road():
(path,) = layout([way({"highway": "footway"}, (40, 100), (3, 100))]).paths
assert path.contains(Point(1001, 1200))
def test_path_along_the_edge_is_not_stretched():
(path,) = layout([way({"highway": "footway"}, (3, 50), (3, 150))]).paths
assert not path.contains(Point(1010, 1200))
assert path.bounds[1] == pytest.approx(1100 - 1.5)
def test_path_ending_far_from_the_edge_stays_as_it_is():
(path,) = layout([way({"highway": "footway"}, (40, 100), (20, 100))]).paths
assert path.bounds[0] == pytest.approx(1070 - 1.5)
def test_surfaces_are_sorted_by_kind_and_dog_parks_stay_lawn():
elements = [
square({"leisure": "playground"}, 10, 10, id=1),
square({"leisure": "pitch", "sport": "tennis"}, 30, 10, id=2),
square({"leisure": "pitch", "sport": "baseball"}, 50, 10, id=3),
square({"amenity": "parking"}, 70, 10, id=4),
square({"leisure": "swimming_pool"}, 10, 40, id=5),
square({"leisure": "sports_centre"}, 30, 40, id=6),
square({"leisure": "pitch", "sport": "soccer;american_football"}, 50, 40, id=7),
square({"leisure": "dog_park"}, 70, 40, id=8),
]
assert [kind for _, kind in layout(elements).surfaces] == ["field", "field", "court", "court", "pool", "parking", "sand"]
def test_features_centred_outside_the_real_park_are_left_out():
assert layout([square({"leisure": "pitch", "sport": "soccer"}, 40, 195, size=20)]).surfaces == []
def test_lawn_is_the_whole_game_park():
assert layout([]).lawn.area == pytest.approx(200 * 400)
def test_buildings_take_their_height_or_the_default_and_roofs_without_walls_are_dropped():
result = layout([
square({"building": "yes", "height": "8.5"}, 10, 10, id=1),
square({"building": "yes"}, 30, 10, id=2),
square({"building": "yes", "height": "0.0"}, 50, 10, id=3),
square({"building": "yes", "leisure": "sports_centre"}, 70, 10, id=4),
])
assert sorted(height for _, height in result.buildings) == [4.0, 4.0, 8.5]
assert result.surfaces == []
def test_building_height_reads_plain_numbers_and_units():
assert building_height({"height": "10.4"}, 4.0) == 10.4
assert building_height({"height": "12 m"}, 4.0) == 12.0
assert building_height({"height": "hoch"}, 4.0) == 4.0
assert building_height({}, 4.0) == 4.0
assert building_height({"height": "0"}, 4.0) is None
def test_keepout_polygon_turns_like_places_js():
plain = keepout_polygon(Keepout("ort", (100.0, 200.0), 0.0, (-10.0, 20.0, -5.0, 8.0)))
assert plain.bounds == pytest.approx((90.0, 192.0, 120.0, 205.0))
turned = keepout_polygon(Keepout("ort", (0.0, 0.0), 90.0, (0.0, 10.0, -1.0, 1.0)))
assert turned.bounds == pytest.approx((-1.0, 0.0, 1.0, 10.0)) # +x zeigt nach 90° nach Norden
def test_keepouts_cut_fields_and_paths_keep_buildings_out_and_warn():
keepout = Keepout("ort", (1100.0, 1100.0), 0.0, (-10.0, 10.0, -10.0, 10.0)) # Welt x 1090–1110, y 1090–1110
result = layout([
square({"leisure": "pitch", "sport": "baseball"}, 40, 40, size=20, id=1), # Welt x 1090–1110, y 1080–1120
way({"highway": "footway"}, (50, 30), (50, 70), id=2), # Welt x 1100, y 1060–1140
square({"building": "yes"}, 55, 48, size=4, id=3), # Welt x 1105–1109, y 1096–1104
], keepouts=[keepout])
hole = keepout_polygon(keepout)
assert all(poly.intersection(hole).area < 1e-6 for poly, _ in result.surfaces)
assert sum(poly.area for poly, _ in result.surfaces) == pytest.approx(20 * 40 - 20 * 20)
assert all(poly.intersection(hole).area < 1e-6 for poly in result.paths)
assert result.buildings == []
assert result.warnings == ["Parkweg schneidet die Sperrfläche ort"]
def test_service_ways_become_six_metre_service_surfaces():
result = layout([way({"highway": "service"}, (20, 20), (80, 20))])
(service,) = result.service
assert service.contains(Point(1100, 1042.9)) and not service.contains(Point(1100, 1043.1))
assert result.paths == []
def test_keepouts_cut_service_surfaces_and_warn():
keepout = Keepout("ort", (1100.0, 1100.0), 0.0, (-10.0, 10.0, -10.0, 10.0)) # Welt x 1090–1110, y 1090–1110
result = layout([way({"highway": "service"}, (30, 50), (70, 50))], # Welt x 1080–1120, y 1100
keepouts=[keepout])
hole = keepout_polygon(keepout)
assert result.service and all(poly.intersection(hole).area < 1e-6 for poly in result.service)
assert result.warnings == ["Zufahrt schneidet die Sperrfläche ort"]