GPX in ArcGIS Pro

GPS Exchange Format (GPX) is a widely-used file format used to store and share GPS data. Many GPS-enabled devices store tracking information in GPX files.

Outdoor athletes (runners, hikers, cyclists) can find recording and analysis of tracking data especially useful assessing performance in fitness activities.

Commercially, this moves into the realm of telematics, where delivery or logistics companies like UPS or Amazon can use analysis of vehicle tracking data to improve inefficient processes (or employees), reduce costs, and increase profits.

Smartphone Apps

There are a variety of free smartphone apps that can be used to capture and map tracking data, although not all apps provide the capability to download GPX files. Some notable ones with download capability include:

Open GPX Tracker (left - iPhone) and Geo Tracker (right - Android)

GPX FIles

The "X" in GPX also alludes to eXtensible Markup Language (XML), which is a general data storage format commonly used by a variety of software. The format is human-readable, although some expertise is needed to interpret the contents of an XML-based file.

XML in a GPX file

Waypoints

Although GPX files can contain a variety of different types of data, the data of primary interest is usually a sequence of waypoints, which are GPS latitude/longitude locations that are regularly captured by the tracking app as it records.

Waypoints also commonly include the date and time when they were recorded and an elevation supplied by GPS.

The approximate paths of travel can be connecting the waypoints with lines. Speed can be estimated based on the distance between waypoints divided by the difference in time between waypoints.

Waypoints

Units

GPX uses the WGS 1984 coordinate system used by the GPS system for latitude and longitude in degrees. ArcGIS Pro will map WGS 1984 data as unprojected latitude and longitude by default. Because unprojected WGS 1984 distorts width the further north you go from the equator, and you will probably want to change the map projection to something more cartographically appropriate, like a Mercator or state plane coordinate system.

The elevation field in the GPX file will probably be the height in meters above the WGS 84 ellipsoid, which is what is provided in the GPS signal used to create the waypoints.

Note that depending on where you are in the world, this may be different from the elevation specified by national geological agencies. In the US, elevations published by USGS are commonly specified as feet above mean sea level, which may be slightly different from the GPS elevations (ESRI 2003).

Elevation data is also subject to the same sources of error and error fluctuation as GPS latitudes and longitudes. Elevation readings are commonly off by as much as +/- 400 feet (Garmin 2022).

Factors affecting GPS accuracy and availability

Alternative File Formats

There are often other file formats that tracking apps may offer as download options. Unless you have a specific need to use one of these, you should probably stick to GPX, since these alternative formats may store the tracking data in nonstandard ways that (depending on your task) may require manual data cleaning or manipulation to use.

Mapping in ArcGIS Pro

GPX files can be imported into new feature classes using the GPX to Features tool.

Mapping a GPX file

Publish to a Web Map

You can share your data as an interactive map in ArcGIS Online.

Sharing the GPX points as a feature service

Statistics

Track Distance

You can get the distance of the line created from the waypoints with the Calculate Geometry tool.

Calculating track distance

Elapsed Time

Calculating elapsed time

Elevation Distribution

To get a histogram of the distribution of elevations on your track, including mean and standard deviation:

Creating an elevation histogram

Gradient Chart

Changes in elevation (slope or grade) are significant for outdoor athletes since moving uphill to a higher elevation requires a greater expenditure of energy and can reduce speed.

Changes in elevation are also important for large freight vehicles as especially steep grades can be difficult to negotiate both in climbing uphill and braking downhill.

A gradient chart can be used to show the changes in elevation along the track.

Creating an gradient chart

Speed

If your GPX file does not contain a speed field, you can use a Python script with ArcPy to calculate speed using distances between successive waypoints and differences in time.

import arcpy

# Specify the waypoint feature class

points = "Meadowbrook_Points"


# Add the speed field if it doesn't already exist

if len(arcpy.ListFields(points, "Speed")) == 0:
        arcpy.management.AddField(points, "Speed", "FLOAT")


# Create a cursor to scan the waypoints

cursor = arcpy.da.UpdateCursor(points, ["SHAPE@X", "SHAPE@Y", "DateTime", "Speed"])


# Track the previous waypoint to calculate distance and time from the prior waypoint.

prior_waypoint = None


# Loop through each waypoint

for waypoint in cursor:

        if prior_waypoint == None:
            prior_waypoint = waypoint
            continue


	# Calculate the distance since the prior waypoint using a line segment

        wgs84 = arcpy.SpatialReference(4326)

        start_point = arcpy.Point(prior_waypoint[0], prior_waypoint[1])

        end_point = arcpy.Point(waypoint[0], waypoint[1])

        segment = arcpy.Polyline(arcpy.Array([start_point, end_point]), wgs84)

        distance = segment.getLength("GEODESIC", "METERS")


	# Calculate the time since the prior waypoint for speed = distance / time

        timechange = waypoint[2] - prior_waypoint[2];

        speed = 0

        if timechange.seconds > 0:
                speed = distance * 0.000621371 * 3600 / timechange.seconds


	# Update the waypoint speed field

        waypoint[3] = speed

        cursor.updateRow(waypoint)
        

	# This waypoint will be the prior waypoint in the next loop interation

        prior_waypoint = waypoint


# Delete the cursor to write the features and free any editing locks.

del cursor

We can insert this code into a notebook and run it to create the new layer.

Calculating speed and grade

Infographic

All of this information can be placed on an infographic.

Infographic

Animation

ArcGIS Pro has the capability to create animations of data that has a time information (time-enabled data) which you can export to files for upload to websites or video sites like YouTube.

Time-Enable the Layer

Your waypoint date and time information will probably come in as the DateTime field.

Time-enabling a layer

Configure the Animation

Configuring the animation

Export the Video

Exporting the animation to a video file

Real-Time Tracking

GPX files are snapshots of past activities. However, there are situations where you might want to have real-time information about where someone or something is, such as keeping track of where your children are.

ArcGIS Field Maps is an app that helps "mobile workers perform data collection and editing, find assets and information, and report their real-time location.s"

You can create ArcGIS Online maps with location sharing and observer where people using that map are located.

ArcGIS Field Maps

Appendix: Speed Script Components

Conditional Field Addition

You can use ListFields() to see if a field already exists before AddField().

This example from the speed script above checks for the existence of the Speed parameter (perhaps from a prior execution of the script) before adding the field.

if len(arcpy.ListFields(points, "Speed")) == 0:
        arcpy.management.AddField(points, "Speed", "FLOAT")

The existence check may not be necessary since AddField() seems to fail silently if the field already exists.

For Loops

for loops are used to iterate through collections of objects.

Iterators are collections of objects. Iterators can be simple Python lists, or objects that return successive values (like the cursors used above).

Python uses indentation to define blocks of code that are controlled by statements like if or for. All code below the for statement is indentented to indicate that it is in the block of code that should be executed in each loop of the for statement.

In this example, this for loop iterates through the values in a list and prints them.

for x in [1, 3, 5, 7]:

	print(x)

Output:

1
3
5
7

Cursors

In ArcPy, a cursor is a Python iterator that can be used in a for loop to step through the features in a feature class.

There are three types of cursors that are created with the following constructors:

The parameters to the cursor constructors are the same.

After the conclusion of the for loop, you should del the cursor to commit any changes to the feature class and free up any sharing locks.

In this example, this search cursor iterates over the waypoints in the GPX feature class used above and prints the DateTime for each waypoint.

points = "Meadowbrook_Points"

cursor = arcpy.da.SearchCursor(points, ["DateTime"])

for waypoint in cursor:

	print(waypoint[0])

del cursor

Distance

To calculate the distance between points, you need to create Point() objects for both points, construct a Polyline() between the two points, and getLength() for the polyline segment.

In the example below,

start_lat = 40.109081594512006

start_long = -88.22720031510285

end_lat = 40.10616484494814

end_long = -88.2271889482995

wgs84 = arcpy.SpatialReference(4326)

start_point = arcpy.Point(start_long, start_lat)

end_point = arcpy.Point(end_long, end_lat)

segment = arcpy.Polyline(arcpy.Array([start_point, end_point]), wgs84)

distance = segment.getLength("GEODESIC", "METERS")

print(distance)

Time Calculations

Dates and times in feature classes are returned as Python datetime objects. Use of these objects permits easier decomposition of date and time into components (like months, minutes, or seconds) and also facilitates calculations using timedelta objects.

In this example, we calculate the distance between two times.

start_time = datetime.datetime.strptime("2022-08-13 21:18:12", "%Y-%m-%d %H:%M:%S")

end_time = datetime.datetime.strptime("2022-08-13 21:40:05", "%Y-%m-%d %H:%M:%S")

print(start_time)

print(end_time)

difference = end_time - start_time

print(difference.seconds)

Appendix: Merging Files with ArcPy

If you have multiple GPX files that you want to import into a single feature class, this ArcPY script will read all GPX files in the gpx_directory and combine them in a feature class specified in the output variable.

import os

import arcpy

aprx = arcpy.mp.ArcGISProject('current')

gpx_directory = "U:/Downloads"

output = aprx.defaultGeodatabase + "/gpx_points"

filecount = 0

for filename in os.listdir(gpx_directory):
	if ".gpx" not in filename:
		continue

	filepath = gpx_directory + "/" + filename

	filecount = filecount + 1

	if filecount == 1:
		arcpy.conversion.GPXtoFeatures(filepath, output)
		continue

	memory_features = "memory/temp"

	arcpy.conversion.GPXtoFeatures(filepath, memory_features)

	arcpy.management.Append(memory_features, output)

	arcpy.management.Delete(memory_features)

Appendix: Line Segments

An alternative to adding a speed field to the waypoints feature class is creating a separate feature class of line segments between waypoints.

# Parameters

points = "Meadowbrook_Points"

segments = "Meadowbrook_Segments"


# Create the line segment feature class.

wgs84 = arcpy.SpatialReference(4326)

arcpy.management.CreateFeatureclass("", segments, "Polyline", "", "", "", wgs84)


# Add the fields.

arcpy.management.AddField(segments, "DateTime", "DATE")

arcpy.management.AddField(segments, "Elevation", "FLOAT")

arcpy.management.AddField(segments, "Distance", "FLOAT")

arcpy.management.AddField(segments, "Speed", "FLOAT")


# Create the cursor to scan the waypoints

incursor = arcpy.da.SearchCursor(points, \
            ["SHAPE@X", "SHAPE@Y", "Elevation", "DateTime"])


# Create the cursor to add the segments

outcursor = arcpy.da.InsertCursor(segments, \
            ["SHAPE@", "DateTime", "Elevation", "Distance", "Speed"])

# Tracking variables for the loop

prior_waypoint = None

totaldistance = 0

for waypoint in incursor:

	# Since lines are between points, keep track of the prior point

	if prior_waypoint == None:
		prior_waypoint = waypoint
		continue


	# Create the line segment

	startpoint = arcpy.Point(prior_waypoint[0], prior_waypoint[1])

	endpoint = arcpy.Point(waypoint[0], waypoint[1])

	segment = arcpy.Polyline(arcpy.Array([startpoint, endpoint]), wgs84)


	# Calculate the distance covered by the line segment

	distance = segment.getLength("GEODESIC", "METERS")

	totaldistance = totaldistance + distance


	# Calculate the time difference to calculate speed

	timechange = waypoint[3] - prior_waypoint[3];

	speed = 0

	if timechange.seconds > 0:
		speed = distance * 0.000621371 * 60 * 60 / timechange.seconds


	# Add the new line segment

	outcursor.insertRow([segment, waypoint[3], waypoint[2], totaldistance, speed])

	prior_waypoint = waypoint


# Delete your cursors to write your new features and release the sharing locks.

del incursor

del outcursor