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.
- geo_activity_playground/__main__.py +5 -47
- geo_activity_playground/alembic/README +1 -0
- geo_activity_playground/alembic/env.py +76 -0
- geo_activity_playground/alembic/script.py.mako +26 -0
- geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py +33 -0
- geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py +73 -0
- geo_activity_playground/alembic/versions/ab83b9d23127_add_upstream_id.py +28 -0
- geo_activity_playground/alembic/versions/b03491c593f6_add_crop_indices.py +30 -0
- geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmark_name.py +28 -0
- geo_activity_playground/alembic/versions/script.py.mako +28 -0
- geo_activity_playground/core/activities.py +53 -136
- geo_activity_playground/core/config.py +3 -3
- geo_activity_playground/core/datamodel.py +257 -0
- geo_activity_playground/core/enrichment.py +90 -92
- geo_activity_playground/core/heart_rate.py +1 -2
- geo_activity_playground/core/parametric_plot.py +101 -0
- geo_activity_playground/core/paths.py +6 -7
- geo_activity_playground/core/raster_map.py +43 -4
- geo_activity_playground/core/similarity.py +1 -2
- geo_activity_playground/core/tasks.py +2 -2
- geo_activity_playground/core/test_meta_search.py +3 -3
- geo_activity_playground/core/test_summary_stats.py +1 -1
- geo_activity_playground/explorer/grid_file.py +2 -2
- geo_activity_playground/explorer/tile_visits.py +8 -10
- geo_activity_playground/heatmap_video.py +7 -8
- geo_activity_playground/importers/activity_parsers.py +2 -2
- geo_activity_playground/importers/directory.py +9 -10
- geo_activity_playground/importers/strava_api.py +9 -9
- geo_activity_playground/importers/strava_checkout.py +12 -13
- geo_activity_playground/importers/test_csv_parser.py +3 -3
- geo_activity_playground/importers/test_directory.py +1 -1
- geo_activity_playground/importers/test_strava_api.py +1 -1
- geo_activity_playground/webui/app.py +96 -86
- geo_activity_playground/webui/authenticator.py +1 -1
- geo_activity_playground/webui/{activity/controller.py → blueprints/activity_blueprint.py} +246 -108
- geo_activity_playground/webui/{auth_blueprint.py → blueprints/auth_blueprint.py} +1 -1
- geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +61 -0
- geo_activity_playground/webui/{calendar/controller.py → blueprints/calendar_blueprint.py} +19 -19
- geo_activity_playground/webui/{eddington_blueprint.py → blueprints/eddington_blueprint.py} +5 -5
- geo_activity_playground/webui/blueprints/entry_views.py +68 -0
- geo_activity_playground/webui/{equipment_blueprint.py → blueprints/equipment_blueprint.py} +37 -4
- geo_activity_playground/webui/{explorer/controller.py → blueprints/explorer_blueprint.py} +88 -54
- geo_activity_playground/webui/blueprints/heatmap_blueprint.py +233 -0
- geo_activity_playground/webui/blueprints/plot_builder_blueprint.py +43 -0
- geo_activity_playground/webui/{search_blueprint.py → blueprints/search_blueprint.py} +7 -11
- geo_activity_playground/webui/blueprints/settings_blueprint.py +446 -0
- geo_activity_playground/webui/{square_planner_blueprint.py → blueprints/square_planner_blueprint.py} +31 -6
- geo_activity_playground/webui/{summary_blueprint.py → blueprints/summary_blueprint.py} +11 -23
- geo_activity_playground/webui/blueprints/tile_blueprint.py +27 -0
- geo_activity_playground/webui/{upload_blueprint.py → blueprints/upload_blueprint.py} +13 -18
- geo_activity_playground/webui/flasher.py +26 -0
- geo_activity_playground/webui/plot_util.py +1 -1
- geo_activity_playground/webui/search_util.py +4 -6
- geo_activity_playground/webui/static/images/layers-2x.png +0 -0
- geo_activity_playground/webui/static/images/layers.png +0 -0
- geo_activity_playground/webui/static/images/marker-icon-2x.png +0 -0
- geo_activity_playground/webui/static/images/marker-icon.png +0 -0
- geo_activity_playground/webui/static/images/marker-shadow.png +0 -0
- geo_activity_playground/webui/templates/activity/day.html.j2 +81 -0
- geo_activity_playground/webui/templates/activity/edit.html.j2 +38 -0
- geo_activity_playground/webui/{activity/templates → templates}/activity/name.html.j2 +29 -27
- geo_activity_playground/webui/{activity/templates → templates}/activity/show.html.j2 +57 -33
- geo_activity_playground/webui/templates/activity/trim.html.j2 +68 -0
- geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +26 -0
- geo_activity_playground/webui/templates/calendar/index.html.j2 +48 -0
- geo_activity_playground/webui/templates/calendar/month.html.j2 +57 -0
- geo_activity_playground/webui/templates/equipment/index.html.j2 +7 -0
- geo_activity_playground/webui/templates/home.html.j2 +6 -6
- geo_activity_playground/webui/templates/page.html.j2 +2 -1
- geo_activity_playground/webui/templates/plot_builder/index.html.j2 +44 -0
- geo_activity_playground/webui/{settings/templates → templates}/settings/index.html.j2 +9 -20
- geo_activity_playground/webui/templates/settings/manage-equipments.html.j2 +49 -0
- geo_activity_playground/webui/templates/settings/manage-kinds.html.j2 +48 -0
- geo_activity_playground/webui/{settings/templates → templates}/settings/privacy-zones.html.j2 +2 -0
- geo_activity_playground/webui/{settings/templates → templates}/settings/strava.html.j2 +2 -0
- geo_activity_playground/webui/templates/square_planner/index.html.j2 +63 -13
- {geo_activity_playground-0.38.2.dist-info → geo_activity_playground-0.39.1.dist-info}/METADATA +5 -1
- geo_activity_playground-0.39.1.dist-info/RECORD +136 -0
- geo_activity_playground/__init__.py +0 -0
- geo_activity_playground/core/__init__.py +0 -0
- geo_activity_playground/explorer/__init__.py +0 -0
- geo_activity_playground/importers/__init__.py +0 -0
- geo_activity_playground/webui/__init__.py +0 -0
- geo_activity_playground/webui/activity/__init__.py +0 -0
- geo_activity_playground/webui/activity/blueprint.py +0 -109
- geo_activity_playground/webui/activity/templates/activity/day.html.j2 +0 -80
- geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -42
- geo_activity_playground/webui/calendar/__init__.py +0 -0
- geo_activity_playground/webui/calendar/blueprint.py +0 -23
- geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -46
- geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -55
- geo_activity_playground/webui/entry_controller.py +0 -63
- geo_activity_playground/webui/explorer/__init__.py +0 -0
- geo_activity_playground/webui/explorer/blueprint.py +0 -62
- geo_activity_playground/webui/heatmap/__init__.py +0 -0
- geo_activity_playground/webui/heatmap/blueprint.py +0 -51
- geo_activity_playground/webui/heatmap/heatmap_controller.py +0 -216
- geo_activity_playground/webui/settings/blueprint.py +0 -262
- geo_activity_playground/webui/settings/controller.py +0 -272
- geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -44
- geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -25
- geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -30
- geo_activity_playground/webui/tile_blueprint.py +0 -42
- geo_activity_playground-0.38.2.dist-info/RECORD +0 -129
- /geo_activity_playground/webui/{activity/templates → templates}/activity/lines.html.j2 +0 -0
- /geo_activity_playground/webui/{explorer/templates → templates}/explorer/index.html.j2 +0 -0
- /geo_activity_playground/webui/{heatmap/templates → templates}/heatmap/index.html.j2 +0 -0
- /geo_activity_playground/webui/{settings/templates → templates}/settings/admin-password.html.j2 +0 -0
- /geo_activity_playground/webui/{settings/templates → templates}/settings/color-schemes.html.j2 +0 -0
- /geo_activity_playground/webui/{settings/templates → templates}/settings/heart-rate.html.j2 +0 -0
- /geo_activity_playground/webui/{settings/templates → templates}/settings/metadata-extraction.html.j2 +0 -0
- /geo_activity_playground/webui/{settings/templates → templates}/settings/segmentation.html.j2 +0 -0
- /geo_activity_playground/webui/{settings/templates → templates}/settings/sharepic.html.j2 +0 -0
- {geo_activity_playground-0.38.2.dist-info → geo_activity_playground-0.39.1.dist-info}/LICENSE +0 -0
- {geo_activity_playground-0.38.2.dist-info → geo_activity_playground-0.39.1.dist-info}/WHEEL +0 -0
- {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
|