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 :
- (UE) 2022/670 — RTTI : événements en temps réel, vitesses, conditions routières
- (UE) n° 886/2013 — SRTI : accès libre et gratuit aux alertes de sécurité
- (UE) n° 885/2013 — SSTP : aires de stationnement sécurisées pour PL
- (UE) 2017/1926 — MMTIS : informations de voyage multimodales
- AFIR article 20 — télémétrie en direct des bornes de recharge VE
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 :
- Pull HTTPS — vous récupérez un document XML à intervalles réguliers
- Push HTTPS (abonnement) — vous enregistrez un endpoint et le NAP vous envoie des POST
- SOAP-over-HTTPS — pull avec enveloppe SOAP
- OCIT-C — l'interface télématique allemande utilisée par Mobilithek
- WFS / GeoJSON — quelques flux complémentaires de villes (Hambourg, Düsseldorf)
4. Authentification
La matrice est large :
- Aucune — les flux SRTI de sécurité doivent être libres et ouverts
- Consommateur enregistré (sans clé) — le NAP met votre origine ou votre endpoint en liste blanche
- Bearer / clé API — jeton standard dans un en-tête
- Certificat client TLS (mTLS) — Mobilithek en Allemagne
- OAuth2 — quelques portails opérateurs
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 :
- Événements RTTI en temps réel — incidents, chantiers, flux de trafic
- Alertes SRTI de sécurité (libres et ouvertes selon 886/2013)
- Contenus VMS des panneaux à messages variables
- Données SSTP de stationnement PL sécurisé avec disponibilité
- Statut des points de recharge selon l'AFIR article 20
- Caméras de trafic avec URL d'image (lorsque le NAP les expose)
- Mesures de stations météo de type RWIS
- Métadonnées de corridor du réseau central RTE-T
- Hauteurs libres de ponts et restrictions de poids par État membre
Pile technique
- Go — routeur chi, pgx v5, slog, encoding/xml + parsers DATEX maison
- PostgreSQL + PostGIS — requêtes spatiales, génération GeoJSON, intersection de corridors
- Redis — cache de réponses, cache de détails de feature (nil-safe, optionnel)
- Vue 3 + Leaflet — carte de trafic en direct
Essayez
L'API est en ligne avec une formule gratuite (sans carte bancaire) :
- Carte en direct — explorer les données visuellement
- Documentation API — référence complète des endpoints
- Portail développeur — s'inscrire et obtenir une clé API
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