geo-activity-playground 0.38.2__py3-none-any.whl → 0.39.1__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 (116) hide show
  1. geo_activity_playground/__main__.py +5 -47
  2. geo_activity_playground/alembic/README +1 -0
  3. geo_activity_playground/alembic/env.py +76 -0
  4. geo_activity_playground/alembic/script.py.mako +26 -0
  5. geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py +33 -0
  6. geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py +73 -0
  7. geo_activity_playground/alembic/versions/ab83b9d23127_add_upstream_id.py +28 -0
  8. geo_activity_playground/alembic/versions/b03491c593f6_add_crop_indices.py +30 -0
  9. geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmark_name.py +28 -0
  10. geo_activity_playground/alembic/versions/script.py.mako +28 -0
  11. geo_activity_playground/core/activities.py +53 -136
  12. geo_activity_playground/core/config.py +3 -3
  13. geo_activity_playground/core/datamodel.py +257 -0
  14. geo_activity_playground/core/enrichment.py +90 -92
  15. geo_activity_playground/core/heart_rate.py +1 -2
  16. geo_activity_playground/core/parametric_plot.py +101 -0
  17. geo_activity_playground/core/paths.py +6 -7
  18. geo_activity_playground/core/raster_map.py +43 -4
  19. geo_activity_playground/core/similarity.py +1 -2
  20. geo_activity_playground/core/tasks.py +2 -2
  21. geo_activity_playground/core/test_meta_search.py +3 -3
  22. geo_activity_playground/core/test_summary_stats.py +1 -1
  23. geo_activity_playground/explorer/grid_file.py +2 -2
  24. geo_activity_playground/explorer/tile_visits.py +8 -10
  25. geo_activity_playground/heatmap_video.py +7 -8
  26. geo_activity_playground/importers/activity_parsers.py +2 -2
  27. geo_activity_playground/importers/directory.py +9 -10
  28. geo_activity_playground/importers/strava_api.py +9 -9
  29. geo_activity_playground/importers/strava_checkout.py +12 -13
  30. geo_activity_playground/importers/test_csv_parser.py +3 -3
  31. geo_activity_playground/importers/test_directory.py +1 -1
  32. geo_activity_playground/importers/test_strava_api.py +1 -1
  33. geo_activity_playground/webui/app.py +96 -86
  34. geo_activity_playground/webui/authenticator.py +1 -1
  35. geo_activity_playground/webui/{activity/controller.py → blueprints/activity_blueprint.py} +246 -108
  36. geo_activity_playground/webui/{auth_blueprint.py → blueprints/auth_blueprint.py} +1 -1
  37. geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +61 -0
  38. geo_activity_playground/webui/{calendar/controller.py → blueprints/calendar_blueprint.py} +19 -19
  39. geo_activity_playground/webui/{eddington_blueprint.py → blueprints/eddington_blueprint.py} +5 -5
  40. geo_activity_playground/webui/blueprints/entry_views.py +68 -0
  41. geo_activity_playground/webui/{equipment_blueprint.py → blueprints/equipment_blueprint.py} +37 -4
  42. geo_activity_playground/webui/{explorer/controller.py → blueprints/explorer_blueprint.py} +88 -54
  43. geo_activity_playground/webui/blueprints/heatmap_blueprint.py +233 -0
  44. geo_activity_playground/webui/blueprints/plot_builder_blueprint.py +43 -0
  45. geo_activity_playground/webui/{search_blueprint.py → blueprints/search_blueprint.py} +7 -11
  46. geo_activity_playground/webui/blueprints/settings_blueprint.py +446 -0
  47. geo_activity_playground/webui/{square_planner_blueprint.py → blueprints/square_planner_blueprint.py} +31 -6
  48. geo_activity_playground/webui/{summary_blueprint.py → blueprints/summary_blueprint.py} +11 -23
  49. geo_activity_playground/webui/blueprints/tile_blueprint.py +27 -0
  50. geo_activity_playground/webui/{upload_blueprint.py → blueprints/upload_blueprint.py} +13 -18
  51. geo_activity_playground/webui/flasher.py +26 -0
  52. geo_activity_playground/webui/plot_util.py +1 -1
  53. geo_activity_playground/webui/search_util.py +4 -6
  54. geo_activity_playground/webui/static/images/layers-2x.png +0 -0
  55. geo_activity_playground/webui/static/images/layers.png +0 -0
  56. geo_activity_playground/webui/static/images/marker-icon-2x.png +0 -0
  57. geo_activity_playground/webui/static/images/marker-icon.png +0 -0
  58. geo_activity_playground/webui/static/images/marker-shadow.png +0 -0
  59. geo_activity_playground/webui/templates/activity/day.html.j2 +81 -0
  60. geo_activity_playground/webui/templates/activity/edit.html.j2 +38 -0
  61. geo_activity_playground/webui/{activity/templates → templates}/activity/name.html.j2 +29 -27
  62. geo_activity_playground/webui/{activity/templates → templates}/activity/show.html.j2 +57 -33
  63. geo_activity_playground/webui/templates/activity/trim.html.j2 +68 -0
  64. geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +26 -0
  65. geo_activity_playground/webui/templates/calendar/index.html.j2 +48 -0
  66. geo_activity_playground/webui/templates/calendar/month.html.j2 +57 -0
  67. geo_activity_playground/webui/templates/equipment/index.html.j2 +7 -0
  68. geo_activity_playground/webui/templates/home.html.j2 +6 -6
  69. geo_activity_playground/webui/templates/page.html.j2 +2 -1
  70. geo_activity_playground/webui/templates/plot_builder/index.html.j2 +44 -0
  71. geo_activity_playground/webui/{settings/templates → templates}/settings/index.html.j2 +9 -20
  72. geo_activity_playground/webui/templates/settings/manage-equipments.html.j2 +49 -0
  73. geo_activity_playground/webui/templates/settings/manage-kinds.html.j2 +48 -0
  74. geo_activity_playground/webui/{settings/templates → templates}/settings/privacy-zones.html.j2 +2 -0
  75. geo_activity_playground/webui/{settings/templates → templates}/settings/strava.html.j2 +2 -0
  76. geo_activity_playground/webui/templates/square_planner/index.html.j2 +63 -13
  77. {geo_activity_playground-0.38.2.dist-info → geo_activity_playground-0.39.1.dist-info}/METADATA +5 -1
  78. geo_activity_playground-0.39.1.dist-info/RECORD +136 -0
  79. geo_activity_playground/__init__.py +0 -0
  80. geo_activity_playground/core/__init__.py +0 -0
  81. geo_activity_playground/explorer/__init__.py +0 -0
  82. geo_activity_playground/importers/__init__.py +0 -0
  83. geo_activity_playground/webui/__init__.py +0 -0
  84. geo_activity_playground/webui/activity/__init__.py +0 -0
  85. geo_activity_playground/webui/activity/blueprint.py +0 -109
  86. geo_activity_playground/webui/activity/templates/activity/day.html.j2 +0 -80
  87. geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -42
  88. geo_activity_playground/webui/calendar/__init__.py +0 -0
  89. geo_activity_playground/webui/calendar/blueprint.py +0 -23
  90. geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -46
  91. geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -55
  92. geo_activity_playground/webui/entry_controller.py +0 -63
  93. geo_activity_playground/webui/explorer/__init__.py +0 -0
  94. geo_activity_playground/webui/explorer/blueprint.py +0 -62
  95. geo_activity_playground/webui/heatmap/__init__.py +0 -0
  96. geo_activity_playground/webui/heatmap/blueprint.py +0 -51
  97. geo_activity_playground/webui/heatmap/heatmap_controller.py +0 -216
  98. geo_activity_playground/webui/settings/blueprint.py +0 -262
  99. geo_activity_playground/webui/settings/controller.py +0 -272
  100. geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -44
  101. geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -25
  102. geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -30
  103. geo_activity_playground/webui/tile_blueprint.py +0 -42
  104. geo_activity_playground-0.38.2.dist-info/RECORD +0 -129
  105. /geo_activity_playground/webui/{activity/templates → templates}/activity/lines.html.j2 +0 -0
  106. /geo_activity_playground/webui/{explorer/templates → templates}/explorer/index.html.j2 +0 -0
  107. /geo_activity_playground/webui/{heatmap/templates → templates}/heatmap/index.html.j2 +0 -0
  108. /geo_activity_playground/webui/{settings/templates → templates}/settings/admin-password.html.j2 +0 -0
  109. /geo_activity_playground/webui/{settings/templates → templates}/settings/color-schemes.html.j2 +0 -0
  110. /geo_activity_playground/webui/{settings/templates → templates}/settings/heart-rate.html.j2 +0 -0
  111. /geo_activity_playground/webui/{settings/templates → templates}/settings/metadata-extraction.html.j2 +0 -0
  112. /geo_activity_playground/webui/{settings/templates → templates}/settings/segmentation.html.j2 +0 -0
  113. /geo_activity_playground/webui/{settings/templates → templates}/settings/sharepic.html.j2 +0 -0
  114. {geo_activity_playground-0.38.2.dist-info → geo_activity_playground-0.39.1.dist-info}/LICENSE +0 -0
  115. {geo_activity_playground-0.38.2.dist-info → geo_activity_playground-0.39.1.dist-info}/WHEEL +0 -0
  116. {geo_activity_playground-0.38.2.dist-info → geo_activity_playground-0.39.1.dist-info}/entry_points.txt +0 -0
@@ -1,216 +0,0 @@
1
- import datetime
2
- import io
3
- import logging
4
- import pathlib
5
- from typing import Optional
6
-
7
- import matplotlib.pylab as pl
8
- import numpy as np
9
- from PIL import Image
10
- from PIL import ImageDraw
11
-
12
- from geo_activity_playground.core.activities import ActivityRepository
13
- from geo_activity_playground.core.config import Config
14
- from geo_activity_playground.core.meta_search import apply_search_query
15
- from geo_activity_playground.core.meta_search import SearchQuery
16
- from geo_activity_playground.core.raster_map import convert_to_grayscale
17
- from geo_activity_playground.core.raster_map import GeoBounds
18
- from geo_activity_playground.core.raster_map import get_sensible_zoom_level
19
- from geo_activity_playground.core.raster_map import get_tile
20
- from geo_activity_playground.core.raster_map import OSM_TILE_SIZE
21
- from geo_activity_playground.core.raster_map import PixelBounds
22
- from geo_activity_playground.core.tasks import work_tracker
23
- from geo_activity_playground.core.tiles import get_tile_upper_left_lat_lon
24
- from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
25
- from geo_activity_playground.webui.explorer.controller import (
26
- bounding_box_for_biggest_cluster,
27
- )
28
-
29
-
30
- logger = logging.getLogger(__name__)
31
-
32
-
33
- class HeatmapController:
34
- def __init__(
35
- self,
36
- repository: ActivityRepository,
37
- tile_visit_accessor: TileVisitAccessor,
38
- config: Config,
39
- ) -> None:
40
- self._repository = repository
41
- self._tile_visit_accessor = tile_visit_accessor
42
- self._config = config
43
-
44
- self.tile_histories = self._tile_visit_accessor.tile_state["tile_history"]
45
- self.tile_evolution_states = self._tile_visit_accessor.tile_state[
46
- "evolution_state"
47
- ]
48
- self.tile_visits = self._tile_visit_accessor.tile_state["tile_visits"]
49
- self.activities_per_tile = self._tile_visit_accessor.tile_state[
50
- "activities_per_tile"
51
- ]
52
-
53
- def render(self, query: SearchQuery) -> dict:
54
- zoom = 14
55
- tiles = self.tile_histories[zoom]
56
- medians = tiles.median(skipna=True)
57
- median_lat, median_lon = get_tile_upper_left_lat_lon(
58
- medians["tile_x"], medians["tile_y"], zoom
59
- )
60
- cluster_state = self.tile_evolution_states[zoom]
61
-
62
- values = {
63
- "center": {
64
- "latitude": median_lat,
65
- "longitude": median_lon,
66
- "bbox": (
67
- bounding_box_for_biggest_cluster(
68
- cluster_state.clusters.values(), zoom
69
- )
70
- if len(cluster_state.memberships) > 0
71
- else {}
72
- ),
73
- },
74
- "extra_args": query.to_url_str(),
75
- "query": query.to_jinja(),
76
- }
77
-
78
- return values
79
-
80
- def _get_counts(
81
- self,
82
- x: int,
83
- y: int,
84
- z: int,
85
- query: SearchQuery,
86
- ) -> np.ndarray:
87
- tile_pixels = (OSM_TILE_SIZE, OSM_TILE_SIZE)
88
- tile_counts = np.zeros(tile_pixels, dtype=np.int32)
89
- if not query.active:
90
- tile_count_cache_path = pathlib.Path(f"Cache/Heatmap/{z}/{x}/{y}.npy")
91
- if tile_count_cache_path.exists():
92
- try:
93
- tile_counts = np.load(tile_count_cache_path)
94
- except ValueError:
95
- logger.warning(
96
- f"Heatmap count file {tile_count_cache_path} is corrupted, deleting."
97
- )
98
- tile_count_cache_path.unlink()
99
- tile_counts = np.zeros(tile_pixels, dtype=np.int32)
100
- tile_count_cache_path.parent.mkdir(parents=True, exist_ok=True)
101
- activity_ids = self.activities_per_tile[z].get((x, y), set())
102
-
103
- with work_tracker(
104
- tile_count_cache_path.with_suffix(".json")
105
- ) as parsed_activities:
106
- if parsed_activities - activity_ids:
107
- logger.warning(
108
- f"Resetting heatmap cache for {x=}/{y=}/{z=} because activities have been removed."
109
- )
110
- tile_counts = np.zeros(tile_pixels, dtype=np.int32)
111
- parsed_activities.clear()
112
- for activity_id in activity_ids:
113
- if activity_id in parsed_activities:
114
- continue
115
- parsed_activities.add(activity_id)
116
- time_series = self._repository.get_time_series(activity_id)
117
- for _, group in time_series.groupby("segment_id"):
118
- xy_pixels = (
119
- np.array([group["x"] * 2**z - x, group["y"] * 2**z - y]).T
120
- * OSM_TILE_SIZE
121
- )
122
- im = Image.new("L", tile_pixels)
123
- draw = ImageDraw.Draw(im)
124
- pixels = list(map(int, xy_pixels.flatten()))
125
- draw.line(pixels, fill=1, width=max(3, 6 * (z - 17)))
126
- aim = np.array(im)
127
- tile_counts += aim
128
- tmp_path = tile_count_cache_path.with_suffix(".tmp.npy")
129
- np.save(tmp_path, tile_counts)
130
- tile_count_cache_path.unlink(missing_ok=True)
131
- tmp_path.rename(tile_count_cache_path)
132
- else:
133
- activities = apply_search_query(self._repository.meta, query)
134
- activity_ids = self.activities_per_tile[z].get((x, y), set())
135
- for activity_id in activity_ids:
136
- if activity_id not in activities["id"]:
137
- continue
138
- time_series = self._repository.get_time_series(activity_id)
139
- for _, group in time_series.groupby("segment_id"):
140
- xy_pixels = (
141
- np.array([group["x"] * 2**z - x, group["y"] * 2**z - y]).T
142
- * OSM_TILE_SIZE
143
- )
144
- im = Image.new("L", tile_pixels)
145
- draw = ImageDraw.Draw(im)
146
- pixels = list(map(int, xy_pixels.flatten()))
147
- draw.line(pixels, fill=1, width=max(3, 6 * (z - 17)))
148
- aim = np.array(im)
149
- tile_counts += aim
150
- return tile_counts
151
-
152
- def _render_tile_image(
153
- self,
154
- x: int,
155
- y: int,
156
- z: int,
157
- query: SearchQuery,
158
- ) -> np.ndarray:
159
- tile_pixels = (OSM_TILE_SIZE, OSM_TILE_SIZE)
160
- tile_counts = np.zeros(tile_pixels)
161
- tile_counts += self._get_counts(x, y, z, query)
162
-
163
- tile_counts = np.sqrt(tile_counts) / 5
164
- tile_counts[tile_counts > 1.0] = 1.0
165
-
166
- cmap = pl.get_cmap(self._config.color_scheme_for_heatmap)
167
- data_color = cmap(tile_counts)
168
- data_color[data_color == cmap(0.0)] = 0.0 # remove background color
169
-
170
- map_tile = np.array(get_tile(z, x, y, self._config.map_tile_url)) / 255
171
- map_tile = convert_to_grayscale(map_tile)
172
- map_tile = 1.0 - map_tile # invert colors
173
- for c in range(3):
174
- map_tile[:, :, c] = (1.0 - data_color[:, :, c]) * map_tile[
175
- :, :, c
176
- ] + data_color[:, :, c]
177
- return map_tile
178
-
179
- def render_tile(self, x: int, y: int, z: int, query: SearchQuery) -> bytes:
180
- f = io.BytesIO()
181
- pl.imsave(
182
- f,
183
- self._render_tile_image(x, y, z, query),
184
- format="png",
185
- )
186
- return bytes(f.getbuffer())
187
-
188
- def download_heatmap(
189
- self, north: float, east: float, south: float, west: float, query: SearchQuery
190
- ) -> bytes:
191
- geo_bounds = GeoBounds(south, west, north, east)
192
- tile_bounds = get_sensible_zoom_level(geo_bounds, (4000, 4000))
193
- pixel_bounds = PixelBounds.from_tile_bounds(tile_bounds)
194
-
195
- background = np.zeros((*pixel_bounds.shape, 3))
196
- for x in range(tile_bounds.x1, tile_bounds.x2):
197
- for y in range(tile_bounds.y1, tile_bounds.y2):
198
- tile = (
199
- np.array(
200
- get_tile(tile_bounds.zoom, x, y, self._config.map_tile_url)
201
- )
202
- / 255
203
- )
204
-
205
- i = y - tile_bounds.y1
206
- j = x - tile_bounds.x1
207
-
208
- background[
209
- i * OSM_TILE_SIZE : (i + 1) * OSM_TILE_SIZE,
210
- j * OSM_TILE_SIZE : (j + 1) * OSM_TILE_SIZE,
211
- :,
212
- ] = self._render_tile_image(x, y, tile_bounds.zoom, query)
213
-
214
- f = io.BytesIO()
215
- pl.imsave(f, background, format="png")
216
- return bytes(f.getbuffer())
@@ -1,262 +0,0 @@
1
- import shutil
2
- from typing import Optional
3
-
4
- from flask import Blueprint
5
- from flask import flash
6
- from flask import redirect
7
- from flask import render_template
8
- from flask import request
9
- from flask import url_for
10
-
11
- from geo_activity_playground.core.config import ConfigAccessor
12
- from geo_activity_playground.core.paths import _activity_enriched_dir
13
- from geo_activity_playground.webui.authenticator import Authenticator
14
- from geo_activity_playground.webui.authenticator import needs_authentication
15
- from geo_activity_playground.webui.settings.controller import SettingsController
16
-
17
-
18
- VEGA_COLOR_SCHEMES_CONTINUOUS = [
19
- "lightgreyred",
20
- "lightgreyteal",
21
- "lightmulti",
22
- "lightorange",
23
- "lighttealblue",
24
- "blues",
25
- "tealblues",
26
- "teals",
27
- "greens",
28
- "browns",
29
- "oranges",
30
- "reds",
31
- "purples",
32
- "warmgreys",
33
- "greys",
34
- ]
35
-
36
- MATPLOTLIB_COLOR_SCHEMES_CONTINUOUS = [
37
- "afmhot",
38
- "bone",
39
- "cividis",
40
- "copper",
41
- "gist_gray",
42
- "gist_heat",
43
- "gnuplot2",
44
- "gray",
45
- "Greys_r",
46
- "hot",
47
- "inferno",
48
- "magma",
49
- "pink",
50
- "plasma",
51
- "viridis",
52
- ]
53
-
54
-
55
- def int_or_none(s: str) -> Optional[int]:
56
- if s:
57
- try:
58
- return int(s)
59
- except ValueError as e:
60
- flash(f"Cannot parse integer from {s}: {e}", category="danger")
61
- return None
62
-
63
-
64
- def make_settings_blueprint(
65
- config_accessor: ConfigAccessor, authenticator: Authenticator
66
- ) -> Blueprint:
67
- settings_controller = SettingsController(config_accessor)
68
- blueprint = Blueprint("settings", __name__, template_folder="templates")
69
-
70
- @blueprint.route("/")
71
- @needs_authentication(authenticator)
72
- def index():
73
- return render_template("settings/index.html.j2")
74
-
75
- @blueprint.route("/admin-password", methods=["GET", "POST"])
76
- @needs_authentication(authenticator)
77
- def admin_password():
78
- if request.method == "POST":
79
- settings_controller.save_admin_password(request.form["password"])
80
- return render_template(
81
- "settings/admin-password.html.j2",
82
- **settings_controller.render_admin_password(),
83
- )
84
-
85
- @blueprint.route("/color-schemes", methods=["GET", "POST"])
86
- @needs_authentication(authenticator)
87
- def color_schemes():
88
- if request.method == "POST":
89
- config_accessor().color_scheme_for_counts = request.form[
90
- "color_scheme_for_counts"
91
- ]
92
- config_accessor().color_scheme_for_kind = request.form[
93
- "color_scheme_for_kind"
94
- ]
95
- config_accessor().color_scheme_for_heatmap = request.form[
96
- "color_scheme_for_heatmap"
97
- ]
98
- config_accessor.save()
99
- flash("Updated color schemes.", category="success")
100
-
101
- return render_template(
102
- "settings/color-schemes.html.j2",
103
- color_scheme_for_counts=config_accessor().color_scheme_for_counts,
104
- color_scheme_for_counts_avail=VEGA_COLOR_SCHEMES_CONTINUOUS,
105
- color_scheme_for_kind=config_accessor().color_scheme_for_kind,
106
- color_scheme_for_kind_avail=[
107
- "accent",
108
- "category10",
109
- "category20",
110
- "category20b",
111
- "category20c",
112
- "dark2",
113
- "paired",
114
- "pastel1",
115
- "pastel2",
116
- "set1",
117
- "set2",
118
- "set3",
119
- "tableau10",
120
- "tableau20",
121
- ],
122
- color_scheme_for_heatmap=config_accessor().color_scheme_for_heatmap,
123
- color_scheme_for_heatmap_avail=MATPLOTLIB_COLOR_SCHEMES_CONTINUOUS,
124
- )
125
-
126
- @blueprint.route("/equipment-offsets", methods=["GET", "POST"])
127
- @needs_authentication(authenticator)
128
- def equipment_offsets():
129
- if request.method == "POST":
130
- equipments = request.form.getlist("equipment")
131
- offsets = request.form.getlist("offset")
132
- settings_controller.save_equipment_offsets(equipments, offsets)
133
- return render_template(
134
- "settings/equipment-offsets.html.j2",
135
- **settings_controller.render_equipment_offsets(),
136
- )
137
-
138
- @blueprint.route("/heart-rate", methods=["GET", "POST"])
139
- @needs_authentication(authenticator)
140
- def heart_rate():
141
- if request.method == "POST":
142
- birth_year = int_or_none(request.form["birth_year"])
143
- heart_rate_resting = int_or_none(request.form["heart_rate_resting"])
144
- if heart_rate_resting is None:
145
- heart_rate_resting = 0
146
- heart_rate_maximum = int_or_none(request.form["heart_rate_maximum"])
147
- settings_controller.save_heart_rate(
148
- birth_year, heart_rate_resting, heart_rate_maximum
149
- )
150
- return render_template(
151
- "settings/heart-rate.html.j2", **settings_controller.render_heart_rate()
152
- )
153
-
154
- @blueprint.route("/kind-renames", methods=["GET", "POST"])
155
- @needs_authentication(authenticator)
156
- def kind_renames():
157
- if request.method == "POST":
158
- rules_str = request.form["rules_str"]
159
- rules = {}
160
- try:
161
- for line in rules_str.strip().split("\n"):
162
- first, second = line.split(" => ")
163
- rules[first.strip()] = second.strip()
164
- config_accessor().kind_renames = rules
165
- config_accessor.save()
166
- flash(f"Kind renames updated.", category="success")
167
- shutil.rmtree(_activity_enriched_dir)
168
- return redirect(url_for("upload.reload"))
169
- except ValueError as e:
170
- flash(f"Cannot parse this. Please try again.", category="danger")
171
- else:
172
- rules_str = "\n".join(
173
- f"{key} => {value}"
174
- for key, value in config_accessor().kind_renames.items()
175
- )
176
- return render_template(
177
- "settings/kind-renames.html.j2",
178
- rules_str=rules_str,
179
- )
180
-
181
- @blueprint.route("/kinds-without-achievements", methods=["GET", "POST"])
182
- @needs_authentication(authenticator)
183
- def kinds_without_achievements():
184
- if request.method == "POST":
185
- kinds = request.form.getlist("kind")
186
- settings_controller.save_kinds_without_achievements(kinds)
187
- return render_template(
188
- "settings/kinds-without-achievements.html.j2",
189
- **settings_controller.render_kinds_without_achievements(),
190
- )
191
-
192
- @blueprint.route("/metadata-extraction", methods=["GET", "POST"])
193
- @needs_authentication(authenticator)
194
- def metadata_extraction():
195
- if request.method == "POST":
196
- regexes = request.form.getlist("regex")
197
- settings_controller.save_metadata_extraction(regexes)
198
- return render_template(
199
- "settings/metadata-extraction.html.j2",
200
- **settings_controller.render_metadata_extraction(),
201
- )
202
-
203
- @blueprint.route("/privacy-zones", methods=["GET", "POST"])
204
- @needs_authentication(authenticator)
205
- def privacy_zones():
206
- if request.method == "POST":
207
- zone_names = request.form.getlist("zone_name")
208
- zone_geojsons = request.form.getlist("zone_geojson")
209
- settings_controller.save_privacy_zones(zone_names, zone_geojsons)
210
- return render_template(
211
- "settings/privacy-zones.html.j2",
212
- **settings_controller.render_privacy_zones(),
213
- )
214
-
215
- @blueprint.route("/segmentation", methods=["GET", "POST"])
216
- @needs_authentication(authenticator)
217
- def segmentation():
218
- if request.method == "POST":
219
- threshold = int(request.form.get("threshold", 0))
220
- config_accessor().time_diff_threshold_seconds = threshold
221
- config_accessor.save()
222
- flash(f"Threshold set to {threshold}.", category="success")
223
- shutil.rmtree(_activity_enriched_dir)
224
- return redirect(url_for("upload.reload"))
225
- return render_template(
226
- "settings/segmentation.html.j2",
227
- threshold=config_accessor().time_diff_threshold_seconds,
228
- )
229
-
230
- @blueprint.route("/sharepic", methods=["GET", "POST"])
231
- @needs_authentication(authenticator)
232
- def sharepic():
233
- if request.method == "POST":
234
- names = request.form.getlist("name")
235
- settings_controller.save_sharepic(names)
236
- return render_template(
237
- "settings/sharepic.html.j2",
238
- **settings_controller.render_sharepic(),
239
- )
240
-
241
- @blueprint.route("/strava", methods=["GET", "POST"])
242
- @needs_authentication(authenticator)
243
- def strava():
244
- if request.method == "POST":
245
- strava_client_id = request.form["strava_client_id"]
246
- strava_client_secret = request.form["strava_client_secret"]
247
- url = settings_controller.save_strava(
248
- strava_client_id, strava_client_secret
249
- )
250
- return redirect(url)
251
- return render_template(
252
- "settings/strava.html.j2", **settings_controller.render_strava()
253
- )
254
-
255
- @blueprint.route("/strava-callback")
256
- @needs_authentication(authenticator)
257
- def strava_callback():
258
- code = request.args.get("code", type=str)
259
- settings_controller.save_strava_code(code)
260
- return redirect(url_for(".strava"))
261
-
262
- return blueprint