Since OpenPaths is dead, we switched to WHIB, which wasn't owned by Facebook (or actually also dead now) and doesn't drain the battery of my phone quickly. A 2$ premium feature lets you export all the data as CSV, which is easy to parse and display.

In [1]:
import glob
import pandas
import geopy
import geopy.distance
import geopy.geocoders
import folium
import folium.plugins
In [2]:
# Which year do we want to look at?
whichyear = 2018
In [3]:
# Map details
# Available tiles: https://github.com/python-visualization/folium/blob/master/folium/folium.py#L75
#tileprovider = 'Mapbox Bright'
tileprovider = 'Cartodb positron'
#tileprovider = 'Cartodb dark_matter'
#tileprovider = 'Mapbox Control Room'
# tileprovider = 'Stamen Toner'
# Default zoom level and circle marker radius
zoom_start = 5
radius = 10
In [4]:
# Settings for address lookup
#geopy.geocoders.options.default_user_agent = 'Jahresrückblick Habi'
geolocator = geopy.geocoders.Nominatim()
/miniconda3/lib/python3.7/site-packages/ipykernel_launcher.py:3: DeprecationWarning: Using Nominatim with the default "geopy/1.18.1" `user_agent` is strongly discouraged, as it violates Nominatim's ToS https://operations.osmfoundation.org/policies/nominatim/ and may possibly cause 403 and 429 HTTP errors. Please specify a custom `user_agent` with `Nominatim(user_agent="my-application")` or by overriding the default `user_agent`: `geopy.geocoders.options.default_user_agent = "my-application"`. In geopy 2.0 this will become an exception.
  This is separate from the ipykernel package so we can avoid doing imports until
In [5]:
# Read in locations from newest CSV-file in the current directory
locations = pandas.read_csv(sorted(glob.glob('journey*.csv'))[-1])
In [6]:
# Modify the dataframe
locations.drop(['Crumb'], axis=1, inplace=True)
locations.rename(columns={'LocalDate': 'Date'}, inplace=True)
locations.rename(columns={'LocalTime': 'Time'}, inplace=True)
locations.rename(columns={'Altitude (in metres)': 'Altitude'}, inplace=True)
locations.rename(columns={'Accuracy (in metres)': 'Accuray'}, inplace=True)
In [7]:
# Make us a proper date column, based on https://stackoverflow.com/a/26763793
locations['Date'] = pandas.to_datetime(locations['Date'])
# Make us a year, month and weekday colum, based on https://stackoverflow.com/q/48623332
locations['Year'] = locations.Date.dt.year
locations['Month'] = locations.Date.dt.month
locations['Day'] = locations.Date.dt.day
locations['Weekday'] = locations.Date.dt.dayofweek
In [8]:
# Drop all values not in 'whichyear'
# https://stackoverflow.com/a/27360130
locations.drop(locations[locations.Year != whichyear].index, inplace=True)
In [9]:
locations.head()
Out[9]:
Date Time Latitude Longitude Altitude Accuray Year Month Day Weekday
0 2018-03-02 23:37 46.935401 7.418162 553 65 2018 3 2 4
1 2018-04-02 09:01 46.935344 7.417897 553 50 2018 4 2 0
2 2018-04-02 09:02 46.932949 7.422317 553 57 2018 4 2 0
3 2018-04-02 09:12 46.932855 7.422384 565 38 2018 4 2 0
4 2018-04-02 09:14 46.923639 7.415233 565 61 2018 4 2 0
In [10]:
locations.tail()
Out[10]:
Date Time Latitude Longitude Altitude Accuray Year Month Day Weekday
13838 2018-12-31 22:50 46.929801 7.439736 547 65 2018 12 31 0
13839 2018-12-31 22:56 46.935350 7.418254 0 98 2018 12 31 0
13840 2018-12-31 22:56 46.935573 7.418067 0 0 2018 12 31 0
13841 2018-12-31 22:56 46.935573 7.418067 0 0 2018 12 31 0
13842 2018-12-31 23:09 46.935413 7.418000 551 153 2018 12 31 0
In [11]:
locations.describe()
Out[11]:
Latitude Longitude Altitude Accuray Year Month Day Weekday
count 13843.000000 13843.000000 13843.000000 13843.000000 13843.0 13843.000000 13843.000000 13843.000000
mean 46.540946 7.650320 524.597414 205.699921 2018.0 7.272195 16.447519 3.087626
std 1.686693 0.574224 403.992156 2293.595890 0.0 3.253913 8.427627 1.956113
min 39.118591 6.630272 -143.000000 0.000000 2018.0 1.000000 2.000000 0.000000
25% 46.932957 7.418067 483.000000 43.000000 2018.0 5.000000 10.000000 2.000000
50% 46.939099 7.428873 547.000000 65.000000 2018.0 8.000000 16.000000 3.000000
75% 46.949052 7.451925 555.000000 81.000000 2018.0 10.000000 24.000000 5.000000
max 49.479696 9.788838 2641.000000 149000.000000 2018.0 12.000000 31.000000 6.000000
In [12]:
# Show the extreme locations on a map
# Marker colors from here: https://stackoverflow.com/a/41993318
m = folium.Map(location=[locations['Latitude'].mean(),
                         locations['Longitude'].mean()],
               tiles=tileprovider,
               zoom_start=zoom_start)
# Altitude
for c, loc in locations.sort_values(by=['Altitude'], ascending=False).head(1).iterrows():
    folium.CircleMarker(location=[loc.Latitude, loc.Longitude],
                        radius=radius,
                        popup='Highest: %s' % (loc.Date),
                       ).add_to(m)
# North
for c, loc in locations.sort_values(by=['Latitude'], ascending=False).head(1).iterrows():
    folium.CircleMarker(location=[loc.Latitude, loc.Longitude],
                        radius=radius,
                        popup='North: %s' % (loc.Date),
                       ).add_to(m)
# East
for c, loc in locations.sort_values(by=['Longitude'], ascending=False).head(1).iterrows():
    folium.CircleMarker(location=[loc.Latitude, loc.Longitude],
                        radius=radius,
                        popup='East: %s' % (loc.Date),
                       ).add_to(m)
# South
for c, loc in locations.sort_values(by=['Latitude']).head(1).iterrows():
    folium.CircleMarker(location=[loc.Latitude, loc.Longitude],
                        radius=radius,
                        popup='South: %s' % (loc.Date),
                       ).add_to(m)
# West
for c, loc in locations.sort_values(by=['Longitude']).head(1).iterrows():
    folium.CircleMarker(location=[loc.Latitude, loc.Longitude],
                        radius=radius,
                        popup='West: %s' % (loc.Date),
                       ).add_to(m)
m
Out[12]:
In [13]:
# Show north, east, south, west extreme (with averaged lat/lon for each)
m = folium.Map(location=[locations['Latitude'].mean(),
                         locations['Longitude'].mean()],
               tiles=tileprovider,
               zoom_start=zoom_start)
folium.CircleMarker(location=[locations.Latitude.mean(), locations.Longitude.min()],
                    radius=radius,
                    popup='Average West').add_to(m)
folium.CircleMarker(location=[locations.Latitude.mean(), locations.Longitude.max()],
                    radius=radius,
                    popup='Average East').add_to(m)
folium.CircleMarker(location=[locations.Latitude.max(), locations.Longitude.mean()],
                    radius=radius,
                    popup='Average North').add_to(m)
folium.CircleMarker(location=[locations.Latitude.min(), locations.Longitude.mean()],
                    radius=radius,
                    popup='Average South').add_to(m)
m
Out[13]:
In [14]:
# How far did we come?
print('In %s, we traveled %0.0f km north-south' % (whichyear,
                                                   geopy.distance.geodesic([locations.Latitude.min(), locations.Longitude.mean()],
                                                                         [locations.Latitude.max(), locations.Longitude.mean()]).km))
print('In %s, we traveled %0.0f km east-west' % (whichyear,
                                                 geopy.distance.geodesic([locations.Latitude.mean(), locations.Longitude.min()],
                                                                         [locations.Latitude.mean(), locations.Longitude.max()]).km))
