geo-activity-playground 0.30.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.
Files changed (132) hide show
  1. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/PKG-INFO +5 -6
  2. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/__main__.py +1 -1
  3. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/activities.py +11 -2
  4. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/config.py +5 -1
  5. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/heatmap.py +61 -15
  6. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/paths.py +3 -0
  7. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/tiles.py +8 -5
  8. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/video.py +2 -1
  9. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/directory.py +6 -2
  10. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/strava_checkout.py +8 -2
  11. geo_activity_playground-0.34.1/geo_activity_playground/webui/activity/blueprint.py +116 -0
  12. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/controller.py +127 -43
  13. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +9 -5
  14. geo_activity_playground-0.34.1/geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +42 -0
  15. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +1 -1
  16. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/name.html.j2 +5 -4
  17. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/templates/activity/show.html.j2 +9 -7
  18. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/app.py +29 -6
  19. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/entry_controller.py +1 -1
  20. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/controller.py +1 -1
  21. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +20 -44
  22. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/blueprint.py +5 -2
  23. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/heatmap_controller.py +22 -10
  24. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +1 -1
  25. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/search/templates/search/index.html.j2 +2 -2
  26. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/blueprint.py +44 -33
  27. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +11 -2
  28. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +1 -1
  29. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2 +1 -1
  30. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +1 -0
  31. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/MarkerCluster.Default.css +60 -0
  32. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/MarkerCluster.css +14 -0
  33. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/bootstrap.min.css +6 -0
  34. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/fullscreen.png +0 -0
  35. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
  36. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.css +661 -0
  37. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.fullscreen.css +40 -0
  38. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.js +6 -0
  39. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/leaflet.markercluster.js +3 -0
  40. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/table-sort.min.js +8 -0
  41. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/vega-embed@6 +7 -0
  42. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/vega-lite@4 +2 -0
  43. geo_activity_playground-0.34.1/geo_activity_playground/webui/static/vega@5 +2 -0
  44. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/controller.py +20 -25
  45. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/templates/summary/index.html.j2 +19 -6
  46. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/templates/home.html.j2 +6 -5
  47. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/templates/page.html.j2 +14 -16
  48. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/tile/blueprint.py +3 -2
  49. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/tile/controller.py +7 -3
  50. geo_activity_playground-0.30.0/geo_activity_playground/webui/upload/controller.py → geo_activity_playground-0.34.1/geo_activity_playground/webui/upload_blueprint.py +41 -35
  51. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/pyproject.toml +8 -8
  52. geo_activity_playground-0.30.0/geo_activity_playground/webui/activity/blueprint.py +0 -57
  53. geo_activity_playground-0.30.0/geo_activity_playground/webui/upload/__init__.py +0 -0
  54. geo_activity_playground-0.30.0/geo_activity_playground/webui/upload/blueprint.py +0 -44
  55. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/LICENSE +0 -0
  56. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/__init__.py +0 -0
  57. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/__init__.py +0 -0
  58. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/coordinates.py +0 -0
  59. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/enrichment.py +0 -0
  60. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/heart_rate.py +0 -0
  61. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/privacy_zones.py +0 -0
  62. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/similarity.py +0 -0
  63. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/tasks.py +0 -0
  64. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/test_tiles.py +0 -0
  65. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/test_time_conversion.py +0 -0
  66. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/core/time_conversion.py +0 -0
  67. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/__init__.py +0 -0
  68. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/grid_file.py +0 -0
  69. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/explorer/tile_visits.py +0 -0
  70. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/__init__.py +0 -0
  71. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/activity_parsers.py +0 -0
  72. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/csv_parser.py +0 -0
  73. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/strava_api.py +0 -0
  74. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/test_csv_parser.py +0 -0
  75. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/test_directory.py +0 -0
  76. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/importers/test_strava_api.py +0 -0
  77. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/__init__.py +0 -0
  78. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/activity/__init__.py +0 -0
  79. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/auth/blueprint.py +0 -0
  80. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/auth/templates/auth/index.html.j2 +0 -0
  81. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/authenticator.py +0 -0
  82. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/__init__.py +0 -0
  83. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/blueprint.py +0 -0
  84. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/controller.py +0 -0
  85. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -0
  86. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -0
  87. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/__init__.py +0 -0
  88. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/blueprint.py +0 -0
  89. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/controller.py +0 -0
  90. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/eddington/templates/eddington/index.html.j2 +0 -0
  91. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/__init__.py +0 -0
  92. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/blueprint.py +0 -0
  93. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/equipment/templates/equipment/index.html.j2 +0 -0
  94. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/__init__.py +0 -0
  95. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/blueprint.py +0 -0
  96. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/explorer/controller.py +0 -0
  97. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/heatmap/__init__.py +0 -0
  98. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/plot_util.py +0 -0
  99. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/search/blueprint.py +0 -0
  100. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/controller.py +0 -0
  101. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2 +0 -0
  102. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -0
  103. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2 +0 -0
  104. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/index.html.j2 +0 -0
  105. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -0
  106. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -0
  107. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2 +0 -0
  108. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/segmentation.html.j2 +0 -0
  109. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2 +0 -0
  110. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/settings/templates/settings/strava.html.j2 +0 -0
  111. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/__init__.py +0 -0
  112. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/blueprint.py +0 -0
  113. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/square_planner/controller.py +0 -0
  114. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
  115. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
  116. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
  117. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
  118. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
  119. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
  120. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
  121. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
  122. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/favicon.ico +0 -0
  123. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/favicon.svg +0 -0
  124. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
  125. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/site.webmanifest +0 -0
  126. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
  127. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
  128. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/__init__.py +0 -0
  129. {geo_activity_playground-0.30.0 → geo_activity_playground-0.34.1}/geo_activity_playground/webui/summary/blueprint.py +0 -0
  130. {geo_activity_playground-0.30.0/geo_activity_playground/webui/upload → geo_activity_playground-0.34.1/geo_activity_playground/webui}/templates/upload/index.html.j2 +0 -0
  131. {geo_activity_playground-0.30.0/geo_activity_playground/webui/upload → geo_activity_playground-0.34.1/geo_activity_playground/webui}/templates/upload/reload.html.j2 +0 -0
  132. {geo_activity_playground-0.30.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.30.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.13
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 (>=10.3.0,<11.0.0)
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.22.4,<2.0.0)
25
+ Requires-Dist: numpy (>=2.1.3,<3.0.0)
26
26
  Requires-Dist: pandas (>=2.2,<3.0)
