← Zurück zu allen Beiträgen
DATEX II Architektur EU

Wie wir DATEX II über 30 europäische National Access Points normalisieren

1. Mai 2026 · 11 Min. Lesezeit

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:

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:

4. Authentifizierung

Die Matrix ist breit:

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:

Tech-Stack

Jetzt ausprobieren

Die API ist live mit einem kostenlosen Tarif (keine Kreditkarte):


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