In 2018, we traveled 1151 km north-south
In 2018, we traveled 242 km east-west

By using the geocoding library for Python and the OpenStreetMap reverse geocoding tool we can assign addresses to locations.

In [15]:
# Get all extrema locations
location_average = [locations['Latitude'].mean(), locations['Longitude'].mean()]
location_median = [locations['Latitude'].median(), locations['Longitude'].median()]
location_north = [locations[locations.Latitude == locations.Latitude.max()].Latitude.values[0],
                  locations[locations.Latitude == locations.Latitude.max()].Longitude.values[0]]
location_east = [locations[locations.Longitude == locations.Longitude.max()].Latitude.values[0],
                 locations[locations.Longitude == locations.Longitude.max()].Longitude.values[0]]
location_south = [locations[locations.Latitude == locations.Latitude.min()].Latitude.values[0],
                  locations[locations.Latitude == locations.Latitude.min()].Longitude.values[0]]
location_west = [locations[locations.Longitude == locations.Longitude.min()].Latitude.values[0],
                 locations[locations.Longitude == locations.Longitude.min()].Longitude.values[0]]
location_top = [locations[locations['Altitude'] == locations['Altitude'].max()].Latitude.values[0],
                locations[locations['Altitude'] == locations['Altitude'].max()].Longitude.values[0]]
In [16]:
# Turn debug on to print and thus find the right value of the address
debug = False
In [17]:
# Address lookup with `geopy` and https://wiki.openstreetmap.org/wiki/Nominatim
location_average_geo = geolocator.reverse(location_average)
# Print the details, so we can get the correct value to save below
if debug:
    print(location_average_geo.raw)
# Save us a name and print it
name_average = location_average_geo.raw.get('address').get('village')
print('The average location in %s was in %s' % (whichyear, name_average))
# Show the point on a map
m = folium.Map(location=[float(location_average_geo.raw.get('lat')),
                         float(location_average_geo.raw.get('lon'))],
               tiles=tileprovider,
               zoom_start=zoom_start*3)
folium.CircleMarker(location=[float(location_average_geo.raw.get('lat')),
                              float(location_average_geo.raw.get('lon'))],
                    radius=radius,
                    popup=name_average).add_to(m)
m
The average location in 2018 was in Kandergrund
Out[17]:
In [18]:
location_median_geo = geolocator.reverse(location_median)
if debug:
    print(location_median_geo.raw)
name_median = location_median_geo.raw.get('address').get('suburb')
print('The median location in %s was in %s' % (whichyear, name_median))
m = folium.Map(location=[float(location_median_geo.raw.get('lat')),
                         float(location_median_geo.raw.get('lon'))],
               tiles=tileprovider,
               zoom_start=zoom_start*3)
folium.CircleMarker(location=[float(location_median_geo.raw.get('lat')),
                              float(location_median_geo.raw.get('lon'))],
                    radius=radius,
                    popup=name_median).add_to(m)
m
The median location in 2018 was in Weissenbühl
Out[18]:
In [19]:
location_north_geo = geolocator.reverse(location_north)
if debug:
    print(location_north_geo.raw)
name_north = location_north_geo.raw.get('address').get('city')
print('The northmost location in %s was in %s' % (whichyear, name_north))
m = folium.Map(location=[float(location_north_geo.raw.get('lat')),
                         float(location_north_geo.raw.get('lon'))],
               tiles=tileprovider,
               zoom_start=zoom_start*3)
folium.CircleMarker(location=[float(location_north_geo.raw.get('lat')),
                              float(location_north_geo.raw.get('lon'))],
                    radius=radius,
                    popup=name_north).add_to(m)
m
The northmost location in 2018 was in Mannheim
Out[19]:
In [20]:
location_east_geo = geolocator.reverse(location_east)
if debug:
    print(location_east_geo.raw)
