geo-activity-playground 0.37.0__tar.gz → 0.38.0__tar.gz
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-0.37.0 → geo_activity_playground-0.38.0}/PKG-INFO +1 -1
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/activities.py +12 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/config.py +6 -2
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/enrichment.py +9 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/meta_search.py +49 -9
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/summary_stats.py +1 -1
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/test_meta_search.py +9 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/controller.py +20 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +3 -10
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/templates/activity/name.html.j2 +2 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/templates/activity/show.html.j2 +17 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/app.py +18 -5
- geo_activity_playground-0.38.0/geo_activity_playground/webui/eddington_blueprint.py +190 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/equipment_blueprint.py +12 -3
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/explorer/blueprint.py +4 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/heatmap/blueprint.py +3 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +1 -1
- geo_activity_playground-0.38.0/geo_activity_playground/webui/search_blueprint.py +70 -0
- geo_activity_playground-0.38.0/geo_activity_playground/webui/search_util.py +64 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/summary_blueprint.py +31 -34
- geo_activity_playground-0.38.0/geo_activity_playground/webui/templates/eddington/index.html.j2 +128 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/search/index.html.j2 +8 -4
- geo_activity_playground-0.38.0/geo_activity_playground/webui/templates/search_form.html.j2 +116 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/summary/index.html.j2 +1 -1
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/pyproject.toml +4 -4
- geo_activity_playground-0.37.0/geo_activity_playground/webui/eddington_blueprint.py +0 -86
- geo_activity_playground-0.37.0/geo_activity_playground/webui/search_blueprint.py +0 -38
- geo_activity_playground-0.37.0/geo_activity_playground/webui/search_util.py +0 -31
- geo_activity_playground-0.37.0/geo_activity_playground/webui/templates/eddington/index.html.j2 +0 -61
- geo_activity_playground-0.37.0/geo_activity_playground/webui/templates/search_form.html.j2 +0 -82
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/LICENSE +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/__main__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/coordinates.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/heart_rate.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/paths.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/privacy_zones.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/raster_map.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/similarity.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/tasks.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/test_summary_stats.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/test_tiles.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/test_time_conversion.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/tiles.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/core/time_conversion.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/explorer/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/explorer/grid_file.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/explorer/tile_visits.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/explorer/video.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/heatmap_video.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/activity_parsers.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/csv_parser.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/directory.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/strava_api.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/strava_checkout.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/test_csv_parser.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/test_directory.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/importers/test_strava_api.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/blueprint.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/auth_blueprint.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/authenticator.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/calendar/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/calendar/blueprint.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/calendar/controller.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/entry_controller.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/explorer/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/explorer/controller.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/heatmap/__init__.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/heatmap/heatmap_controller.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/plot_util.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/blueprint.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/controller.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/index.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/segmentation.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/settings/templates/settings/strava.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/square_planner_blueprint.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/MarkerCluster.Default.css +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/MarkerCluster.css +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/bootstrap.bundle.min.js +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/bootstrap.min.css +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/favicon.ico +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/favicon.svg +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/fullscreen.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/leaflet.css +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/leaflet.fullscreen.css +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/leaflet.js +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/leaflet.markercluster.js +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/site.webmanifest +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/table-sort.min.js +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/vega-embed@6 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/vega-lite@4 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/vega@5 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/auth/index.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/equipment/index.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/home.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/page.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/square_planner/index.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/upload/index.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/templates/upload/reload.html.j2 +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/tile_blueprint.py +0 -0
- {geo_activity_playground-0.37.0 → geo_activity_playground-0.38.0}/geo_activity_playground/webui/upload_blueprint.py +0 -0
@@ -24,11 +24,14 @@ logger = logging.getLogger(__name__)
|
|
24
24
|
|
25
25
|
|
26
26
|
class ActivityMeta(TypedDict):
|
27
|
+
average_speed_elapsed_kmh: float
|
28
|
+
average_speed_moving_kmh: float
|
27
29
|
calories: float
|
28
30
|
commute: bool
|
29
31
|
consider_for_achievements: bool
|
30
32
|
distance_km: float
|
31
33
|
elapsed_time: datetime.timedelta
|
34
|
+
elevation_gain: float
|
32
35
|
end_latitude: float
|
33
36
|
end_longitude: float
|
34
37
|
equipment: str
|
@@ -110,6 +113,15 @@ def build_activity_meta() -> None:
|
|
110
113
|
|
111
114
|
meta.sort_values("start", inplace=True)
|
112
115
|
|
116
|
+
meta.loc[meta["kind"] == "", "kind"] = "Unknown"
|
117
|
+
meta.loc[meta["equipment"] == "", "equipment"] = "Unknown"
|
118
|
+
meta["average_speed_moving_kmh"] = meta["distance_km"] / (
|
119
|
+
meta["moving_time"].dt.total_seconds() / 3_600
|
120
|
+
)
|
121
|
+
meta["average_speed_elapsed_kmh"] = meta["distance_km"] / (
|
122
|
+
meta["elapsed_time"].dt.total_seconds() / 3_600
|
123
|
+
)
|
124
|
+
|
113
125
|
meta.to_parquet(activities_file())
|
114
126
|
|
115
127
|
|
@@ -45,7 +45,12 @@ class Config:
|
|
45
45
|
time_diff_threshold_seconds: Optional[int] = 30
|
46
46
|
upload_password: Optional[str] = None
|
47
47
|
map_tile_url: str = "https://tile.openstreetmap.org/{zoom}/{x}/{y}.png"
|
48
|
-
map_tile_attribution: str =
|
48
|
+
map_tile_attribution: str = (
|
49
|
+
'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> | <a href="https://www.openstreetmap.org/fixthemap">Correct Map</a>'
|
50
|
+
)
|
51
|
+
search_queries_favorites: list[dict] = dataclasses.field(default_factory=list)
|
52
|
+
search_queries_last: list[dict] = dataclasses.field(default_factory=list)
|
53
|
+
search_queries_num_keep: int = 10
|
49
54
|
|
50
55
|
|
51
56
|
class ConfigAccessor:
|
@@ -60,7 +65,6 @@ class ConfigAccessor:
|
|
60
65
|
return self._config
|
61
66
|
|
62
67
|
def save(self) -> None:
|
63
|
-
print(self._config)
|
64
68
|
with open(new_config_file(), "w") as f:
|
65
69
|
json.dump(
|
66
70
|
dataclasses.asdict(self._config),
|
@@ -120,6 +120,8 @@ def _get_metadata_from_timeseries(timeseries: pd.DataFrame) -> ActivityMeta:
|
|
120
120
|
metadata["start_longitude"] = timeseries["longitude"].iloc[0]
|
121
121
|
metadata["end_longitude"] = timeseries["longitude"].iloc[-1]
|
122
122
|
|
123
|
+
metadata["elevation_gain"] = timeseries["elevation_gain_cum"].iloc[-1]
|
124
|
+
|
123
125
|
return metadata
|
124
126
|
|
125
127
|
|
@@ -191,4 +193,11 @@ def _embellish_single_time_series(
|
|
191
193
|
timeseries["x"] = x
|
192
194
|
timeseries["y"] = y
|
193
195
|
|
196
|
+
if "altitude" in timeseries.columns:
|
197
|
+
altitude_diff = timeseries["altitude"].diff()
|
198
|
+
altitude_diff = altitude_diff.ewm(span=5, min_periods=5).mean()
|
199
|
+
altitude_diff.loc[altitude_diff.abs() > 30] = 0
|
200
|
+
altitude_diff.loc[altitude_diff < 0] = 0
|
201
|
+
timeseries["elevation_gain_cum"] = altitude_diff.cumsum()
|
202
|
+
|
194
203
|
return timeseries
|
@@ -4,6 +4,7 @@ import re
|
|
4
4
|
import urllib.parse
|
5
5
|
from typing import Optional
|
6
6
|
|
7
|
+
import dateutil.parser
|
7
8
|
import numpy as np
|
8
9
|
import pandas as pd
|
9
10
|
|
@@ -17,6 +18,23 @@ class SearchQuery:
|
|
17
18
|
start_begin: Optional[datetime.date] = None
|
18
19
|
start_end: Optional[datetime.date] = None
|
19
20
|
|
21
|
+
def __str__(self) -> str:
|
22
|
+
bits = []
|
23
|
+
if self.name:
|
24
|
+
bits.append(f"name is “{self.name}”")
|
25
|
+
if self.equipment:
|
26
|
+
bits.append(
|
27
|
+
"equipment is "
|
28
|
+
+ (" or ".join(f"“{equipment}”" for equipment in self.equipment))
|
29
|
+
)
|
30
|
+
if self.kind:
|
31
|
+
bits.append("kind is " + (" or ".join(f"“{kind}”" for kind in self.kind)))
|
32
|
+
if self.start_begin:
|
33
|
+
bits.append(f"after “{self.start_begin.isoformat()}”")
|
34
|
+
if self.start_end:
|
35
|
+
bits.append(f"until “{self.start_end.isoformat()}”")
|
36
|
+
return " and ".join(bits)
|
37
|
+
|
20
38
|
@property
|
21
39
|
def active(self) -> bool:
|
22
40
|
return (
|
@@ -27,7 +45,7 @@ class SearchQuery:
|
|
27
45
|
or self.start_end
|
28
46
|
)
|
29
47
|
|
30
|
-
def
|
48
|
+
def to_primitives(self) -> dict:
|
31
49
|
return {
|
32
50
|
"equipment": self.equipment,
|
33
51
|
"kind": self.kind,
|
@@ -35,9 +53,24 @@ class SearchQuery:
|
|
35
53
|
"name_case_sensitive": self.name_case_sensitive,
|
36
54
|
"start_begin": _format_optional_date(self.start_begin),
|
37
55
|
"start_end": _format_optional_date(self.start_end),
|
38
|
-
"active": self.active,
|
39
56
|
}
|
40
57
|
|
58
|
+
@classmethod
|
59
|
+
def from_primitives(cls, d: dict) -> "SearchQuery":
|
60
|
+
return cls(
|
61
|
+
equipment=d.get("equipment", []),
|
62
|
+
kind=d.get("kind", []),
|
63
|
+
name=d.get("name", None),
|
64
|
+
name_case_sensitive=d.get("name_case_sensitive", False),
|
65
|
+
start_begin=_parse_date_or_none(d.get("start_begin", None)),
|
66
|
+
start_end=_parse_date_or_none(d.get("start_end", None)),
|
67
|
+
)
|
68
|
+
|
69
|
+
def to_jinja(self) -> dict:
|
70
|
+
result = self.to_primitives()
|
71
|
+
result["active"] = self.active
|
72
|
+
return result
|
73
|
+
|
41
74
|
def to_url_str(self) -> str:
|
42
75
|
variables = []
|
43
76
|
for equipment in self.equipment:
|
@@ -58,13 +91,6 @@ class SearchQuery:
|
|
58
91
|
)
|
59
92
|
|
60
93
|
|
61
|
-
def _format_optional_date(date: Optional[datetime.date]) -> str:
|
62
|
-
if date is None:
|
63
|
-
return ""
|
64
|
-
else:
|
65
|
-
return date.isoformat()
|
66
|
-
|
67
|
-
|
68
94
|
def apply_search_query(
|
69
95
|
activity_meta: pd.DataFrame, search_query: SearchQuery
|
70
96
|
) -> pd.DataFrame:
|
@@ -100,6 +126,13 @@ def apply_search_query(
|
|
100
126
|
return activity_meta.loc[mask]
|
101
127
|
|
102
128
|
|
129
|
+
def _format_optional_date(date: Optional[datetime.date]) -> str:
|
130
|
+
if date is None:
|
131
|
+
return ""
|
132
|
+
else:
|
133
|
+
return date.isoformat()
|
134
|
+
|
135
|
+
|
103
136
|
def _make_mask(
|
104
137
|
index: pd.Index,
|
105
138
|
default: bool,
|
@@ -115,3 +148,10 @@ def _filter_column(column: pd.Series, values: list):
|
|
115
148
|
for equipment in values:
|
116
149
|
sub_mask |= column == equipment
|
117
150
|
return sub_mask
|
151
|
+
|
152
|
+
|
153
|
+
def _parse_date_or_none(s: Optional[str]) -> Optional[datetime.date]:
|
154
|
+
if not s:
|
155
|
+
return None
|
156
|
+
else:
|
157
|
+
return dateutil.parser.parse(s).date()
|
@@ -27,4 +27,4 @@ def get_equipment_use_table(
|
|
27
27
|
result["first_use"] = [date.date().isoformat() for date in result["first_use"]]
|
28
28
|
result["last_use"] = [date.date().isoformat() for date in result["last_use"]]
|
29
29
|
|
30
|
-
return result.reset_index()
|
30
|
+
return result.reset_index()
|
@@ -89,3 +89,12 @@ def test_make_mask() -> None:
|
|
89
89
|
index = [1, 2]
|
90
90
|
assert (_make_mask(index, True) == pd.Series([True, True], index=index)).all()
|
91
91
|
assert (_make_mask(index, False) == pd.Series([False, False], index=index)).all()
|
92
|
+
|
93
|
+
|
94
|
+
def test_search_query_from_primitives() -> None:
|
95
|
+
search_query = SearchQuery.from_primitives(
|
96
|
+
{"start_end": "2025-01-04", "equipment": ["A", "B"]}
|
97
|
+
)
|
98
|
+
assert search_query.start_end == datetime.date(2025, 1, 4)
|
99
|
+
assert search_query.equipment == ["A", "B"]
|
100
|
+
assert search_query.kind == []
|
@@ -108,6 +108,8 @@ class ActivityController:
|
|
108
108
|
result["heart_zones_plot"] = heart_rate_zone_plot(heart_zones)
|
109
109
|
if "altitude" in time_series.columns:
|
110
110
|
result["altitude_time_plot"] = altitude_time_plot(time_series)
|
111
|
+
if "elevation_gain_cum" in time_series.columns:
|
112
|
+
result["elevation_gain_cum_plot"] = elevation_gain_cum_plot(time_series)
|
111
113
|
if "heartrate" in time_series.columns:
|
112
114
|
result["heartrate_time_plot"] = heart_rate_time_plot(time_series)
|
113
115
|
if "cadence" in time_series.columns:
|
@@ -323,6 +325,24 @@ def altitude_time_plot(time_series: pd.DataFrame) -> str:
|
|
323
325
|
)
|
324
326
|
|
325
327
|
|
328
|
+
def elevation_gain_cum_plot(time_series: pd.DataFrame) -> str:
|
329
|
+
return (
|
330
|
+
alt.Chart(time_series, title="Altitude Gain")
|
331
|
+
.mark_line()
|
332
|
+
.encode(
|
333
|
+
alt.X("time", title="Time"),
|
334
|
+
alt.Y(
|
335
|
+
"elevation_gain_cum",
|
336
|
+
scale=alt.Scale(zero=False),
|
337
|
+
title="Altitude gain / m",
|
338
|
+
),
|
339
|
+
alt.Color("segment_id:N", title="Segment"),
|
340
|
+
)
|
341
|
+
.interactive(bind_y=False)
|
342
|
+
.to_json(format="vega")
|
343
|
+
)
|
344
|
+
|
345
|
+
|
326
346
|
def heart_rate_time_plot(time_series: pd.DataFrame) -> str:
|
327
347
|
return (
|
328
348
|
alt.Chart(time_series, title="Heart Rate")
|
@@ -9,7 +9,7 @@
|
|
9
9
|
|
10
10
|
|
11
11
|
<div class="row mb-3">
|
12
|
-
<div class="col-
|
12
|
+
<div class="col-12">
|
13
13
|
<div id="activity-map" style="height: 500px;"></div>
|
14
14
|
<script>
|
15
15
|
var map = L.map('activity-map', {
|
@@ -26,15 +26,6 @@
|
|
26
26
|
map.fitBounds(geojson.getBounds());
|
27
27
|
</script>
|
28
28
|
</div>
|
29
|
-
<div class="col-md-3">
|
30
|
-
<ol>
|
31
|
-
{% for activity in activities %}
|
32
|
-
<li><span style="color: {{ activity['color'] }};">█</span> <a
|
33
|
-
href="{{ url_for('.show', id=activity.id) }}">{{
|
34
|
-
activity.name }}</a></li>
|
35
|
-
{% endfor %}
|
36
|
-
</ol>
|
37
|
-
</div>
|
38
29
|
</div>
|
39
30
|
|
40
31
|
<div class="row mb-3">
|
@@ -48,6 +39,7 @@
|
|
48
39
|
<th>Date</th>
|
49
40
|
<th>Distance / km</th>
|
50
41
|
<th>Elapsed time</th>
|
42
|
+
<th>Speed / km/h</th>
|
51
43
|
<th>Equipment</th>
|
52
44
|
<th>Kind</th>
|
53
45
|
</tr>
|
@@ -61,6 +53,7 @@
|
|
61
53
|
<td>{{ activity.start|dt }}</td>
|
62
54
|
<td>{{ activity.distance_km | round(1) }}</td>
|
63
55
|
<td>{{ activity.elapsed_time|td }}</td>
|
56
|
+
<td>{{ activity.average_speed_moving_kmh|round(1) }}</td>
|
64
57
|
<td>{{ activity["equipment"] }}</td>
|
65
58
|
<td>{{ activity["kind"] }}</td>
|
66
59
|
</tr>
|
@@ -57,6 +57,7 @@
|
|
57
57
|
<th>Date</th>
|
58
58
|
<th class="numeric-sort">Distance / km</th>
|
59
59
|
<th>Elapsed time</th>
|
60
|
+
<th>Speed / km/h</th>
|
60
61
|
<th>Equipment</th>
|
61
62
|
<th>Kind</th>
|
62
63
|
</tr>
|
@@ -70,6 +71,7 @@
|
|
70
71
|
<td>{{ activity.start|dt }}</td>
|
71
72
|
<td>{{ activity.distance_km | round(1) }}</td>
|
72
73
|
<td>{{ activity.elapsed_time|td }}</td>
|
74
|
+
<td>{{ activity.average_speed_moving_kmh|round(1) }}</td>
|
73
75
|
<td>{{ activity["equipment"] }}</td>
|
74
76
|
<td>{{ activity["kind"] }}</td>
|
75
77
|
</tr>
|
@@ -22,6 +22,12 @@
|
|
22
22
|
<dd>{{ activity.elapsed_time|td }}</dd>
|
23
23
|
<dt>Moving time</dt>
|
24
24
|
<dd>{{ activity.moving_time|td }}</dd>
|
25
|
+
<dt>Average moving speed</dt>
|
26
|
+
<dd>{{ activity.average_speed_moving_kmh|round(1) }} km/h = {{
|
27
|
+
(60/activity.average_speed_moving_kmh)|round(1) }} min/km</dd>
|
28
|
+
<dt>Average elapsed speed</dt>
|
29
|
+
<dd>{{ activity.average_speed_elapsed_kmh|round(1) }} km/h = {{
|
30
|
+
(60/activity.average_speed_elapsed_kmh)|round(1) }} min/km</dd>
|
25
31
|
<dt>Start time</dt>
|
26
32
|
<dd><a href="{{ url_for('activity.day', year=date.year, month=date.month, day=date.day) }}">{{ date }}</a>
|
27
33
|
{{ time }}
|
@@ -30,6 +36,10 @@
|
|
30
36
|
<dd>{{ activity.calories }}</dd>
|
31
37
|
<dt>Steps</dt>
|
32
38
|
<dd>{{ activity.steps }}</dd>
|
39
|
+
{% if activity.elevation_gain is defined %}
|
40
|
+
<dt>Elevation gain</dt>
|
41
|
+
<dd>{{ activity.elevation_gain|round(0) }} m</dd>
|
42
|
+
{% endif %}
|
33
43
|
<dt>Equipment</dt>
|
34
44
|
<dd>{{ activity['equipment'] }}</dd>
|
35
45
|
<dt>New Explorer Tiles</dt>
|
@@ -100,6 +110,7 @@
|
|
100
110
|
</div>
|
101
111
|
</div>
|
102
112
|
|
113
|
+
{% if altitude_time_plot is defined %}
|
103
114
|
<div class="row mb-3">
|
104
115
|
<div class="col">
|
105
116
|
<h2>Altitude</h2>
|
@@ -110,7 +121,13 @@
|
|
110
121
|
<div class="col-md-4">
|
111
122
|
{{ vega_direct("altitude_time_plot", altitude_time_plot) }}
|
112
123
|
</div>
|
124
|
+
{% if elevation_gain_cum_plot is defined %}
|
125
|
+
<div class="col-md-4">
|
126
|
+
{{ vega_direct("elevation_gain_cum_plot", elevation_gain_cum_plot) }}
|
127
|
+
</div>
|
128
|
+
{% endif %}
|
113
129
|
</div>
|
130
|
+
{% endif %}
|
114
131
|
|
115
132
|
{% if heartrate_time_plot is defined %}
|
116
133
|
<h2>Heart rate</h2>
|
@@ -8,6 +8,7 @@ import urllib.parse
|
|
8
8
|
|
9
9
|
from flask import Flask
|
10
10
|
from flask import render_template
|
11
|
+
from flask import request
|
11
12
|
|
12
13
|
from ..core.activities import ActivityRepository
|
13
14
|
from ..core.config import Config
|
@@ -31,6 +32,7 @@ from .square_planner_blueprint import make_square_planner_blueprint
|
|
31
32
|
from .summary_blueprint import make_summary_blueprint
|
32
33
|
from .tile_blueprint import make_tile_blueprint
|
33
34
|
from .upload_blueprint import make_upload_blueprint
|
35
|
+
from geo_activity_playground.webui.search_util import SearchQueryHistory
|
34
36
|
|
35
37
|
|
36
38
|
def route_start(app: Flask, repository: ActivityRepository, config: Config) -> None:
|
@@ -79,6 +81,7 @@ def web_ui_main(
|
|
79
81
|
return f"{h}:{m:02d}:{s:02d}"
|
80
82
|
|
81
83
|
authenticator = Authenticator(config_accessor())
|
84
|
+
search_query_history = SearchQueryHistory(config_accessor, authenticator)
|
82
85
|
|
83
86
|
config = config_accessor()
|
84
87
|
activity_controller = ActivityController(repository, tile_visit_accessor, config)
|
@@ -98,16 +101,20 @@ def web_ui_main(
|
|
98
101
|
make_calendar_blueprint(calendar_controller), url_prefix="/calendar"
|
99
102
|
)
|
100
103
|
app.register_blueprint(
|
101
|
-
make_eddington_blueprint(repository),
|
104
|
+
make_eddington_blueprint(repository, search_query_history),
|
105
|
+
url_prefix="/eddington",
|
102
106
|
)
|
103
107
|
app.register_blueprint(
|
104
108
|
make_equipment_blueprint(repository, config), url_prefix="/equipment"
|
105
109
|
)
|
106
110
|
app.register_blueprint(
|
107
|
-
make_explorer_blueprint(explorer_controller),
|
111
|
+
make_explorer_blueprint(explorer_controller, authenticator),
|
112
|
+
url_prefix="/explorer",
|
108
113
|
)
|
109
114
|
app.register_blueprint(
|
110
|
-
make_heatmap_blueprint(
|
115
|
+
make_heatmap_blueprint(
|
116
|
+
repository, tile_visit_accessor, config_accessor(), search_query_history
|
117
|
+
),
|
111
118
|
url_prefix="/heatmap",
|
112
119
|
)
|
113
120
|
app.register_blueprint(
|
@@ -119,11 +126,14 @@ def web_ui_main(
|
|
119
126
|
url_prefix="/square-planner",
|
120
127
|
)
|
121
128
|
app.register_blueprint(
|
122
|
-
make_search_blueprint(
|
129
|
+
make_search_blueprint(
|
130
|
+
repository, search_query_history, authenticator, config_accessor
|
131
|
+
),
|
123
132
|
url_prefix="/search",
|
124
133
|
)
|
125
134
|
app.register_blueprint(
|
126
|
-
make_summary_blueprint(repository, config),
|
135
|
+
make_summary_blueprint(repository, config, search_query_history),
|
136
|
+
url_prefix="/summary",
|
127
137
|
)
|
128
138
|
|
129
139
|
app.register_blueprint(make_tile_blueprint(config), url_prefix="/tile")
|
@@ -148,6 +158,9 @@ def web_ui_main(
|
|
148
158
|
"version": _try_get_version(),
|
149
159
|
"num_activities": len(repository),
|
150
160
|
"map_tile_attribution": config_accessor().map_tile_attribution,
|
161
|
+
"search_query_favorites": search_query_history.prepare_favorites(),
|
162
|
+
"search_query_last": search_query_history.prepare_last(),
|
163
|
+
"request_url": urllib.parse.quote_plus(request.url),
|
151
164
|
}
|
152
165
|
if len(repository):
|
153
166
|
variables["equipments_avail"] = sorted(
|
@@ -0,0 +1,190 @@
|
|
1
|
+
import datetime
|
2
|
+
|
3
|
+
import altair as alt
|
4
|
+
import numpy as np
|
5
|
+
import pandas as pd
|
6
|
+
from flask import Blueprint
|
7
|
+
from flask import render_template
|
8
|
+
from flask import request
|
9
|
+
|
10
|
+
from geo_activity_playground.core.activities import ActivityRepository
|
11
|
+
from geo_activity_playground.core.meta_search import apply_search_query
|
12
|
+
from geo_activity_playground.webui.search_util import search_query_from_form
|
13
|
+
from geo_activity_playground.webui.search_util import SearchQueryHistory
|
14
|
+
|
15
|
+
|
16
|
+
def make_eddington_blueprint(
|
17
|
+
repository: ActivityRepository, search_query_history: SearchQueryHistory
|
18
|
+
) -> Blueprint:
|
19
|
+
blueprint = Blueprint("eddington", __name__, template_folder="templates")
|
20
|
+
|
21
|
+
@blueprint.route("/")
|
22
|
+
def index():
|
23
|
+
query = search_query_from_form(request.args)
|
24
|
+
search_query_history.register_query(query)
|
25
|
+
activities = (
|
26
|
+
apply_search_query(repository.meta, query)
|
27
|
+
.dropna(subset=["start", "distance_km"])
|
28
|
+
.copy()
|
29
|
+
)
|
30
|
+
|
31
|
+
activities["year"] = [start.year for start in activities["start"]]
|
32
|
+
activities["date"] = [start.date() for start in activities["start"]]
|
33
|
+
activities["isoyear"] = [
|
34
|
+
start.isocalendar().year for start in activities["start"]
|
35
|
+
]
|
36
|
+
activities["isoweek"] = [
|
37
|
+
start.isocalendar().week for start in activities["start"]
|
38
|
+
]
|
39
|
+
|
40
|
+
en_per_day, eddington_df_per_day = _get_distances_per_group(
|
41
|
+
activities.groupby("date")
|
42
|
+
)
|
43
|
+
en_per_week, eddington_df_per_week = _get_distances_per_group(
|
44
|
+
activities.groupby(["isoyear", "isoweek"])
|
45
|
+
)
|
46
|
+
|
47
|
+
return render_template(
|
48
|
+
"eddington/index.html.j2",
|
49
|
+
eddington_number=en_per_day,
|
50
|
+
logarithmic_plot=_make_eddington_plot(
|
51
|
+
eddington_df_per_day, en_per_day, "Days"
|
52
|
+
),
|
53
|
+
eddington_per_week=en_per_week,
|
54
|
+
eddington_per_week_plot=_make_eddington_plot(
|
55
|
+
eddington_df_per_week, en_per_week, "Weeks"
|
56
|
+
),
|
57
|
+
eddington_table=eddington_df_per_day.loc[
|
58
|
+
(eddington_df_per_day["distance_km"] > en_per_day)
|
59
|
+
& (eddington_df_per_day["distance_km"] <= en_per_day + 10)
|
60
|
+
].to_dict(orient="records"),
|
61
|
+
eddington_table_weeks=eddington_df_per_week.loc[
|
62
|
+
(eddington_df_per_week["distance_km"] > en_per_week)
|
63
|
+
& (eddington_df_per_week["distance_km"] <= en_per_week + 10)
|
64
|
+
].to_dict(orient="records"),
|
65
|
+
query=query.to_jinja(),
|
66
|
+
yearly_eddington=_get_yearly_eddington(activities),
|
67
|
+
eddington_number_history_plot=_get_eddington_number_history(activities),
|
68
|
+
)
|
69
|
+
|
70
|
+
return blueprint
|
71
|
+
|
72
|
+
|
73
|
+
def _get_distances_per_group(grouped) -> tuple[int, pd.DataFrame]:
|
74
|
+
sum_per_group = grouped.apply(
|
75
|
+
lambda group: int(sum(group["distance_km"])), include_groups=False
|
76
|
+
)
|
77
|
+
counts = dict(zip(*np.unique(sorted(sum_per_group), return_counts=True)))
|
78
|
+
eddington = pd.DataFrame(
|
79
|
+
{"distance_km": d, "count": counts.get(d, 0)}
|
80
|
+
for d in range(max(counts.keys()) + 1)
|
81
|
+
)
|
82
|
+
eddington["total"] = eddington["count"][::-1].cumsum()[::-1]
|
83
|
+
en = eddington.loc[eddington["total"] >= eddington["distance_km"]][
|
84
|
+
"distance_km"
|
85
|
+
].iloc[-1]
|
86
|
+
eddington["missing"] = eddington["distance_km"] - eddington["total"]
|
87
|
+
return en, eddington
|
88
|
+
|
89
|
+
|
90
|
+
def _make_eddington_plot(eddington_df: pd.DataFrame, en: int, interval: str) -> dict:
|
91
|
+
x = list(range(1, max(eddington_df["distance_km"]) + 1))
|
92
|
+
return (
|
93
|
+
(
|
94
|
+
(
|
95
|
+
alt.Chart(
|
96
|
+
eddington_df,
|
97
|
+
height=500,
|
98
|
+
width=800,
|
99
|
+
title=f"Eddington Number {en}",
|
100
|
+
)
|
101
|
+
.mark_area(interpolate="step")
|
102
|
+
.encode(
|
103
|
+
alt.X(
|
104
|
+
"distance_km",
|
105
|
+
scale=alt.Scale(domainMin=0),
|
106
|
+
title="Distance / km",
|
107
|
+
),
|
108
|
+
alt.Y(
|
109
|
+
"total",
|
110
|
+
scale=alt.Scale(domainMax=en + 10),
|
111
|
+
title=f"{interval} exceeding distance",
|
112
|
+
),
|
113
|
+
[
|
114
|
+
alt.Tooltip("distance_km", title="Distance / km"),
|
115
|
+
alt.Tooltip("total", title=f"{interval} exceeding distance"),
|
116
|
+
alt.Tooltip("missing", title=f"{interval} missing for next"),
|
117
|
+
],
|
118
|
+
)
|
119
|
+
)
|
120
|
+
+ (
|
121
|
+
alt.Chart(pd.DataFrame({"distance_km": x, "total": x}))
|
122
|
+
.mark_line(color="red")
|
123
|
+
.encode(alt.X("distance_km"), alt.Y("total"))
|
124
|
+
)
|
125
|
+
)
|
126
|
+
.interactive(bind_x=False)
|
127
|
+
.to_json(format="vega")
|
128
|
+
)
|
129
|
+
|
130
|
+
|
131
|
+
def _get_eddington_number(distances: pd.Series) -> int:
|
132
|
+
if len(distances) == 1:
|
133
|
+
if distances.iloc[0] >= 1:
|
134
|
+
return 1
|
135
|
+
else:
|
136
|
+
0
|
137
|
+
|
138
|
+
sorted_distances = sorted(distances, reverse=True)
|
139
|
+
for en, distance in enumerate(sorted_distances, 1):
|
140
|
+
if distance < en:
|
141
|
+
return en - 1
|
142
|
+
|
143
|
+
|
144
|
+
def _get_yearly_eddington(meta: pd.DataFrame) -> dict[int, int]:
|
145
|
+
meta = meta.dropna(subset=["start", "distance_km"]).copy()
|
146
|
+
meta["year"] = [start.year for start in meta["start"]]
|
147
|
+
meta["date"] = [start.date() for start in meta["start"]]
|
148
|
+
|
149
|
+
yearly_eddington = meta.groupby("year").apply(
|
150
|
+
lambda group: _get_eddington_number(
|
151
|
+
group.groupby("date").apply(
|
152
|
+
lambda group2: int(group2["distance_km"].sum()), include_groups=False
|
153
|
+
)
|
154
|
+
),
|
155
|
+
include_groups=False,
|
156
|
+
)
|
157
|
+
return yearly_eddington.to_dict()
|
158
|
+
|
159
|
+
|
160
|
+
def _get_eddington_number_history(meta: pd.DataFrame) -> dict:
|
161
|
+
|
162
|
+
daily_distances = meta.groupby("date").apply(
|
163
|
+
lambda group2: int(group2["distance_km"].sum()), include_groups=False
|
164
|
+
)
|
165
|
+
|
166
|
+
eddington_number_history = {"date": [], "eddington_number": []}
|
167
|
+
top_days = []
|
168
|
+
for date, distance in daily_distances.items():
|
169
|
+
if len(top_days) == 0:
|
170
|
+
top_days.append(distance)
|
171
|
+
else:
|
172
|
+
if distance >= top_days[0]:
|
173
|
+
top_days.append(distance)
|
174
|
+
top_days.sort()
|
175
|
+
while top_days[0] < len(top_days):
|
176
|
+
top_days.pop(0)
|
177
|
+
eddington_number_history["date"].append(
|
178
|
+
datetime.datetime.combine(date, datetime.datetime.min.time())
|
179
|
+
)
|
180
|
+
eddington_number_history["eddington_number"].append(len(top_days))
|
181
|
+
history = pd.DataFrame(eddington_number_history)
|
182
|
+
|
183
|
+
return (
|
184
|
+
alt.Chart(history)
|
185
|
+
.mark_line(interpolate="step-after")
|
186
|
+
.encode(
|
187
|
+
alt.X("date", title="Date"),
|
188
|
+
alt.Y("eddington_number", title="Eddington number"),
|
189
|
+
)
|
190
|
+
).to_json(format="vega")
|
@@ -6,6 +6,7 @@ from flask import render_template
|
|
6
6
|
from geo_activity_playground.core.activities import ActivityRepository
|
7
7
|
from geo_activity_playground.core.config import Config
|
8
8
|
from geo_activity_playground.core.summary_stats import get_equipment_use_table
|
9
|
+
from geo_activity_playground.webui.plot_util import make_kind_scale
|
9
10
|
|
10
11
|
|
11
12
|
def make_equipment_blueprint(
|
@@ -20,7 +21,7 @@ def make_equipment_blueprint(
|
|
20
21
|
)
|
21
22
|
|
22
23
|
equipment_variables = {}
|
23
|
-
for equipment in
|
24
|
+
for equipment in equipment_summary["equipment"]:
|
24
25
|
selection = repository.meta.loc[repository.meta["equipment"] == equipment]
|
25
26
|
total_distances = pd.DataFrame(
|
26
27
|
{
|
@@ -55,6 +56,11 @@ def make_equipment_blueprint(
|
|
55
56
|
.encode(
|
56
57
|
alt.X("year(start):O", title="Year"),
|
57
58
|
alt.Y("sum(distance_km)", title="Distance / km"),
|
59
|
+
alt.Color(
|
60
|
+
"kind",
|
61
|
+
scale=make_kind_scale(repository.meta, config),
|
62
|
+
title="Kind",
|
63
|
+
),
|
58
64
|
)
|
59
65
|
.to_json(format="vega")
|
60
66
|
)
|
@@ -67,7 +73,10 @@ def make_equipment_blueprint(
|
|
67
73
|
)
|
68
74
|
.mark_bar()
|
69
75
|
.encode(
|
70
|
-
alt.X(
|
76
|
+
alt.X(
|
77
|
+
"kind",
|
78
|
+
title="Kind",
|
79
|
+
),
|
71
80
|
alt.Y("sum(distance_km)", title="Distance / km"),
|
72
81
|
)
|
73
82
|
.to_json(format="vega")
|
@@ -84,7 +93,7 @@ def make_equipment_blueprint(
|
|
84
93
|
|
85
94
|
variables = {
|
86
95
|
"equipment_variables": equipment_variables,
|
87
|
-
"equipment_summary": equipment_summary,
|
96
|
+
"equipment_summary": equipment_summary.to_dict(orient="records"),
|
88
97
|
}
|
89
98
|
|
90
99
|
return render_template("equipment/index.html.j2", **variables)
|