Skip to content

Instantly share code, notes, and snippets.

@trevphil
Created June 3, 2019 09:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trevphil/ce34450fdab345c864099de0fea2d468 to your computer and use it in GitHub Desktop.
Save trevphil/ce34450fdab345c864099de0fea2d468 to your computer and use it in GitHub Desktop.
Python helper to convert GPX data to CSV
"""
Helper to convert GPX files to CSV files for analysis.
Created by Trevor Phillips
"""
import os
import json
try:
import urllib.request
except:
print('You need to use Python3')
exit()
import xml.etree.ElementTree as ET
from datetime import datetime
from math import sin, cos, sqrt, pi
_API_KEY = os.environ['GOOGLE_ELEVATION_API_KEY']
_BASE_URL = 'https://maps.googleapis.com/maps/api/elevation/json'
class Location(object):
"""Data structure for holding location information"""
EARTH_RADIUS = 6378100 # in meters
def __init__(self, lat, lon, altitude=0.0, time=None):
"""
Create a new instance with the given
latitude, longitude, altitude, and timestamp
"""
self.lat = lat
self.lon = lon
self.alt = altitude
self.time = time
def _as_cartesian(self):
"""Returns a representation of the location in (x, y, z)"""
lat_radians = self.lat * pi / 180
lon_radians = self.lon * pi / 180
radius = Location.EARTH_RADIUS + self.alt
x = radius * cos(lat_radians) * sin(lon_radians)
y = radius * sin(lat_radians)
z = radius * cos(lat_radians) * cos(lon_radians)
return (x, y, z)
def distance_to(self, other_location):
"""Returns the distance in meters to `other_location`"""
x1, y1, z1 = self._as_cartesian()
x2, y2, z2 = other_location._as_cartesian()
return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2)
@staticmethod
def total_distance(locations):
"""Computes the total distance along a path, in meters"""
if len(locations) < 2:
return 0.0
total = 0.0
current_loc = locations[0]
for next_loc in locations[1:]:
total += current_loc.distance_to(next_loc)
current_loc = next_loc
return total
def csv_str(self):
"""String representation for CSV file"""
fmt = "%Y-%m-%d'T'%H:%M:%S'Z'"
time_str = self.time.strftime(fmt) if self.time is not None else ''
return '{},{},{},0,0,-1,-1,{}'.format(
self.lat, self.lon, self.alt, time_str
)
def is_gpx(filepath):
"""Returns true if the filepath is a GPX file otherwise False"""
ext_index = filepath.rfind('.')
if ext_index == -1 or \
filepath[ext_index + 1:].lower() != 'gpx' or \
os.path.isfile(filepath) is False:
return False
return True
def _parse_altitudes(json_object):
"""Parse array of altitudes from Google Elevation API JSON"""
results = json_object['results']
return [item['elevation'] for item in results]
def _write_locations_to_csv(locations, csv_filepath):
"""Write array of Location objects as CSV to the given filepath"""
header = 'latitude,longitude,altitude,horizonal_acc,' + \
'vertical_acc,course,speed,timestamp'
location_strings = [loc.csv_str() for loc in locations]
csv_content = '\n'.join([header] + location_strings)
with open(csv_filepath, 'w+') as csv_file:
csv_file.write(csv_content)
def _is_onthegomap_gpx(filepath):
"""
Returns True if `filepath` is a GPX file from
https://onthegomap.com and False otherwise.
"""
if is_gpx(filepath) is False:
return False
return 'https://onthegomap.com' in open(filepath).read()
def _onthegomap_gpx_to_csv(gpx_filepath, csv_filepath):
"""
Convert a GPX file at location `gpx_filepath` which was generated
using the online tool at https://onthegomap.com into a CSV file
with altitude, latitude, and longitude information, stored at the
given `csv_filepath` (existing file will be overwritten).
"""
root = ET.parse(gpx_filepath).getroot()
prefix = root.tag.replace('gpx', '')
path = './{}rte/{}rtept'.format(prefix, prefix)
locations, location_strings = [], []
for point in root.findall(path):
lat, lon = point.get('lat'), point.get('lon')
locations.append(Location(lat, lon))
location_strings.append('{},{}'.format(lat ,lon))
location_str = '|'.join(location_strings)
altitude_api_query = '{}?locations={}&key={}'.format(
_BASE_URL, location_str, _API_KEY
)
url = urllib.request.urlopen(altitude_api_query)
data = url.read()
encoding = url.info().get_content_charset('utf-8')
json_response = json.loads(data.decode(encoding))
altitudes = _parse_altitudes(json_response)
assert len(altitudes) == len(locations)
for i, loc in enumerate(locations):
loc.alt = altitudes[i]
_write_locations_to_csv(locations, csv_filepath)
def _is_garmin_gpx(filepath):
"""
Returns True if `filepath` is a GPX file from
Garmin and False otherwise.
"""
if is_gpx(filepath) is False:
return False
return 'garmin.com' in open(filepath).read()
def _garmin_gpx_to_csv(gpx_filepath, csv_filepath):
"""
Convert a GPX file at location `gpx_filepath` which was generated
from a Garmin device into a CSV file with altitude, latitude,
longitude, and timestamp information, stored at the
given `csv_filepath` (existing file will be overwritten).
"""
root = ET.parse(gpx_filepath).getroot()
prefix = root.tag.replace('gpx', '')
path = './{}trk/{}trkseg/{}trkpt'.format(prefix, prefix, prefix)
locations = []
for point in root.findall(path):
lat, lon = point.get('lat'), point.get('lon')
alt = point.find('{}ele'.format(prefix)).text
time_str = point.find('{}time'.format(prefix)).text
fmt = "%Y-%m-%dT%H:%M:%S.%fZ"
time = datetime.strptime(time_str, fmt)
locations.append(Location(lat, lon, altitude=alt, time=time))
_write_locations_to_csv(locations, csv_filepath)
def gpx_to_csv(gpx_filepath):
"""
Convert a GPX file at location `gpx_filepath` into a CSV file,
with the same filename before the .gpx extension and in the same
directory as the GPX file (any existing file will be overwritten).
"""
csv_path = gpx_filepath.replace('GPX', 'gpx').replace('.gpx', '.csv')
if _is_garmin_gpx(gpx_filepath):
_garmin_gpx_to_csv(gpx_filepath, csv_path)
elif _is_onthegomap_gpx(gpx_filepath):
_onthegomap_gpx_to_csv(gpx_filepath, csv_path)
else:
print('This GPX format is not supported')
return csv_path
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment