STA-Applikation - Technische Dokumentation
Die Applikation besteht aus einem Server (numis-hzn-cld-nbg1,angemietet bei Hetzner, Standort RZ Nürnberg). Der Server wird per Configuration Management auf Ansible-Basis konfiguriert. Die Systemaktualisierung erfolgt laufend. Ein Reboot des Servers erfolgt nur, wenn dies z.B. nach einem Kernel Update erforderlich ist.
Komponenten
Auf dem Server sind folgende Komponenten installiert:
FROST-Server
Der FROST-Server v.2.3.2. (https://github.com/FraunhoferIOSB/FROST-Server), eine Server-Implementierung der OGC SensorThings API (https://www.ogc.org/standards/sensorthings) (API-Version 1.1).
Zugriff über HTTP
- https://sta.niedersachsen.de/v1.1
API des FROST-Server. Lesende Zugriffe sind ohne Anmeldung möglich. Schreibende Zugriffe nur bei vorheriger Anmeldung möglich.
- https://sta.niedersachsen.de/FROST-Server/
Startseite des FROST Servers. Der Zugang ist über BASIC Authentication abgesichert.
- https://sta.niedersachsen.de/grafana/
Grafana Instanz für die Visualisierung von Daten aus dem STA Server. Der Zugang ist über BASIC Authentication abgesichert.
- https://sta.niedersachsen.de/pgadmin/
Datenbankverwaltungstool (PgAdmin 4) für die PostGreSQL Datenbank. Der Zugang ist über BASIC Authentication und ein internes Loginportal abgesichert.
Dokumentation der SensorThings API, Version 1.1: https://docs.ogc.org/is/18-088/18-088.html
Die Parametrisierung der einzelnen Endpoints ist hier dokumentiert: https://github.com/FraunhoferIOSB/FROST-Server/wiki/Features
Beispiele für Abfragen finden sich hier: https://github.com/FraunhoferIOSB/FROST-Server/wiki/Example-queries
Basic Authentication
Seit dem Update auf FROST-Server v1.14.1 und der Umstellung auf die STA-API v1.1 wird die Basic Authentication (https://fraunhoferiosb.github.io/FROST-Server/settings/auth.html) des FROST-Servers eingesetzt.
Nach Aktivierung der Basic Authentication in der docker-compose.yml werden in der Datenbank des FROST-Servers die Tabellen USERS und USER_ROLES erstellt. Hier wurden die User „read“ und „write“ gelöscht, sowie der User „admin“ in „sensorthings“ umbenannt und das Datenbank-Passwort für ihn gesetzt. Nur der User „sensorthings“ darf Änderungen an der Datenbank durchführen. Bei anonymem Zugriff sind ausschließlich Lesevorgänge möglich.
MQTT
Der Message Queuing Telemetry Transport (MQTT) wurde aktiviert. Hiermit ist auch das Abonnieren von „Topics“ (wie z.B. ‚v1.1/Datastreams(3)/Observations‘) möglich. Dazu ist wie beim lesenden Zugriff über HTTP keine Authentifizierung mehr notwendig. Hierfür wurde „auth_allowAnonymousRead=true“ in der docker-compose.yml gesetzt. Ein schreibender Zugriff ist über MQTT nur mit Benutzernamen und Passwort möglich.
Der MQTT-Dienst ist unter „mqtt://sta.niedersachsen.de:1883“ zu erreichen.
Apache Webserver
Der Apache Webserver dient als Proxy für die installierten Komponenten. Der Zugriffsschutz ist hier implementiert.
PostGreSQL
Datenbank für die Zeitreihendaten. (Es wird Version 16-3.4 eingesetzt.)
PgAdmin 4
Datenbankverwaltungstool für PostGeSQL-Datenbank. (Es wird Version 8.9 eingesetzt.)
Grafana
Plattformübergreifende Webanwendung für Analyse und interaktive Visualisierung. (Es wird Version 9.5.20 eingesetzt.)
Data Importer
Importer für LÜN-, NLWKN-, Grundwasser- und ODL-Daten, geschrieben in Kotlin. Der Importer ist auf dem Server sta.niedersachsen.de per docker installiert.
Messnetze
Luftüberwachung Niedersachsen (LÜN)
Der Import der LÜN-Daten basiert auf der Datenquelle https://numis.niedersachsen.de/data/luenm.json und wird stündlich durch einen Webhook ausgelöst.
Folgende Abbildung wird vorgenommen:
STA Property |
JSON Data |
|---|---|
Thing.name |
‚luen_‘ + stationen->kennung |
Thing.description |
stationen->name |
Thing.properties.id |
stationen->kennung |
Thing.properties.eoi |
stationen->EOI-kennung |
Thing.properties.address.street |
stationen->strasse |
Thing.properties.address.city |
stationen->ort |
Thing.properties.measuring_network.name |
Lufthygienisches Überwachungssystem Niedersachsen (LÜN) |
Thing.properties.measuring_network.id |
NI_LUEN |
Thing.properties.urls.url |
[0] https://numis.niedersachsen.de/messwertsuche#/?networks=luen, [1] https://www.umwelt.niedersachsen.de/startseite/themen/luftqualitat/lufthygienische_uberwachung_niedersachsen/ |
Thing.properties.urls.title |
[0] Messdaten Visualisierung im Nds. Umweltportal, [1] Weiterführende Informationen |
Thing.properties.urls.type |
[0] VIEW, [1] INFORMATION |
Thing.properties.operator.name |
Lufthygienisches Überwachungssystem Niedersachsen (LÜN) |
Thing.properties.operator.url |
|
Thing.properties.extras.gebiet |
stationen->gebiet |
Thing.properties.extras.art |
stationen->art |
Thing.properties.extras.reprasentativitaet |
stationen->repräsentativitaet |
Location.name, Location.description |
stationen->name[, stationen->ort][, stationen->strasse] |
Location.encodingType |
application/vnd.geo+json |
Location.location |
GeoJSON Point: {type: Feature, geometry: {type: Point, coordinates: [stationen->koordinaten->laenge, stationen->koordinaten->breite]}} |
ObservedProperty.name |
komponenten->kennung |
ObservedProperty.description |
komponenten->name |
ObservedProperty.definition |
|
ObservedProperty.properties.unit |
komponenten->einheit |
Sensor.name |
komponenten->kennung |
Sensor.description |
komponenten->name[ [komponenten->einheit]] |
Sensor.encodingType |
text/html |
Sensor.metadata |
|
Datastream.name, Datastream.description |
messwerte->kennung / messwerte->messstellen->kennung |
Datastream.unitOfMeasurement |
instanceof UnitOfMeasurement |
Datastream.unitOfMeasurement.name |
komponenten->kennung |
Datastream.unitOfMeasurement.symbol |
komponenten->einheit |
Datastream.unitOfMeasurement.definition |
|
Datastream.observationType |
http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement |
Datastream.properties.temporal_resolution |
z.B. P1D (1 Tag), P1H (1 Stunde), P0.5Y (6 Monate), P1Y (1 Jahr), PT8H (8 Stunden) |
Datastream.properties.type |
z.B. MEAN (Mittelwert), MIN (Minimum), MAX (Maximum), SUM (Summe), MEAN_M24A (gleitender 24h-Mittelwert), MEAN_M8A (gleitender 8h-Mittelwert) |
Datastream.properties.explanation |
(erkärender Text zum jeweiligen Datastream) |
Datastream.thing |
instanceof Thing |
Datastream.sensor |
instanceof Sensor |
Datastream.observedProperty |
instanceof ObservedProperty |
Observation.resultTime |
Zeitpunkt berechnet aus ->datum, ->zeit, ->zeitzone und Index des Messwertes |
Observation.datastream |
instanceof Datastream |
Observation.result |
Werte aus Array messwerte->messstellen->verlauf | wenn Wert mit ‚<‘ startet dann 0.0 | wenn Wert mit ‚*‘ startet, dann ‚n/a‘ | wenn Wert keine Zahl ist, dann ‚n/a‘ |
Observation.phenomenonTime |
instanceof TimeObject mit Zeitpunkt berechnet aus ->datum, ->zeit, ->zeitzone und Index des Messwertes |
Qualitätsindices der Luftstoffe
Qualitätsindices werden anders behandelt. Wenn sie vorhanden aus der Messstationen sind, werden Datastreams dafür erzeugt. Die Folgenden sind Sonderheiten von diesen Datastreams im Vergleich mit dem Standard-Datastream:
STA Property |
JSON Data |
Beispiel |
|---|---|---|
Datastreams.name |
stationen/kennung/QI* |
HRVS/NO2/QI |
unitOfMeasurement.name |
Kurzzeit-Luftqualitätsindex (LQI)* |
|
unitOfMeasurement.symbol |
Index [1-6]* |
|
ObservedProperty.description |
einheit, Kurzzeit-Luftqualitätsindex (LQI)* |
Stickstoffdioxid [µg/m³], Kurzzeit-Luftqualitätsindex (LQI) |
ObservedProperty.name |
kennung/QI* |
NO2/QI |
Observations.result |
qualitätsindex |
3.218 |
* statisch
Webservices von NLWKN-PegelOnline
Der Import der NLWKN-Daten basiert auf dem Webservices:
Über diese URL werden alle Stationen und deren zugehörigen (Mess-)Parametern entnommen.
Anhand den Stationen und den zugehörigen Parametern wird der Webservice erneut angefragt um die Messdaten für die Station-Parameter-Kombinaton zu erhalten.
Hierbei wird folgende URL als Template verwendet:
- https://bis.azure-api.net/PegelonlinePublic/REST/station/{STA_ID}/datenspuren/parameter/{PAT_ID}/tage/{tage}?key=9dc05f4e3b4a43a9988d747825b39f43
{STA_ID} eindeutige ID der Station
{PAT_ID} eindeutige ID der Station
{tage} Anzahl der zurückliegenden Tage ab vom Aufruf der URL: -1: Der Import eines Tages erfolgt stündlich, um xx:15; -30: Der Import von 30 Tagen erfolgt wöchentlich, samstags um 3:50.
Folgende Abbildung wird vorgenommen:
- spurenResult->Parameter->Datenspuren->Pegestaende->DatumUTC“
„Observation.datastream“, „instanceof Datastream“ „Observation.result“, „getPegelDatenspurenResult->Parameter->Datenspuren->Pegestaende->Wert“ „Observation.phenomenonTime“, „getPegelDatenspurenResult->Parameter->Datenspuren->Pegestaende->DatumUTC“
Grundwasserdaten Niedersachsen
Der Import der Grundwasserdaten Niedersachsen basiert auf dem Webservice:
Über diese URL werden alle Stationen abgefragt.
Mit Hilfe dieser Stationen wird der Webservice erneut angefragt um die Messdaten für die einzelnen Stationen zu erhalten. Hierzu wird die Stations-ID („STA_ID“) aus der obigen Anfrage sowie als PAT_ID die ID für den Grundwasserstand (563) verwendet:
https://bis.azure-api.net/GrundwasserstandonlinePublic/REST/station/{STA_ID}/datenspuren/parameter/{PAT_ID}/tage/{tage}?key=9dc05f4e3b4a43a9988d747825b39f43
Der Import erfolgt täglich, um 6:38h.
Folgende Abbildung wird vorgenommen:
STA Property |
JSON Data |
|---|---|
Thing.name |
‚gw_‘ + {STA_ID} |
Thing.description |
{Ort}/{Name} |
Thing.properties.id |
{STA_ID} |
Thing.properties.measuring_network.id |
NI_GW |
Thing.properties.measuring_network.name |
Grundwassermessstellen Niedersachsen |
Thing.properties.address.city |
{Ort} |
Thing.properties.operator.name |
{Betreiber} |
Datastream.name |
THING.name + ‚_‘ + OBSERVED_PROPERTY.name + ‚_‘ + {properties.temporal_resolution} + ‚_‘ + {properties.type} |
Datastream.description |
THING.description + ‚: ‚ + {Stationsart_Text}/{Stationsart}: {WebDisplayName} |
Datastream.observationType |
„http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement“ |
Datastream.unitOfMeasurement |
instanceof UnitOfMeasurement |
Datastream.unitOfMeasurement.name |
{Parameter.ParameterName} |
Datastream.unitOfMeasurement.symbol |
{Parameter.ParameterEinheit} |
Datastream.unitOfMeasurement.definition |
|
Datastream.properties.origin |
datastream_name: WebDisplayName der Datenspur |
Datastream.properties.temporal_resolution |
Zuordnung des ersten Teils des WebDisplayNames zu den möglichen Werten: P1D | P1M | P0.5Y | P0.5Y | P1Y |
Datastream.properties.type |
Zuordnung des Webdisplaynames zu den möglichen Werten: MEAN | MIN | MAX | MIN.LONG_TERM | MAX.LONG_TERM | MEAN.LONG_TERM | Q85 | Q75 | Q25 | Q15 |
Datastream.thing |
instanceof Thing |
Datastream.sensor |
instanceof Sensor |
Datastream.observedProperty |
instanceof ObservedProperty |
Location.name |
THING.name |
Location.description |
THING.description |
Location.encodingType |
„application/vnd.geo+json“ |
Location.location |
GeoJSON Point: {type: Feature, geometry: {type: Point, coordinates: [{Longitude}, {Latitude}]}} |
ObservedProperty.name |
{Stationsart_Text} |
ObservedProperty.definition |
|
ObservedProperty.description |
{Parameter.Name} |
ObservedProperty.properties.unit |
{Parameter.Einheit} |
Sensor.name |
OBSERVED_PROPERTY.name |
Sensor.encodingType |
„text/html“ |
Sensor.description |
{Parameter.ParameterName} |
Sensor.metadata |
„n/a“ |
FeaturesOfInterest.name |
DATASTREAM.name |
FeaturesOfInterest.description |
DATASTREAM.description |
FeaturesOfInterest.encodingType |
„application/vnd.geo+json“ |
FeaturesOfInterest.feature |
{type: Point, coordinates: [{Longitude}, {Latitude}]} |
Ortsdosisleistung (ODL) des Bundesamts für Strahlenschutz
Der Import der ODL-Daten basiert auf dem Webservice:
Über diese URL werden alle Daten zu den Stationen entnommen.
Anhand der Stationen wird der Webservice erneut angefragt, um die Messwerte zu erhalten.
Hierbei wird folgende URL als Template verwendet:
- {station_id}
eindeutige Messstellenkennung
Der Import der ODL-Daten erfolgt stündlich, um xx:00.
Folgende Abbildung wird vorgenommen:
STA Property |
JSON Data |
|---|---|
Thing.name |
‚odl_‘ + Stations->properties.id |
Thing.description |
Stations->properties.name |
Thing.properties.measuring_network.id |
ODL |
Thing.properties.measuring_network.name |
Gamma-Ortsdosisleistung (ODL) des BfS |
Thing.properties.id |
Stations->properties.id |
Thing.properties.eoi |
Stations->properties.kenn |
Thing.properties.zip |
Stations->properties.plz |
Thing.properties.city |
Stations->properties.name |
Thing.properties.state |
site_status: 1 (in Betrieb) → OPERATIONAL, 2 (defekt) → NOT_OPERATIONAL, 3 (Testbetrieb) → TEST_OPERATION |
Thing.properties.urls.url |
[0] https://odlinfo.bfs.de/ODL/DE/themen/wo-stehen-die-sonden/karte/_documents/Messstelle.html?id=[kenn] |
Thing.properties.urls.title |
[0] Informationen zur Messstelle |
Thing.properties.urls.type |
[0] INFORMATION |
Thing.properties.operator.name |
Bundesamt für Strahlenschutz |
Thing.properties.operator.url |
|
Thing.properties.extra.kenn |
Stations->properties.kenn |
Location.name |
‚odl_‘ + Stations->properties.id |
Location.description |
Stations->properties.name |
Location.encodingType |
„application/vnd.geo+json“ |
Location.location |
GeoJSON Point: {type: Feature, geometry: {type: Point, coordinates: [Stations->geometry.coordinates[0], Stations->geometry.coordinates[1], Stations->properties.height_above_sea]}} |
ObservedProperty.name |
Stations->properties.nuclide |
ObservedProperty.definition |
„https://odlinfo.bfs.de/ODL/DE/themen/wie-wird-gemessen/interpretation/interpretation_node.html“ |
ObservedProperty.description |
Stations->properties.nuclide |
ObservedProperty.properties.unit |
Stations->properties.unit |
Sensor.name |
Stations->properties.nuclide |
Sensor.encodingType |
„text/html“ |
Sensor.description |
Stations->properties.nuclide [Stations->properties.unit] |
Sensor.metadata |
„n/a“ |
Datastream.name |
Stations->properties.nuclide/Stations->properties.id |
Datastream.description |
Stations->properties.nuclide/Stations->properties.name |
Datastream.observationType |
„http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement“ |
Datastream.unitOfMeasurement.name |
Stations->properties.nuclide |
Datastream.unitOfMeasurement.symbol |
Stations->properties.unit |
Datastream.unitOfMeasurement.definition |
|
Datastream.unitOfMeasurement |
instanceof UnitOfMeasurement |
Datastream.properties.temporal_resolution |
PT1H (1 Stunde) |
Datastream.properties.type |
SUM (Summe) |
Datastream.properties.origin |
Stations->properties.nuclide/Stations->properties.duration |
Datastream.thing |
instanceof Thing |
Datastream.sensor |
instanceof Sensor |
Datastream.observedProperty |
instanceof ObservedProperty |
Observation.resultTime |
Measurements->properties.end_measure |
Observation.datastream |
instanceof Datastream |
Observation.result |
Measurements->properties.value |
Observation.phenomenonTime |
Measurements->properties.start_measure/Measurements->properties.end_measure |
Observation.resultQuality |
Measurements->properties.validated |
Lufqualitätsindes (LQI) des Umweltbundesamtes
Der Import der LQI-Daten basiert auf dem Webservice:
https://www.umweltbundesamt.de/api/air_data/v3/stations/json?use=airquality&lang=de&date_from=“{DATE_FROM}“&date_to=“{DATE_TO}“ Hierbei werden alle Stationen für den Zeitraum von {DATE_FROM} (= heutiges Datum - 3 Tage) bis {DATE_TO} = (heutiges Datum) abgerufen.
(Schnittstellenbeschreibung siehe https://www.umweltbundesamt.de/daten/luft/luftdaten/doc)
Anhand der Stationen wird der Webservice erneut angefragt, um die Werte des LQI und der Schadstoffe aus denen er vestimmt wird abzurufen.
Hierbei wird folgende URL als Template verwendet:
https://www.umweltbundesamt.de/api/air_data/v3/airquality/json?&date_from={DATE_FROM}&date_to={DATE_TO}&time_from={HOUR_NOW}&time_to={HOUR_NOW}&station=<STATION_CODE>“ Hierbei müss zusätliche zum Datum auch die Zeit (hier die aktuelle Stunde {HOUR}) und der Stationscode ({STATION_CODE} aus der obigen Abfrage) gesetzt werden.
Der Import der LQI-Daten erfolgt stündlich, um xx:25.
Folgende Abbildung wird vorgenommen:
STA Property |
JSON Data |
|---|---|
Thing.name |
‚uba_‘ + [networkCode] + _ + station->code (z.B. uba_RP_DERP019) |
Thing.description |
station->name/station->city (z.B. Kaiserslautern-Rathausplatz/Kaiserslautern) |
Thing.properties.measuring_network.id |
UBA_LQI |
Thing.properties.measuring_network.name |
Umweltbundesamt Luftdaten [Bundesland] |
Thing.properties.id |
station->code (z.B. DERP019) |
Thing.properties.address.street |
station->street station station->street_number (z.B. Rathausplatz 1) |
Thing.properties.address.zip |
station->zip_code (z.B. 67659) |
Thing.properties.address.city |
station->city (z.B. Kaiserslautern) |
Thing.properties.address.country |
DE |
Thing.properties.state |
zur Zeit nicht genutzt: OPERATIONAL wenn ‚Active to‘ null ist. NOT_OPERATIONAL wenn ‚Active to‘ gesetzt ist (Bei Station DEBB067 steht hier allerdings ein Datum-aber die Station bekommt aktuelle Werte, deswegen wird auf diesen Wert verzichtet.) |
Thing.properties.operator.name |
Umweltbundesamt Luftdaten [Bundesland] |
Thing.properties.operator.url |
https://www.umweltbundesamt.de/daten/luft/luftdaten/luftqualitaet |
Thing.properties.extra.gebiet |
station->settingShortName (z.B städtisch) |
Thing.properties.extra.art |
station->typeName (z.B. Hintergrund) |
Location.name |
wie Thing.name |
Location.description |
wie Thing.description |
Location.encodingType |
„application/geo+json“ |
Location.location |
GeoJSON Point: {type: Feature, geometry: {type: Point, coordinates: [station->latitude, station->longitude]}} |
ObservedProperty.name |
z.B. LQI, LQI_PM10, LQI_O3, … |
ObservedProperty.definition |
|
ObservedProperty.description |
z.B. ‚Luftqualitätsindex (LQI)‘, ‚Feinstaub (PM₁₀)‘ |
ObservedProperty.properties.unit |
‚Index‘ oder ‚μg/m³‘ |
Sensor.name |
z.B: LQI_PM10 |
Sensor.encodingType |
„text/html“ |
Sensor.description |
z.B. Particulate matter PM₁₀ |
Sensor.metadata |
„n/a“ |
Datastream.name |
wie Thing.name + LQI oder LQI_PM10 usw. (z.B: uba_RP_DERP019_LQI) |
Datastream.description |
wie Thing.description + LQI oder LQI_PM10 usw. (z.B. Kaiserslautern-Rathausplatz/Kaiserslautern: LQI) |
Datastream.unitOfMeasurement.name |
LQI oder LQI_PM10 usw. |
Datastream.unitOfMeasurement.symbol |
(index) bei LQI, bei Schadstoffen: µg/m³ |
Datastream.unitOfMeasurement.definition |
|
Datastream.unitOfMeasurement |
instanceof UnitOfMeasurement |
Datastream.observationType |
„http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement“ |
Datastream.properties.temporal_resolution |
P1H (1 Stunde) |
Datastream.properties.type |
‚INDEX‘ oder ‚MEAN‘ |
Datastream.thing |
instanceof Thing |
Datastream.sensor |
instanceof Sensor |
Datastream.observedProperty |
instanceof ObservedProperty |
Observation.resultTime |
observation->endDate |
Observation.datastream |
instanceof Datastream |
Observation.result |
observation->value |
Observation.phenomenonTime |
observation->endDate |
Deployment
Alle Komponenten werden über docker (docker-compose) auf sta.niedersachsen.de deployt.
Löschen von Objekten
Objekte können mit Hilfe der HTTP-Methode DELETE gelöscht werden.
Wenn ein ‚Datastream‘ gelöscht wird, werden auch alle zugehörigen ‚Observations‘ gelöscht. Wenn ein ‚Thing‘ gelöscht wird, werden ebenso die zugehörigen ‚Datastreams‘ mit allen ‚Observations‘ gelöscht.
Das Löschen kann nur mit Authentifizierung erfolgen.
Beispiel mit curl:
curl -u {username}:{password} -X DELETE -H "Content-Type: application/json"
"https://sta.niedersachsen.de/v1.1/Things(30662)"