geo-activity-playground 1.0.0__tar.gz → 1.1.0__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 (171) hide show
  1. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/PKG-INFO +1 -1
  2. geo_activity_playground-1.1.0/geo_activity_playground/alembic/versions/dc8073871da7_add_plotspec_group_by.py +28 -0
  3. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/config.py +1 -0
  4. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/datamodel.py +9 -0
  5. geo_activity_playground-1.1.0/geo_activity_playground/core/parametric_plot.py +162 -0
  6. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/app.py +7 -0
  7. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/activity_blueprint.py +11 -10
  8. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/auth_blueprint.py +3 -2
  9. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +2 -1
  10. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/calendar_blueprint.py +3 -2
  11. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/eddington_blueprints.py +3 -2
  12. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/entry_views.py +11 -11
  13. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/equipment_blueprint.py +2 -1
  14. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/explorer_blueprint.py +47 -13
  15. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/export_blueprint.py +3 -2
  16. geo_activity_playground-1.1.0/geo_activity_playground/webui/blueprints/hall_of_fame_blueprint.py +79 -0
  17. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/plot_builder_blueprint.py +38 -19
  18. geo_activity_playground-1.1.0/geo_activity_playground/webui/blueprints/summary_blueprint.py +211 -0
  19. geo_activity_playground-1.1.0/geo_activity_playground/webui/columns.py +70 -0
  20. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/browserconfig.xml +1 -1
  21. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/site.webmanifest +2 -2
  22. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/static/server-side-explorer.js +7 -2
  23. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/activity/name.html.j2 +4 -4
  24. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/activity/show.html.j2 +8 -8
  25. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/eddington/distance.html.j2 +3 -3
  26. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/eddington/elevation_gain.html.j2 +3 -3
  27. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/elevation_eddington/index.html.j2 +3 -3
  28. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/equipment/index.html.j2 +1 -1
  29. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/explorer/server-side.html.j2 +5 -4
  30. geo_activity_playground-1.1.0/geo_activity_playground/webui/templates/hall_of_fame/index.html.j2 +58 -0
  31. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/home.html.j2 +1 -4
  32. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/page.html.j2 +23 -37
  33. geo_activity_playground-1.1.0/geo_activity_playground/webui/templates/plot-macros.html.j2 +72 -0
  34. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/plot_builder/edit.html.j2 +12 -7
  35. geo_activity_playground-1.1.0/geo_activity_playground/webui/templates/plot_builder/import-spec.html.j2 +24 -0
  36. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/plot_builder/index.html.j2 +5 -0
  37. geo_activity_playground-1.1.0/geo_activity_playground/webui/templates/summary/index.html.j2 +50 -0
  38. geo_activity_playground-1.1.0/geo_activity_playground/webui/templates/summary/vega-chart.html.j2 +3 -0
  39. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/pyproject.toml +1 -1
  40. geo_activity_playground-1.0.0/geo_activity_playground/core/parametric_plot.py +0 -108
  41. geo_activity_playground-1.0.0/geo_activity_playground/webui/blueprints/summary_blueprint.py +0 -337
  42. geo_activity_playground-1.0.0/geo_activity_playground/webui/columns.py +0 -37
  43. geo_activity_playground-1.0.0/geo_activity_playground/webui/templates/summary/index.html.j2 +0 -257
  44. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/LICENSE +0 -0
  45. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/__init__.py +0 -0
  46. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/__main__.py +0 -0
  47. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/README +0 -0
  48. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/env.py +0 -0
  49. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/script.py.mako +0 -0
  50. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/0f02b92c4f94_add_tag_color.py +0 -0
  51. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/38882503dc7c_add_tags_to_activities.py +0 -0
  52. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py +0 -0
  53. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py +0 -0
  54. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/93cc82ad1b60_add_parametricplotspec.py +0 -0
  55. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/ab83b9d23127_add_upstream_id.py +0 -0
  56. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/b03491c593f6_add_crop_indices.py +0 -0
  57. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/da2cba03b71d_add_photos.py +0 -0
  58. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmark_name.py +0 -0
  59. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/alembic/versions/script.py.mako +0 -0
  60. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/__init__.py +0 -0
  61. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/activities.py +0 -0
  62. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/coordinates.py +0 -0
  63. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/enrichment.py +0 -0
  64. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/export.py +0 -0
  65. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/heart_rate.py +0 -0
  66. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/meta_search.py +0 -0
  67. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/missing_values.py +0 -0
  68. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/paths.py +0 -0
  69. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/privacy_zones.py +0 -0
  70. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/raster_map.py +0 -0
  71. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/similarity.py +0 -0
  72. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/summary_stats.py +0 -0
  73. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/tasks.py +0 -0
  74. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/test_datamodel.py +0 -0
  75. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/test_meta_search.py +0 -0
  76. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/test_missing_values.py +0 -0
  77. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/test_summary_stats.py +0 -0
  78. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/test_tiles.py +0 -0
  79. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/test_time_conversion.py +0 -0
  80. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/tiles.py +0 -0
  81. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/core/time_conversion.py +0 -0
  82. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/explorer/__init__.py +0 -0
  83. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/explorer/grid_file.py +0 -0
  84. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/explorer/tile_visits.py +0 -0
  85. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/explorer/video.py +0 -0
  86. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/heatmap_video.py +0 -0
  87. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/__init__.py +0 -0
  88. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/activity_parsers.py +0 -0
  89. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/csv_parser.py +0 -0
  90. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/directory.py +0 -0
  91. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/strava_api.py +0 -0
  92. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/strava_checkout.py +0 -0
  93. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/test_csv_parser.py +0 -0
  94. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/test_directory.py +0 -0
  95. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/importers/test_strava_api.py +0 -0
  96. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/__init__.py +0 -0
  97. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/authenticator.py +0 -0
  98. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/__init__.py +0 -0
  99. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/heatmap_blueprint.py +0 -0
  100. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/photo_blueprint.py +0 -0
  101. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/search_blueprint.py +0 -0
  102. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/settings_blueprint.py +0 -0
  103. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/square_planner_blueprint.py +0 -0
  104. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/tile_blueprint.py +0 -0
  105. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/blueprints/upload_blueprint.py +0 -0
  106. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/flasher.py +0 -0
  107. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/plot_util.py +0 -0
  108. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/search_util.py +0 -0
  109. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/bootstrap}/bootstrap-dark-mode.js +0 -0
  110. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/bootstrap}/bootstrap.bundle.min.js +0 -0
  111. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/bootstrap}/bootstrap.min.css +0 -0
  112. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/android-chrome-192x192.png +0 -0
  113. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/android-chrome-512x512.png +0 -0
  114. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/apple-touch-icon.png +0 -0
  115. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/favicon-16x16.png +0 -0
  116. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/favicon-32x32.png +0 -0
  117. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/favicon-48x48.png +0 -0
  118. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/favicon.ico +0 -0
  119. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/favicon.svg +0 -0
  120. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/mstile-150x150.png +0 -0
  121. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/web-app-manifest-192x192.png +0 -0
  122. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/favicons}/web-app-manifest-512x512.png +0 -0
  123. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/static/images/layers-2x.png +0 -0
  124. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/static/images/layers.png +0 -0
  125. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/static/images/marker-icon-2x.png +0 -0
  126. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/static/images/marker-icon.png +0 -0
  127. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/static/images/marker-shadow.png +0 -0
  128. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/Leaflet.fullscreen.min.js +0 -0
  129. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/MarkerCluster.Default.css +0 -0
  130. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/MarkerCluster.css +0 -0
  131. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/fullscreen.png +0 -0
  132. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/fullscreen@2x.png +0 -0
  133. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/leaflet.css +0 -0
  134. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/leaflet.fullscreen.css +0 -0
  135. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/leaflet.js +0 -0
  136. {geo_activity_playground-1.0.0/geo_activity_playground/webui/static → geo_activity_playground-1.1.0/geo_activity_playground/webui/static/leaflet}/leaflet.markercluster.js +0 -0
  137. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/static/table-sort.min.js +0 -0
  138. /geo_activity_playground-1.0.0/geo_activity_playground/webui/static/vega-embed@6 → /geo_activity_playground-1.1.0/geo_activity_playground/webui/static/vega/vega-embed@6.js +0 -0
  139. /geo_activity_playground-1.0.0/geo_activity_playground/webui/static/vega-lite@4 → /geo_activity_playground-1.1.0/geo_activity_playground/webui/static/vega/vega-lite@4.js +0 -0
  140. /geo_activity_playground-1.0.0/geo_activity_playground/webui/static/vega@5 → /geo_activity_playground-1.1.0/geo_activity_playground/webui/static/vega/vega@5.js +0 -0
  141. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/activity/day.html.j2 +0 -0
  142. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/activity/edit.html.j2 +0 -0
  143. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/activity/lines.html.j2 +0 -0
  144. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/activity/trim.html.j2 +0 -0
  145. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/auth/index.html.j2 +0 -0
  146. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +0 -0
  147. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/calendar/index.html.j2 +0 -0
  148. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/calendar/month.html.j2 +0 -0
  149. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/export/index.html.j2 +0 -0
  150. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/heatmap/index.html.j2 +0 -0
  151. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/photo/map.html.j2 +0 -0
  152. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/photo/new.html.j2 +0 -0
  153. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/search/index.html.j2 +0 -0
  154. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/search_form.html.j2 +0 -0
  155. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/admin-password.html.j2 +0 -0
  156. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/color-schemes.html.j2 +0 -0
  157. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/heart-rate.html.j2 +0 -0
  158. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/index.html.j2 +0 -0
  159. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/manage-equipments.html.j2 +0 -0
  160. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/manage-kinds.html.j2 +0 -0
  161. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/metadata-extraction.html.j2 +0 -0
  162. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/privacy-zones.html.j2 +0 -0
  163. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/segmentation.html.j2 +0 -0
  164. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/sharepic.html.j2 +0 -0
  165. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/strava.html.j2 +0 -0
  166. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/tags-edit.html.j2 +0 -0
  167. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/tags-list.html.j2 +0 -0
  168. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/settings/tags-new.html.j2 +0 -0
  169. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/square_planner/index.html.j2 +0 -0
  170. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/upload/index.html.j2 +0 -0
  171. {geo_activity_playground-1.0.0 → geo_activity_playground-1.1.0}/geo_activity_playground/webui/templates/upload/reload.html.j2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geo-activity-playground
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Analysis of geo data activities like rides, runs or hikes.
5
5
  License: MIT
