geo-activity-playground 1.5.2__tar.gz → 1.7.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 (181) hide show
  1. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/PKG-INFO +2 -1
  2. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/__main__.py +8 -0
  3. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/datamodel.py +28 -9
  4. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/enrichment.py +2 -4
  5. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/meta_search.py +1 -3
  6. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/photos.py +7 -3
  7. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_meta_search.py +4 -4
  8. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_time_zone_from_location.py +6 -0
  9. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/time_conversion.py +7 -0
  10. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/explorer/tile_visits.py +23 -10
  11. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/activity_parsers.py +15 -9
  12. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/directory.py +16 -1
  13. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/strava_api.py +24 -6
  14. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/strava_checkout.py +2 -0
  15. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/app.py +18 -3
  16. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/eddington_blueprints.py +1 -5
  17. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/hall_of_fame_blueprint.py +1 -1
  18. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/heatmap_blueprint.py +1 -1
  19. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/photo_blueprint.py +1 -0
  20. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/search_blueprint.py +1 -1
  21. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/summary_blueprint.py +1 -1
  22. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/upload_blueprint.py +6 -1
  23. geo_activity_playground-1.7.0/geo_activity_playground/webui/static/activity-trim.js +39 -0
  24. geo_activity_playground-1.7.0/geo_activity_playground/webui/templates/activity/trim.html.j2 +33 -0
  25. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/pyproject.toml +2 -1
  26. geo_activity_playground-1.5.2/geo_activity_playground/webui/templates/activity/trim.html.j2 +0 -68
  27. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/LICENSE +0 -0
  28. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/__init__.py +0 -0
  29. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/README +0 -0
  30. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/env.py +0 -0
  31. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/script.py.mako +0 -0
  32. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/0f02b92c4f94_add_tag_color.py +0 -0
  33. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/38882503dc7c_add_tags_to_activities.py +0 -0
  34. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py +0 -0
  35. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py +0 -0
  36. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/85fe0348e8a2_add_time_series_uuid_field.py +0 -0
  37. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/93cc82ad1b60_add_parametricplotspec.py +0 -0
  38. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/ab83b9d23127_add_upstream_id.py +0 -0
  39. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/b03491c593f6_add_crop_indices.py +0 -0
  40. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/da2cba03b71d_add_photos.py +0 -0
  41. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/dc8073871da7_add_plotspec_group_by.py +0 -0
  42. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmark_name.py +0 -0
  43. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/f2f50843be2d_make_all_fields_in_activity_nullable.py +0 -0
  44. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/alembic/versions/script.py.mako +0 -0
  45. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/__init__.py +0 -0
  46. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/activities.py +0 -0
  47. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/config.py +0 -0
  48. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/coordinates.py +0 -0
  49. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/copernicus_dem.py +0 -0
  50. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/export.py +0 -0
  51. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/heart_rate.py +0 -0
  52. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/missing_values.py +0 -0
  53. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/parametric_plot.py +0 -0
  54. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/paths.py +0 -0
  55. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/privacy_zones.py +0 -0
  56. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/raster_map.py +0 -0
  57. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/similarity.py +0 -0
  58. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/summary_stats.py +0 -0
  59. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/tasks.py +0 -0
  60. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_datamodel.py +0 -0
  61. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_missing_values.py +0 -0
  62. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_pandas_timezone.py +0 -0
  63. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_summary_stats.py +0 -0
  64. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_tiles.py +0 -0
  65. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_time_zone_import.py +0 -0
  66. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/test_timezone_sqlalchemy.py +0 -0
  67. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/core/tiles.py +0 -0
  68. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/explorer/__init__.py +0 -0
  69. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/explorer/grid_file.py +0 -0
  70. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/explorer/video.py +0 -0
  71. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/heatmap_video.py +0 -0
  72. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/__init__.py +0 -0
  73. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/csv_parser.py +0 -0
  74. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/test_csv_parser.py +0 -0
  75. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/test_directory.py +0 -0
  76. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/importers/test_strava_api.py +0 -0
  77. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/__init__.py +0 -0
  78. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/authenticator.py +0 -0
  79. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/__init__.py +0 -0
  80. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/activity_blueprint.py +0 -0
  81. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/auth_blueprint.py +0 -0
  82. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +0 -0
  83. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/calendar_blueprint.py +0 -0
  84. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/entry_views.py +0 -0
  85. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/equipment_blueprint.py +0 -0
  86. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/explorer_blueprint.py +0 -0
  87. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/export_blueprint.py +0 -0
  88. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/plot_builder_blueprint.py +0 -0
  89. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/settings_blueprint.py +0 -0
  90. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/square_planner_blueprint.py +0 -0
  91. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/tile_blueprint.py +0 -0
  92. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/blueprints/time_zone_fixer_blueprint.py +0 -0
  93. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/columns.py +0 -0
  94. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/flasher.py +0 -0
  95. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/plot_util.py +0 -0
  96. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/search_util.py +0 -0
  97. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/bootstrap/bootstrap-dark-mode.js +0 -0
  98. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/bootstrap/bootstrap.bundle.min.js +0 -0
  99. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/bootstrap/bootstrap.min.css +0 -0
  100. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/android-chrome-192x192.png +0 -0
  101. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/android-chrome-512x512.png +0 -0
  102. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/apple-touch-icon.png +0 -0
  103. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/browserconfig.xml +0 -0
  104. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/favicon-16x16.png +0 -0
  105. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/favicon-32x32.png +0 -0
  106. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/favicon-48x48.png +0 -0
  107. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/favicon.ico +0 -0
  108. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/favicon.svg +0 -0
  109. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/mstile-150x150.png +0 -0
  110. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/site.webmanifest +0 -0
  111. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/web-app-manifest-192x192.png +0 -0
  112. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/favicons/web-app-manifest-512x512.png +0 -0
  113. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/images/layers-2x.png +0 -0
  114. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/images/layers.png +0 -0
  115. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/images/marker-icon-2x.png +0 -0
  116. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/images/marker-icon.png +0 -0
  117. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/images/marker-shadow.png +0 -0
  118. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/Leaflet.fullscreen.min.js +0 -0
  119. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/MarkerCluster.Default.css +0 -0
  120. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/MarkerCluster.css +0 -0
  121. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/fullscreen.png +0 -0
  122. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/fullscreen@2x.png +0 -0
  123. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/images/layers-2x.png +0 -0
  124. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/images/layers.png +0 -0
  125. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/leaflet.css +0 -0
  126. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/leaflet.fullscreen.css +0 -0
  127. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/leaflet.js +0 -0
  128. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/leaflet/leaflet.markercluster.js +0 -0
  129. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/server-side-explorer.js +0 -0
  130. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/table-sort.min.js +0 -0
  131. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/vega/vega-embed@6.js +0 -0
  132. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/vega/vega-lite@4.js +0 -0
  133. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/static/vega/vega@5.js +0 -0
  134. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/activity/day.html.j2 +0 -0
  135. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/activity/edit.html.j2 +0 -0
  136. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/activity/lines.html.j2 +0 -0
  137. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/activity/name.html.j2 +0 -0
  138. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/activity/show.html.j2 +0 -0
  139. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/auth/index.html.j2 +0 -0
  140. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +0 -0
  141. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/calendar/index.html.j2 +0 -0
  142. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/calendar/month.html.j2 +0 -0
  143. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/eddington/distance.html.j2 +0 -0
  144. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/eddington/elevation_gain.html.j2 +0 -0
  145. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/elevation_eddington/index.html.j2 +0 -0
  146. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/equipment/index.html.j2 +0 -0
  147. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/explorer/server-side.html.j2 +0 -0
  148. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/export/index.html.j2 +0 -0
  149. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/hall_of_fame/index.html.j2 +0 -0
  150. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/heatmap/index.html.j2 +0 -0
  151. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/home.html.j2 +0 -0
  152. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/page.html.j2 +0 -0
  153. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/photo/map.html.j2 +0 -0
  154. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/photo/new.html.j2 +0 -0
  155. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/plot-macros.html.j2 +0 -0
  156. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/plot_builder/edit.html.j2 +0 -0
  157. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/plot_builder/import-spec.html.j2 +0 -0
  158. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/plot_builder/index.html.j2 +0 -0
  159. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/search/index.html.j2 +0 -0
  160. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/search_form.html.j2 +0 -0
  161. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/admin-password.html.j2 +0 -0
  162. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/color-schemes.html.j2 +0 -0
  163. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/heart-rate.html.j2 +0 -0
  164. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/index.html.j2 +0 -0
  165. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/manage-equipments.html.j2 +0 -0
  166. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/manage-kinds.html.j2 +0 -0
  167. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/metadata-extraction.html.j2 +0 -0
  168. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/privacy-zones.html.j2 +0 -0
  169. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/segmentation.html.j2 +0 -0
  170. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/sharepic.html.j2 +0 -0
  171. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/strava.html.j2 +0 -0
  172. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/tags-edit.html.j2 +0 -0
  173. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/tags-list.html.j2 +0 -0
  174. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/tags-new.html.j2 +0 -0
  175. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/settings/tile-source.html.j2 +0 -0
  176. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/square_planner/index.html.j2 +0 -0
  177. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/summary/index.html.j2 +0 -0
  178. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/summary/vega-chart.html.j2 +0 -0
  179. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/time_zone_fixer/index.html.j2 +0 -0
  180. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.0}/geo_activity_playground/webui/templates/upload/index.html.j2 +0 -0
  181. {geo_activity_playground-1.5.2 → geo_activity_playground-1.7.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.5.2
3
+ Version: 1.7.0
4
4
  Summary: Analysis of geo data activities like rides, runs or hikes.
5
5
  License: MIT
6
6
  Author: Martin Ueding
@@ -37,6 +37,7 @@ Requires-Dist: shapely (>=2.0.5,<3.0.0)
37
37
  Requires-Dist: sqlalchemy (>=2.0.40,<3.0.0)
38
38
  Requires-Dist: stravalib (>=2.0,<3.0)
39
39
  Requires-Dist: tcxreader (>=0.4.5,<0.5.0)
40
+ Requires-Dist: timezonefinder (>=6.5.9,<7.0.0)
40
41
  Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
41
42
  Requires-Dist: tqdm (>=4.64.0,<5.0.0)
42
43
  Requires-Dist: vegafusion-python-embed (>=1.4.3,<2.0.0)
@@ -53,6 +53,8 @@ def main() -> None:
53
53
  options.skip_reload,
54
54
  host=options.host,
55
55
  port=options.port,
56
+ strava_begin=options.strava_begin,
57
+ strava_end=options.strava_end,
56
58
  )
57
59
  )
58
60
  subparser.add_argument(
@@ -62,6 +64,12 @@ def main() -> None:
62
64
  "--port", default=5000, type=int, help="the port to run listen on"
63
65
  )
64
66
  subparser.add_argument("--skip-reload", action=argparse.BooleanOptionalAction)
67
+ subparser.add_argument(
68
+ "--strava-begin", help="Start date to limit Strava sync, format YYYY-MM-DD"
69
+ )
70
+ subparser.add_argument(
71
+ "--strava-end", help="End date to limit Strava sync, format YYYY-MM-DD"
72
+ )
65
73
 
66
74
  subparser = subparsers.add_parser(
67
75
  "heatmap-video", help="Create a video with the evolution of the heatmap"
@@ -231,7 +231,7 @@ class Activity(DB.Model):
231
231
  return self.start
232
232
 
233
233
  @property
234
- def start_utc(self) -> Optional[datetime.datetime]:
234
+ def start_local(self) -> Optional[datetime.datetime]:
235
235
  if self.start:
236
236
  return self.start.replace(microsecond=0, tzinfo=zoneinfo.ZoneInfo("UTC"))
237
237
  else:
@@ -270,6 +270,7 @@ def query_activity_meta(clauses: list = []) -> pd.DataFrame:
270
270
  Activity.path,
271
271
  Activity.distance_km,
272
272
  Activity.start,
273
+ Activity.iana_timezone,
273
274
  Activity.elapsed_time,
274
275
  Activity.moving_time,
275
276
  Activity.start_latitude,
@@ -302,6 +303,24 @@ def query_activity_meta(clauses: list = []) -> pd.DataFrame:
302
303
  # df["start"] = pd.Series(start)
303
304
  df["elapsed_time"] = pd.to_timedelta(df["elapsed_time"])
304
305
 
306
+ start_local = []
307
+ for start, iana_timezone in zip(df["start"], df["iana_timezone"]):
308
+ if pd.isna(start) or iana_timezone is None:
309
+ start_local.append(start)
310
+ else:
311
+ start_local.append(
312
+ start.tz_localize(zoneinfo.ZoneInfo("UTC"))
313
+ .tz_convert(iana_timezone)
314
+ .tz_localize(None)
315
+ )
316
+ df["start_local"] = start_local
317
+
318
+ # Work around bytes stored in DB.
319
+ df["calories"] = [
320
+ sum(a * 256**b for b, a in enumerate(c)) if isinstance(c, bytes) else c
321
+ for c in df["calories"]
322
+ ]
323
+
305
324
  for old, new in [
306
325
  ("elapsed_time", "average_speed_elapsed_kmh"),
307
326
  ("moving_time", "average_speed_moving_kmh"),
@@ -312,14 +331,14 @@ def query_activity_meta(clauses: list = []) -> pd.DataFrame:
312
331
  df.loc[mask, old].dt.total_seconds() / 3_600
313
332
  )
314
333
 
315
- df["date"] = df["start"].dt.date
316
- df["year"] = df["start"].dt.year
317
- df["month"] = df["start"].dt.month
318
- df["day"] = df["start"].dt.day
319
- df["week"] = df["start"].dt.isocalendar().week
320
- df["day_of_week"] = df["start"].dt.day_of_week
321
- df["iso_year"] = df["start"].dt.isocalendar().year
322
- df["iso_day"] = df["start"].dt.isocalendar().day
334
+ df["date"] = df["start_local"].dt.date
335
+ df["year"] = df["start_local"].dt.year
336
+ df["month"] = df["start_local"].dt.month
337
+ df["day"] = df["start_local"].dt.day
338
+ df["week"] = df["start_local"].dt.isocalendar().week
339
+ df["day_of_week"] = df["start_local"].dt.day_of_week
340
+ df["iso_year"] = df["start_local"].dt.isocalendar().year
341
+ df["iso_day"] = df["start_local"].dt.isocalendar().day
323
342
  df["hours"] = df["elapsed_time"].dt.total_seconds() / 3_600
324
343
  df["hours_moving"] = df["moving_time"].dt.total_seconds() / 3_600
325
344
  df["iso_year_week"] = [
@@ -13,7 +13,7 @@ from .datamodel import Activity
13
13
  from .datamodel import DB
14
14
  from .missing_values import some
15
15
  from .tiles import compute_tile_float
16
- from .time_conversion import get_country_timezone
16
+ from .time_conversion import get_timezone
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
@@ -26,9 +26,7 @@ def enrichment_set_timezone(
26
26
  ), f"You cannot import an activity without points. {activity=}"
27
27
  latitude, longitude = time_series[["latitude", "longitude"]].iloc[0].to_list()
28
28
  if activity.iana_timezone is None or activity.start_country is None:
29
- country, tz_str = get_country_timezone(latitude, longitude)
30
- activity.iana_timezone = tz_str
31
- activity.start_country = country
29
+ activity.iana_timezone = get_timezone(latitude, longitude)
32
30
  return True
33
31
  else:
34
32
  return False
@@ -124,9 +124,7 @@ class SearchQuery:
124
124
  )
125
125
 
126
126
 
127
- def apply_search_query(
128
- activity_meta: pd.DataFrame, search_query: SearchQuery
129
- ) -> pd.DataFrame:
127
+ def apply_search_query(search_query: SearchQuery) -> pd.DataFrame:
130
128
 
131
129
  filter_clauses = []
132
130
 
@@ -23,10 +23,14 @@ def get_metadata_from_image(path: pathlib.Path) -> dict:
23
23
  except KeyError:
24
24
  pass
25
25
  try:
26
- metadata["time"] = dateutil.parser.parse(
27
- str(tags["EXIF DateTimeOriginal"])
28
- + str(tags.get("EXIF OffsetTime", "+00:00"))
26
+ date_time_original = str(tags["EXIF DateTimeOriginal"]) + str(
27
+ tags.get("EXIF OffsetTime", "+00:00")
28
+ ).replace(":", "")
29
+ print(f"Extracted date: {date_time_original}")
30
+ metadata["time"] = datetime.datetime.strptime(
31
+ date_time_original, "%Y:%m:%d %H:%M:%S%z"
29
32
  ).astimezone(zoneinfo.ZoneInfo("UTC"))
33
+
30
34
  except KeyError:
31
35
  pass
32
36
 
@@ -24,7 +24,7 @@ def test_empty_query() -> None:
24
24
 
25
25
  search_query = SearchQuery()
26
26
 
27
- actual = apply_search_query(activity_meta, search_query)
27
+ actual = apply_search_query(search_query)
28
28
  assert (actual["id"] == activity_meta["id"]).all()
29
29
 
30
30
 
@@ -43,7 +43,7 @@ def test_equipment_query() -> None:
43
43
  }
44
44
  )
45
45
  search_query = SearchQuery(equipment=["B"])
46
- actual = apply_search_query(activity_meta, search_query)
46
+ actual = apply_search_query(search_query)
47
47
  assert set(actual["id"]) == {2, 3}
48
48
 
49
49
 
@@ -62,7 +62,7 @@ def test_date_query() -> None:
62
62
  }
63
63
  )
64
64
  search_query = SearchQuery(start_begin=datetime.date(2024, 12, 31))
65
- actual = apply_search_query(activity_meta, search_query)
65
+ actual = apply_search_query(search_query)
66
66
  assert set(actual["id"]) == {2}
67
67
 
68
68
 
@@ -81,7 +81,7 @@ def test_name_query() -> None:
81
81
  }
82
82
  )
83
83
  search_query = SearchQuery(name="Test1")
84
- actual = apply_search_query(activity_meta, search_query)
84
+ actual = apply_search_query(search_query)
85
85
  assert set(actual["id"]) == {1}
86
86
 
87
87
 
@@ -1,7 +1,13 @@
1
1
  from .time_conversion import get_country_timezone
2
+ from .time_conversion import get_timezone
2
3
 
3
4
 
4
5
  def test_time_zone_from_location() -> None:
5
6
  country, iana_timezone = get_country_timezone(50, 7)
6
7
  assert country == "Germany"
7
8
  assert iana_timezone == "Europe/Berlin"
9
+
10
+
11
+ def test_timezone_finder() -> None:
12
+ iana_timezone = get_timezone(50, 7)
13
+ assert iana_timezone == "Europe/Berlin"
@@ -2,8 +2,10 @@ import datetime
2
2
  import json
3
3
  import logging
4
4
  import zoneinfo
5
+ from typing import Optional
5
6
 
6
7
  import requests
8
+ import timezonefinder
7
9
 
8
10
  from .paths import USER_CACHE_DIR
9
11
 
@@ -40,3 +42,8 @@ def get_country_timezone(latitude: float, longitude: float) -> tuple[str, str]:
40
42
  with open(cache_file, "w") as f:
41
43
  json.dump(data, f)
42
44
  return data["location"], data["iana_timezone"]
45
+
46
+
47
+ def get_timezone(latitude: float, longitude: float) -> Optional[str]:
48
+ tf = timezonefinder.TimezoneFinder() # reuse
49
+ return tf.timezone_at(lng=longitude, lat=latitude)
@@ -4,6 +4,7 @@ import itertools
4
4
  import logging
5
5
  import pathlib
6
6
  import pickle
7
+ import zoneinfo
7
8
  from typing import Iterator
8
9
  from typing import Optional
9
10
  from typing import TypedDict
@@ -184,10 +185,13 @@ def compute_tile_visits_new(
184
185
 
185
186
  for zoom in reversed(range(20)):
186
187
  tile_state = tile_visit_accessor.tile_state
187
- if not (
188
- tile_state["tile_history"][zoom]["time"].diff().dropna()
189
- >= datetime.timedelta(seconds=0)
190
- ).all():
188
+ if (
189
+ len(tile_state["tile_history"][zoom])
190
+ and not (
191
+ tile_state["tile_history"][zoom]["time"].dropna().diff().dropna()
192
+ >= datetime.timedelta(seconds=0)
193
+ ).all()
194
+ ):
191
195
  logger.warning(
192
196
  f"The order of the tile history at {zoom=} is not chronological, resetting."
193
197
  )
@@ -250,12 +254,17 @@ def _process_activity(
250
254
 
251
255
  first_time = tile_visit.get("first_time", None)
252
256
  last_time = tile_visit.get("last_time", None)
253
- if first_time is None or time < first_time:
254
- tile_visit["first_id"] = activity_id
255
- tile_visit["first_time"] = time
256
- if last_time is None or time > last_time:
257
- tile_visit["last_id"] = activity_id
258
- tile_visit["last_time"] = time
257
+ try:
258
+ if first_time is None or time < first_time:
259
+ tile_visit["first_id"] = activity_id
260
+ tile_visit["first_time"] = time
261
+ if last_time is None or time > last_time:
262
+ tile_visit["last_id"] = activity_id
263
+ tile_visit["last_time"] = time
264
+ except TypeError as e:
265
+ raise TypeError(
266
+ f"Mismatch in timezone awareness: {time=}, {first_time=}, {last_time=}"
267
+ ) from e
259
268
 
260
269
  activities_per_tile[tile].add(activity_id)
261
270
 
@@ -272,6 +281,10 @@ def _process_activity(
272
281
  def _tiles_from_points(
273
282
  time_series: pd.DataFrame, zoom: int
274
283
  ) -> Iterator[tuple[datetime.datetime, int, int]]:
284
+ # XXX Some people haven't localized their time series yet. This breaks the tile history part. Just assume that it is UTC, should be good enough for tiles.
285
+ if time_series["time"].dt.tz is None:
286
+ time_series = time_series.copy()
287
+ time_series["time"].dt.tz_localize(zoneinfo.ZoneInfo("UTC"))
275
288
  xf = time_series["x"] * 2**zoom
276
289
  yf = time_series["y"] * 2**zoom
277
290
  for t1, x1, y1, x2, y2, s1, s2 in zip(
@@ -4,6 +4,7 @@ import logging
4
4
  import pathlib
5
5
  import xml
6
6
  from collections.abc import Iterator
7
+ from typing import Union
7
8
 
8
9
  import charset_normalizer
9
10
  import dateutil.parser
@@ -127,7 +128,9 @@ def read_fit_activity(path: pathlib.Path, open) -> tuple[Activity, pd.DataFrame]
127
128
  if "altitude" in fields:
128
129
  row["elevation"] = values["altitude"]
129
130
  if "enhanced_altitude" in fields:
130
- row["elevation"] = values["enhanced_altitude"]
131
+ row["elevation"] = _first_of_tuple(
132
+ values["enhanced_altitude"]
133
+ )
131
134
  if "speed" in fields:
132
135
  factor = _fit_speed_unit_factor(fields["speed"].units)
133
136
  row["speed"] = values["speed"] * factor
@@ -135,13 +138,9 @@ def read_fit_activity(path: pathlib.Path, open) -> tuple[Activity, pd.DataFrame]
135
138
  factor = _fit_speed_unit_factor(
136
139
  fields["enhanced_speed"].units
137
140
  )
138
- try:
139
- row["speed"] = values["enhanced_speed"] * factor
140
- except TypeError as e:
141
- # https://github.com/martin-ueding/geo-activity-playground/issues/301
142
- raise ActivityParseError(
143
- f'Cannot work with {values["enhanced_speed"]!r}, {factor!r}'
144
- ) from e
141
+ row["speed"] = (
142
+ _first_of_tuple(values["enhanced_speed"]) * factor
143
+ )
145
144
  if "grade" in fields:
146
145
  row["grade"] = values["grade"]
147
146
  if "temperature" in fields:
@@ -159,7 +158,7 @@ def read_fit_activity(path: pathlib.Path, open) -> tuple[Activity, pd.DataFrame]
159
158
  kind_name += " " + str(values["sub_sport"])
160
159
  activity.kind = get_or_make_kind(kind_name)
161
160
  if "total_calories" in fields:
162
- activity.calories = values["total_calories"]
161
+ activity.calories = int(str(values["total_calories"]))
163
162
  if "total_strides" in fields:
164
163
  activity.steps = 2 * int(values["total_strides"])
165
164
 
@@ -175,6 +174,13 @@ def _fit_speed_unit_factor(unit: str) -> float:
175
174
  raise ActivityParseError(f"Unknown speed unit {unit}")
176
175
 
177
176
 
177
+ def _first_of_tuple(value: Union[float, tuple[float, float]]) -> float:
178
+ try:
179
+ return float(value)
180
+ except TypeError:
181
+ return float(value[0])
182
+
183
+
178
184
  def read_gpx_activity(path: pathlib.Path, open) -> pd.DataFrame:
179
185
  points = []
180
186
  with open(path, "rb") as f:
@@ -4,6 +4,7 @@ import re
4
4
  import traceback
5
5
 
6
6
  import sqlalchemy
7
+ from tqdm import tqdm
7
8
 
8
9
  from ..core.activities import ActivityRepository
9
10
  from ..core.config import Config
@@ -37,8 +38,22 @@ def import_from_directory(
37
38
  and not path.stem.startswith(".")
38
39
  and not path.suffix in config.ignore_suffixes
39
40
  ]
41
+ activity_paths.sort()
40
42
 
41
- for i, activity_path in enumerate(activity_paths):
43
+ paths_to_import = [
44
+ activity_path
45
+ for activity_path in tqdm(
46
+ activity_paths, desc="Scanning for new files", delay=1
47
+ )
48
+ if DB.session.scalar(
49
+ sqlalchemy.select(Activity).filter(Activity.path == str(activity_path))
50
+ )
51
+ is None
52
+ ]
53
+
54
+ for i, activity_path in enumerate(
55
+ tqdm(paths_to_import, desc="Importing activity files", delay=0)
56
+ ):
42
57
  with DB.session.no_autoflush:
43
58
  activity = DB.session.scalar(
44
59
  sqlalchemy.select(Activity).filter(Activity.path == str(activity_path))
@@ -4,6 +4,7 @@ import pathlib
4
4
  import pickle
5
5
  import time
6
6
  import zoneinfo
7
+ from typing import Optional
7
8
 
8
9
  import pandas as pd
9
10
  from stravalib import Client
@@ -80,8 +81,12 @@ def import_from_strava_api(
80
81
  config: Config,
81
82
  repository: ActivityRepository,
82
83
  tile_visit_accessor: TileVisitAccessor,
84
+ strava_begin: Optional[str] = None,
85
+ strava_end: Optional[str] = None,
83
86
  ) -> None:
84
- while try_import_strava(config, repository, tile_visit_accessor):
87
+ while try_import_strava(
88
+ config, repository, tile_visit_accessor, strava_begin, strava_end
89
+ ):
85
90
  now = datetime.datetime.now()
86
91
  next_quarter = round_to_next_quarter_hour(now)
87
92
  seconds_to_wait = (next_quarter - now).total_seconds() + 10
@@ -95,8 +100,13 @@ def try_import_strava(
95
100
  config: Config,
96
101
  repository: ActivityRepository,
97
102
  tile_visit_accessor: TileVisitAccessor,
103
+ strava_begin: Optional[str] = None,
104
+ strava_end: Optional[str] = None,
98
105
  ) -> bool:
99
- get_after = get_state(strava_last_activity_date_path(), "2000-01-01T00:00:00Z")
106
+ if strava_begin:
107
+ get_after = f"{strava_begin}T00:00:00Z"
108
+ else:
109
+ get_after = get_state(strava_last_activity_date_path(), "2000-01-01T00:00:00Z")
100
110
 
101
111
  gear_names = {None: "None"}
102
112
 
@@ -106,6 +116,13 @@ def try_import_strava(
106
116
  for strava_activity in tqdm(
107
117
  client.get_activities(after=get_after), desc="Downloading Strava activities"
108
118
  ):
119
+ if (
120
+ strava_end
121
+ and strava_activity.start_date is not None
122
+ and str(strava_activity.start_date) > strava_end
123
+ ):
124
+ break
125
+
109
126
  cache_file = (
110
127
  pathlib.Path("Cache")
111
128
  / "Strava Activity Metadata"
@@ -171,10 +188,11 @@ def try_import_strava(
171
188
  compute_tile_evolution(tile_visit_accessor.tile_state, config)
172
189
  tile_visit_accessor.save()
173
190
 
174
- set_state(
175
- strava_last_activity_date_path(),
176
- strava_activity.start_date.isoformat().replace("+00:00", "Z"),
177
- )
191
+ if strava_begin is None and strava_end is None:
192
+ set_state(
193
+ strava_last_activity_date_path(),
194
+ strava_activity.start_date.isoformat().replace("+00:00", "Z"),
195
+ )
178
196
 
179
197
  limit_exceeded = False
180
198
  except RateLimitExceeded:
@@ -198,6 +198,8 @@ def import_from_strava_checkout(config: Config) -> None:
198
198
 
199
199
  activity_file = checkout_path / row["Filename"]
200
200
 
201
+ logger.info(f"Importing '{activity_file}' …")
202
+
201
203
  try:
202
204
  activity, time_series = read_activity(activity_file)
203
205
  except ActivityParseError as e:
@@ -11,15 +11,16 @@ import threading
11
11
  import urllib.parse
12
12
  import uuid
13
13
  import warnings
14
+ from typing import Optional
14
15
 
15
16
  import pandas as pd
16
17
  import sqlalchemy
17
- from flask import Config
18
18
  from flask import Flask
19
19
  from flask import request
20
20
  from flask_alembic import Alembic
21
21
 
22
22
  from ..core.activities import ActivityRepository
23
+ from ..core.config import Config
23
24
  from ..core.config import ConfigAccessor
24
25
  from ..core.config import import_old_config
25
26
  from ..core.config import import_old_strava_config
@@ -84,9 +85,13 @@ def importer_thread(
84
85
  repository: ActivityRepository,
85
86
  tile_visit_accessor: TileVisitAccessor,
86
87
  config: Config,
88
+ strava_begin: Optional[str],
89
+ strava_end: Optional[str],
87
90
  ) -> None:
88
91
  with app.app_context():
89
- scan_for_activities(repository, tile_visit_accessor, config)
92
+ scan_for_activities(
93
+ repository, tile_visit_accessor, config, strava_begin, strava_end
94
+ )
90
95
  logger.info("Importer thread is done.")
91
96
 
92
97
 
@@ -95,6 +100,8 @@ def web_ui_main(
95
100
  skip_reload: bool,
96
101
  host: str,
97
102
  port: int,
103
+ strava_begin: Optional[str],
104
+ strava_end: Optional[str],
98
105
  ) -> None:
99
106
  os.chdir(basedir)
100
107
 
@@ -109,6 +116,7 @@ def web_ui_main(
109
116
  database_path = pathlib.Path("database.sqlite")
110
117
  logger.info(f"Using database file at '{database_path.absolute()}'.")
111
118
  app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{database_path.absolute()}"
119
+ # app.config["SQLALCHEMY_ECHO"] = True
112
120
  app.config["ALEMBIC"] = {"script_location": "../alembic/versions"}
113
121
  DB.init_app(app)
114
122
 
@@ -144,7 +152,14 @@ def web_ui_main(
144
152
  if not skip_reload:
145
153
  thread = threading.Thread(
146
154
  target=importer_thread,
147
- args=(app, repository, tile_visit_accessor, config_accessor()),
155
+ args=(
156
+ app,
157
+ repository,
158
+ tile_visit_accessor,
159
+ config_accessor(),
160
+ strava_begin,
161
+ strava_end,
162
+ ),
148
163
  )
149
164
  thread.start()
150
165
 
@@ -64,11 +64,7 @@ def _render_eddington_template(
64
64
 
65
65
  query = search_query_from_form(request.args)
66
66
  search_query_history.register_query(query)
67
- activities = (
68
- apply_search_query(repository.meta, query)
69
- .dropna(subset=["start", column_name])
70
- .copy()
71
- )
67
+ activities = apply_search_query(query).dropna(subset=["start", column_name]).copy()
72
68
 
73
69
  assert (
74
70
  len(activities) > 0
@@ -26,7 +26,7 @@ def make_hall_of_fame_blueprint(
26
26
  def index() -> str:
27
27
  query = search_query_from_form(request.args)
28
28
  search_query_history.register_query(query)
29
- activities = apply_search_query(repository.meta, query)
29
+ activities = apply_search_query(query)
30
30
  df = activities
31
31
 
32
32
  nominations = nominate_activities(df)
@@ -183,7 +183,7 @@ def _get_counts(
183
183
  tile_count_cache_path.unlink(missing_ok=True)
184
184
  tmp_path.rename(tile_count_cache_path)
185
185
  else:
186
- activities = apply_search_query(repository.meta, query)
186
+ activities = apply_search_query(query)
187
187
  activity_ids = activities_per_tile[z].get((x, y), set())
188
188
  for activity_id in activity_ids:
189
189
  if activity_id not in activities["id"]:
@@ -42,6 +42,7 @@ def make_photo_blueprint(
42
42
  if not small_path.exists():
43
43
  with Image.open(original_path) as im:
44
44
  target_size = (size, size)
45
+ im = ImageOps.exif_transpose(im)
45
46
  im = ImageOps.contain(im, target_size)
46
47
  small_path.parent.mkdir(exist_ok=True)
47
48
  im.save(small_path)
@@ -35,7 +35,7 @@ def make_search_blueprint(
35
35
  def index():
36
36
  query = search_query_from_form(request.args)
37
37
  search_query_history.register_query(query)
38
- activities = apply_search_query(repository.meta, query)
38
+ activities = apply_search_query(query)
39
39
 
40
40
  return render_template(
41
41
  "search/index.html.j2",
@@ -178,7 +178,7 @@ def make_summary_blueprint(
178
178
  def index():
179
179
  query = search_query_from_form(request.args)
180
180
  search_query_history.register_query(query)
181
- df = apply_search_query(repository.meta, query)
181
+ df = apply_search_query(query)
182
182
 
183
183
  kind_scale = make_kind_scale(repository.meta, config)
184
184
  df_without_nan = df.loc[~pd.isna(df["start"])]
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import pathlib
3
+ from typing import Optional
3
4
 
4
5
  import sqlalchemy
5
6
  from flask import Blueprint
@@ -115,6 +116,8 @@ def scan_for_activities(
115
116
  repository: ActivityRepository,
116
117
  tile_visit_accessor: TileVisitAccessor,
117
118
  config: Config,
119
+ strava_begin: Optional[str] = None,
120
+ strava_end: Optional[str] = None,
118
121
  skip_strava: bool = False,
119
122
  ) -> None:
120
123
  if pathlib.Path("Activities").exists():
@@ -122,7 +125,9 @@ def scan_for_activities(
122
125
  if pathlib.Path("Strava Export").exists():
123
126
  import_from_strava_checkout(config)
124
127
  if config.strava_client_code and not skip_strava:
125
- import_from_strava_api(config, repository, tile_visit_accessor)
128
+ import_from_strava_api(
129
+ config, repository, tile_visit_accessor, strava_begin, strava_end
130
+ )
126
131
 
127
132
  if len(repository) > 0:
128
133
  compute_tile_visits_new(repository, tile_visit_accessor)
@@ -0,0 +1,39 @@
1
+ var map = L.map('activity-map', {
2
+ fullscreenControl: true
3
+ });
4
+ L.tileLayer('/tile/pastel/{z}/{x}/{y}.png', {
5
+ maxZoom: 19,
6
+ attribution: map_tile_attribution
7
+ }).addTo(map);
8
+
9
+ let copy_to_input = function (value, input) {
10
+ document.getElementById(input).value = value;
11
+ }
12
+
13
+ let layer = L.geoJSON(color_line_geojson, {
14
+ pointToLayer: function (feature, lat_lon) {
15
+ let p = feature.properties
16
+
17
+ let marker = null
18
+ if (p.markerType == "circle") {
19
+ marker = L.circleMarker(lat_lon, p.markerStyle)
20
+ } else {
21
+ marker = L.marker(lat_lon)
22
+ }
23
+
24
+ let text = ''
25
+ if (p.name) {
26
+ text += `<button class="btn btn-primary" onclick="copy_to_input(${p.name}, 'begin')">Use as Begin</button>`
27
+ text += ' '
28
+ text += `<button class="btn btn-primary" onclick="copy_to_input(${p.name}+1, 'end')">Use as End</button>`
29
+ }
30
+ if (text) {
31
+ marker = marker.bindPopup(text)
32
+ }
33
+
34
+ return marker
35
+ }
36
+ })
37
+
38
+ layer.addTo(map)
39
+ map.fitBounds(layer.getBounds());