geo-activity-playground 0.36.2__tar.gz → 0.37.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 (132) hide show
  1. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/PKG-INFO +1 -1
  2. geo_activity_playground-0.37.0/geo_activity_playground/core/meta_search.py +117 -0
  3. geo_activity_playground-0.37.0/geo_activity_playground/core/summary_stats.py +30 -0
  4. geo_activity_playground-0.37.0/geo_activity_playground/core/test_meta_search.py +91 -0
  5. geo_activity_playground-0.37.0/geo_activity_playground/core/test_summary_stats.py +108 -0
  6. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/app.py +9 -5
  7. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/eddington_blueprint.py +8 -3
  8. geo_activity_playground-0.36.2/geo_activity_playground/webui/equipment/controller.py → geo_activity_playground-0.37.0/geo_activity_playground/webui/equipment_blueprint.py +19 -41
  9. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/heatmap/blueprint.py +7 -29
  10. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/heatmap/heatmap_controller.py +45 -103
  11. geo_activity_playground-0.37.0/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +42 -0
  12. geo_activity_playground-0.37.0/geo_activity_playground/webui/search_blueprint.py +38 -0
  13. geo_activity_playground-0.37.0/geo_activity_playground/webui/search_util.py +31 -0
  14. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/summary_blueprint.py +15 -11
  15. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/eddington/index.html.j2 +5 -0
  16. {geo_activity_playground-0.36.2/geo_activity_playground/webui/equipment → geo_activity_playground-0.37.0/geo_activity_playground/webui}/templates/equipment/index.html.j2 +3 -5
  17. geo_activity_playground-0.37.0/geo_activity_playground/webui/templates/search/index.html.j2 +38 -0
  18. geo_activity_playground-0.37.0/geo_activity_playground/webui/templates/search_form.html.j2 +82 -0
  19. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/summary/index.html.j2 +5 -1
  20. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/pyproject.toml +1 -1
  21. geo_activity_playground-0.36.2/geo_activity_playground/webui/equipment/blueprint.py +0 -16
  22. geo_activity_playground-0.36.2/geo_activity_playground/webui/heatmap/__init__.py +0 -0
  23. geo_activity_playground-0.36.2/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +0 -74
  24. geo_activity_playground-0.36.2/geo_activity_playground/webui/search_blueprint.py +0 -101
  25. geo_activity_playground-0.36.2/geo_activity_playground/webui/templates/search/index.html.j2 +0 -95
  26. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/LICENSE +0 -0
  27. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/__init__.py +0 -0
  28. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/__main__.py +0 -0
  29. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/__init__.py +0 -0
  30. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/activities.py +0 -0
  31. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/config.py +0 -0
  32. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/coordinates.py +0 -0
  33. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/enrichment.py +0 -0
  34. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/heart_rate.py +0 -0
  35. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/paths.py +0 -0
  36. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/privacy_zones.py +0 -0
  37. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/raster_map.py +0 -0
  38. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/similarity.py +0 -0
  39. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/tasks.py +0 -0
  40. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/test_tiles.py +0 -0
  41. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/test_time_conversion.py +0 -0
  42. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/tiles.py +0 -0
  43. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/core/time_conversion.py +0 -0
  44. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/explorer/__init__.py +0 -0
  45. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/explorer/grid_file.py +0 -0
  46. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/explorer/tile_visits.py +0 -0
  47. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/explorer/video.py +0 -0
  48. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/heatmap_video.py +0 -0
  49. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/__init__.py +0 -0
  50. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/activity_parsers.py +0 -0
  51. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/csv_parser.py +0 -0
  52. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/directory.py +0 -0
  53. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/strava_api.py +0 -0
  54. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/strava_checkout.py +0 -0
  55. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/test_csv_parser.py +0 -0
  56. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/test_directory.py +0 -0
  57. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/importers/test_strava_api.py +0 -0
  58. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/__init__.py +0 -0
  59. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/__init__.py +0 -0
  60. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/blueprint.py +0 -0
  61. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/controller.py +0 -0
  62. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +0 -0
  63. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -0
  64. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +0 -0
  65. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/templates/activity/name.html.j2 +0 -0
  66. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/activity/templates/activity/show.html.j2 +0 -0
  67. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/auth_blueprint.py +0 -0
  68. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/authenticator.py +0 -0
  69. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/calendar/__init__.py +0 -0
  70. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/calendar/blueprint.py +0 -0
  71. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/calendar/controller.py +0 -0
  72. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -0
  73. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -0
  74. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/entry_controller.py +0 -0
  75. {geo_activity_playground-0.36.2/geo_activity_playground/webui/equipment → geo_activity_playground-0.37.0/geo_activity_playground/webui/explorer}/__init__.py +0 -0
  76. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/explorer/blueprint.py +0 -0
  77. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/explorer/controller.py +0 -0
  78. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +0 -0
  79. {geo_activity_playground-0.36.2/geo_activity_playground/webui/explorer → geo_activity_playground-0.37.0/geo_activity_playground/webui/heatmap}/__init__.py +0 -0
  80. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/plot_util.py +0 -0
  81. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/blueprint.py +0 -0
  82. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/controller.py +0 -0
  83. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2 +0 -0
  84. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +0 -0
  85. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -0
  86. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2 +0 -0
  87. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/index.html.j2 +0 -0
  88. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -0
  89. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -0
  90. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2 +0 -0
  91. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +0 -0
  92. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/segmentation.html.j2 +0 -0
  93. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2 +0 -0
  94. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/settings/templates/settings/strava.html.j2 +0 -0
  95. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/square_planner_blueprint.py +0 -0
  96. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +0 -0
  97. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/MarkerCluster.Default.css +0 -0
  98. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/MarkerCluster.css +0 -0
  99. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
  100. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
  101. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
  102. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
  103. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/bootstrap.bundle.min.js +0 -0
  104. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/bootstrap.min.css +0 -0
  105. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
  106. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
  107. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
  108. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
  109. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/favicon.ico +0 -0
  110. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/favicon.svg +0 -0
  111. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/fullscreen.png +0 -0
  112. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
  113. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/leaflet.css +0 -0
  114. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/leaflet.fullscreen.css +0 -0
  115. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/leaflet.js +0 -0
  116. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/leaflet.markercluster.js +0 -0
  117. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
  118. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/site.webmanifest +0 -0
  119. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/table-sort.min.js +0 -0
  120. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/vega-embed@6 +0 -0
  121. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/vega-lite@4 +0 -0
  122. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/vega@5 +0 -0
  123. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
  124. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
  125. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/auth/index.html.j2 +0 -0
  126. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/home.html.j2 +0 -0
  127. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/page.html.j2 +0 -0
  128. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/square_planner/index.html.j2 +0 -0
  129. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/upload/index.html.j2 +0 -0
  130. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/templates/upload/reload.html.j2 +0 -0
  131. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/tile_blueprint.py +0 -0
  132. {geo_activity_playground-0.36.2 → geo_activity_playground-0.37.0}/geo_activity_playground/webui/upload_blueprint.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geo-activity-playground
3
- Version: 0.36.2
3
+ Version: 0.37.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,117 @@
1
+ import dataclasses
2
+ import datetime
3
+ import re
4
+ import urllib.parse
5
+ from typing import Optional
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+
11
+ @dataclasses.dataclass
12
+ class SearchQuery:
13
+ equipment: list[str] = dataclasses.field(default_factory=list)
14
+ kind: list[str] = dataclasses.field(default_factory=list)
15
+ name: Optional[str] = None
16
+ name_case_sensitive: bool = False
17
+ start_begin: Optional[datetime.date] = None
18
+ start_end: Optional[datetime.date] = None
19
+
20
+ @property
21
+ def active(self) -> bool:
22
+ return (
23
+ self.equipment
24
+ or self.kind
25
+ or self.name
26
+ or self.start_begin
27
+ or self.start_end
28
+ )
29
+
30
+ def to_jinja(self) -> dict:
31
+ return {
32
+ "equipment": self.equipment,
33
+ "kind": self.kind,
34
+ "name": self.name or "",
35
+ "name_case_sensitive": self.name_case_sensitive,
36
+ "start_begin": _format_optional_date(self.start_begin),
37
+ "start_end": _format_optional_date(self.start_end),
38
+ "active": self.active,
39
+ }
40
+
41
+ def to_url_str(self) -> str:
42
+ variables = []
43
+ for equipment in self.equipment:
44
+ variables.append(("equipment", equipment))
45
+ for kind in self.kind:
46
+ variables.append(("kind", kind))
47
+ if self.name:
48
+ variables.append(("name", self.name))
49
+ if self.name_case_sensitive:
50
+ variables.append(("name_case_sensitive", "true"))
51
+ if self.start_begin:
52
+ variables.append(("start_begin", self.start_begin.isoformat()))
53
+ if self.start_end:
54
+ variables.append(("start_end", self.start_end.isoformat()))
55
+
56
+ return "&".join(
57
+ f"{key}={urllib.parse.quote_plus(value)}" for key, value in variables
58
+ )
59
+
60
+
61
+ def _format_optional_date(date: Optional[datetime.date]) -> str:
62
+ if date is None:
63
+ return ""
64
+ else:
65
+ return date.isoformat()
66
+
67
+
68
+ def apply_search_query(
69
+ activity_meta: pd.DataFrame, search_query: SearchQuery
70
+ ) -> pd.DataFrame:
71
+ mask = _make_mask(activity_meta.index, True)
72
+
73
+ if search_query.equipment:
74
+ mask &= _filter_column(activity_meta["equipment"], search_query.equipment)
75
+ if search_query.kind:
76
+ mask &= _filter_column(activity_meta["kind"], search_query.kind)
77
+ if search_query.name:
78
+ mask &= pd.Series(
79
+ [
80
+ bool(
81
+ re.search(
82
+ search_query.name,
83
+ activity_name,
84
+ 0 if search_query.name_case_sensitive else re.IGNORECASE,
85
+ )
86
+ )
87
+ for activity_name in activity_meta["name"]
88
+ ],
89
+ index=activity_meta.index,
90
+ )
91
+ if search_query.start_begin is not None:
92
+ start_begin = datetime.datetime.combine(
93
+ search_query.start_begin, datetime.time.min
94
+ )
95
+ mask &= start_begin <= activity_meta["start"]
96
+ if search_query.start_end is not None:
97
+ start_end = datetime.datetime.combine(search_query.start_end, datetime.time.max)
98
+ mask &= activity_meta["start"] <= start_end
99
+
100
+ return activity_meta.loc[mask]
101
+
102
+
103
+ def _make_mask(
104
+ index: pd.Index,
105
+ default: bool,
106
+ ) -> pd.Series:
107
+ if default:
108
+ return pd.Series(np.ones((len(index),), dtype=np.bool), index=index)
109
+ else:
110
+ return pd.Series(np.zeros((len(index),), dtype=np.bool), index=index)
111
+
112
+
113
+ def _filter_column(column: pd.Series, values: list):
114
+ sub_mask = _make_mask(column.index, False)
115
+ for equipment in values:
116
+ sub_mask |= column == equipment
117
+ return sub_mask
@@ -0,0 +1,30 @@
1
+ import pandas as pd
2
+
3
+
4
+ def get_equipment_use_table(
5
+ activity_meta: pd.DataFrame, offsets: dict[str, float]
6
+ ) -> pd.DataFrame:
7
+ result = (
8
+ activity_meta.groupby("equipment")
9
+ .apply(
10
+ lambda group: pd.Series(
11
+ {
12
+ "total_distance_km": group["distance_km"].sum(),
13
+ "first_use": group["start"].min(skipna=True),
14
+ "last_use": group["start"].max(skipna=True),
15
+ },
16
+ ),
17
+ include_groups=False,
18
+ )
19
+ .sort_values("last_use", ascending=False)
20
+ )
21
+ for equipment, offset in offsets.items():
22
+ result.loc[equipment, "total_distance_km"] += offset
23
+
24
+ result["total_distance_km"] = [
25
+ int(round(elem)) for elem in result["total_distance_km"]
26
+ ]
27
+ result["first_use"] = [date.date().isoformat() for date in result["first_use"]]
28
+ result["last_use"] = [date.date().isoformat() for date in result["last_use"]]
29
+
30
+ return result.reset_index().to_dict(orient="records")
@@ -0,0 +1,91 @@
1
+ import datetime
2
+
3
+ import pandas as pd
4
+
5
+ from geo_activity_playground.core.meta_search import _make_mask
6
+ from geo_activity_playground.core.meta_search import apply_search_query
7
+ from geo_activity_playground.core.meta_search import SearchQuery
8
+
9
+
10
+ def test_empty_query() -> None:
11
+ activity_meta = pd.DataFrame(
12
+ {
13
+ "equipment": pd.Series(["A", "B", "B"]),
14
+ "id": pd.Series([1, 2, 3]),
15
+ "kind": pd.Series(["X", "X", "Y"]),
16
+ "name": ["Test1", "Test2", "Test3"],
17
+ "start": [
18
+ datetime.datetime(2024, 12, 24, 10),
19
+ datetime.datetime(2025, 1, 1, 10),
20
+ None,
21
+ ],
22
+ }
23
+ )
24
+
25
+ search_query = SearchQuery()
26
+
27
+ actual = apply_search_query(activity_meta, search_query)
28
+ assert (actual["id"] == activity_meta["id"]).all()
29
+
30
+
31
+ def test_equipment_query() -> None:
32
+ activity_meta = pd.DataFrame(
33
+ {
34
+ "equipment": pd.Series(["A", "B", "B"]),
35
+ "id": pd.Series([1, 2, 3]),
36
+ "kind": pd.Series(["X", "X", "Y"]),
37
+ "name": ["Test1", "Test2", "Test3"],
38
+ "start": [
39
+ datetime.datetime(2024, 12, 24, 10),
40
+ datetime.datetime(2025, 1, 1, 10),
41
+ None,
42
+ ],
43
+ }
44
+ )
45
+ search_query = SearchQuery(equipment=["B"])
46
+ actual = apply_search_query(activity_meta, search_query)
47
+ assert set(actual["id"]) == {2, 3}
48
+
49
+
50
+ def test_date_query() -> None:
51
+ activity_meta = pd.DataFrame(
52
+ {
53
+ "equipment": pd.Series(["A", "B", "B"]),
54
+ "id": pd.Series([1, 2, 3]),
55
+ "kind": pd.Series(["X", "X", "Y"]),
56
+ "name": ["Test1", "Test2", "Test3"],
57
+ "start": [
58
+ datetime.datetime(2024, 12, 24, 10),
59
+ datetime.datetime(2025, 1, 1, 10),
60
+ None,
61
+ ],
62
+ }
63
+ )
64
+ search_query = SearchQuery(start_begin=datetime.date(2024, 12, 31))
65
+ actual = apply_search_query(activity_meta, search_query)
66
+ assert set(actual["id"]) == {2}
67
+
68
+
69
+ def test_name_query() -> None:
70
+ activity_meta = pd.DataFrame(
71
+ {
72
+ "equipment": pd.Series(["A", "B", "B"]),
73
+ "id": pd.Series([1, 2, 3]),
74
+ "kind": pd.Series(["X", "X", "Y"]),
75
+ "name": ["Test1", "Test2", "Test3"],
76
+ "start": [
77
+ datetime.datetime(2024, 12, 24, 10),
78
+ datetime.datetime(2025, 1, 1, 10),
79
+ None,
80
+ ],
81
+ }
82
+ )
83
+ search_query = SearchQuery(name="Test1")
84
+ actual = apply_search_query(activity_meta, search_query)
85
+ assert set(actual["id"]) == {1}
86
+
87
+
88
+ def test_make_mask() -> None:
89
+ index = [1, 2]
90
+ assert (_make_mask(index, True) == pd.Series([True, True], index=index)).all()
91
+ assert (_make_mask(index, False) == pd.Series([False, False], index=index)).all()
@@ -0,0 +1,108 @@
1
+ import datetime
2
+
3
+ import pandas as pd
4
+ import pytest
5
+
6
+ from geo_activity_playground.core.summary_stats import get_equipment_use_table
7
+
8
+
9
+ @pytest.fixture
10
+ def activity_meta() -> pd.DataFrame:
11
+ """
12
+ calories: float
13
+ commute: bool
14
+ consider_for_achievements: bool
15
+ distance_km: float
16
+ elapsed_time: datetime.timedelta
17
+ end_latitude: float
18
+ end_longitude: float
19
+ equipment: str
20
+ id: int
21
+ kind: str
22
+ moving_time: datetime.timedelta
23
+ name: str
24
+ path: str
25
+ start_latitude: float
26
+ start_longitude: float
27
+ start: np.datetime64
28
+ steps: int
29
+ """
30
+ return pd.DataFrame(
31
+ {
32
+ "calories": pd.Series([None, 1000, 2000]),
33
+ "commute": pd.Series([True, False, True]),
34
+ "consider_for_achievements": pd.Series([True, True, False]),
35
+ "distance_km": pd.Series([9.8, 4.4, 4.3]),
36
+ "elapsed_time": pd.Series(
37
+ [
38
+ datetime.timedelta(minutes=0.34),
39
+ datetime.timedelta(minutes=0.67),
40
+ None,
41
+ ]
42
+ ),
43
+ "end_latitude": pd.Series([0.58, 0.5, 0.19]),
44
+ "end_longitude": pd.Series([0.2, 0.94, 0.69]),
45
+ "equipment": pd.Series(["A", "B", "B"]),
46
+ "id": pd.Series([1, 2, 3]),
47
+ "kind": pd.Series(["X", "X", "Y"]),
48
+ "moving_time": pd.Series(
49
+ [
50
+ datetime.timedelta(minutes=0.32),
51
+ datetime.timedelta(minutes=0.83),
52
+ None,
53
+ ]
54
+ ),
55
+ "name": pd.Series(["Test1", "Test2", "Test1"]),
56
+ "path": pd.Series(["Test1.fit", "Test2.gpx", "Test1.kml"]),
57
+ "start_latitude": pd.Series([0.22, 0.02, 0.35]),
58
+ "start_longitude": pd.Series([0.95, 0.95, 0.81]),
59
+ "start": pd.Series(
60
+ [
61
+ datetime.datetime(2024, 12, 24, 10),
62
+ datetime.datetime(2025, 1, 1, 10),
63
+ None,
64
+ ]
65
+ ),
66
+ "steps": pd.Series([1234, None, 5432]),
67
+ }
68
+ )
69
+
70
+
71
+ def test_activity_meta(activity_meta) -> None:
72
+ print()
73
+ print(activity_meta)
74
+
75
+
76
+ def test_equipment_use_table(activity_meta) -> None:
77
+ activity_meta = pd.DataFrame(
78
+ {
79
+ "distance_km": pd.Series([9.8, 4.4, 4.3]),
80
+ "equipment": pd.Series(["A", "B", "B"]),
81
+ "start": pd.Series(
82
+ [
83
+ datetime.datetime(2024, 12, 24, 10),
84
+ datetime.datetime(2025, 1, 1, 10),
85
+ None,
86
+ ]
87
+ ),
88
+ }
89
+ )
90
+
91
+ offsets = {"A": 4.0}
92
+
93
+ expected = [
94
+ {
95
+ "equipment": "B",
96
+ "total_distance_km": 9,
97
+ "first_use": "2025-01-01",
98
+ "last_use": "2025-01-01",
99
+ },
100
+ {
101
+ "equipment": "A",
102
+ "total_distance_km": 14,
103
+ "first_use": "2024-12-24",
104
+ "last_use": "2024-12-24",
105
+ },
106
+ ]
107
+ actual = get_equipment_use_table(activity_meta, offsets)
108
+ assert actual == expected
@@ -21,8 +21,7 @@ from .calendar.blueprint import make_calendar_blueprint
21
21
  from .calendar.controller import CalendarController
22
22
  from .eddington_blueprint import make_eddington_blueprint
23
23
  from .entry_controller import EntryController
24
- from .equipment.blueprint import make_equipment_blueprint
25
- from .equipment.controller import EquipmentController
24
+ from .equipment_blueprint import make_equipment_blueprint
26
25
  from .explorer.blueprint import make_explorer_blueprint
27
26
  from .explorer.controller import ExplorerController
28
27
  from .heatmap.blueprint import make_heatmap_blueprint
@@ -84,7 +83,6 @@ def web_ui_main(
84
83
  config = config_accessor()
85
84
  activity_controller = ActivityController(repository, tile_visit_accessor, config)
86
85
  calendar_controller = CalendarController(repository)
87
- equipment_controller = EquipmentController(repository, config)
88
86
  explorer_controller = ExplorerController(
89
87
  repository, tile_visit_accessor, config_accessor
90
88
  )
@@ -103,7 +101,7 @@ def web_ui_main(
103
101
  make_eddington_blueprint(repository), url_prefix="/eddington"
104
102
  )
105
103
  app.register_blueprint(
106
- make_equipment_blueprint(equipment_controller), url_prefix="/equipment"
104
+ make_equipment_blueprint(repository, config), url_prefix="/equipment"
107
105
  )
108
106
  app.register_blueprint(
109
107
  make_explorer_blueprint(explorer_controller), url_prefix="/explorer"
@@ -146,11 +144,17 @@ def web_ui_main(
146
144
 
147
145
  @app.context_processor
148
146
  def inject_global_variables() -> dict:
149
- return {
147
+ variables = {
150
148
  "version": _try_get_version(),
151
149
  "num_activities": len(repository),
152
150
  "map_tile_attribution": config_accessor().map_tile_attribution,
153
151
  }
152
+ if len(repository):
153
+ variables["equipments_avail"] = sorted(
154
+ repository.meta["equipment"].unique()
155
+ )
156
+ variables["kinds_avail"] = sorted(repository.meta["kind"].unique())
157
+ return variables
154
158
 
155
159
  app.run(host=host, port=port)
156
160
 
@@ -3,8 +3,11 @@ import numpy as np
3
3
  import pandas as pd
4
4
  from flask import Blueprint
5
5
  from flask import render_template
6
+ from flask import request
6
7
 
7
8
  from geo_activity_playground.core.activities import ActivityRepository
9
+ from geo_activity_playground.core.meta_search import apply_search_query
10
+ from geo_activity_playground.webui.search_util import search_query_from_form
8
11
 
9
12
 
10
13
  def make_eddington_blueprint(repository: ActivityRepository) -> Blueprint:
@@ -12,9 +15,10 @@ def make_eddington_blueprint(repository: ActivityRepository) -> Blueprint:
12
15
 
13
16
  @blueprint.route("/")
14
17
  def index():
15
- activities = repository.meta.loc[
16
- repository.meta["consider_for_achievements"]
17
- ].copy()
18
+ query = search_query_from_form(request.args)
19
+ activities = apply_search_query(repository.meta, query)
20
+ activities = activities.loc[activities["consider_for_achievements"]].copy()
21
+
18
22
  activities["day"] = [start.date() for start in activities["start"]]
19
23
 
20
24
  sum_per_day = activities.groupby("day").apply(
@@ -76,6 +80,7 @@ def make_eddington_blueprint(repository: ActivityRepository) -> Blueprint:
76
80
  eddington_table=eddington.loc[
77
81
  (eddington["distance_km"] > en) & (eddington["distance_km"] <= en + 10)
78
82
  ].to_dict(orient="records"),
83
+ query=query.to_jinja(),
79
84
  )
80
85
 
81
86
  return blueprint
@@ -1,47 +1,27 @@
1
1
  import altair as alt
2
2
  import pandas as pd
3
+ from flask import Blueprint
4
+ from flask import render_template
3
5
 
4
6
  from geo_activity_playground.core.activities import ActivityRepository
5
7
  from geo_activity_playground.core.config import Config
8
+ from geo_activity_playground.core.summary_stats import get_equipment_use_table
6
9
 
7
10
 
8
- class EquipmentController:
9
- def __init__(self, repository: ActivityRepository, config: Config) -> None:
10
- self._repository = repository
11
- self._config = config
11
+ def make_equipment_blueprint(
12
+ repository: ActivityRepository, config: Config
13
+ ) -> Blueprint:
14
+ blueprint = Blueprint("equipment", __name__, template_folder="templates")
12
15
 
13
- def render(self) -> dict:
14
- kind_per_equipment = self._repository.meta.groupby("equipment").apply(
15
- lambda group: group.groupby("kind")
16
- .apply(lambda group2: sum(group2["distance_km"]), include_groups=False)
17
- .idxmax(),
18
- include_groups=False,
16
+ @blueprint.route("/")
17
+ def index():
18
+ equipment_summary = get_equipment_use_table(
19
+ repository.meta, config.equipment_offsets
19
20
  )
20
21
 
21
- equipment_summary = (
22
- self._repository.meta.groupby("equipment")
23
- .apply(
24
- lambda group: pd.Series(
25
- {
26
- "total_distance_km": group["distance_km"].sum(),
27
- "first_use": group["start"].iloc[0],
28
- "last_use": group["start"].iloc[-1],
29
- },
30
- ),
31
- include_groups=False,
32
- )
33
- .sort_values("last_use", ascending=False)
34
- )
35
-
36
- equipment_summary["primarily_used_for"] = None
37
- for equipment, kind in kind_per_equipment.items():
38
- equipment_summary.loc[equipment, "primarily_used_for"] = kind
39
-
40
22
  equipment_variables = {}
41
- for equipment in equipment_summary.index:
42
- selection = self._repository.meta.loc[
43
- self._repository.meta["equipment"] == equipment
44
- ]
23
+ for equipment in repository.meta["equipment"].unique():
24
+ selection = repository.meta.loc[repository.meta["equipment"] == equipment]
45
25
  total_distances = pd.DataFrame(
46
26
  {
47
27
  "time": selection["start"],
@@ -102,13 +82,11 @@ class EquipmentController:
102
82
  "usages_plot_id": f"usages_plot_{hash(equipment)}",
103
83
  }
104
84
 
105
- for equipment, offset in self._config.equipment_offsets.items():
106
- if equipment in equipment_summary.index:
107
- equipment_summary.loc[equipment, "total_distance_km"] += offset
108
-
109
- return {
85
+ variables = {
110
86
  "equipment_variables": equipment_variables,
111
- "equipment_summary": equipment_summary.reset_index().to_dict(
112
- orient="records"
113
- ),
87
+ "equipment_summary": equipment_summary,
114
88
  }
89
+
90
+ return render_template("equipment/index.html.j2", **variables)
91
+
92
+ return blueprint
@@ -8,6 +8,7 @@ from geo_activity_playground.core.activities import ActivityRepository
8
8
  from geo_activity_playground.core.config import Config
9
9
  from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
10
10
  from geo_activity_playground.webui.heatmap.heatmap_controller import HeatmapController
11
+ from geo_activity_playground.webui.search_util import search_query_from_form
11
12
 
12
13
 
13
14
  def make_heatmap_blueprint(
@@ -20,30 +21,16 @@ def make_heatmap_blueprint(
20
21
 
21
22
  @blueprint.route("/")
22
23
  def index():
24
+ query = search_query_from_form(request.args)
23
25
  return render_template(
24
- "heatmap/index.html.j2",
25
- **heatmap_controller.render(
26
- [int(k) for k in request.args.getlist("kind")],
27
- request.args.get(
28
- "date-start", type=dateutil.parser.parse, default=None
29
- ),
30
- request.args.get("date-end", type=dateutil.parser.parse, default=None),
31
- )
26
+ "heatmap/index.html.j2", **heatmap_controller.render(query)
32
27
  )
33
28
 
34
29
  @blueprint.route("/tile/<int:z>/<int:x>/<int:y>.png")
35
30
  def tile(x: int, y: int, z: int):
31
+ query = search_query_from_form(request.args)
36
32
  return Response(
37
- heatmap_controller.render_tile(
38
- x,
39
- y,
40
- z,
41
- [int(k) for k in request.args.getlist("kind")],
42
- request.args.get(
43
- "date-start", type=dateutil.parser.parse, default=None
44
- ),
45
- request.args.get("date-end", type=dateutil.parser.parse, default=None),
46
- ),
33
+ heatmap_controller.render_tile(x, y, z, query),
47
34
  mimetype="image/png",
48
35
  )
49
36
 
@@ -51,18 +38,9 @@ def make_heatmap_blueprint(
51
38
  "/download/<float:north>/<float:east>/<float:south>/<float:west>/heatmap.png"
52
39
  )
53
40
  def download(north: float, east: float, south: float, west: float):
41
+ query = search_query_from_form(request.args)
54
42
  return Response(
55
- heatmap_controller.download_heatmap(
56
- north,
57
- east,
58
- south,
59
- west,
60
- [int(k) for k in request.args.getlist("kind")],
61
- request.args.get(
62
- "date-start", type=dateutil.parser.parse, default=None
63
- ),
64
- request.args.get("date-end", type=dateutil.parser.parse, default=None),
65
- ),
43
+ heatmap_controller.download_heatmap(north, east, south, west, query),
66
44
  mimetype="image/png",
67
45
  headers={"Content-disposition": 'attachment; filename="heatmap.png"'},
68
46
  )