geo-activity-playground 0.29.0__tar.gz → 0.34.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/PKG-INFO +5 -6
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/__main__.py +1 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/activities.py +18 -9
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/config.py +7 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/enrichment.py +18 -7
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/heatmap.py +61 -15
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/paths.py +3 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/tiles.py +8 -5
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/time_conversion.py +1 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/tile_visits.py +2 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/video.py +2 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/activity_parsers.py +28 -17
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/csv_parser.py +1 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/directory.py +8 -3
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/strava_api.py +2 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/strava_checkout.py +8 -2
- geo_activity_playground-0.34.1/geo_activity_playground/webui/activity/blueprint.py +116 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/controller.py +146 -44
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +10 -6
- geo_activity_playground-0.34.1/geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +42 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +1 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/name.html.j2 +7 -6
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/show.html.j2 +23 -9
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/app.py +29 -6
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/entry_controller.py +1 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/controller.py +1 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/templates/equipment/index.html.j2 +1 -1
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/controller.py +2 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +20 -44
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/blueprint.py +5 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/heatmap_controller.py +22 -10
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +1 -1
- geo_activity_playground-0.34.1/geo_activity_playground/webui/search/blueprint.py +101 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/search/templates/search/index.html.j2 +91 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/blueprint.py +90 -33
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/controller.py +4 -3
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +11 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/index.html.j2 +18 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +25 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2 +10 -11
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +1 -1
- geo_activity_playground-0.34.1/geo_activity_playground/webui/settings/templates/settings/segmentation.html.j2 +27 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/controller.py +2 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2 +1 -1
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +1 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/MarkerCluster.Default.css +60 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/MarkerCluster.css +14 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/bootstrap.min.css +6 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/favicon.ico +0 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/favicon.svg +3 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/fullscreen.png +0 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.css +661 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.fullscreen.css +40 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.js +6 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.markercluster.js +3 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/site.webmanifest +21 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/table-sort.min.js +8 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/vega-embed@6 +7 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/vega-lite@4 +2 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/vega@5 +2 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
- geo_activity_playground-0.34.1/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/controller.py +20 -25
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/templates/summary/index.html.j2 +20 -7
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/templates/home.html.j2 +7 -13
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/templates/page.html.j2 +16 -18
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/tile/blueprint.py +3 -2
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/tile/controller.py +7 -3
- geo_activity_playground-0.29.0/geo_activity_playground/webui/upload/controller.py → geo_activity_playground-0.34.1/geo_activity_playground/webui/upload_blueprint.py +42 -36
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/pyproject.toml +8 -8
- geo_activity_playground-0.29.0/geo_activity_playground/webui/activity/blueprint.py +0 -57
- geo_activity_playground-0.29.0/geo_activity_playground/webui/search/blueprint.py +0 -20
- geo_activity_playground-0.29.0/geo_activity_playground/webui/search/templates/search/index.html.j2 +0 -38
- geo_activity_playground-0.29.0/geo_activity_playground/webui/static/android-chrome-384x384.png +0 -0
- geo_activity_playground-0.29.0/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- geo_activity_playground-0.29.0/geo_activity_playground/webui/static/favicon.ico +0 -0
- geo_activity_playground-0.29.0/geo_activity_playground/webui/static/safari-pinned-tab.svg +0 -121
- geo_activity_playground-0.29.0/geo_activity_playground/webui/static/site.webmanifest +0 -19
- geo_activity_playground-0.29.0/geo_activity_playground/webui/upload/__init__.py +0 -0
- geo_activity_playground-0.29.0/geo_activity_playground/webui/upload/blueprint.py +0 -44
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/LICENSE +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/coordinates.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/heart_rate.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/privacy_zones.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/similarity.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/tasks.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/test_tiles.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/test_time_conversion.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/grid_file.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/test_csv_parser.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/test_directory.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/test_strava_api.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/auth/blueprint.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/auth/templates/auth/index.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/authenticator.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/blueprint.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/controller.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/blueprint.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/controller.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/templates/eddington/index.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/blueprint.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/blueprint.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/plot_util.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/strava.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/blueprint.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/__init__.py +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/blueprint.py +0 -0
- {geo_activity_playground-0.29.0/geo_activity_playground/webui/upload → geo_activity_playground-0.34.1/geo_activity_playground/webui}/templates/upload/index.html.j2 +0 -0
- {geo_activity_playground-0.29.0/geo_activity_playground/webui/upload → geo_activity_playground-0.34.1/geo_activity_playground/webui}/templates/upload/reload.html.j2 +0 -0
- {geo_activity_playground-0.29.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/tile/__init__.py +0 -0
@@ -1,17 +1,17 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: geo-activity-playground
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.34.1
|
4
4
|
Summary: Analysis of geo data activities like rides, runs or hikes.
|
5
5
|
License: MIT
|
6
6
|
Author: Martin Ueding
|
7
7
|
Author-email: mu@martin-ueding.de
|
8
|
-
Requires-Python: >=3.10,<3.
|
8
|
+
Requires-Python: >=3.10,<3.14
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
14
|
-
Requires-Dist: Pillow (>=
|
14
|
+
Requires-Dist: Pillow (>=11.0.0,<12.0.0)
|
15
15
|
Requires-Dist: altair (>=5.1.2,<6.0.0)
|
16
16
|
Requires-Dist: appdirs (>=1.4.4,<2.0.0)
|
17
17
|
Requires-Dist: charset-normalizer (>=3.3.2,<4.0.0)
|
@@ -22,12 +22,11 @@ Requires-Dist: geojson (>=3.0.1,<4.0.0)
|
|
22
22
|
Requires-Dist: gpxpy (>=1.5.0,<2.0.0)
|
23
23
|
Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
|
24
24
|
Requires-Dist: matplotlib (>=3.6.3,<4.0.0)
|
25
|
-
Requires-Dist: numpy (>=1.
|
25
|
+
Requires-Dist: numpy (>=2.1.3,<3.0.0)
|
26
26
|
Requires-Dist: pandas (>=2.2,<3.0)
|
27
|
-
Requires-Dist: pyarrow (>=
|
27
|
+
Requires-Dist: pyarrow (>=18.1.0,<19.0.0)
|
28
28
|
Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
|
29
29
|
Requires-Dist: requests (>=2.28.1,<3.0.0)
|
30
|
-
Requires-Dist: scipy (>=1.8.1,<2.0.0)
|
31
30
|
Requires-Dist: shapely (>=2.0.5,<3.0.0)
|
32
31
|
Requires-Dist: stravalib (>=2.0,<3.0)
|
33
32
|
Requires-Dist: tcxreader (>=0.4.5,<0.5.0)
|
@@ -13,7 +13,7 @@ from geo_activity_playground.core.config import import_old_strava_config
|
|
13
13
|
from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
|
14
14
|
from geo_activity_playground.explorer.video import explorer_video_main
|
15
15
|
from geo_activity_playground.webui.app import web_ui_main
|
16
|
-
from geo_activity_playground.webui.
|
16
|
+
from geo_activity_playground.webui.upload_blueprint import scan_for_activities
|
17
17
|
|
18
18
|
logger = logging.getLogger(__name__)
|
19
19
|
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import datetime
|
2
2
|
import functools
|
3
|
+
import json
|
3
4
|
import logging
|
4
5
|
import pickle
|
6
|
+
from typing import Any
|
5
7
|
from typing import Iterator
|
6
8
|
from typing import Optional
|
7
9
|
from typing import TypedDict
|
@@ -15,6 +17,7 @@ from tqdm import tqdm
|
|
15
17
|
from geo_activity_playground.core.paths import activities_file
|
16
18
|
from geo_activity_playground.core.paths import activity_enriched_meta_dir
|
17
19
|
from geo_activity_playground.core.paths import activity_enriched_time_series_dir
|
20
|
+
from geo_activity_playground.core.paths import activity_meta_override_dir
|
18
21
|
|
19
22
|
logger = logging.getLogger(__name__)
|
20
23
|
|
@@ -82,13 +85,21 @@ def build_activity_meta() -> None:
|
|
82
85
|
rows = []
|
83
86
|
for new_id in tqdm(new_ids, desc="Register new activities"):
|
84
87
|
with open(activity_enriched_meta_dir() / f"{new_id}.pickle", "rb") as f:
|
85
|
-
|
88
|
+
data = pickle.load(f)
|
89
|
+
override_file = activity_meta_override_dir() / f"{new_id}.json"
|
90
|
+
if override_file.exists():
|
91
|
+
with open(override_file) as f:
|
92
|
+
data.update(json.load(f))
|
93
|
+
rows.append(data)
|
86
94
|
|
87
95
|
if rows:
|
88
96
|
new_shard = pd.DataFrame(rows)
|
89
97
|
new_shard.index = new_shard["id"]
|
90
98
|
new_shard.index.name = "index"
|
91
|
-
|
99
|
+
if len(meta):
|
100
|
+
meta = pd.concat([meta, new_shard])
|
101
|
+
else:
|
102
|
+
meta = new_shard
|
92
103
|
|
93
104
|
if len(meta):
|
94
105
|
assert pd.api.types.is_dtype_equal(meta["start"].dtype, "datetime64[ns]"), (
|
@@ -103,7 +114,7 @@ def build_activity_meta() -> None:
|
|
103
114
|
|
104
115
|
class ActivityRepository:
|
105
116
|
def __init__(self) -> None:
|
106
|
-
self.meta =
|
117
|
+
self.meta = pd.DataFrame()
|
107
118
|
|
108
119
|
def __len__(self) -> int:
|
109
120
|
return len(self.meta)
|
@@ -116,10 +127,6 @@ class ActivityRepository:
|
|
116
127
|
if activity_id in self.meta["id"]:
|
117
128
|
return True
|
118
129
|
|
119
|
-
for activity_meta in self._loose_activities:
|
120
|
-
if activity_meta["id"] == activity_id:
|
121
|
-
return True
|
122
|
-
|
123
130
|
return False
|
124
131
|
|
125
132
|
def last_activity_date(self) -> Optional[datetime.datetime]:
|
@@ -140,7 +147,6 @@ class ActivityRepository:
|
|
140
147
|
if not dropna or not pd.isna(row["start"]):
|
141
148
|
yield row
|
142
149
|
|
143
|
-
@functools.lru_cache()
|
144
150
|
def get_activity_by_id(self, id: int) -> ActivityMeta:
|
145
151
|
activity = self.meta.loc[id]
|
146
152
|
assert isinstance(activity["name"], str), activity["name"]
|
@@ -158,6 +164,9 @@ class ActivityRepository:
|
|
158
164
|
|
159
165
|
return df
|
160
166
|
|
167
|
+
def save(self) -> None:
|
168
|
+
self.meta.to_parquet(activities_file())
|
169
|
+
|
161
170
|
|
162
171
|
def make_geojson_from_time_series(time_series: pd.DataFrame) -> str:
|
163
172
|
fc = geojson.FeatureCollection(
|
@@ -198,7 +207,7 @@ def make_geojson_color_line(time_series: pd.DataFrame) -> str:
|
|
198
207
|
return geojson.dumps(feature_collection)
|
199
208
|
|
200
209
|
|
201
|
-
def make_speed_color_bar(time_series: pd.DataFrame) -> dict[str,
|
210
|
+
def make_speed_color_bar(time_series: pd.DataFrame) -> dict[str, Any]:
|
202
211
|
speed_without_na = time_series["speed"].dropna()
|
203
212
|
low = min(speed_without_na)
|
204
213
|
high = max(speed_without_na)
|
@@ -21,14 +21,17 @@ logger = logging.getLogger(__name__)
|
|
21
21
|
@dataclasses.dataclass
|
22
22
|
class Config:
|
23
23
|
birth_year: Optional[int] = None
|
24
|
-
color_scheme_for_counts: str = "
|
24
|
+
color_scheme_for_counts: str = "teals"
|
25
25
|
color_scheme_for_kind: str = "category10"
|
26
|
+
color_scheme_for_heatmap: str = "hot"
|
26
27
|
equipment_offsets: dict[str, float] = dataclasses.field(default_factory=dict)
|
27
28
|
explorer_zoom_levels: list[int] = dataclasses.field(
|
28
29
|
default_factory=lambda: [14, 17]
|
29
30
|
)
|
30
31
|
heart_rate_resting: int = 0
|
31
32
|
heart_rate_maximum: Optional[int] = None
|
33
|
+
ignore_suffixes: list[str] = dataclasses.field(default_factory=list)
|
34
|
+
kind_renames: dict[str, str] = dataclasses.field(default_factory=dict)
|
32
35
|
kinds_without_achievements: list[str] = dataclasses.field(default_factory=list)
|
33
36
|
metadata_extraction_regexes: list[str] = dataclasses.field(default_factory=list)
|
34
37
|
num_processes: Optional[int] = 1
|
@@ -39,7 +42,10 @@ class Config:
|
|
39
42
|
strava_client_id: int = 131693
|
40
43
|
strava_client_secret: str = "0ccc0100a2c218512a7ef0cea3b0e322fb4b4365"
|
41
44
|
strava_client_code: Optional[str] = None
|
45
|
+
time_diff_threshold_seconds: Optional[int] = 30
|
42
46
|
upload_password: Optional[str] = None
|
47
|
+
map_tile_url: str = "https://tile.openstreetmap.org/{zoom}/{x}/{y}.png"
|
48
|
+
map_tile_attribution: str = '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> | <a href="https://www.openstreetmap.org/fixthemap">Correct Map</a>'
|
43
49
|
|
44
50
|
|
45
51
|
class ConfigAccessor:
|
@@ -82,11 +82,15 @@ def enrich_activities(config: Config) -> None:
|
|
82
82
|
)
|
83
83
|
continue
|
84
84
|
|
85
|
+
# Rename kinds if needed.
|
86
|
+
if metadata["kind"] in config.kind_renames:
|
87
|
+
metadata["kind"] = config.kind_renames[metadata["kind"]]
|
88
|
+
|
85
89
|
# Enrich time series.
|
86
90
|
if metadata["kind"] in config.kinds_without_achievements:
|
87
91
|
metadata["consider_for_achievements"] = False
|
88
92
|
time_series = _embellish_single_time_series(
|
89
|
-
time_series, metadata.get("start", None)
|
93
|
+
time_series, metadata.get("start", None), config.time_diff_threshold_seconds
|
90
94
|
)
|
91
95
|
metadata.update(_get_metadata_from_timeseries(time_series))
|
92
96
|
|
@@ -131,7 +135,9 @@ def _compute_moving_time(time_series: pd.DataFrame) -> datetime.timedelta:
|
|
131
135
|
|
132
136
|
|
133
137
|
def _embellish_single_time_series(
|
134
|
-
timeseries: pd.DataFrame,
|
138
|
+
timeseries: pd.DataFrame,
|
139
|
+
start: Optional[datetime.datetime],
|
140
|
+
time_diff_threshold_seconds: int,
|
135
141
|
) -> pd.DataFrame:
|
136
142
|
if start is not None and pd.api.types.is_dtype_equal(
|
137
143
|
timeseries["time"].dtype, "int64"
|
@@ -153,10 +159,12 @@ def _embellish_single_time_series(
|
|
153
159
|
timeseries["latitude"],
|
154
160
|
timeseries["longitude"],
|
155
161
|
).fillna(0.0)
|
156
|
-
time_diff_threshold_seconds
|
157
|
-
|
158
|
-
|
159
|
-
|
162
|
+
if time_diff_threshold_seconds:
|
163
|
+
time_diff = (
|
164
|
+
timeseries["time"] - timeseries["time"].shift(1)
|
165
|
+
).dt.total_seconds()
|
166
|
+
jump_indices = time_diff >= time_diff_threshold_seconds
|
167
|
+
distances.loc[jump_indices] = 0.0
|
160
168
|
|
161
169
|
if "distance_km" not in timeseries.columns:
|
162
170
|
timeseries["distance_km"] = pd.Series(np.cumsum(distances)) / 1000
|
@@ -173,7 +181,10 @@ def _embellish_single_time_series(
|
|
173
181
|
timeseries = timeseries.loc[~potential_jumps].copy()
|
174
182
|
|
175
183
|
if "segment_id" not in timeseries.columns:
|
176
|
-
|
184
|
+
if time_diff_threshold_seconds:
|
185
|
+
timeseries["segment_id"] = np.cumsum(jump_indices)
|
186
|
+
else:
|
187
|
+
timeseries["segment_id"] = 0
|
177
188
|
|
178
189
|
if "x" not in timeseries.columns:
|
179
190
|
x, y = compute_tile_float(timeseries["latitude"], timeseries["longitude"], 0)
|
@@ -6,6 +6,7 @@ import logging
|
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
|
+
from geo_activity_playground.core.config import Config
|
9
10
|
from geo_activity_playground.core.tiles import compute_tile_float
|
10
11
|
from geo_activity_playground.core.tiles import get_tile
|
11
12
|
from geo_activity_playground.core.tiles import get_tile_upper_left_lat_lon
|
@@ -123,21 +124,66 @@ def get_sensible_zoom_level(
|
|
123
124
|
)
|
124
125
|
|
125
126
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
127
|
+
def build_map_from_tiles_around_center(
|
128
|
+
center: tuple[float, float],
|
129
|
+
zoom: int,
|
130
|
+
target: tuple[int, int],
|
131
|
+
inner_target: tuple[int, int],
|
132
|
+
config: Config,
|
133
|
+
) -> np.ndarray:
|
134
|
+
background = np.zeros((target[1], target[0], 3))
|
135
|
+
|
136
|
+
# We will work with the center point and have it in terms of tiles `t` and also in terms of pixels `p`. At the start we know that the tile center must be in the middle of the image.
|
137
|
+
t = np.array(center)
|
138
|
+
p = np.array([inner_target[0] / 2, inner_target[1] / 2])
|
139
|
+
|
140
|
+
# Shift both such that they are in the top-left corner of an even tile.
|
141
|
+
t_offset = np.array([center[0] % 1, center[1] % 1])
|
142
|
+
t -= t_offset
|
143
|
+
p -= t_offset * OSM_TILE_SIZE
|
144
|
+
|
145
|
+
# Shift until we have left the image.
|
146
|
+
shift = np.ceil(p / OSM_TILE_SIZE)
|
147
|
+
p -= shift * OSM_TILE_SIZE
|
148
|
+
t -= shift
|
149
|
+
|
150
|
+
num_tiles = np.ceil(np.array(target) / OSM_TILE_SIZE) + 1
|
151
|
+
|
152
|
+
for x in range(int(t[0]), int(t[0] + num_tiles[0])):
|
153
|
+
for y in range(int(t[1]), int(t[1]) + int(num_tiles[1])):
|
154
|
+
source_x_min = 0
|
155
|
+
source_y_min = 0
|
156
|
+
source_x_max = source_x_min + OSM_TILE_SIZE
|
157
|
+
source_y_max = source_y_min + OSM_TILE_SIZE
|
158
|
+
|
159
|
+
target_x_min = (x - int(t[0])) * OSM_TILE_SIZE + int(p[0])
|
160
|
+
target_y_min = (y - int(t[1])) * OSM_TILE_SIZE + int(p[1])
|
161
|
+
target_x_max = target_x_min + OSM_TILE_SIZE
|
162
|
+
target_y_max = target_y_min + OSM_TILE_SIZE
|
163
|
+
|
164
|
+
if target_x_min < 0:
|
165
|
+
source_x_min -= target_x_min
|
166
|
+
target_x_min = 0
|
167
|
+
if target_y_min < 0:
|
168
|
+
source_y_min -= target_y_min
|
169
|
+
target_y_min = 0
|
170
|
+
if target_x_max > target[0]:
|
171
|
+
a = target_x_max - target[0]
|
172
|
+
target_x_max -= a
|
173
|
+
source_x_max -= a
|
174
|
+
if target_y_max > target[1]:
|
175
|
+
a = target_y_max - target[1]
|
176
|
+
target_y_max -= a
|
177
|
+
source_y_max -= a
|
178
|
+
|
179
|
+
if source_x_max < 0 or source_y_max < 0:
|
180
|
+
continue
|
181
|
+
|
182
|
+
tile = np.array(get_tile(zoom, x, y, config.map_tile_url)) / 255
|
183
|
+
|
184
|
+
background[target_y_min:target_y_max, target_x_min:target_x_max] = tile[
|
185
|
+
source_y_min:source_y_max, source_x_min:source_x_max, :3
|
186
|
+
]
|
141
187
|
|
142
188
|
return background
|
143
189
|
|
@@ -8,7 +8,6 @@ import typing
|
|
8
8
|
|
9
9
|
|
10
10
|
def dir_wrapper(path: pathlib.Path) -> typing.Callable[[], pathlib.Path]:
|
11
|
-
@functools.cache
|
12
11
|
def wrapper() -> pathlib.Path:
|
13
12
|
path.mkdir(exist_ok=True, parents=True)
|
14
13
|
return path
|
@@ -17,7 +16,6 @@ def dir_wrapper(path: pathlib.Path) -> typing.Callable[[], pathlib.Path]:
|
|
17
16
|
|
18
17
|
|
19
18
|
def file_wrapper(path: pathlib.Path) -> typing.Callable[[], pathlib.Path]:
|
20
|
-
@functools.cache
|
21
19
|
def wrapper() -> pathlib.Path:
|
22
20
|
path.parent.mkdir(exist_ok=True, parents=True)
|
23
21
|
return path
|
@@ -55,6 +53,8 @@ _strava_last_activity_date_path = _cache_dir / "strava-last-activity-date.json"
|
|
55
53
|
|
56
54
|
_new_config_file = pathlib.Path("config.json")
|
57
55
|
|
56
|
+
_activity_meta_override_dir = pathlib.Path("Metadata Override")
|
57
|
+
|
58
58
|
|
59
59
|
cache_dir = dir_wrapper(_cache_dir)
|
60
60
|
|
@@ -65,6 +65,7 @@ activity_enriched_meta_dir = dir_wrapper(_activity_enriched_meta_dir)
|
|
65
65
|
activity_enriched_time_series_dir = dir_wrapper(_activity_enriched_time_series_dir)
|
66
66
|
tiles_per_time_series = dir_wrapper(_tiles_per_time_series)
|
67
67
|
strava_api_dir = dir_wrapper(_strava_api_dir)
|
68
|
+
activity_meta_override_dir = dir_wrapper(_activity_meta_override_dir)
|
68
69
|
|
69
70
|
activities_file = file_wrapper(_activities_file)
|
70
71
|
strava_dynamic_config_path = file_wrapper(_strava_dynamic_config_path)
|
@@ -3,6 +3,7 @@ import logging
|
|
3
3
|
import math
|
4
4
|
import pathlib
|
5
5
|
import time
|
6
|
+
import urllib.parse
|
6
7
|
from typing import Iterator
|
7
8
|
from typing import Optional
|
8
9
|
|
@@ -13,8 +14,10 @@ from PIL import Image
|
|
13
14
|
logger = logging.getLogger(__name__)
|
14
15
|
|
15
16
|
|
16
|
-
def osm_tile_path(x: int, y: int, zoom: int) -> pathlib.Path:
|
17
|
-
|
17
|
+
def osm_tile_path(x: int, y: int, zoom: int, url_template: str) -> pathlib.Path:
|
18
|
+
base_dir = pathlib.Path("Open Street Map Tiles")
|
19
|
+
dir_for_source = base_dir / urllib.parse.quote_plus(url_template)
|
20
|
+
path = dir_for_source / f"{zoom}/{x}/{y}.png"
|
18
21
|
path.parent.mkdir(parents=True, exist_ok=True)
|
19
22
|
return path
|
20
23
|
|
@@ -62,11 +65,11 @@ def download_file(url: str, destination: pathlib.Path):
|
|
62
65
|
|
63
66
|
|
64
67
|
@functools.lru_cache()
|
65
|
-
def get_tile(zoom: int, x: int, y: int) -> Image.Image:
|
66
|
-
destination = osm_tile_path(x, y, zoom)
|
68
|
+
def get_tile(zoom: int, x: int, y: int, url_template: str) -> Image.Image:
|
69
|
+
destination = osm_tile_path(x, y, zoom, url_template)
|
67
70
|
if not destination.exists():
|
68
71
|
logger.info(f"Downloading OSM tile {x=}, {y=}, {zoom=} …")
|
69
|
-
url =
|
72
|
+
url = url_template.format(x=x, y=y, zoom=zoom)
|
70
73
|
download_file(url, destination)
|
71
74
|
with Image.open(destination) as image:
|
72
75
|
image.load()
|
@@ -59,7 +59,7 @@ class TileEvolutionState:
|
|
59
59
|
class TileState(TypedDict):
|
60
60
|
tile_visits: dict[int, dict[tuple[int, int], TileInfo]]
|
61
61
|
tile_history: dict[int, pd.DataFrame]
|
62
|
-
activities_per_tile: dict[int, set[int]]
|
62
|
+
activities_per_tile: dict[int, dict[tuple[int, int], set[int]]]
|
63
63
|
processed_activities: set[int]
|
64
64
|
evolution_state: dict[int, TileEvolutionState]
|
65
65
|
version: int
|
@@ -165,7 +165,7 @@ def _process_activity(
|
|
165
165
|
for zoom in reversed(range(20)):
|
166
166
|
activities_per_tile = tile_state["activities_per_tile"][zoom]
|
167
167
|
|
168
|
-
new_tile_history_soa = {
|
168
|
+
new_tile_history_soa: dict[str, list] = {
|
169
169
|
"activity_id": [],
|
170
170
|
"time": [],
|
171
171
|
"tile_x": [],
|
@@ -9,13 +9,14 @@ from typing import Tuple
|
|
9
9
|
|
10
10
|
import numpy as np
|
11
11
|
import pandas as pd
|
12
|
-
import scipy.interpolate
|
13
12
|
from PIL import Image
|
14
13
|
from PIL import ImageEnhance
|
15
14
|
from tqdm import tqdm
|
16
15
|
|
17
16
|
from ..core.tiles import get_tile
|
18
17
|
|
18
|
+
# import scipy.interpolate
|
19
|
+
|
19
20
|
|
20
21
|
def build_image(
|
21
22
|
center_x: float,
|
@@ -3,10 +3,10 @@ import gzip
|
|
3
3
|
import logging
|
4
4
|
import pathlib
|
5
5
|
import xml
|
6
|
+
from collections.abc import Iterator
|
6
7
|
|
7
8
|
import charset_normalizer
|
8
9
|
import dateutil.parser
|
9
|
-
import fitdecode
|
10
10
|
import fitdecode.exceptions
|
11
11
|
import gpxpy
|
12
12
|
import pandas as pd
|
@@ -246,26 +246,37 @@ def read_kml_activity(path: pathlib.Path, opener) -> pd.DataFrame:
|
|
246
246
|
with opener(path, "rb") as f:
|
247
247
|
kml_dict = xmltodict.parse(f)
|
248
248
|
doc = kml_dict["kml"]["Document"]
|
249
|
-
keypoint_folder = doc["Folder"]
|
250
|
-
placemark = keypoint_folder["Placemark"]
|
251
|
-
track = placemark["gx:Track"]
|
252
249
|
rows = []
|
253
|
-
for
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
250
|
+
for keypoint_folder in _list_or_scalar(doc["Folder"]):
|
251
|
+
for placemark in _list_or_scalar(keypoint_folder["Placemark"]):
|
252
|
+
for track in _list_or_scalar(placemark.get("gx:Track", [])):
|
253
|
+
for when, where in zip(track["when"], track["gx:coord"]):
|
254
|
+
time = dateutil.parser.parse(when)
|
255
|
+
time = convert_to_datetime_ns(time)
|
256
|
+
parts = where.split(" ")
|
257
|
+
if len(parts) == 2:
|
258
|
+
lon, lat = parts
|
259
|
+
alt = None
|
260
|
+
if len(parts) == 3:
|
261
|
+
lon, lat, alt = parts
|
262
|
+
row = {
|
263
|
+
"time": time,
|
264
|
+
"latitude": float(lat),
|
265
|
+
"longitude": float(lon),
|
266
|
+
}
|
267
|
+
if alt is not None:
|
268
|
+
row["altitude"] = float(alt)
|
269
|
+
rows.append(row)
|
266
270
|
return pd.DataFrame(rows)
|
267
271
|
|
268
272
|
|
273
|
+
def _list_or_scalar(thing) -> Iterator:
|
274
|
+
if isinstance(thing, list):
|
275
|
+
yield from thing
|
276
|
+
else:
|
277
|
+
yield thing
|
278
|
+
|
279
|
+
|
269
280
|
def read_simra_activity(path: pathlib.Path, opener) -> pd.DataFrame:
|
270
281
|
data = pd.read_csv(path, header=1)
|
271
282
|
data["time"] = data["timeStamp"].apply(
|
@@ -20,9 +20,8 @@ This module implements a "recursive descent parser" that parses this grammar.
|
|
20
20
|
|
21
21
|
def parse_csv(text: str) -> list[list]:
|
22
22
|
text = text.strip() + "\n"
|
23
|
-
result = {}
|
24
23
|
index = 0
|
25
|
-
result = []
|
24
|
+
result: list[list] = []
|
26
25
|
while index < len(text):
|
27
26
|
line, index = _parse_line(text, index)
|
28
27
|
result.append(line)
|
@@ -10,6 +10,7 @@ from typing import Optional
|
|
10
10
|
from tqdm import tqdm
|
11
11
|
|
12
12
|
from geo_activity_playground.core.activities import ActivityMeta
|
13
|
+
from geo_activity_playground.core.config import Config
|
13
14
|
from geo_activity_playground.core.paths import activity_extracted_dir
|
14
15
|
from geo_activity_playground.core.paths import activity_extracted_meta_dir
|
15
16
|
from geo_activity_playground.core.paths import activity_extracted_time_series_dir
|
@@ -24,13 +25,16 @@ ACTIVITY_DIR = pathlib.Path("Activities")
|
|
24
25
|
|
25
26
|
|
26
27
|
def import_from_directory(
|
27
|
-
metadata_extraction_regexes: list[str], num_processes: Optional[int]
|
28
|
+
metadata_extraction_regexes: list[str], num_processes: Optional[int], config: Config
|
28
29
|
) -> None:
|
29
30
|
|
30
31
|
activity_paths = [
|
31
32
|
path
|
32
33
|
for path in ACTIVITY_DIR.rglob("*.*")
|
33
|
-
if path.is_file()
|
34
|
+
if path.is_file()
|
35
|
+
and path.suffixes
|
36
|
+
and not path.stem.startswith(".")
|
37
|
+
and not path.suffix in config.ignore_suffixes
|
34
38
|
]
|
35
39
|
work_tracker = WorkTracker(activity_extracted_dir() / "work-tracker-extract.pickle")
|
36
40
|
new_activity_paths = work_tracker.filter(activity_paths)
|
@@ -126,11 +130,12 @@ def _cache_single_file(path: pathlib.Path) -> Optional[tuple[pathlib.Path, str]]
|
|
126
130
|
raise
|
127
131
|
|
128
132
|
if len(timeseries) == 0:
|
129
|
-
return
|
133
|
+
return None
|
130
134
|
|
131
135
|
timeseries.to_parquet(timeseries_path)
|
132
136
|
with open(file_metadata_path, "wb") as f:
|
133
137
|
pickle.dump(activity_meta_from_file, f)
|
138
|
+
return None
|
134
139
|
|
135
140
|
|
136
141
|
def get_file_hash(path: pathlib.Path) -> int:
|
@@ -137,9 +137,9 @@ def try_import_strava(config: Config) -> bool:
|
|
137
137
|
**{
|
138
138
|
"id": activity.id,
|
139
139
|
"commute": activity.commute,
|
140
|
-
"distance_km": activity.distance
|
140
|
+
"distance_km": activity.distance / 1000,
|
141
141
|
"name": activity.name,
|
142
|
-
"kind": str(activity.type),
|
142
|
+
"kind": str(activity.type.root),
|
143
143
|
"start": convert_to_datetime_ns(activity.start_date),
|
144
144
|
"elapsed_time": activity.elapsed_time,
|
145
145
|
"equipment": gear_names[activity.gear_id],
|
@@ -146,7 +146,7 @@ def float_with_comma_or_period(x: str) -> Optional[float]:
|
|
146
146
|
|
147
147
|
def import_from_strava_checkout() -> None:
|
148
148
|
checkout_path = pathlib.Path("Strava Export")
|
149
|
-
with open(checkout_path / "activities.csv") as f:
|
149
|
+
with open(checkout_path / "activities.csv", encoding="utf-8") as f:
|
150
150
|
rows = parse_csv(f.read())
|
151
151
|
header = rows[0]
|
152
152
|
|
@@ -159,9 +159,15 @@ def import_from_strava_checkout() -> None:
|
|
159
159
|
|
160
160
|
if header[0] == EXPECTED_COLUMNS[0]:
|
161
161
|
dayfirst = False
|
162
|
-
|
162
|
+
elif header[0] == "Aktivitäts-ID":
|
163
163
|
header = EXPECTED_COLUMNS
|
164
164
|
dayfirst = True
|
165
|
+
else:
|
166
|
+
logger.error(
|
167
|
+
f"You are trying to import a Strava checkout where the `activities.csv` contains an unexpected header format. In order to import this, we need to map these to the English ones. Unfortunately Strava often changes the number of columns. This means that the program needs to be updated to match the new Strava export format. Please go to https://github.com/martin-ueding/geo-activity-playground/issues and open a new issue and share the following output in the ticket:"
|
168
|
+
)
|
169
|
+
print(header)
|
170
|
+
sys.exit(1)
|
165
171
|
|
166
172
|
table = {
|
167
173
|
header[i]: [rows[r][i] for r in range(1, len(rows))] for i in range(len(header))
|