CH NEO-ZÜRICH AUSGABE
WETTER · GRAU 5°C
BLEND DES TAGES · 07/ROGUE
EST. 2027
DIE AEC CYBER MORGENZEITUNG

PAZ Kaffi

DESIGN · ABBRUCH · KOFFEIN · DEPESCHE
AUSGABE 1105 · 5 November 2026
SENDUNG 04:42 MEZ
2'400 BOGEN GEDRUCKT
LESEZEIT · 47 MIN
Welche Bäume stehen auf dem Grundstück? Zürichs Baumkataster vor der Skizze lesen
Akademie
FRAME · 06:50
05-11-2026

Welche Bäume stehen auf dem Grundstück? Zürichs Baumkataster vor der Skizze lesen

Zürichs CC0-Baumkataster in Python lesen: Bäume am Ort auflisten, 8-m-Platzhalter kennzeichnen, Baufeld testen.

Vor der ersten Skizze die Stadt fragen, welche Bäume sie schon kennt. Grün Stadt Zürich führt den Baumkataster, ein Register mit Position, Art, Kronendurchmesser und Pflanzjahr jedes Baums. Er ist unter CC0 veröffentlicht und wird wöchentlich aktualisiert. Am 30. Oktober 2026 zählte sein Web-Feature-Service 81.142 registrierte Bäume. Das ist nicht jeder Baum in der Stadt. Nach Auskunft von Grün Stadt Zürich umfasst das Register alle städtischen Strassenbäume, Bäume in ausgewählten öffentlichen Parks, private Bäume und das Obstbaumverzeichnis.

Der Grund, ihn jetzt zu öffnen, ist sechs Monate alt und noch gültig. Am 15. April 2026 hat der Stadtrat die BZO-Teilrevision «Baumerhalt» verabschiedet. Das Fällen eines Baums mit Stammumfang ab 100 cm braucht jetzt eine Bewilligung. Die städtische Bauberatung schreibt, dass die Regeln sofort gelten (negative Vorwirkung, § 234 PBG). Die erste Frage auf einem Zürich-Grundstück lautet nicht mehr «darf ich es fällen» sondern «was steht da».

Ein Fakt hält das Skript kurz. Nach swisstopo liegt der LV95-Ursprung bei Bern bei E = 2’600’000 m und N = 1’200’000 m, und „von dort aus werden die Koordinaten in Metern gezählt”. Also ist „wie weit ist der Baum” der Pythagoras. Du brauchst keine Haversine und keine Projektionsbibliothek, nur math.hypot.

Die leisen Helden sitzen unter den Zahlen. Seit 2001 wurden Stammposition tachymetrisch vermessen, mit einer Genauigkeit zwischen unter 1 cm und 30 cm. Die Daten erreichst du über das städtische GIS-Zentrum, Geomatik + Vermessung. Jemand hat ein Stativ neben diesen Stämmen aufgestellt. Deshalb kann dein Skript 3.2 m drucken und es meinen.

Das Werkzeug: der Baumkataster, bereitgestellt durch den Stadt-Zürich-WFS, plus Pythons Standardbibliothek (json, math, sys). Nichts zum pip-Installieren. Ein Bounding-Box um den hier verwendeten Ort gibt 34 KB und 60 Bäume zurück. Lass die BBOX weg und du bekommst die ganze Stadt, 46.6 MB, und das Skript druckt identische Ergebnisse. Die Fähigkeiten übertragen sich auf jeden Schweizer Datensatz in EPSG:2056.

Setup:

# Python 3.10+, nur Standardbibliothek – nichts zum pip-Installieren
mkdir trees
cd trees
# auf Windows PowerShell curl.exe statt curl eingeben
curl -o idaplatz.geojson "https://www.ogd.stadt-zuerich.ch/wfs/geoportal/Baumkataster?SERVICE=WFS&VERSION=1.1.0&REQUEST=GetFeature&TYPENAME=baumkataster_baumstandorte&OUTPUTFORMAT=application/vnd.geo%2Bjson&SRSNAME=EPSG:2056&BBOX=2681206,1247540,2681326,1247660,EPSG:2056"
# das untenstehende Programm als trees_near_site.py speichern, dann:
python -I trees_near_site.py idaplatz.geojson

