{ "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
DateTimeLatitudeLongitudeAltitudeAccurayYearMonthDayWeekday
02023-01-0111:2946.9353807.4178460302023116
12023-01-0111:3346.9356607.4171640952023116
22023-01-0116:0246.9354147.417844555362023116
32023-01-0116:0346.9323917.4206305611772023116
42023-01-0116:0646.9329187.420670555402023116
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
DateTimeLatitudeLongitudeAltitudeAccurayYearMonthDayWeekday
206842023-12-3117:4946.9352787.417892018202312316
206852023-12-3117:5046.9344027.423731040202312316
206862023-12-3117:5146.9344997.4238515636202312316
206872023-12-3117:5646.9289277.446688-50050202312316
206882023-12-3117:5646.9300307.443794-50040202312316
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
DateTimeLatitudeLongitudeAltitudeAccurayYearMonthDayWeekday
109712023-07-2513:4048.1122882.237106114420237251
\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 }