geo-activity-playground 0.23.0__py3-none-any.whl → 0.24.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 (77) hide show
  1. geo_activity_playground/__main__.py +1 -1
  2. geo_activity_playground/core/activities.py +18 -12
  3. geo_activity_playground/core/activity_parsers.py +8 -32
  4. geo_activity_playground/core/cache_migrations.py +24 -0
  5. geo_activity_playground/core/heatmap.py +21 -21
  6. geo_activity_playground/core/privacy_zones.py +16 -0
  7. geo_activity_playground/core/similarity.py +1 -1
  8. geo_activity_playground/core/test_time_conversion.py +37 -0
  9. geo_activity_playground/core/time_conversion.py +14 -0
  10. geo_activity_playground/explorer/tile_visits.py +44 -32
  11. geo_activity_playground/importers/__init__.py +0 -0
  12. geo_activity_playground/importers/directory.py +7 -2
  13. geo_activity_playground/importers/strava_api.py +8 -1
  14. geo_activity_playground/importers/strava_checkout.py +4 -3
  15. geo_activity_playground/webui/__init__.py +0 -0
  16. geo_activity_playground/webui/activity/__init__.py +0 -0
  17. geo_activity_playground/webui/activity/blueprint.py +58 -0
  18. geo_activity_playground/webui/{activity_controller.py → activity/controller.py} +128 -18
  19. geo_activity_playground/webui/{templates/activity-day.html.j2 → activity/templates/activity/day.html.j2} +14 -2
  20. geo_activity_playground/webui/{templates/activity-name.html.j2 → activity/templates/activity/name.html.j2} +1 -1
  21. geo_activity_playground/webui/{templates/activity.html.j2 → activity/templates/activity/show.html.j2} +9 -4
  22. geo_activity_playground/webui/app.py +54 -283
  23. geo_activity_playground/webui/calendar/__init__.py +0 -0
  24. geo_activity_playground/webui/calendar/blueprint.py +26 -0
  25. geo_activity_playground/webui/{calendar_controller.py → calendar/controller.py} +5 -5
  26. geo_activity_playground/webui/{templates/calendar.html.j2 → calendar/templates/calendar/index.html.j2} +3 -2
  27. geo_activity_playground/webui/{templates/calendar-month.html.j2 → calendar/templates/calendar/month.html.j2} +2 -2
  28. geo_activity_playground/webui/eddington/__init__.py +0 -0
  29. geo_activity_playground/webui/eddington/blueprint.py +19 -0
  30. geo_activity_playground/webui/{eddington_controller.py → eddington/controller.py} +14 -6
  31. geo_activity_playground/webui/eddington/templates/eddington/index.html.j2 +56 -0
  32. geo_activity_playground/webui/entry_controller.py +1 -1
  33. geo_activity_playground/webui/equipment/__init__.py +0 -0
  34. geo_activity_playground/webui/equipment/blueprint.py +19 -0
  35. geo_activity_playground/webui/{equipment_controller.py → equipment/controller.py} +5 -3
  36. geo_activity_playground/webui/explorer/__init__.py +0 -0
  37. geo_activity_playground/webui/explorer/blueprint.py +54 -0
  38. geo_activity_playground/webui/{templates/explorer.html.j2 → explorer/templates/explorer/index.html.j2} +2 -2
  39. geo_activity_playground/webui/heatmap/__init__.py +0 -0
  40. geo_activity_playground/webui/heatmap/blueprint.py +41 -0
  41. geo_activity_playground/webui/{heatmap_controller.py → heatmap/heatmap_controller.py} +38 -11
  42. geo_activity_playground/webui/{templates/heatmap.html.j2 → heatmap/templates/heatmap/index.html.j2} +17 -2
  43. geo_activity_playground/webui/search_controller.py +1 -9
  44. geo_activity_playground/webui/square_planner/__init__.py +0 -0
  45. geo_activity_playground/webui/square_planner/blueprint.py +38 -0
  46. geo_activity_playground/webui/summary/__init__.py +0 -0
  47. geo_activity_playground/webui/summary/blueprint.py +16 -0
  48. geo_activity_playground/webui/summary/controller.py +268 -0
  49. geo_activity_playground/webui/summary/templates/summary/index.html.j2 +135 -0
  50. geo_activity_playground/webui/templates/{index.html.j2 → home.html.j2} +1 -1
  51. geo_activity_playground/webui/templates/page.html.j2 +22 -19
  52. geo_activity_playground/webui/templates/search.html.j2 +1 -1
  53. geo_activity_playground/webui/tile/__init__.py +0 -0
  54. geo_activity_playground/webui/tile/blueprint.py +31 -0
  55. geo_activity_playground/webui/upload/__init__.py +0 -0
  56. geo_activity_playground/webui/upload/blueprint.py +28 -0
  57. geo_activity_playground/webui/{upload_controller.py → upload/controller.py} +1 -0
  58. geo_activity_playground/webui/{templates/upload.html.j2 → upload/templates/upload/index.html.j2} +1 -1
  59. {geo_activity_playground-0.23.0.dist-info → geo_activity_playground-0.24.1.dist-info}/METADATA +2 -1
  60. geo_activity_playground-0.24.1.dist-info/RECORD +95 -0
  61. geo_activity_playground/webui/config_controller.py +0 -12
  62. geo_activity_playground/webui/locations_controller.py +0 -28
  63. geo_activity_playground/webui/summary_controller.py +0 -60
  64. geo_activity_playground/webui/templates/config.html.j2 +0 -24
  65. geo_activity_playground/webui/templates/eddington.html.j2 +0 -18
  66. geo_activity_playground/webui/templates/locations.html.j2 +0 -38
  67. geo_activity_playground/webui/templates/summary.html.j2 +0 -21
  68. geo_activity_playground-0.23.0.dist-info/RECORD +0 -74
  69. /geo_activity_playground/webui/{templates/activity-lines.html.j2 → activity/templates/activity/lines.html.j2} +0 -0
  70. /geo_activity_playground/webui/{templates/equipment.html.j2 → equipment/templates/equipment/index.html.j2} +0 -0
  71. /geo_activity_playground/webui/{explorer_controller.py → explorer/controller.py} +0 -0
  72. /geo_activity_playground/webui/{square_planner_controller.py → square_planner/controller.py} +0 -0
  73. /geo_activity_playground/webui/{templates/square-planner.html.j2 → square_planner/templates/square_planner/index.html.j2} +0 -0
  74. /geo_activity_playground/webui/{tile_controller.py → tile/controller.py} +0 -0
  75. {geo_activity_playground-0.23.0.dist-info → geo_activity_playground-0.24.1.dist-info}/LICENSE +0 -0
  76. {geo_activity_playground-0.23.0.dist-info → geo_activity_playground-0.24.1.dist-info}/WHEEL +0 -0
  77. {geo_activity_playground-0.23.0.dist-info → geo_activity_playground-0.24.1.dist-info}/entry_points.txt +0 -0
@@ -1,207 +1,28 @@
1
1
  import json
2
2
  import pathlib
3
3
  import secrets
4
- import urllib
5
4
 
6
5
  from flask import Flask
7
6
  from flask import redirect
8
7
  from flask import render_template
9
8
  from flask import request
10
- from flask import Response
11
9
 
12
- from .locations_controller import LocationsController
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
13
19
  from .search_controller import SearchController
14
- from geo_activity_playground.core.activities import ActivityRepository
15
- from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
16
- from geo_activity_playground.webui.activity_controller import ActivityController
17
- from geo_activity_playground.webui.calendar_controller import CalendarController
18
- from geo_activity_playground.webui.config_controller import ConfigController
19
- from geo_activity_playground.webui.eddington_controller import EddingtonController
20
- from geo_activity_playground.webui.entry_controller import EntryController
21
- from geo_activity_playground.webui.equipment_controller import EquipmentController
22
- from geo_activity_playground.webui.explorer_controller import ExplorerController
23
- from geo_activity_playground.webui.heatmap_controller import HeatmapController
24
- from geo_activity_playground.webui.search_controller import SearchController
25
- from geo_activity_playground.webui.square_planner_controller import (
26
- SquarePlannerController,
27
- )
28
- from geo_activity_playground.webui.strava_controller import StravaController
29
- from geo_activity_playground.webui.summary_controller import SummaryController
30
- from geo_activity_playground.webui.tile_controller import (
31
- TileController,
32
- )
33
- from geo_activity_playground.webui.upload_controller import UploadController
34
-
35
-
36
- def route_activity(app: Flask, repository: ActivityRepository) -> None:
37
- activity_controller = ActivityController(repository)
38
-
39
- @app.route("/activity/all")
40
- def activity_all():
41
- return render_template(
42
- "activity-lines.html.j2", **activity_controller.render_all()
43
- )
44
-
45
- @app.route("/activity/<id>")
46
- def activity(id: str):
47
- return render_template(
48
- "activity.html.j2", **activity_controller.render_activity(int(id))
49
- )
50
-
51
- @app.route("/activity/<id>/sharepic.png")
52
- def activity_sharepic(id: str):
53
- return Response(
54
- activity_controller.render_sharepic(int(id)),
55
- mimetype="image/png",
56
- )
57
-
58
- @app.route("/activity/day/<year>/<month>/<day>")
59
- def activity_day(year: str, month: str, day: str):
60
- return render_template(
61
- "activity-day.html.j2",
62
- **activity_controller.render_day(int(year), int(month), int(day))
63
- )
64
-
65
- @app.route("/activity/name/<name>")
66
- def activity_name(name: str):
67
- return render_template(
68
- "activity-name.html.j2",
69
- **activity_controller.render_name(urllib.parse.unquote(name))
70
- )
71
-
72
-
73
- def route_calendar(app: Flask, repository: ActivityRepository) -> None:
74
- calendar_controller = CalendarController(repository)
75
-
76
- @app.route("/calendar")
77
- def calendar():
78
- return render_template(
79
- "calendar.html.j2", **calendar_controller.render_overview()
80
- )
81
-
82
- @app.route("/calendar/<year>/<month>")
83
- def calendar_month(year: str, month: str):
84
- return render_template(
85
- "calendar-month.html.j2",
86
- **calendar_controller.render_month(int(year), int(month))
87
- )
88
-
89
-
90
- def route_config(app: Flask, repository: ActivityRepository) -> None:
91
- config_controller = ConfigController(repository)
92
-
93
- @app.route("/config")
94
- def config_index():
95
- return render_template("config.html.j2", **config_controller.action_index())
96
-
97
- @app.route("/config/save", methods=["POST"])
98
- def config_save():
99
- form_input = request.form
100
- return render_template(
101
- "config.html.j2", **config_controller.action_save(form_input)
102
- )
103
-
104
-
105
- def route_eddington(app: Flask, repository: ActivityRepository) -> None:
106
- eddington_controller = EddingtonController(repository)
107
-
108
- @app.route("/eddington")
109
- def eddington():
110
- return render_template("eddington.html.j2", **eddington_controller.render())
111
-
112
-
113
- def route_equipment(app: Flask, repository: ActivityRepository) -> None:
114
- equipment_controller = EquipmentController(repository)
115
-
116
- @app.route("/equipment")
117
- def equipment():
118
- return render_template("equipment.html.j2", **equipment_controller.render())
119
-
120
-
121
- def route_explorer(
122
- app: Flask, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
123
- ) -> None:
124
- explorer_controller = ExplorerController(repository, tile_visit_accessor)
125
-
126
- @app.route("/explorer/<zoom>")
127
- def explorer(zoom: str):
128
- return render_template(
129
- "explorer.html.j2", **explorer_controller.render(int(zoom))
130
- )
131
-
132
- @app.route("/explorer/<zoom>/<north>/<east>/<south>/<west>/explored.<suffix>")
133
- def explorer_download(
134
- zoom: str, north: str, east: str, south: str, west: str, suffix: str
135
- ):
136
- mimetypes = {"geojson": "application/json", "gpx": "application/xml"}
137
- return Response(
138
- explorer_controller.export_explored_tiles(
139
- int(zoom),
140
- float(north),
141
- float(east),
142
- float(south),
143
- float(west),
144
- suffix,
145
- ),
146
- mimetype=mimetypes[suffix],
147
- headers={"Content-disposition": "attachment"},
148
- )
149
-
150
- @app.route("/explorer/<zoom>/<north>/<east>/<south>/<west>/missing.<suffix>")
151
- def explorer_missing(
152
- zoom: str, north: str, east: str, south: str, west: str, suffix: str
153
- ):
154
- mimetypes = {"geojson": "application/json", "gpx": "application/xml"}
155
- return Response(
156
- explorer_controller.export_missing_tiles(
157
- int(zoom),
158
- float(north),
159
- float(east),
160
- float(south),
161
- float(west),
162
- suffix,
163
- ),
164
- mimetype=mimetypes[suffix],
165
- headers={"Content-disposition": "attachment"},
166
- )
167
-
168
-
169
- def route_heatmap(
170
- app: Flask, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
171
- ) -> None:
172
- heatmap_controller = HeatmapController(repository, tile_visit_accessor)
173
-
174
- @app.route("/heatmap")
175
- def heatmap():
176
- return render_template("heatmap.html.j2", **heatmap_controller.render())
177
-
178
- @app.route("/heatmap/tile/<z>/<x>/<y>.png")
179
- def heatmap_tile(x: str, y: str, z: str):
180
- return Response(
181
- heatmap_controller.render_tile(int(x), int(y), int(z)),
182
- mimetype="image/png",
183
- )
184
-
185
- @app.route("/heatmap/download/<north>/<east>/<south>/<west>")
186
- def heatmap_download(north: str, east: str, south: str, west: str):
187
- return Response(
188
- heatmap_controller.download_heatmap(
189
- float(north),
190
- float(east),
191
- float(south),
192
- float(west),
193
- ),
194
- mimetype="image/png",
195
- headers={"Content-disposition": 'attachment; filename="heatmap.png"'},
196
- )
197
-
198
-
199
- def route_locations(app: Flask, repository: ActivityRepository) -> None:
200
- controller = LocationsController(repository)
201
-
202
- @app.route("/locations")
203
- def locations_index():
204
- 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
205
26
 
