← Retour à tous les articles
DATEX II Architecture UE

Comment nous normalisons DATEX II sur 30 points d'accès nationaux européens

1er mai 2026 · 11 min de lecture

La directive UE 2010/40/UE impose à chaque État membre de gérer un point d'accès national (NAP) publiant en temps réel des données de trafic, de sécurité et de mobilité multimodale au format DATEX II. Sur le papier : un standard, 27 publieurs, une intégration.

En pratique, chaque NAP livre un profil DATEX II différent, sur un transport différent, derrière une authentification différente, à une cadence différente. « Conforme à DATEX II » est une case que chaque État membre coche à sa façon.

C'est ce que nous avons rencontré en construisant NAPSPAN, une API unifiée sur l'UE 27 + UK + AELE. Voici l'architecture qui nous permet de traiter 30 flux DATEX II incompatibles comme s'ils n'en faisaient qu'un.

La spec, puis la réalité

DATEX II est un standard européen mature, maintenu par datex2.eu et coordonné par NAPCORE. Chaque règlement délégué le désigne comme format d'échange obligatoire :

La spécification est donc claire. La mise en œuvre l'est beaucoup moins.

Là où les NAPs divergent vraiment

« Conforme à DATEX II » cache au moins cinq axes de divergence :

1. Profil

Chaque État membre publie un profil national — un sous-ensemble contraint du modèle DATEX II. Le profil allemand (German Traffic Data Profile) définit les incidents, chantiers et flux sur Autobahn et Bundesstraßen. transport.data.gouv.fr en France utilise d'autres sous-éléments. Le NDW néerlandais publie une taxonomie d'événements plus serrée. Le NAP TDT britannique enveloppe DATEX dans des conteneurs propres au TIH. Le répertoire des profils DATEX II les liste tous — même schéma racine, forme différente par pays.

2. Version

DATEX II v2.3 et v3.x cohabitent dans la nature. Certains NAPs publient les deux. Certains sont en pleine migration. Les espaces de noms XML diffèrent. Quelques éléments ont été renommés entre versions.

3. Transport

DATEX II est un format de payload, pas un transport. Les NAPs le diffusent via :

4. Authentification

La matrice est large :

5. Cadence et fraîcheur

Certaines publications se rafraîchissent toutes les 30 secondes (flux d'autoroute). D'autres tous les soirs (calendriers de chantiers planifiés). Certaines sont derrière un CDN qui ment sur la fraîcheur via des en-têtes Last-Modified obsolètes. Le backoff adaptatif compte.

Le zoo des formats, version DATEX II

Voici à peu près le même incident vu depuis trois NAPs différents :

Mobilithek (DE) — enveloppe OCIT-C autour d'une SituationPublication v2.3 :

<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 avec une enveloppe plus compacte de type VmsTablePublication, JSON-LD autorisé :

{
  "@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 }
        }]
      }
    }]
  }]
}

NAP TDT (UK) — DATEX dans un conteneur TIH avec enveloppe par publieur :

<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>

Même règlement. Même standard. Trois parsers nécessaires.

L'architecture

La solution est la même que côté nord-américain : un registre d'adaptateurs avec une seule signature de fonction.

type FetchFunc func(ctx context.Context, sr SourceResource) (*FetchResult, error)

type FetchResult struct {
    Events        []TrafficEvent
    Features      []Feature
    ResponseBytes int
}

Chaque adaptateur de NAP — qu'il pull le broker OCIT-C de Mobilithek, poll le JSON-LD v3 de NDW ou déballe une enveloppe TIH — implémente cette interface. Il prend une configuration source-resource (URL, identifiants, code pays, version de profil) et renvoie des événements et features normalisés.

L'enregistrement se fait à l'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 enregistrements (adaptateur, ressource)
}

Le scheduler ne sait ni ne se soucie de quel profil, version ou transport DATEX II chaque adaptateur traite. Il appelle Fetch() et reçoit des événements et features normalisés.

Le modèle normalisé

Tout converge vers deux tables :

