Adding elevation to a .gpx file

.gpx files often come without elevation data, or they use a low resolution dataset that’s small enough to pre-load onto a device like a sports watch. With a bit of Python code and the GPXZ API, it’s easy to add high-resolution elevation data to your activity files.

First, use gpxpy to read the gpx file.

import gpxpy

# Load gpx.
gpx_path = 'run.gpx'
with open(gpx_path) as f:
    gpx = gpxpy.parse(f)

Next, here’s a fuction that takes latitude and longitude coordinates, and returns hi-resolution elevation data from the GPXZ API

import numpy as np
import requests

API_KEY = 'ak_demo_1234'  # Get a free key from www.gpxz.io
BATCH_SIZE = 50  # 512 for paid customers.


def gpxz_elevation(lats, lons):
    '''Iterate over the coordinates in chunks, querying the GPXZ api to return
    a list of elevations in the same order.'''
    elevations = []
    n_chunks = int(len(lats) // BATCH_SIZE)  + 1
    lat_chunks = np.array_split(lats, n_chunks) 
    lon_chunks = np.array_split(lons, n_chunks)
    for lat_chunk, lon_chunk in zip(lat_chunks, lon_chunks):
        latlons = '|'.join(f'{lat},{lon}' for lat, lon in zip(lat_chunk, lon_chunk))
        response = requests.post(
            'https://api.gpxz.io/v1/elevation/points', 
            headers={'x-api-key': API_KEY},
            data={'latlons': latlons},
        )
        response.raise_for_status()
        elevations += [r['elevation'] for r in response.json()['results']]
    return elevations

With these pieces in place, we’ll loop over all the points in the gpxz file, get the elevation for all of them, then save the modified file:

# Find points.
points = list(gpx.walk(only_points=True))
latitudes = [p.latitude for p in points]
longitudes = [p.longitude for p in points]

# Update elevations.
elevations = gpxz_elevation(latitudes, longitudes)
for point, elevation in zip(points, elevations):
    point.elevation = elevation

# Save gpx file.
with open('run.with_elevation.gpx', 'w') as f:
    f.write(gpx.to_xml())

The full code looks like

import gpxpy
import numpy as np
import requests


API_KEY = 'ak_demo_1234'  # Get a free key from www.gpxz.io.
BATCH_SIZE = 50  # 512 for paid customers.


def gpxz_elevation(lats, lons):
    '''Iterate over the coordinates in chunks, querying the GPXZ api to return
    a list of elevations in the same order.'''
    elevations = []
    n_chunks = int(len(lats) // BATCH_SIZE)  + 1
    lat_chunks = np.array_split(lats, n_chunks) 
    lon_chunks = np.array_split(lons, n_chunks)
    for lat_chunk, lon_chunk in zip(lat_chunks, lon_chunks):
        latlons = '|'.join(f'{lat},{lon}' for lat, lon in zip(lat_chunk, lon_chunk))
        response = requests.post(
            'https://api.gpxz.io/v1/elevation/points', 
            headers={'x-api-key': API_KEY},
            data={'latlons': latlons},
        )
        response.raise_for_status()
        elevations += [r['elevation'] for r in response.json()['results']]
    return elevations


def add_elevation_to_gpx(gpx_path, output_path='with_elevation.gpx'):
    # Load gpx.
    gpx_path = 'run.gpx'
    with open(gpx_path) as f:
        gpx = gpxpy.parse(f)

    # Find points.
    points = list(gpx.walk(only_points=True))
    latitudes = [p.latitude for p in points]
    longitudes = [p.longitude for p in points]

    # Update elevations.
    elevations = gpxz_elevation(latitudes, longitudes)
    for point, elevation in zip(points, elevations):
        point.elevation = elevation

    # Save gpx file.
    with open(output_path, 'w') as f:
        f.write(gpx.to_xml())