27
- Requires-Dist: pyarrow (>=16.1.0,<17.0.0)
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.upload.controller import scan_for_activities
16
+ from geo_activity_playground.webui.upload_blueprint import scan_for_activities
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
@@ -1,5 +1,6 @@
1
1
  import datetime
2
2
  import functools
3
+ import json
3
4
  import logging
4
5
  import pickle
5
6
  from typing import Any
@@ -16,6 +17,7 @@ from tqdm import tqdm
16
17
  from geo_activity_playground.core.paths import activities_file
17
18
  from geo_activity_playground.core.paths import activity_enriched_meta_dir
18
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
19
21
 
20
22
  logger = logging.getLogger(__name__)
21
23
 
@@ -83,7 +85,12 @@ def build_activity_meta() -> None:
83
85
  rows = []
84
86
  for new_id in tqdm(new_ids, desc="Register new activities"):
85
87
  with open(activity_enriched_meta_dir() / f"{new_id}.pickle", "rb") as f:
86
- rows.append(pickle.load(f))
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)
87
94
 
88
95
  if rows:
89
96
  new_shard = pd.DataFrame(rows)
@@ -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(
@@ -21,14 +21,16 @@ 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 = "viridis"
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)
32
34
  kind_renames: dict[str, str] = dataclasses.field(default_factory=dict)
33
35
  kinds_without_achievements: list[str] = dataclasses.field(default_factory=list)
34
36
  metadata_extraction_regexes: list[str] = dataclasses.field(default_factory=list)
@@ -42,6 +44,8 @@ class Config:
42
44
  strava_client_code: Optional[str] = None
43
45
  time_diff_threshold_seconds: Optional[int] = 30
44
46
  upload_password: Optional[str] = None
47
+ map_tile_url: str = "https://tile.openstreetmap.org/{zoom}/{x}/{y}.png"
48
+ map_tile_attribution: str = '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> | <a href="https://www.openstreetmap.org/fixthemap">Correct Map</a>'
45
49
 
46
50
 
47
51
  class ConfigAccessor:
@@ -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 build_map_from_tiles(tile_bounds: TileBounds) -> np.ndarray:
127
- background = np.zeros((*PixelBounds.from_tile_bounds(tile_bounds).shape, 3))
128
-
129
- for x in range(tile_bounds.x_tile_min, tile_bounds.x_tile_max):
130
- for y in range(tile_bounds.y_tile_min, tile_bounds.y_tile_max):
131
- tile = np.array(get_tile(tile_bounds.zoom, x, y)) / 255
132
-
133
- i = y - tile_bounds.y_tile_min
134
- j = x - tile_bounds.x_tile_min
135
-
136
- background[
137
- i * OSM_TILE_SIZE : (i + 1) * OSM_TILE_SIZE,
138
- j * OSM_TILE_SIZE : (j + 1) * OSM_TILE_SIZE,
139
- :,
140
- ] = tile[:, :, :3]
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
 
@@ -53,6 +53,8 @@ _strava_last_activity_date_path = _cache_dir / "strava-last-activity-date.json"
53
53
 
54
54
  _new_config_file = pathlib.Path("config.json")
55
55
 
56
+ _activity_meta_override_dir = pathlib.Path("Metadata Override")
57
+
56
58
 
57
59
  cache_dir = dir_wrapper(_cache_dir)
58
60
 
@@ -63,6 +65,7 @@ activity_enriched_meta_dir = dir_wrapper(_activity_enriched_meta_dir)
63
65
  activity_enriched_time_series_dir = dir_wrapper(_activity_enriched_time_series_dir)
64
66
  tiles_per_time_series = dir_wrapper(_tiles_per_time_series)
65
67
  strava_api_dir = dir_wrapper(_strava_api_dir)