206
27
 
207
28
  def route_search(app: Flask, repository: ActivityRepository) -> None:
@@ -216,40 +37,12 @@ def route_search(app: Flask, repository: ActivityRepository) -> None:
216
37
  )
217
38
 
218
39
 
219
- def route_square_planner(
220
- app: Flask, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
221
- ) -> None:
222
- controller = SquarePlannerController(repository, tile_visit_accessor)
223
-
224
- @app.route("/square-planner/<zoom>/<x>/<y>/<size>")
225
- def square_planner_planner(zoom, x, y, size):
226
- return render_template(
227
- "square-planner.html.j2",
228
- **controller.action_planner(int(zoom), int(x), int(y), int(size))
229
- )
230
-
231
- @app.route("/square-planner/<zoom>/<x>/<y>/<size>/missing.<suffix>")
232
- def square_planner_missing(zoom, x, y, size, suffix: str):
233
- mimetypes = {"geojson": "application/json", "gpx": "application/xml"}
234
- return Response(
235
- controller.export_missing_tiles(
236
- int(zoom),
237
- int(x),
238
- int(y),
239
- int(size),
240
- suffix,
241
- ),
242
- mimetype=mimetypes[suffix],
243
- headers={"Content-disposition": "attachment"},
244
- )
245
-
246
-
247
40
  def route_start(app: Flask, repository: ActivityRepository) -> None:
248
41
  entry_controller = EntryController(repository)
249
42
 
250
43
  @app.route("/")
251
44
  def index():
252
- return render_template("index.html.j2", **entry_controller.render())
45
+ return render_template("home.html.j2", **entry_controller.render())
253
46
 
254
47
 
255
48
  def route_strava(app: Flask, host: str, port: int) -> None:
@@ -283,54 +76,6 @@ def route_strava(app: Flask, host: str, port: int) -> None:
283
76
  )
284
77
 
285
78
 
286
- def route_summary(app: Flask, repository: ActivityRepository) -> None:
287
- summary_controller = SummaryController(repository)
288
-
289
- @app.route("/summary")
290
- def summary_statistics():
291
- return render_template("summary.html.j2", **summary_controller.render())
292
-
293
-
294
- def route_tiles(app: Flask, repository: ActivityRepository) -> None:
295
- tile_controller = TileController()
296
-
297
- @app.route("/tile/color/<z>/<x>/<y>.png")
298
- def tile_color(x: str, y: str, z: str):
299
- return Response(
300
- tile_controller.render_color(int(x), int(y), int(z)), mimetype="image/png"
301
- )
302
-
303
- @app.route("/tile/grayscale/<z>/<x>/<y>.png")
304
- def tile_grayscale(x: str, y: str, z: str):
305
- return Response(
306
- tile_controller.render_grayscale(int(x), int(y), int(z)),
307
- mimetype="image/png",
308
- )
309
-
310
- @app.route("/tile/pastel/<z>/<x>/<y>.png")
311
- def tile_pastel(x: str, y: str, z: str):
312
- return Response(
313
- tile_controller.render_pastel(int(x), int(y), int(z)), mimetype="image/png"
314
- )
315
-
316
-
317
- def route_upload(
318
- app: Flask,
319
- repository: ActivityRepository,
320
- tile_visit_accessor: TileVisitAccessor,
321
- config: dict,
322
- ):
323
- upload_controller = UploadController(repository, tile_visit_accessor, config)
324
-
325
- @app.route("/upload")
326
- def form():
327
- return render_template("upload.html.j2", **upload_controller.render_form())
328
-
329
- @app.route("/upload/receive", methods=["POST"])
330
- def receive():
331
- return upload_controller.receive()
332
-
333
-
334
79
  def get_secret_key():
