{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Where have I been?\n",
"[WHIB](http://www.bleatinc.com/) is constantly running on my iPhone and tracks my location with minimal battery impact.\n",
"This notebook here parses and display the exported data[^1] from the app into something pretty.\n",
"\n",
"[^1]: A 2$/month *premium* feature lets you export all the data as CSV, which is easy to parse and display."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import glob\n",
"import pandas\n",
"from datetime import datetime\n",
"import geopy\n",
"import geopy.distance\n",
"import geopy.geocoders\n",
"from geopy.extra.rate_limiter import RateLimiter\n",
"import folium\n",
"import folium.plugins"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# Which year do we want to look at?\n",
"whichyear = 2023"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# Tile providers\n",
"# Available tiles: https://github.com/python-visualization/folium/blob/master/folium/folium.py#L75\n",
"#tileprovider = 'Mapbox Bright'\n",
"tileprovider = 'Cartodb positron'\n",
"#tileprovider = 'Cartodb dark_matter'\n",
"#tileprovider = 'Mapbox Control Room'\n",
"#tileprovider = 'Stamen Toner'"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# Default zoom level and circle marker radius for map\n",
"zoom_start = 4\n",
"radius = 10"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# Settings for address lookup\n",
"# Set an unique user agent\n",
"geopy.geocoders.options.default_user_agent = 'Jahresrückblick Habi. Contact habi@gna.ch if you have an issue with me!'\n",
"# Be patient\n",
"geolocator = geopy.geocoders.Nominatim(timeout=5)\n",
"# Don't be too needy\n",
"geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"I am reading journey.2024.02.18.csv\n"
]
}
],
"source": [
"# Read in locations from newest CSV-file in the current directory\n",
"file = sorted(glob.glob('journey*.csv'),key=os.path.getmtime)[-1]\n",
"print('I am reading %s' % file)\n",
"locations = pandas.read_csv(file)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Modify the dataframe\n",
"locations.drop(['Crumb'], axis=1, inplace=True)\n",
"locations.rename(columns={'LocalDate': 'Date'}, inplace=True)\n",
"locations.rename(columns={'LocalTime': 'Time'}, inplace=True)\n",
"locations.rename(columns={'Altitude (in metres)': 'Altitude'}, inplace=True)\n",
"locations.rename(columns={'Accuracy (in metres)': 'Accuray'}, inplace=True)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# Make us a proper date column, based on https://stackoverflow.com/a/26763793\n",
"locations['Date'] = pandas.to_datetime(locations['Date'], format='mixed')\n",
"# Make us a year, month and weekday colum, based on https://stackoverflow.com/q/48623332\n",
"locations['Year'] = locations.Date.dt.year\n",
"locations['Month'] = locations.Date.dt.month\n",
"locations['Day'] = locations.Date.dt.day\n",
"locations['Weekday'] = locations.Date.dt.dayofweek"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"106833"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(locations)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# Drop all values not in 'whichyear'\n",
"# https://stackoverflow.com/a/27360130\n",
"locations.drop(locations[locations.Year != whichyear].index, inplace=True)\n",
"# Reset index, so that we can find the correct date lateron\n",
"locations.reset_index(drop=True, inplace=True)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"20689"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(locations)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2023 had 365 days\n",
"Which is 8760 hours\n",
"Assuming 8 hours of sleep (phone off), we then have 3.54 locations per hour\n"
]
}
],
"source": [
"start = datetime(day=1, month=1, year=whichyear)\n",
"finish = datetime(day=1, month=1, year=whichyear+1)\n",
"time = finish - start\n",
"print('%s had %s days' % (whichyear, time.days))\n",
"print('Which is %s hours' % (time.days * 24))\n",
"print('Assuming 8 hours of sleep (phone off), we then have %0.4s locations per hour' % (len(locations) / (time.days * 16)))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"# Drop all values not in a certain month\n",
"# locations.drop(locations[locations.Month != 7].index, inplace=True)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Date | \n",
" Time | \n",
" Latitude | \n",
" Longitude | \n",
" Altitude | \n",
" Accuray | \n",
" Year | \n",
" Month | \n",
" Day | \n",
" Weekday | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 2023-01-01 | \n",
" 11:29 | \n",
" 46.935380 | \n",
" 7.417846 | \n",
" 0 | \n",
" 30 | \n",
" 2023 | \n",
" 1 | \n",
" 1 | \n",
" 6 | \n",
"
\n",
" \n",
" 1 | \n",
" 2023-01-01 | \n",
" 11:33 | \n",
" 46.935660 | \n",
" 7.417164 | \n",
" 0 | \n",
" 95 | \n",
" 2023 | \n",
" 1 | \n",
" 1 | \n",
" 6 | \n",
"
\n",
" \n",
" 2 | \n",
" 2023-01-01 | \n",
" 16:02 | \n",
" 46.935414 | \n",
" 7.417844 | \n",
" 555 | \n",
" 36 | \n",
" 2023 | \n",
" 1 | \n",
" 1 | \n",
" 6 | \n",
"
\n",
" \n",
" 3 | \n",
" 2023-01-01 | \n",
" 16:03 | \n",
" 46.932391 | \n",
" 7.420630 | \n",
" 561 | \n",
" 177 | \n",
" 2023 | \n",
" 1 | \n",
" 1 | \n",
" 6 | \n",
"
\n",
" \n",
" 4 | \n",
" 2023-01-01 | \n",
" 16:06 | \n",
" 46.932918 | \n",
" 7.420670 | \n",
" 555 | \n",
" 40 | \n",
" 2023 | \n",
" 1 | \n",
" 1 | \n",
" 6 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Date Time Latitude Longitude Altitude Accuray Year Month \\\n",
"0 2023-01-01 11:29 46.935380 7.417846 0 30 2023 1 \n",
"1 2023-01-01 11:33 46.935660 7.417164 0 95 2023 1 \n",
"2 2023-01-01 16:02 46.935414 7.417844 555 36 2023 1 \n",
"3 2023-01-01 16:03 46.932391 7.420630 561 177 2023 1 \n",
"4 2023-01-01 16:06 46.932918 7.420670 555 40 2023 1 \n",
"\n",
" Day Weekday \n",
"0 1 6 \n",
"1 1 6 \n",
"2 1 6 \n",
"3 1 6 \n",
"4 1 6 "
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"locations.head()"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Date | \n",
" Time | \n",
" Latitude | \n",
" Longitude | \n",
" Altitude | \n",
" Accuray | \n",
" Year | \n",
" Month | \n",
" Day | \n",
" Weekday | \n",
"
\n",
" \n",
" \n",
" \n",
" 20684 | \n",
" 2023-12-31 | \n",
" 17:49 | \n",
" 46.935278 | \n",
" 7.417892 | \n",
" 0 | \n",
" 18 | \n",
" 2023 | \n",
" 12 | \n",
" 31 | \n",
" 6 | \n",
"
\n",
" \n",
" 20685 | \n",
" 2023-12-31 | \n",
" 17:50 | \n",
" 46.934402 | \n",
" 7.423731 | \n",
" 0 | \n",
" 40 | \n",
" 2023 | \n",
" 12 | \n",
" 31 | \n",
" 6 | \n",
"
\n",
" \n",
" 20686 | \n",
" 2023-12-31 | \n",
" 17:51 | \n",
" 46.934499 | \n",
" 7.423851 | \n",
" 563 | \n",
" 6 | \n",
" 2023 | \n",
" 12 | \n",
" 31 | \n",
" 6 | \n",
"
\n",
" \n",
" 20687 | \n",
" 2023-12-31 | \n",
" 17:56 | \n",
" 46.928927 | \n",
" 7.446688 | \n",
" -500 | \n",
" 50 | \n",
" 2023 | \n",
" 12 | \n",
" 31 | \n",
" 6 | \n",
"
\n",
" \n",
" 20688 | \n",
" 2023-12-31 | \n",
" 17:56 | \n",
" 46.930030 | \n",
" 7.443794 | \n",
" -500 | \n",
" 40 | \n",
" 2023 | \n",
" 12 | \n",
" 31 | \n",
" 6 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Date Time Latitude Longitude Altitude Accuray Year Month \\\n",
"20684 2023-12-31 17:49 46.935278 7.417892 0 18 2023 12 \n",
"20685 2023-12-31 17:50 46.934402 7.423731 0 40 2023 12 \n",
"20686 2023-12-31 17:51 46.934499 7.423851 563 6 2023 12 \n",
"20687 2023-12-31 17:56 46.928927 7.446688 -500 50 2023 12 \n",
"20688 2023-12-31 17:56 46.930030 7.443794 -500 40 2023 12 \n",
"\n",
" Day Weekday \n",
"20684 31 6 \n",
"20685 31 6 \n",
"20686 31 6 \n",
"20687 31 6 \n",
"20688 31 6 "
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"locations.tail()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# In 2022, there is one datapoint in Croatia on August 31\n",
"# I have no idea how that happened, as I'm very sure I've been at home there :)\n",
"# Let's just drop this *one* point\n",
"#print(locations.Longitude.max())\n",
"#locations = locations.drop(locations[(locations['Longitude'] == 14.84985) & (locations['Date'] == '2022-08-31')].index)\n",
"#print(locations.Longitude.max())"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Show the extreme locations on a map\n",
"# Marker colors from here: https://stackoverflow.com/a/41993318\n",
"m = folium.Map(location=[locations['Latitude'].mean(),\n",
" locations['Longitude'].mean()],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start)\n",
"# Altitude\n",
"for c, loc in locations.sort_values(by=['Altitude'],\n",
" ascending=False).head(1).iterrows():\n",
" folium.CircleMarker(location=[loc.Latitude, loc.Longitude],\n",
" radius=radius,\n",
" popup='Highest: %s' % (loc.Date),\n",
" ).add_to(m)\n",
"# North\n",
"for c, loc in locations.sort_values(by=['Latitude'],\n",
" ascending=False).head(1).iterrows():\n",
" folium.CircleMarker(location=[loc.Latitude, loc.Longitude],\n",
" radius=radius,\n",
" popup='North: %s' % (loc.Date),\n",
" ).add_to(m)\n",
"# East\n",
"for c, loc in locations.sort_values(by=['Longitude'],\n",
" ascending=False).head(1).iterrows():\n",
" folium.CircleMarker(location=[loc.Latitude, loc.Longitude],\n",
" radius=radius,\n",
" popup='East: %s' % (loc.Date),\n",
" ).add_to(m)\n",
"# South\n",
"for c, loc in locations.sort_values(by=['Latitude']).head(1).iterrows():\n",
" folium.CircleMarker(location=[loc.Latitude, loc.Longitude],\n",
" radius=radius,\n",
" popup='South: %s' % (loc.Date),\n",
" ).add_to(m)\n",
"# West\n",
"for c, loc in locations.sort_values(by=['Longitude']).head(1).iterrows():\n",
" folium.CircleMarker(location=[loc.Latitude, loc.Longitude],\n",
" radius=radius,\n",
" popup='West: %s' % (loc.Date),\n",
" ).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Show north, east, south, west extreme (with averaged lat/lon for each)\n",
"m = folium.Map(location=[locations['Latitude'].mean(),\n",
" locations['Longitude'].mean()],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start)\n",
"folium.CircleMarker(location=[locations.Latitude.mean(),\n",
" locations.Longitude.min()],\n",
" radius=radius,\n",
" popup='Average West').add_to(m)\n",
"folium.CircleMarker(location=[locations.Latitude.mean(),\n",
" locations.Longitude.max()],\n",
" radius=radius,\n",
" popup='Average East').add_to(m)\n",
"folium.CircleMarker(location=[locations.Latitude.max(),\n",
" locations.Longitude.mean()],\n",
" radius=radius,\n",
" popup='Average North').add_to(m)\n",
"folium.CircleMarker(location=[locations.Latitude.min(),\n",
" locations.Longitude.mean()],\n",
" radius=radius,\n",
" popup='Average South').add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In 2023, we traveled 910 km north-south\n",
"In 2023, we traveled 893 km east-west\n"
]
}
],
"source": [
"# How far did we come?\n",
"print('In %s, we traveled %0.0f km north-south' % (whichyear,\n",
" geopy.distance.geodesic([locations.Latitude.min(),\n",
" locations.Longitude.mean()],\n",
" [locations.Latitude.max(),\n",
" locations.Longitude.mean()]).km))\n",
"print('In %s, we traveled %0.0f km east-west' % (whichyear,\n",
" geopy.distance.geodesic([locations.Latitude.mean(),\n",
" locations.Longitude.min()],\n",
" [locations.Latitude.mean(),\n",
" locations.Longitude.max()]).km))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"# Get all extrema locations\n",
"location_average = [locations['Latitude'].mean(), locations['Longitude'].mean()]\n",
"location_median = [locations['Latitude'].median(), locations['Longitude'].median()]\n",
"location_north = [locations[locations.Latitude == locations.Latitude.max()].Latitude.values[0],\n",
" locations[locations.Latitude == locations.Latitude.max()].Longitude.values[0]]\n",
"location_east = [locations[locations.Longitude == locations.Longitude.max()].Latitude.values[0],\n",
" locations[locations.Longitude == locations.Longitude.max()].Longitude.values[0]]\n",
"location_south = [locations[locations.Latitude == locations.Latitude.min()].Latitude.values[0],\n",
" locations[locations.Latitude == locations.Latitude.min()].Longitude.values[0]]\n",
"location_west = [locations[locations.Longitude == locations.Longitude.min()].Latitude.values[0],\n",
" locations[locations.Longitude == locations.Longitude.min()].Longitude.values[0]]\n",
"location_top = [locations[locations['Altitude'] == locations['Altitude'].max()].Latitude.values[0],\n",
" locations[locations['Altitude'] == locations['Altitude'].max()].Longitude.values[0]]"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"# Turn debug on to print and thus find the right value of the address\n",
"debug = True"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"# According to https://github.com/openstreetmap/Nominatim/issues/885#issuecomment-358123829\n",
"# we have to\n",
"# > Look for the first of 'city', 'town', 'village', 'hamlet', 'suburb'\n",
"# Let's do this!\n",
"potentialplacename = ['city', 'town', 'village', 'hamlet', 'county', 'municipality', 'suburb']"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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']}\n",
"The average location in 2023 was in District de la Glâne\n"
]
},
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Address lookup with `geopy` and https://wiki.openstreetmap.org/wiki/Nominatim\n",
"location_average_geo = geolocator.reverse(location_average)\n",
"# Print the details, so we can get the correct value to save below\n",
"if debug:\n",
" print(location_average_geo.raw)\n",
"# Save us a name and print it\n",
"for p in potentialplacename:\n",
" if location_average_geo.raw.get('address').get(p):\n",
" name_average = location_average_geo.raw.get('address').get(p)\n",
"print('The average location in %s was in %s' % (whichyear, name_average))\n",
"# Show the point on a map\n",
"m = folium.Map(location=[float(location_average_geo.raw.get('lat')),\n",
" float(location_average_geo.raw.get('lon'))],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start*2)\n",
"folium.CircleMarker(location=[float(location_average_geo.raw.get('lat')),\n",
" float(location_average_geo.raw.get('lon'))],\n",
" radius=radius,\n",
" popup=name_average).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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']}\n",
"The median location in 2023 was in Verwaltungskreis Bern-Mittelland\n"
]
},
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"location_median_geo = geolocator.reverse(location_median)\n",
"if debug:\n",
" print(location_median_geo.raw)\n",
"for p in potentialplacename:\n",
" if location_median_geo.raw.get('address').get(p):\n",
" name_median = location_median_geo.raw.get('address').get(p)\n",
"print('The median location in %s was in %s' % (whichyear, name_median))\n",
"m = folium.Map(location=[float(location_median_geo.raw.get('lat')),\n",
" float(location_median_geo.raw.get('lon'))],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start*2)\n",
"folium.CircleMarker(location=[float(location_median_geo.raw.get('lat')),\n",
" float(location_median_geo.raw.get('lon'))],\n",
" radius=radius,\n",
" popup=name_median).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"## Get day of location, based on \n",
"# https://stackoverflow.com/a/53979441/323100\n",
"# and \n",
"# https://strftime.org\n",
"# print(locations[locations.eq(location_north[0]).any(1)]['Date'].tolist()[0].strftime('%B, %-d'))"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"48.112288"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"location_north[0]"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Date | \n",
" Time | \n",
" Latitude | \n",
" Longitude | \n",
" Altitude | \n",
" Accuray | \n",
" Year | \n",
" Month | \n",
" Day | \n",
" Weekday | \n",
"
\n",
" \n",
" \n",
" \n",
" 10971 | \n",
" 2023-07-25 | \n",
" 13:40 | \n",
" 48.112288 | \n",
" 2.237106 | \n",
" 114 | \n",
" 4 | \n",
" 2023 | \n",
" 7 | \n",
" 25 | \n",
" 1 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Date Time Latitude Longitude Altitude Accuray Year Month \\\n",
"10971 2023-07-25 13:40 48.112288 2.237106 114 4 2023 7 \n",
"\n",
" Day Weekday \n",
"10971 25 1 "
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"locations[locations.Latitude == location_north[0]]"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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']}\n",
"The northmost location in 2023 was in Pithiviers, France on July, 25\n"
]
},
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"location_north_geo = geolocator.reverse(location_north)\n",
"if debug:\n",
" print(location_north_geo.raw)\n",
"for p in potentialplacename:\n",
" if location_north_geo.raw.get('address').get(p):\n",
" name_north = location_north_geo.raw.get('address').get(p)\n",
"print('The northmost location in %s was in '\n",
" '%s, %s on %s' % (whichyear,\n",
" name_north,\n",
" location_north_geo.raw.get('address').get('country'),\n",
" locations[locations.Latitude == location_north[0]]['Date'].tolist()[0].strftime('%B, %-d')))\n",
"m = folium.Map(location=[float(location_north_geo.raw.get('lat')),\n",
" float(location_north_geo.raw.get('lon'))],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start*2)\n",
"folium.CircleMarker(location=[float(location_north_geo.raw.get('lat')),\n",
" float(location_north_geo.raw.get('lon'))],\n",
" radius=radius,\n",
" popup=name_north).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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']}\n",
"The most eastern location in 2023 was in Bastia, France on March, 10\n"
]
},
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"location_east_geo = geolocator.reverse(location_east)\n",
"if debug:\n",
" print(location_east_geo.raw)\n",
"for p in potentialplacename:\n",
" if location_east_geo.raw.get('address').get(p):\n",
" name_east = location_east_geo.raw.get('address').get(p)\n",
"print('The most eastern location in %s was in '\n",
" '%s, %s on %s' % (whichyear,\n",
" name_east,\n",
" location_east_geo.raw.get('address').get('country'),\n",
" locations[locations.Latitude == location_east[0]]['Date'].tolist()[0].strftime('%B, %-d')))\n",
"m = folium.Map(location=[float(location_east_geo.raw.get('lat')),\n",
" float(location_east_geo.raw.get('lon'))],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start*2)\n",
"folium.CircleMarker(location=[float(location_east_geo.raw.get('lat')),\n",
" float(location_east_geo.raw.get('lon'))],\n",
" radius=radius,\n",
" popup=name_east).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Bastia'"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"name_east"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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']}\n",
"The southmost location in 2023 was in Aristanis/Oristano, Italia on October, 13\n"
]
},
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"location_south_geo = geolocator.reverse(location_south)\n",
"if debug:\n",
" print(location_south_geo.raw)\n",
"for p in potentialplacename:\n",
" if location_south_geo.raw.get('address').get(p):\n",
" name_south = location_south_geo.raw.get('address').get(p)\n",
"print('The southmost location in %s was in '\n",
" '%s, %s on %s' % (whichyear,\n",
" name_south,\n",
" location_south_geo.raw.get('address').get('country'),\n",
" locations[locations.Latitude == location_south[0]]['Date'].tolist()[0].strftime('%B, %-d')))\n",
"m = folium.Map(location=[float(location_south_geo.raw.get('lat')),\n",
" float(location_south_geo.raw.get('lon'))],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start*2)\n",
"folium.CircleMarker(location=[float(location_south_geo.raw.get('lat')),\n",
" float(location_south_geo.raw.get('lon'))],\n",
" radius=radius,\n",
" popup=name_south).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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']}\n",
"The most western location in 2023 was in Les Sables-d'Olonne, France on July, 30\n"
]
},
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"location_west_geo = geolocator.reverse(location_west)\n",
"if debug:\n",
" print(location_west_geo.raw)\n",
"for p in potentialplacename:\n",
" if location_west_geo.raw.get('address').get(p):\n",
" name_west = location_west_geo.raw.get('address').get(p)\n",
"print('The most western location in %s was in '\n",
" '%s, %s on %s' % (whichyear,\n",
" name_west,\n",
" location_west_geo.raw.get('address').get('country'),\n",
" locations[locations.Latitude == location_west[0]]['Date'].tolist()[0].strftime('%B, %-d')))\n",
"m = folium.Map(location=[float(location_west_geo.raw.get('lat')),\n",
" float(location_west_geo.raw.get('lon'))],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start*2)\n",
"folium.CircleMarker(location=[float(location_west_geo.raw.get('lat')),\n",
" float(location_west_geo.raw.get('lon'))],\n",
" radius=radius,\n",
" popup=name_west).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'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']}\n",
"The highest location in 2023 was in Visp, Schweiz/Suisse/Svizzera/Svizra on September, 12\n"
]
},
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"location_top_geo = geolocator.reverse(location_top)\n",
"if debug:\n",
" print(location_top_geo.raw)\n",
"for p in potentialplacename:\n",
" if location_top_geo.raw.get('address').get(p):\n",
" name_top = location_top_geo.raw.get('address').get(p)\n",
"print('The highest location in %s was in '\n",
" '%s, %s on %s' % (whichyear,\n",
" name_top,\n",
" location_top_geo.raw.get('address').get('country'),\n",
" locations[locations.Latitude == location_top[0]]['Date'].tolist()[0].strftime('%B, %-d')))\n",
"m = folium.Map(location=[float(location_top_geo.raw.get('lat')),\n",
" float(location_top_geo.raw.get('lon'))],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start*2)\n",
"folium.CircleMarker(location=[float(location_top_geo.raw.get('lat')),\n",
" float(location_top_geo.raw.get('lon'))],\n",
" radius=radius,\n",
" popup=name_top).add_to(m)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [],
"source": [
"# Show the extreme values in the overview below or not\n",
"showextremes = True"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Plot *all* locations on a single map\n",
"m = folium.Map(location=[locations['Latitude'].mean(),\n",
" locations['Longitude'].mean()],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start)\n",
"if showextremes:\n",
" # Extreme locations\n",
" folium.CircleMarker(location=location_average,\n",
" radius=radius,\n",
" popup='Average location in %s' % name_average).add_to(m)\n",
" folium.CircleMarker(location=location_north,\n",
" radius=radius,\n",
" popup='Northmost location in %s' % name_north).add_to(m)\n",
" folium.CircleMarker(location=location_east,\n",
" radius=radius,\n",
" popup='Northmost location in %s' % name_east).add_to(m)\n",
" folium.CircleMarker(location=location_south,\n",
" radius=radius,\n",
" popup='Northmost location in %s' % name_south).add_to(m)\n",
" folium.CircleMarker(location=location_west,\n",
" radius=radius,\n",
" popup='Northmost location in %s' % name_west).add_to(m)\n",
" folium.CircleMarker(location=location_top,\n",
" radius=radius,\n",
" popup='Highest location (%s m) on the %s' % (locations[locations['Altitude'] == locations['Altitude'].max()].Altitude.values[0],\n",
" name_top)).add_to(m)\n",
"# All locations, in different ways\n",
"singlepoints = False\n",
"fast = True\n",
"if singlepoints:\n",
" for c, loc in locations.iterrows():\n",
" # not every point, but every x-th one, or else the map is too slow\n",
" if not c % 10:\n",
" folium.CircleMarker(location=[loc.Latitude, loc.Longitude],\n",
" radius=2,\n",
" popup='%s@%s' % (loc.Date, loc.Time),\n",
" color='darkred'\n",
" ).add_to(m)\n",
"else:\n",
" if fast:\n",
" # FastMarkerCluster\n",
" m.add_child(folium.plugins.FastMarkerCluster(locations[['Latitude', 'Longitude']].values.tolist()))\n",
" else:\n",
" # Markercluster\n",
" mc = folium.plugins.MarkerCluster()\n",
" for c, loc in locations.iterrows():\n",
" mc.add_child(folium.CircleMarker(location=[loc.Latitude, loc.Longitude],\n",
" popup='%s@%s' % (loc.Date, loc.Time)))\n",
" m.add_child(mc)\n",
"m.save('map-points.html')\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [],
"source": [
"# Viridis colormap from here: https://www.thedataschool.co.uk/gwilym-lockwood/viridis-colours-tableau/\n",
"gradient={0.00: '#440154FF',\n",
" 0.05: '#481567FF',\n",
" 0.10: '#482677FF',\n",
" 0.15: '#453781FF',\n",
" 0.20: '#404788FF',\n",
" 0.25: '#39568CFF',\n",
" 0.30: '#33638DFF',\n",
" 0.35: '#2D708EFF',\n",
" 0.40: '#287D8EFF',\n",
" 0.45: '#238A8DFF',\n",
" 0.50: '#1F968BFF',\n",
" 0.55: '#20A387FF',\n",
" 0.60: '#29AF7FFF',\n",
" 0.65: '#3CBB75FF',\n",
" 0.70: '#55C667FF',\n",
" 0.75: '#73D055FF',\n",
" 0.80: '#95D840FF',\n",
" 0.85: '#B8DE29FF',\n",
" 0.90: '#DCE319FF',\n",
" 0.95: '#FDE725FF'}"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Make this Notebook Trusted to load map: File -> Trust Notebook
"
],
"text/plain": [
""
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Show a heatmap instead of single points\n",
"m = folium.Map(location=[locations['Latitude'].mean(),\n",
" locations['Longitude'].mean()],\n",
" tiles=tileprovider,\n",
" zoom_start=zoom_start)\n",
"showextremes=True\n",
"if showextremes:\n",
" # Extreme locations\n",
" folium.CircleMarker(location=location_average,\n",
" radius=radius,\n",
" popup='Average location in %s' % name_average,\n",
" ).add_to(m)\n",
" folium.CircleMarker(location=location_north,\n",
" radius=radius,\n",
" popup='Northmost location in %s' % name_north,\n",
" ).add_to(m)\n",
" folium.CircleMarker(location=location_east,\n",
" radius=radius,\n",
" popup='Most eastern location in %s' % name_east,\n",
" ).add_to(m)\n",
" folium.CircleMarker(location=location_south,\n",
" radius=radius,\n",
" popup='Southmost location in %s' % name_south,\n",
" ).add_to(m)\n",
" folium.CircleMarker(location=location_west,\n",
" radius=radius,\n",
" popup='Most western location in %s' % name_west,\n",
" ).add_to(m)\n",
" folium.CircleMarker(location=location_top,\n",
" radius=radius,\n",
" popup='Highest location (%s m) on the %s' % (locations[locations['Altitude'] == locations['Altitude'].max()].Altitude.values[0],\n",
" name_top),\n",
" ).add_to(m)\n",
"# Add heatmap\n",
"folium.plugins.HeatMap(locations[['Latitude', 'Longitude']].values.tolist(),\n",
" gradient=gradient).add_to(m)\n",
"m.save('map-heat.html')\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
}
},
"nbformat": 4,
"nbformat_minor": 4
}