@@ -2471,6 +2510,11 @@ Changelog
Version 0
This is the pre-release series. Things haven't settled yet, so each minor version might introduce breaking changes.
Version 0.33
+Version 0.33.3
+
+- GH-200: Fix startup without any activities.
+- Fix upload when there is no
Activities
directory.
+
Version 0.33.2
- GH-198: Fix explorer map. The problem was that VS Code auto-formatted the embedded JavaScript and created syntax errors.
diff --git a/search/search_index.json b/search/search_index.json
index fdf2c9e3..2e86fb5d 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Geo Activity Playground","text":"This is a software to view recorded outdoor activities and derive various insights from your data collection. All data is kept on your machine, hence it is suitable for people who have an affinity for data analysis and privacy.
It caters to serve a similar purpose as Strava with Statshunters statshunters does but while not requiring you to share your location data with a service provider.
One can use this program with a local collection of activity files (GPX, FIT, TCX, KML, CSV) or via the Strava API. The latter is appealing to people who want to keep their data with Strava primarily. In case that one wants to move, this might provide a noncommital way of testing out this project.
The main user interface is web-based, you can run this on your Linux, Mac or Windows laptop. If you want, it can also run on a home server or your personal cloud instance.
"},{"location":"#screenshot-tour","title":"Screenshot tour","text":"This is the view of a single activity:
You also get a beautiful interactive heatmap of all your activities:
Also there are plenty of summary statistics that lets you track how many rides, walks or hikes you have done:
If you're into explorer tiles or squadratinhos, this got you covered:
The configuration options are available within the interface such that you do not have to work with configuration files (like in earlier versions):
"},{"location":"#get-started","title":"Get started","text":"Install the software using one of the following options:
- Using the stable version on Linux
- Using the stable version on Windows
- Using the development version on Linux
Get your activity data in place using one of the following options:
- Use local activity files if you already have these.
- Use the Strava API if you want to use this project to analyse your data on Strava.
- Move from Strava if you wan to leave Strava and work locally from then on.
"},{"location":"#free-software","title":"Free software","text":"You can find the code on GitHub where you can also file issues. If you would like to use this yourself or contribute, feel free to reach out via the contact options from my website. I would especially appreciate improvements to the documentation. If you're familiar with Markdown and GitHub, you can also directly create a pull request. The code is licensed under the very permissive MIT license.
"},{"location":"acknowledgments/","title":"Acknowledgments","text":"This project builds on many amazing other projects and would not be possible without them.
"},{"location":"acknowledgments/#bootstrap-css","title":"Bootstrap CSS","text":"Writing CSS is not a trivial task. For many projects I have been using the Bootstrap CSS Framework which provides sensible default values, a 12-column grid system and a lot of components. Using this I didn't have to write any CSS myself and just attach a couple of classes to HTML elements.
"},{"location":"acknowledgments/#coloredlogs","title":"coloredlogs","text":"Log messages in multiple colors are neat. Using the coloredlogs package we can get these super easily.
"},{"location":"acknowledgments/#fitdecode","title":"fitdecode","text":"For reading FIT files I use the fitdecode library which completely handles all the parsing of this file format.
"},{"location":"acknowledgments/#flask","title":"Flask","text":"The webserver is implemented with Flask which provides a really easy way to get started. It also ships with a development webserver which is enough for this project at the moment.
"},{"location":"acknowledgments/#geojson","title":"GeoJSON","text":"Transferring geographic geometry data from the Python code to Leaflet is easiest with using the GeoJSON format. The official standard RFC is a bit hard to read, rather have a look at the Wikipedia article. And there is an online viewer that you can try out.
"},{"location":"acknowledgments/#github","title":"GitHub","text":"For a smooth open source project one needs a place to share the code and collect issues. GitHub provides all of this for free.
"},{"location":"acknowledgments/#gpxpy","title":"gpxpy","text":"For reading GPX files I use the gpxpy library. This allows me to read those files without having to fiddle with the underlying XML format.
"},{"location":"acknowledgments/#leaflet","title":"Leaflet","text":"The interactive maps on the website are powered by Leaflet, a very easy to use JavaScript library for embedding interactive Open Street Map maps. It can also display GeoJSON geometries natively, of which I also make heavy use.
"},{"location":"acknowledgments/#mkdocs","title":"MkDocs","text":"Writing documentation is more fun with a nice tool, therefore I use MkDocs together with Material for MkDocs. This powers this documentation.
"},{"location":"acknowledgments/#open-street-map","title":"Open Street Map","text":"All the maps displayed use tiles from the amazing Open Street Map. This map is created by volunteers, the server hosting is for free. Without these maps this project would be quite boring.
"},{"location":"acknowledgments/#pandas","title":"Pandas","text":"Working with thousands of activities, thousands of tiles and millions of points makes it necessary to have a good library for number crunching structured data. Pandas offers this and gives good performance and many features.
"},{"location":"acknowledgments/#parquet","title":"Parquet","text":"I need to store the intermediate data frames that I generate with Pandas. Storing as JSON has disadvantages because dates are not properly encoded. Also it is a text format and quite verbose. The Parquet format is super fast and memory efficient.
"},{"location":"acknowledgments/#poetry","title":"Poetry","text":"For managing all the Python package dependencies I use Poetry which makes it very easy to have all the Python project housekeeping with one tool.
"},{"location":"acknowledgments/#python","title":"Python","text":"Almost all of the code here is written in Python, a very nice and versatile programming language with a vast ecosystem of packages.
"},{"location":"acknowledgments/#requests","title":"Requests","text":"For doing HTTP requests I use the Requests library. It provides a really easy to use interface for GET and POST requests.
"},{"location":"acknowledgments/#scikit-learn","title":"Scikit-learn","text":"Finding out which cluster is the largest one can either be formed as a graph search problem or as a data science problem. Using the Scikit-learn library I can easily use the DBSCAN algorithm to find the clusters of explorer tiles.
"},{"location":"acknowledgments/#statshunters","title":"Statshunters","text":"The Statshunters page allows to import the activities from Strava and do analysis like explorer tiles, Eddington number and many other things. This has served as inspiration for this project.
"},{"location":"acknowledgments/#strava","title":"Strava","text":"Although I have recorded some of my bike rides, I only really started to record all of them when I started to use Strava. This is a nice platform to track all activities. They also offer a social network feature, which I don't really use. They provide some analyses of the data, but they lack some analyses which I have now implemented in this project.
"},{"location":"acknowledgments/#stravalib","title":"stravalib","text":"Strava has an API, and with stravalib there exists a nice Python wrapper. This makes it much easier to interface with Strava.
"},{"location":"acknowledgments/#strava-local-heatmap","title":"Strava local heatmap","text":"https://github.com/remisalmon/Strava-local-heatmap
"},{"location":"acknowledgments/#tcxreader","title":"tcxreader","text":"https://github.com/alenrajsp/tcxreader
"},{"location":"acknowledgments/#vega-altair","title":"Vega & Altair","text":"https://altair-viz.github.io/index.html https://vega.github.io/vega/
"},{"location":"acknowledgments/#velo-viewer","title":"Velo Viewer","text":""},{"location":"features/activity-view/","title":"Activity View","text":"When you have selected a particular activity, you can view various details about it. This is what the screen looks like, we will go through the different parts in the following.
"},{"location":"features/activity-view/#metadata","title":"Metadata","text":"You have a column with metadata about the activity. The activity kind, whether it is a commute and the equipment are currently only supported via the Strava API, but we can build something to infer that from directories as well.
The calories are broken in the Strava API wrapper library that I use, therefore they don't show even if they are there.
You can also see the ID which is an internal ID. When you use Strava API as a source, it will use the IDs that Strava gives. When you use files from a directory it will be computed from a hash of the path to the activity file.
"},{"location":"features/activity-view/#map-with-track","title":"Map with track","text":"The interactive map shows a line with the activity. The speed is color-coded and peaks at 35 km/h with a yellow color.
"},{"location":"features/activity-view/#distance-speed-and-altitude","title":"Distance, speed and altitude","text":"Then there are a couple of time series plots. One is the distance vs. time. You can see how much distance you covered when and also see plateaus when you went on a break.
From this we can also compute the speed, although that might be pretty noisy:
And more interesting is the distribution of the various speed zones. This gives you an understanding how much time you spent at which speed. The buckets are set in 5 km/h intervals, but we could also change that.
If the time series data has the altitude, which isn't always the case, you can see it in the plot there. Here we can see how I did a tour and continually rode downhill. Except at the end where I had to climb in order to get the explorer tile that I wanted.
"},{"location":"features/activity-view/#heart-rate","title":"Heart rate","text":"The heart rate isn't too helpful, I feel. Still I've created the plot from the given data.
More interesting regarding the heart rate are the zones which one has spent time during this activity.
The definition of the heart rate zones is not standardized. Usually there are five zones and they have the same names. What differs is how their ranges are computed and there is some chaos around that.
All definitions that I found take the maximum heart rate as the upper limit. One can measure this as part of a professional training or just use the 220 minus age prescription which at least for me matches close enough. What they differ on is how they use a lower bound. It seems that Polar or REI basically use 0 as the lower bound. My Garmin system also uses 0 as the lower bound. But as one can see in this blog, one can also use the resting heart rate as the lower bound.
Based on the maximum and resting heart rate we will then compute the heart rate zones using certain percentages of effort. We can compute the heart rate as the following:
rate = effort \u00d7 (maximum \u2013 minimum) + minimum
The zones then take the following efforts:
Zone Effort Training 1 50 to 60 % Warmup/Recovery 2 60 to 70 % Base Fitness 3 70 to 80 % Aerobic Endurance 4 80 to 90 % Anerobic Capacity 5 90 to 100 % Speed Training You can decide how you want to do work with that. If you want to have the same definitions that say Garmin uses, you need to just enter your birth year and we can compute the rest. If you want to use a lower bound, you need to specify that.
"},{"location":"features/calendar/","title":"Calendar","text":"In order to access all the activities, there is a calendar view. It shows the years in rows and the months in columns. Each cell is a particular month and indicates the total distance traveled.
When you click on any of the months, you will see a calendar for a given month. Here is one of my months which doesn't show too personal data:
Clicking on an activity will lead you to the activity detail view.
"},{"location":"features/eddington/","title":"Eddington Number","text":"The astronomer Sir Arthur Eddington like to go on longer bike rides. Apparently he did a lot of rides and had 84 days where he rode at least 84 miles. The Eddington number for cycling was coined from this.
If you have an Eddington number E, it means that you have had E days with at least E kilometer distance. At the time of writing my number is 62, which means that I rode at least 62 km on 62 separate days. If I want to extend it to 63, I would need to have at least 63 km on 63 separate days. My bike rides that are just 62 km long will not count any more, making this challenge really hard.
In the following plot you can see in blue the number of days that exceed the given distance. You can see that I have a 998 days that exceed 1 km, that's pretty easy when one just records enough data over many years. But then there are only 259 days where I exceed 20 km as this is beyond the distance that I have when I only go for a walk or run some simple errands.
The red curve indicates how many rides one needs to get the Eddington number. As it is a semi-log plot, the straight line is curved like a log-curve.
You can see this cliff at around 80 km. That is the distance that I ride to work and back. I have many days with around 80 km, but longer rides are only on occasional bike trips. Therefore I think I will eventually make it to an Eddington number of 80, but beyond that will be super difficult.
This is a life-long challenge, so who knows what happens in the future.
"},{"location":"features/eddington/#length-unit","title":"Length unit","text":"The definition of the Eddington number depends on the length unit that one has. Eddington as a British person used the English mile as a base unit. Therefore his number of 84 is actually harder to achieve than a 84 based on kilometers because he needed to exceed 135 km (84 mi) on each ride.
Therefore using a different length unit as a base changes the meaning. The kilometer is easier, the mile (1609.344 m) is harder. One could also use nautical miles (1852 m). And while we are at arbitrary unit systems, we could also use furlongs (201.168 m).
"},{"location":"features/equipment/","title":"Equipment Overview","text":"When activities are tagged with the used equipment, one can tally the total distance traveled with each thing. This given as a table with the recently used equipment at the top:
And for each thing there is also a graph which shows how the distance accumulates over time:
Here one can see how with my old bike I only recorded the occasional bike trip and not nearly the 4000 km/a which I was doing. And then it got stolen in 2019, so I bought a new bike.
This metadata is downloaded via the Strava API, for the directory source it is not yet supported. I thought about using directories like Activities/{Activity Kind}/{Equipment Name}/{Activity Name}.gpx
. to indicate the kind and equipment. If you're interested in implementing this, let me know.
"},{"location":"features/explorer-tiles/","title":"Explorer Tiles","text":"Maps accessible via the web browser are usually served as little image tiles. The Open Street Map uses the Web Mercator coordinate system to map from latitude and longitude to pixels on the map.
Each tile is 256\u00d7256 pixels in size. The zoom levels zoom in by a factor of two. Therefore all the tiles are organized in a quad tree. As you zoom in, each tile gets split into four tiles which can then show more detail. The following prescription maps from latitude and longitude (given in degrees) to tile indices:
def compute_tile(lat: float, lon: float, zoom: int = 14) -> tuple[int, int]:\n x = np.radians(lon)\n y = np.arcsinh(np.tan(np.radians(lat)))\n x = (1 + x / np.pi) / 2\n y = (1 - y / np.pi) / 2\n n = 2**zoom\n return int(x * n), int(y * n)\n
At zoom level 14 the tiles have a side length of roughly 1.5 km in Germany. These tiles are used as the basis for explorer tiles. The basic idea is that every tile where you have at least one point in an activity is considered an explored tile.
From your activities the program will extract all the tiles that you have visited. And then it does a few things with those. One main thing is that it will display these on an interactive map. When we zoom into one area where I've been on vacation in 2023, you can see the explored tiles there:
The filled tiles are explored, I have been there. The colored tiles are cluster tiles, that means that all their four neighbor tiles are also explored.
You can see here how I have explored a region and ensured that it is mostly contiguous.
There is another vacation from 2013 where I wasn't aware of the cluster tiles. I just did some bike trips and didn't look out for the tiles. There the tiles look like this:
You see all these gaps in there. Also there are three different clusters which are not connected. Each unique cluster is assigned a different color such that one can see where there are gaps between the cluster tiles. And filling the gaps is what the explorer tiles are about: This OCD (obsessive compulsive disorder) like craving to fill in the gaps.
Let's take a look at my main cluster of explorer tiles. Here I have explored much more than in the areas where I was on vacation.
You can see an additional feature, the blue square. This is the one largest square which can be fit into all explored tiles. In this picture it has size 21\u00b2. The idea of the square is to have a really tough challenge. Not only does one need to explore increasingly many tiles to expand the square by one unit, there must not be any gaps.
As you can see in this picture, there is a tile missing right at the top edge. I will never be able to get that because that is an off-limits area of the German air force at the airport. So I can expand my square to the south only.
You can click on each tile and get some information about that particular tile. You can see when you first explored that and with which activity. Also it shows the last activity there as well as the number of activities. If it is a local cluster, it will also show the cluster size.
There is also the option to color the tiles by first or last visit. Use one of the buttons above the map:
Then the map will show the first visit:
Or how recent your last visit is:
This uses Matplotlib's Plasma scale (see below) to color the age of a tile. Very new tiles will get a yellow color, a year old tiles a reddish color and tiles two years old or older a colder blue. This is the scale:
You can switch this with the buttons above the map.
"},{"location":"features/explorer-tiles/#squadratinhos","title":"Squadratinhos","text":"The explorer tiles at zoom level 14 are best suited for cycling and to discover the area around the city. There is a derived definition, the squadratinhos which are defined at zoom level 17 and therefore a factor 8 smaller in each direction. Each explorer tile is therefore divided into 256 squadratinhos.
These are better suited for walking and making sure that you really explored every little place in your neighborhood. Since they are so small, there are many properties which one cannot go onto, like industrial sites, airports or just a wide river.
For my home city it looks like this:
You can see how the squadratinhos are much smaller than the explorer tiles and how they lend themselves to more local exploring.
"},{"location":"features/explorer-tiles/#history","title":"History","text":"The map only shows the current state of your explorer tiles. In order to get a sense of how many new tiles you have discovered in the past, there are also plots that show you how you have extended the total number of squares, the size of your largest cluster and the size of your largest square over time:
"},{"location":"features/explorer-tiles/#missing-tile-files","title":"Missing tile files","text":"Looking at these maps you can see the gaps. And if you feel challenged to fill those, you might want to plan a \u201ctactical bike ride\u201d to explore those. Let us take another look at my tile history in Sint Annaland:
You can see those gaps in the clusters. To make it easier to explore tiles while on the go, we can export a file with the missing tiles. Pan and zoom the map to an area which you want to export. Below the map you will find two links:
Download missing tiles in visible area as GeoJSON or GPX.
This export is available as GeoJSON or GPX such that you can open it with other applications. For instance with GPX See on Linux it looks like this when opening the GeoJSON file:
You can then upload the GeoJSON file to Bikerouter and it will display there:
Then plan a route that goes through as many tiles as possible. Download the route as GPX and use an app like OsmAnd to ride along it.
"},{"location":"features/explorer-tiles/#missing-tiles-on-the-go","title":"Missing tiles on the go","text":"The above is nice to plan the route, perhaps you also want to take the missing tiles along to do spontaneous tile hunting.
A possibility should be Organic Maps which is a FOSS app that can display offline maps and also show GPX files.
Another method is to use Open Street Map uMap, either the one hosted in Germany or France. Then you can create a new personal map (consider limiting the access rights, default is public) and upload the GeoJSON file. Then you can use that map on the code to see your position and the missing tiles:
Yet another option is Offline Maps. That is able to display GeoJSON on Android, though one needs to buy the add-on for like 5 EUR.
On Android one can use the OsmAnd app to display tracks and also try to visualize the missing tiles. Unfortunately GeoJSON is not supported, therefore one has to play some tricks. The missing tiles are also exported as a GPX file with a track for each missing tile. This looks strange, but it is a bit helpful with OsmAnd. This is how the file looks like in GPXSee:
And on OsmAnd such files look like this:
Unfortunately OsmAnd becomes a very sluggish with such a huge track imported, so make sure to only export it from rather small regions.
"},{"location":"features/explorer-tiles/#square-planner","title":"Square planner","text":"From the explorer tile views you can open the square planner which allows you to see which tiles you need to explore in order to extend the square into a particular direction. The screen will open with the largest square that you have, then you can use the buttons to extend or move your square.
Using the buttons in the middle you can move the square, the buttons in the corners allow to extend or shrink the square.
When you have selected the square that you want to target, you can download the missing files in for that square as GeoJSON or GPX.
"},{"location":"features/heatmaps/","title":"Heatmaps","text":"A heatmap shows where you have been more often than other places. There is an interactive and zoomable heatmap that looks like this:
Here you can see where I mostly travel between Bonn and Cologne.
You have the option to download a rendered heatmap from the current viewport of the interactive map as in image with up to 4000\u00d74000 pixels, there is a link below the map.
"},{"location":"features/overview/","title":"Overview page","text":"When you start the webserver, you will see the overview page. It looks like the following and shows the activities you've done in the past 30 days:
Then below that you see the latest 15 activities with their tracks on interactive maps.
Each card contains the name, activity type, distance and duration. The non-commute activities are highlighted with a blue border, the commutes just have a gray border.
Click on any of the names and you will see the details of that activity.
"},{"location":"features/share-picture/","title":"Share Picture","text":"On each activity page you will find a \"share picture\" like the following:
"},{"location":"features/share-picture/#privacy-zones","title":"Privacy zones","text":"You might want to remove points that are close to your home, work or relatives. For this you can define arbitrary polygons as \"privacy zones\".
To create such a polygon, go to GeoJSON.io. You will see a map similar to this one:
Select the polygon tool and click on the map to span the polygon.
Once you are done, press Enter to finish the polygon. In the left panel the GeoJSON output will appear:
For this case, we have this GeoJSON:
{\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"coordinates\": [\n [\n [\n 6.87987514009842,\n 50.68272071401333\n ],\n [\n 6.878628929151887,\n 50.6819310903943\n ],\n [\n 6.8780142440226655,\n 50.68125883278765\n ],\n [\n 6.879563587362242,\n 50.68022375065988\n ],\n [\n 6.880599289703014,\n 50.68029311254671\n ],\n [\n 6.8814665851591315,\n 50.68102940933676\n ],\n [\n 6.881542368256589,\n 50.681723011688035\n ],\n [\n 6.8812729172415175,\n 50.682176515374465\n ],\n [\n 6.87987514009842,\n 50.68272071401333\n ]\n ]\n ],\n \"type\": \"Polygon\"\n }\n }\n ]\n}\n
Paste this in the appropriate settings menu.
You can name the zone to help you remember what it encompasses. You can add multiple zones.
Points that are within any of the privacy zones will not be shown in the share pictures. Except when all of the points are in the privacy zone, then all the points will be shown.
"},{"location":"features/upload/","title":"Uploading activities","text":"Some users don't want to restart the application each time they add new activities but run it on their home server. For this use case you can upload activities. Uploading files is a potential security issue and it is protected with a password. This feature is only enabled when you have set a password in the configuration file.
Put the following into your config.json
file:
{\n \"upload_password\": \"fb1c8d62-07a5-47bf-ada1-30aa66e41d8a\"\n}\n
Be sure to choose your own password and not use the one from this documentation.
Then you can go to the upload page and see this form:
Select a file that you want to upload and a target directory within the \u201cActivities\u201d directory. Finally, enter the password from the configuration file.
After you have uploaded the file, you will be redirected to the parsed activity.
"},{"location":"getting-started/advanced-metadata-extraction/","title":"Advanced Metadata extraction","text":"If you would like to set the metadata fields or change what part of the filename should be the activity name, you can use a custom directory structure with corresponding regular expressions.
An example directory structure:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n\u251c\u2500 Hike/\n\u2502 \u251c\u2500 Hiking Boots 2019/\n\u2502 \u2502 \u251c\u2500 2024-03-03-11-03-18 Some nice place with Alice and Bob.fit\n
"},{"location":"getting-started/advanced-metadata-extraction/#custom-regular-expressions","title":"Custom Regular expressions","text":"The program uses regular expressions to search for patterns in the relative path (in Activities) and extracts the relevant parts with named capture groups (?P<kind>)
, (?P<equipment>)
, (?P<name>)
.
You can use python to test your regular expressions. Read the python re documentation for help.
import re\nre.search(r'(?P<kind>[^/]+)/(?P<equipment>[^/]+)/(?P<name>[^/.]+)', '/Ride/Trekking Bike/2024-03-03-17-42-10 Home to Bakery.gpx').groupdict()\n
{'kind': 'Ride', 'equipment': 'Trekking Bike', 'name': '2024-03-03-17-42-10 Home to Bakery'}\n
You can add your custom regular expressions under the Admin
menu - Settings
- Metadata Extraction
in the WebUI. Settings are saved in your Playground
directory.
"},{"location":"getting-started/advanced-metadata-extraction/#filename-as-name-simple","title":"Filename as Name (simple)","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/(?P<name>[^/.]+)\n
- kind:
Ride
- equipment:
Trekking Bike
- name:
2024-03-03-17-42-10 Home to Bakery
"},{"location":"getting-started/advanced-metadata-extraction/#filename-without-date-as-name-useful-for-osmand-naming","title":"Filename without date as Name (useful for OsmAnd naming)","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n\u2502 \u2502 \u251c\u2500 2024-03-04-16-52-26.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-21_10-28_Sun OsmAnd default track.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-22_07-55_Mon.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/[-\\d_ ]+(?P<name>[^/]+)(?:\\.\\w+)+$\n
- kind:
Ride
- equipment:
Trekking Bike
- names:
Home to Bakery
;
; Sun OsmAnd default track
; Mon
Attention, name may be empty if it is not included in the file name. For OsmAnd default naming the weekday is included in the name.
"},{"location":"getting-started/advanced-metadata-extraction/#filename-after-first-space-as-name","title":"Filename after first space as Name","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-22_07-55_Mon.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-21_10-28_Sun OsmAnd default track.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/\\S+ ?(?P<name>[^/\\.]*)\n
- kind:
Ride
- equipment:
Trekking Bike
- names:
Home to Bakery
;
; OsmAnd default track
Attention, name may be empty if it is not included in the file name (also for OsmAnd default naming).
"},{"location":"getting-started/advanced-metadata-extraction/#grouping-activity-files-under-a-common-name-for-example-all-your-commutes","title":"Grouping activity files under a common name, for example all your commutes","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 Commute/\n\u2502 \u2502 \u2502 \u251c\u2500 2024-03-04-07-06-12.gpx\n\u2502 \u2502 \u2502 \u251c\u2500 2024-03-04-15-42-32.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/(?P<name>[^/]+)/\n
- kind:
Ride
- equipment:
Trekking Bike
- name:
Commute
(for all activities in Commute directory )
"},{"location":"getting-started/advanced-metadata-extraction/#activities-without-equipment","title":"Activities without equipment","text":"Path:
Activities/\n\u251c\u2500 Run/\n\u2502 \u251c\u2500 2024-03-09-09-24-03 To the lake.gpx\n\u2502 \u251c\u2500 2024-03-10-09-44-37 To the top of the hill.gpx\n
(?P<kind>[^/]+)/[-\\d_ ]+(?P<name>[^/]+)(?:\\.\\w+)+$\n
- kind:
Run
- equipment:
Unknown
- names:
To the lake
; To the top of the hill
"},{"location":"getting-started/advanced-metadata-extraction/#next-steps","title":"Next Steps","text":"If you you manually rename, move or delete your activity files, the program needs to reload to respect these changes. You can restart the program or visit Scan New Activities
in the admin menu of the WebUI.
"},{"location":"getting-started/docker-compose-tailscale/","title":"Using Git Version via Docker Compose and Tailscale VPN","text":"Docker is a software that allows you to run Linux programs in a container. Docker Compose is a tool for defining multi-container Docker environments in a single YAML configuration file and deploy it with a single command.
Tailscale is a VPN solution based on the Wireguard protocol which lets you connect all devices within your virtual private network (tailnet). The Tailscale Docker container exposes the services only via a direct VPN connection, which avoids exposing ports to the open internet to connect to your geo-activity-playground instance on-the-go. It provides a domain with a valid Let's Encrypt certificate which is only accessible via the tailnet. The configuration is based on Docker Tailscale Guide.
This how-to will give you an example compose.yml
that can build the geo-activity-playground docker image from Github and start this project within a Docker container and connecting it via Tailscale.
"},{"location":"getting-started/docker-compose-tailscale/#tailscale-prerequisites","title":"Tailscale Prerequisites","text":" - Active account
- Enabled MagicDNS (in DNS section of admin console)
- Enabled HTTPS (in DNS section of admin console)
- Auth-Key
- ACL policy for tag
"},{"location":"getting-started/docker-compose-tailscale/#create-auth-key-and-acl-policy-for-tag","title":"Create Auth-Key and ACL policy for tag","text":"More information on generating auth keys Navigate to https://login.tailscale.com/admin/settings/keys and generate an auth key.
Example Auth-Key configuration: - Description: docker - Reusable: yes - Expiration: 7 days - Ephemeral: No - Tags: tag:container
In order to use the tag, it must first be defined in your Access control policy in the admin console. Set the same tag as in the Auth-Key.
\"tagOwners\": {\n \"tag:container\": [\"autogroup:admin\"],\n},\n
When you apply a tag to a device for the first time and authenticate it, the tagged device's key expiry is disabled by default.
"},{"location":"getting-started/docker-compose-tailscale/#preparing-tailscale-configuration","title":"Preparing Tailscale configuration","text":"The geo-activity-playground service will be made available by using the Tailscale Serve functionality. It routes traffic from other devices on your Tailscale network (known as a tailnet) to a local service, in this case inside the container. It creates a reverse proxy to the geo-activity-playground container internal port 5000 (do not change it). TS_CERT_DOMAIN
is comprised of a subdomain (hostname set in the compose.yml
) and the tailnet root domain.
mkdir -p /docker/geo-activity-playground/{ts-state,ts-config}\ncd /docker/geo-activity-playground/ts-config\nnano geo-activity-playground.json\n
{\n \"TCP\": {\n \"443\": {\n \"HTTPS\": true\n }\n },\n \"Web\": {\n \"${TS_CERT_DOMAIN}:443\": {\n \"Handlers\": {\n \"/\": {\n \"Proxy\": \"http://127.0.0.1:5000\"\n }\n }\n }\n }\n}\n
"},{"location":"getting-started/docker-compose-tailscale/#compose-configuration-with-tailscale-network","title":"Compose configuration with Tailscale network","text":"With these steps the playground folder (which contains the activities) will be located in the docker project folder. The location can be changed in the compose.yml
.
mkdir -p /docker/geo-activity-playground/playground/Activities\ncd /docker/geo-activity-playground\nnano compose.yml\n
services:\n ts-geo-activity-playground:\n image: tailscale/tailscale:latest\n container_name: ts-geo-activity-playground\n hostname: geo-activity-playground # set your desired name, which will be the tailscale subdomain\n environment:\n - TS_AUTHKEY=tskey-auth-yyyyyyyyyyyyyyyyyyyyyyyyyyyy # paste your created Auth-Key\n - TS_EXTRA_ARGS=--advertise-tags=tag:container # set the same tag as in the Auth-Key\n - TS_SERVE_CONFIG=/config/geo-activity-playground.json\n - TS_STATE_DIR=/var/lib/tailscale\n volumes:\n - /docker/geo-activity-playground/ts-state:/var/lib/tailscale\n - /docker/geo-activity-playground/ts-config:/config\n - /dev/net/tun:/dev/net/tun\n cap_add:\n - net_admin\n - sys_module\n restart: unless-stopped\n geo-activity:\n build:\n context: https://github.com/martin-ueding/geo-activity-playground.git\n # this sets the build context to the DOCKERFILE located in the Github repository\n container_name: geo-activity-playground\n depends_on:\n - ts-geo-activity-playground # start container after the VPN network is active\n network_mode: service:ts-geo-activity-playground # link container network to tailscale container\n volumes:\n - /docker/geo-activity-playground/playground:/data # optional: change left side to your desired playground directory\n restart: unless-stopped\n
If you want to build the release version of geo-activity-playground from Github instead, you can adjust the build context and add the release tag. context: https://github.com/martin-ueding/geo-activity-playground.git#0.29.1
"},{"location":"getting-started/docker-compose-tailscale/#building-image-and-running-container","title":"Building image and running container","text":"You need to set up your files according to one of the presented methods, like activity files or the Strava API. Consult the other pages in the sidebar for the details.
Once you have your playground directory, you can build the image and start the container.
docker compose build\ndocker compose up -d\n
This will start the webserver and expose it via your tailnet on https://[HOSTNAME].[YourTailnetName].ts.net/
, eg. https://geo-activity-playground.tail41a3.ts.net/
. In order to access your instance via that domain, you have to install and authenticate the Tailscale client app on your device you want to open it from.
"},{"location":"getting-started/docker-compose-tailscale/#updating-the-image","title":"Updating the image","text":"If using the tagged release version of geo-activity-playground, update the tag to the latest one first.
docker compose down\ndocker compose build\ndocker compose up -d --force-recreate\n
"},{"location":"getting-started/docker-compose/","title":"Using Git Version via Docker Compose","text":"Docker is a software that allows you to run Linux programs in a container. Docker Compose is a tool for defining multi-container Docker environments in a single YAML configuration file and deploy it with a single command.
This how-to will give you an example compose.yml
that can build the geo-activity-playground docker image from Github and start this project within a Docker container.
"},{"location":"getting-started/docker-compose/#creating-directory-structure-and-composeyml","title":"Creating directory structure and compose.yml","text":"With these steps the playground folder (which contains the activities) will be located in the docker project folder. The location can be changed in the compose.yml
.
mkdir -p /docker/geo-activity-playground/playground/Activities\ncd /docker/geo-activity-playground\nnano compose.yml\n
services:\n geo-activity:\n build:\n context: https://github.com/martin-ueding/geo-activity-playground.git\n # this sets the build context to the DOCKERFILE located in the Github repository\n container_name: geo-activity-playground\n volumes:\n - /docker/geo-activity-playground/playground:/data # optional: change left side to your desired playground directory\n ports:\n - 5000:5000 # optional: change the exposed port on the left side\n restart: unless-stopped\n
If you want to build the release version from Github instead, you can adjust the build context and add the release tag. context: https://github.com/martin-ueding/geo-activity-playground.git#0.29.1
"},{"location":"getting-started/docker-compose/#building-image-and-running-container","title":"Building image and running container","text":"You need to set up your files according to one of the presented methods, like activity files or the Strava API. Consult the other pages in the sidebar for the details.
Once you have your playground directory, you can build the image and start the container.
docker compose build\ndocker compose up -d\n
This will start the webserver on http://localhost:5000/ or at the port you chose to expose.
Note that port 5000 may not be available on macOS because of AirPlay, so you can map to another port.
"},{"location":"getting-started/docker-compose/#updating-the-image","title":"Updating the image","text":"If using the tagged release version, update the tag to the latest one first.
docker compose down\ndocker compose build\ndocker compose up -d --force-recreate\n
"},{"location":"getting-started/docker/","title":"Using Git Version via Docker","text":"Docker is a software that allows you to run Linux programs in a container. This how-to will show you how to build and start this project within a Docker container.
"},{"location":"getting-started/docker/#build-the-image","title":"Build the image","text":"First you need to build the Docker image. For this download the source code and build the image using the following commands:
git clone https://github.com/martin-ueding/geo-activity-playground.git\ncd geo-activity-playground\nsudo docker build -t geo-activity-playground .\n
Perhaps you do not need sudo
on your system.
"},{"location":"getting-started/docker/#run-the-image","title":"Run the image","text":"You need to set up your files according to one of the presented methods, like activity files or the Strava API. Consult the other pages in the sidebar for the details.
Once you have your playground directory, you can launch the Docker image with the following. Be sure to replace path/to/playground
with your path.
sudo docker run -p 5000:5000 -v path/to/playground:/data -it geo-activity-playground\n
This will start the webserver on http://localhost:5000/.
Note that port 5000 may not be available on macOS because of AirPlay, so you can map to another port by replacing the port specifier from above with -p 8000:5000
. Then you can open http://localhost:8000/ in your browser.
"},{"location":"getting-started/installing-git-on-linux/","title":"Installing Git Version On Linux","text":"As this project is still in development, you might want to have a peek into the development version. This is more advanced than using the stable versions, but not impossibly hard.
First you need to clone the git repository from GitHub using the following command:
git clone https://github.com/martin-ueding/geo-activity-playground.git\n
That will create a directory geo-activity-playground
in your current working directory.
Then change into that directory:
cd geo-activity-playground\n
Next we will use Poetry to install the dependencies of the project. First you need to make sure that you have Poetry available. On Ubuntu/Debian run sudo apt install python3-poetry
, on Fedora/RedHat run sudo dnf install poetry
to install it.
Then we can create the virtual environment:
poetry install\n
And next we can run the program:
poetry run geo-activity-playground --basedir path/to/your/playground --help\n
Replace the --help
with the subcommands described in the help message or the other parts described this documentation.
You will need the --basedir
option because you run the program from the source directory and not from your playground directory. If you install the stable version via PIP as described in the other page, you will not need this option.
"},{"location":"getting-started/installing-git-on-linux/#updating-to-the-latest-version","title":"Updating to the latest version","text":"Over time I will add more commits to the source control system. In order to update your clone to the latest version, execute the following:
git pull\n
This will download the missing changesets and apply them to your downloaded version. After that is done, you need to update your virtual environment with this:
poetry install\n
And then you can continue using it as before.
"},{"location":"getting-started/installing-stable-on-linux/","title":"Installing Stable On Linux","text":"In this how-to guide I will show you how you can install the latest stable version of this project on Linux.
Using PIP, you can install the latest version using this command:
pip install --user geo-activity-playground\n
If you get an error about the command pip
not found, you will need to install that first. On Ubuntu or Debian use sudo apt install python3-pip
, on Fedora or RedHat use sudo dnf install python3-pip
. After you have installed PIP, repeat the above command.
"},{"location":"getting-started/installing-stable-on-linux/#ensure-that-the-path-is-correct","title":"Ensure that the PATH is correct","text":"Next you can try to start the program by just entering the following into the terminal:
geo-activity-playground --help\n
If you get a help message, everything is fine. If you get an error about command not found, we need to adjust your PATH. Execute the following in your command line:
xdg-open ~/.profile\n
This brings up an editor with your shell profile. Add a line containing the following at the end of the file:
PATH=$PATH:$HOME/.local/bin\n
This adds the path to your shell environment. This becomes active after you log in again. In order to apply it also to your current shell session, execute export PATH=$PATH:$HOME/.local/bin
in the terminal window. Try the first command in this section again, you should see the help message now.
"},{"location":"getting-started/installing-stable-on-linux/#upgrading-to-the-latest-version","title":"Upgrading to the latest version","text":"At some later point you likely want to upgrade to the latest version. For this use this command:
pip install --user --upgrade geo-activity-playground\n
"},{"location":"getting-started/installing-stable-on-windows/","title":"Installing Stable on Windows","text":"This how-to will show you the installation of the project on Windows. Here in the guide we use Windows 10 with the locale set to German, it should generalize to Windows 11 as well.
"},{"location":"getting-started/installing-stable-on-windows/#installing-python","title":"Installing Python","text":"First we need to install Python because that doesn't ship with Windows. Fortunately we can get it from the Microsoft Store. Open that via the start menu and you should see something like this:
Type \u201cPython\u201d into the search bar at the top. In the search results you likely see different Python versions like 3.11 and 3.10. The project is compatible with 3.10 to 3.12; I'd suggest to just go with 3.12. In case that you have already installed one of the other compatible versions, you can skip this step.
Here we select Python 3.11.
In the top right there is a blue button to install the software. Click that.
"},{"location":"getting-started/installing-stable-on-windows/#installing-the-project","title":"Installing the project","text":"After that has run through, you need to open the Power Shell via the start menu. It should open a command line window like this:
We can verify that Python is working by entering python --version
and pip --version
. It should give a sensible version message like this:
Then we can ues PIP to install the project. Type the following:
pip install -U geo-activity-playground\n
It should look like this:
Then press Enter and it will install it, looking like this:
That might take a while. After that has run through, it should give a success message:
Then we're done with this window, you can close it now.
"},{"location":"getting-started/installing-stable-on-windows/#putting-your-activity-files-in-place","title":"Putting your activity files in place","text":"Next you need to create a folder to put the files. I've placed mine just into the Documents folder. In there I've created a folder named exactly \u201cActivities\u201d.
Inside this folder there are my GPX/FIT/TCX/KML files as outlined in how-to guide on using activity files.
You need to have at least one activity file before you can start the program.
"},{"location":"getting-started/installing-stable-on-windows/#starting-the-webserver","title":"Starting the webserver","text":"Once you have your activity files in place, we need to add a start script. Right-click into the playground folder (next to the \u201cActivities folder\u201d) and in the context menu select \u201cCreate New\u201d and then \u201cText File\u201d. Name it start.bat
. Windows will ask you whether want to change the suffix (file extension) because it might get unusable. Yes, we want to do that. It should look like this:
Then right-click on that file and select \u201cEdit\u201d. A text editor will open up. Put the following content into this file:
python -m geo_activity_playground serve\npause\n
Then save and close it. I need you to create this file yourself and cannot offer a download because the Windows Defender will not allow you to execute such script files downloaded as a security precaution. If you create the file yourself, it will let you execute it.
Once you have the start.bat
there, you can double-click on it to execute it. A new terminal window should open and it should start to parse your activities.
After it has loaded everything, you can open http://127.0.0.1:5000/ in your browser. You should then see the landing page:
Also other functions like the heatmap work:
That's it, have fun!
"},{"location":"getting-started/moving-from-strava/","title":"Moving from Strava","text":"If you have been using Strava up to this point but want to use this project from now on, this is the correct guide. Here I will show how you can convert your data from Strava into the format of this project and keep adding new data without Strava in the future.
"},{"location":"getting-started/moving-from-strava/#download-your-archive-from-strava","title":"Download your archive from Strava","text":"Go to the Strava account download page and request a download of your data. This will take a while and you get a notification via e-mail when it is done.
Once it has run through, you will be able to download a ZIP file. Once extracted, it will have a structure like this:
.\n\u251c\u2500\u2500 activities [2217 entries exceeds filelimit, not opening dir]\n\u251c\u2500\u2500 activities.csv\n\u251c\u2500\u2500 applications.csv\n\u251c\u2500\u2500 bikes.csv\n\u251c\u2500\u2500 blocks.csv\n\u251c\u2500\u2500 categories_of_personal_information_we_collect.pdf\n\u251c\u2500\u2500 clubs\n\u251c\u2500\u2500 clubs.csv\n\u251c\u2500\u2500 comments.csv\n\u251c\u2500\u2500 community_content.json\n\u251c\u2500\u2500 community_personal_data.json\n\u251c\u2500\u2500 components.csv\n\u251c\u2500\u2500 connected_apps.csv\n\u251c\u2500\u2500 contacts.csv\n\u251c\u2500\u2500 email_preferences.csv\n\u251c\u2500\u2500 events.csv\n\u251c\u2500\u2500 favorites.csv\n\u251c\u2500\u2500 flags.csv\n\u251c\u2500\u2500 followers.csv\n\u251c\u2500\u2500 following.csv\n\u251c\u2500\u2500 general_preferences.csv\n\u251c\u2500\u2500 global_challenges.csv\n\u251c\u2500\u2500 goals.csv\n\u251c\u2500\u2500 group_challenges.csv\n\u251c\u2500\u2500 information_we_disclose_for_a_business_purpose.pdf\n\u251c\u2500\u2500 local_legend_segments.csv\n\u251c\u2500\u2500 logins.csv\n\u251c\u2500\u2500 media [252 entries exceeds filelimit, not opening dir]\n\u251c\u2500\u2500 media.csv\n\u251c\u2500\u2500 memberships.csv\n\u251c\u2500\u2500 messaging.json\n\u251c\u2500\u2500 metering.csv\n\u251c\u2500\u2500 mobile_device_identifiers.csv\n\u251c\u2500\u2500 orders.csv\n\u251c\u2500\u2500 partner_opt_outs.csv\n\u251c\u2500\u2500 posts.csv\n\u251c\u2500\u2500 privacy_zones.csv\n\u251c\u2500\u2500 profile.csv\n\u251c\u2500\u2500 profile.jpg\n\u251c\u2500\u2500 reactions.csv\n\u251c\u2500\u2500 routes\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 1.gpx\n\u251c\u2500\u2500 routes.csv\n\u251c\u2500\u2500 segments.csv\n\u251c\u2500\u2500 shoes.csv\n\u251c\u2500\u2500 social_settings.csv\n\u251c\u2500\u2500 starred_routes.csv\n\u251c\u2500\u2500 starred_segments.csv\n\u251c\u2500\u2500 support_tickets.csv\n\u2514\u2500\u2500 visibility_settings.csv\n
This directory contains a file activities.csv
with the metadata and also a directory activities
with the files that you have recorded.
"},{"location":"getting-started/moving-from-strava/#convert-your-checkout","title":"Convert your checkout","text":"Use the following command to create a directory from your Strava actitivies:
geo-activity-playground convert-strava-checkout ~/Downloads/export_123456/ ~/Documents/Outdoors/Playground\n
This should read through all the activities and create a directory structure with the pattern ~/Documents/Outdoors/Playground/Activities/{Kind}/{Equipment}/{Commute}/{Date} {Time} {Name}.{Suffix}
. For instance one file might be named Activities/Run/5212701.0/2019-07-09 09-59-25 Around the \u5317\u4eac\u5927\u5b66 campus.gpx.gz
.
The equipment might have nonsensical seeming names like 10370891.0
. The problem here is that Strava doesn't export the list of activities with that index. If your equipment doesn't have a nickname, it will just be such a number.
"},{"location":"getting-started/moving-from-strava/#use-the-directory","title":"Use the directory","text":"Now that the files from Strava are converted, consult the guide on using activity files to proceed from here.
"},{"location":"getting-started/moving-from-strava/#recording-more-activities","title":"Recording more activities","text":"Now that you don't record via Strava, you will need some other app to record your activities. There are Apps like OsmAnd or OpenTracks which provide such functionality. Export the files as GPX, FIT, TCX or KML files and put them into the directory structure. On the next start of the program, they will be picked up.
"},{"location":"getting-started/starting-the-webserver/","title":"Starting The Webserver","text":"Before you start here, you should have done these things:
- You have installed the program either from a stable version or from git.
- You have set up a playground with either activity files or the Strava API.
Now we can start the webserver which provides most of the features. This is done with the serve
command. So depending on how you have installed it, the commands could look like these:
geo-activity-playground serve
if you are in the playground directory and have installed a stable version. geo-activity-playground --basedir ~/Dokumente/Karten/Playground serve
if your playground directory is somewhere else and you have installed a stable version. poetry run geo-activity-playground --basedir ~/Dokumente/Karten/Playground serve
if you have it from the git checkout and want to use local files in your directory as a data source.
The webserver will start up and give you a bit of output like this:
2023-11-19 17:59:23 geo_activity_playground.importers.strava_api INFO Loading metadata file \u2026\n2023-11-19 17:59:23 stravalib.protocol.ApiV3 INFO GET 'https://www.strava.com/api/v3/athlete/activities' with params {'before': None, 'after': 1700392964, 'page': 1, 'per_page': 200}\n2023-11-19 17:59:23 geo_activity_playground.importers.strava_api INFO Checking for missing time series data \u2026\n * Serving Flask app 'geo_activity_playground.webui.app'\n * Debug mode: off\n2023-11-19 17:59:23 werkzeug INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.\n * Running on http://127.0.0.1:5000\n2023-11-19 17:59:23 werkzeug INFO Press CTRL+C to quit\n
The warning about the development server is fine. We are using this only to play around, not to power a web service for other users.
Open http://127.0.0.1:5000 to open the website in your browser. There might be some more messages about downloading and parsing data. The first startup will take quite some time. When it is done you will see something like this:
Click around and explore the various features.
"},{"location":"getting-started/starting-the-webserver/#setting-host-and-port","title":"Setting host and port","text":"In case you don't like the default value of 127.0.0.1:5000
, you can use the optional command line arguments --host
and --port
to specify your values.
"},{"location":"getting-started/using-activity-files/","title":"Using Activity Files","text":"Outdoor activities are usually recorded as .GPX
or .FIT
files. Some apps like OsmAnd , OpenTracks or Organic Maps, GPS handhelds, smartwatches or cycling computers give you these files.
"},{"location":"getting-started/using-activity-files/#supported-file-formats","title":"Supported file formats","text":" - FIT
- GPX
- TCX
- KML
- KMZ
- Simra CSV export
"},{"location":"getting-started/using-activity-files/#add-activity-files","title":"Add Activity Files","text":"Before starting the service you need to create a folder for your activities and put at least one activity file in there.
Create a Playground
folder on your storage somewhere and add a subfolder Activities
. There you can add your activity files. For example:
~/\n\u251c\u2500 Documents[or other location]/\n\u2502 \u251c\u2500 Playground/\n\u2502 \u2502 \u251c\u2500 Activities/\n\u2502 \u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n
The program will treat the files as read-only and does not modify them.
Once the service is running you can use the Uploader to add your files. You can manually rename, move or delete your activity files, but the program needs to reload to respect these changes. You can restart the program or visit Scan New Activities
in the admin menu of the WebUI.
"},{"location":"getting-started/using-activity-files/#metadata-extraction","title":"Metadata extraction","text":"Most activity file formats contain basic data like date
, time
and track points
. Each activity in geo-activity-playground also has the metadata fields kind
, equipment
and name
. They can be extracted from files that contain them.
If no metadata is found, kind
and equipment
default to Unknown
. The name
is then extracted from the file name (without the suffix). So for Activities/2024-03-03-17-42-10 Home to Bakery.gpx
the name
is 2024-03-03-17-42-10 Home to Bakery
.
"},{"location":"getting-started/using-activity-files/#next-steps","title":"Next steps","text":"Once you have your files put into the directory, you're all set and can proceed with the next steps.
You can extend the directory structure to categorize your activities, see Advanced Metadata Extraction.
"},{"location":"getting-started/using-strava-api/","title":"Using Strava API","text":"You might have all your data on the Strava service and would like to use this for additional analytics without moving your data. That is fine.
If you don't mind a bit of rate-limiting, you can just directly go ahead and start the webserver. It will offer to connect with Strava.
"},{"location":"getting-started/using-strava-api/#your-own-strava-app","title":"Your own Strava App","text":"In order to use the Strava API without sharing the rate-limiting with other users, you need to create your own app. If my explanation doesn't suit you, have a look at this how-to guide as well.
Navigate to the API settings page and create an app. It only needs to have read permissions.
After you are done with that, you can see your App here:
There is a \"client ID\" and a \"client secret\" that we are going to need for the next step. In general our app could be used by all sorts of people who can then access their data only. We want to access our own data, but we still need to authorize our app to use our data.
Open the webserver of this program and go the Strava API setup page. Enter your client ID and client secret, click on \"Connect to Strava\".
This will prompt an OAuth2 request where you have to grant permissions to your app. After that you will be redirected back to the app and it should be set up. At the moment you need to restart the webserver such that it can start to download the activities. Due to rate-limiting it can still take a while.
"},{"location":"getting-started/using-strava-api/#use-export-to-avoid-rate-limiting","title":"Use export to avoid rate limiting","text":"When you first start this program and use the Strava API as a data source, it will download the metadata for all your activities. Then it will start to download all the time series data for each activity. Strava has a rate limiting, so after the first 200 activities it will crash and you will have to wait for 15 minutes until you can try again and it will download the next batch.
Therefore it is recommended to use a Strava export in order to get started quicker. For this go to the Strava account download page and download all your data. You will get a ZIP file. Unpack the files into Playground/Strava Export
. These will be picked up there. Activities from Strava will only be downloaded after importing all these, and only the ones after the last one in the export will be downloaded. This way you can get started much quicker.
"},{"location":"getting-started/using-strava-api/#skip-strava-download","title":"Skip Strava download","text":"If you don't want to download new activities from Strava, use --skip-strava
to have the webserver start right away.
"},{"location":"reference/changelog/","title":"Changelog","text":"This is the log of high-level changes that I have done in the various versions.
"},{"location":"reference/changelog/#version-0","title":"Version 0","text":"This is the pre-release series. Things haven't settled yet, so each minor version might introduce breaking changes.
"},{"location":"reference/changelog/#version-033","title":"Version 0.33","text":""},{"location":"reference/changelog/#version-0332","title":"Version 0.33.2","text":" - GH-198: Fix explorer map. The problem was that VS Code auto-formatted the embedded JavaScript and created syntax errors.
"},{"location":"reference/changelog/#version-0331","title":"Version 0.33.1","text":" - GH-156: Fix little bug with
_meta
.
"},{"location":"reference/changelog/#version-0330","title":"Version 0.33.0","text":" - Make heatmap colormap configurable via web UI.
- GH-196: Make tile map URL configurable via configuration file.
- Make daily pulse plot per year in tabs.
"},{"location":"reference/changelog/#version-0320","title":"Version 0.32.0","text":" - GH-173: Add config option
ignore_suffixes
which can be set to something like [\".kml\"]
to ignore certain file types. - Include all activities in the summary, even those which are not to be considered for achievements.
- Remove debug print.
- Make share picture always the same size independent of the content.
"},{"location":"reference/changelog/#version-0310","title":"Version 0.31.0","text":" - GH-189: Fix heatmap tile cache expiry in cases where the activity kind has changed.
- Make date and time formats better to read.
- GH-156: Add metadata editing functionality with override files.
"},{"location":"reference/changelog/#version-0300","title":"Version 0.30.0","text":" - GH-187: Update favicon to new logo.
- GH-174: Add new search functionality that also serves as an overview over all activities.
- GH-168: Clicking on table headers will sort the tables now.
- GH-162: Make track segmentation configurable with a configuration setting.
- Visualize cadence on the activity page.
- GH-188: Remove
root=
prefix in activity kind when importing from Strava. - GH-188: Add option to rename activity kinds.
"},{"location":"reference/changelog/#version-029","title":"Version 0.29","text":""},{"location":"reference/changelog/#version-0292","title":"Version 0.29.2","text":" - Documentation improvements by beautiful-orca: GH-180, GH-181, GH-182, GH-183, GH-185
- GH-184: Use Python 3.12 in Docker.
- GH-178: Fix display of number of new tiles in activity view.
- GH-177: Fix distance from new
stravalib
version. - GH-179: Work around Pandas deprecation message.
- GH-176: Do not modify filename on upload any more.
- GH-175: Mention Organic Maps.
"},{"location":"reference/changelog/#version-0291","title":"Version 0.29.1","text":" - GH-167: Fix explorer tile export.
- GH-169: Fix import of KML files with waypoints.
"},{"location":"reference/changelog/#version-0290","title":"Version 0.29.0","text":" - Use dropdown menus to make navigation a bit smaller.
- GH-163: Recompute explorer tiles when there are deleted activities. Previously this would lead to
KeyError
when trying to use the heatmap or the explorer tile maps. - GH-161: Fix explorer tile clusters and square if one has activities that are not to be considered for achievements.
- GH-164: Create new function to handle write-and-replace on Windows.
- GH-155: Use the same scale for all plots with kind, make this configurable in the settings menu.
- Rewrite the documentation start page to make it more appealing and reflect the work in the web interface.
- GH-166: Add map with new explorer tiles to activity view.
- GH-160: Update version of
stravalib
and with that also pydantic
. That fixes a bug with recursive_guard
.
"},{"location":"reference/changelog/#version-028","title":"Version 0.28","text":" - Add settings menu to suppress fields from share pictures.
- Fix spelling mistake in navigation bar.
- Accelerate the tile visit computation.
- Ignore equipment offsets of equipments that don't exist.
- Reset corrupt heatmap cache files.
- GH-159: Improve password mechanism to protect both upload and settings.
- Document the use of Open Street Map uMap for missing explorer tiles on the go.
"},{"location":"reference/changelog/#version-027","title":"Version 0.27","text":""},{"location":"reference/changelog/#version-0271","title":"Version 0.27.1","text":" - Fix
num_processes
option.
"},{"location":"reference/changelog/#version-0270","title":"Version 0.27.0","text":" - GH-128: Let the Strava Checkout importer set the file
strava-last-activity-date.json
which is needed such that the Strava API importer can pick up after all the activities that have been imported via the checkout. - GH-143: Use custom CSV parser to read activities that have newlines in their descriptions.
- GH-146: Make multiprocessing optional with
num_processes = 1
in the configuration. - GH-147: Add another safeguard against activities that don't have latitude/longitude data.
- GH-149: Only pre-compute explorer maps for zoom 14 and 17 by default. Other ones just have to be enabled once. This saves a bit of computing time for most people that don't need to go down to zoom 19.
- GH-151: Do not fail if version cannot be determined.
- Add settings menu where one can configure various things:
- Equipment offsets
- Maximum heart rate for heart rate zones
- Metadata extractions from paths
- Privacy zones
- Strava connection
- The
config.json
replaces the config.toml
and will automatically be generated. - Fix bug in explorer tile interpolation that likely doesn't have an effect in practice.
"},{"location":"reference/changelog/#version-026","title":"Version 0.26","text":""},{"location":"reference/changelog/#version-0263","title":"Version 0.26.3","text":" - GH-142: Require
pandas >= 2.2.0
to make sure that it knows about include_groups
. - GH-144: Ignore activities without time series when using the Strava Checkout import.
"},{"location":"reference/changelog/#version-0262","title":"Version 0.26.2","text":" - Start with a test suite for the web server that also tests importing.
- Already fixed a few little bugs with that.
- GH-141: Fix summary page if there are no activities with steps.
"},{"location":"reference/changelog/#version-0261","title":"Version 0.26.1","text":" - GH-139, GH-140: More fixes for Strava archive importer.
"},{"location":"reference/changelog/#version-0260","title":"Version 0.26.0","text":" - Add automatic dark mode.
- Add some more explanation for the Strava connection.
- GH-138: Fix import from Strava archive that was broken in 0.25.0.
- Style the settings page a bit.
"},{"location":"reference/changelog/#version-025","title":"Version 0.25","text":""},{"location":"reference/changelog/#version-024","title":"Version 0.24","text":""},{"location":"reference/changelog/#version-0242","title":"Version 0.24.2","text":" - GH-127: Make calories and steps optional for the summary statistics.
"},{"location":"reference/changelog/#version-0241","title":"Version 0.24.1","text":" - GH-124: Add more timezone handling for Strava API.
- GH-125: Fix building of Docker container.
- GH-126: Fix heatmap download.
"},{"location":"reference/changelog/#version-0240","title":"Version 0.24.0","text":" - GH-43: Added nicer share pictures and privacy zones.
- GH-95: Display the number of new explorer tiles and squadratinhos per activity.
- GH-113: Open footer links in a new tab.
- GH-114: Show total distance and duration in day overview.
- GH-115: Add more summary statistics and add a \"hall of fame\" as well.
- GH-161: Show table for Eddington number, also update the plot to make it a bit easier to read. Add some more explanatory text.
- GH-118: Fix links in search results.
- GH-121: Fix link to share picture.
- GH-122: Convert everything to \"timezone naive\" dates in order to get rid of inconsistencies.
- GH-123: Fix startup from empty cache. A cache migration assumed that
activities.parquet
exists. I've added a check. - Use Flask Blueprints to organize code.
- Remove half-finished \"locations\" feature from the navigation.
- Allow filtering the heatmap by activity kinds.
- Remove duplicate link to landing page from navigation.
"},{"location":"reference/changelog/#version-023","title":"Version 0.23","text":" - GH-111: Add password protection for upload.
- Use Flask \u201cflash\u201d messages.
- GH-110: Support routes that don't have time information attached them. That might be useful if you haven't recorded some particular track but still want it to count towards your heatmap and explorer tiles.
"},{"location":"reference/changelog/#version-022","title":"Version 0.22","text":" - GH-111: Allow uploading files from within the web UI and parse them directly after uploading.
- Fix bug that lead to re-parsing of activity files during startup.
"},{"location":"reference/changelog/#version-021","title":"Version 0.21","text":""},{"location":"reference/changelog/#version-0212","title":"Version 0.21.2","text":" - Fix crash in search due to missing
distance/km
.
"},{"location":"reference/changelog/#version-0211","title":"Version 0.21.1","text":" - Add support for Python 3.12.
"},{"location":"reference/changelog/#version-0210","title":"Version 0.21.0","text":" -
Breaking change: New way to extract metadata from paths and filenames. This uses regular expressions and is more versatile than the heuristic before. If you have used prefer_metadata_from_file
before, see the documentation on activity files for the new way.
-
GH-105: Ignore similar activities that have vanished.
- GH-106: Be more strict when identifying jumps in activities. Take 30 s and 100 m distance as criterion now.
- GH-107: Remove warning by fixing a Pandas slice assignment.
- GH-108: Calories and steps are now extracted from FIT files.
-
GH-109: Better error message when trying to start up without any activity files.
-
Removed imagehash
from the dependencies.
- Single day overview is now linked from each activity.
- Parsing of activity files is now parallelized over all CPU cores and faster than before.
- The coloring of the speed along the activity line doesn't remove outliers any more.
"},{"location":"reference/changelog/#version-020","title":"Version 0.20","text":" - GH-88: Fix failure to import Strava distance stream due to
unsupported operand type(s) for /: 'list' and 'int'
. - GH-90: Take time jumps into account in activity distance computation and the various plots of the activities.
- GH-91: Import altitude information from GPX files if available.
- GH-92: Keep identity of activities based on hash of the file content, not the path. This allows to rename activities and just update their metadata, without having duplicates.
- GH-99: Skip Strava export activities that don't have a file.
- GH-98: Also accept boolean values in commute column of Strava's
activities.csv
. - GH-100: Protect fingerprint computation from bogus values
- GH-102: Make dependency on
vegafusion[embed]
explicit in the dependencies. - GH-103: Delete old pickle file before moving the new one onto it.
"},{"location":"reference/changelog/#version-019","title":"Version 0.19","text":""},{"location":"reference/changelog/#version-0191","title":"Version 0.19.1","text":" - Fix broken import of CSV files due to missing argument
opener
.
"},{"location":"reference/changelog/#version-0190","title":"Version 0.19.0","text":" - GH-88: Fix confusion about the internal data type for distance. Most of the time it was in meter, but the display was always in kilometer. In order to make it more clear now, the internal data now only contains the field
distance_km
and everything is represented as kilometer internally now. - Add more tooltip information in the plot on the landing page.
- GH-87: Add
prefer_metadata_from_file
configuration option. - GH-17: Download calories from Strava via the detailed API.
- Add option
--skip-strava
to the serve
command in order to start the webserver without reaching out to Strava first. This might be useful if the rate limit has been exceeded. - GH-89: Refactor some paths into a module such that there are not so many redundant definitions around.
- GH-86: Attempt to also read Strava exports that are localized to German, though untested.
- GH-36: Add a square planner.
"},{"location":"reference/changelog/#version-018","title":"Version 0.18","text":" - Fix internal server error 500 when there are not-a-number entries in the speed. GH-85
- Display activity source path in detail view.
- Ignore files which start with a period. This should also avoid Apple Quarantine files. GH-83
- Allow to have both Strava API and activity files.
- Use an existing Strava Export to load activities, retrieve only the remainder from the Strava API.
- In the calender, give the yearly total.
"},{"location":"reference/changelog/#version-017","title":"Version 0.17","text":""},{"location":"reference/changelog/#version-0175","title":"Version 0.17.5","text":" - Convert FIT sport type enum to strings. GH-84
"},{"location":"reference/changelog/#version-0174","title":"Version 0.17.4","text":" - Try to use charset-normalizer to figure out the strange encoding. GH-83
"},{"location":"reference/changelog/#version-0173","title":"Version 0.17.3","text":" - Fix error handler for GPX encoding issues. GH-83
"},{"location":"reference/changelog/#version-0172","title":"Version 0.17.2","text":" - Fix FIT import failure when the sub-sport is none. GH-84
"},{"location":"reference/changelog/#version-0171","title":"Version 0.17.1","text":" - Use locally downloaded tiles for all maps, this way we do not need to download them twice for activities and explorer/heatmap.
- Localize SimRa files to local time zone. GH-80
- Parse speed unit from FIT file. There are many devices which record in m/s and not in km/h, yielding too low speeds in the analysis. This is now fixed. GH-82
- Skip
.DS_Store
files in the activity directory. GH-81 - From FIT files we also extract the grade, temperature and GPS accuracy fields if they are present. There is no analysis for them yet, though. Also extract the workout name, sport and sub-sport fields from FIT files. GH-81
- Add more logging to diagnose Unicode issue on macOS. GH-83
"},{"location":"reference/changelog/#version-0170","title":"Version 0.17.0","text":" - Fix bug which broke the import of
.tcx.gz
files. - Add
Dockerfile
such that one can easily use this with Docker. GH-78 - Add support for the CSV files of the SimRa Project. GH-79
"},{"location":"reference/changelog/#version-016","title":"Version 0.16","text":""},{"location":"reference/changelog/#version-0164","title":"Version 0.16.4","text":""},{"location":"reference/changelog/#version-0163","title":"Version 0.16.3","text":" - Ignore Strava activities without a time series.
"},{"location":"reference/changelog/#version-0162","title":"Version 0.16.2","text":" - Make heatmap images that are downloaded look the same as the interactive one.
- Always emit the path when there is something wrong while parsing an activity file.
"},{"location":"reference/changelog/#version-0161","title":"Version 0.16.1","text":" - Fix handling of TCX files on Windows. On that platform one cannot open the same file twice, therefore my approach failed. Now I close the file properly such that this should work on Windows as well.
"},{"location":"reference/changelog/#version-0160","title":"Version 0.16.0","text":" - Add feature to render heatmap from visible area. GH-73
- Remove heatmap image generation from clusters, remove Scikit-Learn dependency.
- Add offsets for equipment. GH-71
- Fix number of tile visits in explorer view. GH-69
- Add action to convert Strava checkout to our format. GH-65
- Filter out some GPS jumps. GH-54
- Add simple search function. GH-70
"},{"location":"reference/changelog/#version-015","title":"Version 0.15","text":""},{"location":"reference/changelog/#version-0153","title":"Version 0.15.3","text":" - Create temporary file for TCX parsing in the same directory. There was a problem on Windows where the program didn't have access permissions to the temporary files directory.
"},{"location":"reference/changelog/#version-0152","title":"Version 0.15.2","text":" - Try to open GPX files in binary mode to avoid encoding issues. GH-74
"},{"location":"reference/changelog/#version-0151","title":"Version 0.15.1","text":" - Add
if __name__ == \"__main__\"
clause such that one can use python -m geo_activity_playground
on Windows.
"},{"location":"reference/changelog/#version-0150","title":"Version 0.15.0","text":" - Export all missing tiles in the viewport, not just the neighbors.
- Automatically retry Strava API when the rate limit is exhausted. GH-67
- Give more helpful error messages when the are no activity files present.
"},{"location":"reference/changelog/#version-014","title":"Version 0.14","text":""},{"location":"reference/changelog/#version-0142","title":"Version 0.14.2","text":" - Fix broken Strava import (bug introduced in 0.14.0).
"},{"location":"reference/changelog/#version-0141","title":"Version 0.14.1","text":" - Fix hard-coded part in KML import (bug introduced in 0.14.0).
"},{"location":"reference/changelog/#version-0140","title":"Version 0.14.0","text":" - Do more calculations eagerly at startup such that the webserver is more responsive. GH-58
- Allow setting host and port via the command line. GH-61
- Re-add download of explored tiles in area. GH-63
- Unify time handling, use UTC for all internal representations. GH-52
- Add some sort of KML support that at least works for KML exported by Viking. GH-62
"},{"location":"reference/changelog/#version-013","title":"Version 0.13","text":" - Revamp heatmap, use interpolated lines to provide a good experience even at high zoom levels.
- This also fixes the gaps that were present before. GH-34
- Add cache migration functionality.
- Make sure that cache directory is created beforehand. GH-55
- Split tracks into segments based on gaps of 30 seconds in the time data. That helps with interpolation across long distances when one has paused the recording. GH-47
- Fix introduced bug. GH-56
- Add cache to heatmap such that it doesn't need to render all activities and only add new activities as needed.
- Add a footer. GH-49
- Only export missing tiles in the active viewport. GH-53
- Add missing dependency to SciKit Learn again; I was too eager to remove that. GH-59
"},{"location":"reference/changelog/#version-012","title":"Version 0.12","text":" - Change coloring of clusters, have a color per cluster. Also mark the square just as an overlay.
- Fix bug with explorer tile page when the maximum cluster or square is just 1. GH-51
- Speed up the computation of the latest tiles.
"},{"location":"reference/changelog/#version-011","title":"Version 0.11","text":" - Add last activity in tile to the tooltip. GH-35
- Add explorer coloring mode by last activity. GH-45
- Actually implement
Activity/{Kind}/{Equipment}/{Name}.{Format}
directory structure. - Document configuration file.
- Interpolate tracks to find more explorer tiles. GH-27
- Fix bug that occurs when activities have no distance information.
- Show time evolution of the number of explorer tiles, the largest cluster and the square size. GH-33
- Center map view on biggest explorer cluster.
- Show speed distribution. GH-42
"},{"location":"reference/changelog/#version-010","title":"Version 0.10","text":" - Use a grayscale map for the explorer tile maps. GH-38
- Explicitly write \u201c0 km\u201d in calendar cells where there are no activities. GH-39, GH-40
"},{"location":"reference/changelog/#version-09","title":"Version 0.9","text":" - Certain exceptions are not skipped when parsing files. This way one can gather all errors at the end. GH-29
- Support TCX files. GH-8
- Fix equipment view when using the directory source. GH-25
- Fix links from the explorer tiles to the first activity that explored them. GH-30
- Fix how the API response from Strava is handled during the initial token exchange. GH-37
"},{"location":"reference/changelog/#version-08","title":"Version 0.8","text":""},{"location":"reference/changelog/#version-083","title":"Version 0.8.3","text":" - Only compute the explorer tile cluster size if there are cluster tiles. Otherwise the DBSCAN algorithm doesn't work anyway. GH-24
- Remove allocation of huge array. GH-23
"},{"location":"reference/changelog/#version-082","title":"Version 0.8.2","text":" - Some FIT files apparently have entries with explicit latitude/longitude values, but those are null. I've added a check which skips those points.
"},{"location":"reference/changelog/#version-081","title":"Version 0.8.1","text":" - Fix reading of FIT files from Wahoo hardware by reading them in binary mode. GH-20.
- Fix divide-by-zero error in speed calculation. GH-21
"},{"location":"reference/changelog/#version-080","title":"Version 0.8.0","text":" - Make heart rate zone computation a bit more flexibly by offering a lower bound for the resting heart rate.
- Open explorer map centered around median tile.
- Compute explorer cluster and square size, print that. GH-2
- Make it compatible with Python versions from 3.9 to 3.11 such that more people can use it. GH-22
"},{"location":"reference/changelog/#version-07","title":"Version 0.7","text":" - Add Squadratinhos, which are explorer tiles at zoom 17 instead of zoom 14.
- Reduce memory footprint for explorer tile computation.
"},{"location":"reference/changelog/#version-06","title":"Version 0.6","text":" - Interactive map for each activity.
- Color explorer tiles in red, green and blue. GH-2
- Directly serve GeoJSON and Vega JSON embedded in the document.
- Automatically detect which source is to be used. GH-16
- Fix the name of the script to be
geo-activity-playground
and not just geo-playground
. GH-11 - Add mini maps to the landing page. GH-9
- Add fullscreen button to the maps. GH-4
- Add favicon. GH-19
- Added some more clever caching to the explorer tiles such that loading the page with explorer tiles comes up in just a few seconds.
- Add a triplet of time series plots (distance, altitude, heart rate) for each activity.
- Show plot for heart rate zones per activity. GH-12
- Handle activities without any location points. GH-10
- Resolve Strava Gear name. GH-18
- Add page for equipment. GH-3
- Add a pop-up with some metadata about the first visit to the explorer tiles. GH-14
- Integrate missing explorer tiles into the web interface. GH-7.
- Color activity line with speed. GH-13
- Add interactive heatmap.
- Add margin to generated heatmaps. GH-1
"},{"location":"reference/changelog/#version-05","title":"Version 0.5","text":" - Add some plots for the Eddington number. GH-3
"},{"location":"reference/changelog/#version-04","title":"Version 0.4","text":""},{"location":"reference/changelog/#version-03","title":"Version 0.3","text":" - Start to build web interface with Flask.
- Remove tqdm progress bars and use colorful logging instead.
- Add interactive explorer tile map.
"},{"location":"reference/changelog/#version-02","title":"Version 0.2","text":" - Unity command line entrypoint.
- Crop heatmaps to fit.
- Export missing tiles as GeoJSON.
- Add Strava API.
- Add directory source.
"},{"location":"reference/changelog/#version-01","title":"Version 0.1","text":""},{"location":"reference/changelog/#version-013_1","title":"Version 0.1.3","text":" - Generate some heatmap images.
- Generate an explorer tile video.
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Geo Activity Playground","text":"This is a software to view recorded outdoor activities and derive various insights from your data collection. All data is kept on your machine, hence it is suitable for people who have an affinity for data analysis and privacy.
It caters to serve a similar purpose as Strava with Statshunters statshunters does but while not requiring you to share your location data with a service provider.
One can use this program with a local collection of activity files (GPX, FIT, TCX, KML, CSV) or via the Strava API. The latter is appealing to people who want to keep their data with Strava primarily. In case that one wants to move, this might provide a noncommital way of testing out this project.
The main user interface is web-based, you can run this on your Linux, Mac or Windows laptop. If you want, it can also run on a home server or your personal cloud instance.
"},{"location":"#screenshot-tour","title":"Screenshot tour","text":"This is the view of a single activity:
You also get a beautiful interactive heatmap of all your activities:
Also there are plenty of summary statistics that lets you track how many rides, walks or hikes you have done:
If you're into explorer tiles or squadratinhos, this got you covered:
The configuration options are available within the interface such that you do not have to work with configuration files (like in earlier versions):
"},{"location":"#get-started","title":"Get started","text":"Install the software using one of the following options:
- Using the stable version on Linux
- Using the stable version on Windows
- Using the development version on Linux
Get your activity data in place using one of the following options:
- Use local activity files if you already have these.
- Use the Strava API if you want to use this project to analyse your data on Strava.
- Move from Strava if you wan to leave Strava and work locally from then on.
"},{"location":"#free-software","title":"Free software","text":"You can find the code on GitHub where you can also file issues. If you would like to use this yourself or contribute, feel free to reach out via the contact options from my website. I would especially appreciate improvements to the documentation. If you're familiar with Markdown and GitHub, you can also directly create a pull request. The code is licensed under the very permissive MIT license.
"},{"location":"acknowledgments/","title":"Acknowledgments","text":"This project builds on many amazing other projects and would not be possible without them.
"},{"location":"acknowledgments/#bootstrap-css","title":"Bootstrap CSS","text":"Writing CSS is not a trivial task. For many projects I have been using the Bootstrap CSS Framework which provides sensible default values, a 12-column grid system and a lot of components. Using this I didn't have to write any CSS myself and just attach a couple of classes to HTML elements.
"},{"location":"acknowledgments/#coloredlogs","title":"coloredlogs","text":"Log messages in multiple colors are neat. Using the coloredlogs package we can get these super easily.
"},{"location":"acknowledgments/#fitdecode","title":"fitdecode","text":"For reading FIT files I use the fitdecode library which completely handles all the parsing of this file format.
"},{"location":"acknowledgments/#flask","title":"Flask","text":"The webserver is implemented with Flask which provides a really easy way to get started. It also ships with a development webserver which is enough for this project at the moment.
"},{"location":"acknowledgments/#geojson","title":"GeoJSON","text":"Transferring geographic geometry data from the Python code to Leaflet is easiest with using the GeoJSON format. The official standard RFC is a bit hard to read, rather have a look at the Wikipedia article. And there is an online viewer that you can try out.
"},{"location":"acknowledgments/#github","title":"GitHub","text":"For a smooth open source project one needs a place to share the code and collect issues. GitHub provides all of this for free.
"},{"location":"acknowledgments/#gpxpy","title":"gpxpy","text":"For reading GPX files I use the gpxpy library. This allows me to read those files without having to fiddle with the underlying XML format.
"},{"location":"acknowledgments/#leaflet","title":"Leaflet","text":"The interactive maps on the website are powered by Leaflet, a very easy to use JavaScript library for embedding interactive Open Street Map maps. It can also display GeoJSON geometries natively, of which I also make heavy use.
"},{"location":"acknowledgments/#mkdocs","title":"MkDocs","text":"Writing documentation is more fun with a nice tool, therefore I use MkDocs together with Material for MkDocs. This powers this documentation.
"},{"location":"acknowledgments/#open-street-map","title":"Open Street Map","text":"All the maps displayed use tiles from the amazing Open Street Map. This map is created by volunteers, the server hosting is for free. Without these maps this project would be quite boring.
"},{"location":"acknowledgments/#pandas","title":"Pandas","text":"Working with thousands of activities, thousands of tiles and millions of points makes it necessary to have a good library for number crunching structured data. Pandas offers this and gives good performance and many features.
"},{"location":"acknowledgments/#parquet","title":"Parquet","text":"I need to store the intermediate data frames that I generate with Pandas. Storing as JSON has disadvantages because dates are not properly encoded. Also it is a text format and quite verbose. The Parquet format is super fast and memory efficient.
"},{"location":"acknowledgments/#poetry","title":"Poetry","text":"For managing all the Python package dependencies I use Poetry which makes it very easy to have all the Python project housekeeping with one tool.
"},{"location":"acknowledgments/#python","title":"Python","text":"Almost all of the code here is written in Python, a very nice and versatile programming language with a vast ecosystem of packages.
"},{"location":"acknowledgments/#requests","title":"Requests","text":"For doing HTTP requests I use the Requests library. It provides a really easy to use interface for GET and POST requests.
"},{"location":"acknowledgments/#scikit-learn","title":"Scikit-learn","text":"Finding out which cluster is the largest one can either be formed as a graph search problem or as a data science problem. Using the Scikit-learn library I can easily use the DBSCAN algorithm to find the clusters of explorer tiles.
"},{"location":"acknowledgments/#statshunters","title":"Statshunters","text":"The Statshunters page allows to import the activities from Strava and do analysis like explorer tiles, Eddington number and many other things. This has served as inspiration for this project.
"},{"location":"acknowledgments/#strava","title":"Strava","text":"Although I have recorded some of my bike rides, I only really started to record all of them when I started to use Strava. This is a nice platform to track all activities. They also offer a social network feature, which I don't really use. They provide some analyses of the data, but they lack some analyses which I have now implemented in this project.
"},{"location":"acknowledgments/#stravalib","title":"stravalib","text":"Strava has an API, and with stravalib there exists a nice Python wrapper. This makes it much easier to interface with Strava.
"},{"location":"acknowledgments/#strava-local-heatmap","title":"Strava local heatmap","text":"https://github.com/remisalmon/Strava-local-heatmap
"},{"location":"acknowledgments/#tcxreader","title":"tcxreader","text":"https://github.com/alenrajsp/tcxreader
"},{"location":"acknowledgments/#vega-altair","title":"Vega & Altair","text":"https://altair-viz.github.io/index.html https://vega.github.io/vega/
"},{"location":"acknowledgments/#velo-viewer","title":"Velo Viewer","text":""},{"location":"features/activity-view/","title":"Activity View","text":"When you have selected a particular activity, you can view various details about it. This is what the screen looks like, we will go through the different parts in the following.
"},{"location":"features/activity-view/#metadata","title":"Metadata","text":"You have a column with metadata about the activity. The activity kind, whether it is a commute and the equipment are currently only supported via the Strava API, but we can build something to infer that from directories as well.
The calories are broken in the Strava API wrapper library that I use, therefore they don't show even if they are there.
You can also see the ID which is an internal ID. When you use Strava API as a source, it will use the IDs that Strava gives. When you use files from a directory it will be computed from a hash of the path to the activity file.
"},{"location":"features/activity-view/#map-with-track","title":"Map with track","text":"The interactive map shows a line with the activity. The speed is color-coded and peaks at 35 km/h with a yellow color.
"},{"location":"features/activity-view/#distance-speed-and-altitude","title":"Distance, speed and altitude","text":"Then there are a couple of time series plots. One is the distance vs. time. You can see how much distance you covered when and also see plateaus when you went on a break.
From this we can also compute the speed, although that might be pretty noisy:
And more interesting is the distribution of the various speed zones. This gives you an understanding how much time you spent at which speed. The buckets are set in 5 km/h intervals, but we could also change that.
If the time series data has the altitude, which isn't always the case, you can see it in the plot there. Here we can see how I did a tour and continually rode downhill. Except at the end where I had to climb in order to get the explorer tile that I wanted.
"},{"location":"features/activity-view/#heart-rate","title":"Heart rate","text":"The heart rate isn't too helpful, I feel. Still I've created the plot from the given data.
More interesting regarding the heart rate are the zones which one has spent time during this activity.
The definition of the heart rate zones is not standardized. Usually there are five zones and they have the same names. What differs is how their ranges are computed and there is some chaos around that.
All definitions that I found take the maximum heart rate as the upper limit. One can measure this as part of a professional training or just use the 220 minus age prescription which at least for me matches close enough. What they differ on is how they use a lower bound. It seems that Polar or REI basically use 0 as the lower bound. My Garmin system also uses 0 as the lower bound. But as one can see in this blog, one can also use the resting heart rate as the lower bound.
Based on the maximum and resting heart rate we will then compute the heart rate zones using certain percentages of effort. We can compute the heart rate as the following:
rate = effort \u00d7 (maximum \u2013 minimum) + minimum
The zones then take the following efforts:
Zone Effort Training 1 50 to 60 % Warmup/Recovery 2 60 to 70 % Base Fitness 3 70 to 80 % Aerobic Endurance 4 80 to 90 % Anerobic Capacity 5 90 to 100 % Speed Training You can decide how you want to do work with that. If you want to have the same definitions that say Garmin uses, you need to just enter your birth year and we can compute the rest. If you want to use a lower bound, you need to specify that.
"},{"location":"features/calendar/","title":"Calendar","text":"In order to access all the activities, there is a calendar view. It shows the years in rows and the months in columns. Each cell is a particular month and indicates the total distance traveled.
When you click on any of the months, you will see a calendar for a given month. Here is one of my months which doesn't show too personal data:
Clicking on an activity will lead you to the activity detail view.
"},{"location":"features/eddington/","title":"Eddington Number","text":"The astronomer Sir Arthur Eddington like to go on longer bike rides. Apparently he did a lot of rides and had 84 days where he rode at least 84 miles. The Eddington number for cycling was coined from this.
If you have an Eddington number E, it means that you have had E days with at least E kilometer distance. At the time of writing my number is 62, which means that I rode at least 62 km on 62 separate days. If I want to extend it to 63, I would need to have at least 63 km on 63 separate days. My bike rides that are just 62 km long will not count any more, making this challenge really hard.
In the following plot you can see in blue the number of days that exceed the given distance. You can see that I have a 998 days that exceed 1 km, that's pretty easy when one just records enough data over many years. But then there are only 259 days where I exceed 20 km as this is beyond the distance that I have when I only go for a walk or run some simple errands.
The red curve indicates how many rides one needs to get the Eddington number. As it is a semi-log plot, the straight line is curved like a log-curve.
You can see this cliff at around 80 km. That is the distance that I ride to work and back. I have many days with around 80 km, but longer rides are only on occasional bike trips. Therefore I think I will eventually make it to an Eddington number of 80, but beyond that will be super difficult.
This is a life-long challenge, so who knows what happens in the future.
"},{"location":"features/eddington/#length-unit","title":"Length unit","text":"The definition of the Eddington number depends on the length unit that one has. Eddington as a British person used the English mile as a base unit. Therefore his number of 84 is actually harder to achieve than a 84 based on kilometers because he needed to exceed 135 km (84 mi) on each ride.
Therefore using a different length unit as a base changes the meaning. The kilometer is easier, the mile (1609.344 m) is harder. One could also use nautical miles (1852 m). And while we are at arbitrary unit systems, we could also use furlongs (201.168 m).
"},{"location":"features/equipment/","title":"Equipment Overview","text":"When activities are tagged with the used equipment, one can tally the total distance traveled with each thing. This given as a table with the recently used equipment at the top:
And for each thing there is also a graph which shows how the distance accumulates over time:
Here one can see how with my old bike I only recorded the occasional bike trip and not nearly the 4000 km/a which I was doing. And then it got stolen in 2019, so I bought a new bike.
This metadata is downloaded via the Strava API, for the directory source it is not yet supported. I thought about using directories like Activities/{Activity Kind}/{Equipment Name}/{Activity Name}.gpx
. to indicate the kind and equipment. If you're interested in implementing this, let me know.
"},{"location":"features/explorer-tiles/","title":"Explorer Tiles","text":"Maps accessible via the web browser are usually served as little image tiles. The Open Street Map uses the Web Mercator coordinate system to map from latitude and longitude to pixels on the map.
Each tile is 256\u00d7256 pixels in size. The zoom levels zoom in by a factor of two. Therefore all the tiles are organized in a quad tree. As you zoom in, each tile gets split into four tiles which can then show more detail. The following prescription maps from latitude and longitude (given in degrees) to tile indices:
def compute_tile(lat: float, lon: float, zoom: int = 14) -> tuple[int, int]:\n x = np.radians(lon)\n y = np.arcsinh(np.tan(np.radians(lat)))\n x = (1 + x / np.pi) / 2\n y = (1 - y / np.pi) / 2\n n = 2**zoom\n return int(x * n), int(y * n)\n
At zoom level 14 the tiles have a side length of roughly 1.5 km in Germany. These tiles are used as the basis for explorer tiles. The basic idea is that every tile where you have at least one point in an activity is considered an explored tile.
From your activities the program will extract all the tiles that you have visited. And then it does a few things with those. One main thing is that it will display these on an interactive map. When we zoom into one area where I've been on vacation in 2023, you can see the explored tiles there:
The filled tiles are explored, I have been there. The colored tiles are cluster tiles, that means that all their four neighbor tiles are also explored.
You can see here how I have explored a region and ensured that it is mostly contiguous.
There is another vacation from 2013 where I wasn't aware of the cluster tiles. I just did some bike trips and didn't look out for the tiles. There the tiles look like this:
You see all these gaps in there. Also there are three different clusters which are not connected. Each unique cluster is assigned a different color such that one can see where there are gaps between the cluster tiles. And filling the gaps is what the explorer tiles are about: This OCD (obsessive compulsive disorder) like craving to fill in the gaps.
Let's take a look at my main cluster of explorer tiles. Here I have explored much more than in the areas where I was on vacation.
You can see an additional feature, the blue square. This is the one largest square which can be fit into all explored tiles. In this picture it has size 21\u00b2. The idea of the square is to have a really tough challenge. Not only does one need to explore increasingly many tiles to expand the square by one unit, there must not be any gaps.
As you can see in this picture, there is a tile missing right at the top edge. I will never be able to get that because that is an off-limits area of the German air force at the airport. So I can expand my square to the south only.
You can click on each tile and get some information about that particular tile. You can see when you first explored that and with which activity. Also it shows the last activity there as well as the number of activities. If it is a local cluster, it will also show the cluster size.
There is also the option to color the tiles by first or last visit. Use one of the buttons above the map:
Then the map will show the first visit:
Or how recent your last visit is:
This uses Matplotlib's Plasma scale (see below) to color the age of a tile. Very new tiles will get a yellow color, a year old tiles a reddish color and tiles two years old or older a colder blue. This is the scale:
You can switch this with the buttons above the map.
"},{"location":"features/explorer-tiles/#squadratinhos","title":"Squadratinhos","text":"The explorer tiles at zoom level 14 are best suited for cycling and to discover the area around the city. There is a derived definition, the squadratinhos which are defined at zoom level 17 and therefore a factor 8 smaller in each direction. Each explorer tile is therefore divided into 256 squadratinhos.
These are better suited for walking and making sure that you really explored every little place in your neighborhood. Since they are so small, there are many properties which one cannot go onto, like industrial sites, airports or just a wide river.
For my home city it looks like this:
You can see how the squadratinhos are much smaller than the explorer tiles and how they lend themselves to more local exploring.
"},{"location":"features/explorer-tiles/#history","title":"History","text":"The map only shows the current state of your explorer tiles. In order to get a sense of how many new tiles you have discovered in the past, there are also plots that show you how you have extended the total number of squares, the size of your largest cluster and the size of your largest square over time:
"},{"location":"features/explorer-tiles/#missing-tile-files","title":"Missing tile files","text":"Looking at these maps you can see the gaps. And if you feel challenged to fill those, you might want to plan a \u201ctactical bike ride\u201d to explore those. Let us take another look at my tile history in Sint Annaland:
You can see those gaps in the clusters. To make it easier to explore tiles while on the go, we can export a file with the missing tiles. Pan and zoom the map to an area which you want to export. Below the map you will find two links:
Download missing tiles in visible area as GeoJSON or GPX.
This export is available as GeoJSON or GPX such that you can open it with other applications. For instance with GPX See on Linux it looks like this when opening the GeoJSON file:
You can then upload the GeoJSON file to Bikerouter and it will display there:
Then plan a route that goes through as many tiles as possible. Download the route as GPX and use an app like OsmAnd to ride along it.
"},{"location":"features/explorer-tiles/#missing-tiles-on-the-go","title":"Missing tiles on the go","text":"The above is nice to plan the route, perhaps you also want to take the missing tiles along to do spontaneous tile hunting.
A possibility should be Organic Maps which is a FOSS app that can display offline maps and also show GPX files.
Another method is to use Open Street Map uMap, either the one hosted in Germany or France. Then you can create a new personal map (consider limiting the access rights, default is public) and upload the GeoJSON file. Then you can use that map on the code to see your position and the missing tiles:
Yet another option is Offline Maps. That is able to display GeoJSON on Android, though one needs to buy the add-on for like 5 EUR.
On Android one can use the OsmAnd app to display tracks and also try to visualize the missing tiles. Unfortunately GeoJSON is not supported, therefore one has to play some tricks. The missing tiles are also exported as a GPX file with a track for each missing tile. This looks strange, but it is a bit helpful with OsmAnd. This is how the file looks like in GPXSee:
And on OsmAnd such files look like this:
Unfortunately OsmAnd becomes a very sluggish with such a huge track imported, so make sure to only export it from rather small regions.
"},{"location":"features/explorer-tiles/#square-planner","title":"Square planner","text":"From the explorer tile views you can open the square planner which allows you to see which tiles you need to explore in order to extend the square into a particular direction. The screen will open with the largest square that you have, then you can use the buttons to extend or move your square.
Using the buttons in the middle you can move the square, the buttons in the corners allow to extend or shrink the square.
When you have selected the square that you want to target, you can download the missing files in for that square as GeoJSON or GPX.
"},{"location":"features/heatmaps/","title":"Heatmaps","text":"A heatmap shows where you have been more often than other places. There is an interactive and zoomable heatmap that looks like this:
Here you can see where I mostly travel between Bonn and Cologne.
You have the option to download a rendered heatmap from the current viewport of the interactive map as in image with up to 4000\u00d74000 pixels, there is a link below the map.
"},{"location":"features/kind-rename/","title":"Kind rename","text":"Metadata and importing from several sources can be messy and in some cases Strava will export its acivity types/activity kinds under names like root='Ride' and not \"Ride\". That can lead to issues with tagging and the heatmap - in this case we have multiple, overlapping activity kinds for the same activity kinds.
To fix this, go to Admin -> Settings -> Kind rename
- Type in the box to define how you want to reprocess your activities
- The Format is
Existing name => New name
- f.e. to rename
root='Ride'
to 'Ride'
put in root='Ride' => Ride
- Click on \"Save\", the system will then reprocess all affected activities
- This can take a few minutes depending on the amount of activities
"},{"location":"features/overview/","title":"Overview page","text":"When you start the webserver, you will see the overview page. It looks like the following and shows the activities you've done in the past 30 days:
Then below that you see the latest 15 activities with their tracks on interactive maps.
Each card contains the name, activity type, distance and duration. The non-commute activities are highlighted with a blue border, the commutes just have a gray border.
Click on any of the names and you will see the details of that activity.
"},{"location":"features/share-picture/","title":"Share Picture","text":"On each activity page you will find a \"share picture\" like the following:
"},{"location":"features/share-picture/#privacy-zones","title":"Privacy zones","text":"You might want to remove points that are close to your home, work or relatives. For this you can define arbitrary polygons as \"privacy zones\".
To create such a polygon, go to GeoJSON.io. You will see a map similar to this one:
Select the polygon tool and click on the map to span the polygon.
Once you are done, press Enter to finish the polygon. In the left panel the GeoJSON output will appear:
For this case, we have this GeoJSON:
{\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"coordinates\": [\n [\n [\n 6.87987514009842,\n 50.68272071401333\n ],\n [\n 6.878628929151887,\n 50.6819310903943\n ],\n [\n 6.8780142440226655,\n 50.68125883278765\n ],\n [\n 6.879563587362242,\n 50.68022375065988\n ],\n [\n 6.880599289703014,\n 50.68029311254671\n ],\n [\n 6.8814665851591315,\n 50.68102940933676\n ],\n [\n 6.881542368256589,\n 50.681723011688035\n ],\n [\n 6.8812729172415175,\n 50.682176515374465\n ],\n [\n 6.87987514009842,\n 50.68272071401333\n ]\n ]\n ],\n \"type\": \"Polygon\"\n }\n }\n ]\n}\n
Paste this in the appropriate settings menu.
You can name the zone to help you remember what it encompasses. You can add multiple zones.
Points that are within any of the privacy zones will not be shown in the share pictures. Except when all of the points are in the privacy zone, then all the points will be shown.
"},{"location":"features/upload/","title":"Uploading activities","text":"Some users don't want to restart the application each time they add new activities but run it on their home server. For this use case you can upload activities. Uploading files is a potential security issue and it is protected with a password. This feature is only enabled when you have set a password in the configuration file.
Put the following into your config.json
file:
{\n \"upload_password\": \"fb1c8d62-07a5-47bf-ada1-30aa66e41d8a\"\n}\n
Be sure to choose your own password and not use the one from this documentation.
Then you can go to the upload page and see this form:
Select a file that you want to upload and a target directory within the \u201cActivities\u201d directory. Finally, enter the password from the configuration file.
After you have uploaded the file, you will be redirected to the parsed activity.
"},{"location":"getting-started/advanced-metadata-extraction/","title":"Advanced Metadata extraction","text":"If you would like to set the metadata fields or change what part of the filename should be the activity name, you can use a custom directory structure with corresponding regular expressions.
An example directory structure:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n\u251c\u2500 Hike/\n\u2502 \u251c\u2500 Hiking Boots 2019/\n\u2502 \u2502 \u251c\u2500 2024-03-03-11-03-18 Some nice place with Alice and Bob.fit\n
"},{"location":"getting-started/advanced-metadata-extraction/#custom-regular-expressions","title":"Custom Regular expressions","text":"The program uses regular expressions to search for patterns in the relative path (in Activities) and extracts the relevant parts with named capture groups (?P<kind>)
, (?P<equipment>)
, (?P<name>)
.
You can use python to test your regular expressions. Read the python re documentation for help.
import re\nre.search(r'(?P<kind>[^/]+)/(?P<equipment>[^/]+)/(?P<name>[^/.]+)', '/Ride/Trekking Bike/2024-03-03-17-42-10 Home to Bakery.gpx').groupdict()\n
{'kind': 'Ride', 'equipment': 'Trekking Bike', 'name': '2024-03-03-17-42-10 Home to Bakery'}\n
You can add your custom regular expressions under the Admin
menu - Settings
- Metadata Extraction
in the WebUI. Settings are saved in your Playground
directory.
"},{"location":"getting-started/advanced-metadata-extraction/#filename-as-name-simple","title":"Filename as Name (simple)","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/(?P<name>[^/.]+)\n
- kind:
Ride
- equipment:
Trekking Bike
- name:
2024-03-03-17-42-10 Home to Bakery
"},{"location":"getting-started/advanced-metadata-extraction/#filename-without-date-as-name-useful-for-osmand-naming","title":"Filename without date as Name (useful for OsmAnd naming)","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n\u2502 \u2502 \u251c\u2500 2024-03-04-16-52-26.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-21_10-28_Sun OsmAnd default track.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-22_07-55_Mon.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/[-\\d_ ]+(?P<name>[^/]+)(?:\\.\\w+)+$\n
- kind:
Ride
- equipment:
Trekking Bike
- names:
Home to Bakery
;
; Sun OsmAnd default track
; Mon
Attention, name may be empty if it is not included in the file name. For OsmAnd default naming the weekday is included in the name.
"},{"location":"getting-started/advanced-metadata-extraction/#filename-after-first-space-as-name","title":"Filename after first space as Name","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-22_07-55_Mon.gpx\n\u2502 \u2502 \u251c\u2500 2024-04-21_10-28_Sun OsmAnd default track.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/\\S+ ?(?P<name>[^/\\.]*)\n
- kind:
Ride
- equipment:
Trekking Bike
- names:
Home to Bakery
;
; OsmAnd default track
Attention, name may be empty if it is not included in the file name (also for OsmAnd default naming).
"},{"location":"getting-started/advanced-metadata-extraction/#grouping-activity-files-under-a-common-name-for-example-all-your-commutes","title":"Grouping activity files under a common name, for example all your commutes","text":"Path:
Activities/\n\u251c\u2500 Ride/\n\u2502 \u251c\u2500 Trekking Bike/\n\u2502 \u2502 \u251c\u2500 Commute/\n\u2502 \u2502 \u2502 \u251c\u2500 2024-03-04-07-06-12.gpx\n\u2502 \u2502 \u2502 \u251c\u2500 2024-03-04-15-42-32.gpx\n
(?P<kind>[^/]+)/(?P<equipment>[^/]+)/(?P<name>[^/]+)/\n
- kind:
Ride
- equipment:
Trekking Bike
- name:
Commute
(for all activities in Commute directory )
"},{"location":"getting-started/advanced-metadata-extraction/#activities-without-equipment","title":"Activities without equipment","text":"Path:
Activities/\n\u251c\u2500 Run/\n\u2502 \u251c\u2500 2024-03-09-09-24-03 To the lake.gpx\n\u2502 \u251c\u2500 2024-03-10-09-44-37 To the top of the hill.gpx\n
(?P<kind>[^/]+)/[-\\d_ ]+(?P<name>[^/]+)(?:\\.\\w+)+$\n
- kind:
Run
- equipment:
Unknown
- names:
To the lake
; To the top of the hill
"},{"location":"getting-started/advanced-metadata-extraction/#next-steps","title":"Next Steps","text":"If you you manually rename, move or delete your activity files, the program needs to reload to respect these changes. You can restart the program or visit Scan New Activities
in the admin menu of the WebUI.
"},{"location":"getting-started/docker-compose-tailscale/","title":"Using Git Version via Docker Compose and Tailscale VPN","text":"Docker is a software that allows you to run Linux programs in a container. Docker Compose is a tool for defining multi-container Docker environments in a single YAML configuration file and deploy it with a single command.
Tailscale is a VPN solution based on the Wireguard protocol which lets you connect all devices within your virtual private network (tailnet). The Tailscale Docker container exposes the services only via a direct VPN connection, which avoids exposing ports to the open internet to connect to your geo-activity-playground instance on-the-go. It provides a domain with a valid Let's Encrypt certificate which is only accessible via the tailnet. The configuration is based on Docker Tailscale Guide.
This how-to will give you an example compose.yml
that can build the geo-activity-playground docker image from Github and start this project within a Docker container and connecting it via Tailscale.
"},{"location":"getting-started/docker-compose-tailscale/#tailscale-prerequisites","title":"Tailscale Prerequisites","text":" - Active account
- Enabled MagicDNS (in DNS section of admin console)
- Enabled HTTPS (in DNS section of admin console)
- Auth-Key
- ACL policy for tag
"},{"location":"getting-started/docker-compose-tailscale/#create-auth-key-and-acl-policy-for-tag","title":"Create Auth-Key and ACL policy for tag","text":"More information on generating auth keys Navigate to https://login.tailscale.com/admin/settings/keys and generate an auth key.
Example Auth-Key configuration: - Description: docker - Reusable: yes - Expiration: 7 days - Ephemeral: No - Tags: tag:container
In order to use the tag, it must first be defined in your Access control policy in the admin console. Set the same tag as in the Auth-Key.
\"tagOwners\": {\n \"tag:container\": [\"autogroup:admin\"],\n},\n
When you apply a tag to a device for the first time and authenticate it, the tagged device's key expiry is disabled by default.
"},{"location":"getting-started/docker-compose-tailscale/#preparing-tailscale-configuration","title":"Preparing Tailscale configuration","text":"The geo-activity-playground service will be made available by using the Tailscale Serve functionality. It routes traffic from other devices on your Tailscale network (known as a tailnet) to a local service, in this case inside the container. It creates a reverse proxy to the geo-activity-playground container internal port 5000 (do not change it). TS_CERT_DOMAIN
is comprised of a subdomain (hostname set in the compose.yml
) and the tailnet root domain.
mkdir -p /docker/geo-activity-playground/{ts-state,ts-config}\ncd /docker/geo-activity-playground/ts-config\nnano geo-activity-playground.json\n
{\n \"TCP\": {\n \"443\": {\n \"HTTPS\": true\n }\n },\n \"Web\": {\n \"${TS_CERT_DOMAIN}:443\": {\n \"Handlers\": {\n \"/\": {\n \"Proxy\": \"http://127.0.0.1:5000\"\n }\n }\n }\n }\n}\n
"},{"location":"getting-started/docker-compose-tailscale/#compose-configuration-with-tailscale-network","title":"Compose configuration with Tailscale network","text":"With these steps the playground folder (which contains the activities) will be located in the docker project folder. The location can be changed in the compose.yml
.
mkdir -p /docker/geo-activity-playground/playground/Activities\ncd /docker/geo-activity-playground\nnano compose.yml\n
services:\n ts-geo-activity-playground:\n image: tailscale/tailscale:latest\n container_name: ts-geo-activity-playground\n hostname: geo-activity-playground # set your desired name, which will be the tailscale subdomain\n environment:\n - TS_AUTHKEY=tskey-auth-yyyyyyyyyyyyyyyyyyyyyyyyyyyy # paste your created Auth-Key\n - TS_EXTRA_ARGS=--advertise-tags=tag:container # set the same tag as in the Auth-Key\n - TS_SERVE_CONFIG=/config/geo-activity-playground.json\n - TS_STATE_DIR=/var/lib/tailscale\n volumes:\n - /docker/geo-activity-playground/ts-state:/var/lib/tailscale\n - /docker/geo-activity-playground/ts-config:/config\n - /dev/net/tun:/dev/net/tun\n cap_add:\n - net_admin\n - sys_module\n restart: unless-stopped\n geo-activity:\n build:\n context: https://github.com/martin-ueding/geo-activity-playground.git\n # this sets the build context to the DOCKERFILE located in the Github repository\n container_name: geo-activity-playground\n depends_on:\n - ts-geo-activity-playground # start container after the VPN network is active\n network_mode: service:ts-geo-activity-playground # link container network to tailscale container\n volumes:\n - /docker/geo-activity-playground/playground:/data # optional: change left side to your desired playground directory\n restart: unless-stopped\n
If you want to build the release version of geo-activity-playground from Github instead, you can adjust the build context and add the release tag. context: https://github.com/martin-ueding/geo-activity-playground.git#0.29.1
"},{"location":"getting-started/docker-compose-tailscale/#building-image-and-running-container","title":"Building image and running container","text":"You need to set up your files according to one of the presented methods, like activity files or the Strava API. Consult the other pages in the sidebar for the details.
Once you have your playground directory, you can build the image and start the container.
docker compose build\ndocker compose up -d\n
This will start the webserver and expose it via your tailnet on https://[HOSTNAME].[YourTailnetName].ts.net/
, eg. https://geo-activity-playground.tail41a3.ts.net/
. In order to access your instance via that domain, you have to install and authenticate the Tailscale client app on your device you want to open it from.
"},{"location":"getting-started/docker-compose-tailscale/#updating-the-image","title":"Updating the image","text":"If using the tagged release version of geo-activity-playground, update the tag to the latest one first.
docker compose down\ndocker compose build\ndocker compose up -d --force-recreate\n
"},{"location":"getting-started/docker-compose/","title":"Using Git Version via Docker Compose","text":"Docker is a software that allows you to run Linux programs in a container. Docker Compose is a tool for defining multi-container Docker environments in a single YAML configuration file and deploy it with a single command.
This how-to will give you an example compose.yml
that can build the geo-activity-playground docker image from Github and start this project within a Docker container.
"},{"location":"getting-started/docker-compose/#creating-directory-structure-and-composeyml","title":"Creating directory structure and compose.yml","text":"With these steps the playground folder (which contains the activities) will be located in the docker project folder. The location can be changed in the compose.yml
.
mkdir -p /docker/geo-activity-playground/playground/Activities\ncd /docker/geo-activity-playground\nnano compose.yml\n
services:\n geo-activity:\n build:\n context: https://github.com/martin-ueding/geo-activity-playground.git\n # this sets the build context to the DOCKERFILE located in the Github repository\n container_name: geo-activity-playground\n volumes:\n - /docker/geo-activity-playground/playground:/data # optional: change left side to your desired playground directory\n ports:\n - 5000:5000 # optional: change the exposed port on the left side\n restart: unless-stopped\n
If you want to build the release version from Github instead, you can adjust the build context and add the release tag. context: https://github.com/martin-ueding/geo-activity-playground.git#0.29.1
"},{"location":"getting-started/docker-compose/#building-image-and-running-container","title":"Building image and running container","text":"You need to set up your files according to one of the presented methods, like activity files or the Strava API. Consult the other pages in the sidebar for the details.
Once you have your playground directory, you can build the image and start the container.
docker compose build\ndocker compose up -d\n
This will start the webserver on http://localhost:5000/ or at the port you chose to expose.
Note that port 5000 may not be available on macOS because of AirPlay, so you can map to another port.
"},{"location":"getting-started/docker-compose/#updating-the-image","title":"Updating the image","text":"If using the tagged release version, update the tag to the latest one first.
docker compose down\ndocker compose build\ndocker compose up -d --force-recreate\n
"},{"location":"getting-started/docker/","title":"Using Git Version via Docker","text":"Docker is a software that allows you to run Linux programs in a container. This how-to will show you how to build and start this project within a Docker container.
"},{"location":"getting-started/docker/#build-the-image","title":"Build the image","text":"First you need to build the Docker image. For this download the source code and build the image using the following commands:
git clone https://github.com/martin-ueding/geo-activity-playground.git\ncd geo-activity-playground\nsudo docker build -t geo-activity-playground .\n
Perhaps you do not need sudo
on your system.
"},{"location":"getting-started/docker/#run-the-image","title":"Run the image","text":"You need to set up your files according to one of the presented methods, like activity files or the Strava API. Consult the other pages in the sidebar for the details.
Once you have your playground directory, you can launch the Docker image with the following. Be sure to replace path/to/playground
with your path.
sudo docker run -p 5000:5000 -v path/to/playground:/data -it geo-activity-playground\n
This will start the webserver on http://localhost:5000/.
Note that port 5000 may not be available on macOS because of AirPlay, so you can map to another port by replacing the port specifier from above with -p 8000:5000
. Then you can open http://localhost:8000/ in your browser.
"},{"location":"getting-started/installing-git-on-linux/","title":"Installing Git Version On Linux","text":"As this project is still in development, you might want to have a peek into the development version. This is more advanced than using the stable versions, but not impossibly hard.
First you need to clone the git repository from GitHub using the following command:
git clone https://github.com/martin-ueding/geo-activity-playground.git\n
That will create a directory geo-activity-playground
in your current working directory.
Then change into that directory:
cd geo-activity-playground\n
Next we will use Poetry to install the dependencies of the project. First you need to make sure that you have Poetry available. On Ubuntu/Debian run sudo apt install python3-poetry
, on Fedora/RedHat run sudo dnf install poetry
to install it.
Then we can create the virtual environment:
poetry install\n
And next we can run the program:
poetry run geo-activity-playground --basedir path/to/your/playground --help\n
Replace the --help
with the subcommands described in the help message or the other parts described this documentation.
You will need the --basedir
option because you run the program from the source directory and not from your playground directory. If you install the stable version via PIP as described in the other page, you will not need this option.
"},{"location":"getting-started/installing-git-on-linux/#updating-to-the-latest-version","title":"Updating to the latest version","text":"Over time I will add more commits to the source control system. In order to update your clone to the latest version, execute the following:
git pull\n
This will download the missing changesets and apply them to your downloaded version. After that is done, you need to update your virtual environment with this:
poetry install\n
And then you can continue using it as before.
"},{"location":"getting-started/installing-stable-on-linux/","title":"Installing Stable On Linux","text":"In this how-to guide I will show you how you can install the latest stable version of this project on Linux.
Using PIP, you can install the latest version using this command:
pip install --user geo-activity-playground\n
If you get an error about the command pip
not found, you will need to install that first. On Ubuntu or Debian use sudo apt install python3-pip
, on Fedora or RedHat use sudo dnf install python3-pip
. After you have installed PIP, repeat the above command.
"},{"location":"getting-started/installing-stable-on-linux/#ensure-that-the-path-is-correct","title":"Ensure that the PATH is correct","text":"Next you can try to start the program by just entering the following into the terminal:
geo-activity-playground --help\n
If you get a help message, everything is fine. If you get an error about command not found, we need to adjust your PATH. Execute the following in your command line:
xdg-open ~/.profile\n
This brings up an editor with your shell profile. Add a line containing the following at the end of the file:
PATH=$PATH:$HOME/.local/bin\n
This adds the path to your shell environment. This becomes active after you log in again. In order to apply it also to your current shell session, execute export PATH=$PATH:$HOME/.local/bin
in the terminal window. Try the first command in this section again, you should see the help message now.
"},{"location":"getting-started/installing-stable-on-linux/#upgrading-to-the-latest-version","title":"Upgrading to the latest version","text":"At some later point you likely want to upgrade to the latest version. For this use this command:
pip install --user --upgrade geo-activity-playground\n
"},{"location":"getting-started/installing-stable-on-windows/","title":"Installing Stable on Windows","text":"This how-to will show you the installation of the project on Windows. Here in the guide we use Windows 10 with the locale set to German, it should generalize to Windows 11 as well.
"},{"location":"getting-started/installing-stable-on-windows/#installing-python","title":"Installing Python","text":"First we need to install Python because that doesn't ship with Windows. Fortunately we can get it from the Microsoft Store. Open that via the start menu and you should see something like this:
Type \u201cPython\u201d into the search bar at the top. In the search results you likely see different Python versions like 3.11 and 3.10. The project is compatible with 3.10 to 3.12; I'd suggest to just go with 3.12. In case that you have already installed one of the other compatible versions, you can skip this step.
Here we select Python 3.11.
In the top right there is a blue button to install the software. Click that.
"},{"location":"getting-started/installing-stable-on-windows/#installing-the-project","title":"Installing the project","text":"After that has run through, you need to open the Power Shell via the start menu. It should open a command line window like this:
We can verify that Python is working by entering python --version
and pip --version
. It should give a sensible version message like this:
Then we can ues PIP to install the project. Type the following:
pip install -U geo-activity-playground\n
It should look like this:
Then press Enter and it will install it, looking like this:
That might take a while. After that has run through, it should give a success message:
Then we're done with this window, you can close it now.
"},{"location":"getting-started/installing-stable-on-windows/#putting-your-activity-files-in-place","title":"Putting your activity files in place","text":"Next you need to create a folder to put the files. I've placed mine just into the Documents folder. In there I've created a folder named exactly \u201cActivities\u201d.
Inside this folder there are my GPX/FIT/TCX/KML files as outlined in how-to guide on using activity files.
You need to have at least one activity file before you can start the program.
"},{"location":"getting-started/installing-stable-on-windows/#starting-the-webserver","title":"Starting the webserver","text":"Once you have your activity files in place, we need to add a start script. Right-click into the playground folder (next to the \u201cActivities folder\u201d) and in the context menu select \u201cCreate New\u201d and then \u201cText File\u201d. Name it start.bat
. Windows will ask you whether want to change the suffix (file extension) because it might get unusable. Yes, we want to do that. It should look like this:
Then right-click on that file and select \u201cEdit\u201d. A text editor will open up. Put the following content into this file:
python -m geo_activity_playground serve\npause\n
Then save and close it. I need you to create this file yourself and cannot offer a download because the Windows Defender will not allow you to execute such script files downloaded as a security precaution. If you create the file yourself, it will let you execute it.
Once you have the start.bat
there, you can double-click on it to execute it. A new terminal window should open and it should start to parse your activities.
After it has loaded everything, you can open http://127.0.0.1:5000/ in your browser. You should then see the landing page:
Also other functions like the heatmap work:
That's it, have fun!
"},{"location":"getting-started/moving-from-strava/","title":"Moving from Strava","text":"If you have been using Strava up to this point but want to use this project from now on, this is the correct guide. Here I will show how you can convert your data from Strava into the format of this project and keep adding new data without Strava in the future.
"},{"location":"getting-started/moving-from-strava/#download-your-archive-from-strava","title":"Download your archive from Strava","text":"Go to the Strava account download page and request a download of your data. This will take a while and you get a notification via e-mail when it is done.
Once it has run through, you will be able to download a ZIP file. Once extracted, it will have a structure like this:
.\n\u251c\u2500\u2500 activities [2217 entries exceeds filelimit, not opening dir]\n\u251c\u2500\u2500 activities.csv\n\u251c\u2500\u2500 applications.csv\n\u251c\u2500\u2500 bikes.csv\n\u251c\u2500\u2500 blocks.csv\n\u251c\u2500\u2500 categories_of_personal_information_we_collect.pdf\n\u251c\u2500\u2500 clubs\n\u251c\u2500\u2500 clubs.csv\n\u251c\u2500\u2500 comments.csv\n\u251c\u2500\u2500 community_content.json\n\u251c\u2500\u2500 community_personal_data.json\n\u251c\u2500\u2500 components.csv\n\u251c\u2500\u2500 connected_apps.csv\n\u251c\u2500\u2500 contacts.csv\n\u251c\u2500\u2500 email_preferences.csv\n\u251c\u2500\u2500 events.csv\n\u251c\u2500\u2500 favorites.csv\n\u251c\u2500\u2500 flags.csv\n\u251c\u2500\u2500 followers.csv\n\u251c\u2500\u2500 following.csv\n\u251c\u2500\u2500 general_preferences.csv\n\u251c\u2500\u2500 global_challenges.csv\n\u251c\u2500\u2500 goals.csv\n\u251c\u2500\u2500 group_challenges.csv\n\u251c\u2500\u2500 information_we_disclose_for_a_business_purpose.pdf\n\u251c\u2500\u2500 local_legend_segments.csv\n\u251c\u2500\u2500 logins.csv\n\u251c\u2500\u2500 media [252 entries exceeds filelimit, not opening dir]\n\u251c\u2500\u2500 media.csv\n\u251c\u2500\u2500 memberships.csv\n\u251c\u2500\u2500 messaging.json\n\u251c\u2500\u2500 metering.csv\n\u251c\u2500\u2500 mobile_device_identifiers.csv\n\u251c\u2500\u2500 orders.csv\n\u251c\u2500\u2500 partner_opt_outs.csv\n\u251c\u2500\u2500 posts.csv\n\u251c\u2500\u2500 privacy_zones.csv\n\u251c\u2500\u2500 profile.csv\n\u251c\u2500\u2500 profile.jpg\n\u251c\u2500\u2500 reactions.csv\n\u251c\u2500\u2500 routes\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 1.gpx\n\u251c\u2500\u2500 routes.csv\n\u251c\u2500\u2500 segments.csv\n\u251c\u2500\u2500 shoes.csv\n\u251c\u2500\u2500 social_settings.csv\n\u251c\u2500\u2500 starred_routes.csv\n\u251c\u2500\u2500 starred_segments.csv\n\u251c\u2500\u2500 support_tickets.csv\n\u2514\u2500\u2500 visibility_settings.csv\n
This directory contains a file activities.csv
with the metadata and also a directory activities
with the files that you have recorded.
"},{"location":"getting-started/moving-from-strava/#convert-your-checkout","title":"Convert your checkout","text":"Use the following command to create a directory from your Strava actitivies:
geo-activity-playground convert-strava-checkout ~/Downloads/export_123456/ ~/Documents/Outdoors/Playground\n
This should read through all the activities and create a directory structure with the pattern ~/Documents/Outdoors/Playground/Activities/{Kind}/{Equipment}/{Commute}/{Date} {Time} {Name}.{Suffix}
. For instance one file might be named Activities/Run/5212701.0/2019-07-09 09-59-25 Around the \u5317\u4eac\u5927\u5b66 campus.gpx.gz
.
The equipment might have nonsensical seeming names like 10370891.0
. The problem here is that Strava doesn't export the list of activities with that index. If your equipment doesn't have a nickname, it will just be such a number.
"},{"location":"getting-started/moving-from-strava/#use-the-directory","title":"Use the directory","text":"Now that the files from Strava are converted, consult the guide on using activity files to proceed from here.
"},{"location":"getting-started/moving-from-strava/#recording-more-activities","title":"Recording more activities","text":"Now that you don't record via Strava, you will need some other app to record your activities. There are Apps like OsmAnd or OpenTracks which provide such functionality. Export the files as GPX, FIT, TCX or KML files and put them into the directory structure. On the next start of the program, they will be picked up.
"},{"location":"getting-started/starting-the-webserver/","title":"Starting The Webserver","text":"Before you start here, you should have done these things:
- You have installed the program either from a stable version or from git.
- You have set up a playground with either activity files or the Strava API.
Now we can start the webserver which provides most of the features. This is done with the serve
command. So depending on how you have installed it, the commands could look like these:
geo-activity-playground serve
if you are in the playground directory and have installed a stable version. geo-activity-playground --basedir ~/Dokumente/Karten/Playground serve
if your playground directory is somewhere else and you have installed a stable version. poetry run geo-activity-playground --basedir ~/Dokumente/Karten/Playground serve
if you have it from the git checkout and want to use local files in your directory as a data source.
The webserver will start up and give you a bit of output like this:
2023-11-19 17:59:23 geo_activity_playground.importers.strava_api INFO Loading metadata file \u2026\n2023-11-19 17:59:23 stravalib.protocol.ApiV3 INFO GET 'https://www.strava.com/api/v3/athlete/activities' with params {'before': None, 'after': 1700392964, 'page': 1, 'per_page': 200}\n2023-11-19 17:59:23 geo_activity_playground.importers.strava_api INFO Checking for missing time series data \u2026\n * Serving Flask app 'geo_activity_playground.webui.app'\n * Debug mode: off\n2023-11-19 17:59:23 werkzeug INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.\n * Running on http://127.0.0.1:5000\n2023-11-19 17:59:23 werkzeug INFO Press CTRL+C to quit\n
The warning about the development server is fine. We are using this only to play around, not to power a web service for other users.
Open http://127.0.0.1:5000 to open the website in your browser. There might be some more messages about downloading and parsing data. The first startup will take quite some time. When it is done you will see something like this:
Click around and explore the various features.
"},{"location":"getting-started/starting-the-webserver/#setting-host-and-port","title":"Setting host and port","text":"In case you don't like the default value of 127.0.0.1:5000
, you can use the optional command line arguments --host
and --port
to specify your values.
"},{"location":"getting-started/using-activity-files/","title":"Using Activity Files","text":"Outdoor activities are usually recorded as .GPX
or .FIT
files. Some apps like OsmAnd , OpenTracks or Organic Maps, GPS handhelds, smartwatches or cycling computers give you these files.
"},{"location":"getting-started/using-activity-files/#supported-file-formats","title":"Supported file formats","text":" - FIT
- GPX
- TCX
- KML
- KMZ
- Simra CSV export
"},{"location":"getting-started/using-activity-files/#add-activity-files","title":"Add Activity Files","text":"Before starting the service you need to create a folder for your activities and put at least one activity file in there.
Create a Playground
folder on your storage somewhere and add a subfolder Activities
. There you can add your activity files. For example:
~/\n\u251c\u2500 Documents[or other location]/\n\u2502 \u251c\u2500 Playground/\n\u2502 \u2502 \u251c\u2500 Activities/\n\u2502 \u2502 \u2502 \u251c\u2500 2024-03-03-17-42-10 Home to Bakery.gpx\n
The program will treat the files as read-only and does not modify them.
Once the service is running you can use the Uploader to add your files. You can manually rename, move or delete your activity files, but the program needs to reload to respect these changes. You can restart the program or visit Scan New Activities
in the admin menu of the WebUI.
"},{"location":"getting-started/using-activity-files/#metadata-extraction","title":"Metadata extraction","text":"Most activity file formats contain basic data like date
, time
and track points
. Each activity in geo-activity-playground also has the metadata fields kind
, equipment
and name
. They can be extracted from files that contain them.
If no metadata is found, kind
and equipment
default to Unknown
. The name
is then extracted from the file name (without the suffix). So for Activities/2024-03-03-17-42-10 Home to Bakery.gpx
the name
is 2024-03-03-17-42-10 Home to Bakery
.
"},{"location":"getting-started/using-activity-files/#next-steps","title":"Next steps","text":"Once you have your files put into the directory, you're all set and can proceed with the next steps.
You can extend the directory structure to categorize your activities, see Advanced Metadata Extraction.
"},{"location":"getting-started/using-strava-api/","title":"Using Strava API","text":"You might have all your data on the Strava service and would like to use this for additional analytics without moving your data. That is fine.
If you don't mind a bit of rate-limiting, you can just directly go ahead and start the webserver. It will offer to connect with Strava.
"},{"location":"getting-started/using-strava-api/#your-own-strava-app","title":"Your own Strava App","text":"In order to use the Strava API without sharing the rate-limiting with other users, you need to create your own app. If my explanation doesn't suit you, have a look at this how-to guide as well.
Navigate to the API settings page and create an app. It only needs to have read permissions.
After you are done with that, you can see your App here:
There is a \"client ID\" and a \"client secret\" that we are going to need for the next step. In general our app could be used by all sorts of people who can then access their data only. We want to access our own data, but we still need to authorize our app to use our data.
Open the webserver of this program and go the Strava API setup page. Enter your client ID and client secret, click on \"Connect to Strava\".
This will prompt an OAuth2 request where you have to grant permissions to your app. After that you will be redirected back to the app and it should be set up. At the moment you need to restart the webserver such that it can start to download the activities. Due to rate-limiting it can still take a while.
"},{"location":"getting-started/using-strava-api/#use-export-to-avoid-rate-limiting","title":"Use export to avoid rate limiting","text":"When you first start this program and use the Strava API as a data source, it will download the metadata for all your activities. Then it will start to download all the time series data for each activity. Strava has a rate limiting, so after the first 200 activities it will crash and you will have to wait for 15 minutes until you can try again and it will download the next batch.
Therefore it is recommended to use a Strava export in order to get started quicker. For this go to the Strava account download page and download all your data. You will get a ZIP file. Unpack the files into Playground/Strava Export
. These will be picked up there. Activities from Strava will only be downloaded after importing all these, and only the ones after the last one in the export will be downloaded. This way you can get started much quicker.
"},{"location":"getting-started/using-strava-api/#skip-strava-download","title":"Skip Strava download","text":"If you don't want to download new activities from Strava, use --skip-strava
to have the webserver start right away.
"},{"location":"reference/changelog/","title":"Changelog","text":"This is the log of high-level changes that I have done in the various versions.
"},{"location":"reference/changelog/#version-0","title":"Version 0","text":"This is the pre-release series. Things haven't settled yet, so each minor version might introduce breaking changes.
"},{"location":"reference/changelog/#version-033","title":"Version 0.33","text":""},{"location":"reference/changelog/#version-0333","title":"Version 0.33.3","text":" - GH-200: Fix startup without any activities.
- Fix upload when there is no
Activities
directory.
"},{"location":"reference/changelog/#version-0332","title":"Version 0.33.2","text":" - GH-198: Fix explorer map. The problem was that VS Code auto-formatted the embedded JavaScript and created syntax errors.
"},{"location":"reference/changelog/#version-0331","title":"Version 0.33.1","text":" - GH-156: Fix little bug with
_meta
.
"},{"location":"reference/changelog/#version-0330","title":"Version 0.33.0","text":" - Make heatmap colormap configurable via web UI.
- GH-196: Make tile map URL configurable via configuration file.
- Make daily pulse plot per year in tabs.
"},{"location":"reference/changelog/#version-0320","title":"Version 0.32.0","text":" - GH-173: Add config option
ignore_suffixes
which can be set to something like [\".kml\"]
to ignore certain file types. - Include all activities in the summary, even those which are not to be considered for achievements.
- Remove debug print.
- Make share picture always the same size independent of the content.
"},{"location":"reference/changelog/#version-0310","title":"Version 0.31.0","text":" - GH-189: Fix heatmap tile cache expiry in cases where the activity kind has changed.
- Make date and time formats better to read.
- GH-156: Add metadata editing functionality with override files.
"},{"location":"reference/changelog/#version-0300","title":"Version 0.30.0","text":" - GH-187: Update favicon to new logo.
- GH-174: Add new search functionality that also serves as an overview over all activities.
- GH-168: Clicking on table headers will sort the tables now.
- GH-162: Make track segmentation configurable with a configuration setting.
- Visualize cadence on the activity page.
- GH-188: Remove
root=
prefix in activity kind when importing from Strava. - GH-188: Add option to rename activity kinds.
"},{"location":"reference/changelog/#version-029","title":"Version 0.29","text":""},{"location":"reference/changelog/#version-0292","title":"Version 0.29.2","text":" - Documentation improvements by beautiful-orca: GH-180, GH-181, GH-182, GH-183, GH-185
- GH-184: Use Python 3.12 in Docker.
- GH-178: Fix display of number of new tiles in activity view.
- GH-177: Fix distance from new
stravalib
version. - GH-179: Work around Pandas deprecation message.
- GH-176: Do not modify filename on upload any more.
- GH-175: Mention Organic Maps.
"},{"location":"reference/changelog/#version-0291","title":"Version 0.29.1","text":" - GH-167: Fix explorer tile export.
- GH-169: Fix import of KML files with waypoints.
"},{"location":"reference/changelog/#version-0290","title":"Version 0.29.0","text":" - Use dropdown menus to make navigation a bit smaller.
- GH-163: Recompute explorer tiles when there are deleted activities. Previously this would lead to
KeyError
when trying to use the heatmap or the explorer tile maps. - GH-161: Fix explorer tile clusters and square if one has activities that are not to be considered for achievements.
- GH-164: Create new function to handle write-and-replace on Windows.
- GH-155: Use the same scale for all plots with kind, make this configurable in the settings menu.
- Rewrite the documentation start page to make it more appealing and reflect the work in the web interface.
- GH-166: Add map with new explorer tiles to activity view.
- GH-160: Update version of
stravalib
and with that also pydantic
. That fixes a bug with recursive_guard
.
"},{"location":"reference/changelog/#version-028","title":"Version 0.28","text":" - Add settings menu to suppress fields from share pictures.
- Fix spelling mistake in navigation bar.
- Accelerate the tile visit computation.
- Ignore equipment offsets of equipments that don't exist.
- Reset corrupt heatmap cache files.
- GH-159: Improve password mechanism to protect both upload and settings.
- Document the use of Open Street Map uMap for missing explorer tiles on the go.
"},{"location":"reference/changelog/#version-027","title":"Version 0.27","text":""},{"location":"reference/changelog/#version-0271","title":"Version 0.27.1","text":" - Fix
num_processes
option.
"},{"location":"reference/changelog/#version-0270","title":"Version 0.27.0","text":" - GH-128: Let the Strava Checkout importer set the file
strava-last-activity-date.json
which is needed such that the Strava API importer can pick up after all the activities that have been imported via the checkout. - GH-143: Use custom CSV parser to read activities that have newlines in their descriptions.
- GH-146: Make multiprocessing optional with
num_processes = 1
in the configuration. - GH-147: Add another safeguard against activities that don't have latitude/longitude data.
- GH-149: Only pre-compute explorer maps for zoom 14 and 17 by default. Other ones just have to be enabled once. This saves a bit of computing time for most people that don't need to go down to zoom 19.
- GH-151: Do not fail if version cannot be determined.
- Add settings menu where one can configure various things:
- Equipment offsets
- Maximum heart rate for heart rate zones
- Metadata extractions from paths
- Privacy zones
- Strava connection
- The
config.json
replaces the config.toml
and will automatically be generated. - Fix bug in explorer tile interpolation that likely doesn't have an effect in practice.
"},{"location":"reference/changelog/#version-026","title":"Version 0.26","text":""},{"location":"reference/changelog/#version-0263","title":"Version 0.26.3","text":" - GH-142: Require
pandas >= 2.2.0
to make sure that it knows about include_groups
. - GH-144: Ignore activities without time series when using the Strava Checkout import.
"},{"location":"reference/changelog/#version-0262","title":"Version 0.26.2","text":" - Start with a test suite for the web server that also tests importing.
- Already fixed a few little bugs with that.
- GH-141: Fix summary page if there are no activities with steps.
"},{"location":"reference/changelog/#version-0261","title":"Version 0.26.1","text":" - GH-139, GH-140: More fixes for Strava archive importer.
"},{"location":"reference/changelog/#version-0260","title":"Version 0.26.0","text":" - Add automatic dark mode.
- Add some more explanation for the Strava connection.
- GH-138: Fix import from Strava archive that was broken in 0.25.0.
- Style the settings page a bit.
"},{"location":"reference/changelog/#version-025","title":"Version 0.25","text":""},{"location":"reference/changelog/#version-024","title":"Version 0.24","text":""},{"location":"reference/changelog/#version-0242","title":"Version 0.24.2","text":" - GH-127: Make calories and steps optional for the summary statistics.
"},{"location":"reference/changelog/#version-0241","title":"Version 0.24.1","text":" - GH-124: Add more timezone handling for Strava API.
- GH-125: Fix building of Docker container.
- GH-126: Fix heatmap download.
"},{"location":"reference/changelog/#version-0240","title":"Version 0.24.0","text":" - GH-43: Added nicer share pictures and privacy zones.
- GH-95: Display the number of new explorer tiles and squadratinhos per activity.
- GH-113: Open footer links in a new tab.
- GH-114: Show total distance and duration in day overview.
- GH-115: Add more summary statistics and add a \"hall of fame\" as well.
- GH-161: Show table for Eddington number, also update the plot to make it a bit easier to read. Add some more explanatory text.
- GH-118: Fix links in search results.
- GH-121: Fix link to share picture.
- GH-122: Convert everything to \"timezone naive\" dates in order to get rid of inconsistencies.
- GH-123: Fix startup from empty cache. A cache migration assumed that
activities.parquet
exists. I've added a check. - Use Flask Blueprints to organize code.
- Remove half-finished \"locations\" feature from the navigation.
- Allow filtering the heatmap by activity kinds.
- Remove duplicate link to landing page from navigation.
"},{"location":"reference/changelog/#version-023","title":"Version 0.23","text":" - GH-111: Add password protection for upload.
- Use Flask \u201cflash\u201d messages.
- GH-110: Support routes that don't have time information attached them. That might be useful if you haven't recorded some particular track but still want it to count towards your heatmap and explorer tiles.
"},{"location":"reference/changelog/#version-022","title":"Version 0.22","text":" - GH-111: Allow uploading files from within the web UI and parse them directly after uploading.
- Fix bug that lead to re-parsing of activity files during startup.
"},{"location":"reference/changelog/#version-021","title":"Version 0.21","text":""},{"location":"reference/changelog/#version-0212","title":"Version 0.21.2","text":" - Fix crash in search due to missing
distance/km
.
"},{"location":"reference/changelog/#version-0211","title":"Version 0.21.1","text":" - Add support for Python 3.12.
"},{"location":"reference/changelog/#version-0210","title":"Version 0.21.0","text":" -
Breaking change: New way to extract metadata from paths and filenames. This uses regular expressions and is more versatile than the heuristic before. If you have used prefer_metadata_from_file
before, see the documentation on activity files for the new way.
-
GH-105: Ignore similar activities that have vanished.
- GH-106: Be more strict when identifying jumps in activities. Take 30 s and 100 m distance as criterion now.
- GH-107: Remove warning by fixing a Pandas slice assignment.
- GH-108: Calories and steps are now extracted from FIT files.
-
GH-109: Better error message when trying to start up without any activity files.
-
Removed imagehash
from the dependencies.
- Single day overview is now linked from each activity.
- Parsing of activity files is now parallelized over all CPU cores and faster than before.
- The coloring of the speed along the activity line doesn't remove outliers any more.
"},{"location":"reference/changelog/#version-020","title":"Version 0.20","text":" - GH-88: Fix failure to import Strava distance stream due to
unsupported operand type(s) for /: 'list' and 'int'
. - GH-90: Take time jumps into account in activity distance computation and the various plots of the activities.
- GH-91: Import altitude information from GPX files if available.
- GH-92: Keep identity of activities based on hash of the file content, not the path. This allows to rename activities and just update their metadata, without having duplicates.
- GH-99: Skip Strava export activities that don't have a file.
- GH-98: Also accept boolean values in commute column of Strava's
activities.csv
. - GH-100: Protect fingerprint computation from bogus values
- GH-102: Make dependency on
vegafusion[embed]
explicit in the dependencies. - GH-103: Delete old pickle file before moving the new one onto it.
"},{"location":"reference/changelog/#version-019","title":"Version 0.19","text":""},{"location":"reference/changelog/#version-0191","title":"Version 0.19.1","text":" - Fix broken import of CSV files due to missing argument
opener
.
"},{"location":"reference/changelog/#version-0190","title":"Version 0.19.0","text":" - GH-88: Fix confusion about the internal data type for distance. Most of the time it was in meter, but the display was always in kilometer. In order to make it more clear now, the internal data now only contains the field
distance_km
and everything is represented as kilometer internally now. - Add more tooltip information in the plot on the landing page.
- GH-87: Add
prefer_metadata_from_file
configuration option. - GH-17: Download calories from Strava via the detailed API.
- Add option
--skip-strava
to the serve
command in order to start the webserver without reaching out to Strava first. This might be useful if the rate limit has been exceeded. - GH-89: Refactor some paths into a module such that there are not so many redundant definitions around.
- GH-86: Attempt to also read Strava exports that are localized to German, though untested.
- GH-36: Add a square planner.
"},{"location":"reference/changelog/#version-018","title":"Version 0.18","text":" - Fix internal server error 500 when there are not-a-number entries in the speed. GH-85
- Display activity source path in detail view.
- Ignore files which start with a period. This should also avoid Apple Quarantine files. GH-83
- Allow to have both Strava API and activity files.
- Use an existing Strava Export to load activities, retrieve only the remainder from the Strava API.
- In the calender, give the yearly total.
"},{"location":"reference/changelog/#version-017","title":"Version 0.17","text":""},{"location":"reference/changelog/#version-0175","title":"Version 0.17.5","text":" - Convert FIT sport type enum to strings. GH-84
"},{"location":"reference/changelog/#version-0174","title":"Version 0.17.4","text":" - Try to use charset-normalizer to figure out the strange encoding. GH-83
"},{"location":"reference/changelog/#version-0173","title":"Version 0.17.3","text":" - Fix error handler for GPX encoding issues. GH-83
"},{"location":"reference/changelog/#version-0172","title":"Version 0.17.2","text":" - Fix FIT import failure when the sub-sport is none. GH-84
"},{"location":"reference/changelog/#version-0171","title":"Version 0.17.1","text":" - Use locally downloaded tiles for all maps, this way we do not need to download them twice for activities and explorer/heatmap.
- Localize SimRa files to local time zone. GH-80
- Parse speed unit from FIT file. There are many devices which record in m/s and not in km/h, yielding too low speeds in the analysis. This is now fixed. GH-82
- Skip
.DS_Store
files in the activity directory. GH-81 - From FIT files we also extract the grade, temperature and GPS accuracy fields if they are present. There is no analysis for them yet, though. Also extract the workout name, sport and sub-sport fields from FIT files. GH-81
- Add more logging to diagnose Unicode issue on macOS. GH-83
"},{"location":"reference/changelog/#version-0170","title":"Version 0.17.0","text":" - Fix bug which broke the import of
.tcx.gz
files. - Add
Dockerfile
such that one can easily use this with Docker. GH-78 - Add support for the CSV files of the SimRa Project. GH-79
"},{"location":"reference/changelog/#version-016","title":"Version 0.16","text":""},{"location":"reference/changelog/#version-0164","title":"Version 0.16.4","text":""},{"location":"reference/changelog/#version-0163","title":"Version 0.16.3","text":" - Ignore Strava activities without a time series.
"},{"location":"reference/changelog/#version-0162","title":"Version 0.16.2","text":" - Make heatmap images that are downloaded look the same as the interactive one.
- Always emit the path when there is something wrong while parsing an activity file.
"},{"location":"reference/changelog/#version-0161","title":"Version 0.16.1","text":" - Fix handling of TCX files on Windows. On that platform one cannot open the same file twice, therefore my approach failed. Now I close the file properly such that this should work on Windows as well.
"},{"location":"reference/changelog/#version-0160","title":"Version 0.16.0","text":" - Add feature to render heatmap from visible area. GH-73
- Remove heatmap image generation from clusters, remove Scikit-Learn dependency.
- Add offsets for equipment. GH-71
- Fix number of tile visits in explorer view. GH-69
- Add action to convert Strava checkout to our format. GH-65
- Filter out some GPS jumps. GH-54
- Add simple search function. GH-70
"},{"location":"reference/changelog/#version-015","title":"Version 0.15","text":""},{"location":"reference/changelog/#version-0153","title":"Version 0.15.3","text":" - Create temporary file for TCX parsing in the same directory. There was a problem on Windows where the program didn't have access permissions to the temporary files directory.
"},{"location":"reference/changelog/#version-0152","title":"Version 0.15.2","text":" - Try to open GPX files in binary mode to avoid encoding issues. GH-74
"},{"location":"reference/changelog/#version-0151","title":"Version 0.15.1","text":" - Add
if __name__ == \"__main__\"
clause such that one can use python -m geo_activity_playground
on Windows.
"},{"location":"reference/changelog/#version-0150","title":"Version 0.15.0","text":" - Export all missing tiles in the viewport, not just the neighbors.
- Automatically retry Strava API when the rate limit is exhausted. GH-67
- Give more helpful error messages when the are no activity files present.
"},{"location":"reference/changelog/#version-014","title":"Version 0.14","text":""},{"location":"reference/changelog/#version-0142","title":"Version 0.14.2","text":" - Fix broken Strava import (bug introduced in 0.14.0).
"},{"location":"reference/changelog/#version-0141","title":"Version 0.14.1","text":" - Fix hard-coded part in KML import (bug introduced in 0.14.0).
"},{"location":"reference/changelog/#version-0140","title":"Version 0.14.0","text":" - Do more calculations eagerly at startup such that the webserver is more responsive. GH-58
- Allow setting host and port via the command line. GH-61
- Re-add download of explored tiles in area. GH-63
- Unify time handling, use UTC for all internal representations. GH-52
- Add some sort of KML support that at least works for KML exported by Viking. GH-62
"},{"location":"reference/changelog/#version-013","title":"Version 0.13","text":" - Revamp heatmap, use interpolated lines to provide a good experience even at high zoom levels.
- This also fixes the gaps that were present before. GH-34
- Add cache migration functionality.
- Make sure that cache directory is created beforehand. GH-55
- Split tracks into segments based on gaps of 30 seconds in the time data. That helps with interpolation across long distances when one has paused the recording. GH-47
- Fix introduced bug. GH-56
- Add cache to heatmap such that it doesn't need to render all activities and only add new activities as needed.
- Add a footer. GH-49
- Only export missing tiles in the active viewport. GH-53
- Add missing dependency to SciKit Learn again; I was too eager to remove that. GH-59
"},{"location":"reference/changelog/#version-012","title":"Version 0.12","text":" - Change coloring of clusters, have a color per cluster. Also mark the square just as an overlay.
- Fix bug with explorer tile page when the maximum cluster or square is just 1. GH-51
- Speed up the computation of the latest tiles.
"},{"location":"reference/changelog/#version-011","title":"Version 0.11","text":" - Add last activity in tile to the tooltip. GH-35
- Add explorer coloring mode by last activity. GH-45
- Actually implement
Activity/{Kind}/{Equipment}/{Name}.{Format}
directory structure. - Document configuration file.
- Interpolate tracks to find more explorer tiles. GH-27
- Fix bug that occurs when activities have no distance information.
- Show time evolution of the number of explorer tiles, the largest cluster and the square size. GH-33
- Center map view on biggest explorer cluster.
- Show speed distribution. GH-42
"},{"location":"reference/changelog/#version-010","title":"Version 0.10","text":" - Use a grayscale map for the explorer tile maps. GH-38
- Explicitly write \u201c0 km\u201d in calendar cells where there are no activities. GH-39, GH-40
"},{"location":"reference/changelog/#version-09","title":"Version 0.9","text":" - Certain exceptions are not skipped when parsing files. This way one can gather all errors at the end. GH-29
- Support TCX files. GH-8
- Fix equipment view when using the directory source. GH-25
- Fix links from the explorer tiles to the first activity that explored them. GH-30
- Fix how the API response from Strava is handled during the initial token exchange. GH-37
"},{"location":"reference/changelog/#version-08","title":"Version 0.8","text":""},{"location":"reference/changelog/#version-083","title":"Version 0.8.3","text":" - Only compute the explorer tile cluster size if there are cluster tiles. Otherwise the DBSCAN algorithm doesn't work anyway. GH-24
- Remove allocation of huge array. GH-23
"},{"location":"reference/changelog/#version-082","title":"Version 0.8.2","text":" - Some FIT files apparently have entries with explicit latitude/longitude values, but those are null. I've added a check which skips those points.
"},{"location":"reference/changelog/#version-081","title":"Version 0.8.1","text":" - Fix reading of FIT files from Wahoo hardware by reading them in binary mode. GH-20.
- Fix divide-by-zero error in speed calculation. GH-21
"},{"location":"reference/changelog/#version-080","title":"Version 0.8.0","text":" - Make heart rate zone computation a bit more flexibly by offering a lower bound for the resting heart rate.
- Open explorer map centered around median tile.
- Compute explorer cluster and square size, print that. GH-2
- Make it compatible with Python versions from 3.9 to 3.11 such that more people can use it. GH-22
"},{"location":"reference/changelog/#version-07","title":"Version 0.7","text":" - Add Squadratinhos, which are explorer tiles at zoom 17 instead of zoom 14.
- Reduce memory footprint for explorer tile computation.
"},{"location":"reference/changelog/#version-06","title":"Version 0.6","text":" - Interactive map for each activity.
- Color explorer tiles in red, green and blue. GH-2
- Directly serve GeoJSON and Vega JSON embedded in the document.
- Automatically detect which source is to be used. GH-16
- Fix the name of the script to be
geo-activity-playground
and not just geo-playground
. GH-11 - Add mini maps to the landing page. GH-9
- Add fullscreen button to the maps. GH-4
- Add favicon. GH-19
- Added some more clever caching to the explorer tiles such that loading the page with explorer tiles comes up in just a few seconds.
- Add a triplet of time series plots (distance, altitude, heart rate) for each activity.
- Show plot for heart rate zones per activity. GH-12
- Handle activities without any location points. GH-10
- Resolve Strava Gear name. GH-18
- Add page for equipment. GH-3
- Add a pop-up with some metadata about the first visit to the explorer tiles. GH-14
- Integrate missing explorer tiles into the web interface. GH-7.
- Color activity line with speed. GH-13
- Add interactive heatmap.
- Add margin to generated heatmaps. GH-1
"},{"location":"reference/changelog/#version-05","title":"Version 0.5","text":" - Add some plots for the Eddington number. GH-3
"},{"location":"reference/changelog/#version-04","title":"Version 0.4","text":""},{"location":"reference/changelog/#version-03","title":"Version 0.3","text":" - Start to build web interface with Flask.
- Remove tqdm progress bars and use colorful logging instead.
- Add interactive explorer tile map.
"},{"location":"reference/changelog/#version-02","title":"Version 0.2","text":" - Unity command line entrypoint.
- Crop heatmaps to fit.
- Export missing tiles as GeoJSON.
- Add Strava API.
- Add directory source.
"},{"location":"reference/changelog/#version-01","title":"Version 0.1","text":""},{"location":"reference/changelog/#version-013_1","title":"Version 0.1.3","text":" - Generate some heatmap images.
- Generate an explorer tile video.
"}]}
\ No newline at end of file
diff --git a/sitemap.xml b/sitemap.xml
index 2b36fa55..ca30309e 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -2,94 +2,98 @@
https://martin-ueding.github.io/geo-activity-playground/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/acknowledgments/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/activity-view/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/calendar/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/eddington/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/equipment/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/explorer-tiles/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/heatmaps/
- 2024-11-17
+ 2024-11-24
+
+
+ https://martin-ueding.github.io/geo-activity-playground/features/kind-rename/
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/overview/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/share-picture/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/features/upload/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/advanced-metadata-extraction/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/docker-compose-tailscale/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/docker-compose/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/docker/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/installing-git-on-linux/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/installing-stable-on-linux/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/installing-stable-on-windows/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/moving-from-strava/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/starting-the-webserver/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/using-activity-files/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/getting-started/using-strava-api/
- 2024-11-17
+ 2024-11-24
https://martin-ueding.github.io/geo-activity-playground/reference/changelog/
- 2024-11-17
+ 2024-11-24
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index e8041931..4d413527 100644
Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