geo-activity-playground 0.27.0__tar.gz → 0.28.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.27.0 → geo_activity_playground-0.28.0}/PKG-INFO +1 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/__main__.py +1 -2
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/activities.py +3 -3
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/config.py +2 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/tasks.py +2 -2
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/explorer/tile_visits.py +135 -134
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/directory.py +1 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/activity/controller.py +29 -13
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/activity/templates/activity/show.html.j2 +6 -11
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/app.py +10 -2
- geo_activity_playground-0.28.0/geo_activity_playground/webui/auth/blueprint.py +27 -0
- geo_activity_playground-0.28.0/geo_activity_playground/webui/auth/templates/auth/index.html.j2 +21 -0
- geo_activity_playground-0.28.0/geo_activity_playground/webui/authenticator.py +49 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/equipment/controller.py +2 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/explorer/controller.py +3 -3
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/heatmap/heatmap_controller.py +19 -6
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/blueprint.py +34 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/controller.py +43 -0
- geo_activity_playground-0.28.0/geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2 +19 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/templates/settings/index.html.j2 +18 -0
- geo_activity_playground-0.28.0/geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2 +22 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/square_planner/controller.py +1 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/templates/home.html.j2 +1 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/templates/page.html.j2 +1 -1
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/upload/blueprint.py +7 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/upload/controller.py +5 -9
- geo_activity_playground-0.28.0/geo_activity_playground/webui/upload/templates/upload/index.html.j2 +21 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/pyproject.toml +1 -1
- geo_activity_playground-0.27.0/geo_activity_playground/webui/upload/templates/upload/index.html.j2 +0 -37
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/LICENSE +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/coordinates.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/enrichment.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/heart_rate.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/heatmap.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/paths.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/privacy_zones.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/similarity.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/test_tiles.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/test_time_conversion.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/tiles.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/core/time_conversion.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/explorer/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/explorer/grid_file.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/explorer/video.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/activity_parsers.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/csv_parser.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/strava_api.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/strava_checkout.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/test_csv_parser.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/test_directory.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/importers/test_strava_api.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/activity/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/activity/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/activity/templates/activity/name.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/calendar/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/calendar/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/calendar/controller.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/eddington/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/eddington/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/eddington/controller.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/eddington/templates/eddington/index.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/entry_controller.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/equipment/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/equipment/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/equipment/templates/equipment/index.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/explorer/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/explorer/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/heatmap/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/heatmap/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/search_controller.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/settings/templates/settings/strava.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/square_planner/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/square_planner/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/android-chrome-384x384.png +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/favicon.ico +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/safari-pinned-tab.svg +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/static/site.webmanifest +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/summary/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/summary/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/summary/controller.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/summary/templates/summary/index.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/templates/search.html.j2 +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/tile/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/tile/blueprint.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/tile/controller.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/upload/__init__.py +0 -0
- {geo_activity_playground-0.27.0 → geo_activity_playground-0.28.0}/geo_activity_playground/webui/upload/templates/upload/reload.html.j2 +0 -0
@@ -109,10 +109,9 @@ def make_activity_repository(
|
|
109
109
|
|
110
110
|
|
111
111
|
def main_cache(basedir: pathlib.Path) -> None:
|
112
|
-
repository, tile_visit_accessor, config_accessor = make_activity_repository(
|
112
|
+
(repository, tile_visit_accessor, config_accessor) = make_activity_repository(
|
113
113
|
basedir, False
|
114
114
|
)
|
115
|
-
scan_for_activities(repository, tile_visit_accessor, config_accessor())
|
116
115
|
|
117
116
|
|
118
117
|
if __name__ == "__main__":
|
@@ -128,11 +128,11 @@ class ActivityRepository:
|
|
128
128
|
else:
|
129
129
|
return None
|
130
130
|
|
131
|
-
def get_activity_ids(self, only_achievements: bool = False) ->
|
131
|
+
def get_activity_ids(self, only_achievements: bool = False) -> list[int]:
|
132
132
|
if only_achievements:
|
133
|
-
return
|
133
|
+
return list(self.meta.loc[self.meta["consider_for_achievements"]].index)
|
134
134
|
else:
|
135
|
-
return
|
135
|
+
return list(self.meta.index)
|
136
136
|
|
137
137
|
def iter_activities(self, new_to_old=True, dropna=False) -> Iterator[ActivityMeta]:
|
138
138
|
direction = -1 if new_to_old else 1
|
@@ -29,10 +29,11 @@ class Config:
|
|
29
29
|
heart_rate_maximum: Optional[int] = None
|
30
30
|
kinds_without_achievements: list[str] = dataclasses.field(default_factory=list)
|
31
31
|
metadata_extraction_regexes: list[str] = dataclasses.field(default_factory=list)
|
32
|
-
num_processes: Optional[int] =
|
32
|
+
num_processes: Optional[int] = 1
|
33
33
|
privacy_zones: dict[str, list[list[float]]] = dataclasses.field(
|
34
34
|
default_factory=dict
|
35
35
|
)
|
36
|
+
sharepic_suppressed_fields: list[str] = dataclasses.field(default_factory=list)
|
36
37
|
strava_client_id: int = 131693
|
37
38
|
strava_client_secret: str = "0ccc0100a2c218512a7ef0cea3b0e322fb4b4365"
|
38
39
|
strava_client_code: Optional[str] = None
|
@@ -59,8 +59,8 @@ class WorkTracker:
|
|
59
59
|
else:
|
60
60
|
self._done = set()
|
61
61
|
|
62
|
-
def filter(self, ids: Iterable) ->
|
63
|
-
return
|
62
|
+
def filter(self, ids: Iterable) -> list:
|
63
|
+
return [elem for elem in ids if elem not in self._done]
|
64
64
|
|
65
65
|
def mark_done(self, id: int) -> None:
|
66
66
|
self._done.add(id)
|
@@ -25,139 +25,159 @@ from geo_activity_playground.core.tiles import interpolate_missing_tile
|
|
25
25
|
logger = logging.getLogger(__name__)
|
26
26
|
|
27
27
|
|
28
|
+
class TileInfo(TypedDict):
|
29
|
+
activity_ids: set[int]
|
30
|
+
first_time: datetime.datetime
|
31
|
+
first_id: int
|
32
|
+
last_time: datetime.datetime
|
33
|
+
last_id: int
|
34
|
+
|
35
|
+
|
28
36
|
class TileHistoryRow(TypedDict):
|
37
|
+
activity_id: int
|
29
38
|
time: datetime.datetime
|
30
39
|
tile_x: int
|
31
40
|
tile_y: int
|
32
41
|
|
33
42
|
|
43
|
+
class TileEvolutionState:
|
44
|
+
def __init__(self) -> None:
|
45
|
+
self.num_neighbors: dict[tuple[int, int], int] = {}
|
46
|
+
self.memberships: dict[tuple[int, int], tuple[int, int]] = {}
|
47
|
+
self.clusters: dict[tuple[int, int], list[tuple[int, int]]] = {}
|
48
|
+
self.cluster_evolution = pd.DataFrame()
|
49
|
+
self.square_start = 0
|
50
|
+
self.cluster_start = 0
|
51
|
+
self.max_square_size = 0
|
52
|
+
self.visited_tiles: set[tuple[int, int]] = set()
|
53
|
+
self.square_evolution = pd.DataFrame()
|
54
|
+
self.square_x: Optional[int] = None
|
55
|
+
self.square_y: Optional[int] = None
|
56
|
+
|
57
|
+
|
58
|
+
class TileState(TypedDict):
|
59
|
+
tile_visits: dict[int, dict[tuple[int, int], TileInfo]]
|
60
|
+
tile_history: dict[int, pd.DataFrame]
|
61
|
+
activities_per_tile: dict[int, set[int]]
|
62
|
+
processed_activities: set[int]
|
63
|
+
evolution_state: dict[int, TileEvolutionState]
|
64
|
+
version: int
|
65
|
+
|
66
|
+
|
67
|
+
TILE_STATE_VERSION = 2
|
68
|
+
|
69
|
+
|
34
70
|
class TileVisitAccessor:
|
35
|
-
|
36
|
-
TILE_HISTORIES_PATH = pathlib.Path(f"Cache/tile-history.pickle")
|
37
|
-
TILE_VISITS_PATH = pathlib.Path(f"Cache/tile-visits.pickle")
|
38
|
-
ACTIVITIES_PER_TILE_PATH = pathlib.Path(f"Cache/activities-per-tile.pickle")
|
71
|
+
PATH = pathlib.Path("Cache/tile-state-2.pickle")
|
39
72
|
|
40
73
|
def __init__(self) -> None:
|
41
|
-
self.
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
) or collections.defaultdict(pd.DataFrame)
|
49
|
-
|
50
|
-
self.states = try_load_pickle(
|
51
|
-
self.TILE_EVOLUTION_STATES_PATH
|
52
|
-
) or collections.defaultdict(TileEvolutionState)
|
53
|
-
|
54
|
-
self.activities_per_tile: dict[
|
55
|
-
int, dict[tuple[int, int], set[int]]
|
56
|
-
] = try_load_pickle(self.ACTIVITIES_PER_TILE_PATH) or collections.defaultdict(
|
57
|
-
dict
|
58
|
-
)
|
74
|
+
self.tile_state: TileState = try_load_pickle(self.PATH)
|
75
|
+
if (
|
76
|
+
self.tile_state is None
|
77
|
+
or self.tile_state.get("version", None) != TILE_STATE_VERSION
|
78
|
+
):
|
79
|
+
self.tile_state = make_tile_state()
|
80
|
+
# TODO: Reset work tracker
|
59
81
|
|
60
82
|
def save(self) -> None:
|
61
|
-
|
62
|
-
|
83
|
+
tmp_path = self.PATH.with_suffix(".tmp")
|
84
|
+
with open(tmp_path, "wb") as f:
|
85
|
+
pickle.dump(self.tile_state, f)
|
86
|
+
tmp_path.rename(self.PATH)
|
63
87
|
|
64
|
-
with open(self.TILE_HISTORIES_PATH, "wb") as f:
|
65
|
-
pickle.dump(self.histories, f)
|
66
88
|
|
67
|
-
|
68
|
-
|
89
|
+
def make_defaultdict_dict():
|
90
|
+
return collections.defaultdict(dict)
|
69
91
|
|
70
|
-
with open(self.ACTIVITIES_PER_TILE_PATH, "wb") as f:
|
71
|
-
pickle.dump(self.activities_per_tile, f)
|
72
92
|
|
93
|
+
def make_defaultdict_set():
|
94
|
+
return collections.defaultdict(set)
|
95
|
+
|
96
|
+
|
97
|
+
def make_tile_state() -> TileState:
|
98
|
+
tile_state: TileState = {
|
99
|
+
"tile_visits": collections.defaultdict(make_defaultdict_dict),
|
100
|
+
"tile_history": collections.defaultdict(pd.DataFrame),
|
101
|
+
"activities_per_tile": collections.defaultdict(make_defaultdict_set),
|
102
|
+
"processed_activities": set(),
|
103
|
+
"evolution_state": collections.defaultdict(TileEvolutionState),
|
104
|
+
"version": TILE_STATE_VERSION,
|
105
|
+
}
|
106
|
+
return tile_state
|
73
107
|
|
74
|
-
def compute_tile_visits(
|
75
|
-
repository: ActivityRepository, tile_visits_accessor: TileVisitAccessor
|
76
|
-
) -> None:
|
77
|
-
present_activity_ids = repository.get_activity_ids()
|
78
|
-
work_tracker = WorkTracker(work_tracker_path("tile-visits"))
|
79
|
-
|
80
|
-
changed_zoom_tile = collections.defaultdict(set)
|
81
|
-
|
82
|
-
# Delete visits from removed activities.
|
83
|
-
for zoom, activities_per_tile in tile_visits_accessor.activities_per_tile.items():
|
84
|
-
for tile, activity_ids in activities_per_tile.items():
|
85
|
-
deleted_ids = activity_ids - present_activity_ids
|
86
|
-
if deleted_ids:
|
87
|
-
logger.debug(
|
88
|
-
f"Removing activities {deleted_ids} from tile {tile} at {zoom=}."
|
89
|
-
)
|
90
|
-
for activity_id in deleted_ids:
|
91
|
-
activity_ids.remove(activity_id)
|
92
|
-
work_tracker.discard(activity_id)
|
93
|
-
changed_zoom_tile[zoom].add(tile)
|
94
108
|
|
95
|
-
|
96
|
-
|
109
|
+
def compute_tile_visits_new(
|
110
|
+
repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
|
111
|
+
) -> None:
|
112
|
+
work_tracker = WorkTracker(work_tracker_path("tile-state"))
|
97
113
|
for activity_id in tqdm(
|
98
|
-
|
114
|
+
work_tracker.filter(repository.get_activity_ids()), desc="Tile visits (new)"
|
99
115
|
):
|
100
|
-
|
101
|
-
for time, tile_x, tile_y in _tiles_from_points(
|
102
|
-
repository.get_time_series(activity_id), zoom
|
103
|
-
):
|
104
|
-
tile = (tile_x, tile_y)
|
105
|
-
if tile not in tile_visits_accessor.activities_per_tile[zoom]:
|
106
|
-
tile_visits_accessor.activities_per_tile[zoom][tile] = set()
|
107
|
-
tile_visits_accessor.activities_per_tile[zoom][tile].add(activity_id)
|
108
|
-
changed_zoom_tile[zoom].add(tile)
|
116
|
+
do_tile_stuff(repository, tile_visit_accessor.tile_state, activity_id)
|
109
117
|
work_tracker.mark_done(activity_id)
|
110
|
-
|
111
|
-
# Update tile visits structure.
|
112
|
-
for zoom, changed_tiles in tqdm(
|
113
|
-
changed_zoom_tile.items(), desc="Incorporate changes in tiles"
|
114
|
-
):
|
115
|
-
soa = {"activity_id": [], "time": [], "tile_x": [], "tile_y": []}
|
116
|
-
|
117
|
-
for tile in changed_tiles:
|
118
|
-
activity_ids = tile_visits_accessor.activities_per_tile[zoom][tile]
|
119
|
-
activities = [
|
120
|
-
repository.get_activity_by_id(activity_id)
|
121
|
-
for activity_id in activity_ids
|
122
|
-
]
|
123
|
-
activities_to_consider = [
|
124
|
-
activity
|
125
|
-
for activity in activities
|
126
|
-
if activity["consider_for_achievements"]
|
127
|
-
]
|
128
|
-
activities_to_consider.sort(key=lambda activity: activity["start"])
|
129
|
-
|
130
|
-
if activities_to_consider:
|
131
|
-
tile_visits_accessor.visits[zoom][tile] = {
|
132
|
-
"first_time": activities_to_consider[0]["start"],
|
133
|
-
"first_id": activities_to_consider[0]["id"],
|
134
|
-
"last_time": activities_to_consider[-1]["start"],
|
135
|
-
"last_id": activities_to_consider[-1]["id"],
|
136
|
-
"activity_ids": {
|
137
|
-
activity["id"] for activity in activities_to_consider
|
138
|
-
},
|
139
|
-
}
|
140
|
-
|
141
|
-
soa["activity_id"].append(activities_to_consider[0]["id"])
|
142
|
-
soa["time"].append(activities_to_consider[0]["start"])
|
143
|
-
soa["tile_x"].append(tile[0])
|
144
|
-
soa["tile_y"].append(tile[1])
|
145
|
-
else:
|
146
|
-
if tile in tile_visits_accessor.visits[zoom]:
|
147
|
-
del tile_visits_accessor.visits[zoom][tile]
|
148
|
-
|
149
|
-
df = pd.DataFrame(soa)
|
150
|
-
if len(df) > 0:
|
151
|
-
df = pd.concat([tile_visits_accessor.histories[zoom], df])
|
152
|
-
df.sort_values("time", inplace=True)
|
153
|
-
tile_visits_accessor.histories[zoom] = df.groupby(
|
154
|
-
["tile_x", "tile_y"]
|
155
|
-
).head(1)
|
156
|
-
|
157
|
-
tile_visits_accessor.save()
|
118
|
+
tile_visit_accessor.save()
|
158
119
|
work_tracker.close()
|
159
120
|
|
160
121
|
|
122
|
+
def do_tile_stuff(
|
123
|
+
repository: ActivityRepository, tile_state: TileState, activity_id: int
|
124
|
+
) -> None:
|
125
|
+
activity = repository.get_activity_by_id(activity_id)
|
126
|
+
time_series = repository.get_time_series(activity_id)
|
127
|
+
|
128
|
+
activity_tiles = pd.DataFrame(
|
129
|
+
_tiles_from_points(time_series, 19), columns=["time", "tile_x", "tile_y"]
|
130
|
+
)
|
131
|
+
for zoom in reversed(range(20)):
|
132
|
+
activities_per_tile = tile_state["activities_per_tile"][zoom]
|
133
|
+
|
134
|
+
new_tile_history_soa = {
|
135
|
+
"activity_id": [],
|
136
|
+
"time": [],
|
137
|
+
"tile_x": [],
|
138
|
+
"tile_y": [],
|
139
|
+
}
|
140
|
+
|
141
|
+
activity_tiles = activity_tiles.groupby(["tile_x", "tile_y"]).head(1)
|
142
|
+
|
143
|
+
for time, tile in zip(
|
144
|
+
activity_tiles["time"],
|
145
|
+
zip(activity_tiles["tile_x"], activity_tiles["tile_y"]),
|
146
|
+
):
|
147
|
+
if activity["consider_for_achievements"]:
|
148
|
+
if tile not in activities_per_tile:
|
149
|
+
new_tile_history_soa["activity_id"].append(activity_id)
|
150
|
+
new_tile_history_soa["time"].append(time)
|
151
|
+
new_tile_history_soa["tile_x"].append(tile[0])
|
152
|
+
new_tile_history_soa["tile_y"].append(tile[1])
|
153
|
+
|
154
|
+
tile_visit = tile_state["tile_visits"][zoom][tile]
|
155
|
+
if not tile_visit:
|
156
|
+
tile_visit["activity_ids"] = {activity_id}
|
157
|
+
else:
|
158
|
+
tile_visit["activity_ids"].add(activity_id)
|
159
|
+
|
160
|
+
first_time = tile_visit.get("first_time", None)
|
161
|
+
last_time = tile_visit.get("last_time", None)
|
162
|
+
if first_time is None or time < first_time:
|
163
|
+
tile_visit["first_id"] = activity_id
|
164
|
+
tile_visit["first_time"] = time
|
165
|
+
if last_time is None or time > last_time:
|
166
|
+
tile_visit["last_id"] = activity_id
|
167
|
+
tile_visit["last_time"] = time
|
168
|
+
|
169
|
+
activities_per_tile[tile].add(activity_id)
|
170
|
+
|
171
|
+
if new_tile_history_soa["activity_id"]:
|
172
|
+
tile_state["tile_history"][zoom] = pd.concat(
|
173
|
+
[tile_state["tile_history"][zoom], pd.DataFrame(new_tile_history_soa)]
|
174
|
+
)
|
175
|
+
|
176
|
+
# Move up one layer in the quad-tree.
|
177
|
+
activity_tiles["tile_x"] //= 2
|
178
|
+
activity_tiles["tile_y"] //= 2
|
179
|
+
|
180
|
+
|
161
181
|
def _tiles_from_points(
|
162
182
|
time_series: pd.DataFrame, zoom: int
|
163
183
|
) -> Iterator[tuple[datetime.datetime, int, int]]:
|
@@ -181,38 +201,19 @@ def _tiles_from_points(
|
|
181
201
|
yield (t1,) + interpolated
|
182
202
|
|
183
203
|
|
184
|
-
|
185
|
-
def __init__(self) -> None:
|
186
|
-
self.num_neighbors: dict[tuple[int, int], int] = {}
|
187
|
-
self.memberships: dict[tuple[int, int], tuple[int, int]] = {}
|
188
|
-
self.clusters: dict[tuple[int, int], list[tuple[int, int]]] = {}
|
189
|
-
self.cluster_evolution = pd.DataFrame()
|
190
|
-
self.square_start = 0
|
191
|
-
self.cluster_start = 0
|
192
|
-
self.max_square_size = 0
|
193
|
-
self.visited_tiles: set[tuple[int, int]] = set()
|
194
|
-
self.square_evolution = pd.DataFrame()
|
195
|
-
self.square_x: Optional[int] = None
|
196
|
-
self.square_y: Optional[int] = None
|
197
|
-
|
198
|
-
|
199
|
-
def compute_tile_evolution(
|
200
|
-
tile_visits_accessor: TileVisitAccessor, config: Config
|
201
|
-
) -> None:
|
204
|
+
def compute_tile_evolution(tile_state: TileState, config: Config) -> None:
|
202
205
|
for zoom in config.explorer_zoom_levels:
|
203
206
|
_compute_cluster_evolution(
|
204
|
-
|
205
|
-
|
207
|
+
tile_state["tile_history"][zoom],
|
208
|
+
tile_state["evolution_state"][zoom],
|
206
209
|
zoom,
|
207
210
|
)
|
208
211
|
_compute_square_history(
|
209
|
-
|
210
|
-
|
212
|
+
tile_state["tile_history"][zoom],
|
213
|
+
tile_state["evolution_state"][zoom],
|
211
214
|
zoom,
|
212
215
|
)
|
213
216
|
|
214
|
-
tile_visits_accessor.save()
|
215
|
-
|
216
217
|
|
217
218
|
def _compute_cluster_evolution(
|
218
219
|
tiles: pd.DataFrame, s: TileEvolutionState, zoom: int
|
@@ -61,7 +61,7 @@ def import_from_directory(
|
|
61
61
|
|
62
62
|
if num_processes == 1:
|
63
63
|
paths_with_errors = []
|
64
|
-
for path in tqdm(new_activity_paths, desc="Parse activity metadata"):
|
64
|
+
for path in tqdm(new_activity_paths, desc="Parse activity metadata (serially)"):
|
65
65
|
errors = _cache_single_file(path)
|
66
66
|
if errors:
|
67
67
|
paths_with_errors.append(errors)
|
@@ -61,7 +61,9 @@ class ActivityController:
|
|
61
61
|
|
62
62
|
new_tiles = {
|
63
63
|
zoom: sum(
|
64
|
-
self._tile_visit_accessor.
|
64
|
+
self._tile_visit_accessor.tile_state["tile_history"][zoom][
|
65
|
+
"activity_id"
|
66
|
+
]
|
65
67
|
== activity["id"]
|
66
68
|
)
|
67
69
|
for zoom in [14, 17]
|
@@ -100,7 +102,9 @@ class ActivityController:
|
|
100
102
|
time_series = privacy_zone.filter_time_series(time_series)
|
101
103
|
if len(time_series) == 0:
|
102
104
|
time_series = self._repository.get_time_series(id)
|
103
|
-
return make_sharepic(
|
105
|
+
return make_sharepic(
|
106
|
+
activity, time_series, self._config.sharepic_suppressed_fields
|
107
|
+
)
|
104
108
|
|
105
109
|
def render_day(self, year: int, month: int, day: int) -> dict:
|
106
110
|
meta = self._repository.meta
|
@@ -411,7 +415,11 @@ def pixels_in_bounds(bounds: PixelBounds) -> int:
|
|
411
415
|
return (bounds.x_max - bounds.x_min) * (bounds.y_max - bounds.y_min)
|
412
416
|
|
413
417
|
|
414
|
-
def make_sharepic(
|
418
|
+
def make_sharepic(
|
419
|
+
activity: ActivityMeta,
|
420
|
+
time_series: pd.DataFrame,
|
421
|
+
sharepic_suppressed_fields: list[str],
|
422
|
+
) -> bytes:
|
415
423
|
lat_lon_data = np.array([time_series["latitude"], time_series["longitude"]]).T
|
416
424
|
|
417
425
|
geo_bounds = get_bounds(lat_lon_data)
|
@@ -448,19 +456,27 @@ def make_sharepic(activity: ActivityMeta, time_series: pd.DataFrame) -> bytes:
|
|
448
456
|
draw.line(yx, fill="red", width=4)
|
449
457
|
|
450
458
|
draw.rectangle([0, img.height - 70, img.width, img.height], fill=(0, 0, 0, 128))
|
451
|
-
|
452
|
-
|
453
|
-
f"{activity['
|
454
|
-
f"{activity['
|
455
|
-
f"
|
456
|
-
|
457
|
-
|
459
|
+
|
460
|
+
facts = {
|
461
|
+
"kind": f"{activity['kind']}",
|
462
|
+
"start": f"{activity['start'].date()}",
|
463
|
+
"equipment": f"{activity['equipment']}",
|
464
|
+
"distance_km": f"\n{activity['distance_km']:.1f} km",
|
465
|
+
"elapsed_time": re.sub(r"^0 days ", "", f"{activity['elapsed_time']}"),
|
466
|
+
}
|
467
|
+
|
458
468
|
if activity.get("calories", 0) and not pd.isna(activity["calories"]):
|
459
|
-
facts
|
469
|
+
facts["calories"] = f"{activity['calories']:.0f} kcal"
|
460
470
|
if activity.get("steps", 0) and not pd.isna(activity["steps"]):
|
461
|
-
facts
|
471
|
+
facts["steps"] = f"{activity['steps']:.0f} steps"
|
472
|
+
|
473
|
+
facts = {
|
474
|
+
key: value
|
475
|
+
for key, value in facts.items()
|
476
|
+
if not key in sharepic_suppressed_fields
|
477
|
+
}
|
462
478
|
|
463
|
-
draw.text((35, img.height - 70 + 10), " ".join(facts), font_size=20)
|
479
|
+
draw.text((35, img.height - 70 + 10), " ".join(facts.values()), font_size=20)
|
464
480
|
|
465
481
|
# img_array = np.array(img) / 255
|
466
482
|
|
@@ -111,11 +111,7 @@
|
|
111
111
|
</div>
|
112
112
|
|
113
113
|
{% if heartrate_time_plot is defined %}
|
114
|
-
<
|
115
|
-
<div class="col">
|
116
|
-
<h2>Heart rate</h2>
|
117
|
-
</div>
|
118
|
-
</div>
|
114
|
+
<h2>Heart rate</h2>
|
119
115
|
|
120
116
|
<div class="row mb-3">
|
121
117
|
<div class="col-md-4">
|
@@ -133,13 +129,12 @@
|
|
133
129
|
</div>
|
134
130
|
{% endif %}
|
135
131
|
|
136
|
-
<
|
137
|
-
<div class="col">
|
138
|
-
<h2>Share picture</h2>
|
132
|
+
<h2>Share picture</h2>
|
139
133
|
|
140
|
-
|
141
|
-
|
142
|
-
|
134
|
+
<p><img src="{{ url_for('.sharepic', id=activity.id) }}" /></p>
|
135
|
+
|
136
|
+
<p>Not happy with the displayed data? <a href="{{ url_for('settings.sharepic') }}">Change share picture
|
137
|
+
settings</a>.</p>
|
143
138
|
|
144
139
|
{% if similar_activites|length > 0 %}
|
145
140
|
<div class="row mb-3">
|
@@ -22,6 +22,8 @@ from .summary.blueprint import make_summary_blueprint
|
|
22
22
|
from .tile.blueprint import make_tile_blueprint
|
23
23
|
from .upload.blueprint import make_upload_blueprint
|
24
24
|
from geo_activity_playground.core.config import ConfigAccessor
|
25
|
+
from geo_activity_playground.webui.auth.blueprint import make_auth_blueprint
|
26
|
+
from geo_activity_playground.webui.authenticator import Authenticator
|
25
27
|
from geo_activity_playground.webui.settings.blueprint import make_settings_blueprint
|
26
28
|
|
27
29
|
|
@@ -71,9 +73,13 @@ def web_ui_main(
|
|
71
73
|
app.config["UPLOAD_FOLDER"] = "Activities"
|
72
74
|
app.secret_key = get_secret_key()
|
73
75
|
|
76
|
+
authenticator = Authenticator(config_accessor())
|
77
|
+
|
74
78
|
route_search(app, repository)
|
75
79
|
route_start(app, repository)
|
76
80
|
|
81
|
+
app.register_blueprint(make_auth_blueprint(authenticator), url_prefix="/auth")
|
82
|
+
|
77
83
|
app.register_blueprint(
|
78
84
|
make_activity_blueprint(
|
79
85
|
repository,
|
@@ -97,7 +103,7 @@ def web_ui_main(
|
|
97
103
|
make_heatmap_blueprint(repository, tile_visit_accessor), url_prefix="/heatmap"
|
98
104
|
)
|
99
105
|
app.register_blueprint(
|
100
|
-
make_settings_blueprint(config_accessor),
|
106
|
+
make_settings_blueprint(config_accessor, authenticator),
|
101
107
|
url_prefix="/settings",
|
102
108
|
)
|
103
109
|
app.register_blueprint(
|
@@ -110,7 +116,9 @@ def web_ui_main(
|
|
110
116
|
)
|
111
117
|
app.register_blueprint(make_tile_blueprint(), url_prefix="/tile")
|
112
118
|
app.register_blueprint(
|
113
|
-
make_upload_blueprint(
|
119
|
+
make_upload_blueprint(
|
120
|
+
repository, tile_visit_accessor, config_accessor(), authenticator
|
121
|
+
),
|
114
122
|
url_prefix="/upload",
|
115
123
|
)
|
116
124
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from flask import Blueprint
|
2
|
+
from flask import redirect
|
3
|
+
from flask import render_template
|
4
|
+
from flask import request
|
5
|
+
from flask import url_for
|
6
|
+
|
7
|
+
from geo_activity_playground.webui.authenticator import Authenticator
|
8
|
+
|
9
|
+
|
10
|
+
def make_auth_blueprint(authenticator: Authenticator) -> Blueprint:
|
11
|
+
blueprint = Blueprint("auth", __name__, template_folder="templates")
|
12
|
+
|
13
|
+
@blueprint.route("/", methods=["GET", "POST"])
|
14
|
+
def index():
|
15
|
+
if request.method == "POST":
|
16
|
+
authenticator.authenticate(request.form["password"])
|
17
|
+
return render_template(
|
18
|
+
"auth/index.html.j2",
|
19
|
+
is_authenticated=authenticator.is_authenticated(),
|
20
|
+
)
|
21
|
+
|
22
|
+
@blueprint.route("/logout")
|
23
|
+
def logout():
|
24
|
+
authenticator.logout()
|
25
|
+
return redirect(url_for(".index"))
|
26
|
+
|
27
|
+
return blueprint
|
geo_activity_playground-0.28.0/geo_activity_playground/webui/auth/templates/auth/index.html.j2
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
{% extends "page.html.j2" %}
|
2
|
+
|
3
|
+
{% block container %}
|
4
|
+
<h1>Authentication</h1>
|
5
|
+
|
6
|
+
{% if is_authenticated %}
|
7
|
+
<p>You are either logged in or don't have a password set. You can do everything.</p>
|
8
|
+
|
9
|
+
<a class="btn btn-primary" href="{{ url_for('.logout') }}">Log Out</a>
|
10
|
+
{% else %}
|
11
|
+
<form method="POST">
|
12
|
+
<div class="mb-3">
|
13
|
+
<label for="password" class="form-label">Password</label>
|
14
|
+
<input type="password" class="form-control" id="password" name="password" />
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<button type="submit" class="btn btn-primary">Log In</button>
|
18
|
+
</form>
|
19
|
+
{% endif %}
|
20
|
+
|
21
|
+
{% endblock %}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import functools
|
2
|
+
from typing import Callable
|
3
|
+
|
4
|
+
from flask import flash
|
5
|
+
from flask import redirect
|
6
|
+
from flask import session
|
7
|
+
from flask import url_for
|
8
|
+
|
9
|
+
from geo_activity_playground.core.config import Config
|
10
|
+
|
11
|
+
|
12
|
+
class Authenticator:
|
13
|
+
def __init__(self, config: Config) -> None:
|
14
|
+
self._config = config
|
15
|
+
|
16
|
+
def is_authenticated(self) -> bool:
|
17
|
+
print(
|
18
|
+
f"Password={self._config.upload_password}, Session={session.get('is_authenticated', False)}"
|
19
|
+
)
|
20
|
+
return not self._config.upload_password or session.get(
|
21
|
+
"is_authenticated", False
|
22
|
+
)
|
23
|
+
|
24
|
+
def authenticate(self, password: str) -> None:
|
25
|
+
if password == self._config.upload_password:
|
26
|
+
session["is_authenticated"] = True
|
27
|
+
session.permanent = True
|
28
|
+
flash("Login successful.", category="success")
|
29
|
+
else:
|
30
|
+
flash("Incorrect password.", category="warning")
|
31
|
+
|
32
|
+
def logout(self) -> None:
|
33
|
+
session["is_authenticated"] = False
|
34
|
+
flash("Logout successful.", category="success")
|
35
|
+
|
36
|
+
|
37
|
+
def needs_authentication(authenticator: Authenticator) -> Callable:
|
38
|
+
def decorator(route: Callable) -> Callable:
|
39
|
+
@functools.wraps(route)
|
40
|
+
def wrapped_route(*args, **kwargs):
|
41
|
+
if authenticator.is_authenticated():
|
42
|
+
return route(*args, **kwargs)
|
43
|
+
else:
|
44
|
+
flash("You need to be logged in to view that site.", category="Warning")
|
45
|
+
return redirect(url_for("auth.index"))
|
46
|
+
|
47
|
+
return wrapped_route
|
48
|
+
|
49
|
+
return decorator
|
@@ -103,7 +103,8 @@ class EquipmentController:
|
|
103
103
|
}
|
104
104
|
|
105
105
|
for equipment, offset in self._config.equipment_offsets.items():
|
106
|
-
|
106
|
+
if equipment in equipment_summary.index:
|
107
|
+
equipment_summary.loc[equipment, "total_distance_km"] += offset
|
107
108
|
|
108
109
|
return {
|
109
110
|
"equipment_variables": equipment_variables,
|
@@ -54,9 +54,9 @@ class ExplorerController:
|
|
54
54
|
if zoom not in self._config_accessor().explorer_zoom_levels:
|
55
55
|
return {"zoom_level_not_generated": zoom}
|
56
56
|
|
57
|
-
tile_evolution_states = self._tile_visit_accessor.
|
58
|
-
tile_visits = self._tile_visit_accessor.
|
59
|
-
tile_histories = self._tile_visit_accessor.
|
57
|
+
tile_evolution_states = self._tile_visit_accessor.tile_state["evolution_state"]
|
58
|
+
tile_visits = self._tile_visit_accessor.tile_state["tile_visits"]
|
59
|
+
tile_histories = self._tile_visit_accessor.tile_state["tile_history"]
|
60
60
|
|
61
61
|
medians = tile_histories[zoom].median()
|
62
62
|
median_lat, median_lon = get_tile_upper_left_lat_lon(
|