name_east = location_east_geo.raw.get('address').get('town')
print('The most eastern location in %s was in %s' % (whichyear, name_east))
m = folium.Map(location=[float(location_east_geo.raw.get('lat')),
                         float(location_east_geo.raw.get('lon'))],
               tiles=tileprovider,
               zoom_start=zoom_start*3)
folium.CircleMarker(location=[float(location_east_geo.raw.get('lat')),
                              float(location_east_geo.raw.get('lon'))],
                    radius=radius,
                    popup=name_east).add_to(m)
m
The most eastern location in 2018 was in Thiniscole/Siniscola
Out[20]:
In [30]:
location_south_geo = geolocator.reverse(location_south)
if debug:
    print(location_south_geo.raw)
name_south = location_south_geo.raw.get('address').get('beach')
print('The southmost location in %s was at the %s' % (whichyear, name_south))
m = folium.Map(location=[float(location_south_geo.raw.get('lat')),
                         float(location_south_geo.raw.get('lon'))],
               tiles=tileprovider,
               zoom_start=zoom_start*3)
folium.CircleMarker(location=[float(location_south_geo.raw.get('lat')),
                              float(location_south_geo.raw.get('lon'))],
                    radius=radius,
                    popup=name_south).add_to(m)
m
{'place_id': '198231354', 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', 'osm_type': 'relation', 'osm_id': '1851526', 'lat': '39.11600835', 'lon': '9.51917775228553', 'display_name': 'Spiaggia di Porto Giunco, Via Notteri, Crabonaxa/Villasimius, SU, SAR, 09049, Italia', 'address': {'beach': 'Spiaggia di Porto Giunco', 'road': 'Via Notteri', 'village': 'Crabonaxa/Villasimius', 'county': 'SU', 'state': 'SAR', 'postcode': '09049', 'country': 'Italia', 'country_code': 'it'}, 'boundingbox': ['39.1114548', '39.1208526', '9.5177113', '9.5232052']}
The southmost location in 2018 was at the Spiaggia di Porto Giunco
Out[30]:
In [22]:
location_west_geo = geolocator.reverse(location_west)
if debug:
    print(location_west_geo.raw)
name_west = location_west_geo.raw.get('address').get('town')
print('The most western location in %s was in %s' % (whichyear, name_west))
print('We were actually in Geneva in January, but lost three weeks of data due to a (now-fixed) but in WHIB.')
m = folium.Map(location=[float(location_west_geo.raw.get('lat')),
                         float(location_west_geo.raw.get('lon'))],
               tiles=tileprovider,
               zoom_start=zoom_start*3)
folium.CircleMarker(location=[float(location_west_geo.raw.get('lat')),
                              float(location_west_geo.raw.get('lon'))],
                    radius=radius,
                    popup=name_west).add_to(m)
m
The most western location in 2018 was in Yverdon
We were actually in Geneva in January, but lost three weeks of data due to a (now-fixed) but in WHIB.
Out[22]:
In [23]:
location_top_geo = geolocator.reverse(location_top)
if debug:
    print(location_top_geo.raw)
name_top = location_top_geo.raw.get('address').get('path')
print('The highest location in %s was on the %s with %s masl.' % (whichyear,
                                                                  name_top,
                                                                  locations[locations['Altitude'] == locations['Altitude'].max()].Altitude.values[0]))
m = folium.Map(location=[float(location_top_geo.raw.get('lat')),
                         float(location_top_geo.raw.get('lon'))],
               tiles=tileprovider,
               zoom_start=zoom_start*3)
folium.CircleMarker(location=[float(location_top_geo.raw.get('lat')),
                              float(location_top_geo.raw.get('lon'))],
                    radius=radius,
                    popup=name_top).add_to(m)
m
The highest location in 2018 was on the Bettmergrat-Höhenweg with 2641 masl.
Out[23]:
In [24]:
# Plot *all* locations on a single map
m = folium.Map(location=[locations['Latitude'].mean(),
                         locations['Longitude'].mean()],
               tiles=tileprovider,
               zoom_start=zoom_start)
# Extreme locations
folium.CircleMarker(location=location_average,
                    radius=radius,
                    popup='Average location in %s' % name_average).add_to(m)
folium.CircleMarker(location=location_north,
                    radius=radius,
                    popup='Northmost location in %s' % name_north).add_to(m)
folium.CircleMarker(location=location_east,
                    radius=radius,
                    popup='Northmost location in %s' % name_east).add_to(m)
folium.CircleMarker(location=location_south,
                    radius=radius,
                    popup='Northmost location in %s' % name_south).add_to(m)
folium.CircleMarker(location=location_west,
                    radius=radius,
                    popup='Northmost location in %s' % name_west).add_to(m)
folium.CircleMarker(location=location_top,
                    radius=radius,
                    popup='Highest location (%s m) on the %s' % (locations[locations['Altitude'] == locations['Altitude'].max()].Altitude.values[0],
                                                                 name_top)).add_to(m)
# All locations, in different ways
singlepoints = False
fast = True
if singlepoints:
    for c, loc in locations.iterrows():
        # not every point, but every x-th one, or else the map is too slow
        if not c % 50:
            folium.CircleMarker(location=[loc.Latitude, loc.Longitude],
                                radius=2,
                                popup='%s@%s' % (loc.Date, loc.Time),
                                color='darkred'
                               ).add_to(m)
else:
    if fast:
        # FastMarkerCluster
        m.add_child(folium.plugins.FastMarkerCluster(locations[['Latitude', 'Longitude']].values.tolist()))
    else:
        # Markercluster
        mc = folium.plugins.MarkerCluster()
        for c, loc in locations.iterrows():
            mc.add_child(folium.CircleMarker(location=[loc.Latitude, loc.Longitude],
                                             popup='%s@%s' % (loc.Date, loc.Time)))
        m.add_child(mc)
m.save('map-points.html')
m
Out[24]:
In [25]:
# Viridis colormap from here: https://www.thedataschool.co.uk/gwilym-lockwood/viridis-colours-tableau/
gradient={0.00: '#440154FF',
          0.05: '#481567FF',
          0.10: '#482677FF',
          0.15: '#453781FF',
          0.20: '#404788FF',
          0.25: '#39568CFF',
          0.30: '#33638DFF',
          0.35: '#2D708EFF',
          0.40: '#287D8EFF',
          0.45: '#238A8DFF',
          0.50: '#1F968BFF',
          0.55: '#20A387FF',
          0.60: '#29AF7FFF',
          0.65: '#3CBB75FF',
          0.70: '#55C667FF',
          0.75: '#73D055FF',
          0.80: '#95D840FF',
          0.85: '#B8DE29FF',
          0.90: '#DCE319FF',
          0.95: '#FDE725FF'}
In [26]:
# Show a heatmap instead of single points
m = folium.Map(location=[locations['Latitude'].mean(),
                         locations['Longitude'].mean()],
               tiles=tileprovider,
               zoom_start=zoom_start)
# Extreme locations
folium.CircleMarker(location=location_average,
                    radius=radius,
                    popup='Average location in %s' % name_average,
                   ).add_to(m)
folium.CircleMarker(location=location_north,
                    radius=radius,
                    popup='Northmost location in %s' % name_north,
                   ).add_to(m)
folium.CircleMarker(location=location_east,
                    radius=radius,
                    popup='Most eastern location in %s' % name_east,
                   ).add_to(m)
folium.CircleMarker(location=location_south,
                    radius=radius,
                    popup='Southmost location in %s' % name_south,
                   ).add_to(m)
folium.CircleMarker(location=location_west,
                    radius=radius,
                    popup='Most western location in %s' % name_west,
                   ).add_to(m)
folium.CircleMarker(location=location_top,
                    radius=radius,
                    popup='Highest location (%s m) on the %s' % (locations[locations['Altitude'] == locations['Altitude'].max()].Altitude.values[0],
                                                                 name_top),
                   ).add_to(m)
# Add heatmap
folium.plugins.HeatMap(locations[['Latitude', 'Longitude']].values.tolist(),
                       gradient=gradient, blur=0).add_to(m)
m.save('map-heat.html')
m
Out[26]: