geo-activity-playground 0.11.0__tar.gz → 0.12.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.12.0/LICENSE +21 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/PKG-INFO +1 -1
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/activity_parsers.py +6 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/explorer/clusters.py +30 -18
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/explorer/converters.py +33 -19
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/explorer/grid_file.py +47 -18
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/explorer_controller.py +8 -2
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/explorer.html.j2 +17 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/pyproject.toml +2 -1
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/__init__.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/__main__.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/__init__.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/activities.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/config.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/coordinates.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/heatmap.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/plots.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/sources.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/tasks.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/test_tiles.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/core/tiles.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/explorer/__init__.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/explorer/video.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/heatmap.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/importers/directory.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/importers/strava_api.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/activity_controller.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/app.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/calendar_controller.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/eddington_controller.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/entry_controller.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/equipment_controller.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/grayscale_tile_controller.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/heatmap_controller.py +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/android-chrome-384x384.png +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/favicon.ico +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/safari-pinned-tab.svg +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/static/site.webmanifest +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/activity.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/calendar-month.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/calendar.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/eddington.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/equipment.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/heatmap.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/index.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/page.html.j2 +0 -0
- {geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/webui/templates/summary-statistics.html.j2 +0 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Martin Ueding
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -159,6 +159,12 @@ def read_activity(path: pathlib.Path) -> pd.DataFrame:
|
|
159
159
|
except AttributeError as e:
|
160
160
|
print(df)
|
161
161
|
print(df.dtypes)
|
162
|
+
types = {}
|
163
|
+
for elem in df["time"]:
|
164
|
+
t = str(type(elem))
|
165
|
+
if t not in types:
|
166
|
+
types[t] = elem
|
167
|
+
print(types)
|
162
168
|
raise ActivityParseError(
|
163
169
|
"It looks like the date parsing has gone wrong."
|
164
170
|
) from e
|
@@ -24,11 +24,12 @@ def adjacent_to(tile: tuple[int, int]) -> Iterator[tuple[int, int]]:
|
|
24
24
|
class ExplorerClusterState:
|
25
25
|
def __init__(self, zoom: int) -> None:
|
26
26
|
self.num_neighbors: dict[tuple[int, int], int] = {}
|
27
|
-
self.
|
27
|
+
self.memberships: dict[tuple[int, int], tuple[int, int]] = {}
|
28
|
+
self.clusters: dict[tuple[int, int], list[tuple[int, int]]] = {}
|
28
29
|
self.cluster_evolution = pd.DataFrame()
|
29
30
|
self.start = 0
|
30
31
|
|
31
|
-
self._state_path = pathlib.Path(f"Cache/explorer_cluster_{zoom}
|
32
|
+
self._state_path = pathlib.Path(f"Cache/explorer_cluster_{zoom}_state_v2.json")
|
32
33
|
self._cluster_evolution_path = pathlib.Path(
|
33
34
|
f"Cache/explorer_cluster_{zoom}_evolution.parquet"
|
34
35
|
)
|
@@ -42,12 +43,14 @@ class ExplorerClusterState:
|
|
42
43
|
tuple(map(int, key.split("/"))): value
|
43
44
|
for key, value in data["num_neighbors"].items()
|
44
45
|
}
|
45
|
-
self.
|
46
|
-
tuple(map(int, key.split("/"))):
|
47
|
-
tuple(t) for t in data["clusters"][str(value)]
|
48
|
-
]
|
46
|
+
self.memberships = {
|
47
|
+
tuple(map(int, key.split("/"))): tuple(value)
|
49
48
|
for key, value in data["memberships"].items()
|
50
49
|
}
|
50
|
+
self.clusters = {
|
51
|
+
tuple(map(int, key.split("/"))): [tuple(t) for t in value]
|
52
|
+
for key, value in data["clusters"].items()
|
53
|
+
}
|
51
54
|
self.start = data["start"]
|
52
55
|
|
53
56
|
if self._cluster_evolution_path.exists():
|
@@ -60,11 +63,11 @@ class ExplorerClusterState:
|
|
60
63
|
f"{x}/{y}": count for (x, y), count in self.num_neighbors.items()
|
61
64
|
},
|
62
65
|
"memberships": {
|
63
|
-
f"{x}/{y}":
|
64
|
-
for (x, y), members in self.cluster_tiles.items()
|
66
|
+
f"{x}/{y}": value for (x, y), value in self.memberships.items()
|
65
67
|
},
|
66
68
|
"clusters": {
|
67
|
-
|
69
|
+
f"{x}/{y}": [tuple(member) for member in members]
|
70
|
+
for (x, y), members in self.clusters.items()
|
68
71
|
},
|
69
72
|
"start": self.start,
|
70
73
|
}
|
@@ -107,34 +110,43 @@ def get_explorer_cluster_evolution(zoom: int) -> ExplorerClusterState:
|
|
107
110
|
|
108
111
|
# If the current tile has all neighbors, make it it's own cluster.
|
109
112
|
if s.num_neighbors[tile] == 4:
|
110
|
-
s.
|
113
|
+
s.clusters[tile] = [tile]
|
114
|
+
s.memberships[tile] = tile
|
111
115
|
|
112
116
|
# Also make the adjacent tiles their own clusters, if they are full.
|
113
117
|
this_and_neighbors = [tile] + list(adjacent_to(tile))
|
114
118
|
for other in this_and_neighbors:
|
115
119
|
if s.num_neighbors.get(other, 0) == 4:
|
116
|
-
s.
|
120
|
+
s.clusters[other] = [other]
|
121
|
+
s.memberships[other] = other
|
117
122
|
|
118
123
|
for candidate in this_and_neighbors:
|
119
|
-
|
124
|
+
# If the the candidate is not a cluster tile, skip.
|
125
|
+
if candidate not in s.memberships:
|
120
126
|
continue
|
121
127
|
# The candidate is a cluster tile. Let's see whether any of the neighbors are also cluster tiles but with a different cluster. Then we need to join them.
|
122
128
|
for other in adjacent_to(candidate):
|
123
|
-
if other not in s.
|
129
|
+
if other not in s.memberships:
|
124
130
|
continue
|
125
131
|
# The other tile is also a cluster tile.
|
126
|
-
if s.
|
132
|
+
if s.memberships[candidate] == s.memberships[other]:
|
127
133
|
continue
|
128
134
|
# The two clusters are not the same. We add the other's cluster tile to this tile.
|
129
|
-
s.
|
135
|
+
this_cluster = s.clusters[s.memberships[candidate]]
|
136
|
+
assert isinstance(other, tuple), other
|
137
|
+
assert isinstance(s.memberships[other], tuple), s.memberships[other]
|
138
|
+
other_cluster = s.clusters[s.memberships[other]]
|
139
|
+
other_cluster_name = s.memberships[other]
|
140
|
+
this_cluster.extend(other_cluster)
|
130
141
|
# Update the other cluster tiles that they now point to the new cluster. This also updates the other tile.
|
131
|
-
for member in
|
132
|
-
s.
|
142
|
+
for member in other_cluster:
|
143
|
+
s.memberships[member] = s.memberships[candidate]
|
144
|
+
del s.clusters[other_cluster_name]
|
133
145
|
new_clusters = True
|
134
146
|
|
135
147
|
if new_clusters:
|
136
148
|
max_cluster_size = max(
|
137
|
-
(len(members) for members in s.
|
149
|
+
(len(members) for members in s.clusters.values()),
|
138
150
|
default=0,
|
139
151
|
)
|
140
152
|
if max_cluster_size > max_cluster_so_far:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import functools
|
2
2
|
import logging
|
3
3
|
import pathlib
|
4
|
+
import pickle
|
4
5
|
|
5
6
|
import pandas as pd
|
6
7
|
|
@@ -75,14 +76,16 @@ def reduce_tile_group(group: pd.DataFrame) -> pd.DataFrame:
|
|
75
76
|
def get_tile_history(repository: ActivityRepository, zoom: int) -> pd.DataFrame:
|
76
77
|
logger.info("Building explorer tile history from all activities …")
|
77
78
|
|
78
|
-
cache_file = pathlib.Path(f"Cache/first_time_per_tile_{zoom}.
|
79
|
+
cache_file = pathlib.Path(f"Cache/first_time_per_tile_{zoom}.pickle")
|
80
|
+
|
79
81
|
if cache_file.exists():
|
80
|
-
|
82
|
+
with open(cache_file, "rb") as f:
|
83
|
+
tile_visits = pickle.load(f)
|
81
84
|
else:
|
82
|
-
|
85
|
+
tile_visits = {}
|
83
86
|
|
84
87
|
with work_tracker(
|
85
|
-
pathlib.Path(f"Cache/task_first_time_per_tile_{zoom}.json")
|
88
|
+
pathlib.Path(f"Cache/task_first_time_per_tile_{zoom}_v2.json")
|
86
89
|
) as parsed_activities:
|
87
90
|
for activity in repository.iter_activities(new_to_old=False):
|
88
91
|
if activity.id in parsed_activities:
|
@@ -93,22 +96,33 @@ def get_tile_history(repository: ActivityRepository, zoom: int) -> pd.DataFrame:
|
|
93
96
|
shard = get_first_tiles(activity.id, repository, zoom)
|
94
97
|
if not len(shard):
|
95
98
|
continue
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
"
|
101
|
-
"
|
102
|
-
|
103
|
-
|
104
|
-
"
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
99
|
+
for _, row in shard.iterrows():
|
100
|
+
tile = (row["tile_x"], row["tile_y"])
|
101
|
+
if tile in tile_visits:
|
102
|
+
d = tile_visits[tile]
|
103
|
+
d["count"] += 1
|
104
|
+
if d["first_time"] > row["time"]:
|
105
|
+
d["first_time"] = row["time"]
|
106
|
+
d["first_id"] = activity.id
|
107
|
+
if d["last_time"] < row["time"]:
|
108
|
+
d["last_time"] = row["time"]
|
109
|
+
d["last_id"] = activity.id
|
110
|
+
else:
|
111
|
+
tile_visits[tile] = {
|
112
|
+
"count": 1,
|
113
|
+
"first_time": row["time"],
|
114
|
+
"first_id": activity.id,
|
115
|
+
"last_time": row["time"],
|
116
|
+
"last_id": activity.id,
|
117
|
+
}
|
110
118
|
|
111
119
|
logger.info("Store explorer tile history to cache file …")
|
112
|
-
|
120
|
+
with open(cache_file, "wb") as f:
|
121
|
+
pickle.dump(tile_visits, f)
|
113
122
|
|
123
|
+
tiles = pd.DataFrame(
|
124
|
+
[{"tile_x": x, "tile_y": y, **meta} for (x, y), meta in tile_visits.items()]
|
125
|
+
)
|
126
|
+
parquet_output = pathlib.Path(f"Cache/first_time_per_tile_{zoom}.parquet")
|
127
|
+
tiles.to_parquet(parquet_output)
|
114
128
|
return tiles
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import datetime
|
2
|
+
import itertools
|
2
3
|
import logging
|
3
4
|
import pathlib
|
4
5
|
from typing import Iterator
|
6
|
+
from typing import Optional
|
5
7
|
|
6
8
|
import geojson
|
7
9
|
import gpxpy
|
@@ -45,7 +47,7 @@ def get_three_color_tiles(
|
|
45
47
|
cmap_last(max(1 - last_age_days / (2 * 365), 0.0))
|
46
48
|
),
|
47
49
|
"cluster": False,
|
48
|
-
"color":
|
50
|
+
"color": "#303030",
|
49
51
|
"first_visit": row["first_time"].date().isoformat(),
|
50
52
|
"last_visit": row["last_time"].date().isoformat(),
|
51
53
|
"num_visits": row["count"],
|
@@ -74,26 +76,32 @@ def get_three_color_tiles(
|
|
74
76
|
for x in range(square_x, square_x + square_size):
|
75
77
|
for y in range(square_y, square_y + square_size):
|
76
78
|
tile_dict[(x, y)]["square"] = True
|
77
|
-
tile_dict[(x, y)]["color"] = "blue"
|
78
79
|
|
79
80
|
# Add cluster information.
|
80
|
-
for
|
81
|
-
|
82
|
-
|
81
|
+
for members in cluster_state.clusters.values():
|
82
|
+
for member in members:
|
83
|
+
tile_dict[member]["this_cluster_size"] = len(members)
|
84
|
+
tile_dict[member]["cluster"] = True
|
83
85
|
if len(cluster_state.cluster_evolution) > 0:
|
84
86
|
max_cluster_size = cluster_state.cluster_evolution["max_cluster_size"].iloc[-1]
|
85
87
|
else:
|
86
88
|
max_cluster_size = 0
|
87
|
-
num_cluster_tiles = len(cluster_state.
|
89
|
+
num_cluster_tiles = len(cluster_state.memberships)
|
88
90
|
|
89
91
|
# Apply cluster colors.
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
92
|
+
cluster_cmap = matplotlib.colormaps["tab10"]
|
93
|
+
for color, members in zip(
|
94
|
+
itertools.cycle(map(cluster_cmap, [0, 1, 2, 3, 4, 5, 6, 8, 9])),
|
95
|
+
sorted(
|
96
|
+
cluster_state.clusters.values(),
|
97
|
+
key=lambda members: len(members),
|
98
|
+
reverse=True,
|
99
|
+
),
|
100
|
+
):
|
101
|
+
hex_color = matplotlib.colors.to_hex(color)
|
102
|
+
print(f"Color cluster with {len(members)} tiles in {hex_color}.")
|
103
|
+
for member in members:
|
104
|
+
tile_dict[member]["color"] = hex_color
|
97
105
|
|
98
106
|
result = {
|
99
107
|
"explored_geojson": geojson.dumps(
|
@@ -113,6 +121,19 @@ def get_three_color_tiles(
|
|
113
121
|
"num_cluster_tiles": num_cluster_tiles,
|
114
122
|
"num_tiles": len(tile_dict),
|
115
123
|
"square_size": square_size,
|
124
|
+
"square_geojson": geojson.dumps(
|
125
|
+
geojson.FeatureCollection(
|
126
|
+
features=[
|
127
|
+
make_explorer_rectangle(
|
128
|
+
square_x,
|
129
|
+
square_y,
|
130
|
+
square_x + square_size,
|
131
|
+
square_y + square_size,
|
132
|
+
zoom,
|
133
|
+
)
|
134
|
+
]
|
135
|
+
)
|
136
|
+
),
|
116
137
|
}
|
117
138
|
return result
|
118
139
|
|
@@ -134,15 +155,23 @@ def get_explored_tiles(tiles: pd.DataFrame, zoom: int) -> list[list[list[float]]
|
|
134
155
|
|
135
156
|
def make_explorer_tile(
|
136
157
|
tile_x: int, tile_y: int, properties: dict, zoom: int
|
158
|
+
) -> geojson.Feature:
|
159
|
+
return make_explorer_rectangle(
|
160
|
+
tile_x, tile_y, tile_x + 1, tile_y + 1, zoom, properties
|
161
|
+
)
|
162
|
+
|
163
|
+
|
164
|
+
def make_explorer_rectangle(
|
165
|
+
x1: int, y1: int, x2: int, y2: int, zoom: int, properties: Optional[dict] = None
|
137
166
|
) -> geojson.Feature:
|
138
167
|
corners = [
|
139
168
|
get_tile_upper_left_lat_lon(*args)
|
140
169
|
for args in [
|
141
|
-
(
|
142
|
-
(
|
143
|
-
(
|
144
|
-
(
|
145
|
-
(
|
170
|
+
(x1, y1, zoom),
|
171
|
+
(x2, y1, zoom),
|
172
|
+
(x2, y2, zoom),
|
173
|
+
(x1, y2, zoom),
|
174
|
+
(x1, y1, zoom),
|
146
175
|
]
|
147
176
|
]
|
148
177
|
return geojson.Feature(
|
@@ -51,9 +51,9 @@ class ExplorerController:
|
|
51
51
|
"latitude": median_lat,
|
52
52
|
"longitude": median_lon,
|
53
53
|
"bbox": bounding_box_for_biggest_cluster(
|
54
|
-
cluster_state.
|
54
|
+
cluster_state.clusters.values(), zoom
|
55
55
|
)
|
56
|
-
if len(cluster_state.
|
56
|
+
if len(cluster_state.memberships) > 0
|
57
57
|
else {},
|
58
58
|
},
|
59
59
|
"explored": explored,
|
@@ -69,6 +69,8 @@ class ExplorerController:
|
|
69
69
|
|
70
70
|
|
71
71
|
def plot_tile_evolution(tiles: pd.DataFrame) -> str:
|
72
|
+
if len(tiles) == 0:
|
73
|
+
return ""
|
72
74
|
tiles.sort_values("first_time", inplace=True)
|
73
75
|
tiles["count"] = np.arange(1, len(tiles) + 1)
|
74
76
|
return (
|
@@ -83,6 +85,8 @@ def plot_tile_evolution(tiles: pd.DataFrame) -> str:
|
|
83
85
|
|
84
86
|
|
85
87
|
def plot_cluster_evolution(cluster_evolution: pd.DataFrame) -> str:
|
88
|
+
if len(cluster_evolution) == 0:
|
89
|
+
return ""
|
86
90
|
return (
|
87
91
|
alt.Chart(cluster_evolution, title="Cluster")
|
88
92
|
.mark_line(interpolate="step-after")
|
@@ -96,6 +100,8 @@ def plot_cluster_evolution(cluster_evolution: pd.DataFrame) -> str:
|
|
96
100
|
|
97
101
|
|
98
102
|
def plot_square_evolution(square_evolution: pd.DataFrame) -> str:
|
103
|
+
if len(square_evolution) == 0:
|
104
|
+
return ""
|
99
105
|
return (
|
100
106
|
alt.Chart(square_evolution, title="Square")
|
101
107
|
.mark_line(interpolate="step-after")
|
@@ -48,6 +48,7 @@
|
|
48
48
|
}
|
49
49
|
|
50
50
|
let explorer_geojson = {{ explored.explored_geojson| safe}}
|
51
|
+
let square_geojson = {{ explored.square_geojson | safe }}
|
51
52
|
let map = L.map('explorer-map', {
|
52
53
|
fullscreenControl: true,
|
53
54
|
center: [{{ center.latitude }}, {{ center.longitude }}],
|
@@ -75,6 +76,16 @@
|
|
75
76
|
map.fitBounds(L.geoJSON(bbox).getBounds())
|
76
77
|
}
|
77
78
|
|
79
|
+
let explorer_square_layer = L.geoJSON(square_geojson,
|
80
|
+
{
|
81
|
+
style: function (feature) {
|
82
|
+
return {
|
83
|
+
color: "blue", fill: false, weight: 2
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
).addTo(map)
|
88
|
+
|
78
89
|
active_layer = explorer_layer_cluster_color
|
79
90
|
|
80
91
|
function changeColor(method) {
|
@@ -100,13 +111,19 @@
|
|
100
111
|
|
101
112
|
<div class="row mb-3">
|
102
113
|
<div class="col-md-4">
|
114
|
+
{% if plot_tile_evolution|length > 0 %}
|
103
115
|
{{ vega_direct("plot_tile_evolution", plot_tile_evolution) }}
|
116
|
+
{% endif %}
|
104
117
|
</div>
|
105
118
|
<div class="col-md-4">
|
119
|
+
{% if plot_cluster_evolution|length > 0 %}
|
106
120
|
{{ vega_direct("plot_cluster_evolution", plot_cluster_evolution) }}
|
121
|
+
{% endif %}
|
107
122
|
</div>
|
108
123
|
<div class="col-md-4">
|
124
|
+
{% if plot_square_evolution|length > 0 %}
|
109
125
|
{{ vega_direct("plot_square_evolution", plot_square_evolution) }}
|
126
|
+
{% endif %}
|
110
127
|
</div>
|
111
128
|
</div>
|
112
129
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "geo-activity-playground"
|
3
|
-
version = "0.
|
3
|
+
version = "0.12.0"
|
4
4
|
description = "Analysis of geo data activities like rides, runs or hikes."
|
5
5
|
authors = ["Martin Ueding <mu@martin-ueding.de>"]
|
6
6
|
license = "MIT"
|
@@ -40,6 +40,7 @@ black = "^22.3.0"
|
|
40
40
|
mkdocs-material = "^9.4.1"
|
41
41
|
mypy = "^0.961"
|
42
42
|
pytest = "^7.1.2"
|
43
|
+
py-spy = "^0.3.14"
|
43
44
|
|
44
45
|
[build-system]
|
45
46
|
requires = ["poetry-core>=1.0.0"]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{geo_activity_playground-0.11.0 → geo_activity_playground-0.12.0}/geo_activity_playground/heatmap.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|