6
6
  Author: Martin Ueding
@@ -0,0 +1,28 @@
1
+ from typing import Sequence
2
+ from typing import Union
3
+
4
+ import sqlalchemy as sa
5
+ from alembic import op
6
+
7
+
8
+ # revision identifiers, used by Alembic.
9
+ revision: str = "dc8073871da7"
10
+ down_revision: Union[str, None] = "0f02b92c4f94"
11
+ branch_labels: Union[str, Sequence[str], None] = None
12
+ depends_on: Union[str, Sequence[str], None] = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ # ### commands auto generated by Alembic - please adjust! ###
17
+ with op.batch_alter_table("plot_specs", schema=None) as batch_op:
18
+ batch_op.add_column(sa.Column("group_by", sa.String(), nullable=True))
19
+
20
+ # ### end Alembic commands ###
21
+
22
+
23
+ def downgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ with op.batch_alter_table("plot_specs", schema=None) as batch_op:
26
+ batch_op.drop_column("group_by")
27
+
28
+ # ### end Alembic commands ###
@@ -21,6 +21,7 @@ logger = logging.getLogger(__name__)
21
21
  @dataclasses.dataclass
22
22
  class Config:
23
23
  birth_year: Optional[int] = None
24
+ cluster_color_strategy: str = "colorful_cluster"
24
25
  color_scheme_for_counts: str = "teals"
