7 min read

Display GPX data using R

GPS trackers are now part of the hobby for many hikers. Minaturized devices display maps, track walks, and produce data known as GPS traces. A lot of these hiking traces are available on the web, on open data portals. An exchange format, called GPX for [GPS] e[X]change format, offers a simple way to organize these data into small and pratical files.

A GPS Tracker watch.

A GPS Tracker watch.

A GPX file contains a collection of points, forming a trace when put together. Some metadata like the name, author or copyright of the trace can be added to the file. The city of Grenoble, in southeastern France, publishes a dataset of many hiking routes around its surroundings [1] . Many traces are available as GPX files, free to download and use for hikers.

One of them can be downloaded using this URL:

http://www.grenoble-montagne.com/uploads/Ballade/95/36_216_dent-de-crolles.gpx

This trace shows a path around the Dent de Crolles, Crolles Tooth, a mountain at the north east of Grenoble, France.

Dent de Crolles, Photo: Matthieu Riegler.

Dent de Crolles, Photo: Matthieu Riegler.

Reading GPX data

To load GPX data, the readOGR function from the rgdal [2] package can be used. The function needs teh user to name the layer containing GPS points, in our case track_points.

library(rgdal)
gpx <- rgdal::readOGR("36_216_dent-de-crolles.gpx",
                      layer = "track_points")

The gpx variable is now a SpatialPointsDataFrame object, or an ordered collection of points. It must be converted to a line object to use it as a trace.

From multiple GPS points to a single trace

To convert a SpatialPoints object to a trace, the sp [3] package comes to help. First, 2 dimensional points are converted to GPS coordinates using the coordinates function. Then the Line function concatenates the coordiantes into a single line. To finish, we lay the Line object into a list, assigning an identifier to encapsulate it in a SpatialLines object. Many functions use objects from the SpatialLines class to display or modify traces.

library(sp)
track <- coordinates(gpx)
track <- Line(track)
track <- list(Lines(track, 
                    ID = "Dent de Crolles"))
track <- SpatialLines(track)

Map projection

Map projection is the ensemble of techniques allowing us to display non-flat surface (Like earth) on flat surface (Like maps) [4] . Many projection techniques exist [5], as rectangular maps are not the only available solution for this problem.

In our case, a SpatialLines object must contain the projection metadata of the geodata it carries. This infomrmation can be retreived form the GPX file, using the proj4string function on the gpx variable containing GPX raw data.

proj4string(gpx)
## [1] "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"

This long string contains all the references needed to display the points on a regular map. WGS84 stands for [W]orld [G]eodetic [S]ystem, a standard projection in GPS systems. The projection metadata can now be assigned to the SpatialLines object stored in the track variable.

proj4string(track) <- proj4string(gpx)

Display the trace on a regular map

The leaflet R package [6] makes it easy to display maps using R. It encapsulates the popular Leaflet Javascript library [7].

library(leaflet)

Only a few functions are needed to display the trace. leaflet initialize the map, while addTiles adds the default map background from OpenStreetMap [8]. To finish, addPolylines adds all the lines containes in the track variable.

map <- leaflet()
map <- addTiles(map)
map <- addPolylines(map, data = track)
map

Getting elevation data

Displaying a trace on a 2D can be usefull, but many hikers also need informations about the elevation to plan and prepare for a hike. Elevation is often contained in GPS data. However, points may not all at equal distance from each other. Distance between points is needed to display a correct elevation graph for a given track. For this case, the geosphere package can be used to compute the distance between each point of our track.

library(geosphere)

A distance vector is initialized to 0, as there is no point preceding it, thus a null distance. A simple loop iterates each point of the track, using the distm function to compute the distance of the point from the previous one. The Haversine formula [9] is used here, meaning the shortest distance between two points situated on a sphere.

gpx$distance <- 0

for(i in c(1:nrow(gpx))){
  point <- gpx[i,]
  if(i > 1){
    gpx$distance[i] <- distm(
      coordinates(gpx[(i-1),]),
      coordinates(gpx[i,]),
      fun = distHaversine) +
       gpx$distance[i-1]
  }
}

Finally, the elevation data can be plotted, here using the plotly library.

library(plotly)

p <- plot_ly(data.frame(gpx),
             x=~distance,
             y=~ele,
             mode = 'lines',
             type = 'scatter')
p <- layout(p,
            xaxis = list(title = "Distance (meters)"),
            yaxis = list(title = "Elevation (meters)"))
p

Net gain, Ascent and Descent

Height, Ascent and Descent are aggregated values used by hiker to quickly get an idea of a path difficulty, without looking at the elevation chart we previously plotted.

Height, Ascent and Descent are 3 distinct indicators:

  • Net gain: The difference between arrival elevation and departure elevation.
  • Ascent: The sum of all elevation gains.
  • Descent: The sum of all elevation losses.

Net gain is ineed an easy formula:

max(gpx$ele) - min(gpx$ele)
## [1] 599

To compute ascent and descent, we can use a loop to iterate over each point elevation and add the differences in the right vector.

ascent <- 0
descent <- 0

for(i in c(2:length(gpx$ele))){
  diff <- gpx$ele[i] - gpx$ele[i-1]
  if(diff > 0){
    ascent <- ascent + diff
  } else {
    descent <- descent + diff
  }
}
ascent
## [1] 874
descent
## [1] -874

The Dent de Crolles path starts and finishes in the same place. Ascent and absolute descent are thus the same values.

References

[1] G. Montagne, Parcourir nos massifs, (n.d.). http://www.grenoble-montagne.com/779-parcourir-nos-massifs.htm (accessed April 9, 2019).

[2] R. Bivand, T. Keitt, B. Rowlingson, E. Pebesma, Rgdal: Bindings for the geospatial data abstraction library, R Package Version 0.8-16. (2014).

[3] E. Pebesma, R.S. Bivand, S classes and methods for spatial data: The sp package, Unpublished Report. (2005).

[4] M. kennedy, Understanding map projections, (n.d.). https://kartoweb.itc.nl/geometrics/Map%20projections/Understanding%20Map%20Projections.pdf (accessed April 9, 2019).

[5] Map projections examples, (n.d.). https://www.mapthematics.com/Downloads/Images/Cornucopia33.jpg (accessed April 9, 2019).

[6] Leaflet for r, (n.d.). https://rstudio.github.io/leaflet/ (accessed April 9, 2019).

[7] Leaflet, an open-source javascript library for mobile-friendly interactive maps, (n.d.). https://leafletjs.com/ (accessed April 9, 2019).

[8] OpenStreetMap, (n.d.). https://www.openstreetmap.org (accessed April 9, 2019).

[9] R.W. Sinnott, Virtues of the haversine, Sky Telesc. 68 (1984) 159.