GPX to GeoJSON converter
Turn a GPX file from a GPS watch, Strava or Garmin Connect into a GeoJSON FeatureCollection for Leaflet, MapLibre or your own code. Point times and heart rate come along. Free, and no account needed.
Convert GPX to GeoJSON online, in your browser
The Spatibase GPX to GeoJSON converter reads a GPX file and writes an RFC 7946 FeatureCollection in your web browser. How the GPX maps to GeoJSON:
- Tracks become lines. A
LineString, or aMultiLineStringwith one part pertrkseg, with the elevation as the third coordinate. - Per-point data stays. Times, heart rate, cadence, temperature and power sit in
properties.coordinateProperties, one array entry per point. - Waypoints become Points. With
name,description,sym,typeand vendor fields such as OsmAnd’sosmand_icon. - Nothing is uploaded. Files up to 50 MB, free, no account.
How to convert GPX to GeoJSON
- Find a GPX: an activity exported from Strava or Garmin Connect, a route planned on gpx.studio or Komoot, or a log straight off a GPS unit.
- Drop it on the box above, or press Choose a file. The sample holds a walk through Barcelona, three areas saved as closed tracks and five waypoints.
- Check the preview: every track, route and waypoint is one feature in the list.
- Press Download GeoJSON, or Copy GeoJSON to paste it into geojson.io or your code.
- Load it in Leaflet with
L.geoJSON(data)or in MapLibre as ageojsonsource, or import it into a Spatibase project.
How GPX elements land in the GeoJSON
| What | What happens |
|---|---|
Track (trk) | A LineString feature, or a MultiLineString when the track has more than one segment (trkseg). Its _gpxType is "trk". |
Route (rte) | A LineString through the route points, with _gpxType: "rte". The names of single route points (rtept) are not kept. |
Waypoint (wpt) | A Point feature with name, description (from desc), cmt, sym, type and time. |
Elevation (ele) | The third number of a position: [2.17, 41.38, 12.4]. A point without <ele> keeps two numbers, so one line can mix both. |
| Point times | coordinateProperties.times: one array per track, or one per segment for a MultiLineString. The track’s own time is its first point’s time. |
| Heart rate, cadence, temperature, power | coordinateProperties.heart, cads, atemps and powers, aligned with the coordinates, with null where a point has no value. |
| Vendor fields in their own namespace | Kept with the prefix: OsmAnd’s <osmand:icon> becomes osmand_icon. |
| Feature order | Tracks first, then routes, then waypoints, whatever the order in the file. |
| Not kept | The file’s metadata (author, bounds), route-point names and times, and any segment or route with a single point. |
Reading a GPS track in GeoJSON
GeoJSON has no time. RFC 7946 stores shapes and properties, nothing per vertex. coordinateProperties is a convention of togeojson, the open-source library this converter uses: arrays that run parallel to the coordinates. GDAL and GPSBabel don’t write it, and Leaflet and MapLibre ignore it when they draw.
Three details to handle when you read the file in code:
- Times can be shorter than the line. Only points with a
<time>are listed. If a few points have none,times[i]no longer belongs tocoordinates[i], so compare the lengths before pairing them. - Segments mark gaps. A
MultiLineStringpart ends where recording stopped. Flattening the parts into one line connects the gap with a straight stroke. - Sensor arrays hold nulls. A point without a heart-rate reading is
nullinheart, so filter before you average.
Need one feature per point? For charts and tables, the GPX to CSV converter writes a row per track point with time, elevation and heart rate.
Convert GPX to GeoJSON in JavaScript
togeojson (@tmcw/togeojson) reads GPX in a browser or in Node.js. In Node it needs a DOM parser such as @xmldom/xmldom:
import { readFile, writeFile } from 'node:fs/promises'
import { DOMParser } from '@xmldom/xmldom' // browsers have DOMParser built in
import { gpx } from '@tmcw/togeojson'
const xml = new DOMParser().parseFromString(await readFile('ride.gpx', 'utf8'), 'text/xml')
const geojson = gpx(xml) // tracks, then routes, then waypoints
await writeFile('ride.geojson', JSON.stringify(geojson))Two fixes this page adds. Plain togeojson copies the last point’s sensor values onto the whole track (gpxtpx_hr: "151" on a Strava run), and it misses the heart rate in Garmin Connect files, which write ns3: instead of gpxtpx:. The converter renames the prefix and drops those keys.
On the command line, ogr2ogr -f GeoJSON tracks.geojson ride.gpx tracks writes each track as a 2D MultiLineString without times, and its track_points layer gives one Point per track point with ele and time. GPSBabel’s -o geojson joins a track’s segments into one line and drops the times.
Questions about converting GPX to GeoJSON
Where do the GPX timestamps go in the GeoJSON?
Into properties.coordinateProperties.times, one ISO 8601 time per track point, in the same order as the coordinates. A track with several segments gets one array per segment. The geometry itself has no place for time in GeoJSON.
Is elevation kept when converting GPX to GeoJSON?
Yes, as the third value of each position, in metres: [2.17, 41.38, 12.4]. RFC 7946 allows a third value, and web maps draw the line flat and ignore it.
Why is my track a MultiLineString?
Your GPX track has more than one segment (trkseg): recording stopped and started again. Each segment becomes one part, so no line is drawn across the gap. A track with a single segment comes out as a plain LineString.
How do I show a Strava or Garmin activity on a Leaflet map?
Export the activity as GPX, convert it here and load the file with L.geoJSON(data).addTo(map). Leaflet draws the track and the waypoints; the times and heart rate stay in the properties for your own popups or charts.
What is the _gpxType property?
togeojson marks every line with where it came from: "trk" for a recorded track and "rte" for a planned route. Waypoints have none. Filter on it when you only want the tracks.
Moving the places into Spatibase? Read how to import your data. Need another pair of formats? The GIS file converter converts between all of them.