The idea is to manage a long-term database of waypoints and routes, and to allow interactive exploration on an actual map. In order to guarantee long-term compatibility and to minimise the risk of loss, the waypoints and routes can be stored as csv and gpx files, locally or in the cloud.

For my current application, I have a database of outdoor locations, which I have recorded through years of hiking and riding in several countries. This data includes camping locations, nices swims and interesting routs. The main requisites are that this data needs to be stored persistently for a long time (I wouldn’t like to lose it!) and be easy to access and explore. One obvious service which fulfils these constraints to some extent is Google My Maps. This service allows the creation of custom maps, which can display routes and waypoints. However, each map has a limit of 10 layers of data, which soon becomes restrictiv and the idea of splitting the data in separate maps does not sound appealing.

This motivated searching for an agile way to store all the GPS data locally and to create an interactive map without any reasonble size limits (as is the case in Google My Maps mentioned above). After a bit of research, Python and Folium seemed like a good choice.

Open an interactive OpenStreetMap

With Folium, we can open an interactive OpenStreetMap window centered at the coordinates and level of zoom or our choice. In this case, we will focus on the south of the UK.

import os
import numpy as np
import pandas as pd
import folium
from folium.features import CustomIcon
import gpxpy
m = folium.Map(location=[50.9, -1.35], zoom_start=8)
m

The idea is to store all our GPS data in a local file structure which can be backed up in the cloud. For this matter, Dropbox or similar services seem convenient.

Then, using Python, we can loop over all the waypoints and routes in the database and include them in the interactive map automatically. This is quick and works fine with moderate amounts of GPS data, which is perfectly fine for my use case.

The current folder structure for my outdoor database is as follows:

  • swimming.csv file with name, latitude, longitude and description fields
  • camping.csv file with name, latitude, longitude and description fields
  • interestPoints.csv file with name, latitude, longitude and description fields
  • bikeRoutes folder filled with gpx files
  • walkingRoutes folder filled with gpx files

We can now bulk-load each type of data to the map, using custom icons and colours.

Bulk-load waypoints

# Custom icons for each type of waypoint
wpData = {'interestPoints': 'https://user-images.githubusercontent.com/4785303/37106149-f7ecae4c-2228-11e8-87ee-c54af8e2be0f.png',
           'commercialCamping': 'https://user-images.githubusercontent.com/4785303/36684818-702a2022-1b19-11e8-8c09-93854492c986.png',
           'wildSwimming': 'https://user-images.githubusercontent.com/4785303/36684819-7047b4a2-1b19-11e8-894e-32719427605a.png'}
def loadWaypoints(fileName, icon_url, m):
    data = pd.read_csv('../exampleDatabase/{}.csv'.format(fileName))
    for index, row in data.iterrows():
        folium.Marker(location=[row['lat'], row['long']],
                      popup='<b>{}</b> <br> {}'.format(
                          row['name'], row['description']) \
                             if row['description'] is not np.nan \
                             else '<b>{}</b>'.format(row['name']),
                      icon=CustomIcon(icon_url,
                                      icon_size=(20, 20))
                      ).add_to(m)
for fileName, icon_url in wpData.items():
    loadWaypoints(fileName, icon_url, m)
m

Bulk load routes

We can do the same with the routes, which get converted from the gpx file to a latitude,longitude pairs.

# Custom colours for each route type
routeData = {'walkRoutes': 'blue',
             'bikeRoutes': 'red'}
def loadRoute(fileName, colour, name, m):
    # Load all data from gpx file
    gpx_file = open(fileName, 'r')
    gpx = gpxpy.parse(gpx_file)
    locs = [] # List of lists with [lat, long] for each track and route
    names = []

    for track in gpx.tracks:
        temp_locs = []
        names.append(track.name)
        for segment in track.segments:
            for point in segment.points:
                temp_locs.append([point.latitude, point.longitude])
        locs.append(temp_locs)

    for route in gpx.routes:
        temp_locs = []
        names.append(route.name)
        for point in route.points:
            temp_locs.append([point.latitude, point.longitude])
        locs.append(temp_locs)

    # Plot each route with Polylines
    for i, loc_set in enumerate(locs):
        folium.PolyLine(locations=loc_set,
                        popup=names[i].replace("'","\\'"),
                        color=colour
                       ).add_to(m);
# Bike routes
bikeRoutesDir = '../exampleDatabase/bikeRoutes/'
for fileName in os.listdir(bikeRoutesDir):
    loadRoute(os.path.join(bikeRoutesDir, fileName), routeData['bikeRoutes'], 'lallala', m)
# Walking routes
walkRoutesDir = '../exampleDatabase/walkRoutes/'
for fileName in os.listdir(walkRoutesDir):
    loadRoute(os.path.join(walkRoutesDir, fileName), routeData['walkRoutes'], 'lallala', m)
m

Save map

Once our map is loaded with the desired information, we can export it to a standalone html file. Note however that it requires an Internet connection in order to zoom in and provide higher level of detail.

m.save('exampleMap.html')

Conclusion

The presented code is able to load a responsive interactive map which allows the easy visualisation of a collection of GPS data. By keeping GPS data locally in csv and gpx files, we ensure long term compatibility. The current file structure is easy to backup in the cloud, e.g. one can use Dropbox straight away. The library Folium provides much more functionality, such as richer pop-ups with images and interactive html code, which can make the map more useful.


License: MIT

The code snippets accompanying this project are available under an MIT license.