fix(extract): Zebrabalken mittig verteilt, Kantenbreite gerundet

Befund 8, zwei kleine, aber datenwirksame Funde:

- Zebrastreifen: Der Startversatz (-halb + BAR/2, Takt 2*BAR) liess die
  Balkenreihe an einem Fahrbahnrand bündig sitzen und am gegenüber-
  liegenden bis zu eine Taktbreite frei (0,6 m bei Hauptstraße/Boulevard,
  0,4 m bei Wohnstraße), statt der laut Entwurf verlangten Aufteilung
  "Balken 60 cm, Lücke 60 cm" über die ganze Breite. n = floor((2*halb +
  BAR) / (2*BAR)) Balken, symmetrisch bei -(n-1)*BAR gestartet, lassen an
  beiden Rändern denselben Rest frei (nachgemessen: 0,3 m statt 0/0,6 m
  bei Hauptstraße, 0,2 m statt 0/0,4 m bei Wohnstraße), ohne die Balkenzahl
  je Arm zu ändern.
- edges[].breite (Median aus der Distanztransformation) kam ungerundet an
  (Werte wie 31.622776601683793), während jede andere Zahl in der Ausgabe
  auf 2 Stellen gerundet ist. fahrbahn bleibt unverändert, weil es schon
  eine der vier festen SECTION-Konstanten ist.

Zwei Tests, beide ohne die jeweilige Korrektur rot: die Zebrabalken einer
Kreuzungszufahrt lassen ungleiche Ränder (0,0/0,6 m statt 0,3/0,3 m), und
eine krumme Kantenbreite blieb ungerundet in der Ausgabe.
This commit is contained in:
knso committed 2026-09-18 07:09:15 +02:00
1 parent 2605f95951
commit ca01141c5b
4 files changed
+51 -5

No files matched your search

