Die EU-Richtlinie 2010/40/EU verpflichtet jeden Mitgliedstaat, einen National Access Point (NAP) zu betreiben, der Echtzeit-Verkehrs-, Sicherheits- und multimodale Reiseinformationen in DATEX II veröffentlicht. Auf dem Papier heißt das: ein Standard, 27 Veröffentlicher, eine Integration.
In der Praxis liefert jeder NAP ein anderes DATEX-II-Profil über einen anderen Transport hinter einer anderen Authentifizierung in einer anderen Aktualisierungsfrequenz. „DATEX-II-konform" ist ein Häkchen, das jeder Mitgliedstaat anders setzt.
Genau das haben wir beim Aufbau von NAPSPAN erlebt — einer einheitlichen API über EU 27 + UK + EFTA. Hier ist die Architektur, die uns 30 inkompatible DATEX-II-Feeds wie einen einzigen behandeln lässt.
Die Spezifikation und die Realität
DATEX II ist ein ausgereifter europäischer Standard, gepflegt von datex2.eu und koordiniert durch NAPCORE. Jede delegierte Verordnung verweist darauf als verbindliches Austauschformat:
- (EU) 2022/670 — RTTI: Echtzeit-Ereignisse, Geschwindigkeiten, Straßenzustände
- (EU) Nr. 886/2013 — SRTI: kostenloser, offener Zugang zu Sicherheitsmeldungen
- (EU) Nr. 885/2013 — SSTP: sichere und gesicherte LKW-Parkplätze
- (EU) 2017/1926 — MMTIS: multimodale Reiseinformationen
- AFIR Artikel 20 — Echtzeit-Ladestatus von E-Ladepunkten
Die Spezifikation ist also eindeutig. Die Umsetzung ist alles andere als das.
Worin sich die NAPs tatsächlich unterscheiden
„DATEX-II-konform" verbirgt mindestens fünf Achsen der Divergenz:
1. Profil
Jeder Mitgliedstaat veröffentlicht ein nationales Profil — eine eingeschränkte Teilmenge des DATEX-II-Modells. Das deutsche Profil (German Traffic Data Profile) definiert Vorfälle, Baustellen und Verkehrsfluss auf Autobahn und Bundesstraßen. Frankreichs transport.data.gouv.fr nutzt andere Unterelemente. Das niederländische NDW veröffentlicht eine engere Ereignistaxonomie. Der britische NAP TDT wickelt DATEX in TIH-spezifische Container. Im DATEX-II-Profilverzeichnis sind sie alle gelistet — gleiches Wurzelschema, in jedem Land eine andere Form.
2. Version
DATEX II v2.3 und v3.x sind beide aktiv im Einsatz. Manche NAPs liefern beide Versionen. Manche befinden sich mitten in der Migration. Die XML-Namespaces unterscheiden sich. Einige Elemente wurden zwischen den Versionen umbenannt.
3. Transport
DATEX II ist ein Payload-Format, kein Transport. Die NAPs liefern es über:
- HTTPS-Pull — Sie holen ein XML-Dokument im Zeitintervall ab
- HTTPS-Push (Subscription) — Sie melden einen Endpunkt an, der NAP sendet POST-Requests an Sie
- SOAP-over-HTTPS — Pull mit zusätzlicher SOAP-Hülle
- OCIT-C — die deutsche Telematik-Schnittstelle, die Mobilithek einsetzt
- WFS / GeoJSON — einige städtische Ergänzungsfeeds (Hamburg, Düsseldorf)
4. Authentifizierung
Die Matrix ist breit:
- Keine — SRTI-Sicherheitsfeeds müssen frei und offen zugänglich sein
- Registrierter Konsument (ohne Schlüssel) — der NAP setzt Ihren Origin oder Endpunkt auf eine Whitelist
- Bearer / API-Schlüssel — Standard-Token im Header
- TLS-Client-Zertifikat (mTLS) — Mobilithek in Deutschland
- OAuth2 — einige Betreiberportale
5. Frequenz und Frische
Manche Publikationen aktualisieren alle 30 Sekunden (Autobahn-Verkehrsfluss). Andere nur nächtlich (geplante Baustellen-Kalender). Manche sitzen hinter einem CDN, das mit Last-Modified-Headern die Frische schönrechnet. Adaptives Backoff zählt.
Der Format-Zoo, in DATEX-II-Form
Hier in etwa derselbe Vorfall aus drei verschiedenen NAPs:
Mobilithek (DE) — OCIT-C-Hülle um eine v2.3-SituationPublication:
<d2:payloadPublication
xsi:type="d2:SituationPublication"
xmlns:d2="http://datex2.eu/schema/2/2_0">
<d2:situation id="DE_BAB7_4711">
<d2:situationRecord
xsi:type="d2:Accident"
id="DE_BAB7_4711_R1">
<d2:probabilityOfOccurrence>certain</d2:probabilityOfOccurrence>
<d2:groupOfLocations xsi:type="d2:Point">
<d2:pointByCoordinates>
<d2:pointCoordinates>
<d2:latitude>53.5511</d2:latitude>
<d2:longitude>9.9937</d2:longitude>
</d2:pointCoordinates>
</d2:pointByCoordinates>
</d2:groupOfLocations>
</d2:situationRecord>
</d2:situation>
</d2:payloadPublication>
NDW (NL) — v3.x mit einer kompakteren VmsTablePublication-Hülle, JSON-LD erlaubt:
{
"@context": "https://datex2.eu/schema/3/jsonld",
"publicationCreator": { "country": "nl", "nationalIdentifier": "NDW" },
"situation": [{
"id": "NDW_2026_05_01_42",
"situationRecord": [{
"@type": "Accident",
"severity": "high",
"groupOfLocations": {
"locationContainedInGroup": [{
"pointByCoordinates": { "latitude": 52.0907, "longitude": 5.1214 }
}]
}
}]
}]
}
UK NAP TDT — DATEX in einem TIH-Container mit Publisher-spezifischer Feed-Hülle:
<tih:feed xmlns:tih="https://nap.tdt.gov.uk/tih">
<tih:publisher>National Highways</tih:publisher>
<d2:payloadPublication xsi:type="d2:SituationPublication">
<d2:situation id="NH-INC-9001">...</d2:situation>
</d2:payloadPublication>
</tih:feed>
Gleiche Verordnung. Gleicher Standard. Drei verschiedene Parser nötig.
Die Architektur
Die Lösung ist dieselbe wie auf der nordamerikanischen Seite: ein Adapter-Register mit einer einzigen Funktionssignatur.
type FetchFunc func(ctx context.Context, sr SourceResource) (*FetchResult, error)
type FetchResult struct {
Events []TrafficEvent
Features []Feature
ResponseBytes int
}
Jeder NAP-Adapter — ob er Mobilitheks OCIT-C-Broker abruft, NDWs v3-JSON-LD pollt oder eine TIH-Hülle entfernt — implementiert dieses Interface. Er erhält eine Source-Resource-Konfiguration (URL, Zugangsdaten, Ländercode, Profilversion) und gibt normalisierte Events und Features zurück.
Die Registrierung passiert beim init:
func init() {
Register("mobilithek", "events", fetchMobilithekSituations)
Register("mobilithek", "afir_charging", fetchMobilithekAFIR)
Register("mobilithek", "parking", fetchMobilithekParking)
Register("ndw", "events", fetchNDWSituations)
Register("ndw", "vms", fetchNDWVMS)
Register("uk_tdt", "events", fetchUKTDTSituations)
Register("transport_data_gouv_fr", "events", fetchFRSituations)
// ... 30 NAPs, ~120 (Adapter, Resource)-Registrierungen
}
Der Scheduler weiß und interessiert sich nicht dafür, welches DATEX-II-Profil, welche Version oder welcher Transport hinter jedem Adapter steckt. Er ruft Fetch() auf und erhält normalisierte Events und Features zurück.
Das normalisierte Modell
Alles konvergiert in zwei Tabellen:
traffic_events — zeitlich begrenzte Vorfälle mit Lebenszyklus-Tracking:
type TrafficEvent struct {
ID string
Source string // "mobilithek", "ndw", "uk_tdt"
Jurisdiction string // "DE", "NL", "GB"
Type EventType // incident, construction, closure, weather
Severity Severity // minor, moderate, major, critical
Status EventStatus // active, archived
Title string
Description string
AffectedRoads []string
Direction string
LanesAffected string
Latitude, Longitude float64
StartTime time.Time
EndTime *time.Time
EstimatedEndTime *time.Time
RoadClass string // motorway, trunk, primary, secondary
Metadata json.RawMessage // DATEX-II-spezifische Felder bewahrt
}
features — eine generische Tabelle für alles andere. Kameras, VMS-Anzeigen, Wetterstationen, Parkplätze, Ladestationen, LKW-Routensegmente, Brücken-Durchfahrtshöhen — alle in derselben Tabelle mit einem feature_type-Diskriminator und typspezifischen Feldern in einer JSONB-properties-Spalte. Ein neuer Ressourcentyp benötigt keine Schemamigration.
Die schwierigen Teile
1. Profilfeld-Mapping
Das deutsche Profil verwendet locationDescriptor mit roadName. Das niederländische v3-Profil legt eine roadInformation-Struktur auf der obersten Ebene ab. Britische Feeds verstecken den Straßennamen drei Ebenen tief in tih:routeContext. Jeder Adapter hat eine kleine Mapping-Funktion, die weiß, wo sein Profil welches kanonische Feld ablegt. Wo ein Profil nichts zum Mappen hat, bleibt das Feld leer — wir erfinden keine Daten.
2. Lebenszyklus-Tracking
Ein DATEX-II-SituationRecord hat eine situationRecordVersion und eine situationRecordVersionTime. Jeder neue Pull kann eine neue Version desselben Datensatzes liefern. Manche NAPs erhöhen die Version bei jeder Metadatenänderung; andere nur bei substantiellen Updates.
Die Lösung: den aktuellen Zustand mit dem letzten Fetch differenzieren. Jede Änderung in einer event_history-Tabelle protokollieren — Schweregrad, Beschreibung, Spuren, Archivierung. So werden Analytics wie Räumzeit-Perzentile und grenzüberschreitende Korridor-Zuverlässigkeit möglich.
3. Koordinatenreferenzsysteme
Die meisten NAPs liefern WGS84 (EPSG:4326). Einige deutsche Landesfeeds nutzen ETRS89 / UTM-Zonen. Manche städtische Feeds verwenden das lokale Katasterystem. PostGIS übernimmt die Transformation, aber jeder Adapter muss wissen, was er empfängt.
4. Rate-Limiting im großen Maßstab
30 NAPs, jeder mit mehreren Ressourcentypen (Events, VMS, Parkplätze, Ladestationen, Wetter), jeder mit einer 30-Sekunden- bis 5-Minuten-Frequenz. Der Scheduler nutzt pro Server ein Semaphor mit konfigurierbarer Parallelität und Mindestabstand zwischen Anfragen. Circuit Breaker zurücksetzen bei wiederholten Fehlern. Adaptives Backoff erhöht die Frequenz, wenn ein NAP langsam ist oder Fehler liefert — sonst zieht ein einzelner langsamer NAP die Frische einer ganzen Region nach unten.
5. Schemafreie Feature-Typen
Anfangs hatten wir auf der nordamerikanischen Seite separate Tabellen für Kameras, VMS, Parkplätze. Jeder neue Typ bedeutete eine Migration. Die Umstellung auf eine generische features-Tabelle mit JSONB-Properties war die beste Architekturentscheidung im Projekt. Auf der EU-Seite zahlt sich diese Entscheidung erneut aus, sobald AFIR-Ladedaten online gehen — null Schemamigrationen, einfach ein neuer feature_type.
Die API
Nach der Normalisierungsarbeit ist die API geradlinig:
Aktive Vorfälle in Deutschland:
curl "https://api.napspan.com/api/v1/events?country=DE&type=incident&status=active" \
-H "X-API-Key: your_key"
{
"data": [
{
"id": "de_mobilithek_4711",
"country": "DE",
"type": "incident",
"severity": "major",
"title": "Verkehrsunfall A7 Richtung Hamburg",
"affected_roads": ["A7"],
"direction": "Hamburg",
"lanes_affected": "2 of 3 lanes closed",
"latitude": 53.5511,
"longitude": 9.9937,
"start_time": "2026-05-01T08:15:00Z",
"estimated_end_time": "2026-05-01T12:00:00Z"
}
],
"total": 142,
"limit": 100,
"offset": 0,
"has_more": true
}
Verkehrskameras in den Niederlanden als GeoJSON:
curl "https://api.napspan.com/api/v1/features/geojson?type=cameras&country=NL" \
-H "X-API-Key: your_key"
Die Antwort lässt sich direkt in Leaflet, Mapbox oder ein anderes GeoJSON-kompatibles Werkzeug einsetzen.
Grenzüberschreitende Korridor-Abfrage (Berlin nach Amsterdam):
curl "https://api.napspan.com/api/v1/events/corridor?\
from_lat=52.52&from_lng=13.40&\
to_lat=52.37&to_lng=4.90&buffer_km=5" \
-H "X-API-Key: your_key"
Ein Aufruf, zwei NAPs (Mobilithek + NDW), eine konsistente Antwortstruktur.
Was in den Daten steckt
Nach der Normalisierung enthält der Datensatz unter anderem:
- Echtzeit-RTTI-Events — Vorfälle, Baustellen, Verkehrsfluss
- SRTI-Sicherheitsmeldungen (frei und offen nach 886/2013)
- VMS-Inhalte dynamischer Anzeigetafeln mit dem aktuell angezeigten Text
- SSTP-Daten zu sicheren LKW-Parkplätzen mit Verfügbarkeit
- AFIR-Artikel-20-Ladestatus von Ladepunkten
- Verkehrskameras mit Bild-URLs (sofern der NAP sie ausgibt)
- RWIS-ähnliche Wetterstationsmessungen
- TEN-T-Kernnetz-Korridor-Metadaten
- Brücken-Durchfahrtshöhen und Gewichtsbeschränkungen je Mitgliedstaat
Tech-Stack
- Go — chi-Router, pgx v5, slog, encoding/xml + eigene DATEX-Schema-Parser
- PostgreSQL + PostGIS — räumliche Abfragen, GeoJSON-Generierung, Korridor-Verschneidung
- Redis — Response-Caching, Feature-Detail-Caching (nil-sicher, optional)
- Vue 3 + Leaflet — Live-Verkehrskarte
Jetzt ausprobieren
Die API ist live mit einem kostenlosen Tarif (keine Kreditkarte):
- Live-Karte — Daten visuell erkunden
- API-Dokumentation — vollständige Endpunktreferenz
- Entwicklerportal — Anmelden und API-Schlüssel erhalten
Wenn Sie irgendetwas auf europäischen Mobilitätsdaten aufbauen — Logistik, Flottenrouting, Navigation, urbane Mobilitäts-Dashboards — würden wir gerne wissen, welche NAPs und welche DATEX-II-Profile für Sie als Erstes normalisiert werden sollten. Der schwierigste Teil ist nicht das Parsen, sondern zu wissen, welcher Mitgliedstaat welche Daten über welchen Transport hinter welcher Auth-Methode veröffentlicht. Nach 30 NAPs haben wir davon eine ziemlich gute Karte.
Bereit, NAPSPAN auszuprobieren?
14 Tage kostenlos. Keine Kreditkarte. EU 27 + UK + EFTA, normalisiert.
Kostenlosen API-Schlüssel sichern Karte erkunden