Integration method with OSRM routing engine

WrA 5 days ago

Hello everyone,
I’d like to share a method for integrating the Traccar backend (Java) and React UI with the OSRM engine (https://project-osrm.org/)
to enable the “Snap to Road” feature.
I hope this proves useful for those who are looking for a feature like this.

Ref :

Some technical documentation, patch files, feature updates, and other explanations can be found at :

Dev Repositories :

  • https://github.com/wr4eng/saturn-osrm-backend
  • https://github.com/wr4eng/saturn-osrm-ui

Motivation

GPS devices in fleet environments frequently produce GPS drift — erroneous coordinates caused by multipath, poor satellite geometry, or brief signal loss.
When these raw coordinates are fed directly to /route/v1, OSRM faithfully routes through the drift points, producing loops and impossible paths on the map.
/match/v1 uses a Hidden Markov Model that evaluates the probability of each GPS point belonging to each candidate road segment, considering both spatial distance and implied travel speed between consecutive timestamps. Outlier points receive near-zero probability and are dropped (returned as null tracepoints).


Components

OsrmClient/route/v1 with GPS Drift Pre-filter

Handles coordinate conversion, validation, HTTP retries, result caching, and a three-stage drift filter applied before sending coordinates to OSRM.

Drift Filter Pipeline (applyDriftFilter)

Caching

Cache TTL is controlled by routing.cache.ttl (default 1 hour).


OsrmMatchClient/match/v1 with HMM

Uses OSRM's map matching API which applies a Hidden Markov Model to the GPS trace.
The HMM considers both the geometric distance from a candidate road and the transition probability between consecutive road segments given the elapsed time and implied speed.

Fallback Conditions

OsrmMatchClient delegates to OsrmClient.calculateRoute() when:

  • OSRM returns code: NoMatch (trace too sparse or entirely outside road network)
  • All matchings[] segments have confidence < routing.match.minConfidence
  • HTTP request fails after all retry attempts
  • JSON parse error in the response
  • filterValid() yields fewer than 2 valid positions

The fallback client itself applies the drift filter pipeline before calling /route/v1, providing a second layer of noise reduction.

I hope this is helpful
For more details, see the GitHub link above

Track-trace 4 days ago

Nice, i will check it out, Thank you!

Byhturk 2 days ago

I've been following this for a while now, and I think it's a great addition to traccar.

I'll try it.

Track-trace 2 days ago

@WrA

I wonder, could you also make this work for the realtime route on the map?

Byhturk 2 days ago

@Track-trace

Perhaps it could also be used for route lines on the livemap? (via WebLiveRouteLength).

However, there's a problem with WebLiveRouteLength: when a user reconnects to the application after disconnecting from the socket, it draws a long line on the map from the last point in memory to the current point. The current UI behavior might cause this problem.

The best thing to do would be to disable WebLiveRouteLength on the livemap :)

WrA 11 hours ago

For that approach, it seems it can be done by creating a new API in the backend that integrates with the route/v1 API function of the existing OsrmClient

api/resource/LiveRouteSnapResource.java

- Endpoint : GET /api/route/live-snap?deviceId={id}
- Auth : existing session (same as all /api/* endpoints)
- Cache : BYPASSED — always fresh for live tracking
- Fallllback : if OSRM unavailable, returns raw straight-line segment (Haversine)

Additions to the front end e.g :

  • src/map/main/MapLiveRoutesSnap.js (new)

  • src/main/MainMap.jsx (add extended - call MapLiveRoutesSnap.js)

  • src/common/attributes/useCommonUserAttributes.js (add extended)

such as :

      mapLiveRoutes: {
        name: t('mapLiveRoutes'),
        type: 'string',
      },
      mapLiveRoutesSnap: {
        name: t('mapLiveRoutesSnap'),
        type: 'boolean',
      },

user frontend :

  • mapLiveRoutes = selected / all
  • mapLiveRoutesSnap = true
  • mapFollow = true

May add this in the future; currently, on progress test the trip/v1 and table/v1 APIs from the OsrmTripClient and OsrmTableClient classes. It hasn't been pushed to Git yet.

- reports/eta-matrix?deviceId={id}&deviceId={id}&deviceId={id}&deviceId={id}&geofenceId={id}&geofenceId={id}&geofenceId={id}
- reports/tripx-snap?deviceId={id}&deviceId={id}&deviceId={id}&deviceId={id}

BACKEND logs :

2026-05-09 22:09:55  INFO: RouteReportProvider: using OsrmMatchClient (match/v1) as primary router
2026-05-09 22:09:55  INFO: TripxReportProvider: OsrmTripClient (trip/v1) ready
2026-05-09 22:09:55  INFO: TripxReportProvider: OsrmTableClient (table/v1) ready
2026-05-09 22:09:55  INFO: TripClient: trip planned — 3 waypoints, 3 legs, 17.44 km, 1116 s
2026-05-09 22:09:55  INFO: TripxReportProvider: trip planned — 3 stops, 17.44 km, 1116 s
2026-05-09 22:03:14  INFO: RouteReportProvider: using OsrmMatchClient (match/v1) as primary router
2026-05-09 22:03:14  INFO: TripxReportProvider: OsrmTripClient (trip/v1) ready
2026-05-09 22:03:14  INFO: TripxReportProvider: OsrmTableClient (table/v1) ready
2026-05-09 22:03:14  INFO: TableClient: matrix computed — 3x3 (0 unreachable)
2026-05-09 22:03:14  INFO: TableClient: matrix computed — 3x3 (0 unreachable)
2026-05-09 22:03:14  INFO: TripxReportProvider: table computed — 3 devices × 3 geofences (9 cells)