traffic_events — incidents bornés dans le temps avec suivi du cycle de vie :

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 // champs DATEX II propres à la source préservés
}

features — une table générique pour tout le reste. Caméras, panneaux VMS, stations météo, parkings, bornes de recharge, segments de route PL, hauteurs libres de ponts — tous dans la même table avec un discriminateur feature_type et des champs spécifiques au type dans une colonne JSONB properties. Aucune migration de schéma pour un nouveau type de ressource.

Les parties difficiles

1. Mappage des champs de profil

Le profil allemand utilise locationDescriptor avec roadName. Le profil v3 néerlandais place une structure roadInformation au plus haut niveau. Les flux britanniques enfouissent le nom de la route à trois niveaux dans tih:routeContext. Chaque adaptateur a une petite fonction de mappage qui sait où son profil place chaque champ canonique. Là où un profil n'a rien à mapper, le champ reste vide — nous ne synthétisons pas de données absentes.

2. Suivi du cycle de vie

Un SituationRecord DATEX II porte une situationRecordVersion et une situationRecordVersionTime. Chaque pull peut amener une nouvelle version du même enregistrement. Certains NAPs incrémentent la version au moindre changement de métadonnées ; d'autres seulement aux mises à jour substantielles.

Solution : diff de l'état actuel contre le pull précédent. Chaque changement est tracé dans une table event_history — gravité, description, voies, archivage. Cela permet des analytiques comme les percentiles de temps de dégagement et la fiabilité de corridor au-delà des frontières.

3. Systèmes de référence des coordonnées

La plupart des NAPs publient en WGS84 (EPSG:4326). Quelques flux d'États allemands publient en ETRS89 / zones UTM. Quelques flux urbains utilisent le système cadastral local. PostGIS gère la transformation, mais chaque adaptateur doit savoir ce qu'il reçoit.

4. Limitation de débit à grande échelle

30 NAPs, chacun avec plusieurs types de ressources (events, VMS, parkings, recharge, météo), polled toutes les 30 sec à 5 min. Le scheduler utilise des sémaphores par serveur avec des limites de concurrence et des intervalles minimum entre requêtes. Des disjoncteurs reculent en cas d'échecs répétés. Le backoff adaptatif augmente l'intervalle de poll quand un NAP est lent ou en erreur — sans cela, un seul NAP lent peut tirer la fraîcheur de toute une région vers le bas.

5. Types de feature sans schéma

Au début, côté nord-américain, nous avions des tables séparées pour caméras, VMS, parkings. Chaque nouveau type imposait une migration. Le passage à une table générique features avec JSONB a été la meilleure décision d'architecture du projet. Côté UE, cette décision repaie dès que les données AFIR de recharge arrivent — aucune migration, simplement un nouveau feature_type.

L'API

Une fois la normalisation faite, l'API est simple :

Incidents actifs en Allemagne :

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
}

Caméras de trafic aux Pays-Bas en GeoJSON :

curl "https://api.napspan.com/api/v1/features/geojson?type=cameras&country=NL" \
  -H "X-API-Key: your_key"

La réponse s'injecte directement dans Leaflet, Mapbox ou tout outil compatible GeoJSON.

Requête de corridor transfrontalier (Berlin–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"

Un appel, deux NAPs (Mobilithek + NDW), une seule structure de réponse.

Ce que contiennent les données

Une fois normalisé, le jeu de données comprend :

Pile technique

Essayez

L'API est en ligne avec une formule gratuite (sans carte bancaire) :


Si vous construisez quoi que ce soit sur les données de mobilité européennes — logistique, routage de flotte, navigation, tableaux de bord urbains — nous serions ravis de savoir quels NAPs et quels profils DATEX II sont prioritaires pour vous. Le plus dur n'est pas le parsing ; c'est de savoir quel État membre publie quelles données sur quel transport derrière quelle authentification. Après 30 NAPs, nous avons une assez bonne carte de cela.

Prêt à essayer NAPSPAN ?

Essai gratuit de 14 jours. Sans carte bancaire. UE 27 + UK + AELE, normalisé.

Obtenir une clé API gratuite Explorer la carte