25
26
  color_scheme_for_kind: str = "category10"
26
27
  color_scheme_for_heatmap: str = "hot"
@@ -231,6 +231,9 @@ def query_activity_meta(clauses: list = []) -> pd.DataFrame:
231
231
  df = pd.DataFrame(rows)
232
232
  # If the search yields only activities without time information, the dtype isn't derived correctly.
233
233
  df["start"] = pd.to_datetime(df["start"])
234
+ # start = df["start"].to_list()
235
+ # random.shuffle(start)
236
+ # df["start"] = pd.Series(start)
234
237
  df["elapsed_time"] = pd.to_timedelta(df["elapsed_time"])
235
238
 
236
239
  if len(df):
@@ -251,8 +254,12 @@ def query_activity_meta(clauses: list = []) -> pd.DataFrame:
251
254
  df["week"] = df["start"].dt.isocalendar().week
252
255
  df["day_of_week"] = df["start"].dt.day_of_week
253
256
  df["iso_year"] = df["start"].dt.isocalendar().year
257
+ df["iso_day"] = df["start"].dt.isocalendar().day
254
258
  df["hours"] = df["elapsed_time"].dt.total_seconds() / 3_600
255
259
  df["hours_moving"] = df["moving_time"].dt.total_seconds() / 3_600
260
+ df["iso_year_week"] = [
261
+ f"{year:04d}-{week:02d}" for year, week in zip(df["iso_year"], df["week"])
262
+ ]
256
263
 
257
264
  df.index = df["id"]
258
265
 
@@ -365,6 +372,7 @@ class PlotSpec(DB.Model):
365
372
  opacity: Mapped[str] = mapped_column(sa.String, nullable=False, default="")
366
373
  column: Mapped[str] = mapped_column(sa.String, nullable=False, default="")
367
374
  facet: Mapped[str] = mapped_column(sa.String, nullable=False, default="")
375
+ group_by: Mapped[str] = mapped_column(sa.String, nullable=True, default="")
368
376
 
369
377
  FIELDS = [
370
378
  "name",
@@ -378,6 +386,7 @@ class PlotSpec(DB.Model):
378
386
  "opacity",
379
387
  "column",
380
388
  "facet",
389
+ "group_by",
381
390
  ]
382
391
 
383
392
  def __str__(self) -> str:
@@ -0,0 +1,162 @@
1
+ import altair as alt
2
+ import pandas as pd
3
+
4
+ from .datamodel import PlotSpec
5
+
6
+
7
+ MARKS = {
8
+ "point": "Point",
9
+ "circle": "Circle",
10
+ "area": "Area",
11
+ "bar": "Bar",
12
+ "rect": "Rectangle",
13
+ "line": "Line",
14
+ }
15
+ CONTINUOUS_VARIABLES = {
16
+ "distance_km": "Distance / km",
17
+ "distance_km_cumsum": "Cumulative Distance / km",
18
+ "sum(distance_km)": "Total distance / km",
19
+ "mean(distance_km)": "Average distance / km",
20
+ "start": "Date",
21
+ "hours": "Elapsed time / h",
22
+ "hours_moving": "Moving time / h",
23
+ "calories": "Energy / kcal",
24
+ "steps": "Steps",
25
+ "elevation_gain": "Elevation gain / m",
26
+ "start_elevation": "Start elevation / m",
27
+ "end_elevation": "End elevation / m",
28
+ "sum(elevation_gain)": "Total elevation gain / m",
29
+ "mean(elevation_gain)": "Average elevation gain / m",
30
+ "num_new_tiles_14": "New tiles 14",
31
+ "num_new_tiles_14": "New tiles 17",
32
+ "average_speed_moving_kmh": "Average moving speed / km/h",
33
+ "average_speed_elapsed_kmh": "Average elapsed speed / km/h",
34
+ "start_latitude": "Start latitude / °",
35
+ "start_longitude": "Start longitude / °",
36
+ "end_latitude": "End latitude / °",
37
+ "end_longitude": "End longitude / °",
38
+ }
39
+ DISCRETE_VARIABLES = {
40
+ "equipment": "Equipment",
41
+ "kind": "Activity kind",
42
+ "consider_for_achievements": "Consider for achievements",
43
+ "year(start):O": "Year",
44
+ "iso_year:O": "ISO Year",
45
+ "yearquarter(start)": "Year, Quarter",
46
+ "yearquartermonth(start)": "Year, Quarter, Month",
47
+ "yearmonth(start)": "Year, Month",
48
+ "quarter(start)": "Quarter",
49
+ "quartermonth(start)": "Quarter, Month",
50
+ "month(start)": "Month",
51
+ "week:O": "ISO Week",
52
+ "date(start)": "Day of month",
53
+ "weekday(start)": "Day of week",
54
+ "iso_day:O": "ISO Day",
55
+ }
56
+ GROUP_BY_VARIABLES = {
57
+ "": "(no grouping)",
58
+ "equipment": "Equipment",
59
+ "kind": "Activity kind",
60
+ "consider_for_achievements": "Consider for achievements",
61
+ "year": "Year",
62
+ "iso_year": "ISO Year",
63
+ "week": "ISO Week",
64
+ }
65
+
66
+ VARIABLES_1 = {"": "", **DISCRETE_VARIABLES}
67
+ VARIABLES_2 = {"": "", **DISCRETE_VARIABLES, **CONTINUOUS_VARIABLES}
68
+
69
+
70
+ def make_parametric_plot(df: pd.DataFrame, spec: PlotSpec) -> dict[str, str]:
71
+ if spec.group_by:
72
+ grouped = df.groupby(spec.group_by)
73
+ else:
74
+ grouped = [("", df)]
75
+
76
+ chart_groups = {}
77
+ for key, group in grouped:
78
+ chart = alt.Chart(group)
79
+
80
+ match spec.mark:
81
+ case "point":
82
+ chart = chart.mark_point()
83
+ case "circle":
84
+ chart = chart.mark_circle()
85
+ case "area":
86
+ chart = chart.mark_area()
87
+ case "bar":
88
+ chart = chart.mark_bar()
89
+ case "rect":
90
+ chart = chart.mark_rect()
91
+ case "line":
92
+ chart = chart.mark_line()
93
+ case _:
94
+ raise ValueError()
95
+
96
+ if spec.mark in ["area", "line"]:
97
+ group_by_variables = [
98
+ var.split(":")[0]
99
+ for var in [
100
+ spec.color,
101
+ spec.shape,
102
+ spec.size,
103
+ spec.opacity,
104
+ spec.row,
105
+ spec.column,
106
+ spec.facet,
107
+ ]
108
+ if var
109
+ ]
110
+ for column in ["distance_km"]:
111
+ group[column + "_cumsum"] = (
112
+ group[group_by_variables + [column]]
113
+ .groupby(group_by_variables)
114
+ .cumsum()
115
+ )
116
+
117
+ encodings = [
118
+ (
119
+ alt.X(spec.x, type="ordinal", title=VARIABLES_2[spec.x])
120
+ if spec.mark == "rect"
121
+ else alt.X(spec.x, title=VARIABLES_2[spec.x])
122
+ ),
123
+ (
124
+ alt.Y(spec.y, type="ordinal", title=VARIABLES_2[spec.y])
125
+ if spec.mark == "rect"
126
+ else alt.Y(spec.y, title=VARIABLES_2[spec.y])
127
+ ),
128
+ ]
129
+ tooltips = [
130
+ alt.Tooltip(spec.x, title=VARIABLES_2[spec.x]),
131
+ alt.Tooltip(spec.y, title=VARIABLES_2[spec.y]),
132
+ ]
133
+
134
+ if spec.color:
135
+ encodings.append(alt.Color(spec.color, title=VARIABLES_2[spec.color]))
136
+ tooltips.append(alt.Tooltip(spec.color, title=VARIABLES_2[spec.color]))
137
+ if spec.shape:
138
+ encodings.append(alt.Shape(spec.shape, title=VARIABLES_2[spec.shape]))
139
+ tooltips.append(alt.Tooltip(spec.shape, title=VARIABLES_2[spec.shape]))
140
+ if spec.size:
141
+ encodings.append(alt.Size(spec.size, title=VARIABLES_2[spec.size]))
142
+ tooltips.append(alt.Tooltip(spec.size, title=VARIABLES_2[spec.size]))
143
+ if spec.opacity:
144
+ encodings.append(alt.Opacity(spec.opacity, title=VARIABLES_2[spec.opacity]))
145
+ tooltips.append(alt.Tooltip(spec.opacity, title=VARIABLES_2[spec.opacity]))
146
+ if spec.row:
147
+ encodings.append(alt.Row(spec.row, title=VARIABLES_2[spec.row]))
148
+ tooltips.append(alt.Tooltip(spec.row, title=VARIABLES_2[spec.row]))
149
+ if spec.column:
150
+ encodings.append(alt.Column(spec.column, title=VARIABLES_2[spec.column]))
151
+ tooltips.append(alt.Tooltip(spec.column, title=VARIABLES_2[spec.column]))
152
+ if spec.facet:
153
+ encodings.append(
154
+ alt.Facet(spec.facet, columns=3, title=VARIABLES_2[spec.facet])
155
+ )
156
+ tooltips.append(alt.Tooltip(spec.facet, title=VARIABLES_2[spec.facet]))
157
+
158
+ key = str(int(key) if isinstance(key, float) else key)
159
+ chart_groups[key] = (
160
+ chart.encode(*encodings, tooltips).interactive().to_json(format="vega")
161
+ )
162
+ return chart_groups
@@ -7,6 +7,7 @@ import pathlib
7
7
  import secrets
