Kostengünstige Plattformen wie der Raspberry Pi ermöglichen es, geosensorische Messsysteme einzusetzen und individuell an spezifische Fragestellungen anzupassen. Im Bereich des öffentlichen Personennahverkehrs spielen solche Messungen eine wichtige Rolle, da sie Aufschluss über den Zustand von Fahrzeugen, Gleisanlagen und den Fahrkomfort geben können.
Im Rahmen dieses Projekts wird ein mobiles Messsystem entwickelt, das Vibrationen während der Fahrt mit Berliner S-Bahnen erfasst und räumlich verortet. Der Fokus liegt dabei auf dem Vergleich unterschiedlicher Fahrzeugtypen der Baureihen BR 480, BR 481 (modernisiert) sowie BR 483/484. Durch die Kombination von Sensor- und GPS-Daten sollen Unterschiede im Vibrationsverhalten analysiert, ausgewertet und visuell dargestellt werden.
Die Motivation für dieses Projekt liegt in der Anwendung geosensorischer Methoden sowie in der Verknüpfung von Hardware-, Software-, und der Datenverarbeitung. Durch den Einsatz eines Rasperry Pi in Kombination mit einem Beschleunigungssensor (dem U Blox XXXX) und einem GPS-Empfänger wird ein vollständiger Messworkflow nach dem EVAP Modell dokumentiert.
Ziel der Aufgabe ist es, ein funktionsfähiges Messsystem aufzubauen, das während S-Bahn-Fahrten Beschleunigungsdaten aufzeichnet und diese eindeutig mit geografischen Koordinaten und Zeitstempel verknüpft. Die erhobenen Daten sollen anschließend mit Python verarbeitet, analysiert und visualisiert werden. Darüber hinaus soll untersucht werden, inwiefern sich die verschiedenen Fahrzeugbaureihen hinsichtlich ihres Vibrationsverhaltens unterscheiden.
Ausgehend von der technischen Umsetzung und der Datenerhebung ergibt sich folgende zentrale Fragestellung:
Inwiefern unterscheiden sich die geosensorisch erfassten Vibrationsmuster der Berliner S-Bahn-Baureihen BR 480, BR 481 (modernisiert) sowie BR 483/484, und lassen sich diese Unterschiede räumlich und statistisch nachvollziehbar darstellen?
Das Messsystem basiert auf:
einem Rasperry Pi mit Speicherkarte: Der Raspberry Pi dient als zentrale Recheneinheit des Messsystems. Er steuert die angeschlossenen Sensoren, erfasst und speichert die Messdaten und ermöglicht deren Verarbeitung mithilfe von Python-Skripten. Aufgrund seiner kompakten Bauweise, der guten Unterstützung für serielle Schnittstellen sowie der hohen Flexibilität eignet sich der Raspberry Pi besonders für mobile geosensorische Anwendungen.
dem u-blox EVK-F9DR: Das u-blox EVK-F9DR ist ein Evaluations- und Entwicklungsboard zur hochpräzisen GNSS-Navigation mit integrierter Dead-Reckoning-Funktionalität. Es unterstützt den gleichzeitigen Empfang mehrerer GNSS-Systeme und ermöglicht eine zuverlässige Positionsbestimmung auch in GNSS-gestörten Umgebungen, wie z. B. in Tunneln oder dicht bebauten Stadtbereichen. Das Board stellt die Schnittstelle zwischen GNSS-/IMU-Sensorik und dem Raspberry Pi dar und erlaubt eine stabile Datenübertragung (Quelle: https://www.u-blox.com/en/product/evk-f9dr)
ZED-F9K-Modul: Das ZED-F9K-Modul ist das zentrale GNSS- und IMU-Modul innerhalb des EVK-F9DR. Es kombiniert satellitengestützte Positionsdaten mit Beschleunigungsdaten. Diese werden zur Erfassung von Vibrationen während der S-Bahn-Fahrten genutzt. Durch die Verknüpfung von Beschleunigungs- und Positionsdaten können die gemessenen Vibrationen räumlich eindeutig zugeordnet und für den Vergleich der verschiedenen Fahrzeugbaureihen ausgewertet werden.
Vor dem Feldeinsatz wurde der Raspberry Pi eingerichtet und über einen externen Bildschirm überprüft. Dabei wurde sichergestellt, dass die Stromversorgung über USB-C stabil ist und eine Internetverbindung besteht. Zusätzlich wurde ein kleiner Bildschirm am Raspberry Pi angeschlossen, um die Daten im Feld live kontrollieren zu können. Auf einem Windows-Rechner wurde die zugehörige u‑blox Software installiert, um einen Überblick über die verfügbaren Sensordaten zu erhalten.
Für die Datenerfassung wurden relevante Codes vom u‑blox System kopiert und in die Python-Umgebung übertragen. Im Feld wurde der Python-Code innerhalb einer Anaconda-Projektumgebung gestartet. Alle 0,2 Sekunden werden dabei die aktuellen GPS-Koordinaten, zwei Zeitstempel (Raspberry Pi und GPS-Modul) sowie die Beschleunigungswerte in drei Achsen erfasst und in einer nach Datum benannten Textdatei gespeichert.
Die gespeicherten Daten werden anschließend am Rechner manuell ausgewertet.
Für die Datenerhebung wurden zunächst zwei Verfahren geprüft: Entweder jede Baureihe einmal auf einer festen Strecke mit drei Stationen zu fahren oder jede Baureihe auf einer möglichst geraden Strecke über einen längeren Zeitraum zu messen. Da in Berlin keine Strecke existiert, auf der alle Baureihen verkehren, wurde die zweite Methode gewählt. Jede Baureihe wurde auf einer eigenen, möglichst geraden Strecke für etwa 10 Minuten gefahren, um Unterschiede durch Fahrweise, Gleisbeschaffenheit und andere Streckenparameter auszugleichen.
Grobe Fehler:
Verbindungsprobleme zwischen Raspberry Pi und EVK-F9DR (z. B. lose USB-Verbindung)
Ausfall der Stromversorgung während der Messung
Softwareabsturz oder falsches Starten des Python-Codes
Systematische Fehler:
Streckenvariabilität: Unterschiede in Gleisbeschaffenheit, Kurvenradien oder Weichen beeinflussen die Vibrationsdaten konstant auf bestimmten Streckenabschnitten
Fahrweise der Lokführer: konstante Unterschiede im Beschleunigungs- oder Bremsverhalten zwischen einzelnen Baureihen oder Fahrten
GNSS-Signalstörungen: Tunnel oder Unterführungen verursachen systematisch reduzierte Positionsgenauigkeit
Zufällige Fehler:
Umgebungsbedingte Vibrationen, z. B. Passagierbewegungen oder vorbeifahrende Züge
Kurzfristige Störungen in der Datenaufzeichnung (z. B. einzelne fehlerhafte Messpunkte)
Wetterabhängige Einflüsse, die nicht konstant auftreten (z. B. leichte Temperaturschwankungen, Regen)
(HIER ROHDATEN BINÄR ZEIGEN)
(Hier SCREENSHOT) 2026-01-02,11:47:37:753,10:47:36,13.435651902,52.509374338,8364.18,-0.400,-0.655,9.854,-0.679,-0.466,0.298 2026-01-02,11:47:37:959,10:47:36,13.435651902,52.509374338,8364.18,-0.382,-0.648,9.848,-0.679,-0.618,0.382 2026-01-02,11:47:38:157,10:47:36,13.435651902,52.509374338,8364.18,-0.378,-0.623,9.871,-0.595,-0.290,0.382 Die erfassten Daten liegen in einer strukturierten Textdatei vor und enthalten pro Messpunkt folgende Informationen:
date: Datum der Messung
timestamp: Zeitstempel des Raspberry Pi
gps_timestamp: Zeitstempel des GPS-Moduls
lon / lat: GPS-Koordinaten (Längengrad / Breitengrad)
accuracy: Genauigkeit der Positionsbestimmung
acc_x / acc_y / acc_z: Beschleunigungswerte in drei Achsen (m/s²)
gyro_x / gyro_y / gyro_z: Gyroskopwerte in drei Achsen (rad/s)
Alle Messwerte werden alle 0,2 Sekunden erfasst, wodurch eine hohe zeitliche Auflösung für die Analyse der Vibrationsmuster der S-Bahn-Fahrzeuge gegeben ist.
# bibliotheken laden
library(stringr)
library(sf)
## Linking to GEOS 3.13.0, GDAL 3.10.1, PROJ 9.5.1; sf_use_s2() is TRUE
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(zoo)
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(tmap)
library(lubridate)
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
library(ggplot2)
# arbeitsverzeichnis festlegen
setwd("C:/Users/sphil/Nextcloud/Geosensorik/Python/")
# csv einlesen
daten <- read.csv("2026-01-21_12-29-26_ubx_F9K_log.csv", stringsAsFactors = FALSE)
# neue spalte datetime erstellen - für spätere zuordnung der baureihen wichtig (datentyp muss stimmen)
daten <- daten |>
mutate(
datetime = ymd(date) +
hms(str_replace(timestamp, ":(?=[^:]+$)", "."))
)
# nach fahrzeiten zugeordnete baureihe
daten <- daten |>
mutate(
baureihe = case_when( # mehrstufige if-bedingung
datetime >= ymd_hms("2026-01-21 13:39:00") &
datetime <= ymd_hms("2026-01-21 14:19:45") ~ "BR481_mod",
#datetime >= ymd_hms("2026-01-21 15:41:00") &
# datetime <= ymd_hms("2026-01-21 15:49:30") ~ "BR481_mod",
datetime >= ymd_hms("2026-01-21 15:08:00") &
datetime <= ymd_hms("2026-01-21 15:37:00") ~ "BR483_484",
datetime >= ymd_hms("2026-01-21 16:09:00") &
datetime <= ymd_hms("2026-01-21 16:41:00") ~ "BR480",
TRUE ~ NA_character_ # wenn keine zeit zutrifft dann eintrag baureihe auf NA
)
) |>
filter(!is.na(baureihe)) # alle nicht relevanten zeilen (bauhreihe = NA) werden gelöscht
# sonst wird auswertung verfälscht - nur aufzeichnungen behalten bei optimalen messbedingungen (umsteigen ausschließen usw.)
# räumliches sf objekt erstellen
daten_sf <- st_as_sf(
daten,
coords = c("lon", "lat"),
crs = 4326,
remove = FALSE
)
# vibrationsberechnung
# acc_x, acc_y, acc_z sind die Beschleunigungen in den drei Raumrichtungen
# acc_res = sqrt(acc_x^2 + acc_y^2 + acc_z^2))
daten_sf <- daten_sf %>%
mutate(
acc_res = sqrt(acc_x^2 + acc_y^2 + acc_z^2)
) %>% # die gesamtbewegung berechnen
group_by(baureihe) %>% # sorteirt nach baureihe
mutate(
acc_dyn = abs(acc_res - mean(acc_res, na.rm = TRUE)), # vibration ohne die normale beschleunigung
acc_smooth = rollmean(acc_dyn, k = 20, fill = NA) # geglättete version
) %>%
ungroup() # für weitere berechnungen wieder ungruppieren
# statistischer vergleich tabelle
vibration_stats <- daten_sf %>%
st_drop_geometry() %>% # wird nicht gebraucht für den vergleich
group_by(baureihe) %>%
summarise(
mean_acc = mean(acc_dyn),
sd_acc = sd(acc_dyn),
rms_acc = sqrt(mean(acc_dyn^2)),
q95_acc = quantile(acc_dyn, 0.95)
)
print(vibration_stats)
## # A tibble: 3 × 5
## baureihe mean_acc sd_acc rms_acc q95_acc
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 BR480 0.246 0.344 0.422 0.913
## 2 BR481_mod 0.164 0.248 0.297 0.539
## 3 BR483_484 0.246 0.474 0.534 1.13
# boxplot mit acc_dyn für normalisierten vergleich
ggplot(daten_sf, aes(x = baureihe, y = acc_dyn, fill = baureihe)) +
geom_boxplot(outlier.alpha = 0.3) +
labs(
title = "Vergleich der Vibrationsintensität nach Baureihe",
x = "Baureihe",
y = "Dynamische Beschleunigung"
) +
theme_minimal()
# kartendarstellung
tmap_mode("plot")
## ℹ tmap mode set to "plot".
tm_shape(daten_sf) +
tm_dots(
col = "acc_dyn",
palette = "Reds",
size = 0.04,
title = "Vibration"
) +
tm_facets(by = "baureihe") +
tm_layout(
title = "Räumliche Verteilung der Vibrationen nach Baureihe",
legend.outside = TRUE
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_tm_dots()`: migrate the argument(s) related to the scale of the
## visual variable `fill` namely 'palette' (rename to 'values') to fill.scale =
## tm_scale(<HERE>).
## [v3->v4] `tm_dots()`: use 'fill' for the fill color of polygons/symbols
## (instead of 'col'), and 'col' for the outlines (instead of 'border.col').
## [tm_dots()] Argument `title` unknown.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.
##
## [plot mode] fit legend/component: Some legend items or map compoments do not
## fit well, and are therefore rescaled.
## ℹ Set the tmap option `component.autoscale = FALSE` to disable rescaling.
tmap_mode("view")
## ℹ tmap mode set to "view".
tm <- tm_shape(daten_sf) +
tm_dots(
col = "acc_dyn",
palette = "viridis",
size = 1,
alpha = 0.7,
popup.vars = c(
"Baureihe" = "baureihe",
"Vibration" = "acc_smooth"
),
title = "Vibrationsstärke"
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_tm_dots()`: migrate the argument(s) related to the scale of the
## visual variable `fill` namely 'palette' (rename to 'values') to fill.scale =
## tm_scale(<HERE>).[v3->v4] `tm_dots()`: use `fill_alpha` instead of `alpha`.[tm_dots()] Argument `title` unknown.
tm
## Registered S3 method overwritten by 'jsonify':
## method from
## print.json jsonlite