How to Turn a GPX File into a Map Matching Request

API or service
Map Matching API
Task
GPX file → waypoints → matched road route
Examples
URLAI
Difficulty
Beginner
Time
10 min

Turn a GPX file into a Map Matching request

You have a GPS track exported as a GPX file from a phone app, a bike computer, or a vehicle tracker, and you want the route it followed on the road network. The Map Matching API doesn't read GPX: it takes a JSON body with a travel mode and an ordered list of waypoints. This guide converts the track points of a GPX file into that body, handles files with more than 1,000 points, and sends the request.

The example file is a 28-minute bike ride on the Lakefront Trail in Chicago, from Navy Pier (600 East Grand Avenue, Chicago, IL 60611) to 31st Street Beach (Chicago, IL 60653), with one point per second: lakefront-trail-ride.gpx. It is a test file for this guide, generated from a Routing API bicycle route with small random offsets.

If you already have the points as JSON, see How to Snap a GPS Track to the Road Network.

Task flow: GPX file → track points → waypoints (thinned or split to ≤ 1,000) → Map Matching API → matched route.

Map GPX track points to waypoints

A GPX file stores a recorded track as <trk> elements, each with one or more <trkseg> segments of <trkpt> points. Each track point becomes one waypoint, in the same order:

GPX Map Matching waypoints[] Notes
<trkpt lat="…"> location[1] Latitude is the second value
<trkpt lon="…"> location[0] location is [longitude, latitude], the opposite of the GPX attribute order
<time> timestamp Optional. GPX times are ISO 8601 date-times; the UTC times of the example file (…Z) were sent unchanged. Timestamps help calculate effective speed and filter incorrect waypoints
<course> (GPX 1.0 only) bearing Optional, 0–360. GPX 1.1 has no course element; some apps write it in <extensions>
<ele>, <name>, <extensions> — Not used by the Map Matching API

The first track points of the example file:

<trk>
  <name>Lakefront Trail ride</name>
  <trkseg>
    <trkpt lat="41.892123" lon="-87.608621"><time>2026-11-01T14:05:00Z</time></trkpt>
    <trkpt lat="41.892142" lon="-87.608674"><time>2026-11-01T14:05:01Z</time></trkpt>

become:

{
  "mode": "bicycle",
  "waypoints": [
    { "location": [-87.608621, 41.892123], "timestamp": "2026-11-01T14:05:00Z" },
    { "location": [-87.608674, 41.892142], "timestamp": "2026-11-01T14:05:01Z" }
  ]
}

Choose mode from the activity or the vehicle that recorded the file; see How to Choose the Travel Mode for Map Matching.

A GPX file can contain several <trk> or <trkseg> elements. A new segment usually means the recording was paused or lost the signal. Keep the points in recorded order, and when the gap between segments is long, send each segment as a separate request instead of joining them into one track.

Fit the track into 1,000 points

One request accepts up to 1,000 waypoints. Apps that record one point per second reach that in about 17 minutes. The example file has 1,664 points, and sending all of them returns HTTP 400:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "\"waypoints\" must contain less than or equal to 1000 items"
}

There are two ways to fit the track:

  • Thin the points. Keep every Nth point plus the last one. For a bike or a car, a point every few seconds is usually enough to follow the road.
  • Split the track. Send consecutive chunks of up to 1,000 points. Repeat the last point of one chunk as the first point of the next so the matched routes connect. Use this when you need every recorded point matched.

Every request costs max(1, ceil(waypoints / 100)) credits, so thinning also lowers the cost:

Input from the 1,664-point file Requests Waypoints Credits
All points 1 1,664 — (HTTP 400)
Split into two chunks 2 1,000 + 665 10 + 7 = 17
Every 2nd point 1 833 9
Every 5th point 1 334 4
Every 10th point 1 168 2

The split adds one point to the second chunk (point 1,000 is sent twice). The thinned versions always keep the last point of the file.

Send the converted track

Send the body to the Map Matching API. This request uses every 5th point of the example file, 334 waypoints, 5 seconds apart:

POST https://api.geoapify.com/v1/mapmatching?apiKey=YOUR_API_KEY
Content-Type: application/json