8
8
  import shutil
9
9
  import urllib.parse
10
+ import uuid
10
11
 
11
12
  import pandas as pd
12
13
  import sqlalchemy
@@ -39,6 +40,7 @@ from .blueprints.entry_views import register_entry_views
39
40
  from .blueprints.equipment_blueprint import make_equipment_blueprint
40
41
  from .blueprints.explorer_blueprint import make_explorer_blueprint
41
42
  from .blueprints.export_blueprint import make_export_blueprint
43
+ from .blueprints.hall_of_fame_blueprint import make_hall_of_fame_blueprint
42
44
  from .blueprints.heatmap_blueprint import make_heatmap_blueprint
43
45
  from .blueprints.photo_blueprint import make_photo_blueprint
44
46
  from .blueprints.plot_builder_blueprint import make_plot_builder_blueprint
@@ -112,6 +114,10 @@ def web_ui_main(
112
114
  else:
113
115
  return value.strftime("%Y-%m-%d %H:%M")
114
116
 
117
+ @app.template_global("unique_id")
118
+ def unique_id():
119
+ return f"id-{uuid.uuid4()}"
120
+
115
121
  @app.template_filter()
116
122
  def td(v: datetime.timedelta):
117
123
  if pd.isna(v):
@@ -162,6 +168,7 @@ def web_ui_main(
162
168
  image_transforms,
163
169
  ),
164
170
  "/export": make_export_blueprint(authenticator),
171
+ "/hall-of-fame": make_hall_of_fame_blueprint(repository, search_query_history),
165
172
  "/heatmap": make_heatmap_blueprint(
166
173
  repository, tile_visit_accessor, config_accessor(), search_query_history
167
174
  ),
@@ -18,6 +18,7 @@ from flask import render_template
18
18
  from flask import request
19
19
  from flask import Response
20
20
  from flask import url_for
21
+ from flask.typing import ResponseReturnValue
21
22
  from PIL import Image
22
23
  from PIL import ImageDraw
23
24
 
@@ -59,7 +60,7 @@ def make_activity_blueprint(
59
60
  blueprint = Blueprint("activity", __name__, template_folder="templates")
60
61
 
61
62
  @blueprint.route("/all")
62
- def all():
63
+ def all() -> ResponseReturnValue:
63
64
  cmap = matplotlib.colormaps["Dark2"]
64
65
  fc = geojson.FeatureCollection(
65
66
  features=[
@@ -93,7 +94,7 @@ def make_activity_blueprint(
93
94
  return render_template("activity/lines.html.j2", **context)
94
95
 
95
96
  @blueprint.route("/<int:id>")
96
- def show(id: str):
97
+ def show(id: str) -> ResponseReturnValue:
97
98
  activity = repository.get_activity_by_id(id)
98
99
 
99
100
  time_series = repository.get_time_series(id)
@@ -189,7 +190,7 @@ def make_activity_blueprint(
189
190
  return render_template("activity/show.html.j2", **context)
190
191
 
191
192
  @blueprint.route("/<int:id>/sharepic.png")
192
- def sharepic(id: int):
193
+ def sharepic(id: int) -> ResponseReturnValue:
193
194
  activity = repository.get_activity_by_id(id)
194
195
  time_series = repository.get_time_series(id)
195
196
  for coordinates in config.privacy_zones.values():
@@ -205,7 +206,7 @@ def make_activity_blueprint(
205
206
  )
206
207
 
207
208
  @blueprint.route("/day/<int:year>/<int:month>/<int:day>")
208
- def day(year: int, month: int, day: int):
209
+ def day(year: int, month: int, day: int) -> ResponseReturnValue:
209
210
  meta = repository.meta
210
211
  selection = meta["start"].dt.date == datetime.date(year, month, day)
211
212
  activities_that_day = meta.loc[selection]
@@ -256,7 +257,7 @@ def make_activity_blueprint(
256
257
  )
257
258
 
258
259
  @blueprint.route("/day-sharepic/<int:year>/<int:month>/<int:day>/sharepic.png")
259
- def day_sharepic(year: int, month: int, day: int):
260
+ def day_sharepic(year: int, month: int, day: int) -> ResponseReturnValue:
260
261
  meta = repository.meta
261
262
  selection = meta["start"].dt.date == datetime.date(year, month, day)
262
263
  activities_that_day = meta.loc[selection]
@@ -273,7 +274,7 @@ def make_activity_blueprint(
273
274
  )
274
275
 
275
276
  @blueprint.route("/name/<name>")
276
- def name(name: str):
277
+ def name(name: str) -> ResponseReturnValue:
277
278
  meta = repository.meta
278
279
  selection = meta["name"] == name
279
280
  activities_with_name = meta.loc[selection]
@@ -324,7 +325,7 @@ def make_activity_blueprint(
324
325
 
325
326
  @blueprint.route("/edit/<id>", methods=["GET", "POST"])
326
327
  @needs_authentication(authenticator)
327
- def edit(id: str):
328
+ def edit(id: str) -> ResponseReturnValue:
328
329
  activity = DB.session.get(Activity, int(id))
329
330
  if activity is None:
330
331
  abort(404)
@@ -365,7 +366,7 @@ def make_activity_blueprint(
365
366
 
366
367
  @blueprint.route("/trim/<id>", methods=["GET", "POST"])
367
368
  @needs_authentication(authenticator)
368
- def trim(id: str):
369
+ def trim(id: str) -> ResponseReturnValue:
369
370
  activity = DB.session.get(Activity, int(id))
370
371
  if activity is None:
371
372
  abort(404)
@@ -434,7 +435,7 @@ def make_activity_blueprint(
434
435
 
435
436
  @blueprint.route("/delete/<id>")
436
437
  @needs_authentication(authenticator)
437
- def delete(id: int):
438
+ def delete(id: int) -> ResponseReturnValue:
438
439
  activity = DB.session.get_one(Activity, id)
439
440
  activity.delete_data()
440
441
  DB.session.delete(activity)
@@ -443,7 +444,7 @@ def make_activity_blueprint(
443
444
 
444
445
  @blueprint.route("/download-original/<id>")
445
446
  @needs_authentication(authenticator)
446
- def download_original(id: int):
447
+ def download_original(id: int) -> ResponseReturnValue:
447
448
  activity = DB.session.get_one(Activity, id)
448
449
  path = pathlib.Path(activity.path)
449
450
  with open(path) as f:
@@ -3,6 +3,7 @@ from flask import redirect
3
3
  from flask import render_template
4
4
  from flask import request
5
5
  from flask import url_for
6
+ from flask.typing import ResponseReturnValue
6
7
 
7
8
  from ..authenticator import Authenticator
8
9
 
@@ -11,7 +12,7 @@ def make_auth_blueprint(authenticator: Authenticator) -> Blueprint:
11
12
  blueprint = Blueprint("auth", __name__, template_folder="templates")
12
13
 
13
14
  @blueprint.route("/", methods=["GET", "POST"])
14
- def index():
15
+ def index() -> ResponseReturnValue:
15
16
  if request.method == "POST":
16
17
  authenticator.authenticate(request.form["password"])
17
18
  if redirect_to := request.form["redirect"]:
@@ -23,7 +24,7 @@ def make_auth_blueprint(authenticator: Authenticator) -> Blueprint:
23
24
  )
24
25
 
25
26
  @blueprint.route("/logout")
26
- def logout():
27
+ def logout() -> ResponseReturnValue:
27
28
  authenticator.logout()
28
29
  return redirect(url_for(".index"))
29
30
 
@@ -2,6 +2,7 @@ import altair as alt
2
2
  import pandas as pd
3
3
  from flask import Blueprint
4
4
  from flask import render_template
5
+ from flask.typing import ResponseReturnValue
5
6
 
6
7
  from ..columns import column_distance
7
8
  from ..columns import column_elevation_gain
@@ -12,7 +13,7 @@ def make_bubble_chart_blueprint(repository) -> Blueprint:
12
13
  blueprint = Blueprint("bubble_chart", __name__, template_folder="templates")
13
14
 
14
15
  @blueprint.route("/", endpoint="index")
15
- def bubble_chart():
16
+ def bubble_chart() -> ResponseReturnValue:
16
17
  activities = repository.meta
17
18
 
18
19
  # Ensure 'activity_id' exists in the activities DataFrame
@@ -5,6 +5,7 @@ import pandas as pd
5
5
  import sqlalchemy
6
6
  from flask import Blueprint
7
7
  from flask import render_template
8
+ from flask.typing import ResponseReturnValue
8
9
 
9
10
  from ...core.activities import ActivityRepository
10
11
  from ...core.datamodel import Activity
@@ -15,7 +16,7 @@ def make_calendar_blueprint(repository: ActivityRepository) -> Blueprint:
15
16
  blueprint = Blueprint("calendar", __name__, template_folder="templates")
16
17
 
17
18
  @blueprint.route("/")
18
- def index():
19
+ def index() -> ResponseReturnValue:
19
20
  data = DB.session.execute(
20
21
  sqlalchemy.select(Activity.start, Activity.distance_km)
21
22
  ).all()
@@ -49,7 +50,7 @@ def make_calendar_blueprint(repository: ActivityRepository) -> Blueprint:
49
50
  return render_template("calendar/index.html.j2", **context)
50
51
 
51
52
  @blueprint.route("/<int:year>/<int:month>")
52
- def month(year: int, month: int):
53
+ def month(year: int, month: int) -> ResponseReturnValue:
53
54
  meta = repository.meta
54
55
 
55
56
  filtered = meta.loc[
@@ -8,6 +8,7 @@ from flask import Blueprint
8
8
  from flask import render_template
9
9
  from flask import Request
10
10
  from flask import request
11
+ from flask.typing import ResponseReturnValue
11
12
 
12
13
  from ...core.activities import ActivityRepository
13
14
  from ...core.meta_search import apply_search_query
@@ -24,7 +25,7 @@ def register_eddington_blueprint(
24
25
  blueprint = Blueprint("eddington", __name__, template_folder="templates")
25
26
 
26
27
  @blueprint.route("/")
27
- def distance():
28
+ def distance() -> ResponseReturnValue:
28
29
  return _render_eddington_template(
29
30
  repository,
30
31
  request,
@@ -35,7 +36,7 @@ def register_eddington_blueprint(
35
36
  )
36
37
 
37
38
  @blueprint.route("/elevation_gain")
38
- def elevation_gain():
39
+ def elevation_gain() -> ResponseReturnValue:
39
40
  return _render_eddington_template(
40
41
  repository,
41
42
  request,
@@ -1,5 +1,6 @@
1
1
  import collections
2
2
  import datetime
3
+ from typing import Any
3
4
 
4
5
  import altair as alt
5
6
  import flask
@@ -7,6 +8,7 @@ import pandas as pd
7
8
  import sqlalchemy
8
9
  from flask import render_template
9
10
  from flask import Response
11
+ from flask.typing import ResponseReturnValue
10
12
 
11
13
  from ...core.activities import ActivityRepository
12
14
  from ...core.activities import make_geojson_from_time_series
@@ -16,6 +18,7 @@ from ...core.datamodel import DB
16
18
  from ..columns import column_distance
17
19
  from ..columns import column_elevation_gain
18
20
  from ..columns import ColumnDescription
21
+ from ..columns import META_COLUMNS
19
22
  from ..plot_util import make_kind_scale
20
23
 
21
24
 
@@ -23,19 +26,16 @@ def register_entry_views(
23
26
  app: flask.Flask, repository: ActivityRepository, config: Config
24
27
  ) -> None:
25
28
  @app.route("/")
26
- def index() -> Response:
27
- context = {"latest_activities": []}
29
+ def index() -> ResponseReturnValue:
30
+ context: dict[str, Any] = {"latest_activities": []}
31
+ df = repository.meta
28
32
 
29
33
  if len(repository):
30
- kind_scale = make_kind_scale(repository.meta, config)
31
- context["distance_last_30_days_plot"] = _last_30_days_meta_plot(
32
- repository.meta, kind_scale, column_distance
33
- )
34
-
35
- if config.reliable_elevation_measurements:
36
- context["elevation_gain_last_30_days_plot"] = _last_30_days_meta_plot(
37
- repository.meta, kind_scale, column_elevation_gain
38
- )
34
+ kind_scale = make_kind_scale(df, config)
35
+ context["last_30_days_plot"] = {
36
+ column.display_name: _last_30_days_meta_plot(df, kind_scale, column)
37
+ for column in META_COLUMNS
38
+ }
39
39
 
40
40
  context["latest_activities"] = collections.defaultdict(list)
41
41
  for activity in DB.session.scalars(
@@ -2,6 +2,7 @@ import altair as alt
2
2
  import pandas as pd
3
3
  from flask import Blueprint
4
4
  from flask import render_template
5
+ from flask.typing import ResponseReturnValue
5
6
 
6
7
  from ...core.activities import ActivityRepository
7
8
  from ...core.config import Config
@@ -15,7 +16,7 @@ def make_equipment_blueprint(
15
16
  blueprint = Blueprint("equipment", __name__, template_folder="templates")
16
17
 
17
18
  @blueprint.route("/")
18
- def index():
19
+ def index() -> ResponseReturnValue:
19
20
  equipment_summary = get_equipment_use_table(
20
21
  repository.meta, config.equipment_offsets
21
22
  )
@@ -1,5 +1,6 @@
1
1
  import abc
2
2
  import datetime
3
+ import hashlib
3
4
  import io
4
5
  import itertools
5
6
  import logging
@@ -21,6 +22,7 @@ from flask import render_template
21
22
  from flask import request
22
23
  from flask import Response
23
24
  from flask import url_for
25
+ from flask.typing import ResponseReturnValue
24
26
 
25
27
  from ...core.activities import ActivityRepository
26
28
  from ...core.config import ConfigAccessor
@@ -62,7 +64,7 @@ class ColorStrategy(abc.ABC):
62
64
  pass
63
65
 
64
66
 
65
- class ClusterColorStrategy(ColorStrategy):
67
+ class MaxClusterColorStrategy(ColorStrategy):
66
68
  def __init__(self, evolution_state, tile_visits):
67
69
  self.evolution_state = evolution_state
68
70
  self.tile_visits = tile_visits
@@ -84,6 +86,31 @@ class ClusterColorStrategy(ColorStrategy):
84
86
  return grayscale
85
87
 
86
88
 
89
+ class ColorfulClusterColorStrategy(ColorStrategy):
90
+ def __init__(self, evolution_state: TileEvolutionState, tile_visits):
91
+ self.evolution_state = evolution_state
92
+ self.tile_visits = tile_visits
93
+ self.max_cluster_members = max(
94
+ evolution_state.clusters.values(),
95
+ key=len,
96
+ )
97
+ self._cmap = matplotlib.colormaps["hsv"]
98
+
99
+ def color_image(
100
+ self, tile_xy: tuple[int, int], grayscale: np.ndarray
101
+ ) -> np.ndarray:
102
+ if tile_xy in self.evolution_state.memberships:
103
+ cluster_id = self.evolution_state.memberships[tile_xy]
104
+ m = hashlib.sha256()
105
+ m.update(str(cluster_id).encode())
106
+ d = int(m.hexdigest(), base=16) / (256.0**m.digest_size)
107
+ return blend_color(grayscale, np.array([[self._cmap(d)[:3]]]), 0.3)
108
+ elif tile_xy in self.tile_visits:
109
+ return blend_color(grayscale, 0.0, 0.3)
110
+ else:
111
+ return grayscale
112
+
113
+
87
114
  class VisitTimeColorStrategy(ColorStrategy):
88
115
  def __init__(self, tile_visits, use_first=True):
89
116
  self.tile_visits = tile_visits
@@ -133,7 +160,7 @@ def make_explorer_blueprint(
133
160
 
134
161
  @blueprint.route("/enable-zoom-level/<int:zoom>")
135
162
  @needs_authentication(authenticator)
136
- def enable_zoom_level(zoom: int):
163
+ def enable_zoom_level(zoom: int) -> ResponseReturnValue:
137
164
  if 0 <= zoom <= 19:
138
165
  config_accessor().explorer_zoom_levels.append(zoom)
139
166
  config_accessor().explorer_zoom_levels.sort()
@@ -145,11 +172,11 @@ def make_explorer_blueprint(
145
172
  return redirect(url_for(".map", zoom=zoom))
146
173
 
147
174
  @blueprint.route(
148
- "/<int:zoom>/<float:north>/<float:east>/<float:south>/<float:west>/explored.<suffix>"
175
+ "/<int:zoom>/<float:north>/<float:east>/<float:south>/<float:west>/missing.<suffix>"
149
176
  )
150
- def download(
177
+ def download_missing(
151
178
  zoom: int, north: float, east: float, south: float, west: float, suffix: str
152
- ):
179
+ ) -> ResponseReturnValue:
153
180
  x1, y1 = compute_tile(north, west, zoom)
154
181
  x2, y2 = compute_tile(south, east, zoom)
155
182
  tile_bounds = Bounds(x1, y1, x2 + 2, y2 + 2)
@@ -170,11 +197,11 @@ def make_explorer_blueprint(
170
197
  )
171
198
 
172
199
  @blueprint.route(
173
- "/<int:zoom>/<float:north>/<float:east>/<float:south>/<float:west>/missing.<suffix>"
200
+ "/<int:zoom>/<float:north>/<float:east>/<float:south>/<float:west>/explored.<suffix>"
174
201
  )
175
- def missing(
202
+ def download_explored(
176
203
  zoom: int, north: float, east: float, south: float, west: float, suffix: str
177
- ):
204
+ ) -> ResponseReturnValue:
178
205
  x1, y1 = compute_tile(north, west, zoom)
179
206
  x2, y2 = compute_tile(south, east, zoom)
180
207
  tile_bounds = Bounds(x1, y1, x2 + 2, y2 + 2)
@@ -197,7 +224,7 @@ def make_explorer_blueprint(
197
224
  )
198
225
 
199
226
  @blueprint.route("/<int:zoom>/server-side")
200
- def server_side(zoom: int):
227
+ def server_side(zoom: int) -> ResponseReturnValue:
201
228
  if zoom not in config_accessor().explorer_zoom_levels:
202
229
  return {"zoom_level_not_generated": zoom}
203
230
 
@@ -239,7 +266,7 @@ def make_explorer_blueprint(
239
266
  return render_template("explorer/server-side.html.j2", **context)
240
267
 
241
268
  @blueprint.route("/<int:zoom>/tile/<int:z>/<int:x>/<int:y>.png")
242
- def tile(zoom: int, z: int, x: int, y: int) -> Response:
269
+ def tile(zoom: int, z: int, x: int, y: int) -> ResponseReturnValue:
243
270
  tile_visits = tile_visit_accessor.tile_state["tile_visits"][zoom]
244
271
  evolution_state = tile_visit_accessor.tile_state["evolution_state"][zoom]
245
272
 
@@ -248,9 +275,16 @@ def make_explorer_blueprint(
248
275
  square_line_width = 3
249
276
  square_color = np.array([[[228, 26, 28]]]) / 256
250
277
 
251
- match request.args.get("color_strategy", "cluster"):
252
- case "cluster":
253
- color_strategy = ClusterColorStrategy(evolution_state, tile_visits)
278
+ color_strategy_name = request.args.get("color_strategy", "colorful_cluster")
279
+ if color_strategy_name == "default":
280
+ color_strategy_name = config_accessor().cluster_color_strategy
281
+ match color_strategy_name:
282
+ case "max_cluster":
283
+ color_strategy = MaxClusterColorStrategy(evolution_state, tile_visits)
284
+ case "colorful_cluster":
285
+ color_strategy = ColorfulClusterColorStrategy(
286
+ evolution_state, tile_visits
287
+ )
254
288
  case "first":
255
289
  color_strategy = VisitTimeColorStrategy(tile_visits, use_first=True)
256
290
  case "last":