+5 -1
View File
@@ -139,7 +139,11 @@ def chunk_payload(i, j, *, land, surfaces, carriageway, curb, sidewalk, markings
key=lambda m: (m["poly"], m["art"])), key=lambda m: (m["poly"], m["art"])),
"lots": sorted(({"poly": _rings(p, i, j)} for p in lots), key=lambda l: l["poly"]), "lots": sorted(({"poly": _rings(p, i, j)} for p in lots), key=lambda l: l["poly"]),
"bridges": sorted(({"poly": _rings(p, i, j)} for p in bridges), key=lambda b: b["poly"]), "bridges": sorted(({"poly": _rings(p, i, j)} for p in bridges), key=lambda b: b["poly"]),
"edges": sorted(({"line": _line(l, i, j), "klasse": k, "breite": w, "fahrbahn": f} # breite ist die gemessene Kantenbreite (Median aus der Distanztransformation) und kommt
# ungerundet an (Befund 8 der Fixwelle: Werte wie 31.622776601683793), waehrend jede
# andere Zahl in der Ausgabe gerundet ist; fahrbahn ist dagegen schon eine der vier festen
# SECTION-Konstanten und braucht keine Rundung.
"edges": sorted(({"line": _line(l, i, j), "klasse": k, "breite": round(w, 2), "fahrbahn": f}
for l, k, w, f in edges), for l, k, w, f in edges),
key=lambda e: (e["line"], e["klasse"], e["breite"], e["fahrbahn"])), key=lambda e: (e["line"], e["klasse"], e["breite"], e["fahrbahn"])),
"buildings": sorted(({"poly": _rings(p, i, j), "height": h["height"], "buildings": sorted(({"poly": _rings(p, i, j), "height": h["height"],
+11 -3
View File
@@ -11,6 +11,8 @@ unterbringen, und aus der Nähe bleibt Geometrie scharf, wo eine Textur verwisch
""" """
from __future__ import annotations from __future__ import annotations
import math
from shapely.geometry import LineString, Polygon from shapely.geometry import LineString, Polygon
from shapely.ops import substring from shapely.ops import substring
@@ -157,9 +159,15 @@ def junction_marks(graph: Graph, node: int) -> list[tuple[Polygon, str]]:
# Haltelinie brauchen unterschiedlich viel Platz, deshalb getrennt geprüft statt # Haltelinie brauchen unterschiedlich viel Platz, deshalb getrennt geprüft statt
# gemeinsam verworfen. # gemeinsam verworfen.
if länge >= reach + CROSSWALK_DEPTH: if länge >= reach + CROSSWALK_DEPTH:
# Balken längs der Fahrtrichtung, quer über die Fahrbahn verteilt. # Balken längs der Fahrtrichtung, mittig über die Fahrbahn verteilt (Befund 8 der
offset = -halb + BAR / 2 # Fixwelle): Der alte Startversatz (-halb + BAR/2, im Takt 2*BAR) saß am einen
while offset <= halb - BAR / 2 + 1e-9: # Rand bündig und ließ am anderen bis zu eine Taktbreite frei - 0,6 m bei Haupt-
# straße/Boulevard, 0,4 m bei Wohnstraße, statt der verlangten 60/60-cm-Teilung
# über die ganze Breite. n Balken im selben Takt, symmetrisch um die Fahrbahnmitte
# gestartet, lassen an beiden Rändern denselben Rest frei.
n = math.floor((2 * halb + BAR) / (2 * BAR))
offset = -(n - 1) * BAR
for _ in range(n):
mitte = (origin[0] + across[0] * offset, origin[1] + across[1] * offset) mitte = (origin[0] + across[0] * offset, origin[1] + across[1] * offset)
out.append((_rect(mitte, along, across, reach, CROSSWALK_DEPTH, BAR), "zebra")) out.append((_rect(mitte, along, across, reach, CROSSWALK_DEPTH, BAR), "zebra"))
offset += 2 * BAR offset += 2 * BAR
+14
View File
@@ -174,6 +174,20 @@ def test_neue_schichten_stehen_im_chunk():
assert "roads" not in payload and "centerlines" not in payload assert "roads" not in payload and "centerlines" not in payload
def test_edges_breite_wird_gerundet():
"""Befund 8 der Fixwelle: breite ist die gemessene Kantenbreite (Median aus der
Distanztransformation) und kommt deshalb ungerundet an - am echten Netz Werte wie
31.622776601683793 -, während jede andere Zahl in der Ausgabe auf 2 Stellen gerundet ist.
fahrbahn bleibt unverändert: Es ist schon eine der vier festen SECTION-Konstanten."""
from shapely.geometry import LineString
payload = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[], curb=[], sidewalk=[],
markings=[], lots=[], bridges=[],
edges=[(LineString([(0, 2400), (10, 2400)]), "wohnstrasse",
31.622776601683793, 7.0)],
buildings=[], palms=[])
assert payload["edges"][0]["breite"] == 31.62
def test_neue_schichten_sind_lauf_stabil(): def test_neue_schichten_sind_lauf_stabil():
"""Zwei Aufrufe mit vertauschter Eingabe liefern dieselbe Ausgabe (Fixrunde 3 aus Teil 1).""" """Zwei Aufrufe mit vertauschter Eingabe liefern dieselbe Ausgabe (Fixrunde 3 aus Teil 1)."""
from shapely.geometry import LineString, box from shapely.geometry import LineString, box
+21 -1
View File
@@ -3,10 +3,11 @@ from __future__ import annotations
from collections import Counter from collections import Counter
import pytest
from shapely.geometry import LineString from shapely.geometry import LineString
from leonida.roads import Edge, Graph, Node from leonida.roads import Edge, Graph, Node
from leonida.marking import CROSSWALK_DEPTH, junction_marks, markings from leonida.marking import BAR, CROSSWALK_DEPTH, junction_marks, markings
from leonida.section import SECTION, setback from leonida.section import SECTION, setback
@@ -62,6 +63,25 @@ def test_haltelinie_deckt_nur_die_rechte_haelfte():
assert round(ost.bounds[3] - ost.bounds[1], 1) == round(SECTION["hauptstrasse"].carriageway / 2, 1) assert round(ost.bounds[3] - ost.bounds[1], 1) == round(SECTION["hauptstrasse"].carriageway / 2, 1)
def test_zebrabalken_liegen_symmetrisch_zur_fahrbahnmitte():
"""Befund 8 der Fixwelle: Der alte Startversatz (-halb + BAR/2, Takt 2*BAR) liess die
Balkenreihe an einem Fahrbahnrand bündig sitzen und am gegenüberliegenden bis zu eine
Taktbreite frei - 0,6 m bei Hauptstraße/Boulevard, 0,4 m bei Wohnstraße, statt der laut
Entwurf verlangten Aufteilung "Balken 60 cm, Lücke 60 cm" über die ganze Breite. n Balken,
symmetrisch um die Fahrbahnmitte gestartet, lassen an beiden Rändern denselben Rest frei.
"""
halb = SECTION["hauptstrasse"].carriageway / 2
zebra = [p for p, art in junction_marks(kreuzung(), 0) if art == "zebra"]
# Wie in test_zebra_liegt_am_kreuzungsrand: nur der Ostarm, klar von den Nachbararmen isoliert.
ost = sorted((p.centroid.y for p in zebra if p.centroid.x > 0 and abs(p.centroid.y) < halb))
rand_unten = (ost[0] - BAR / 2) - (-halb)
rand_oben = halb - (ost[-1] + BAR / 2)
assert rand_unten == pytest.approx(rand_oben, abs=1e-6), (
f"Ränder ungleich: unten {rand_unten:.3f} m, oben {rand_oben:.3f} m"
)
assert rand_unten == pytest.approx(0.3, abs=1e-6), "bei 12 m Fahrbahn bleiben 0,3 m an jedem Rand"
def test_arm_der_eine_gasse_ist_bekommt_keinen_zebrastreifen(): def test_arm_der_eine_gasse_ist_bekommt_keinen_zebrastreifen():
graph = kreuzung(("hauptstrasse", "hauptstrasse", "hauptstrasse", "gasse")) graph = kreuzung(("hauptstrasse", "hauptstrasse", "hauptstrasse", "gasse"))
marks = junction_marks(graph, 0) marks = junction_marks(graph, 0)