Reduced request body (the first three and the last of 334 waypoints):

{
  "mode": "bicycle",
  "waypoints": [
    { "location": [-87.608621, 41.892123], "timestamp": "2026-11-01T14:05:00Z" },
    { "location": [-87.608859, 41.892131], "timestamp": "2026-11-01T14:05:05Z" },
    { "location": [-87.609161, 41.892134], "timestamp": "2026-11-01T14:05:10Z" },
    { "location": [-87.607263, 41.839283], "timestamp": "2026-11-01T14:32:43Z" }
  ]
}

Reduced response:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "mode": "bicycle",
        "distance": 6987,
        "time": 1479,
        "waypoints": [
          {
            "original_index": 0,
            "location": [-87.608621, 41.892159],
            "original_location": [-87.608621, 41.892123],
            "match_type": "matched",
            "match_distance": 3.991888,
            "leg_index": 0,
            "step_index": 0
          }
        ],
        "legs": [{ "distance": 6987, "time": 1479, "steps": [] }]
      },
      "geometry": { "type": "MultiLineString", "coordinates": [] }
    }
  ]
}

All five variants from the previous section returned the same roads on November 4, 2026: Lakefront Trail 6,242 m, plus East Grand Avenue, the Lakefront Trail Connector, and the Navy Pier Flyover at the start.

Input Route distance Legs match_type counts Average match_distance
Every 2nd point (833) 6,987 m 1 487 matched, 346 interpolated 2.0 m
Every 5th point (334) 6,987 m 1 334 matched 1.9 m
Every 10th point (168) 6,987 m 1 168 matched 1.9 m
Chunk 1 (1,000) + chunk 2 (665) 4,198 m + 2,792 m 1 + 1 558 matched, 1,107 interpolated 2.1 m, 2.0 m

With points 1–2 seconds apart, many points come back as interpolated: their position was inferred from the matched points around them. With every 5th point, every point is matched, and the result costs 4 credits instead of 17. Check the match_type and match_distance of each waypoint as described in Check match quality.

GPX map matching with an AI coding agent

Copy this prompt into Codex, Claude Code, Cursor, or another coding agent to add GPX upload and map matching to your project.

Add GPX track matching to my project with the Geoapify Map Matching API.

Before you start
- Inspect the existing project first. Reuse its current framework, file upload handling, XML parsing, HTTP client, map library, and conventions. Make the smallest necessary changes.
- Ask only what the project does not answer: where GPX files come from, which travel mode to use (or how to pick it), and whether requests run in a backend or use a restricted browser API key.
- Read the Geoapify API key from the project's environment configuration. Never hardcode, print, or log it.

GPX conversion
- Parse every <trk>/<trkseg>/<trkpt> in document order with an XML parser, not regular expressions.
- For each trkpt: location = [Number(lon), Number(lat)] (longitude first); timestamp = the <time> text if present; bearing = <course> if present (GPX 1.0). Ignore <ele>, names, and extensions.
- Skip points with missing or invalid coordinates. Treat each trkseg as its own track if the time gap to the previous segment is long.

API request
- POST https://api.geoapify.com/v1/mapmatching?apiKey=... with a JSON body { mode, waypoints }.
- The API accepts up to 1,000 waypoints. If a track is longer, either keep every Nth point plus the last point (default: one point about every 5 seconds), or split it into chunks of up to 1,000 points and repeat the last point of each chunk as the first point of the next.
- Each request costs max(1, ceil(waypoints / 100)) credits; show the estimate before sending large files.

Result
- Read features[0]: the MultiLineString geometry, properties.distance, and properties.waypoints[] with match_type and match_distance. For split tracks, join the chunk geometries in order.
- If a map is used, draw the matched geometry and, optionally, the raw track points.

Errors
- Show the API message for HTTP 400 responses (for example the 1,000-waypoint limit), show an error for files without track points, and retry 429 responses a limited number of times with backoff.
- Add a test with a small GPX fixture that has two segments, one point without <time>, and more than 1,000 points after concatenation.

Review the generated code against this guide. Check the coordinate order ([longitude, latitude]), that the last point is kept when thinning, that split chunks overlap by one point, and the API-key handling.