Map matching
Snap noisy GPS traces to the road network. Clean up fleet tracks, activity recordings, and raw sensor data.
GPS points drift into buildings, jump across rivers, and wander off roads. Map matching snaps a raw trace to the most likely path on the road network using a Hidden Markov Model, returning snapped tracepoints with matched road segments.
Basic request
Section titled “Basic request”Send the trace as a GeoJSON LineString in the geometry field. Coordinates are [longitude, latitude] pairs. Optional radiuses controls the search radius per point (meters, default 50):
curl -X POST https://plaza.fyi/api/v1/map-match \ -H "x-api-key: pk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "geometry": { "type": "LineString", "coordinates": [ [13.3888, 52.5170], [13.3910, 52.5168], [13.3935, 52.5172], [13.3961, 52.5165], [13.3990, 52.5161] ] } }'import Plaza from "@plazafyi/sdk";
const client = new Plaza();
const matched = await client.v1.mapMatch({ geometry: { type: "LineString", coordinates: [ [13.3888, 52.517], [13.391, 52.5168], [13.3935, 52.5172], [13.3961, 52.5165], [13.399, 52.5161], ], },});import plaza
client = plaza.Client()
matched = client.v1.map_match( geometry={ "type": "LineString", "coordinates": [ [13.3888, 52.5170], [13.3910, 52.5168], [13.3935, 52.5172], [13.3961, 52.5165], [13.3990, 52.5161], ], },)import "github.com/plazafyi/plaza-go"
client := plaza.NewClient()
matched, _ := client.V1.MapMatch(ctx, plaza.MapMatchParams{ Geometry: plaza.GeoJSONLineString{ Type: "LineString", Coordinates: [][]float64{ {13.3888, 52.5170}, {13.3910, 52.5168}, {13.3935, 52.5172}, {13.3961, 52.5165}, {13.3990, 52.5161}, }, },})Response
Section titled “Response”Response — a GeoJSON FeatureCollection. Each feature is a snapped tracepoint; the top-level matchings array contains matched road segments.
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [13.3889, 52.5170] }, "properties": { "original": [13.3888, 52.5170], "distance_m": 1.2, "edge_id": 48201, "name": "Unter den Linden", "matchings_index": 0, "waypoint_index": 0 } } ], "matchings": [ { "distance": 720, "geometry": { "type": "LineString", "coordinates": "..." } } ]}| Field | What it means |
|---|---|
original | The original [lng, lat] coordinate you sent. |
distance_m | Distance from the original point to the snapped location in meters. |
edge_id | The road edge the point was snapped to. |
name | Street name of the matched edge (when available). |
matchings_index | Index into the top-level matchings array for this point’s matched sub-route. |
waypoint_index | This point’s index in the original input. |
Custom radiuses
Section titled “Custom radiuses”Control how far each GPS point searches for candidate roads:
{ "geometry": { "type": "LineString", "coordinates": [ [13.3888, 52.5170], [13.3910, 52.5168], [13.3935, 52.5172] ] }, "radiuses": [100, 50, 75]}Each value is a search radius in meters for the corresponding coordinate (default: 50m).
Tips for good results
Section titled “Tips for good results”Point density matters. One point every few seconds works well. One per minute produces poor matches — too much ambiguity between points.
Trim cold-start junk. GPS receivers produce noise for the first few seconds. Cut scattered initial points before sending.
Coordinate limit. Max 50 points per request. For longer traces, split into overlapping segments and stitch.
Use cases
Section titled “Use cases”Fleet tracking. Snap delivery vehicle traces to actual roads for accurate distance and replay.
Activity recording. Clean up running/cycling GPS tracks that zigzag through buildings.
GPS cleanup before analysis. Map match probe data before travel time or speed studies — raw noise pollutes results.