68
+ activity_meta_override_dir = dir_wrapper(_activity_meta_override_dir)
66
69
 
67
70
  activities_file = file_wrapper(_activities_file)
68
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
- path = pathlib.Path("Open Street Map Tiles") / f"{zoom}/{x}/{y}.png"
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 = f"https://tile.openstreetmap.org/{zoom}/{x}/{y}.png"
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()
@@ -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,
@@ -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() and path.suffixes and not path.stem.startswith(".")
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)
@@ -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
- if header[0] == "Aktivitäts-ID":
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))
@@ -0,0 +1,116 @@
1
+ import json
2
+ import urllib.parse
3
+ from collections.abc import Collection
4
+
5
+ from flask import Blueprint
6
+ from flask import redirect
7
+ from flask import render_template
8
+ from flask import request
9
+ from flask import Response
10
+ from flask import url_for
11
+
12
+ from ...core.activities import ActivityRepository
13
+ from ...explorer.tile_visits import TileVisitAccessor
14
+ from .controller import ActivityController
15
+ from geo_activity_playground.core.config import Config
16
+ from geo_activity_playground.core.paths import activity_meta_override_dir
17
+ from geo_activity_playground.core.privacy_zones import PrivacyZone
18
+ from geo_activity_playground.webui.authenticator import Authenticator
19
+ from geo_activity_playground.webui.authenticator import needs_authentication
20
+
21
+
22
+ def make_activity_blueprint(
23
+ repository: ActivityRepository,
24
+ tile_visit_accessor: TileVisitAccessor,
25
+ config: Config,
26
+ authenticator: Authenticator,
27
+ ) -> Blueprint:
28
+ blueprint = Blueprint("activity", __name__, template_folder="templates")
29
+
30
+ activity_controller = ActivityController(repository, tile_visit_accessor, config)
31
+
32
+ @blueprint.route("/all")
33
+ def all():
34
+ return render_template(
35
+ "activity/lines.html.j2", **activity_controller.render_all()
36
+ )
37
+
38
+ @blueprint.route("/<id>")
39
+ def show(id: str):
40
+ return render_template(
41
+ "activity/show.html.j2", **activity_controller.render_activity(int(id))
42
+ )
43
+
44
+ @blueprint.route("/<id>/sharepic.png")
45
+ def sharepic(id: str):
46
+ return Response(
47
+ activity_controller.render_sharepic(int(id)),
48
+ mimetype="image/png",
49
+ )
50
+
51
+ @blueprint.route("/day/<year>/<month>/<day>")
52
+ def day(year: str, month: str, day: str):
53
+ return render_template(
54
+ "activity/day.html.j2",
55
+ **activity_controller.render_day(int(year), int(month), int(day)),
56
+ )
57
+
58
+ @blueprint.route("/day-sharepic/<year>/<month>/<day>/sharepic.png")
59
+ def day_sharepic(year: str, month: str, day: str):
60
+ return Response(
61
+ activity_controller.render_day_sharepic(int(year), int(month), int(day)),
62
+ mimetype="image/png",
63
+ )
64
+
65
+ @blueprint.route("/name/<name>")
66
+ def name(name: str):
67
+ return render_template(
68
+ "activity/name.html.j2",
69
+ **activity_controller.render_name(urllib.parse.unquote(name)),
70
+ )
71
+
72
+ @blueprint.route("/edit/<id>", methods=["GET", "POST"])
73
+ @needs_authentication(authenticator)
74
+ def edit(id: str):
75
+ activity_id = int(id)
76
+ activity = repository.get_activity_by_id(activity_id)
77
+ override_file = activity_meta_override_dir() / f"{activity_id}.json"
78
+ if override_file.exists():
79
+ with open(override_file) as f:
80
+ override = json.load(f)
81
+ else:
82
+ override = {}
83
+
84
+ if request.method == "POST":
85
+ override = {}
86
+ if value := request.form.get("name"):
87
+ override["name"] = value
88
+ repository.meta.loc[activity_id, "name"] = value
89
+ if value := request.form.get("kind"):
90
+ override["kind"] = value
91
+ repository.meta.loc[activity_id, "kind"] = value
92
+ if value := request.form.get("equipment"):
93
+ override["equipment"] = value
94
+ repository.meta.loc[activity_id, "equipment"] = value
95
+ if value := request.form.get("commute"):
96
+ override["commute"] = True
97
+ repository.meta.loc[activity_id, "commute"] = True
98
+ if value := request.form.get("consider_for_achievements"):
99
+ override["consider_for_achievements"] = True
100
+ repository.meta.loc[activity_id, "consider_for_achievements"] = True
101
+
102
+ with open(override_file, "w") as f:
103
+ json.dump(override, f, ensure_ascii=False, indent=4, sort_keys=True)
104
+
105
+ repository.save()
106
+
107
+ return redirect(url_for(".show", id=activity_id))
108
+
109
+ return render_template(
110
+ "activity/edit.html.j2",
111
+ activity_id=activity_id,
112
+ activity=activity,
113
+ override=override,
114
+ )
115
+
116
+ return blueprint