#!/usr/bin/env python # coding: utf-8 # # Where have I been? # [WHIB](http://www.bleatinc.com/) is constantly running on my iPhone and tracks my location with minimal battery impact. # This notebook here parses and display the exported data[^1] from the app into something pretty. # # [^1]: A 2$/month *premium* feature lets you export all the data as CSV, which is easy to parse and display. # In[1]: import os import glob import pandas from datetime import datetime import geopy import geopy.distance import geopy.geocoders from geopy.extra.rate_limiter import RateLimiter import folium import folium.plugins # In[2]: # Which year do we want to look at? whichyear = 2023 # In[3]: # Tile providers # 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' # In[4]: # Default zoom level and circle marker radius for map zoom_start = 4 radius = 10 # In[5]: # Settings for address lookup # Set an unique user agent geopy.geocoders.options.default_user_agent = 'Jahresrückblick Habi. Contact habi@gna.ch if you have an issue with me!' # Be patient geolocator = geopy.geocoders.Nominatim(timeout=5) # Don't be too needy geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1) # In[6]: # Read in locations from newest CSV-file in the current directory file = sorted(glob.glob('journey*.csv'),key=os.path.getmtime)[-1] print('I am reading %s' % file) locations = pandas.read_csv(file) # In[7]: # 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[8]: # Make us a proper date column, based on https://stackoverflow.com/a/26763793 locations['Date'] = pandas.to_datetime(locations['Date'], format='mixed') # 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[9]: len(locations) # In[10]: # Drop all values not in 'whichyear' # https://stackoverflow.com/a/27360130 locations.drop(locations[locations.Year != whichyear].index, inplace=True) # Reset index, so that we can find the correct date lateron locations.reset_index(drop=True, inplace=True) # In[11]: len(locations) # In[12]: start = datetime(day=1, month=1, year=whichyear) finish = datetime(day=1, month=1, year=whichyear+1) time = finish - start print('%s had %s days' % (whichyear, time.days)) print('Which is %s hours' % (time.days * 24)) print('Assuming 8 hours of sleep (phone off), we then have %0.4s locations per hour' % (len(locations) / (time.days * 16))) # In[13]: # Drop all values not in a certain month # locations.drop(locations[locations.Month != 7].index, inplace=True) # In[14]: locations.head() # In[15]: locations.tail() # In[16]: # In 2022, there is one datapoint in Croatia on August 31 # I have no idea how that happened, as I'm very sure I've been at home there :) # Let's just drop this *one* point #print(locations.Longitude.max()) #locations = locations.drop(locations[(locations['Longitude'] == 14.84985) & (locations['Date'] == '2022-08-31')].index) #print(locations.Longitude.max()) # In[17]: # 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 # In[18]: # 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 # In[19]: # 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)) # By using [the geocoding library for Python](https://github.com/geopy/geopy) and the [OpenStreetMap reverse geocoding tool](https://wiki.openstreetmap.org/wiki/Nominatim) we can assign addresses to locations. # In[20]: # 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[21]: # Turn debug on to print and thus find the right value of the address debug = True # In[22]: # According to https://github.com/openstreetmap/Nominatim/issues/885#issuecomment-358123829 # we have to # > Look for the first of 'city', 'town', 'village', 'hamlet', 'suburb' # Let's do this! potentialplacename = ['city', 'town', 'village', 'hamlet', 'county', 'municipality', 'suburb'] # In[23]: # 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 for p in potentialplacename: if location_average_geo.raw.get('address').get(p): name_average = location_average_geo.raw.get('address').get(p) 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*2) 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 # In[24]: location_median_geo = geolocator.reverse(location_median) if debug: print(location_median_geo.raw) for p in potentialplacename: if location_median_geo.raw.get('address').get(p): name_median = location_median_geo.raw.get('address').get(p) 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*2) 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 # In[25]: ## Get day of location, based on # https://stackoverflow.com/a/53979441/323100 # and # https://strftime.org # print(locations[locations.eq(location_north[0]).any(1)]['Date'].tolist()[0].strftime('%B, %-d')) # In[26]: location_north[0] # In[27]: locations[locations.Latitude == location_north[0]] # In[28]: location_north_geo = geolocator.reverse(location_north) if debug: print(location_north_geo.raw) for p in potentialplacename: if location_north_geo.raw.get('address').get(p): name_north = location_north_geo.raw.get('address').get(p) print('The northmost location in %s was in ' '%s, %s on %s' % (whichyear, name_north, location_north_geo.raw.get('address').get('country'), locations[locations.Latitude == location_north[0]]['Date'].tolist()[0].strftime('%B, %-d'))) m = folium.Map(location=[float(location_north_geo.raw.get('lat')), float(location_north_geo.raw.get('lon'))], tiles=tileprovider, zoom_start=zoom_start*2) 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 # In[29]: location_east_geo = geolocator.reverse(location_east) if debug: print(location_east_geo.raw) for p in potentialplacename: if location_east_geo.raw.get('address').get(p): name_east = location_east_geo.raw.get('address').get(p) print('The most eastern location in %s was in ' '%s, %s on %s' % (whichyear, name_east, location_east_geo.raw.get('address').get('country'), locations[locations.Latitude == location_east[0]]['Date'].tolist()[0].strftime('%B, %-d'))) m = folium.Map(location=[float(location_east_geo.raw.get('lat')), float(location_east_geo.raw.get('lon'))], tiles=tileprovider, zoom_start=zoom_start*2) 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 # In[30]: name_east # In[31]: location_south_geo = geolocator.reverse(location_south) if debug: print(location_south_geo.raw) for p in potentialplacename: if location_south_geo.raw.get('address').get(p): name_south = location_south_geo.raw.get('address').get(p) print('The southmost location in %s was in ' '%s, %s on %s' % (whichyear, name_south, location_south_geo.raw.get('address').get('country'), locations[locations.Latitude == location_south[0]]['Date'].tolist()[0].strftime('%B, %-d'))) m = folium.Map(location=[float(location_south_geo.raw.get('lat')), float(location_south_geo.raw.get('lon'))], tiles=tileprovider, zoom_start=zoom_start*2) 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 # In[32]: location_west_geo = geolocator.reverse(location_west) if debug: print(location_west_geo.raw) for p in potentialplacename: if location_west_geo.raw.get('address').get(p): name_west = location_west_geo.raw.get('address').get(p) print('The most western location in %s was in ' '%s, %s on %s' % (whichyear, name_west, location_west_geo.raw.get('address').get('country'), locations[locations.Latitude == location_west[0]]['Date'].tolist()[0].strftime('%B, %-d'))) m = folium.Map(location=[float(location_west_geo.raw.get('lat')), float(location_west_geo.raw.get('lon'))], tiles=tileprovider, zoom_start=zoom_start*2) 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 # In[33]: location_top_geo = geolocator.reverse(location_top) if debug: print(location_top_geo.raw) for p in potentialplacename: if location_top_geo.raw.get('address').get(p): name_top = location_top_geo.raw.get('address').get(p) print('The highest location in %s was in ' '%s, %s on %s' % (whichyear, name_top, location_top_geo.raw.get('address').get('country'), locations[locations.Latitude == location_top[0]]['Date'].tolist()[0].strftime('%B, %-d'))) m = folium.Map(location=[float(location_top_geo.raw.get('lat')), float(location_top_geo.raw.get('lon'))], tiles=tileprovider, zoom_start=zoom_start*2) 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 # In[34]: # Show the extreme values in the overview below or not showextremes = True # In[35]: # Plot *all* locations on a single map m = folium.Map(location=[locations['Latitude'].mean(), locations['Longitude'].mean()], tiles=tileprovider, zoom_start=zoom_start) if showextremes: # 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 % 10: 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 # In[36]: # 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[37]: # Show a heatmap instead of single points m = folium.Map(location=[locations['Latitude'].mean(), locations['Longitude'].mean()], tiles=tileprovider, zoom_start=zoom_start) showextremes=True if showextremes: # 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).add_to(m) m.save('map-heat.html') m # In[ ]: