geo-activity-playground 0.28.0__py3-none-any.whl → 0.29.0__py3-none-any.whl

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 (28) hide show
  1. geo_activity_playground/core/config.py +3 -0
  2. geo_activity_playground/core/paths.py +10 -0
  3. geo_activity_playground/core/tasks.py +5 -4
  4. geo_activity_playground/explorer/tile_visits.py +41 -7
  5. geo_activity_playground/webui/activity/controller.py +22 -1
  6. geo_activity_playground/webui/activity/templates/activity/show.html.j2 +33 -0
  7. geo_activity_playground/webui/app.py +10 -20
  8. geo_activity_playground/webui/authenticator.py +0 -3
  9. geo_activity_playground/webui/entry_controller.py +8 -4
  10. geo_activity_playground/webui/explorer/controller.py +1 -0
  11. geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +2 -0
  12. geo_activity_playground/webui/heatmap/heatmap_controller.py +1 -0
  13. geo_activity_playground/webui/plot_util.py +9 -0
  14. geo_activity_playground/webui/search/blueprint.py +20 -0
  15. geo_activity_playground/webui/settings/blueprint.py +67 -0
  16. geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +33 -0
  17. geo_activity_playground/webui/settings/templates/settings/index.html.j2 +9 -0
  18. geo_activity_playground/webui/summary/blueprint.py +3 -2
  19. geo_activity_playground/webui/summary/controller.py +20 -13
  20. geo_activity_playground/webui/templates/home.html.j2 +1 -1
  21. geo_activity_playground/webui/templates/page.html.j2 +56 -28
  22. {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/METADATA +3 -4
  23. {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/RECORD +27 -25
  24. geo_activity_playground/webui/search_controller.py +0 -19
  25. /geo_activity_playground/webui/{templates/search.html.j2 → search/templates/search/index.html.j2} +0 -0
  26. {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/LICENSE +0 -0
  27. {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/WHEEL +0 -0
  28. {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/entry_points.txt +0 -0
@@ -21,6 +21,8 @@ logger = logging.getLogger(__name__)
21
21
  @dataclasses.dataclass
22
22
  class Config:
23
23
  birth_year: Optional[int] = None
24
+ color_scheme_for_counts: str = "viridis"
25
+ color_scheme_for_kind: str = "category10"
24
26
  equipment_offsets: dict[str, float] = dataclasses.field(default_factory=dict)
25
27
  explorer_zoom_levels: list[int] = dataclasses.field(
26
28
  default_factory=lambda: [14, 17]
@@ -52,6 +54,7 @@ class ConfigAccessor:
52
54
  return self._config
53
55
 
54
56
  def save(self) -> None:
57
+ print(self._config)
55
58
  with open(new_config_file(), "w") as f:
56
59
  json.dump(
57
60
  dataclasses.asdict(self._config),
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Paths within the playground and cache.
3
3
  """
4
+ import contextlib
4
5
  import functools
5
6
  import pathlib
6
7
  import typing
@@ -24,6 +25,15 @@ def file_wrapper(path: pathlib.Path) -> typing.Callable[[], pathlib.Path]:
24
25
  return wrapper
25
26
 
26
27
 
28
+ @contextlib.contextmanager
29
+ def atomic_open(path: pathlib.Path, mode: str):
30
+ temp_path = path.with_stem(path.stem + "-temp")
31
+ with open(temp_path, mode) as f:
32
+ yield f
33
+ path.unlink(missing_ok=True)
34
+ temp_path.rename(path)
35
+
36
+
27
37
  _cache_dir = pathlib.Path("Cache")
28
38
 
29
39
  _activity_dir = _cache_dir / "Activity"
@@ -8,6 +8,7 @@ from typing import Generic
8
8
  from typing import Sequence
9
9
  from typing import TypeVar
10
10
 
11
+ from geo_activity_playground.core.paths import atomic_open
11
12
  from geo_activity_playground.core.paths import cache_dir
12
13
 
13
14
 
@@ -24,11 +25,8 @@ def stored_object(path: pathlib.Path, default):
24
25
 
25
26
  yield payload
26
27
 
27
- temp_location = path.with_suffix(".tmp")
28
- with open(temp_location, "wb") as f:
28
+ with atomic_open(path, "wb") as f:
29
29
  pickle.dump(payload, f)
30
- path.unlink(missing_ok=True)
31
- temp_location.rename(path)
32
30
 
33
31
 
34
32
  def work_tracker_path(name: str) -> pathlib.Path:
@@ -68,6 +66,9 @@ class WorkTracker:
68
66
  def discard(self, id) -> None:
69
67
  self._done.discard(id)
70
68
 
69
+ def reset(self) -> None:
70
+ self._done = set()
71
+
71
72
  def close(self) -> None:
72
73
  with open(self._path, "wb") as f:
73
74
  pickle.dump(self._done, f)
@@ -14,6 +14,7 @@ from tqdm import tqdm
14
14
 
15
15
  from geo_activity_playground.core.activities import ActivityRepository
16
16
  from geo_activity_playground.core.config import Config
17
+ from geo_activity_playground.core.paths import atomic_open
17
18
  from geo_activity_playground.core.paths import tiles_per_time_series
18
19
  from geo_activity_playground.core.tasks import try_load_pickle
19
20
  from geo_activity_playground.core.tasks import work_tracker_path
@@ -79,11 +80,12 @@ class TileVisitAccessor:
79
80
  self.tile_state = make_tile_state()
80
81
  # TODO: Reset work tracker
81
82
 
83
+ def reset(self) -> None:
84
+ self.tile_state = make_tile_state()
85
+
82
86
  def save(self) -> None:
83
- tmp_path = self.PATH.with_suffix(".tmp")
84
- with open(tmp_path, "wb") as f:
87
+ with atomic_open(self.PATH, "wb") as f:
85
88
  pickle.dump(self.tile_state, f)
86
- tmp_path.rename(self.PATH)
87
89
 
88
90
 
89
91
  def make_defaultdict_dict():
@@ -106,20 +108,52 @@ def make_tile_state() -> TileState:
106
108
  return tile_state
107
109
 
108
110
 
111
+ def _consistency_check(
112
+ repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
113
+ ) -> bool:
114
+ present_activity_ids = set(repository.get_activity_ids())
115
+
116
+ for zoom, activities_per_tile in tile_visit_accessor.tile_state[
117
+ "activities_per_tile"
118
+ ].items():
119
+ for tile, tile_activity_ids in activities_per_tile.items():
120
+ deleted_activity_ids = tile_activity_ids - present_activity_ids
121
+ if deleted_activity_ids:
122
+ logger.info(f"Activities {deleted_activity_ids} have been deleted.")
123
+ return False
124
+
125
+ for zoom, tile_visits in tile_visit_accessor.tile_state["tile_visits"].items():
126
+ for tile, meta in tile_visits.items():
127
+ if meta["first_id"] not in present_activity_ids:
128
+ logger.info(f"Activity {meta['first_id']} have been deleted.")
129
+ return False
130
+ if meta["last_id"] not in present_activity_ids:
131
+ logger.info(f"Activity {meta['last_id']} have been deleted.")
132
+ return False
133
+
134
+ return True
135
+
136
+
109
137
  def compute_tile_visits_new(
110
138
  repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
111
139
  ) -> None:
112
140
  work_tracker = WorkTracker(work_tracker_path("tile-state"))
141
+
142
+ if not _consistency_check(repository, tile_visit_accessor):
143
+ logger.warning("Need to recompute Explorer Tiles due to deleted activities.")
144
+ tile_visit_accessor.reset()
145
+ work_tracker.reset()
146
+
113
147
  for activity_id in tqdm(
114
- work_tracker.filter(repository.get_activity_ids()), desc="Tile visits (new)"
148
+ work_tracker.filter(repository.get_activity_ids()), desc="Tile visits"
115
149
  ):
116
- do_tile_stuff(repository, tile_visit_accessor.tile_state, activity_id)
150
+ _process_activity(repository, tile_visit_accessor.tile_state, activity_id)
117
151
  work_tracker.mark_done(activity_id)
118
152
  tile_visit_accessor.save()
119
153
  work_tracker.close()
120
154
 
121
155
 
122
- def do_tile_stuff(
156
+ def _process_activity(
123
157
  repository: ActivityRepository, tile_state: TileState, activity_id: int
124
158
  ) -> None:
125
159
  activity = repository.get_activity_by_id(activity_id)
@@ -145,7 +179,7 @@ def do_tile_stuff(
145
179
  zip(activity_tiles["tile_x"], activity_tiles["tile_y"]),
146
180
  ):
147
181
  if activity["consider_for_achievements"]:
148
- if tile not in activities_per_tile:
182
+ if tile not in tile_state["tile_visits"][zoom]:
149
183
  new_tile_history_soa["activity_id"].append(activity_id)
150
184
  new_tile_history_soa["time"].append(time)
151
185
  new_tile_history_soa["tile_x"].append(tile[0])
@@ -12,6 +12,8 @@ import pandas as pd
12
12
  from PIL import Image
13
13
  from PIL import ImageDraw
14
14
 
15
+ from ...explorer.grid_file import make_grid_file_geojson
16
+ from ...explorer.grid_file import make_grid_points
15
17
  from geo_activity_playground.core.activities import ActivityMeta
16
18
  from geo_activity_playground.core.activities import ActivityRepository
17
19
  from geo_activity_playground.core.activities import make_geojson_color_line
@@ -66,9 +68,27 @@ class ActivityController:
66
68
  ]
67
69
  == activity["id"]
68
70
  )
69
- for zoom in [14, 17]
71
+ for zoom in sorted(self._config.explorer_zoom_levels)
70
72
  }
71
73
 
74
+ new_tiles_geojson = {}
75
+ for zoom in sorted(self._config.explorer_zoom_levels):
76
+ new_tiles = self._tile_visit_accessor.tile_state["tile_history"][zoom].loc[
77
+ self._tile_visit_accessor.tile_state["tile_history"][zoom][
78
+ "activity_id"
79
+ ]
80
+ == activity["id"]
81
+ ]
82
+ if len(new_tiles):
83
+ points = make_grid_points(
84
+ (
85
+ (row["tile_x"], row["tile_y"])
86
+ for index, row in new_tiles.iterrows()
87
+ ),
88
+ zoom,
89
+ )
90
+ new_tiles_geojson[zoom] = make_grid_file_geojson(points)
91
+
72
92
  result = {
73
93
  "activity": activity,
74
94
  "line_json": line_json,
@@ -81,6 +101,7 @@ class ActivityController:
81
101
  "date": activity["start"].date(),
82
102
  "time": activity["start"].time(),
83
103
  "new_tiles": new_tiles,
104
+ "new_tiles_geojson": new_tiles_geojson,
84
105
  }
85
106
  if (
86
107
  heart_zones := _extract_heart_rate_zones(
@@ -136,6 +136,39 @@
136
136
  <p>Not happy with the displayed data? <a href="{{ url_for('settings.sharepic') }}">Change share picture
137
137
  settings</a>.</p>
138
138
 
139
+ {% if new_tiles_geojson %}
140
+ <h2>New explorer tiles</h2>
141
+ <p>With this activity you have explored new explorer tiles. The following maps show the new tiles on the respective zoom
142
+ levels.</p>
143
+ <script>
144
+ function add_map(id, geojson) {
145
+ let map = L.map(`map-${id}`, {
146
+ fullscreenControl: true
147
+ })
148
+ L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
149
+ maxZoom: 19,
150
+ attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
151
+ }).addTo(map)
152
+
153
+ let geojson_layer = L.geoJSON(geojson).addTo(map)
154
+ map.fitBounds(geojson_layer.getBounds())
155
+ return map
156
+ }
157
+ </script>
158
+
159
+ <div class="row mb-3">
160
+ {% for zoom, geojson in new_tiles_geojson.items() %}
161
+ <div class="col-md-6">
162
+ <h3>Zoom {{ zoom }}</h3>
163
+ <div id="map-{{ zoom }}" style="height: 300px; width: 100%;"></div>
164
+ <script>
165
+ let map{{ zoom }} = add_map("{{ zoom }}", {{ geojson | safe }})
166
+ </script>
167
+ </div>
168
+ {% endfor %}
169
+ </div>
170
+ {% endif %}
171
+
139
172
  {% if similar_activites|length > 0 %}
140
173
  <div class="row mb-3">
141
174
  <div class="col">
@@ -5,7 +5,6 @@ import secrets
5
5
 
6
6
  from flask import Flask
7
7
  from flask import render_template
8
- from flask import request
9
8
 
10
9
  from ..core.activities import ActivityRepository
11
10
  from ..explorer.tile_visits import TileVisitAccessor
@@ -16,31 +15,20 @@ from .entry_controller import EntryController
16
15
  from .equipment.blueprint import make_equipment_blueprint
17
16
  from .explorer.blueprint import make_explorer_blueprint
18
17
  from .heatmap.blueprint import make_heatmap_blueprint
19
- from .search_controller import SearchController
18
+ from .search.blueprint import make_search_blueprint
20
19
  from .square_planner.blueprint import make_square_planner_blueprint
21
20
  from .summary.blueprint import make_summary_blueprint
22
21
  from .tile.blueprint import make_tile_blueprint
23
22
  from .upload.blueprint import make_upload_blueprint
23
+ from geo_activity_playground.core.config import Config
24
24
  from geo_activity_playground.core.config import ConfigAccessor
25
25
  from geo_activity_playground.webui.auth.blueprint import make_auth_blueprint
26
26
  from geo_activity_playground.webui.authenticator import Authenticator
27
27
  from geo_activity_playground.webui.settings.blueprint import make_settings_blueprint
28
28
 
29
29
 
30
- def route_search(app: Flask, repository: ActivityRepository) -> None:
31
- search_controller = SearchController(repository)
32
-
33
- @app.route("/search", methods=["POST"])
34
- def search():
35
- form_input = request.form
36
- return render_template(
37
- "search.html.j2",
38
- **search_controller.render_search_results(form_input["name"])
39
- )
40
-
41
-
42
- def route_start(app: Flask, repository: ActivityRepository) -> None:
43
- entry_controller = EntryController(repository)
30
+ def route_start(app: Flask, repository: ActivityRepository, config: Config) -> None:
31
+ entry_controller = EntryController(repository, config)
44
32
 
45
33
  @app.route("/")
46
34
  def index():
@@ -66,7 +54,6 @@ def web_ui_main(
66
54
  host: str,
67
55
  port: int,
68
56
  ) -> None:
69
-
70
57
  repository.reload()
71
58
 
72
59
  app = Flask(__name__)
@@ -75,8 +62,7 @@ def web_ui_main(
75
62
 
76
63
  authenticator = Authenticator(config_accessor())
77
64
 
78
- route_search(app, repository)
79
- route_start(app, repository)
65
+ route_start(app, repository, config_accessor())
80
66
 
81
67
  app.register_blueprint(make_auth_blueprint(authenticator), url_prefix="/auth")
82
68
 
@@ -111,7 +97,11 @@ def web_ui_main(
111
97
  url_prefix="/square-planner",
112
98
  )
113
99
  app.register_blueprint(
114
- make_summary_blueprint(repository),
100
+ make_search_blueprint(repository),
101
+ url_prefix="/search",
102
+ )
103
+ app.register_blueprint(
104
+ make_summary_blueprint(repository, config_accessor()),
115
105
  url_prefix="/summary",
116
106
  )
117
107
  app.register_blueprint(make_tile_blueprint(), url_prefix="/tile")
@@ -14,9 +14,6 @@ class Authenticator:
14
14
  self._config = config
15
15
 
16
16
  def is_authenticated(self) -> bool:
17
- print(
18
- f"Password={self._config.upload_password}, Session={session.get('is_authenticated', False)}"
19
- )
20
17
  return not self._config.upload_password or session.get(
21
18
  "is_authenticated", False
22
19
  )
@@ -6,18 +6,22 @@ import pandas as pd
6
6
 
7
7
  from geo_activity_playground.core.activities import ActivityRepository
8
8
  from geo_activity_playground.core.activities import make_geojson_from_time_series
9
+ from geo_activity_playground.core.config import Config
10
+ from geo_activity_playground.webui.plot_util import make_kind_scale
9
11
 
10
12
 
11
13
  class EntryController:
12
- def __init__(self, repository: ActivityRepository) -> None:
14
+ def __init__(self, repository: ActivityRepository, config: Config) -> None:
13
15
  self._repository = repository
16
+ self._config = config
14
17
 
15
18
  def render(self) -> dict:
19
+ kind_scale = make_kind_scale(self._repository.meta, self._config)
16
20
  result = {"latest_activities": []}
17
21
 
18
22
  if len(self._repository):
19
23
  result["distance_last_30_days_plot"] = distance_last_30_days_meta_plot(
20
- self._repository.meta
24
+ self._repository.meta, kind_scale
21
25
  )
22
26
 
23
27
  for activity in itertools.islice(
@@ -33,7 +37,7 @@ class EntryController:
33
37
  return result
34
38
 
35
39
 
36
- def distance_last_30_days_meta_plot(meta: pd.DataFrame) -> str:
40
+ def distance_last_30_days_meta_plot(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
37
41
  before_30_days = pd.to_datetime(
38
42
  datetime.datetime.now() - datetime.timedelta(days=31)
39
43
  )
@@ -48,7 +52,7 @@ def distance_last_30_days_meta_plot(meta: pd.DataFrame) -> str:
48
52
  .encode(
49
53
  alt.X("yearmonthdate(start)", title="Date"),
50
54
  alt.Y("sum(distance_km)", title="Distance / km"),
51
- alt.Color("kind", scale=alt.Scale(scheme="category10"), title="Kind"),
55
+ alt.Color("kind", scale=kind_scale, title="Kind"),
52
56
  [
53
57
  alt.Tooltip("yearmonthdate(start)", title="Date"),
54
58
  alt.Tooltip("kind", title="Kind"),
@@ -161,6 +161,7 @@ def get_three_color_tiles(
161
161
  "last_visit": tile_data["last_time"].date().isoformat(),
162
162
  "num_visits": len(tile_data["activity_ids"]),
163
163
  "square": False,
164
+ "tile": f"({zoom}, {tile[0]}, {tile[1]})",
164
165
  }
165
166
 
166
167
  # Mark biggest square.
@@ -48,6 +48,8 @@
48
48
  function onEachFeature(feature, layer) {
49
49
  if (feature.properties && feature.properties.first_visit) {
50
50
  let lines = [
51
+ `<dt>Tile</dt>`,
52
+ `<dd>${feature.properties.tile}</dd>`,
51
53
  `<dt>First visit</dt>`,
52
54
  `<dd>${feature.properties.first_visit}</br><a href=/activity/${feature.properties.first_activity_id}>${feature.properties.first_activity_name}</a></dd>`,
53
55
  `<dt>Last visit</dt>`,
@@ -123,6 +123,7 @@ class HeatmapController:
123
123
  tile_counts += aim
124
124
  tmp_path = tile_count_cache_path.with_suffix(".tmp.npy")
125
125
  np.save(tmp_path, tile_counts)
126
+ tile_count_cache_path.unlink(missing_ok=True)
126
127
  tmp_path.rename(tile_count_cache_path)
127
128
  return tile_counts
128
129
 
@@ -0,0 +1,9 @@
1
+ import altair as alt
2
+ import pandas as pd
3
+
4
+ from geo_activity_playground.core.config import Config
5
+
6
+
7
+ def make_kind_scale(meta: pd.DataFrame, config: Config) -> alt.Scale:
8
+ kinds = sorted(meta["kind"].unique())
9
+ return alt.Scale(domain=kinds, scheme=config.color_scheme_for_kind)
@@ -0,0 +1,20 @@
1
+ from flask import Blueprint
2
+ from flask import render_template
3
+ from flask import request
4
+ from flask import Response
5
+
6
+ from ...core.activities import ActivityRepository
7
+
8
+
9
+ def make_search_blueprint(repository: ActivityRepository) -> Blueprint:
10
+ blueprint = Blueprint("search", __name__, template_folder="templates")
11
+
12
+ @blueprint.route("/", methods=["POST"])
13
+ def index():
14
+ activities = []
15
+ for _, row in repository.meta.iterrows():
16
+ if request.form["name"] in row["name"]:
17
+ activities.append(row)
18
+ return render_template("search/index.html.j2", activities=activities)
19
+
20
+ return blueprint
@@ -42,6 +42,73 @@ def make_settings_blueprint(
42
42
  **settings_controller.render_admin_password(),
43
43
  )
44
44
 
45
+ @blueprint.route("/color-schemes", methods=["GET", "POST"])
46
+ @needs_authentication(authenticator)
47
+ def color_schemes():
48
+ if request.method == "POST":
49
+ config_accessor().color_scheme_for_counts = request.form[
50
+ "color_scheme_for_counts"
51
+ ]
52
+ config_accessor().color_scheme_for_kind = request.form[
53
+ "color_scheme_for_kind"
54
+ ]
55
+ config_accessor.save()
56
+ flash("Updated color schemes.", category="success")
57
+ return render_template(
58
+ "settings/color-schemes.html.j2",
59
+ color_scheme_for_counts=config_accessor().color_scheme_for_counts,
60
+ color_scheme_for_counts_avail=[
61
+ "viridis",
62
+ "magma",
63
+ "inferno",
64
+ "plasma",
65
+ "cividis",
66
+ "turbo",
67
+ "bluegreen",
68
+ "bluepurple",
69
+ "goldgreen",
70
+ "goldorange",
71
+ "goldred",
72
+ "greenblue",
73
+ "orangered",
74
+ "purplebluegreen",
75
+ "purpleblue",
76
+ "purplered",
77
+ "redpurple",
78
+ "yellowgreenblue",
79
+ "yellowgreen",
80
+ "yelloworangebrown",
81
+ "yelloworangered",
82
+ "darkblue",
83
+ "darkgold",
84
+ "darkgreen",
85
+ "darkmulti",
86
+ "darkred",
87
+ "lightgreyred",
88
+ "lightgreyteal",
89
+ "lightmulti",
90
+ "lightorange",
91
+ "lighttealblue",
92
+ ],
93
+ color_scheme_for_kind=config_accessor().color_scheme_for_kind,
94
+ color_scheme_for_kind_avail=[
95
+ "accent",
96
+ "category10",
97
+ "category20",
98
+ "category20b",
99
+ "category20c",
100
+ "dark2",
101
+ "paired",
102
+ "pastel1",
103
+ "pastel2",
104
+ "set1",
105
+ "set2",
106
+ "set3",
107
+ "tableau10",
108
+ "tableau20",
109
+ ],
110
+ )
111
+
45
112
  @blueprint.route("/equipment-offsets", methods=["GET", "POST"])
46
113
  @needs_authentication(authenticator)
47
114
  def equipment_offsets():
@@ -0,0 +1,33 @@
1
+ {% extends "page.html.j2" %}
2
+
3
+ {% block container %}
4
+
5
+ <h1 class="mb-3">Color Schemes for Plots</h1>
6
+
7
+ <p>Don't like color schemes in the plots? Have a look at the <a href="https://vega.github.io/vega/docs/schemes/"
8
+ target="_blank">colors schemes of Vega</a> and pick one that you like.</p>
9
+
10
+ <form method="POST">
11
+ <div class="mb-3">
12
+ <label class="form-label">Color scheme for activity kinds</label>
13
+ <select class="form-select" aria-label="Color scheme for activity kinds" name="color_scheme_for_kind">
14
+ {% for cs in color_scheme_for_kind_avail %}
15
+ <option {% if cs==color_scheme_for_kind %} selected {% endif %}>{{ cs }}</option>
16
+ {% endfor %}
17
+ </select>
18
+ </div>
19
+
20
+ <div class="mb-3">
21
+ <label class="form-label">Color scheme for heatmaps</label>
22
+ <select class="form-select" aria-label="Color scheme for heatmaps" name="color_scheme_for_counts">
23
+ {% for cs in color_scheme_for_counts_avail %}
24
+ <option {% if cs==color_scheme_for_counts %} selected {% endif %}>{{ cs }}</option>
25
+ {% endfor %}
26
+ </select>
27
+ </div>
28
+
29
+ <button type="submit" class="btn btn-primary">Save</button>
30
+ </form>
31
+
32
+
33
+ {% endblock %}
@@ -20,6 +20,15 @@
20
20
  </div>
21
21
  </div>
22
22
  </div>
23
+ <div class="col">
24
+ <div class="card">
25
+ <div class="card-body">
26
+ <h5 class="card-title">Color schemes</h5>
27
+ <p class="card-text">Don't like the colors in the plots?</p>
28
+ <a href="{{ url_for('.color_schemes') }}" class="btn btn-primary">Set up color schemes</a>
29
+ </div>
30
+ </div>
31
+ </div>
23
32
  <div class="col">
24
33
  <div class="card">
25
34
  <div class="card-body">
@@ -3,10 +3,11 @@ from flask import render_template
3
3
 
4
4
  from ...core.activities import ActivityRepository
5
5
  from .controller import SummaryController
6
+ from geo_activity_playground.core.config import Config
6
7
 
7
8
 
8
- def make_summary_blueprint(repository: ActivityRepository) -> Blueprint:
9
- summary_controller = SummaryController(repository)
9
+ def make_summary_blueprint(repository: ActivityRepository, config: Config) -> Blueprint:
10
+ summary_controller = SummaryController(repository, config)
10
11
  blueprint = Blueprint("summary", __name__, template_folder="templates")
11
12
 
12
13
  @blueprint.route("/")
@@ -8,14 +8,18 @@ import pandas as pd
8
8
 
9
9
  from geo_activity_playground.core.activities import ActivityRepository
10
10
  from geo_activity_playground.core.activities import make_geojson_from_time_series
11
+ from geo_activity_playground.core.config import Config
12
+ from geo_activity_playground.webui.plot_util import make_kind_scale
11
13
 
12
14
 
13
15
  class SummaryController:
14
- def __init__(self, repository: ActivityRepository) -> None:
16
+ def __init__(self, repository: ActivityRepository, config: Config) -> None:
15
17
  self._repository = repository
18
+ self._config = config
16
19
 
17
20
  @functools.cache
18
21
  def render(self) -> dict:
22
+ kind_scale = make_kind_scale(self._repository.meta, self._config)
19
23
  df = embellished_activities(self._repository.meta)
20
24
  df = df.loc[df["consider_for_achievements"]]
21
25
 
@@ -27,14 +31,14 @@ class SummaryController:
27
31
  )
28
32
 
29
33
  return {
30
- "plot_distance_heatmap": plot_distance_heatmap(df),
31
- "plot_monthly_distance": plot_monthly_distance(df),
32
- "plot_yearly_distance": plot_yearly_distance(year_kind_total),
34
+ "plot_distance_heatmap": plot_distance_heatmap(df, self._config),
35
+ "plot_monthly_distance": plot_monthly_distance(df, kind_scale),
36
+ "plot_yearly_distance": plot_yearly_distance(year_kind_total, kind_scale),
33
37
  "plot_year_cumulative": plot_year_cumulative(df),
34
38
  "tabulate_year_kind_mean": tabulate_year_kind_mean(df)
35
39
  .reset_index()
36
40
  .to_dict(orient="split"),
37
- "plot_weekly_distance": plot_weekly_distance(df),
41
+ "plot_weekly_distance": plot_weekly_distance(df, kind_scale),
38
42
  "nominations": [
39
43
  (
40
44
  self._repository.get_activity_by_id(activity_id),
@@ -108,7 +112,7 @@ def embellished_activities(meta: pd.DataFrame) -> pd.DataFrame:
108
112
  return df
109
113
 
110
114
 
111
- def plot_distance_heatmap(meta: pd.DataFrame) -> str:
115
+ def plot_distance_heatmap(meta: pd.DataFrame, config: Config) -> str:
112
116
  return (
113
117
  alt.Chart(
114
118
  meta.loc[
@@ -129,7 +133,10 @@ def plot_distance_heatmap(meta: pd.DataFrame) -> str:
129
133
  scale=alt.Scale(reverse=True),
130
134
  title="Year and month",
131
135
  ),
132
- alt.Color("sum(distance_km)", scale=alt.Scale(scheme="viridis")),
136
+ alt.Color(
137
+ "sum(distance_km)",
138
+ scale=alt.Scale(scheme=config.color_scheme_for_counts),
139
+ ),
133
140
  [
134
141
  alt.Tooltip("yearmonthdate(start)", title="Date"),
135
142
  alt.Tooltip(
@@ -142,7 +149,7 @@ def plot_distance_heatmap(meta: pd.DataFrame) -> str:
142
149
  )
143
150
 
144
151
 
145
- def plot_monthly_distance(meta: pd.DataFrame) -> str:
152
+ def plot_monthly_distance(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
146
153
  return (
147
154
  alt.Chart(
148
155
  meta.loc[
@@ -159,7 +166,7 @@ def plot_monthly_distance(meta: pd.DataFrame) -> str:
159
166
  .encode(
160
167
  alt.X("month(start)", title="Month"),
161
168
  alt.Y("sum(distance_km)", title="Distance / km"),
162
- alt.Color("kind", scale=alt.Scale(scheme="category10"), title="Kind"),
169
+ alt.Color("kind", scale=kind_scale, title="Kind"),
163
170
  alt.Column("year(start):O", title="Year"),
164
171
  )
165
172
  .resolve_axis(x="independent")
@@ -167,14 +174,14 @@ def plot_monthly_distance(meta: pd.DataFrame) -> str:
167
174
  )
168
175
 
169
176
 
170
- def plot_yearly_distance(year_kind_total: pd.DataFrame) -> str:
177
+ def plot_yearly_distance(year_kind_total: pd.DataFrame, kind_scale: alt.Scale) -> str:
171
178
  return (
172
179
  alt.Chart(year_kind_total, title="Total Distance per Year")
173
180
  .mark_bar()
174
181
  .encode(
175
182
  alt.X("year:O", title="Year"),
176
183
  alt.Y("distance_km", title="Distance / km"),
177
- alt.Color("kind", title="Kind"),
184
+ alt.Color("kind", scale=kind_scale, title="Kind"),
178
185
  [
179
186
  alt.Tooltip("year:O", title="Year"),
180
187
  alt.Tooltip("kind", title="Kind"),
@@ -231,7 +238,7 @@ def tabulate_year_kind_mean(df: pd.DataFrame) -> pd.DataFrame:
231
238
  return year_kind_mean_distance
232
239
 
233
240
 
234
- def plot_weekly_distance(df: pd.DataFrame) -> str:
241
+ def plot_weekly_distance(df: pd.DataFrame, kind_scale: alt.Scale) -> str:
235
242
  week_kind_total_distance = (
236
243
  df[["year", "week", "kind", "distance_km"]]
237
244
  .groupby(["year", "week", "kind"])
@@ -261,7 +268,7 @@ def plot_weekly_distance(df: pd.DataFrame) -> str:
261
268
  .encode(
262
269
  alt.X("year_week", title="Year and Week"),
263
270
  alt.Y("distance_km", title="Distance / km"),
264
- alt.Color("kind", title="Kind"),
271
+ alt.Color("kind", scale=kind_scale, title="Kind"),
265
272
  [
266
273
  alt.Tooltip("year_week", title="Year and Week"),
267
274
  alt.Tooltip("kind", title="Kind"),
@@ -12,7 +12,7 @@
12
12
  </div>
13
13
  <div class="col-md-3">
14
14
  <h2>Search activities</h2>
15
- <form method="post" action="/search">
15
+ <form method="post" action="{{ url_for('search.index') }}">
16
16
  <input type="search" name="name" />
17
17
  <input type="submit" class="button" />
18
18
  </form>
@@ -62,42 +62,70 @@
62
62
  <ul class="navbar-nav me-auto mb-2 mb-lg-0">
63
63
 
64
64
  {% if num_activities > 0 %}
65
- <li class="nav-item">
66
- <a class="nav-link" aria-current="page" href="{{ url_for('summary.index') }}">Summary</a>
67
- </li>
68
- <li class="nav-item">
69
- <a class="nav-link" aria-current="page" href="{{ url_for('calendar.index') }}">Calendar</a>
65
+ <li class="nav-item dropdown">
66
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
67
+ aria-expanded="false">
68
+ Activities
69
+ </a>
70
+ <ul class="dropdown-menu">
71
+ <li><a class="dropdown-item" href="{{ url_for('calendar.index') }}">Calendar</a></li>
72
+ {# <li><a class="dropdown-item" href="{{ url_for('search.index') }}">Search</a></li> #}
73
+ </ul>
70
74
  </li>
71
- <li class="nav-item">
72
- <a class="nav-link" aria-current="page"
73
- href="{{ url_for('explorer.map', zoom=14) }}">Explorer</a>
75
+
76
+ <li class="nav-item dropdown">
77
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
78
+ aria-expanded="false">
79
+ Statistics
80
+ </a>
81
+ <ul class="dropdown-menu">
82
+ <li><a class="dropdown-item" href="{{ url_for('summary.index') }}">Summary
83
+ Statistics</a>
84
+ </li>
85
+ <li><a class="dropdown-item" href="{{ url_for('eddington.index') }}">Eddington
86
+ Number</a>
87
+ </li>
88
+ <li><a class="dropdown-item" href="{{ url_for('equipment.index') }}">Equipment</a></li>
89
+ </ul>
74
90
  </li>
75
- <li class="nav-item">
76
- <a class="nav-link" aria-current="page"
77
- href="{{ url_for('explorer.map', zoom=17) }}">Squadratinhos</a>
91
+
92
+ <li class="nav-item dropdown">
93
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
94
+ aria-expanded="false">
95
+ Explorer
96
+ </a>
97
+ <ul class="dropdown-menu">
98
+ <li><a class="dropdown-item" href="{{ url_for('explorer.map', zoom=14) }}">Explorer
99
+ Tiles (Zoom 14)</a></li>
100
+ <li><a class="dropdown-item" href="{{ url_for('explorer.map', zoom=17) }}">Squadratinhos
101
+ (Zoom 17)</a></li>
102
+ </ul>
78
103
  </li>
104
+
79
105
  <li class="nav-item">
80
106
  <a class="nav-link" aria-current="page" href="{{ url_for('heatmap.index') }}">Heatmap</a>
81
107
  </li>
82
- <li class="nav-item">
83
- <a class="nav-link" aria-current="page"
84
- href="{{ url_for('eddington.index') }}">Eddington</a>
85
- </li>
86
- <li class="nav-item">
87
- <a class="nav-link" aria-current="page"
88
- href="{{ url_for('equipment.index') }}">Equipment</a>
89
- </li>
90
108
  {% endif %}
91
- <li class="nav-item">
92
- <a class="nav-link" aria-current="page" href="{{ url_for('upload.index') }}">Upload</a>
93
- </li>
94
- <li class="nav-item">
95
- <a class="nav-link" aria-current="page" href="{{ url_for('upload.reload') }}">Refresh</a>
96
- </li>
97
- <li class="nav-item">
98
- <a class="nav-link" aria-current="page" href="{{ url_for('settings.index') }}">Settings</a>
99
- </li>
100
109
 
110
+ <li class="nav-item dropdown">
111
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
112
+ aria-expanded="false">
113
+ Admin
114
+ </a>
115
+ <ul class="dropdown-menu">
116
+ <li><a class="dropdown-item" href="{{ url_for('upload.index') }}">Upload Activities</a>
117
+ </li>
118
+ <li><a class="dropdown-item" href="{{ url_for('upload.reload') }}">Scan New
119
+ Activities</a>
120
+ </li>
121
+
122
+ <li>
123
+ <hr class="dropdown-divider">
124
+ </li>
125
+
126
+ <li><a class="dropdown-item" href="{{ url_for('settings.index') }}">Settings</a></li>
127
+ </ul>
128
+ </li>
101
129
 
102
130
  <li class="nav-item dropdown">
103
131
  <button
@@ -1,14 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geo-activity-playground
3
- Version: 0.28.0
3
+ Version: 0.29.0
4
4
  Summary: Analysis of geo data activities like rides, runs or hikes.
5
5
  License: MIT
6
6
  Author: Martin Ueding
7
7
  Author-email: mu@martin-ueding.de
8
- Requires-Python: >=3.9,<3.13
8
+ Requires-Python: >=3.10,<3.13
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.9
12
11
  Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
@@ -30,7 +29,7 @@ Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
30
29
  Requires-Dist: requests (>=2.28.1,<3.0.0)
31
30
  Requires-Dist: scipy (>=1.8.1,<2.0.0)
32
31
  Requires-Dist: shapely (>=2.0.5,<3.0.0)
33
- Requires-Dist: stravalib (>=1.3.3,<2.0.0)
32
+ Requires-Dist: stravalib (>=2.0,<3.0)
34
33
  Requires-Dist: tcxreader (>=0.4.5,<0.5.0)
35
34
  Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
36
35
  Requires-Dist: tqdm (>=4.64.0,<5.0.0)
@@ -2,22 +2,22 @@ geo_activity_playground/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
2
2
  geo_activity_playground/__main__.py,sha256=MBZn9K1m3PofiPNTtpsSTVCyB_Gz95TjVP-nb9v_-JE,3989
3
3
  geo_activity_playground/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  geo_activity_playground/core/activities.py,sha256=yDjf3aVOC3LvnAwAvQUl_e7S3NXq_vKgccq0hGuFdLI,6609
5
- geo_activity_playground/core/config.py,sha256=uys4O2OfXC6yNAsgbgm72WGIhy903zomr_SOwk4GNmA,4498
5
+ geo_activity_playground/core/config.py,sha256=DsjZhiq2BZs94rjd7zgt_KTZIjVfxfntiEQZhgq8NeA,4617
6
6
  geo_activity_playground/core/coordinates.py,sha256=tDfr9mlXhK6E_MMIJ0vYWVCoH0Lq8uyuaqUgaa8i0jg,966
7
7
  geo_activity_playground/core/enrichment.py,sha256=CwZhW-svgPAYbdx3n9kvKlTgcsiCaeuJfSRCC4JxX6g,7411
8
8
  geo_activity_playground/core/heart_rate.py,sha256=IwMt58TpjOYqpAxtsj07zP2ttpN_J3GZeiv-qGhYyJc,1598
9
9
  geo_activity_playground/core/heatmap.py,sha256=bRLQHzmTEsQbX8XWeg85x_lRGk272UoYRiCnoxZ5da0,4189
10
- geo_activity_playground/core/paths.py,sha256=AiYUJv46my_FGYbHZmSs5ZrqeE65GNdWEMmXZgunZrk,2150
10
+ geo_activity_playground/core/paths.py,sha256=BZYuIg1LVHjuWLKB0Iz6Cevlq-XSalpCes_ClFuXa0s,2410
11
11
  geo_activity_playground/core/privacy_zones.py,sha256=4TumHsVUN1uW6RG3ArqTXDykPVipF98DCxVBe7YNdO8,512
12
12
  geo_activity_playground/core/similarity.py,sha256=Jo8jRViuORCxdIGvyaflgsQhwu9S_jn10a450FRL18A,3159
13
- geo_activity_playground/core/tasks.py,sha256=f3C2gCJiiqMS1eRQ-Hp5hxRbkwl60rotrYfzKsWdpSU,2937
13
+ geo_activity_playground/core/tasks.py,sha256=aMDBWJqp6ek2ao6G6Xa8GOSZbcQqXoWL74SGRowRPIk,2942
14
14
  geo_activity_playground/core/test_tiles.py,sha256=zce1FxNfsSpOQt66jMehdQRVoNdl-oiFydx6iVBHZXM,764
15
15
  geo_activity_playground/core/test_time_conversion.py,sha256=Sh6nZA3uCTOdZTZa3yOijtR0m74QtZu2mcWXsDNnyQI,984
16
16
  geo_activity_playground/core/tiles.py,sha256=KpzD-h3kNzZ2ieLt6f2xHilSF3lHyfaEXPnrGvlIAz0,3379
17
17
  geo_activity_playground/core/time_conversion.py,sha256=9J6aTlqJhWvsknQkoECNL-CIG-8BKs6ZatJJ9XJnTsg,367
18
18
  geo_activity_playground/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  geo_activity_playground/explorer/grid_file.py,sha256=k6j6KBEk2a2BY-onE8SV5TJsERGGyOrlY4as__meWpA,3304
20
- geo_activity_playground/explorer/tile_visits.py,sha256=IqkaGPq-xrmR81Ze31VL1v4NUxQ2YGQeQawvsjYe25s,12776
20
+ geo_activity_playground/explorer/tile_visits.py,sha256=VaenegdPQ51vXxvn2oQXj8-3A7t8ihMhkgV8HpAqEuI,14133
21
21
  geo_activity_playground/explorer/video.py,sha256=ROAmV9shfJyqTgnXVD41KFORiwnRgVpEWenIq4hMCRM,4389
22
22
  geo_activity_playground/importers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  geo_activity_playground/importers/activity_parsers.py,sha256=m2SpvGlTZ8F3gG6YB24_ZFrlOAbtqbfWi-GIYspeUco,10593
@@ -31,15 +31,15 @@ geo_activity_playground/importers/test_strava_api.py,sha256=4vX7wDr1a9aRh8myxNrI
31
31
  geo_activity_playground/webui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  geo_activity_playground/webui/activity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  geo_activity_playground/webui/activity/blueprint.py,sha256=upQzZa5sKApj_Fmu6PziFDboi7SBL5Zsi-tNSSNPlEE,1759
34
- geo_activity_playground/webui/activity/controller.py,sha256=YaWHXIfSYSeybEFY706s_m4uHbfJ9FHxqIYtqwR5sGQ,17955
34
+ geo_activity_playground/webui/activity/controller.py,sha256=PJIZ7pFqpUyWDrintnnlW6Hxj7DbdPswARozws6TE30,18861
35
35
  geo_activity_playground/webui/activity/templates/activity/day.html.j2,sha256=r3qKl9uTzOko4R-ZzyYAZt1j61JSevYP4g0Yi06HHPg,2702
36
36
  geo_activity_playground/webui/activity/templates/activity/lines.html.j2,sha256=5gB1aDjRgi_RventenRfC10_FtMT4ch_VuWvA9AMlBY,1121
37
37
  geo_activity_playground/webui/activity/templates/activity/name.html.j2,sha256=RDLEt6ip8_ngmdLgaC5jg92Dk-F2umGwKkd8cWmvVko,2400
38
- geo_activity_playground/webui/activity/templates/activity/show.html.j2,sha256=6k3_MC_AR_sSWVYvH53E5o6YFtE9HZD62NlPf90Rj8Q,5509
39
- geo_activity_playground/webui/app.py,sha256=Vn8G6wf2YSKc_n7JBkulry51Gl57_UjrAW0TftXLQgM,4456
38
+ geo_activity_playground/webui/activity/templates/activity/show.html.j2,sha256=W77M1S7RQOGY3Vg9LRT5mFnefuMWUKKU1Vd-ZKxUoKg,6552
39
+ geo_activity_playground/webui/app.py,sha256=foON49jw8klBJ3GF70DDjgz-_KRDPFvC31_mScaqDXk,4255
40
40
  geo_activity_playground/webui/auth/blueprint.py,sha256=Lx-ZvMnfHLC1CMre1xPQI3k_pCtQoZvgRhtmafULzoE,812
41
41
  geo_activity_playground/webui/auth/templates/auth/index.html.j2,sha256=ILQ5HvTEYc3OrtOAIFt1VrqWorVD70V9DC342znmP70,579
42
- geo_activity_playground/webui/authenticator.py,sha256=jXwWJFuinSE-7sQxdi7s7WO9ifvERmeXAM5-hsAPmwQ,1546
42
+ geo_activity_playground/webui/authenticator.py,sha256=k278OEVuOfAmTGT4F2X4pqSTwwkK_FA87EIhAeysEqc,1416
43
43
  geo_activity_playground/webui/calendar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
44
  geo_activity_playground/webui/calendar/blueprint.py,sha256=rlnhgU2DWAcdLMRq7m77NzrM_aDyp4s3kuuQHuzjHhg,782
45
45
  geo_activity_playground/webui/calendar/controller.py,sha256=QpSAkR2s1sbLSu6P_fNNTccgGglOzEH2PIv1XwKxeVY,2778
@@ -49,26 +49,29 @@ geo_activity_playground/webui/eddington/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
49
49
  geo_activity_playground/webui/eddington/blueprint.py,sha256=evIvueLfDWVTxJ9pRguqmZ9-Pybd2WmBRst_-7vX2QA,551
50
50
  geo_activity_playground/webui/eddington/controller.py,sha256=ly7JSkSS79kO4CL_xugB62uRuuWKVqOjbN-pheelv94,2910
51
51
  geo_activity_playground/webui/eddington/templates/eddington/index.html.j2,sha256=XHKeUymQMS5x00PLOVlg-nSRCz_jHB2pvD8QunULWJ4,1839
52
- geo_activity_playground/webui/entry_controller.py,sha256=n9v4MriyL8kDR91LE9eeqc2tAvxyzFgoNMMXpr0qh4g,1906
52
+ geo_activity_playground/webui/entry_controller.py,sha256=kTEToBtR1T4l30cV3HkCK3KO2hVYfb22BSgcWLdLEXQ,2164
53
53
  geo_activity_playground/webui/equipment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  geo_activity_playground/webui/equipment/blueprint.py,sha256=_NIhRJuJNbXpEd_nEPo01AqnUqPgo1vawFn7E3yoeng,636
55
55
  geo_activity_playground/webui/equipment/controller.py,sha256=Sx9i2RCK7m4W6FgpDfRMewcH64VBQfUhHJdTSCwMqOU,4079
56
56
  geo_activity_playground/webui/equipment/templates/equipment/index.html.j2,sha256=FEfxB4XwVYELAOdjVlSlprjJH_kLmE-pNWEEXdPqc6I,1778
57
57
  geo_activity_playground/webui/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  geo_activity_playground/webui/explorer/blueprint.py,sha256=EKnBs8llqT6Wy1uac18dF2epp3TebF9p3iGlSbj6Vl0,2337
59
- geo_activity_playground/webui/explorer/controller.py,sha256=-iMsuB05JgUYKahJxiOPZeNVmzr_Uw2psHwmaFiptkI,11614
60
- geo_activity_playground/webui/explorer/templates/explorer/index.html.j2,sha256=cm9pWY0vB84DtkTH-LBvSzfLU1FnmxQ2ECyw3Bl7dTo,6945
59
+ geo_activity_playground/webui/explorer/controller.py,sha256=PAVyewO5ZY8BM1-EwtiqZuU-u63BJYuAKNNEDNutJkw,11669
60
+ geo_activity_playground/webui/explorer/templates/explorer/index.html.j2,sha256=u2htecx-XwINRiINHFN6EZDaRXVtiape1OCUZexTBU8,7049
61
61
  geo_activity_playground/webui/heatmap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  geo_activity_playground/webui/heatmap/blueprint.py,sha256=bjQu-HL3QN5UpJ6tHOifhcLGlPr_hIKvaRu78md4JqM,1470
63
- geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256=Bn7FO_ciMEXghSwz2tEQ8fJsbK8AXaHTPNgnGuQ7an8,7353
63
+ geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256=Q17Ay8hbU5ZlUiz2a9S-ULWrnNGWQHvTTV3kDY5FhNc,7411
64
64
  geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2,sha256=YLeu6P4djl8G4qAXR6DhetseqrbOodN7aN4coocknc4,1875
65
- geo_activity_playground/webui/search_controller.py,sha256=PzMf7b8tiJKZIZoPvQ9A2hOrzoKV9cS3jq05w2fK94c,532
66
- geo_activity_playground/webui/settings/blueprint.py,sha256=boKeKkG4bbSA7HvgpaqlUXzrcCnOPwys6KWw-Dp5sAE,5416
65
+ geo_activity_playground/webui/plot_util.py,sha256=pTTQoqOCkLVjkgOit7mbry28kMruZIL8amZozSzEpxQ,283
66
+ geo_activity_playground/webui/search/blueprint.py,sha256=b3TCIplY60MWE2_VsKHuoV1LAgNwd_T5ft5t0CKALFI,642
67
+ geo_activity_playground/webui/search/templates/search/index.html.j2,sha256=FvNRoDfUlSzXjM_tqZY_fDhuhUDgbPaY73q56gdvF1A,1130
68
+ geo_activity_playground/webui/settings/blueprint.py,sha256=u5VGzVoRRA9Am5WqC1VGod4L-k839_9qVlecKO9sRUE,7593
67
69
  geo_activity_playground/webui/settings/controller.py,sha256=v14oKvI1QzWn0g41Sm4NA_g9q4SUVZ_bO9SUOZuPAaY,9121
68
70
  geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2,sha256=VYwddpObD1RpeTH5Dm4y7VtmT7kwURDCIjxyzJeq08c,495
71
+ geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2,sha256=CaFbYkkU1yGTOlAzGq97u3tVgS79RIo7PEmiVjuZiBc,1226
69
72
  geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2,sha256=ltaYwFe8S8Mi72ddmIp1vwqlu8MEXXjBGfbpN2WBTC4,1728
70
73
  geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2,sha256=UPT3MegRgSeff36lhCo0l3ZwhqNSIg5gM6h2s32GkCY,4255
71
- geo_activity_playground/webui/settings/templates/settings/index.html.j2,sha256=rXKq2v42J0eW5OSWx7VU6EFT9jZkbzKTW600Y0OqBtY,4035
74
+ geo_activity_playground/webui/settings/templates/settings/index.html.j2,sha256=-ZSlR6htaeMMOf4ISUSzWPu5BUhYODuAmcWPN0ZoBno,4443
72
75
  geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2,sha256=IdUfXon1Pu8zX3NirKb28ypshLHOvZRpz2T4bJrzrak,1067
73
76
  geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2,sha256=Ppa8O-zRJznbeCsF4YQj37_HM9nOW8fyTi66jvWvHmA,2285
74
77
  geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2,sha256=7BxFvCaVJOEqbImyK5vxCmhh-NGSFaRa9ARhqjZeYJ0,3093
@@ -91,12 +94,11 @@ geo_activity_playground/webui/static/mstile-150x150.png,sha256=j1ANUQJ1Xi1DR2sGq
91
94
  geo_activity_playground/webui/static/safari-pinned-tab.svg,sha256=OzoEVGY0igWRXM1NiM3SRKugdICBN7aB_XuxaC3Mu9Q,8371
92
95
  geo_activity_playground/webui/static/site.webmanifest,sha256=4vYxdPMpwTdB8EmOvHkkYcjZ8Yrci3pOwwY3o_VwACA,440
93
96
  geo_activity_playground/webui/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
- geo_activity_playground/webui/summary/blueprint.py,sha256=kzQ6MDOycQKfDcVoEUmL7HYHJA_gu8DlzVHwO37-_jA,514
95
- geo_activity_playground/webui/summary/controller.py,sha256=ZOrwfrKjpc8hecUYImBvesKXZi06obfR1yhQkVTeWzw,8981
97
+ geo_activity_playground/webui/summary/blueprint.py,sha256=tfA2aPF19yKwkQOb5lPQBySoQYYhTn49Iuh0SYvsGP8,593
98
+ geo_activity_playground/webui/summary/controller.py,sha256=cWn5szA1o5Vjht0DyhRwBjmwqJryrLcmm4FUdmVpUoo,9443
96
99
  geo_activity_playground/webui/summary/templates/summary/index.html.j2,sha256=rsII1eMY-xNugh8A9SecnEcDZqkEOWYIfiHAGroQYuM,4442
97
- geo_activity_playground/webui/templates/home.html.j2,sha256=FjEwr9kt_3qu_evIHpa7F_oGAINN8W2Z1T_j56ugJ5c,2406
98
- geo_activity_playground/webui/templates/page.html.j2,sha256=8DkXaXx_cc9FvfI6eo9z3fbeNDRcvJC2NXcHXs8ytSU,9588
99
- geo_activity_playground/webui/templates/search.html.j2,sha256=FvNRoDfUlSzXjM_tqZY_fDhuhUDgbPaY73q56gdvF1A,1130
100
+ geo_activity_playground/webui/templates/home.html.j2,sha256=eIPNyLHhUNVTITDbn6nR82-ZJA5Dp4SY41cZTjymZDU,2428
101
+ geo_activity_playground/webui/templates/page.html.j2,sha256=HN7s4i4kR3laMFkToktjwmTQiTHdrY3nqKcCVO9nKdA,11088
100
102
  geo_activity_playground/webui/tile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
103
  geo_activity_playground/webui/tile/blueprint.py,sha256=cK0o2Z3BrLycgF9zw0F8s9qF-JaYDbF5Gog-GXDtUZ8,943
102
104
  geo_activity_playground/webui/tile/controller.py,sha256=PISh4vKs27b-LxFfTARtr5RAwHFresA1Kw1MDcERSRU,1221
@@ -105,8 +107,8 @@ geo_activity_playground/webui/upload/blueprint.py,sha256=xX9scEmleN_dL03jfhWRh5y
105
107
  geo_activity_playground/webui/upload/controller.py,sha256=disRtrlvPiqsIFt9UaAokgtRtXCvosg7bXkAnN_qaxk,4102
106
108
  geo_activity_playground/webui/upload/templates/upload/index.html.j2,sha256=I1Ix8tDS3YBdi-HdaNfjkzYXVVCjfUTe5PFTnap1ydc,775
107
109
  geo_activity_playground/webui/upload/templates/upload/reload.html.j2,sha256=YZWX5eDeNyqKJdQAywDBcU8DZBm22rRBbZqFjrFrCvQ,556
108
- geo_activity_playground-0.28.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
109
- geo_activity_playground-0.28.0.dist-info/METADATA,sha256=0ZheRNfyebSkkcFUph4A3Wo3EheMmw0c0U2fi9C-MWc,1665
110
- geo_activity_playground-0.28.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
111
- geo_activity_playground-0.28.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
112
- geo_activity_playground-0.28.0.dist-info/RECORD,,
110
+ geo_activity_playground-0.29.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
111
+ geo_activity_playground-0.29.0.dist-info/METADATA,sha256=692u7MhLYbWTJR5OSwKqyS8sZDG3oq3TNZQyPIsHVlk,1612
112
+ geo_activity_playground-0.29.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
113
+ geo_activity_playground-0.29.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
114
+ geo_activity_playground-0.29.0.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- import logging
2
-
3
- from ..core.activities import ActivityRepository
4
-
5
- logger = logging.getLogger(__name__)
6
-
7
-
8
- class SearchController:
9
- def __init__(self, repository: ActivityRepository) -> None:
10
- self._repository = repository
11
-
12
- def render_search_results(self, name: str) -> dict:
13
- logger.info(f"Searching for {name=}")
14
- activities = []
15
- for _, row in self._repository.meta.iterrows():
16
- if name in row["name"]:
17
- activities.append(row)
18
-
19
- return {"activities": activities}