Add elevation to a GPX file
Routes drawn in a planner often have no heights, and GPS heights are noisy. An elevation API fills in every point from a terrain model. You can get distance, ascent and descent with one curl command, or write the heights into the file with a short Python script.
curl https://api.overpass.ch/v1/YOUR_API_KEY/elevation/profile --data-binary @track.gpx
For a GPX file with an <ele> in every point, use the script below.
Distance, ascent and descent with curl
/elevation/profile reads the track of a GPX file (or its route, or its waypoints, if it has no track), samples it every 90 m and answers with a summary and the heights along the way. For the Matterhorn Trail from Schwarzsee to Furi:
"profile": { "distance_m": 7261.2, "ascent_m": 29.8, "descent_m": 745.5,
"min_m": 1862.5, "max_m": 2579.5, "samples": 82, "interval_m": 89.6 },
"results": [
{ "elevation": 2579.5, "location": { "lat": 45.9910109, "lng": 7.7097862 }, "distance": 0, … },
{ "elevation": 2572.2, "location": { "lat": 45.9909652, "lng": 7.7086596 }, "distance": 90, … },
…
- The samples are what you need to draw a profile:
distanceon the x axis,elevationon the y axis. - Each sample is one lookup, here 82. Set
?interval=(metres, at least 10) or?samples=to change the spacing. - Ascent and descent ignore changes of less than 2 m, which filters out noise in the data.
Write the heights into the GPX file
This script uses gpxpy to read the file, looks up every track, route and waypoint position, and writes a new file with an <ele> for each point:
import os
import sys
import gpxpy
import requests
URL = f"https://api.overpass.ch/v1/{os.environ['OVERPASS_CH_KEY']}/elevation"
BATCH = 1000 # locations per request: 1,000 on the free plan, 10'000 on paid plans
with open(sys.argv[1]) as f:
gpx = gpxpy.parse(f)
points = [p for t in gpx.tracks for s in t.segments for p in s.points]
points += [p for r in gpx.routes for p in r.points] + gpx.waypoints
for i in range(0, len(points), BATCH):
batch = points[i:i + BATCH]
r = requests.post(URL, json={"locations": [[p.latitude, p.longitude] for p in batch]}, timeout=60)
r.raise_for_status()
for p, res in zip(batch, r.json()["results"]):
p.elevation = res["elevation"]
with open(sys.argv[2], "w") as f:
f.write(gpx.to_xml())
print(f"{len(points)} points with heights")
pip install gpxpy requests
OVERPASS_CH_KEY=YOUR_API_KEY python add_elevation.py track.gpx track-with-ele.gpx
- Every point is one lookup. A track recorded once a second over a four-hour hike has about 14,400 points; the free plan includes 1 million lookups a month.
- Existing heights are overwritten. To keep them, skip points where
p.elevationis already set. - The answer keeps the order of the request, so the results line up with the points.
GPS heights or terrain heights?
- GPS heights are less precise than GPS positions, often off by 10 m or more, and they jump around, which inflates the total climb. Barometric altimeters are steadier but drift with the weather.
- Terrain heights from an elevation model are consistent and need no device at all, which is why they suit planned routes. This API uses the Copernicus 90 m model: villages, valleys and paths are typically within a few metres, but summits and narrow ridges read low, and short ups and downs are smoothed. On rugged trails the real climb is therefore somewhat higher.
- For summaries of many tracks, terrain heights make the numbers comparable, because every track is measured the same way.
More about the data and its accuracy is on the Elevation API page. To build profiles from OpenStreetMap routes instead of GPX files, see the hiking route profile example.