geo-activity-playground 0.42.0__tar.gz → 0.43.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 (157) hide show
  1. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/PKG-INFO +2 -1
  2. {geo_activity_playground-0.42.0/geo_activity_playground/alembic/versions → geo_activity_playground-0.43.0/geo_activity_playground/alembic}/script.py.mako +0 -2
  3. geo_activity_playground-0.43.0/geo_activity_playground/alembic/versions/da2cba03b71d_add_photos.py +40 -0
  4. {geo_activity_playground-0.42.0/geo_activity_playground/alembic → geo_activity_playground-0.43.0/geo_activity_playground/alembic/versions}/script.py.mako +2 -0
  5. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/activities.py +3 -1
  6. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/datamodel.py +48 -22
  7. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/enrichment.py +4 -2
  8. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/meta_search.py +78 -34
  9. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/missing_values.py +5 -2
  10. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/paths.py +2 -0
  11. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/app.py +21 -7
  12. geo_activity_playground-0.43.0/geo_activity_playground/webui/blueprints/photo_blueprint.py +198 -0
  13. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/search_util.py +23 -7
  14. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/activity/show.html.j2 +43 -10
  15. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/eddington/distance.html.j2 +1 -2
  16. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/eddington/elevation_gain.html.j2 +1 -2
  17. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/elevation_eddington/index.html.j2 +18 -15
  18. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/heatmap/index.html.j2 +1 -2
  19. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/page.html.j2 +8 -0
  20. geo_activity_playground-0.43.0/geo_activity_playground/webui/templates/photo/map.html.j2 +45 -0
  21. geo_activity_playground-0.43.0/geo_activity_playground/webui/templates/photo/new.html.j2 +13 -0
  22. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/search/index.html.j2 +1 -2
  23. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/search_form.html.j2 +47 -22
  24. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/summary/index.html.j2 +12 -10
  25. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/pyproject.toml +2 -1
  26. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/LICENSE +0 -0
  27. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/__init__.py +0 -0
  28. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/__main__.py +0 -0
  29. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/README +0 -0
  30. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/env.py +0 -0
  31. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/versions/38882503dc7c_add_tags_to_activities.py +0 -0
  32. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py +0 -0
  33. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py +0 -0
  34. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/versions/93cc82ad1b60_add_parametricplotspec.py +0 -0
  35. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/versions/ab83b9d23127_add_upstream_id.py +0 -0
  36. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/versions/b03491c593f6_add_crop_indices.py +0 -0
  37. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmark_name.py +0 -0
  38. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/__init__.py +0 -0
  39. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/config.py +0 -0
  40. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/coordinates.py +0 -0
  41. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/heart_rate.py +0 -0
  42. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/parametric_plot.py +0 -0
  43. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/privacy_zones.py +0 -0
  44. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/raster_map.py +0 -0
  45. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/similarity.py +0 -0
  46. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/summary_stats.py +0 -0
  47. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/tasks.py +0 -0
  48. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/test_datamodel.py +0 -0
  49. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/test_meta_search.py +0 -0
  50. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/test_missing_values.py +0 -0
  51. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/test_summary_stats.py +0 -0
  52. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/test_tiles.py +0 -0
  53. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/test_time_conversion.py +0 -0
  54. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/tiles.py +0 -0
  55. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/core/time_conversion.py +0 -0
  56. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/explorer/__init__.py +0 -0
  57. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/explorer/grid_file.py +0 -0
  58. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/explorer/tile_visits.py +0 -0
  59. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/explorer/video.py +0 -0
  60. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/heatmap_video.py +0 -0
  61. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/__init__.py +0 -0
  62. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/activity_parsers.py +0 -0
  63. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/csv_parser.py +0 -0
  64. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/directory.py +0 -0
  65. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/strava_api.py +0 -0
  66. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/strava_checkout.py +0 -0
  67. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/test_csv_parser.py +0 -0
  68. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/test_directory.py +0 -0
  69. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/importers/test_strava_api.py +0 -0
  70. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/__init__.py +0 -0
  71. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/authenticator.py +0 -0
  72. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/__init__.py +0 -0
  73. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/activity_blueprint.py +0 -0
  74. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/auth_blueprint.py +0 -0
  75. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +0 -0
  76. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/calendar_blueprint.py +0 -0
  77. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/eddington_blueprints.py +0 -0
  78. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/entry_views.py +0 -0
  79. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/equipment_blueprint.py +0 -0
  80. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/explorer_blueprint.py +0 -0
  81. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/heatmap_blueprint.py +0 -0
  82. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/plot_builder_blueprint.py +0 -0
  83. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/search_blueprint.py +0 -0
  84. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/settings_blueprint.py +0 -0
  85. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/square_planner_blueprint.py +0 -0
  86. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/summary_blueprint.py +0 -0
  87. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/tile_blueprint.py +0 -0
  88. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/blueprints/upload_blueprint.py +0 -0
  89. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/columns.py +0 -0
  90. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/flasher.py +0 -0
  91. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/plot_util.py +0 -0
  92. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +0 -0
  93. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/MarkerCluster.Default.css +0 -0
  94. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/MarkerCluster.css +0 -0
  95. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
  96. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
  97. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
  98. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
  99. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/bootstrap.bundle.min.js +0 -0
  100. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/bootstrap.min.css +0 -0
  101. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
  102. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
  103. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
  104. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
  105. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/favicon.ico +0 -0
  106. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/favicon.svg +0 -0
  107. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/fullscreen.png +0 -0
  108. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
  109. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/images/layers-2x.png +0 -0
  110. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/images/layers.png +0 -0
  111. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/images/marker-icon-2x.png +0 -0
  112. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/images/marker-icon.png +0 -0
  113. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/images/marker-shadow.png +0 -0
  114. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/leaflet.css +0 -0
  115. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/leaflet.fullscreen.css +0 -0
  116. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/leaflet.js +0 -0
  117. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/leaflet.markercluster.js +0 -0
  118. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
  119. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/site.webmanifest +0 -0
  120. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/table-sort.min.js +0 -0
  121. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/vega-embed@6 +0 -0
  122. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/vega-lite@4 +0 -0
  123. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/vega@5 +0 -0
  124. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
  125. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
  126. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/activity/day.html.j2 +0 -0
  127. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/activity/edit.html.j2 +0 -0
  128. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/activity/lines.html.j2 +0 -0
  129. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/activity/name.html.j2 +0 -0
  130. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/activity/trim.html.j2 +0 -0
  131. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/auth/index.html.j2 +0 -0
  132. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +0 -0
  133. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/calendar/index.html.j2 +0 -0
  134. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/calendar/month.html.j2 +0 -0
  135. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/equipment/index.html.j2 +0 -0
  136. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/explorer/index.html.j2 +0 -0
  137. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/explorer/server-side.html.j2 +0 -0
  138. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/home.html.j2 +0 -0
  139. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/plot_builder/edit.html.j2 +0 -0
  140. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/plot_builder/index.html.j2 +0 -0
  141. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/admin-password.html.j2 +0 -0
  142. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/color-schemes.html.j2 +0 -0
  143. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/heart-rate.html.j2 +0 -0
  144. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/index.html.j2 +0 -0
  145. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/manage-equipments.html.j2 +0 -0
  146. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/manage-kinds.html.j2 +0 -0
  147. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/metadata-extraction.html.j2 +0 -0
  148. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/privacy-zones.html.j2 +0 -0
  149. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/segmentation.html.j2 +0 -0
  150. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/sharepic.html.j2 +0 -0
  151. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/strava.html.j2 +0 -0
  152. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/tags-edit.html.j2 +0 -0
  153. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/tags-list.html.j2 +0 -0
  154. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/settings/tags-new.html.j2 +0 -0
  155. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/square_planner/index.html.j2 +0 -0
  156. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.0}/geo_activity_playground/webui/templates/upload/index.html.j2 +0 -0
  157. {geo_activity_playground-0.42.0 → geo_activity_playground-0.43.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: 0.42.0
3
+ Version: 0.43.0
4
4
  Summary: Analysis of geo data activities like rides, runs or hikes.
5
5
  License: MIT
6
6
  Author: Martin Ueding
@@ -18,6 +18,7 @@ Requires-Dist: altair (>=5.5.0,<6.0.0)
18
18
  Requires-Dist: appdirs (>=1.4.4,<2.0.0)
19
19
  Requires-Dist: charset-normalizer (>=3.3.2,<4.0.0)
20
20
  Requires-Dist: coloredlogs (>=15.0.1,<16.0.0)
21
+ Requires-Dist: exifread (>=3.2.0,<4.0.0)
21
22
  Requires-Dist: fitdecode (>=0.10.0,<0.11.0)
22
23
  Requires-Dist: flask (>=3.0.0,<4.0.0)
23
24
  Requires-Dist: flask-alembic (>=3.1.1,<4.0.0)
@@ -13,10 +13,8 @@ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
13
13
 
14
14
 
15
15
  def upgrade() -> None:
16
- """Upgrade schema."""
17
16
  ${upgrades if upgrades else "pass"}
18
17
 
19
18
 
20
19
  def downgrade() -> None:
21
- """Downgrade schema."""
22
20
  ${downgrades if downgrades else "pass"}
@@ -0,0 +1,40 @@
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 = "da2cba03b71d"
10
+ down_revision: Union[str, None] = "38882503dc7c"
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
+ op.create_table(
18
+ "photos",
19
+ sa.Column("id", sa.Integer(), nullable=False),
20
+ sa.Column("filename", sa.String(), nullable=False),
21
+ sa.Column("time", sa.DateTime(), nullable=False),
22
+ sa.Column("latitude", sa.Float(), nullable=False),
23
+ sa.Column("longitude", sa.Float(), nullable=False),
24
+ sa.Column("activity_id", sa.Integer(), nullable=False),
25
+ sa.ForeignKeyConstraint(["activity_id"], ["activities.id"], name="activity_id"),
26
+ sa.PrimaryKeyConstraint("id"),
27
+ )
28
+ with op.batch_alter_table("tags", schema=None) as batch_op:
29
+ batch_op.create_unique_constraint("tags_tag", ["tag"])
30
+
31
+ # ### end Alembic commands ###
32
+
33
+
34
+ def downgrade() -> None:
35
+ # ### commands auto generated by Alembic - please adjust! ###
36
+ with op.batch_alter_table("tags", schema=None) as batch_op:
37
+ batch_op.drop_constraint("tags_tag", type_="unique")
38
+
39
+ op.drop_table("photos")
40
+ # ### end Alembic commands ###
@@ -19,8 +19,10 @@ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
19
 
20
20
 
21
21
  def upgrade() -> None:
22
+ """Upgrade schema."""
22
23
  ${upgrades if upgrades else "pass"}
23
24
 
24
25
 
25
26
  def downgrade() -> None:
27
+ """Downgrade schema."""
26
28
  ${downgrades if downgrades else "pass"}
@@ -24,7 +24,9 @@ logger = logging.getLogger(__name__)
24
24
 
25
25
  class ActivityRepository:
26
26
  def __len__(self) -> int:
27
- return len(self.get_activity_ids())
27
+ return DB.session.scalars(
28
+ sqlalchemy.select(sqlalchemy.func.count()).select_from(Activity)
29
+ ).one()
28
30
 
29
31
  def has_activity(self, activity_id: int) -> bool:
30
32
  return bool(
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  import json
3
3
  import logging
4
+ import pathlib
4
5
  from typing import Any
5
6
  from typing import Optional
6
7
  from typing import TypedDict
@@ -117,6 +118,10 @@ class Activity(DB.Model):
117
118
  secondary=activity_tag_association_table, back_populates="activities"
118
119
  )
119
120
 
121
+ photos: Mapped[list["Photo"]] = relationship(
122
+ back_populates="activity", cascade="all, delete-orphan"
123
+ )
124
+
120
125
  def __str__(self) -> str:
121
126
  return f"{self.start} {self.name}"
122
127
 
@@ -179,7 +184,7 @@ def get_or_make_tag(tag: str) -> Tag:
179
184
  return tag
180
185
 
181
186
 
182
- def query_activity_meta() -> pd.DataFrame:
187
+ def query_activity_meta(clauses: list = []) -> pd.DataFrame:
183
188
  rows = DB.session.execute(
184
189
  sqlalchemy.select(
185
190
  Activity.id,
@@ -206,31 +211,33 @@ def query_activity_meta() -> pd.DataFrame:
206
211
  )
207
212
  .join(Activity.equipment)
208
213
  .join(Activity.kind)
214
+ .where(*clauses)
209
215
  .order_by(Activity.start)
210
216
  ).all()
211
217
  df = pd.DataFrame(rows)
212
218
 
213
- for old, new in [
214
- ("elapsed_time", "average_speed_elapsed_kmh"),
215
- ("moving_time", "average_speed_moving_kmh"),
216
- ]:
217
- df[new] = pd.NA
218
- mask = df[old].dt.total_seconds() > 0
219
- df.loc[mask, new] = df.loc[mask, "distance_km"] / (
220
- df.loc[mask, old].dt.total_seconds() / 3_600
221
- )
222
-
223
- df["date"] = df["start"].dt.date
224
- df["year"] = df["start"].dt.year
225
- df["month"] = df["start"].dt.month
226
- df["day"] = df["start"].dt.day
227
- df["week"] = df["start"].dt.isocalendar().week
228
- df["day_of_week"] = df["start"].dt.day_of_week
229
- df["iso_year"] = df["start"].dt.isocalendar().year
230
- df["hours"] = df["elapsed_time"].dt.total_seconds() / 3_600
231
- df["hours_moving"] = df["moving_time"].dt.total_seconds() / 3_600
232
-
233
- df.index = df["id"]
219
+ if len(df):
220
+ for old, new in [
221
+ ("elapsed_time", "average_speed_elapsed_kmh"),
222
+ ("moving_time", "average_speed_moving_kmh"),
223
+ ]:
224
+ df[new] = pd.NA
225
+ mask = df[old].dt.total_seconds() > 0
226
+ df.loc[mask, new] = df.loc[mask, "distance_km"] / (
227
+ df.loc[mask, old].dt.total_seconds() / 3_600
228
+ )
229
+
230
+ df["date"] = df["start"].dt.date
231
+ df["year"] = df["start"].dt.year
232
+ df["month"] = df["start"].dt.month
233
+ df["day"] = df["start"].dt.day
234
+ df["week"] = df["start"].dt.isocalendar().week
235
+ df["day_of_week"] = df["start"].dt.day_of_week
236
+ df["iso_year"] = df["start"].dt.isocalendar().year
237
+ df["hours"] = df["elapsed_time"].dt.total_seconds() / 3_600
238
+ df["hours_moving"] = df["moving_time"].dt.total_seconds() / 3_600
239
+
240
+ df.index = df["id"]
234
241
 
235
242
  return df
236
243
 
@@ -363,3 +370,22 @@ class PlotSpec(DB.Model):
363
370
  return json.dumps(
364
371
  {key: getattr(self, key) for key in self.FIELDS if getattr(self, key)}
365
372
  )
373
+
374
+
375
+ class Photo(DB.Model):
376
+ __tablename__ = "photos"
377
+ id: Mapped[int] = mapped_column(primary_key=True)
378
+
379
+ filename: Mapped[str] = mapped_column(sa.String, nullable=False)
380
+ time: Mapped[datetime.datetime] = mapped_column(sa.DateTime, nullable=False)
381
+ latitude: Mapped[float] = mapped_column(sa.Float, nullable=False)
382
+ longitude: Mapped[float] = mapped_column(sa.Float, nullable=False)
383
+
384
+ activity_id: Mapped[int] = mapped_column(
385
+ ForeignKey("activities.id", name="activity_id"), nullable=False
386
+ )
387
+ activity: Mapped["Activity"] = relationship(back_populates="photos")
388
+
389
+ @property
390
+ def path(self) -> pathlib.Path:
391
+ return pathlib.Path(self.filename)
@@ -108,8 +108,10 @@ def populate_database_from_extracted(config: Config) -> None:
108
108
  def update_via_time_series(
109
109
  activity: Activity, time_series: pd.DataFrame
110
110
  ) -> ActivityMeta:
111
- activity.start = time_series["time"].iloc[0]
112
- activity.elapsed_time = time_series["time"].iloc[-1] - time_series["time"].iloc[0]
111
+ activity.start = some(time_series["time"].iloc[0])
112
+ activity.elapsed_time = some(
113
+ time_series["time"].iloc[-1] - time_series["time"].iloc[0]
114
+ )
113
115
  activity.distance_km = (
114
116
  time_series["distance_km"].iloc[-1] - time_series["distance_km"].iloc[0]
115
117
  )
@@ -2,21 +2,33 @@ import dataclasses
2
2
  import datetime
3
3
  import re
4
4
  import urllib.parse
5
+ from collections.abc import Sequence
5
6
  from typing import Optional
6
7
 
7
8
  import dateutil.parser
8
9
  import numpy as np
9
10
  import pandas as pd
11
+ import sqlalchemy
12
+
13
+ from .datamodel import Activity
14
+ from .datamodel import DB
15
+ from .datamodel import Equipment
16
+ from .datamodel import Kind
17
+ from .datamodel import query_activity_meta
18
+ from .datamodel import Tag
10
19
 
11
20
 
12
21
  @dataclasses.dataclass
13
22
  class SearchQuery:
14
- equipment: list[str] = dataclasses.field(default_factory=list)
15
- kind: list[str] = dataclasses.field(default_factory=list)
23
+ equipment: list[Equipment] = dataclasses.field(default_factory=list)
24
+ kind: list[Kind] = dataclasses.field(default_factory=list)
25
+ tag: list[Tag] = dataclasses.field(default_factory=list)
16
26
  name: Optional[str] = None
17
27
  name_case_sensitive: bool = False
18
28
  start_begin: Optional[datetime.date] = None
19
29
  start_end: Optional[datetime.date] = None
30
+ distance_km_min: Optional[float] = None
31
+ distance_km_max: Optional[float] = None
20
32
 
21
33
  def __str__(self) -> str:
22
34
  bits = []
@@ -25,10 +37,14 @@ class SearchQuery:
25
37
  if self.equipment:
26
38
  bits.append(
27
39
  "equipment is "
28
- + (" or ".join(f"“{equipment}”" for equipment in self.equipment))
40
+ + (" or ".join(f"“{equipment.name}”" for equipment in self.equipment))
29
41
  )
30
42
  if self.kind:
31
- bits.append("kind is " + (" or ".join(f"“{kind}”" for kind in self.kind)))
43
+ bits.append(
44
+ "kind is " + (" or ".join(f"“{kind.name}”" for kind in self.kind))
45
+ )
46
+ if self.tag:
47
+ bits.append("tag is " + (" or ".join(f"“{tag.tag}”" for tag in self.tag)))
32
48
  if self.start_begin:
33
49
  bits.append(f"after “{self.start_begin.isoformat()}”")
34
50
  if self.start_end:
@@ -43,27 +59,38 @@ class SearchQuery:
43
59
  or self.name
44
60
  or self.start_begin
45
61
  or self.start_end
62
+ or self.tag
63
+ or self.distance_km_min
64
+ or self.distance_km_max
46
65
  )
47
66
 
48
67
  def to_primitives(self) -> dict:
49
68
  return {
50
- "equipment": self.equipment,
51
- "kind": self.kind,
69
+ "equipment": [equipment.id for equipment in self.equipment],
70
+ "kind": [kind.id for kind in self.kind],
71
+ "tag": [tag.id for tag in self.tag],
52
72
  "name": self.name or "",
53
73
  "name_case_sensitive": self.name_case_sensitive,
54
74
  "start_begin": _format_optional_date(self.start_begin),
55
75
  "start_end": _format_optional_date(self.start_end),
76
+ "distance_km_min": self.distance_km_min,
77
+ "distance_km_max": self.distance_km_max,
56
78
  }
57
79
 
58
80
  @classmethod
59
81
  def from_primitives(cls, d: dict) -> "SearchQuery":
60
82
  return cls(
61
- equipment=d.get("equipment", []),
62
- kind=d.get("kind", []),
83
+ equipment=[
84
+ DB.session.get_one(Equipment, id) for id in d.get("equipment", [])
85
+ ],
86
+ kind=[DB.session.get_one(Kind, id) for id in d.get("kind", [])],
87
+ tag=[DB.session.get_one(Tag, id) for id in d.get("tag", [])],
63
88
  name=d.get("name", None),
64
89
  name_case_sensitive=d.get("name_case_sensitive", False),
65
90
  start_begin=_parse_date_or_none(d.get("start_begin", None)),
66
91
  start_end=_parse_date_or_none(d.get("start_end", None)),
92
+ distance_km_min=d.get("distance_km_min", None),
93
+ distance_km_max=d.get("distance_km_max", None),
67
94
  )
68
95
 
69
96
  def to_jinja(self) -> dict:
@@ -74,9 +101,11 @@ class SearchQuery:
74
101
  def to_url_str(self) -> str:
75
102
  variables = []
76
103
  for equipment in self.equipment:
77
- variables.append(("equipment", equipment))
104
+ variables.append(("equipment", equipment.id))
78
105
  for kind in self.kind:
79
- variables.append(("kind", kind))
106
+ variables.append(("kind", kind.id))
107
+ for tag in self.tag:
108
+ variables.append(("tag", tag.id))
80
109
  if self.name:
81
110
  variables.append(("name", self.name))
82
111
  if self.name_case_sensitive:
@@ -85,6 +114,10 @@ class SearchQuery:
85
114
  variables.append(("start_begin", self.start_begin.isoformat()))
86
115
  if self.start_end:
87
116
  variables.append(("start_end", self.start_end.isoformat()))
117
+ if self.distance_km_min:
118
+ variables.append(("distance_km_min", self.distance_km_min))
119
+ if self.distance_km_max:
120
+ variables.append(("distance_km_max", self.distance_km_max))
88
121
 
89
122
  return "&".join(
90
123
  f"{key}={urllib.parse.quote_plus(value)}" for key, value in variables
@@ -94,36 +127,47 @@ class SearchQuery:
94
127
  def apply_search_query(
95
128
  activity_meta: pd.DataFrame, search_query: SearchQuery
96
129
  ) -> pd.DataFrame:
97
- mask = _make_mask(activity_meta.index, True)
130
+
131
+ filter_clauses = []
98
132
 
99
133
  if search_query.equipment:
100
- mask &= _filter_column(activity_meta["equipment"], search_query.equipment)
134
+ filter_clauses.append(
135
+ sqlalchemy.or_(
136
+ *[
137
+ Activity.equipment == equipment
138
+ for equipment in search_query.equipment
139
+ ]
140
+ )
141
+ )
142
+
101
143
  if search_query.kind:
102
- mask &= _filter_column(activity_meta["kind"], search_query.kind)
103
- if search_query.name:
104
- mask &= pd.Series(
105
- [
106
- bool(
107
- re.search(
108
- search_query.name,
109
- activity_name,
110
- 0 if search_query.name_case_sensitive else re.IGNORECASE,
111
- )
112
- )
113
- for activity_name in activity_meta["name"]
114
- ],
115
- index=activity_meta.index,
144
+ filter_clauses.append(
145
+ sqlalchemy.or_(*[Activity.kind == kind for kind in search_query.kind])
146
+ )
147
+
148
+ if search_query.tag:
149
+ filter_clauses.append(
150
+ sqlalchemy.or_(*[Activity.tags.contains(tag) for tag in search_query.tag])
116
151
  )
117
- if search_query.start_begin is not None:
118
- start_begin = datetime.datetime.combine(
119
- search_query.start_begin, datetime.time.min
152
+
153
+ if search_query.name:
154
+ filter_clauses.append(
155
+ Activity.name.contains(search_query.name)
156
+ if search_query.name_case_sensitive
157
+ else Activity.name.icontains(search_query.name)
120
158
  )
121
- mask &= start_begin <= activity_meta["start"]
122
- if search_query.start_end is not None:
123
- start_end = datetime.datetime.combine(search_query.start_end, datetime.time.max)
124
- mask &= activity_meta["start"] <= start_end
125
159
 
126
- return activity_meta.loc[mask]
160
+ if search_query.start_begin:
161
+ filter_clauses.append(Activity.start <= search_query.start_begin)
162
+ if search_query.start_end:
163
+ filter_clauses.append(Activity.start < search_query.start_end)
164
+
165
+ if search_query.distance_km_min:
166
+ filter_clauses.append(Activity.distance_km >= search_query.distance_km_min)
167
+ if search_query.distance_km_max:
168
+ filter_clauses.append(Activity.distance_km <= search_query.distance_km_max)
169
+
170
+ return query_activity_meta(filter_clauses)
127
171
 
128
172
 
129
173
  def _format_optional_date(date: Optional[datetime.date]) -> str:
@@ -1,13 +1,16 @@
1
+ from typing import Any
1
2
  from typing import Optional
2
- from typing import Union
3
3
 
4
4
  import numpy as np
5
+ import pandas as pd
5
6
 
6
7
 
7
- def some(value) -> Optional[Union[float, int]]:
8
+ def some(value: Any) -> Optional[Any]:
8
9
  if value is None:
9
10
  return None
10
11
  elif np.isnan(value):
11
12
  return None
13
+ elif pd.isna(value):
14
+ return None
12
15
  else:
13
16
  return value
@@ -53,6 +53,7 @@ _strava_last_activity_date_path = _cache_dir / "strava-last-activity-date.json"
53
53
  _new_config_file = pathlib.Path("config.json")
54
54
  _activity_meta_override_dir = pathlib.Path("Metadata Override")
55
55
  _time_series_dir = pathlib.Path("Time Series")
56
+ _photos_dir = pathlib.Path("Photos")
56
57
 
57
58
 
58
59
  cache_dir = dir_wrapper(_cache_dir)
@@ -65,6 +66,7 @@ tiles_per_time_series = dir_wrapper(_tiles_per_time_series)
65
66
  strava_api_dir = dir_wrapper(_strava_api_dir)
66
67
  activity_meta_override_dir = dir_wrapper(_activity_meta_override_dir)
67
68
  time_series_dir = dir_wrapper(_time_series_dir)
69
+ PHOTOS_DIR = dir_wrapper(_photos_dir)
68
70
 
69
71
  activities_file = file_wrapper(_activities_file)
70
72
  strava_dynamic_config_path = file_wrapper(_strava_dynamic_config_path)
@@ -8,6 +8,7 @@ import secrets
8
8
  import shutil
9
9
  import urllib.parse
10
10
 
11
+ import sqlalchemy
11
12
  from flask import Flask
12
13
  from flask import request
13
14
  from flask_alembic import Alembic
@@ -17,6 +18,10 @@ from ..core.config import ConfigAccessor
17
18
  from ..core.config import import_old_config
18
19
  from ..core.config import import_old_strava_config
19
20
  from ..core.datamodel import DB
21
+ from ..core.datamodel import Equipment
22
+ from ..core.datamodel import Kind
23
+ from ..core.datamodel import Photo
24
+ from ..core.datamodel import Tag
20
25
  from ..core.heart_rate import HeartRateZoneComputer
21
26
  from ..core.raster_map import GrayscaleImageTransform
22
27
  from ..core.raster_map import IdentityImageTransform
@@ -33,6 +38,7 @@ from .blueprints.entry_views import register_entry_views
33
38
  from .blueprints.equipment_blueprint import make_equipment_blueprint
34
39
  from .blueprints.explorer_blueprint import make_explorer_blueprint
35
40
  from .blueprints.heatmap_blueprint import make_heatmap_blueprint
41
+ from .blueprints.photo_blueprint import make_photo_blueprint
36
42
  from .blueprints.plot_builder_blueprint import make_plot_builder_blueprint
37
43
  from .blueprints.search_blueprint import make_search_blueprint
38
44
  from .blueprints.settings_blueprint import make_settings_blueprint
@@ -146,6 +152,7 @@ def web_ui_main(
146
152
  "/heatmap": make_heatmap_blueprint(
147
153
  repository, tile_visit_accessor, config_accessor(), search_query_history
148
154
  ),
155
+ "/photo": make_photo_blueprint(config_accessor, authenticator, flasher),
149
156
  "/plot-builder": make_plot_builder_blueprint(
150
157
  repository, flasher, authenticator
151
158
  ),
@@ -178,15 +185,22 @@ def web_ui_main(
178
185
  "version": _try_get_version(),
179
186
  "num_activities": len(repository),
180
187
  "map_tile_attribution": config_accessor().map_tile_attribution,
181
- "search_query_favorites": search_query_history.prepare_favorites(),
182
- "search_query_last": search_query_history.prepare_last(),
188
+ # "search_query_favorites": search_query_history.prepare_favorites(),
189
+ # "search_query_last": search_query_history.prepare_last(),
183
190
  "request_url": urllib.parse.quote_plus(request.url),
184
191
  }
185
- if len(repository):
186
- variables["equipments_avail"] = sorted(
187
- repository.meta["equipment"].unique()
188
- )
189
- variables["kinds_avail"] = sorted(repository.meta["kind"].unique())
192
+ variables["equipments_avail"] = DB.session.scalars(
193
+ sqlalchemy.select(Equipment).order_by(Equipment.name)
194
+ ).all()
195
+ variables["kinds_avail"] = DB.session.scalars(
196
+ sqlalchemy.select(Kind).order_by(Kind.name)
197
+ ).all()
198
+ variables["tags_avail"] = DB.session.scalars(
199
+ sqlalchemy.select(Tag).order_by(Tag.tag)
200
+ ).all()
201
+ variables["photo_count"] = DB.session.scalar(
202
+ sqlalchemy.select(sqlalchemy.func.count()).select_from(Photo)
203
+ )
190
204
  return variables
191
205
 
192
206
  app.run(host=host, port=port)