335
80
  secret_file = pathlib.Path("Cache/flask-secret.json")
336
81
  if secret_file.exists():
@@ -352,23 +97,49 @@ def webui_main(
352
97
  ) -> None:
353
98
  app = Flask(__name__)
354
99
 
355
- route_activity(app, repository)
356
- route_calendar(app, repository)
357
- route_config(app, repository)
358
- route_eddington(app, repository)
359
- route_equipment(app, repository)
360
- route_explorer(app, repository, tile_visit_accessor)
361
- route_heatmap(app, repository, tile_visit_accessor)
362
- route_locations(app, repository)
363
100
  route_search(app, repository)
364
- route_square_planner(app, repository, tile_visit_accessor)
365
101
  route_start(app, repository)
366
102
  route_strava(app, host, port)
367
- route_summary(app, repository)
368
- route_tiles(app, repository)
369
- route_upload(app, repository, tile_visit_accessor, config)
370
103
 
371
104
  app.config["UPLOAD_FOLDER"] = "Activities"
372
105
  app.secret_key = get_secret_key()
373
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
+ )
144
+
374
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 geo_activity_playground.core.activities import ActivityRepository
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(["year", "month"]).apply(
18
- lambda group: sum(group["distance_km"])
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>{{ year|round(0)|int }}</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="/calendar/{{ year|round(0)|int }}/{{ month }}">{{ distance|int() }} km</a>
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="/activity/day/{{ year }}/{{ month }}/{{ day_of_month[week][day] }}"><b>{{
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="/activity/{{ activity.id }}">{{ activity.name }}</a></li>
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 geo_activity_playground.core.activities import ActivityRepository
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.copy()
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
- .mark_bar()
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(type="log"),
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(bind_y=False)
67
+ .interactive()
66
68
  .to_json(format="vega")
67
69
  )
68
70
 
69
- return {"eddington_number": en, "logarithmic_plot": logarithmic_plot}
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 %}
@@ -35,7 +35,7 @@ class EntryController:
35
35
 
36
36
  def distance_last_30_days_meta_plot(meta: pd.DataFrame) -> str:
37
37
  before_30_days = pd.to_datetime(
38
- datetime.datetime.utcnow() - datetime.timedelta(days=31), utc=True
38
+ datetime.datetime.now() - datetime.timedelta(days=31)
39
39
  )
40
40
  return (
41
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
@@ -1,8 +1,8 @@
1
1
  import altair as alt
2
2
  import pandas as pd
3
3
 
4
- from ..core.config import get_config
5
4
  from geo_activity_playground.core.activities import ActivityRepository
5
+ from geo_activity_playground.core.config import get_config
6
6
 
7
7
 
8
8
  class EquipmentController:
@@ -18,7 +18,8 @@ class EquipmentController:
18
18
  "time": group["start"],
19
19
  "total_distance_km": group["distance_km"].cumsum(),
20
20
  }
21
- )
21
+ ),
22
+ include_groups=False,
22
23
  )
23
24
  .reset_index()
24
25
  )
@@ -52,7 +53,8 @@ class EquipmentController:
52
53
  "last_use": group["start"].iloc[-1],
53
54
  },
54
55
  index=[0],
55
- )
56
+ ),
57
+ include_groups=False,
56
58
  )
57
59
  .reset_index()
58
60
  .sort_values("last_use", ascending=False)
File without changes