Erste Schritte:

  1. Führe den Setup-Block aus. Eine Datei namens idaplatz.geojson mit etwa 34 KB erscheint.
  2. Speichere das Programm unten und führe es aus. Es druckt Baumkataster file: 60 trees, dann Site E=2681266 N=1247600, radius 50 m -> 42 trees, dann die Tabelle, nächster Baum zuerst.
  3. Stelle SITE auf deinen Grundstücks-LV95-Punkt und verschiebe die BBOX der URL um ihn herum. Die geo.admin.ch-Suche gibt EPSG:2056 zurück; „Idaplatz 1, 8003 Zürich” wird aufgelöst zu E 2681294.1 / N 1247579.5.
  4. Ersetze die zwei Rechtecke in FOOTPRINTS mit deiner eigenen ersten Massierungsidee.

Der Code, erklärt:

# trees_near_site.py - welche Stadtbäume stehen innerhalb von R Metern eines Orts, wie viel
# Blattdach das ist, und welche Kronen in einen Fussabdruck reichen. Nur Standardbibliothek.
# Daten: Baumkataster, Stadt Zürich (CC0), GeoJSON in LV95 (EPSG:2056) = Meter.
import csv, json, math, sys

GEOJSON = sys.argv[1]                      # path to the downloaded GeoJSON
SITE = (2681266.0, 1247600.0)              # LV95 E, N - Idaplatz, 8003 Zuerich
RADIUS = 50.0                              # search radius in metres
DEFAULT_CROWN = 8                          # the cadastre writes 8 m when the crown is unknown

def rect(e, n, w, h):                      # a w x h footprint with its SW corner at (e, n)
    return [(e, n), (e + w, n), (e + w, n + h), (e, n + h)]

FOOTPRINTS = {"A  pavilion 10 x 14 m as sketched": rect(2681258, 1247594, 10, 14),
              "B  same pavilion, 13 m south, 1 m east": rect(2681259, 1247581, 10, 14)}

def dist(a, b):
    return math.hypot(a[0] - b[0], a[1] - b[1])

def dist_point_segment(p, a, b):
    """Distance from point p to the segment a-b."""
    dx, dy = b[0] - a[0], b[1] - a[1]
    if dx == 0 and dy == 0:
        return dist(p, a)
    t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / (dx * dx + dy * dy)
    t = max(0.0, min(1.0, t))
    return dist(p, (a[0] + t * dx, a[1] + t * dy))

def point_in_polygon(p, poly):
    """Ray casting: odd number of edge crossings to the right of p = inside."""
    inside, n = False, len(poly)
    for i in range(n):
        (x1, y1), (x2, y2) = poly[i], poly[(i + 1) % n]
        if (y1 > p[1]) != (y2 > p[1]):
            if p[0] < x1 + (p[1] - y1) * (x2 - x1) / (y2 - y1):
                inside = not inside
    return inside

def dist_point_polygon(p, poly):
    """0.0 inside the polygon, otherwise the distance to the nearest edge."""
    if point_in_polygon(p, poly):
        return 0.0
    return min(dist_point_segment(p, poly[i], poly[(i + 1) % len(poly)]) for i in range(len(poly)))

with open(GEOJSON, encoding="utf-8") as f:
    fc = json.load(f)
assert fc["crs"]["properties"]["name"].endswith("EPSG::2056"), "expected LV95 metres"

near = []
for feat in fc["features"]:
    e, n = feat["geometry"]["coordinates"]
    d = dist((e, n), SITE)
    if d <= RADIUS:
        near.append((d, e, n, feat["properties"]))
near.sort(key=lambda r: r[0])

print(f"Baumkataster file: {len(fc['features'])} trees")
print(f"Site E={SITE[0]:.0f} N={SITE[1]:.0f}, radius {RADIUS:.0f} m -> {len(near)} trees
")
print(f"{'dist':>5} {'E':>11} {'N':>11} {'crown':>6} {'year':>5}  {'fix':<9} species")
canopy = defaults = 0
for d, e, n, q in near:
    crown = q["kronendurchmesser"]
    canopy += math.pi * (crown / 2) ** 2
    defaults += crown == DEFAULT_CROWN
    fix = "measured" if q["genauigkeit"].startswith("Eingemessen") else "screen"
    print(f"{d:5.1f} {e:11.1f} {n:11.1f} {crown:5d}{'*' if crown == DEFAULT_CROWN else ' '} "
          f"{q['pflanzjahr'] or '-':>5}  {fix:<9} {q['baumnamelat']}")

disc = math.pi * RADIUS ** 2
years = [q["pflanzjahr"] for *_, q in near if q["pflanzjahr"]]
print(f"
Canopy, sum of crown discs (overlaps not merged): {canopy:,.0f} m2 = {100 * canopy / disc:.0f} % of the {disc:,.0f} m2 search disc")
print(f"* = crown exactly {DEFAULT_CROWN} m, the cadastre's placeholder for 'unknown': {defaults} of {len(near)}")
print(f"Planting year: oldest {min(years)}, newest {max(years)}, unknown {len(near) - len(years)}")
print(f"Positions surveyed on site: {sum(1 for *_, q in near if q['genauigkeit'].startswith('Eingemessen'))} of {len(near)}")
print("Trunk circumference: NOT in the cadastre. The 100 cm permit rule needs a tape measure on site.")

for name, poly in FOOTPRINTS.items():
    print(f"
Footprint {name}:")
    trunks = crowns = 0
    for d, e, n, q in near:
        gap, r = dist_point_polygon((e, n), poly), q["kronendurchmesser"] / 2
        if gap == 0:
            trunks += 1
            print(f"  TRUNK INSIDE   {q['baumnamelat']} ({q['pflanzjahr'] or '?'}), crown {2 * r:.0f} m")
        elif gap < r:
            crowns += 1
            print(f"  crown overlaps {q['baumnamelat']}: trunk {gap:.1f} m outside the edge, crown radius {r:.1f} m")
    print(f"  -> {trunks} trunk(s) inside, {crowns} further crown(s) overhanging")

Zeilen 6–9: die vier Schalter. Diese Zeilen halten den Dateipfad, den Ortspunkt in Metern, den Radius und die 8-m-Konstante. Eine Konstante ist eine benannte Zahl, die du einmal setzt, damit der nächste Leser weiss, was die 8 bedeutet. SITE ist der gerundete Schwerpunkt der 17 Katasterbäume, deren Strassenfeld Idaplatz liest. Der 10 × 14-m-Pavillon ist hypothetisch.

Zeilen 17–27: Entfernung, zweimal. dist ist Pythagoras, √(ΔE² + ΔN²). Du kannst den nächsten Baum von Hand überprüfen: √((2681262.862 − 2681266)² + (1247599.325 − 1247600)²) = 3.21 m. dist_point_segment projiziert p auf die Kante a–b als Bruch t, wobei 0 bei a und 1 bei b ist. Es begrenzt t auf den Bereich 0 bis 1 und misst zu diesem Punkt. Das Clamping ist die ganze Idee: ohne es würdest du zu einer Wand messen, die in die Strasse hinein führt.

Zeilen 29–43: innen oder aussen. point_in_polygon wirft einen Strahl vom Stamm nach rechts und zählt die Fussabdruck-Kanten, die er kreuzt. Eine ungerade Anzahl bedeutet innen. Dies funktioniert für jedes einfache Polygon, nicht nur Rechtecke. dist_point_polygon gibt 0.0 innen zurück, andernfalls die Entfernung zur nächsten Kante.

Zeilen 45–55: laden, überprüfen, filtern. json.load verwandelt die Datei in Wörterbücher und Listen. Das assert schützt vor dem klassischen Anfängerfehler. Wenn du die URL bearbeitest und SRSNAME verlierst, sind die Koordinaten möglicherweise nicht in Metern, und jede Entfernung wird stillschweigend falsch. Die Schleife behält dann die 42 Bäume innerhalb von 50 m, nächster zuerst. Es ist der List-Move, den PAZ in Arrays for architects gelehrt hat.

Zeilen 57–75: der Bericht und das Lesen der Metadaten. Jede Krone zählt als Scheibe, π·(d/2)². Die Zusammenfassung druckt:

Canopy, sum of crown discs (overlaps not merged): 1,086 m2 = 14 % of the 7,854 m2 search disc
* = crown exactly 8 m, the cadastre's placeholder for 'unknown': 9 of 42
Planting year: oldest 1970, newest 2024, unknown 11
Positions surveyed on site: 31 of 42
Trunk circumference: NOT in the cadastre. The 100 cm permit rule needs a tape measure on site.

Lies die Datenseite, bevor du der Kronenspalte vertraust. Grün Stadt Zürich schreibt, dass Kronendurchmesser unvollständig, unregelmässig aktualisiert und „nur geschätzt” sind, und dass 8 m überall dort verwendet wird, wo kein Durchmesser existiert. Der Stern markiert diese Zeilen: 9 von 42 hier und 13.815 stadtweite (17 %). Acht Meter ist das höfliche Schulterzuckend des Katasters. Die 14 % addieren Scheiben mit den Überlappungen auf, also ist es keine gemessene Schattenfläche. Es gibt überhaupt kein Umfangsfeld, also kann die 100-cm-Regel von deinem Schreibtisch aus nicht beantwortet werden.

Zeilen 77–88: der Fussabdruck-Test. gap ist die Entfernung des Stamms zum Fussabdruck und r ist der Kronenradius. Eine Lücke von 0 bedeutet, der Stamm steht innen; eine Lücke unter r bedeutet, die Krone reicht über die Kante. Fussabdruck A hat einen Stamm innen, einen 2001 gepflanzten Acer pseudoplatanus. Es hat auch eine Gleditsia, deren Stamm 0.1 m ausserhalb der Kante steht, mit überhängender Krone. Das Skript druckt -> 1 trunk(s) inside, 1 further crown(s) overhanging. Fussabdruck B, 13 m südlich und 1 m östlich, druckt -> 0 trunk(s) inside, 0 further crown(s) overhanging. Eine Rastersuche in 1-m-Schritten fand 1.380 klare Positionen innerhalb von 40 m, und B ist die dem Entwurf am nächsten liegende. Einfach gesagt, der Kompromiss ist dieser: Der Test ist nur so gut wie eine Kronenspalte, die die Stadt selbst als geschätzt bezeichnet. Ein klares Ergebnis bedeutet „geh und miss”, nicht „geh und baue”.

←HEUTE: Seit 15. April 2026 erfüllt jedes Zürich-Baugesuch die Baumerhalt-Regeln, und die Stadt veröffentlicht 81.142 registrierte Bäume unter CC0 wöchentlich.
→3012: Der Baum gelangt vor der Wand ins Modell, an seiner wahren Position, mit seiner Unsicherheit daneben gedruckt.
Drehpunkt: Offene metrische Daten sagen dir, wo du hinschauen sollst; nur das Stahllineal sagt dir, was die Bewilligung braucht.

Atelier: Für ein kleines Zürich-Studio geht es bei der Verschiebung um die Reihenfolge. Seit April ist ein Baum in der Skizze eine Bewilligungsfrage, und das Kataster zeigt dir diesen Baum, bevor sich jemand in die Massierung verliebt. Ein KI-Massierungsassistent wird glücklich einen Pavillon auf einem Bergahorn platzieren. Eine 34-KB-Datei kümmert sich nicht darum, wie gut das Rendering aussieht. Der Montag-Schritt ist eine Zeile auf der Projekt-Start-Checkliste: trees_near_site.py mit dem Parzellenblatt-BBOX ausführen, den Ausdruck archivieren und einen Messtermin buchen für jeden Baum, dessen Krone das Baufeld erreicht.

Hack: Schicke die Kronen an Rhino als Kreise, gemessen vom Ortspunkt, damit die Geometrie nahe dem Ursprung sitzt, wo Rhino am präzisesten ist. Füge diese Zeilen am Ende des Programms hinzu und sage das Ergebnis voraus, bevor du es ausführst. crowns_rhino.txt sollte 42 Zeilen enthalten, und die erste sollte mit _Circle -3.14,-0.68,0 beginnen: der 3.21-m-Baum aus der Hand-Überprüfung. In Rhino 8 sollte ReadCommandFile die Datei in ein Meter-Modell abspielen, bereit für eine Grasshopper-Definition.

# one Rhino command per crown, site point = model origin
with open("crowns_rhino.txt", "w") as out:
    for d, e, n, q in near:
        out.write(f"_Circle {e - SITE[0]:.2f},{n - SITE[1]:.2f},0 {q['kronendurchmesser'] / 2:.1f}
")

Von wo ich sitze, in den späten 2070ern, waren die Datensätze, die überlebten, die langweiligen: Klartext, eine offene Lizenz und eine Seite, die sagt, was die Zahlen bedeuten. Wenn du dieses Quartal einen Site-Analyse-Stack wählst, frag dich, ob ein 25-Jähriger die Datei noch öffnen könnte, sobald der Anbieter weg ist. Hier ist die Antwort ja. Archiviere das Raw GeoJSON bei jedem Projekt, um es so zu halten.

Learn-it:

Speichere trees_near_site.py mit deinem Parzellenblatt-BBOX und führe es vor der ersten Skizze jedes Zürich-Projekts aus.

GEMELDET AUS
MIT-UNTERZEICHNER
PAZ Academy
VERTRAUEN
HIGH
NACHDRUCKE
© PAZ - PARAMETRIC ACADEMY ZURICH · ALLE RECHTE VORBEHALTEN

PAZ Kaffi · interdisziplinäre Redaktionsarbeit, geleitet von der PAZ Academy

⚑ FEHLER MELDEN · KORREKTUR EINSENDEN
◂ ZURÜCK ZUR TITELSEITE · PAZ KAFFI

© 2026 PAZ Academy.