3.4. Vorbereitung der Daten: Einlesen und Bereinigung#
3.4.1. Umsetzung mit R Shiny#
Zielsetzung Ziel dieses Schrittes ist es, verschiedene Datensätze – insbesondere den Berliner Baumkataster sowie manuell dokumentierte Gießdaten – zusammenzuführen und so aufzubereiten, dass sie für weitere Analysen und Visualisierungen (z. B. in einer interaktiven Karte) verwendet werden können.
Laden der Baumkatasterdaten Die Berliner Baumdaten werden über eine WFS-Schnittstelle (Web Feature Service) bezogen. Dabei werden sowohl Anlagenbäume als auch Straßenbäume geladen.
anlagenbaeume <- st_read("WFS:https://gdi.berlin.de/services/wfs/baumbestand", layer = "baumbestand:anlagenbaeume")
strassenbaeume <- st_read("WFS:https://gdi.berlin.de/services/wfs/baumbestand", layer = "baumbestand:strassenbaeume")
Laden und Bereinigung der Gießdaten Die Gießdaten stammen aus einer CSV-Datei des Projekts „Gieß den Kiez“. Sie werden eingelesen und anschließend bereinigt:
Ungültige oder fehlende Koordinaten (Längen-/Breitengrad) werden entfernt.
Datensätze ohne Straßenname oder mit fehlerhaften Gattungsbezeichnungen (z. B. numerische Werte) werden ausgeschlossen.
# 2. Bewässerungsdaten laden
df_clean <- read.csv("data/giessdenkiez_bewässerungsdaten.csv", sep = ";", stringsAsFactors = FALSE, fileEncoding = "UTF-8") %>%
drop_na(lng, lat, bewaesserungsmenge_in_liter) %>%
filter(strname != "Undefined" & strname != "" & !str_detect(gattung_deutsch, "[0-9]"))
Vereinheitlichung und Zusammenführung der Baumdaten Die beiden Baumdatenquellen werden vereinheitlicht (gemeinsames Koordinatensystem EPSG:4326) und zusammengeführt. Danach werden die Koordinaten explizit extrahiert und die Geometriedaten entfernt, um die Dateigröße zu reduzieren und die Weiterverarbeitung zu erleichtern.
# 3. Bäume zusammenführen
baumbestand <- bind_rows(anlagenbaeume, strassenbaeume) %>%
st_transform(crs = 4326)
# 4. Geometrie extrahieren
coords <- st_coordinates(baumbestand$geom)
baumbestand$lng <- coords[, "X"]
baumbestand$lat <- coords[, "Y"]
Harmonisierung und Verknüpfung der Daten Die eindeutige Baumkennung gisid wird so angepasst, dass sie mit der id aus den Gießdaten übereinstimmt (Unterstrich wird zu Doppelpunkt). Dadurch können die beiden Datensätze über einen sogenannten “Left Join” zusammengeführt werden.
# 5. Geometrie entfernen
baumbestand <- st_drop_geometry(baumbestand)
# 6. gisid anpassen (Unterstrich → Doppelpunkt)
baumbestand$gisid <- str_replace_all(baumbestand$gisid, "_", ":")
# 7. Bäume und Bewässerungsdaten mergen (über gisid = id)
df_merged <- baumbestand %>%
left_join(df_clean %>% select(id, bewaesserungsmenge_in_liter, timestamp),
by = c("gisid" = "id"))
Speichern der kombinierten Daten Die aufbereiteten Daten werden als CSV-Datei gespeichert. Eine Ausgabe relevanter Kennzahlen (z. B. Anzahl verknüpfter Bäume) dient der Kontrolle.
# 8. Ergebnis speichern
write.csv2(df_merged, "data/df_merged_final.csv", row.names = FALSE, fileEncoding = "UTF-8")
gesamter Code
library(sf)
library(dplyr)
library(tidyr)
library(stringr)
# 1. Bäume laden
anlagenbaeume <- st_read("WFS:https://gdi.berlin.de/services/wfs/baumbestand", layer = "baumbestand:anlagenbaeume")
strassenbaeume <- st_read("WFS:https://gdi.berlin.de/services/wfs/baumbestand", layer = "baumbestand:strassenbaeume")
# 2. Bewässerungsdaten laden
df_clean <- read.csv("data/giessdenkiez_bewässerungsdaten.csv", sep = ";", stringsAsFactors = FALSE, fileEncoding = "UTF-8") %>%
drop_na(lng, lat, bewaesserungsmenge_in_liter) %>%
filter(strname != "Undefined" & strname != "" & !str_detect(gattung_deutsch, "[0-9]"))
# 3. Bäume zusammenführen
baumbestand <- bind_rows(anlagenbaeume, strassenbaeume) %>%
st_transform(crs = 4326)
# 4. Geometrie extrahieren
coords <- st_coordinates(baumbestand$geom)
baumbestand$lng <- coords[, "X"]
baumbestand$lat <- coords[, "Y"]
# 5. Geometrie entfernen
baumbestand <- st_drop_geometry(baumbestand)
# 6. gisid anpassen (Unterstrich → Doppelpunkt)
baumbestand$gisid <- str_replace_all(baumbestand$gisid, "_", ":")
# 7. Bäume und Bewässerungsdaten mergen (über gisid = id)
df_merged <- baumbestand %>%
left_join(df_clean %>% select(id, bewaesserungsmenge_in_liter, timestamp),
by = c("gisid" = "id"))
# 8. Ergebnis speichern
write.csv2(df_merged, "data/df_merged_final.csv", row.names = FALSE, fileEncoding = "UTF-8")
# 9. Kontrolle: Anzahl der Zeilen
cat("Anzahl Bäume nach Merge:", nrow(df_merged), "\n")
cat("Anzahl eindeutiger Bäume (pitid):", n_distinct(df_merged$pitid), "\n")
cat("Anzahl Bäume mit Bewässerungsdaten:", sum(!is.na(df_merged$bewaesserungsmenge_in_liter)), "\n")
Geografische Zuordnung zu Berliner Bezirken#
Zielsetzung Einige Bäume verfügen nicht über eine Angabe zu ihrem Bezirk. Um eine aggregierte räumliche Analyse (z. B. Gießverhalten nach Bezirk) zu ermöglichen, werden fehlende Bezirksangaben durch räumliches Verschneiden mit offiziellen Bezirkspolygonen ergänzt.
Methodik
Bäume ohne Bezirk werden in räumliche Objekte konvertiert (sf-Objekte).
Mittels eines „spatial join“ wird ermittelt, in welchem Bezirk sich jeder Baum befindet.
Das Ergebnis wird mit den ursprünglichen Daten wieder zusammengeführt.
Code Erklärung: 1. Die Bezirkskarte laden
bezirksgrenzen <- st_read("data/bezirksgrenzen.geojson")
Es wird eine digitale Karte geladen, auf der die Bezirksgrenzen Berlins eingezeichnet sind.
Jeder Bezirk hat dabei ein sogenanntes „Polygon“ – eine Art Umrisslinie.
2. Die Baumdaten laden
df_baeume <- read.csv("data/df_merged_final.csv", sep = ";", stringsAsFactors = FALSE)
Eine Tabelle mit Baumdaten wird eingelesen. Jeder Eintrag beschreibt einen Baum: z. B. seine Art, Pflanzjahr und die Koordinaten, wo er steht.
Manche Bäume haben schon einen Bezirk eingetragen, andere nicht.
3. Koordinaten umwandeln
df_baeume <- df_baeume %>%
mutate(
lng = as.numeric(gsub(",", ".", lng)),
lat = as.numeric(gsub(",", ".", lat))
)
Manche Koordinaten sind falsch formatiert (mit Komma statt Punkt, z. B. „13,405“ statt „13.405“). Das wird korrigiert, damit der Computer die Zahlen richtig versteht.
4. Zwei Gruppen bilden:
df_mit_bezirk <- df_baeume %>% filter(!is.na(bezirk))
df_ohne_bezirk <- df_baeume %>% filter(is.na(bezirk) & !is.na(lng) & !is.na(lat))
Gruppe 1: Bäume, bei denen der Bezirk schon bekannt ist.
Gruppe 2: Bäume, bei denen der Bezirk fehlt, aber die Koordinaten vorhanden sind.
5. Gruppe ohne Bezirk in geografisches Format umwandeln
df_ohne_bezirk_sf <- st_as_sf(df_ohne_bezirk, coords = c("lng", "lat"), crs = 4326, remove = FALSE)
Die zweite Gruppe wird in ein spezielles Format (sogenannte sf-Objekte) umgewandelt.
Das ist notwendig, damit man mit Geodaten (Karten und Punkten auf Karten) arbeiten kann.
6. Bezirksgrenzen vorbereiten
bezirksgrenzen <- st_transform(bezirksgrenzen, crs = st_crs(df_ohne_bezirk_sf)) %>%
rename(bezirk = Gemeinde_name)
Die Karte der Bezirke wird ins gleiche geografische System wie die Baumdaten gebracht (Koordinatensystem).
Außerdem wird der Name des Bezirksfeldes vereinfacht in „bezirk“.
7. Räumlicher Vergleich: Welcher Baum liegt in welchem Bezirk?
df_ohne_bezirk_joined <- st_join(df_ohne_bezirk_sf, bezirksgrenzen["bezirk"], left = TRUE)
Jetzt wird für jeden Baum ohne Bezirk geschaut, ob er innerhalb eines Bezirks liegt.
Dafür wird überprüft, welches Bezirks-Polygon den jeweiligen Baum „einschließt“.
Dieser Vorgang heißt „spatial join“ – also ein räumliches Verbinden.
8. Ergebnis bereinigen und in normales Tabellenformat bringen
df_ohne_bezirk_filled <- df_ohne_bezirk_joined %>%
mutate(bezirk = ifelse(is.na(bezirk.x), bezirk.y, bezirk.x)) %>%
select(-bezirk.x, -bezirk.y) %>%
st_drop_geometry()
Die berechneten Bezirksangaben werden in die Tabelle übernommen.
Zusätzliche technische Spalten werden entfernt.
Die geografischen Informationen werden wieder „fallen gelassen“, damit es wieder eine normale Tabelle ist.
9. Beide Gruppen wieder zusammenfügen
df_baeume_final <- bind_rows(df_mit_bezirk, df_ohne_bezirk_filled)
Jetzt werden alle Bäume wieder in einer Tabelle vereint:
Die, die schon einen Bezirk hatten.
Und die, denen jetzt ein Bezirk zugeordnet wurde.
10. Neue, vollständige Tabelle speichern
write.csv2(df_baeume_final, file = "data/df_merged_final.csv", row.names = FALSE)
Die neue Tabelle mit allen Bäumen und Bezirken wird als Datei gespeichert.
gesamter Code
library(sf)
library(dplyr)
# 1. Lade die Bezirkspolygone
bezirksgrenzen <- st_read("data/bezirksgrenzen.geojson")
# 2. Lade die Baumdaten
df_baeume <- read.csv("data/df_merged_final.csv", sep = ";", stringsAsFactors = FALSE)
# 3. Koordinaten korrekt umwandeln
df_baeume <- df_baeume %>%
mutate(
lng = as.numeric(gsub(",", ".", lng)),
lat = as.numeric(gsub(",", ".", lat))
)
# 4. Zerlege in zwei Gruppen
df_mit_bezirk <- df_baeume %>% filter(!is.na(bezirk))
df_ohne_bezirk <- df_baeume %>% filter(is.na(bezirk) & !is.na(lng) & !is.na(lat))
# 5. Konvertiere nur die ohne Bezirk zu sf
df_ohne_bezirk_sf <- st_as_sf(df_ohne_bezirk, coords = c("lng", "lat"), crs = 4326, remove = FALSE)
# 6. Bezirkspolygone vorbereiten
bezirksgrenzen <- st_transform(bezirksgrenzen, crs = st_crs(df_ohne_bezirk_sf)) %>%
rename(bezirk = Gemeinde_name)
# 7. Räumlicher Join
df_ohne_bezirk_joined <- st_join(df_ohne_bezirk_sf, bezirksgrenzen["bezirk"], left = TRUE)
# 8. In DataFrame zurückwandeln
df_ohne_bezirk_filled <- df_ohne_bezirk_joined %>%
mutate(bezirk = ifelse(is.na(bezirk.x), bezirk.y, bezirk.x)) %>%
select(-bezirk.x, -bezirk.y) %>%
st_drop_geometry()
# 9. Zusammenführen mit ursprünglichen Daten, die bereits einen Bezirk hatten
df_baeume_final <- bind_rows(df_mit_bezirk, df_ohne_bezirk_filled)
# 10. Ergebnis speichern
write.csv2(df_baeume_final, file = "data/df_merged_final.csv", row.names = FALSE)
Voraggregation: Erstellung von df_merged_sum#
Zielsetzung Um die Performance in der Shiny-App zu verbessern, werden pro Baum aggregierte Kennzahlen berechnet, wie z. B.:
Gesamte Bewässerungsmenge
Durchschnittliches Gießintervall in Tagen
Methodik
Gruppierung aller Gießeinträge nach Baum-ID (gisid).
Sortierung der Gießvorgänge nach Datum.
Berechnung der Zeitabstände zwischen den Gießungen.
Zusammenfassung zu Mittelwerten und Summen.
Erklärung des Codes: 1. Einlesen der Daten
df_merged_full <- fread("data/df_merged_final.csv", sep = ";", encoding = "UTF-8")
Die CSV-Datei mit allen Gießvorgängen wird eingelesen.
Jeder Eintrag steht für eine Gießung eines bestimmten Baums.
gisid ist die eindeutige Kennung (ID) jedes Baumes.
2. Berechnung der durchschnittlichen Gießabstände pro Baum
bewässerungs_frequenz <- df_merged_full %>%
group_by(gisid) %>% # Alle Einträge eines Baums zusammenfassen
filter(n() > 1) %>% # Nur Bäume, die mehr als einmal gegossen wurden
arrange(gisid, timestamp) %>% # Nach Zeit sortieren
mutate(differenz = as.numeric(difftime(timestamp, lag(timestamp), units = "days"))) %>%
summarise(durchschnitts_intervall = mean(differenz, na.rm = TRUE)) %>%
ungroup()
Was passiert hier?
Für jeden Baum, der mehr als einmal gegossen wurde:
Die Gießtermine werden chronologisch sortiert.
Für jeden Gießvorgang wird berechnet, wie viele Tage seit dem letzten Mal vergangen sind (
differenz
).Aus allen Abständen wird ein Durchschnittswert gebildet: „Wie oft wird dieser Baum im Schnitt gegossen?“
3. Ergebnis (die Durchschnittswerte) mit den Gießdaten verbinden
df_merged_full <- df_merged_full %>%
left_join(bewässerungs_frequenz, by = "gisid")
Diese neu berechneten Durchschnittswerte werden jetzt zurück in die Gesamttabelle eingefügt, damit man alle Infos in einer Tabelle hat.
4. Fehlende Werte ersetzen
df_merged_full$durchschnitts_intervall[is.na(df_merged_full$durchschnitts_intervall)] <- 0
Manche Bäume wurden nur einmal gegossen – da lässt sich kein Abstand berechnen.
Für diese Bäume wird der Durchschnitt einfach auf 0 gesetzt.
5. Endgültige Zusammenfassung: Ein Eintrag pro Baum
df_merged_sum <- df_merged_full %>%
group_by(gisid) %>%
summarise(
gesamt_bewaesserung = sum(bewaesserungsmenge_in_liter, na.rm = TRUE),
durchschnitts_intervall = unique(durchschnitts_intervall),
lat = first(lat),
lng = first(lng),
gattung_deutsch = first(gattung_deutsch),
art_dtsch = first(art_dtsch),
hausnr = first(hausnr),
strname = first(strname),
bezirk = first(bezirk),
bewaesserungsmenge_in_liter = first(bewaesserungsmenge_in_liter),
timestamp = first(timestamp),
.groups = "drop"
)
Was passiert hier?
Jetzt wird die Tabelle so zusammengefasst, dass es nur noch eine Zeile pro Baum gibt.
Dabei werden:
Summen gebildet (z. B. wie viel Liter insgesamt gegossen wurde),
oder einfach ein erster Wert übernommen (z. B. der Straßenname, Koordinaten usw. – weil diese sich bei einem Baum nicht ändern).
6. Speichern des Endergebnisses
write.csv2(df_merged_sum, file = "data/df_merged_gesamter_baumbestand_sum1.csv", sep = ";")
Die neue, kompakte Tabelle wird gespeichert.
Diese Datei enthält einen Eintrag pro Baum, mit den wichtigsten zusammengefassten Infos.
gesamter Code:
df_merged_full <- fread("data/df_merged_final.csv", sep = ";", encoding = "UTF-8")
# 1. Intervall berechnen
bewässerungs_frequenz <- df_merged_full %>%
group_by(gisid) %>%
filter(n() > 1) %>%
arrange(gisid, timestamp) %>%
mutate(differenz = as.numeric(difftime(timestamp, lag(timestamp), units = "days"))) %>%
summarise(durchschnitts_intervall = mean(differenz, na.rm = TRUE)) %>%
ungroup()
# 2. Mit df_merged zusammenführen
df_merged_full <- df_merged_full %>%
left_join(bewässerungs_frequenz, by = "gisid")
# 3. 0 setzen für fehlende Intervalle
df_merged_full$durchschnitts_intervall[is.na(df_merged_full$durchschnitts_intervall)] <- 0
# 4. df_merged_sum <- df_merged_full %>%
group_by(gisid) %>%
summarise(
gesamt_bewaesserung = sum(bewaesserungsmenge_in_liter, na.rm = TRUE),
durchschnitts_intervall = unique(durchschnitts_intervall),
lat = first(lat),
lng = first(lng),
gattung_deutsch = first(gattung_deutsch),
art_dtsch = first(art_dtsch),
hausnr = first(hausnr),
strname = first(strname),
bezirk = first(bezirk),
bewaesserungsmenge_in_liter = first(bewaesserungsmenge_in_liter),
timestamp = first(timestamp),
.groups = "drop"
)
write.csv2(df_merged_sum, file = "data/df_merged_gesamter_baumbestand_sum1.csv", sep = ";")
Reduktion der Pumpendaten#
Zielsetzung Die Originaldaten der Wasserpumpen enthalten viele unnötige Spalten. Um Ressourcen zu schonen, wird ein reduzierter Datensatz erzeugt.
Vorgehen Nur die relevanten Informationen (z. B. ob die Pumpe funktionstüchtig ist, ihre ID, der Pumpentyp und die Geometrie) werden beibehalten.
Code Erklärung: 1. Die vollständigen Pumpendaten einlesen
pumpen_full <- st_read("data/pumpen.geojson")
Es wird die vollständige Datei mit allen Wasserpumpen geöffnet.
2. Nur die relevanten Spalten auswählen
pumpen <- pumpen_full %>%
select(pump, pump.style, pump.status, geometry, man_made, id)
Was passiert hier?
Es wird eine neue Tabelle erstellt, die nur die wichtigsten Infos aus der großen Datei übernimmt:
Spalte |
Bedeutung |
---|---|
|
Ist die Pumpe funktionsfähig? (z. B. “ja” oder “nein”) |
|
Welche Art von Pumpe ist es? (z. B. Handschwengelpumpe) |
|
Der Standort der Pumpe auf der Karte (Längen- und Breitengrad) |
|
Allgemeine Einordnung: Es handelt sich um ein von Menschen gebautes Objekt |
|
Eine eindeutige ID, um die Pumpe zu identifizieren |
|
Information ob die Pumpe Funktionsfähig ist |
Alles andere wird weggelassen.
3. Die reduzierte Datei abspeichern
st_write(pumpen, "data/pumpen_minimal.geojson",
driver = "GeoJSON", delete_dsn = TRUE)
Die neue, kleinere Datei mit nur den benötigten Infos wird unter dem Namen
pumpen_minimal.geojson
gespeichert.Format bleibt weiterhin GeoJSON – das ist ein gängiges Format für geografische Daten.
delete_dsn = TRUE
sorgt dafür, dass eine eventuell vorhandene Datei mit demselben Namen überschrieben wird.
gesamter Code:
# --- pumpen minimieren ---
pumpen_full <- st_read("data/pumpen.geojson")
pumpen <- pumpen_full %>%
select(pump, pump.style, geometry, man_made, id)
st_write(pumpen, "data/pumpen_minimal.geojson",
driver = "GeoJSON", delete_dsn = TRUE)
Bezirkszuordnung für Pumpen#
Zielsetzung Analog zur Baumzuordnung sollen auch Pumpen mit ihrem Bezirk verknüpft werden, um regionale Analysen zu ermöglichen.
Vorgehen Ein räumlicher Join ermittelt für jede Pumpe, in welchem Bezirk sie liegt.
Code Erklärung: 1. Die reduzierten Pumpendaten laden
pumpen <- st_read("data/pumpen_minimal.geojson")
Es wird die kleinere, bereits vorbereitete Datei mit den wichtigsten Pumpendaten geöffnet.
Jede Zeile beschreibt eine einzelne Pumpe.
2. Die Bezirksgrenzen laden
bezirksgrenzen <- st_read("data/bezirksgrenzen.geojson")
Es wird eine Datei geöffnet, in der die Umrisse der Berliner Bezirke enthalten sind.
Jeder Bezirk ist als Fläche auf einer Karte dargestellt.
3. Einheitliches Koordinatensystem sicherstellen
pumpen <- st_transform(pumpen, crs = 4326)
bezirksgrenzen <- st_transform(bezirksgrenzen, crs = 4326)
Damit die Vergleiche korrekt funktionieren, müssen beide Datensätze dasselbe geografische Bezugssystem verwenden – hier
WGS84
, das weltweit verwendet wird.Ohne diesen Schritt würden die Pumpenpunkte und Bezirksflächen eventuell an völlig unterschiedlichen Orten erscheinen.
4. Räumlicher Join: Pumpen bekommen ihren Bezirk`
pumpen_mit_bezirk <- st_join(pumpen, bezirksgrenzen[, c("Gemeinde_name")], left = TRUE)
Für jede Pumpe wird automatisch berechnet, in welchem Bezirk sie sich befindet.
Der Name des Bezirks (
Gemeinde_name
) wird als neue Spalte in die Pumpen-Daten eingefügt.left = TRUE
bedeutet: alle Pumpen bleiben erhalten, auch wenn sie aus irgendeinem Grund außerhalb der Bezirksgrenzen liegen sollten.
5. Spalte umbenennen für Klarheit
pumpen_mit_bezirk <- pumpen_mit_bezirk %>%
rename(bezirk = Gemeinde_name)
Die Spalte mit dem Bezirksnamen wird von Gemeinde_name
in bezirk
umbenannt – damit sie besser verständlich ist.
6. Datei speichern
st_write(pumpen_mit_bezirk, "data/pumpen_mit_bezirk.geojson", driver = "GeoJSON", delete_dsn = TRUE)
Die fertige Datei wird gespeichert.
Sie enthält jetzt alle Pumpen, inklusive einer neuen Spalte mit dem Bezirksnamen.
Das Format bleibt GeoJSON, damit es weiterhin in Kartenanwendungen verwendet werden kann.
gesamter Code:
pumpen <- st_read("data/pumpen_minimal.geojson")
bezirksgrenzen <- st_read(data/bezirksgrenzen.geojson")
# Sicherstellen, dass beide im selben CRS sind (z.B. WGS84)
pumpen <- st_transform(pumpen, crs = 4326)
bezirksgrenzen <- st_transform(bezirksgrenzen, crs = 4326)
# Räumlicher Join: Pumpen bekommt den Namen des Bezirks, in dem sie liegt
pumpen_mit_bezirk <- st_join(pumpen, bezirksgrenzen[, c(Gemeinde_name")], left = TRUE)
pumpen_mit_bezirk <- pumpen_mit_bezirk %>%
rename(bezirk = Gemeinde_name)
# GeoJSON-Datei speichern
st_write(pumpen_mit_bezirk, "data/pumpen_mit_bezirk.geojson", driver = "GeoJSON", delete_dsn = TRUE)
Pumpendatensatz mit Bezirk minimiert:
# --- pumpen_mit_bezirk minimieren ---
pumpen_mit_bezirk_full <- st_read("data/pumpen_mit_bezirk.geojson")
pumpen_mit_bezirk <- pumpen_mit_bezirk_full %>%
select(pump, pump.style, pump.status, bezirk, geometry, man_made, id)
st_write(pumpen_mit_bezirk, "data/pumpen_mit_bezirk_minimal.geojson",
driver = "GeoJSON", delete_dsn = TRUE)
Entfernung zur nächsten Pumpe berechnen#
Zielsetzung Für jede Baumposition soll berechnet werden, wie weit die nächste funktionierende Wasserpumpe entfernt ist.
Vorgehen
Nur funktionierende Pumpen auswählen.
Beide Datensätze in ein metrisches Koordinatensystem (UTM) umwandeln, da Distanzen in Metern berechnet werden.
Mit der Funktion st_nn() wird für jeden Baum die nächstgelegene Pumpe ermittelt.
Die berechneten Entfernungen werden in einer neuen Spalte gespeichert.
gesamter Code:
library(data.table)
library(sf)
library(dplyr)
library(stringr)
library(tidyr)
library(nngeo)
# # 1. Daten einlesen
pumpen_mit_bezirk <- st_read("data/pumpen_mit_bezirk_minimal.geojson")
df_merged_sum <- read.csv("data/df_merged_gesamter_baumbestand_sum1.csv", sep = ";", stringsAsFactors = FALSE, fileEncoding = "UTF-8")
pumpen_mit_bezirk_ok <- pumpen_mit_bezirk %>%
filter(pump.status == "ok")
# 2. Koordinaten umwandeln (Komma zu Punkt)
df_merged_sum <- df_merged_sum %>%
mutate(
lat = as.numeric(str_replace(lat, ",", ".")),
lng = as.numeric(str_replace(lng, ",", "."))
) %>%
drop_na(lat, lng)
# 3. sf-Objekte erstellen mit WGS84 (Geodätisch)
df_merged_sum_sf <- st_as_sf(df_merged_sum, coords = c("lng", "lat"), crs = 4326)
pumpen_sf <- st_transform(pumpen_mit_bezirk_ok, crs = 4326)
# 4. In metrisches CRS transformieren (z.B. UTM Zone 33N für Berlin)
df_merged_sum_sf_proj <- st_transform(df_merged_sum_sf, crs = 32633)
pumpen_sf_proj <- st_transform(pumpen_sf, crs = 32633)
# 5. Berechnung der Distanz zur nächsten Pumpe (nur die nächste)
nearest_index <- st_nn(df_merged_sum_sf_proj, pumpen_sf_proj, k = 1, returnDist = TRUE)
# 6. Extrahiere minimale Distanzen (in Meter)
min_dist <- sapply(nearest_index$dist, function(x) x[1])
# 7. Zurück ins ursprüngliche DataFrame
df_merged_sum$distanz_zur_pumpe_m <- as.numeric(min_dist)
write.csv2(df_merged_sum, "data/df_merged_sum_mit_distanzen_gesamter_Baumbestand_nur_Pumpen_ok.csv", row.names = FALSE, fileEncoding = "UTF-8")
Anzahl Pumpen im 100-Meter-Umkreis berechnen#
Zielsetzung Ein Maß für die Zugänglichkeit zu Wasser ist die Anzahl der Pumpen in unmittelbarer Nähe eines Baumes.
Vorgehen
Um jeden Baum wird ein 100-Meter-Kreis (Buffer) gezogen.
Es wird gezählt, wie viele funktionierende Pumpen sich in diesem Kreis befinden.
Das Ergebnis wird im Baumdatensatz gespeichert.
Code Erklärung:
pumpen_mit_bezirk <- st_read("data/pumpen_mit_bezirk_minimal.geojson")
df_merged_sum <- read.csv("data/df_merged_gesamter_baumbestand_sum1.csv", ...)
Es werden zwei Dateien geladen: - Pumpen mit Positionsdaten und Bezirkszuordnung. - Bäume mit Infos wie Standort, Gießmengen, Gießhäufigkeit usw.
2. Nur funktionierende Pumpen auswählen
pumpen_mit_bezirk_ok <- pumpen_mit_bezirk %>%
filter(pump.status == "ok")
Nur Pumpen, die als “ok” (funktionsfähig) markiert sind, werden berücksichtigt.
Defekte Pumpen werden ignoriert, weil sie zum Gießen sowieso nicht genutzt werden können.
3. Dezimalpunkt bei Koordinaten korrigieren
df_merged_sum <- df_merged_sum %>%
mutate(
lat = as.numeric(str_replace(lat, ",", ".")),
lng = as.numeric(str_replace(lng, ",", "."))
) %>%
drop_na(lat, lng)
In deutschen Datensätzen wird oft ein Komma statt Punkt für Dezimalzahlen verwendet.
Das wird hier korrigiert, damit die Koordinaten richtig als Zahlen erkannt werden.
Danach werden alle Zeilen ohne gültige Koordinaten entfernt.
4. Umwandlung in “sf”-Objekte für Raumbezug
df_merged_sum_sf <- st_as_sf(df_merged_sum, coords = c("lng", "lat"), crs = 4326)
pumpen_sf <- st_transform(pumpen_mit_bezirk_ok, crs = 4326)
Die Bäume werden zu “räumlichen Objekten” gemacht (sog. sf-Objekte), damit man mit ihnen geografisch rechnen kann.
crs = 4326
steht für das bekannte WGS84-System – das ist gut für Karten, aber nicht für Entfernungen.
5. Umrechnung ins metrische System (UTM)
df_merged_sum_sf_proj <- st_transform(df_merged_sum_sf, crs = 32633)
pumpen_sf_proj <- st_transform(pumpen_sf, crs = 32633)
Jetzt wird das Koordinatensystem umgewandelt in UTM Zone 33N (
crs = 32633
), das für Berlin geeignet ist.Nur so kann man exakt in Metern rechnen (z. B. 247 m zur nächsten Pumpe).`
6. Entfernung zur nächsten Pumpe berechnen
nearest_index <- st_nn(df_merged_sum_sf_proj, pumpen_sf_proj, k = 1, returnDist = TRUE)
st_nn()
ist eine Funktion aus dem Paketnngeo
.Für jeden Baum wird die nächstgelegene Pumpe gefunden.
k = 1
bedeutet: Es wird nur die nächste Pumpe gesucht.returnDist = TRUE
liefert die tatsächliche Entfernung (nicht nur die Position).
7. Entfernungen extrahieren
min_dist <- sapply(nearest_index$dist, function(x) x[1])
Es wird eine Liste von Distanzen erzeugt.
Jede Zahl gibt an, wie weit ein bestimmter Baum von seiner nächsten Pumpe entfernt ist (in Metern).
8. Distanz im Baum-Datensatz speichern
df_merged_sum$distanz_zur_pumpe_m <- as.numeric(min_dist)
Die berechnete Entfernung wird als neue Spalte in die Baumtabelle geschrieben.
Jetzt hat jeder Baum einen Messwert wie z. B. 184.72 Meter bis zur nächsten funktionierenden Pumpe.
9. Neue Datei speichern
write.csv2(df_merged_sum, "data/df_merged_sum_mit_distanzen_gesamter_Baumbestand_nur_Pumpen_ok.csv", ...)
Die Tabelle mit den zusätzlichen Entfernungsdaten wird gespeichert.
gesamter Code:
library(data.table)
library(sf)
library(dplyr)
library(stringr)
library(tidyr)
library(nngeo)
df_merged_sum_mit_distanzen <- read.csv("data/df_merged_sum_mit_distanzen_gesamter_Baumbestand_nur_Pumpen_ok.csv", sep = ";", stringsAsFactors = FALSE, fileEncoding = "UTF-8")
df_merged_sum_mit_distanzen$lat <- as.numeric(gsub(",", ".", df_merged_sum_mit_distanzen$lat))
df_merged_sum_mit_distanzen$lng <- as.numeric(gsub(",", ".", df_merged_sum_mit_distanzen$lng))
# 1. Rechnen
df_giess_sf <- st_as_sf(df_merged_sum_mit_distanzen, coords = c("lng", "lat"), crs = 4326)
pumpen_sf <- st_transform(pumpen_mit_bezirk_ok, crs = 4326)
df_giess_sf_m <- st_transform(df_giess_sf, 3857)
pumpen_sf_m <- st_transform(pumpen_sf, 3857)
giess_buffer <- st_buffer(df_giess_sf_m, dist = 100)
pumpen_im_umkreis <- lengths(st_intersects(giess_buffer, pumpen_sf_m))
df_merged_sum_mit_distanzen$pumpen_im_umkreis_100m <- pumpen_im_umkreis
# 2. Speichern
write.csv2(df_merged_sum_mit_distanzen, "data/df_merged_sum_mit_distanzen_mit_umkreis_baumbestand_nur_Pumpen_ok.csv", row.names = FALSE)
Integration der Lebensweltlich orientierten Räume (LOR)#
Zielsetzung Zur feinräumigen Analyse (unterhalb der Bezirksebene) sollen Bäume zusätzlich einem LOR-Gebiet zugeordnet werden.
Vorgehen
LOR-Grenzen werden über eine WFS-Schnittstelle geladen.
Jeder Baum wird demjenigen LOR zugeordnet, in dessen Fläche er liegt.
Ergebnis wird für spätere Analysen gespeichert.
Code Erklärung: 1. LOR-Grenzen aus dem Internet laden
lor_url <- "https://gdi.berlin.de/services/wfs/lor_2019?service=WFS&version=1.1.0&request=GetFeature&typeName=lor_2019:b_lor_bzr_2019"
lor <- st_read(lor_url) %>%
st_transform(4326)
Die LOR-Gebiete (als digitale Stadtkarte) werden direkt aus dem Berliner Geodatenportal geladen.
Danach wird das Koordinatensystem in WGS84 geändert (damit es zu den Baumdaten passt).
Was ist WGS84? Ein weltweites Koordinatensystem – es nutzt Längen- und Breitengrade (wie in Google Maps).
2. Baumdaten einlesen
df_merged <- read_csv2("data/df_merged_sum_mit_distanzen_mit_umkreis_baumbestand_nur_Pumpen_ok.csv", ...)
Hier wird die Tabelle mit allen Bäumen geladen, inklusive Position, Gießdaten, Abstand zur Pumpe usw.
3. Koordinaten bereinigen
mutate(lng = ..., lat = ...) %>%
filter(!is.na(lng) & !is.na(lat))
Die Koordinaten (Länge, Breite) werden von Text in Zahlen umgewandelt.
Gleichzeitig werden fehlerhafte Zeilen (z. B. fehlende Koordinaten) aussortiert.
4. Baumdaten als Geo-Daten vorbereiten
trees_sf <- st_as_sf(df_merged, coords = c("lng", "lat"), crs = 4326)
Aus der Tabelle wird ein sogenanntes sf-Objekt gemacht – also ein „räumlich intelligentes“ Datenobjekt.
Jeder Baum hat jetzt eine echte Position auf der Karte.
5. Räumlicher Join: Baum ↔ LOR
trees_with_lor <- st_join(trees_sf, lor, join = st_within)
Für jeden Baum wird geschaut: „In welchem LOR-Gebiet liegt dieser Punkt?“
Das passende LOR-Feld (z. B. bzr_id) wird in die Baumtabelle übernommen.
6. Bäume ohne Zuordnung entfernen
filter(!is.na(bzr_id))
Falls ein Baum außerhalb der LOR-Grenzen liegt (z. B. durch falsche Koordinaten), wird er ausgefiltert.
7. Ergebnis speichern (GeoJSON + CSV)
st_write(trees_with_lor, "output.geojson")
write.csv2(trees_csv, "output.csv")
Das Ergebnis wird gespeichert: - GeoJSON für Kartenanwendungen. - CSV für Auswertungen in Excel, R oder Shiny.
Zusätzlich werden aus der Geometrie wieder Breiten- und Längengradspalten erzeugt, bevor man es als CSV speichert.
8. Fehlerbehandlung mit tryCatch
tryCatch({ ... }, error = function(e) { ... })
Sollte beim Einlesen oder Verarbeiten ein Fehler auftreten (z. B. wegen Serverproblemen oder falscher Daten), wird eine Fehlermeldung mit Zusatzinfos ausgegeben, statt dass der ganze Prozess abstürzt.
gesamter Code:
library(sf)
library(dplyr)
library(readr)
# st_layers("https://gdi.berlin.de/services/wfs/lor_2019?REQUEST=GetCapabilities&SERVICE=wfs")
# 1. LOR-Daten korrekt einlesen
lor_url <- "https://gdi.berlin.de/services/wfs/lor_2019?service=WFS&version=1.1.0&request=GetFeature&typeName=lor_2019:b_lor_bzr_2019"
lor <- st_read(lor_url) %>%
st_transform(4326) # Zu WGS84 transformieren
# 2. Baumdaten einlesen
# df_merged <- read_csv2("data/df_merged_final.csv",
# locale = locale(decimal_mark = ",", grouping_mark = ".", encoding = "UTF-8"))
# df_merged <- read_csv2("data/df_merged_sum_mit_distanzen_gesamter_Baumbestand_nur_Pumpen_ok.csv",
# locale = locale(decimal_mark = ",", grouping_mark = ".", encoding = "UTF-8"))
df_merged <- read_csv2("data/df_merged_sum_mit_distanzen_mit_umkreis_baumbestand_nur_Pumpen_ok.csv",
locale = locale(decimal_mark = ",", grouping_mark = ".", encoding = "UTF-8"))
# 3. Koordinaten bereinigen und konvertieren
df_merged <- df_merged %>%
mutate(
lng = as.numeric(gsub(",", ".", lng)),
lat = as.numeric(gsub(",", ".", lat))
) %>%
filter(!is.na(lng) & !is.na(lat))
# 4. In sf-Objekt umwandeln
trees_sf <- st_as_sf(df_merged, coords = c("lng", "lat"), crs = 4326)
# 5. Räumlichen Join durchführen (mit Fehlerbehandlung)
tryCatch({
trees_with_lor <- st_join(trees_sf, lor, join = st_within)
# 6. Nicht zugeordnete Bäume entfernen
trees_with_lor <- trees_with_lor %>%
filter(!is.na(bzr_id))
# 7. Ergebnis speichern
# st_write(trees_with_lor, "data/trees_with_lor.geojson", driver = "GeoJSON")
#st_write(trees_with_lor, "data/df_merged_sum_mit_distanzen_gesamter_Baumbestand_nur_Pumpen_ok_lor.geojson", driver = "GeoJSON")
st_write(trees_with_lor, "data/df_merged_sum_mit_distanzen_mit_umkreis_gesamter_Baumbestand_nur_Pumpen_ok_lor.geojson", driver = "GeoJSON")
# Alternativ als CSV
trees_csv <- trees_with_lor %>%
mutate(
lng = st_coordinates(.)[,1],
lat = st_coordinates(.)[,2]
) %>%
st_drop_geometry()
#write_csv2(trees_csv, "data/trees_with_lor.csv")
# write.csv2(trees_csv, "data/df_merged_sum_mit_distanzen_gesamter_Baumbestand_nur_Pumpen_ok_lor.csv")
write.csv2(trees_csv, "data/df_merged_sum_mit_distanzen_mit_umkreis_gesamter_Baumbestand_nur_Pumpen_ok_lor.csv")
message("Verarbeitung erfolgreich abgeschlossen!")
}, error = function(e) {
message("Fehler bei der Verarbeitung: ", e$message)
# Debug-Informationen
message("LOR CRS: ", st_crs(lor)$input)
message("Bäume CRS: ", st_crs(trees_sf)$input)
message("Anzahl LOR-Features: ", nrow(lor))
message("Anzahl Baum-Features: ", nrow(trees_sf))
})
<<<<<<< HEAD
LOR zuordnung zum Baumbestand#
=======
LOR Zuordnung zum Baumbestand#
f92d1630a2be26834024a436cd01c5350db50f3d
Zielsetzung
Ziel ist es, die Baumstandorte den entsprechenden LOR-Gebieten zuzuordnen, um auf dieser feinräumigen Ebene die Gesamtbewässerungsmenge je Gebiet zu analysieren und umsomit spezifische Filterung zu ermöglichen.
Vorgehen
Einlesen der Baumdaten mit Geokoordinaten.
Umwandlung der Daten in ein Geo-Datenobjekt (sf).
Einlesen und Vorbereitung der LOR-Geometrien über eine WFS-Schnittstelle.
Räumliche Zuordnung (Join) der Baumdaten zu den LOR-Gebieten.
Aggregation der Bewässerungsmengen je LOR.
Rückführung der Summendaten in ein vollständiges sf-Objekt mit Geometrie.
Speicherung des Endergebnisses im GeoJSON-Format.
Code-Erklärung 1. Laden der benötigten Pakete
library(dplyr)
library(sf)
library(data.table)
dplyr
: Für effiziente Datenmanipulation.sf
: Für die Arbeit mit räumlichen (Geo-)Daten.data.table
: Für schnelles Einlesen großer CSV-Dateien.
2. Einlesen der Baumdaten
df_merged <- fread("data/df_merged_final.csv", sep = ";", encoding = "UTF-8")
Die CSV-Datei enthält Baumdaten inklusive Längen- und Breitengrad sowie Bewässerungsmenge.
3. Umwandlung in ein sf-Objekt
df_merged <- st_as_sf(df_merged, coords = c("lng", "lat"), crs = 4326)
Die Koordinaten-Spalten lng und lat werden in ein Punkt-Objekt überführt.
Das Koordinatensystem WGS84 (EPSG:4326) wird festgelegt, um Kompatibilität mit anderen Geodaten zu gewährleisten.
4. Einlesen der LOR-Grenzen
lor_url <- "https://gdi.berlin.de/services/wfs/lor_2019?service=WFS&version=1.1.0&request=GetFeature&typeName=lor_2019:b_lor_bzr_2019"
lor <- st_read(lor_url) %>%
select(bzr_id, bzr_name, geom) %>%
st_simplify(preserveTopology = TRUE, dTolerance = 0.001) %>%
st_transform(4326)
Die LOR-Gebiete werden über die WFS-Schnittstelle der Berliner Geodateninfrastruktur abgerufen.
Es werden nur relevante Spalten ausgewählt (bzr_id, bzr_name, geom).
Mit st_simplify() werden die Geometrien vereinfacht, um die Datenmenge zu reduzieren und damit die Performance bei der Verarbeitung und Visualisierung zu verbessern. Die Topologie bleibt dabei erhalten.
Das Koordinatensystem wird ebenfalls auf WGS84 eingestellt.
5. Räumlicher Join: Baum ↔ LOR
df_merged_mit_lor <- st_join(df_merged, lor[, c("bzr_id", "bzr_name")], left = TRUE)
Jeder Baum wird dem LOR-Gebiet zugeordnet, in dem er räumlich liegt (Punkt-in-Polygon-Zuordnung).
Die entsprechenden LOR-Informationen (bzr_id, bzr_name) werden übernommen.
6. Aggregation: Summe der Bewässerungsmengen pro LOR
df_merged_mit_lor_sum <- df_merged_mit_lor %>%
group_by(bzr_id, bzr_name) %>%
summarise(gesamt_bewaesserung_lor = sum(bewaesserungsmenge_in_liter, na.rm = TRUE)) %>%
ungroup()
Die Bewässerungsmenge wird für jedes LOR summiert.
Fehlende Werte (NA) werden bei der Summenbildung ignoriert.
7. Verknüpfung mit LOR-Geometrie
df_merged_sum_with_geom <- lor %>%
left_join(st_drop_geometry(df_merged_mit_lor_sum), by = "bzr_id")
Die berechneten Summen werden den ursprünglichen LOR-Geometrien wieder hinzugefügt.
Die Geometrie wird dabei aus dem lor-Objekt übernommen.
8. Export des Ergebnisses
st_write(df_merged_sum_with_geom, "data/df_merged_mit_lor_und_sum.geojson", driver = "GEOJSON", delete_dsn = TRUE)
Das finale Objekt wird im GeoJSON-Format gespeichert
gesamter Code:
library(dplyr)
library(sf)
library(data.table)
# Daten einlesen
df_merged <- fread("data/df_merged_final.csv", sep = ";", encoding = "UTF-8")
# Falls df_merged keine sf ist
df_merged <- st_as_sf(df_merged, coords = c("lng", "lat"), crs = 4326)
# LOR-Geometrien laden
lor_url <- "https://gdi.berlin.de/services/wfs/lor_2019?service=WFS&version=1.1.0&request=GetFeature&typeName=lor_2019:b_lor_bzr_2019"
lor <- st_read(lor_url) %>%
select(bzr_id, bzr_name, geom) %>%
st_simplify(preserveTopology = TRUE, dTolerance = 0.001) %>%
st_transform(4326)
# Join: Punkte mit LOR verknüpfen
df_merged_mit_lor <- st_join(df_merged, lor[, c("bzr_id", "bzr_name")], left = TRUE)
# Summe je LOR berechnen
df_merged_mit_lor_sum <- df_merged_mit_lor %>%
group_by(bzr_id, bzr_name) %>%
summarise(gesamt_bewaesserung_lor = sum(bewaesserungsmenge_in_liter, na.rm = TRUE)) %>%
ungroup()
# Geometrien hinzufügen
df_merged_sum_with_geom <- lor %>%
left_join(st_drop_geometry(df_merged_mit_lor_sum), by = "bzr_id")
st_write(df_merged_sum_with_geom, "data/df_merged_mit_lor_und_sum.geojson", driver = "GEOJSON", delete_dsn = TRUE)
Integration von Pumpenstandorten in Lebensweltlich orientierte Räume (LOR)#
Zielsetzung Die Zuordnung von Wasserpumpen zu den entsprechenden LOR-Gebieten ermöglicht räumlich differenzierte Analysen der Infrastruktur auf unterbezirklicher Ebene (z. B. für Planungszwecke oder Gießroutenoptimierung).
Vorgehen
Einlesen der Pumpenstandorte als Geo-Daten.
Filtern der aktiven („ok“) Pumpen.
Laden und Vereinfachen der LOR-Grenzen über eine WFS-Schnittstelle.
Transformation beider Datensätze in ein einheitliches Koordinatensystem (WGS84).
Räumliche Verknüpfung der Pumpen mit den jeweiligen LOR-Gebieten.
Speicherung des Endergebnisses im GeoJSON-Format.
Code-Erklärung 1. Laden der benötigten Pakete
library(dplyr) # Für Datenmanipulation (z. B. filter, select, %>%)
library(sf) # Für das Arbeiten mit Geodaten (Einlesen, räumlicher Join, Transformation)
library(data.table)
2. Einlesen der Pumpendaten
pumpen <- st_read("data/pumpen_mit_bezirk_minimal.geojson")
Die Datei enthält Trinkwasserpumpen als Punkt-Geometrien mit weiteren Attributen, u. a. Status und Bezirk.
Es handelt sich um ein sf-Objekt mit räumlicher Referenz.
3. Filtern funktionsfähiger Pumpen
pumpen <- pumpen %>%
filter(pump.status == "ok")
Nur funktionstüchtige Pumpen mit dem Status „ok“ werden für die weitere Analyse berücksichtigt.
4. Einlesen und Vereinfachen der LOR-Gebiete
lor <- st_read(lor_url) %>%
select(bzr_id, bzr_name, geom) %>%
st_simplify(preserveTopology = TRUE, dTolerance = 0.001) %>%
st_transform(4326)
Die LOR-Gebiete werden direkt über die WFS-Schnittstelle des Berliner Geodatenportals geladen.
Es werden nur relevante Felder (bzr_id, bzr_name) übernommen.
Mit st_simplify() werden die Geometrien vereinfacht, um die Verarbeitungs- und Darstellungsleistung zu optimieren – besonders bei Webanwendungen ein wichtiger Faktor.
Die Koordinaten werden auf WGS84 (EPSG:4326) transformiert, um mit den Pumpendaten kompatibel zu sein.
5. Harmonisierung des Koordinatensystems
pumpen <- st_transform(pumpen, crs = 4326)
Sicherstellung, dass beide Datensätze im selben Koordinatensystem (WGS84) vorliegen – Voraussetzung für räumliche Operationen wie Joins
6. Räumliche Verknüpfung Pumpen ↔ LOR
pumpen_mit_lor <- st_join(pumpen, lor[, c("bzr_name", "bzr_id")], left = TRUE)
Jede Pumpe wird dem LOR-Gebiet zugewiesen, in dem sie sich geografisch befindet.
Die Felder
bzr_name
undbzr_id
werden den Pumpendaten hinzugefügt.
7. Speichern der Ergebnisse
st_write(pumpen_mit_lor, "data/pumpen_mit_lor.geojson", driver = "GEOJSON", delete_dsn = TRUE)
Das Ergebnis wird als GeoJSON-Datei gespeichert
Die Option delete_dsn = TRUE überschreibt bestehende Dateien automatisch.
gesamter Code:
library(dplyr) # Für Datenmanipulation (z. B. filter, select, %>%)
library(sf) # Für das Arbeiten mit Geodaten (Einlesen, räumlicher Join, Transformation)
library(data.table)
pumpen <- st_read("data/pumpen_mit_bezirk_minimal.geojson")
lor_url <- "https://gdi.berlin.de/services/wfs/lor_2019?service=WFS&version=1.1.0&request=GetFeature&typeName=lor_2019:b_lor_bzr_2019"
pumpen <- pumpen %>%
filter(pump.status == "ok")
lor <- st_read(lor_url) %>%
select(bzr_id, bzr_name, geom) %>%
st_simplify(preserveTopology = TRUE, dTolerance = 0.001) %>%
st_transform(4326) # Zu WGS84 transformieren
pumpen <- st_transform(pumpen, crs = 4326)
pumpen_mit_lor <- st_join(pumpen, lor[, c("bzr_name", "bzr_id")], left = TRUE)
st_write(pumpen_mit_lor, "data/pumpen_mit_lor.geojson", driver = "GEOJSON", delete_dsn = TRUE)