geo-activity-playground 0.22.0__py3-none-any.whl → 0.24.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.
- geo_activity_playground/__main__.py +1 -1
- geo_activity_playground/core/activities.py +16 -9
- geo_activity_playground/core/activity_parsers.py +17 -32
- geo_activity_playground/core/cache_migrations.py +24 -0
- geo_activity_playground/core/heatmap.py +21 -21
- geo_activity_playground/core/privacy_zones.py +16 -0
- geo_activity_playground/core/similarity.py +1 -1
- geo_activity_playground/core/test_time_conversion.py +37 -0
- geo_activity_playground/core/time_conversion.py +14 -0
- geo_activity_playground/explorer/tile_visits.py +44 -27
- geo_activity_playground/importers/__init__.py +0 -0
- geo_activity_playground/importers/directory.py +7 -2
- geo_activity_playground/importers/strava_api.py +6 -0
- geo_activity_playground/importers/strava_checkout.py +12 -3
- geo_activity_playground/webui/__init__.py +0 -0
- geo_activity_playground/webui/activity/__init__.py +0 -0
- geo_activity_playground/webui/activity/blueprint.py +58 -0
- geo_activity_playground/webui/{activity_controller.py → activity/controller.py} +128 -18
- geo_activity_playground/webui/{templates/activity-day.html.j2 → activity/templates/activity/day.html.j2} +14 -2
- geo_activity_playground/webui/{templates/activity-name.html.j2 → activity/templates/activity/name.html.j2} +1 -1
- geo_activity_playground/webui/{templates/activity.html.j2 → activity/templates/activity/show.html.j2} +9 -4
- geo_activity_playground/webui/app.py +68 -281
- geo_activity_playground/webui/calendar/__init__.py +0 -0
- geo_activity_playground/webui/calendar/blueprint.py +26 -0
- geo_activity_playground/webui/{calendar_controller.py → calendar/controller.py} +5 -5
- geo_activity_playground/webui/{templates/calendar.html.j2 → calendar/templates/calendar/index.html.j2} +3 -2
- geo_activity_playground/webui/{templates/calendar-month.html.j2 → calendar/templates/calendar/month.html.j2} +2 -2
- geo_activity_playground/webui/eddington/__init__.py +0 -0
- geo_activity_playground/webui/eddington/blueprint.py +19 -0
- geo_activity_playground/webui/{eddington_controller.py → eddington/controller.py} +14 -6
- geo_activity_playground/webui/eddington/templates/eddington/index.html.j2 +56 -0
- geo_activity_playground/webui/entry_controller.py +4 -2
- geo_activity_playground/webui/equipment/__init__.py +0 -0
- geo_activity_playground/webui/equipment/blueprint.py +19 -0
- geo_activity_playground/webui/{equipment_controller.py → equipment/controller.py} +5 -3
- geo_activity_playground/webui/explorer/__init__.py +0 -0
- geo_activity_playground/webui/explorer/blueprint.py +54 -0
- geo_activity_playground/webui/{explorer_controller.py → explorer/controller.py} +6 -2
- geo_activity_playground/webui/{templates/explorer.html.j2 → explorer/templates/explorer/index.html.j2} +2 -2
- geo_activity_playground/webui/heatmap/__init__.py +0 -0
- geo_activity_playground/webui/heatmap/blueprint.py +41 -0
- geo_activity_playground/webui/{heatmap_controller.py → heatmap/heatmap_controller.py} +36 -13
- geo_activity_playground/webui/{templates/heatmap.html.j2 → heatmap/templates/heatmap/index.html.j2} +17 -2
- geo_activity_playground/webui/search_controller.py +1 -9
- geo_activity_playground/webui/square_planner/__init__.py +0 -0
- geo_activity_playground/webui/square_planner/blueprint.py +38 -0
- geo_activity_playground/webui/summary/__init__.py +0 -0
- geo_activity_playground/webui/summary/blueprint.py +16 -0
- geo_activity_playground/webui/summary/controller.py +268 -0
- geo_activity_playground/webui/summary/templates/summary/index.html.j2 +135 -0
- geo_activity_playground/webui/templates/{index.html.j2 → home.html.j2} +1 -1
- geo_activity_playground/webui/templates/page.html.j2 +32 -19
- geo_activity_playground/webui/templates/search.html.j2 +1 -1
- geo_activity_playground/webui/tile/__init__.py +0 -0
- geo_activity_playground/webui/tile/blueprint.py +31 -0
- geo_activity_playground/webui/upload/__init__.py +0 -0
- geo_activity_playground/webui/upload/blueprint.py +28 -0
- geo_activity_playground/webui/{upload_controller.py → upload/controller.py} +15 -6
- geo_activity_playground/webui/{templates/upload.html.j2 → upload/templates/upload/index.html.j2} +12 -11
- {geo_activity_playground-0.22.0.dist-info → geo_activity_playground-0.24.0.dist-info}/METADATA +2 -1
- geo_activity_playground-0.24.0.dist-info/RECORD +95 -0
- geo_activity_playground/webui/config_controller.py +0 -12
- geo_activity_playground/webui/locations_controller.py +0 -28
- geo_activity_playground/webui/summary_controller.py +0 -60
- geo_activity_playground/webui/templates/config.html.j2 +0 -24
- geo_activity_playground/webui/templates/eddington.html.j2 +0 -18
- geo_activity_playground/webui/templates/locations.html.j2 +0 -38
- geo_activity_playground/webui/templates/summary.html.j2 +0 -21
- geo_activity_playground-0.22.0.dist-info/RECORD +0 -74
- /geo_activity_playground/webui/{templates/activity-lines.html.j2 → activity/templates/activity/lines.html.j2} +0 -0
- /geo_activity_playground/webui/{templates/equipment.html.j2 → equipment/templates/equipment/index.html.j2} +0 -0
- /geo_activity_playground/webui/{square_planner_controller.py → square_planner/controller.py} +0 -0
- /geo_activity_playground/webui/{templates/square-planner.html.j2 → square_planner/templates/square_planner/index.html.j2} +0 -0
- /geo_activity_playground/webui/{tile_controller.py → tile/controller.py} +0 -0
- {geo_activity_playground-0.22.0.dist-info → geo_activity_playground-0.24.0.dist-info}/LICENSE +0 -0
- {geo_activity_playground-0.22.0.dist-info → geo_activity_playground-0.24.0.dist-info}/WHEEL +0 -0
- {geo_activity_playground-0.22.0.dist-info → geo_activity_playground-0.24.0.dist-info}/entry_points.txt +0 -0
@@ -1,204 +1,28 @@
|
|
1
|
-
import
|
1
|
+
import json
|
2
|
+
import pathlib
|
3
|
+
import secrets
|
2
4
|
|
3
5
|
from flask import Flask
|
4
6
|
from flask import redirect
|
5
7
|
from flask import render_template
|
6
8
|
from flask import request
|
7
|
-
from flask import Response
|
8
9
|
|
9
|
-
from .
|
10
|
+
from ..core.activities import ActivityRepository
|
11
|
+
from ..explorer.tile_visits import TileVisitAccessor
|
12
|
+
from .activity.blueprint import make_activity_blueprint
|
13
|
+
from .calendar.blueprint import make_calendar_blueprint
|
14
|
+
from .eddington.blueprint import make_eddington_blueprint
|
15
|
+
from .entry_controller import EntryController
|
16
|
+
from .equipment.blueprint import make_equipment_blueprint
|
17
|
+
from .explorer.blueprint import make_explorer_blueprint
|
18
|
+
from .heatmap.blueprint import make_heatmap_blueprint
|
10
19
|
from .search_controller import SearchController
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from geo_activity_playground.
|
17
|
-
from geo_activity_playground.webui.entry_controller import EntryController
|
18
|
-
from geo_activity_playground.webui.equipment_controller import EquipmentController
|
19
|
-
from geo_activity_playground.webui.explorer_controller import ExplorerController
|
20
|
-
from geo_activity_playground.webui.heatmap_controller import HeatmapController
|
21
|
-
from geo_activity_playground.webui.search_controller import SearchController
|
22
|
-
from geo_activity_playground.webui.square_planner_controller import (
|
23
|
-
SquarePlannerController,
|
24
|
-
)
|
25
|
-
from geo_activity_playground.webui.strava_controller import StravaController
|
26
|
-
from geo_activity_playground.webui.summary_controller import SummaryController
|
27
|
-
from geo_activity_playground.webui.tile_controller import (
|
28
|
-
TileController,
|
29
|
-
)
|
30
|
-
from geo_activity_playground.webui.upload_controller import UploadController
|
31
|
-
|
32
|
-
|
33
|
-
def route_activity(app: Flask, repository: ActivityRepository) -> None:
|
34
|
-
activity_controller = ActivityController(repository)
|
35
|
-
|
36
|
-
@app.route("/activity/all")
|
37
|
-
def activity_all():
|
38
|
-
return render_template(
|
39
|
-
"activity-lines.html.j2", **activity_controller.render_all()
|
40
|
-
)
|
41
|
-
|
42
|
-
@app.route("/activity/<id>")
|
43
|
-
def activity(id: str):
|
44
|
-
return render_template(
|
45
|
-
"activity.html.j2", **activity_controller.render_activity(int(id))
|
46
|
-
)
|
47
|
-
|
48
|
-
@app.route("/activity/<id>/sharepic.png")
|
49
|
-
def activity_sharepic(id: str):
|
50
|
-
return Response(
|
51
|
-
activity_controller.render_sharepic(int(id)),
|
52
|
-
mimetype="image/png",
|
53
|
-
)
|
54
|
-
|
55
|
-
@app.route("/activity/day/<year>/<month>/<day>")
|
56
|
-
def activity_day(year: str, month: str, day: str):
|
57
|
-
return render_template(
|
58
|
-
"activity-day.html.j2",
|
59
|
-
**activity_controller.render_day(int(year), int(month), int(day))
|
60
|
-
)
|
61
|
-
|
62
|
-
@app.route("/activity/name/<name>")
|
63
|
-
def activity_name(name: str):
|
64
|
-
return render_template(
|
65
|
-
"activity-name.html.j2",
|
66
|
-
**activity_controller.render_name(urllib.parse.unquote(name))
|
67
|
-
)
|
68
|
-
|
69
|
-
|
70
|
-
def route_calendar(app: Flask, repository: ActivityRepository) -> None:
|
71
|
-
calendar_controller = CalendarController(repository)
|
72
|
-
|
73
|
-
@app.route("/calendar")
|
74
|
-
def calendar():
|
75
|
-
return render_template(
|
76
|
-
"calendar.html.j2", **calendar_controller.render_overview()
|
77
|
-
)
|
78
|
-
|
79
|
-
@app.route("/calendar/<year>/<month>")
|
80
|
-
def calendar_month(year: str, month: str):
|
81
|
-
return render_template(
|
82
|
-
"calendar-month.html.j2",
|
83
|
-
**calendar_controller.render_month(int(year), int(month))
|
84
|
-
)
|
85
|
-
|
86
|
-
|
87
|
-
def route_config(app: Flask, repository: ActivityRepository) -> None:
|
88
|
-
config_controller = ConfigController(repository)
|
89
|
-
|
90
|
-
@app.route("/config")
|
91
|
-
def config_index():
|
92
|
-
return render_template("config.html.j2", **config_controller.action_index())
|
93
|
-
|
94
|
-
@app.route("/config/save", methods=["POST"])
|
95
|
-
def config_save():
|
96
|
-
form_input = request.form
|
97
|
-
return render_template(
|
98
|
-
"config.html.j2", **config_controller.action_save(form_input)
|
99
|
-
)
|
100
|
-
|
101
|
-
|
102
|
-
def route_eddington(app: Flask, repository: ActivityRepository) -> None:
|
103
|
-
eddington_controller = EddingtonController(repository)
|
104
|
-
|
105
|
-
@app.route("/eddington")
|
106
|
-
def eddington():
|
107
|
-
return render_template("eddington.html.j2", **eddington_controller.render())
|
108
|
-
|
109
|
-
|
110
|
-
def route_equipment(app: Flask, repository: ActivityRepository) -> None:
|
111
|
-
equipment_controller = EquipmentController(repository)
|
112
|
-
|
113
|
-
@app.route("/equipment")
|
114
|
-
def equipment():
|
115
|
-
return render_template("equipment.html.j2", **equipment_controller.render())
|
116
|
-
|
117
|
-
|
118
|
-
def route_explorer(
|
119
|
-
app: Flask, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
|
120
|
-
) -> None:
|
121
|
-
explorer_controller = ExplorerController(repository, tile_visit_accessor)
|
122
|
-
|
123
|
-
@app.route("/explorer/<zoom>")
|
124
|
-
def explorer(zoom: str):
|
125
|
-
return render_template(
|
126
|
-
"explorer.html.j2", **explorer_controller.render(int(zoom))
|
127
|
-
)
|
128
|
-
|
129
|
-
@app.route("/explorer/<zoom>/<north>/<east>/<south>/<west>/explored.<suffix>")
|
130
|
-
def explorer_download(
|
131
|
-
zoom: str, north: str, east: str, south: str, west: str, suffix: str
|
132
|
-
):
|
133
|
-
mimetypes = {"geojson": "application/json", "gpx": "application/xml"}
|
134
|
-
return Response(
|
135
|
-
explorer_controller.export_explored_tiles(
|
136
|
-
int(zoom),
|
137
|
-
float(north),
|
138
|
-
float(east),
|
139
|
-
float(south),
|
140
|
-
float(west),
|
141
|
-
suffix,
|
142
|
-
),
|
143
|
-
mimetype=mimetypes[suffix],
|
144
|
-
headers={"Content-disposition": "attachment"},
|
145
|
-
)
|
146
|
-
|
147
|
-
@app.route("/explorer/<zoom>/<north>/<east>/<south>/<west>/missing.<suffix>")
|
148
|
-
def explorer_missing(
|
149
|
-
zoom: str, north: str, east: str, south: str, west: str, suffix: str
|
150
|
-
):
|
151
|
-
mimetypes = {"geojson": "application/json", "gpx": "application/xml"}
|
152
|
-
return Response(
|
153
|
-
explorer_controller.export_missing_tiles(
|
154
|
-
int(zoom),
|
155
|
-
float(north),
|
156
|
-
float(east),
|
157
|
-
float(south),
|
158
|
-
float(west),
|
159
|
-
suffix,
|
160
|
-
),
|
161
|
-
mimetype=mimetypes[suffix],
|
162
|
-
headers={"Content-disposition": "attachment"},
|
163
|
-
)
|
164
|
-
|
165
|
-
|
166
|
-
def route_heatmap(
|
167
|
-
app: Flask, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
|
168
|
-
) -> None:
|
169
|
-
heatmap_controller = HeatmapController(repository, tile_visit_accessor)
|
170
|
-
|
171
|
-
@app.route("/heatmap")
|
172
|
-
def heatmap():
|
173
|
-
return render_template("heatmap.html.j2", **heatmap_controller.render())
|
174
|
-
|
175
|
-
@app.route("/heatmap/tile/<z>/<x>/<y>.png")
|
176
|
-
def heatmap_tile(x: str, y: str, z: str):
|
177
|
-
return Response(
|
178
|
-
heatmap_controller.render_tile(int(x), int(y), int(z)),
|
179
|
-
mimetype="image/png",
|
180
|
-
)
|
181
|
-
|
182
|
-
@app.route("/heatmap/download/<north>/<east>/<south>/<west>")
|
183
|
-
def heatmap_download(north: str, east: str, south: str, west: str):
|
184
|
-
return Response(
|
185
|
-
heatmap_controller.download_heatmap(
|
186
|
-
float(north),
|
187
|
-
float(east),
|
188
|
-
float(south),
|
189
|
-
float(west),
|
190
|
-
),
|
191
|
-
mimetype="image/png",
|
192
|
-
headers={"Content-disposition": 'attachment; filename="heatmap.png"'},
|
193
|
-
)
|
194
|
-
|
195
|
-
|
196
|
-
def route_locations(app: Flask, repository: ActivityRepository) -> None:
|
197
|
-
controller = LocationsController(repository)
|
198
|
-
|
199
|
-
@app.route("/locations")
|
200
|
-
def locations_index():
|
201
|
-
return render_template("locations.html.j2", **controller.render_index())
|
20
|
+
from .square_planner.blueprint import make_square_planner_blueprint
|
21
|
+
from .strava_controller import StravaController
|
22
|
+
from .summary.blueprint import make_summary_blueprint
|
23
|
+
from .tile.blueprint import make_tile_blueprint
|
24
|
+
from .upload.blueprint import make_upload_blueprint
|
25
|
+
from geo_activity_playground.core.privacy_zones import PrivacyZone
|
202
26
|
|
203
27
|
|
204
28
|
def route_search(app: Flask, repository: ActivityRepository) -> None:
|
@@ -213,40 +37,12 @@ def route_search(app: Flask, repository: ActivityRepository) -> None:
|
|
213
37
|
)
|
214
38
|
|
215
39
|
|
216
|
-
def route_square_planner(
|
217
|
-
app: Flask, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
|
218
|
-
) -> None:
|
219
|
-
controller = SquarePlannerController(repository, tile_visit_accessor)
|
220
|
-
|
221
|
-
@app.route("/square-planner/<zoom>/<x>/<y>/<size>")
|
222
|
-
def square_planner_planner(zoom, x, y, size):
|
223
|
-
return render_template(
|
224
|
-
"square-planner.html.j2",
|
225
|
-
**controller.action_planner(int(zoom), int(x), int(y), int(size))
|
226
|
-
)
|
227
|
-
|
228
|
-
@app.route("/square-planner/<zoom>/<x>/<y>/<size>/missing.<suffix>")
|
229
|
-
def square_planner_missing(zoom, x, y, size, suffix: str):
|
230
|
-
mimetypes = {"geojson": "application/json", "gpx": "application/xml"}
|
231
|
-
return Response(
|
232
|
-
controller.export_missing_tiles(
|
233
|
-
int(zoom),
|
234
|
-
int(x),
|
235
|
-
int(y),
|
236
|
-
int(size),
|
237
|
-
suffix,
|
238
|
-
),
|
239
|
-
mimetype=mimetypes[suffix],
|
240
|
-
headers={"Content-disposition": "attachment"},
|
241
|
-
)
|
242
|
-
|
243
|
-
|
244
40
|
def route_start(app: Flask, repository: ActivityRepository) -> None:
|
245
41
|
entry_controller = EntryController(repository)
|
246
42
|
|
247
43
|
@app.route("/")
|
248
44
|
def index():
|
249
|
-
return render_template("
|
45
|
+
return render_template("home.html.j2", **entry_controller.render())
|
250
46
|
|
251
47
|
|
252
48
|
def route_strava(app: Flask, host: str, port: int) -> None:
|
@@ -280,52 +76,16 @@ def route_strava(app: Flask, host: str, port: int) -> None:
|
|
280
76
|
)
|
281
77
|
|
282
78
|
|
283
|
-
def
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
@app.route("/tile/color/<z>/<x>/<y>.png")
|
295
|
-
def tile_color(x: str, y: str, z: str):
|
296
|
-
return Response(
|
297
|
-
tile_controller.render_color(int(x), int(y), int(z)), mimetype="image/png"
|
298
|
-
)
|
299
|
-
|
300
|
-
@app.route("/tile/grayscale/<z>/<x>/<y>.png")
|
301
|
-
def tile_grayscale(x: str, y: str, z: str):
|
302
|
-
return Response(
|
303
|
-
tile_controller.render_grayscale(int(x), int(y), int(z)),
|
304
|
-
mimetype="image/png",
|
305
|
-
)
|
306
|
-
|
307
|
-
@app.route("/tile/pastel/<z>/<x>/<y>.png")
|
308
|
-
def tile_pastel(x: str, y: str, z: str):
|
309
|
-
return Response(
|
310
|
-
tile_controller.render_pastel(int(x), int(y), int(z)), mimetype="image/png"
|
311
|
-
)
|
312
|
-
|
313
|
-
|
314
|
-
def route_upload(
|
315
|
-
app: Flask,
|
316
|
-
repository: ActivityRepository,
|
317
|
-
tile_visit_accessor: TileVisitAccessor,
|
318
|
-
config: dict,
|
319
|
-
):
|
320
|
-
upload_controller = UploadController(repository, tile_visit_accessor, config)
|
321
|
-
|
322
|
-
@app.route("/upload")
|
323
|
-
def form():
|
324
|
-
return render_template("upload.html.j2", **upload_controller.render_form())
|
325
|
-
|
326
|
-
@app.route("/upload/receive", methods=["POST"])
|
327
|
-
def receive():
|
328
|
-
return upload_controller.receive()
|
79
|
+
def get_secret_key():
|
80
|
+
secret_file = pathlib.Path("Cache/flask-secret.json")
|
81
|
+
if secret_file.exists():
|
82
|
+
with open(secret_file) as f:
|
83
|
+
secret = json.load(f)
|
84
|
+
else:
|
85
|
+
secret = secrets.token_hex()
|
86
|
+
with open(secret_file, "w") as f:
|
87
|
+
json.dump(secret, f)
|
88
|
+
return secret
|
329
89
|
|
330
90
|
|
331
91
|
def webui_main(
|
@@ -337,22 +97,49 @@ def webui_main(
|
|
337
97
|
) -> None:
|
338
98
|
app = Flask(__name__)
|
339
99
|
|
340
|
-
route_activity(app, repository)
|
341
|
-
route_calendar(app, repository)
|
342
|
-
route_config(app, repository)
|
343
|
-
route_eddington(app, repository)
|
344
|
-
route_equipment(app, repository)
|
345
|
-
route_explorer(app, repository, tile_visit_accessor)
|
346
|
-
route_heatmap(app, repository, tile_visit_accessor)
|
347
|
-
route_locations(app, repository)
|
348
100
|
route_search(app, repository)
|
349
|
-
route_square_planner(app, repository, tile_visit_accessor)
|
350
101
|
route_start(app, repository)
|
351
102
|
route_strava(app, host, port)
|
352
|
-
route_summary(app, repository)
|
353
|
-
route_tiles(app, repository)
|
354
|
-
route_upload(app, repository, tile_visit_accessor, config)
|
355
103
|
|
356
104
|
app.config["UPLOAD_FOLDER"] = "Activities"
|
105
|
+
app.secret_key = get_secret_key()
|
106
|
+
|
107
|
+
app.register_blueprint(
|
108
|
+
make_activity_blueprint(
|
109
|
+
repository,
|
110
|
+
tile_visit_accessor,
|
111
|
+
[
|
112
|
+
PrivacyZone(points)
|
113
|
+
for points in config.get("privacy_zones", {}).values()
|
114
|
+
],
|
115
|
+
),
|
116
|
+
url_prefix="/activity",
|
117
|
+
)
|
118
|
+
app.register_blueprint(make_calendar_blueprint(repository), url_prefix="/calendar")
|
119
|
+
app.register_blueprint(
|
120
|
+
make_eddington_blueprint(repository), url_prefix="/eddington"
|
121
|
+
)
|
122
|
+
app.register_blueprint(
|
123
|
+
make_equipment_blueprint(repository), url_prefix="/equipment"
|
124
|
+
)
|
125
|
+
app.register_blueprint(
|
126
|
+
make_explorer_blueprint(repository, tile_visit_accessor), url_prefix="/explorer"
|
127
|
+
)
|
128
|
+
app.register_blueprint(
|
129
|
+
make_heatmap_blueprint(repository, tile_visit_accessor), url_prefix="/heatmap"
|
130
|
+
)
|
131
|
+
app.register_blueprint(
|
132
|
+
make_square_planner_blueprint(repository, tile_visit_accessor),
|
133
|
+
url_prefix="/square-planner",
|
134
|
+
)
|
135
|
+
app.register_blueprint(
|
136
|
+
make_summary_blueprint(repository),
|
137
|
+
url_prefix="/summary",
|
138
|
+
)
|
139
|
+
app.register_blueprint(make_tile_blueprint(), url_prefix="/tile")
|
140
|
+
app.register_blueprint(
|
141
|
+
make_upload_blueprint(repository, tile_visit_accessor, config),
|
142
|
+
url_prefix="/upload",
|
143
|
+
)
|
357
144
|
|
358
145
|
app.run(host=host, port=port)
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from flask import Blueprint
|
2
|
+
from flask import render_template
|
3
|
+
|
4
|
+
from ...core.activities import ActivityRepository
|
5
|
+
from .controller import CalendarController
|
6
|
+
|
7
|
+
|
8
|
+
def make_calendar_blueprint(repository: ActivityRepository) -> Blueprint:
|
9
|
+
blueprint = Blueprint("calendar", __name__, template_folder="templates")
|
10
|
+
|
11
|
+
calendar_controller = CalendarController(repository)
|
12
|
+
|
13
|
+
@blueprint.route("/")
|
14
|
+
def index():
|
15
|
+
return render_template(
|
16
|
+
"calendar/index.html.j2", **calendar_controller.render_overview()
|
17
|
+
)
|
18
|
+
|
19
|
+
@blueprint.route("/<year>/<month>")
|
20
|
+
def month(year: str, month: str):
|
21
|
+
return render_template(
|
22
|
+
"calendar/month.html.j2",
|
23
|
+
**calendar_controller.render_month(int(year), int(month))
|
24
|
+
)
|
25
|
+
|
26
|
+
return blueprint
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import collections
|
2
2
|
import datetime
|
3
3
|
|
4
|
-
from
|
4
|
+
from ...core.activities import ActivityRepository
|
5
5
|
|
6
6
|
|
7
7
|
class CalendarController:
|
@@ -14,9 +14,9 @@ class CalendarController:
|
|
14
14
|
meta["year"] = meta["start"].dt.year
|
15
15
|
meta["month"] = meta["start"].dt.month
|
16
16
|
|
17
|
-
monthly_distance = meta.groupby(
|
18
|
-
|
19
|
-
)
|
17
|
+
monthly_distance = meta.groupby(
|
18
|
+
["year", "month"],
|
19
|
+
).apply(lambda group: sum(group["distance_km"]), include_groups=False)
|
20
20
|
monthly_distance.name = "total_distance_km"
|
21
21
|
monthly_pivot = (
|
22
22
|
monthly_distance.reset_index()
|
@@ -25,7 +25,7 @@ class CalendarController:
|
|
25
25
|
)
|
26
26
|
|
27
27
|
yearly_distance = meta.groupby(["year"]).apply(
|
28
|
-
lambda group: sum(group["distance_km"])
|
28
|
+
lambda group: sum(group["distance_km"]), include_groups=False
|
29
29
|
)
|
30
30
|
yearly_distance.name = "total_distance_km"
|
31
31
|
yearly_distances = {
|
@@ -21,13 +21,14 @@
|
|
21
21
|
</thead>
|
22
22
|
<tbody>
|
23
23
|
{% for year, month_data in monthly_distances.items() %}
|
24
|
+
{% set year_int = year|round(0)|int %}
|
24
25
|
<tr>
|
25
|
-
<td>{{
|
26
|
+
<td>{{ year_int }}</td>
|
26
27
|
{% for month in range(1, 13) %}
|
27
28
|
<td align="right">
|
28
29
|
{% set distance = month_data[month] %}
|
29
30
|
{% if distance %}
|
30
|
-
<a href="
|
31
|
+
<a href="{{ url_for(".month", year=year_int, month=month) }}">{{ distance|int() }} km</a>
|
31
32
|
{% else %}
|
32
33
|
0 km
|
33
34
|
{% endif %}
|
@@ -29,7 +29,7 @@
|
|
29
29
|
{% for day in range(1, 8) %}
|
30
30
|
<td>
|
31
31
|
{% if weeks[week][day] %}
|
32
|
-
<a href="
|
32
|
+
<a href="{{ url_for('activity.day', year=year, month=month, day=day_of_month[week][day]) }}"><b>{{
|
33
33
|
day_of_month[week][day] }}.</b></a>
|
34
34
|
{% elif day_of_month[week][day] %}
|
35
35
|
<b>{{ day_of_month[week][day] }}.</b>
|
@@ -38,7 +38,7 @@
|
|
38
38
|
{% if weeks[week][day] %}
|
39
39
|
<ul>
|
40
40
|
{% for activity in weeks[week][day] %}
|
41
|
-
<li><a href="
|
41
|
+
<li><a href="{{ url_for('activity.show', id=activity.id) }}">{{ activity.name }}</a></li>
|
42
42
|
{% endfor %}
|
43
43
|
</ul>
|
44
44
|
{% endif %}
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from flask import Blueprint
|
2
|
+
from flask import render_template
|
3
|
+
|
4
|
+
from ...core.activities import ActivityRepository
|
5
|
+
from .controller import EddingtonController
|
6
|
+
|
7
|
+
|
8
|
+
def make_eddington_blueprint(repository: ActivityRepository) -> Blueprint:
|
9
|
+
blueprint = Blueprint("eddington", __name__, template_folder="templates")
|
10
|
+
|
11
|
+
eddington_controller = EddingtonController(repository)
|
12
|
+
|
13
|
+
@blueprint.route("/")
|
14
|
+
def index():
|
15
|
+
return render_template(
|
16
|
+
"eddington/index.html.j2", **eddington_controller.render()
|
17
|
+
)
|
18
|
+
|
19
|
+
return blueprint
|
@@ -2,7 +2,7 @@ import altair as alt
|
|
2
2
|
import numpy as np
|
3
3
|
import pandas as pd
|
4
4
|
|
5
|
-
from
|
5
|
+
from ...core.activities import ActivityRepository
|
6
6
|
|
7
7
|
|
8
8
|
class EddingtonController:
|
@@ -10,7 +10,9 @@ class EddingtonController:
|
|
10
10
|
self._repository = repository
|
11
11
|
|
12
12
|
def render(self) -> dict:
|
13
|
-
activities = self._repository.meta.
|
13
|
+
activities = self._repository.meta.loc[
|
14
|
+
self._repository.meta["consider_for_achievements"]
|
15
|
+
].copy()
|
14
16
|
activities["day"] = [start.date() for start in activities["start"]]
|
15
17
|
|
16
18
|
sum_per_day = activities.groupby("day").apply(
|
@@ -37,7 +39,7 @@ class EddingtonController:
|
|
37
39
|
width=1000,
|
38
40
|
title=f"Eddington Number {en}",
|
39
41
|
)
|
40
|
-
.
|
42
|
+
.mark_area(interpolate="step")
|
41
43
|
.encode(
|
42
44
|
alt.X(
|
43
45
|
"distance_km",
|
@@ -46,7 +48,7 @@ class EddingtonController:
|
|
46
48
|
),
|
47
49
|
alt.Y(
|
48
50
|
"total",
|
49
|
-
scale=alt.Scale(
|
51
|
+
scale=alt.Scale(domainMax=en + 10),
|
50
52
|
title="Days exceeding distance",
|
51
53
|
),
|
52
54
|
[
|
@@ -62,8 +64,14 @@ class EddingtonController:
|
|
62
64
|
.encode(alt.X("distance_km"), alt.Y("total"))
|
63
65
|
)
|
64
66
|
)
|
65
|
-
.interactive(
|
67
|
+
.interactive()
|
66
68
|
.to_json(format="vega")
|
67
69
|
)
|
68
70
|
|
69
|
-
return {
|
71
|
+
return {
|
72
|
+
"eddington_number": en,
|
73
|
+
"logarithmic_plot": logarithmic_plot,
|
74
|
+
"eddington_table": eddington.loc[
|
75
|
+
(eddington["distance_km"] > en) & (eddington["distance_km"] <= en + 10)
|
76
|
+
].to_dict(orient="records"),
|
77
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
{% extends "page.html.j2" %}
|
2
|
+
|
3
|
+
{% block container %}
|
4
|
+
<h1 class="mb-3">Eddington Number</h1>
|
5
|
+
|
6
|
+
<div class="row mb-3">
|
7
|
+
<div class="col-md-4">
|
8
|
+
<h2>Definition</h2>
|
9
|
+
|
10
|
+
<p>Your Eddington number is <b>{{ eddington_number }}</b>.</p>
|
11
|
+
|
12
|
+
<p>That means that on {{ eddington_number }} separate days you
|
13
|
+
have recorded activities exceeding {{ eddington_number }} km. Going far is one thing, going often is
|
14
|
+
another. But going far often is hard. Also if you increment the Eddington number, all days with less
|
15
|
+
distance will not count any more. It becomes increasingly hard to increment the Eddington number because you
|
16
|
+
don't only need to achieve a higher count, but all shorter activities don't count towards the bigger number.
|
17
|
+
</p>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div class="col-md-8">
|
21
|
+
<h2>Missing days</h2>
|
22
|
+
|
23
|
+
<p>How many more days do you need to increment your Eddington number?</p>
|
24
|
+
|
25
|
+
<table class="table">
|
26
|
+
<thead>
|
27
|
+
<tr>
|
28
|
+
<th>Distance / km</th>
|
29
|
+
<th>Count</th>
|
30
|
+
<th>Missing days</th>
|
31
|
+
</tr>
|
32
|
+
</thead>
|
33
|
+
<tbody>
|
34
|
+
{% for row in eddington_table %}
|
35
|
+
<tr>
|
36
|
+
<td>{{ row['distance_km'] }}</td>
|
37
|
+
<td>{{ row['total'] }}</td>
|
38
|
+
<td>{{ row['missing'] }}</td>
|
39
|
+
</tr>
|
40
|
+
{% endfor %}
|
41
|
+
</tbody>
|
42
|
+
</table>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div class="row mb-1">
|
47
|
+
<div class="col">
|
48
|
+
<h2>Plot</h2>
|
49
|
+
|
50
|
+
<p>In a graphical representation, the Eddington number is the distance where the red line intersects with the
|
51
|
+
blue area.</p>
|
52
|
+
|
53
|
+
{{ vega_direct("eddington-log", logarithmic_plot) }}
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
{% endblock %}
|
@@ -20,7 +20,9 @@ class EntryController:
|
|
20
20
|
"latest_activities": [],
|
21
21
|
}
|
22
22
|
|
23
|
-
for activity in itertools.islice(
|
23
|
+
for activity in itertools.islice(
|
24
|
+
self._repository.iter_activities(dropna=True), 15
|
25
|
+
):
|
24
26
|
time_series = self._repository.get_time_series(activity["id"])
|
25
27
|
result["latest_activities"].append(
|
26
28
|
{
|
@@ -33,7 +35,7 @@ class EntryController:
|
|
33
35
|
|
34
36
|
def distance_last_30_days_meta_plot(meta: pd.DataFrame) -> str:
|
35
37
|
before_30_days = pd.to_datetime(
|
36
|
-
datetime.datetime.
|
38
|
+
datetime.datetime.now() - datetime.timedelta(days=31)
|
37
39
|
)
|
38
40
|
return (
|
39
41
|
alt.Chart(
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from flask import Blueprint
|
2
|
+
from flask import render_template
|
3
|
+
|
4
|
+
from ...core.activities import ActivityRepository
|
5
|
+
from .controller import EquipmentController
|
6
|
+
|
7
|
+
|
8
|
+
def make_equipment_blueprint(repository: ActivityRepository) -> Blueprint:
|
9
|
+
blueprint = Blueprint("equipment", __name__, template_folder="templates")
|
10
|
+
|
11
|
+
equipment_controller = EquipmentController(repository)
|
12
|
+
|
13
|
+
@blueprint.route("/")
|
14
|
+
def index():
|
15
|
+
return render_template(
|
16
|
+
"equipment/index.html.j2", **equipment_controller.render()
|
17
|
+
)
|
18
|
+
|
19
|
+
return blueprint
|