WHIB 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.
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
# Which year do we want to look at?
whichyear = 2023
# 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'
# Default zoom level and circle marker radius for map
zoom_start = 4
radius = 10
# 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)
# 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)
I am reading journey.2024.02.18.csv
# 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)
# 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
len(locations)
106833
# 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)
len(locations)
20689
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)))
2023 had 365 days Which is 8760 hours Assuming 8 hours of sleep (phone off), we then have 3.54 locations per hour
# Drop all values not in a certain month
# locations.drop(locations[locations.Month != 7].index, inplace=True)
locations.head()
Date | Time | Latitude | Longitude | Altitude | Accuray | Year | Month | Day | Weekday | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 2023-01-01 | 11:29 | 46.935380 | 7.417846 | 0 | 30 | 2023 | 1 | 1 | 6 |
1 | 2023-01-01 | 11:33 | 46.935660 | 7.417164 | 0 | 95 | 2023 | 1 | 1 | 6 |
2 | 2023-01-01 | 16:02 | 46.935414 | 7.417844 | 555 | 36 | 2023 | 1 | 1 | 6 |
3 | 2023-01-01 | 16:03 | 46.932391 | 7.420630 | 561 | 177 | 2023 | 1 | 1 | 6 |
4 | 2023-01-01 | 16:06 | 46.932918 | 7.420670 | 555 | 40 | 2023 | 1 | 1 | 6 |
locations.tail()
Date | Time | Latitude | Longitude | Altitude | Accuray | Year | Month | Day | Weekday | |
---|---|---|---|---|---|---|---|---|---|---|
20684 | 2023-12-31 | 17:49 | 46.935278 | 7.417892 | 0 | 18 | 2023 | 12 | 31 | 6 |
20685 | 2023-12-31 | 17:50 | 46.934402 | 7.423731 | 0 | 40 | 2023 | 12 | 31 | 6 |
20686 | 2023-12-31 | 17:51 | 46.934499 | 7.423851 | 563 | 6 | 2023 | 12 | 31 | 6 |
20687 | 2023-12-31 | 17:56 | 46.928927 | 7.446688 | -500 | 50 | 2023 | 12 | 31 | 6 |
20688 | 2023-12-31 | 17:56 | 46.930030 | 7.443794 | -500 | 40 | 2023 | 12 | 31 | 6 |
# 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())
# 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
# 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
# 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 2023, we traveled 910 km north-south In 2023, we traveled 893 km east-west
By using the geocoding library for Python and the OpenStreetMap reverse geocoding tool we can assign addresses to locations.
# 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]]
# Turn debug on to print and thus find the right value of the address
debug = True
# 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']
# 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
{'place_id': 78138001, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'way', 'osm_id': 151747521, 'lat': '46.69611469398588', 'lon': '6.97708964376041', 'class': 'highway', 'type': 'unclassified', 'place_rank': 26, 'importance': 0.10000999999999993, 'addresstype': 'road', 'name': 'Route de la Combetta', 'display_name': 'Route de la Combetta, Massonnens, District de la Glâne, Fribourg/Freiburg, 1692, Schweiz/Suisse/Svizzera/Svizra', 'address': {'road': 'Route de la Combetta', 'village': 'Massonnens', 'county': 'District de la Glâne', 'state': 'Fribourg/Freiburg', 'ISO3166-2-lvl4': 'CH-FR', 'postcode': '1692', 'country': 'Schweiz/Suisse/Svizzera/Svizra', 'country_code': 'ch'}, 'boundingbox': ['46.6880655', '46.6976282', '6.9758992', '6.9843329']} The average location in 2023 was in District de la Glâne
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
{'place_id': 77086287, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'node', 'osm_id': 4779114123, 'lat': '46.9355772', 'lon': '7.4255931', 'class': 'amenity', 'type': 'bench', 'place_rank': 30, 'importance': 9.99999999995449e-06, 'addresstype': 'amenity', 'name': '', 'display_name': 'Steinhölzliweg, Weissenbühl, Stadtteil III, Bern, Verwaltungskreis Bern-Mittelland, Verwaltungsregion Bern-Mittelland, Bern/Berne, 3008, Schweiz/Suisse/Svizzera/Svizra', 'address': {'road': 'Steinhölzliweg', 'quarter': 'Weissenbühl', 'city_district': 'Stadtteil III', 'city': 'Bern', 'county': 'Verwaltungskreis Bern-Mittelland', 'state_district': 'Verwaltungsregion Bern-Mittelland', 'state': 'Bern/Berne', 'ISO3166-2-lvl4': 'CH-BE', 'postcode': '3008', 'country': 'Schweiz/Suisse/Svizzera/Svizra', 'country_code': 'ch'}, 'boundingbox': ['46.9355272', '46.9356272', '7.4255431', '7.4256431']} The median location in 2023 was in Verwaltungskreis Bern-Mittelland
## 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'))
location_north[0]
48.112288
locations[locations.Latitude == location_north[0]]
Date | Time | Latitude | Longitude | Altitude | Accuray | Year | Month | Day | Weekday | |
---|---|---|---|---|---|---|---|---|---|---|
10971 | 2023-07-25 | 13:40 | 48.112288 | 2.237106 | 114 | 4 | 2023 | 7 | 25 | 1 |
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
{'place_id': 84226910, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'way', 'osm_id': 656614555, 'lat': '48.11228140757506', 'lon': '2.2371063064734846', 'class': 'highway', 'type': 'motorway', 'place_rank': 26, 'importance': 0.10000999999999993, 'addresstype': 'road', 'name': 'A 19', 'display_name': 'A 19, Bouzonville-aux-Bois, Pithiviers, Loiret, Centre-Val de Loire, France métropolitaine, 45300, France', 'address': {'road': 'A 19', 'village': 'Bouzonville-aux-Bois', 'municipality': 'Pithiviers', 'county': 'Loiret', 'ISO3166-2-lvl6': 'FR-45', 'state': 'Centre-Val de Loire', 'ISO3166-2-lvl4': 'FR-CVL', 'region': 'France métropolitaine', 'postcode': '45300', 'country': 'France', 'country_code': 'fr'}, 'boundingbox': ['48.1117993', '48.1147889', '2.1962128', '2.2802154']} The northmost location in 2023 was in Pithiviers, France on July, 25
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
{'place_id': 68874435, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'way', 'osm_id': 975813500, 'lat': '42.5450643', 'lon': '9.3889527', 'class': 'highway', 'type': 'tertiary', 'place_rank': 26, 'importance': 0.10000999999999993, 'addresstype': 'road', 'name': 'Route de Borgo Village', 'display_name': 'Route de Borgo Village, Vignale, Bastia, Haute-Corse, Corse, France métropolitaine, 20290, France', 'address': {'road': 'Route de Borgo Village', 'village': 'Vignale', 'municipality': 'Bastia', 'county': 'Haute-Corse', 'ISO3166-2-lvl6': 'FR-2B', 'state': 'Corse', 'ISO3166-2-lvl4': 'FR-20R', 'region': 'France métropolitaine', 'postcode': '20290', 'country': 'France', 'country_code': 'fr'}, 'boundingbox': ['42.5427695', '42.5451103', '9.3883711', '9.3897302']} The most eastern location in 2023 was in Bastia, France on March, 10
name_east
'Bastia'
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
{'place_id': 45500174, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'node', 'osm_id': 4466532708, 'lat': '39.9235028', 'lon': '8.5401437', 'class': 'place', 'type': 'house', 'place_rank': 30, 'importance': 9.99999999995449e-06, 'addresstype': 'place', 'name': '', 'display_name': '206, Corso Italia, Solanas, Crabas/Cabras, Aristanis/Oristano, Sardigna/Sardegna, 09072, Italia', 'address': {'house_number': '206', 'road': 'Corso Italia', 'village': 'Solanas', 'town': 'Crabas/Cabras', 'county': 'Aristanis/Oristano', 'ISO3166-2-lvl6': 'IT-OR', 'state': 'Sardigna/Sardegna', 'ISO3166-2-lvl4': 'IT-88', 'postcode': '09072', 'country': 'Italia', 'country_code': 'it'}, 'boundingbox': ['39.9234528', '39.9235528', '8.5400937', '8.5401937']} The southmost location in 2023 was in Aristanis/Oristano, Italia on October, 13
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
{'place_id': 248613388, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'way', 'osm_id': 34297672, 'lat': '47.02019327999554', 'lon': '-2.300542145953392', 'class': 'highway', 'type': 'residential', 'place_rank': 26, 'importance': 0.10000999999999993, 'addresstype': 'road', 'name': '', 'display_name': "Port de l'Herbaudière, L'Herbaudière, Noirmoutier-en-l'Île, Les Sables-d'Olonne, Vendée, Pays de la Loire, France métropolitaine, 85330, France", 'address': {'neighbourhood': "Port de l'Herbaudière", 'village': "L'Herbaudière", 'municipality': "Les Sables-d'Olonne", 'county': 'Vendée', 'ISO3166-2-lvl6': 'FR-85', 'state': 'Pays de la Loire', 'ISO3166-2-lvl4': 'FR-PDL', 'region': 'France métropolitaine', 'postcode': '85330', 'country': 'France', 'country_code': 'fr'}, 'boundingbox': ['47.0201315', '47.0202054', '-2.3014431', '-2.3004990']} The most western location in 2023 was in Les Sables-d'Olonne, France on July, 30
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
{'place_id': 74652758, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'way', 'osm_id': 1036016462, 'lat': '45.9373465', 'lon': '7.7292823', 'class': 'highway', 'type': 'footway', 'place_rank': 27, 'importance': 0.07500999999999991, 'addresstype': 'road', 'name': 'Matterhorn Glacier Paradise', 'display_name': 'Matterhorn Glacier Paradise, Zermatt, Visp, Valais/Wallis, 3920, Schweiz/Suisse/Svizzera/Svizra', 'address': {'road': 'Matterhorn Glacier Paradise', 'town': 'Zermatt', 'county': 'Visp', 'state': 'Valais/Wallis', 'ISO3166-2-lvl4': 'CH-VS', 'postcode': '3920', 'country': 'Schweiz/Suisse/Svizzera/Svizra', 'country_code': 'ch'}, 'boundingbox': ['45.9373465', '45.9373845', '7.7292823', '7.7294473']} The highest location in 2023 was in Visp, Schweiz/Suisse/Svizzera/Svizra on September, 12
# Show the extreme values in the overview below or not
showextremes = True
# 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
# 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'}
# 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