geo-activity-playground 0.16.1__tar.gz → 0.16.2__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.16.1 → geo_activity_playground-0.16.2}/PKG-INFO +1 -1
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/activity_parsers.py +4 -1
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/heatmap.py +0 -49
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/importers/directory.py +3 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/explorer_controller.py +13 -13
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/heatmap_controller.py +18 -38
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/pyproject.toml +1 -1
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/LICENSE +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/__init__.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/__main__.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/__init__.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/activities.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/cache_migrations.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/config.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/coordinates.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/tasks.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/test_tiles.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/core/tiles.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/explorer/__init__.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/explorer/grid_file.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/explorer/tile_visits.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/explorer/video.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/importers/strava_api.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/importers/strava_checkout.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/importers/test_strava_api.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/activity_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/app.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/calendar_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/eddington_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/entry_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/equipment_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/grayscale_tile_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/search_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/android-chrome-384x384.png +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/favicon.ico +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/safari-pinned-tab.svg +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/static/site.webmanifest +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/summary_controller.py +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/activity.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/calendar-month.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/calendar.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/eddington.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/equipment.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/explorer.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/heatmap.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/index.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/page.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/search.html.j2 +0 -0
- {geo_activity_playground-0.16.1 → geo_activity_playground-0.16.2}/geo_activity_playground/webui/templates/summary-statistics.html.j2 +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
import datetime
|
2
2
|
import gzip
|
3
|
+
import logging
|
3
4
|
import pathlib
|
4
5
|
import xml
|
5
6
|
|
@@ -10,6 +11,8 @@ import pandas as pd
|
|
10
11
|
import tcxreader.tcxreader
|
11
12
|
import xmltodict
|
12
13
|
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
13
16
|
|
14
17
|
class ActivityParseError(BaseException):
|
15
18
|
pass
|
@@ -43,7 +46,7 @@ def read_activity(path: pathlib.Path) -> pd.DataFrame:
|
|
43
46
|
elif file_type in [".kml", ".kmz"]:
|
44
47
|
df = read_kml_activity(path, opener)
|
45
48
|
else:
|
46
|
-
raise ActivityParseError(f"Unsupported file format
|
49
|
+
raise ActivityParseError(f"Unsupported file format: {file_type}")
|
47
50
|
|
48
51
|
if len(df):
|
49
52
|
try:
|
@@ -176,52 +176,3 @@ def gaussian_filter(image, sigma):
|
|
176
176
|
image = np.fft.irfft2(image_fft * gaussian_fft)
|
177
177
|
|
178
178
|
return image
|
179
|
-
|
180
|
-
|
181
|
-
def build_heatmap_image(
|
182
|
-
xy_data: np.ndarray,
|
183
|
-
mean_latitude: float,
|
184
|
-
num_activities: int,
|
185
|
-
tile_bounds: TileBounds,
|
186
|
-
) -> np.ndarray:
|
187
|
-
assert xy_data.shape[1] == 2
|
188
|
-
|
189
|
-
data = np.zeros(tile_bounds.shape)
|
190
|
-
|
191
|
-
xy_data = np.array(xy_data)
|
192
|
-
xy_data = np.round(
|
193
|
-
(xy_data - [tile_bounds.x_tile_min, tile_bounds.y_tile_min]) * OSM_TILE_SIZE
|
194
|
-
)
|
195
|
-
|
196
|
-
sigma_pixel = 1
|
197
|
-
for j, i in xy_data.astype(int):
|
198
|
-
data[
|
199
|
-
i - sigma_pixel : i + sigma_pixel, j - sigma_pixel : j + sigma_pixel
|
200
|
-
] += 1.0
|
201
|
-
|
202
|
-
res_pixel = (
|
203
|
-
156543.03 * np.cos(np.radians(mean_latitude)) / (2.0**tile_bounds.zoom)
|
204
|
-
) # from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
205
|
-
|
206
|
-
# trackpoint max accumulation per pixel = 1/5 (trackpoint/meter) * res_pixel (meter/pixel) * activities
|
207
|
-
# (Strava records trackpoints every 5 meters in average for cycling activites)
|
208
|
-
m = np.round((1.0 / 5.0) * res_pixel * num_activities)
|
209
|
-
|
210
|
-
data[data > m] = m
|
211
|
-
|
212
|
-
# equalize histogram and compute kernel density estimation
|
213
|
-
data_hist, _ = np.histogram(data, bins=int(m + 1))
|
214
|
-
data_hist = np.cumsum(data_hist) / data.size # normalized cumulated histogram
|
215
|
-
for i in range(data.shape[0]):
|
216
|
-
for j in range(data.shape[1]):
|
217
|
-
data[i, j] = m * data_hist[int(data[i, j])]
|
218
|
-
|
219
|
-
data = gaussian_filter(data, float(sigma_pixel))
|
220
|
-
|
221
|
-
data = (data - data.min()) / (data.max() - data.min())
|
222
|
-
|
223
|
-
cmap = pl.get_cmap("hot")
|
224
|
-
|
225
|
-
data_color = cmap(data)
|
226
|
-
data_color[data_color == cmap(0.0)] = 0.0 # remove background color
|
227
|
-
return data_color
|
@@ -122,20 +122,20 @@ def get_three_color_tiles(
|
|
122
122
|
else:
|
123
123
|
square_geojson = "{}"
|
124
124
|
|
125
|
+
try:
|
126
|
+
feature_collection = geojson.FeatureCollection(
|
127
|
+
features=[
|
128
|
+
make_explorer_tile(x, y, v, zoom) for (x, y), v in tile_dict.items()
|
129
|
+
]
|
130
|
+
)
|
131
|
+
explored_geojson = geojson.dumps(feature_collection)
|
132
|
+
except TypeError as e:
|
133
|
+
logger.error(f"Encountered TypeError while building GeoJSON: {e=}")
|
134
|
+
logger.error(f"{tile_dict = }")
|
135
|
+
raise
|
136
|
+
|
125
137
|
result = {
|
126
|
-
"explored_geojson":
|
127
|
-
geojson.FeatureCollection(
|
128
|
-
features=[
|
129
|
-
make_explorer_tile(
|
130
|
-
x,
|
131
|
-
y,
|
132
|
-
tile_dict[(x, y)],
|
133
|
-
zoom,
|
134
|
-
)
|
135
|
-
for (x, y), v in tile_dict.items()
|
136
|
-
]
|
137
|
-
)
|
138
|
-
),
|
138
|
+
"explored_geojson": explored_geojson,
|
139
139
|
"max_cluster_size": max_cluster_size,
|
140
140
|
"num_cluster_tiles": num_cluster_tiles,
|
141
141
|
"num_tiles": len(tile_dict),
|
@@ -13,10 +13,7 @@ from PIL import Image
|
|
13
13
|
from PIL import ImageDraw
|
14
14
|
|
15
15
|
from geo_activity_playground.core.activities import ActivityRepository
|
16
|
-
from geo_activity_playground.core.heatmap import build_heatmap_image
|
17
|
-
from geo_activity_playground.core.heatmap import build_map_from_tiles
|
18
16
|
from geo_activity_playground.core.heatmap import convert_to_grayscale
|
19
|
-
from geo_activity_playground.core.heatmap import crop_image_to_bounds
|
20
17
|
from geo_activity_playground.core.heatmap import GeoBounds
|
21
18
|
from geo_activity_playground.core.heatmap import get_sensible_zoom_level
|
22
19
|
from geo_activity_playground.core.tasks import work_tracker
|
@@ -69,7 +66,7 @@ class HeatmapController:
|
|
69
66
|
}
|
70
67
|
}
|
71
68
|
|
72
|
-
def
|
69
|
+
def _render_tile_image(self, x: int, y: int, z: int) -> np.ndarray:
|
73
70
|
tile_pixels = (OSM_TILE_SIZE, OSM_TILE_SIZE)
|
74
71
|
tile_count_cache_path = pathlib.Path(f"Cache/Heatmap/{z}/{x}/{y}.npy")
|
75
72
|
if tile_count_cache_path.exists():
|
@@ -115,47 +112,30 @@ class HeatmapController:
|
|
115
112
|
map_tile[:, :, c] = (1.0 - data_color[:, :, c]) * map_tile[
|
116
113
|
:, :, c
|
117
114
|
] + data_color[:, :, c]
|
115
|
+
return map_tile
|
118
116
|
|
117
|
+
def render_tile(self, x: int, y: int, z: int) -> bytes:
|
119
118
|
f = io.BytesIO()
|
120
|
-
pl.imsave(f,
|
119
|
+
pl.imsave(f, self._render_tile_image(x, y, z), format="png")
|
121
120
|
return bytes(f.getbuffer())
|
122
121
|
|
123
122
|
def download_heatmap(self, north, east, south, west) -> bytes:
|
124
123
|
geo_bounds = GeoBounds(south, west, north, east)
|
125
|
-
tile_bounds = get_sensible_zoom_level(geo_bounds, (
|
126
|
-
background = build_map_from_tiles(tile_bounds)
|
127
|
-
background = convert_to_grayscale(background)
|
128
|
-
background = 1.0 - background
|
129
|
-
|
130
|
-
relevant_activities = set()
|
131
|
-
|
132
|
-
for tile_x in range(tile_bounds.x_tile_min, tile_bounds.x_tile_max):
|
133
|
-
for tile_y in range(tile_bounds.y_tile_min, tile_bounds.y_tile_max):
|
134
|
-
tile = (tile_x, tile_y)
|
135
|
-
if tile in self.tile_visits[tile_bounds.zoom]:
|
136
|
-
relevant_activities |= self.tile_visits[tile_bounds.zoom][tile][
|
137
|
-
"activity_ids"
|
138
|
-
]
|
139
|
-
|
140
|
-
points = pd.concat(map(self._repository.get_time_series, relevant_activities))
|
141
|
-
xy_data = np.array([points["x"], points["y"]]).T * 2**tile_bounds.zoom
|
142
|
-
|
143
|
-
within = (
|
144
|
-
(tile_bounds.x_tile_min <= xy_data[:, 0])
|
145
|
-
& (xy_data[:, 0] <= tile_bounds.x_tile_max)
|
146
|
-
& (tile_bounds.y_tile_min <= xy_data[:, 1])
|
147
|
-
& (xy_data[:, 1] <= tile_bounds.y_tile_max)
|
148
|
-
)
|
149
|
-
xy_data = xy_data[within]
|
124
|
+
tile_bounds = get_sensible_zoom_level(geo_bounds, (4000, 4000))
|
150
125
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
126
|
+
background = np.zeros((*tile_bounds.shape, 3))
|
127
|
+
for x in range(tile_bounds.x_tile_min, tile_bounds.x_tile_max):
|
128
|
+
for y in range(tile_bounds.y_tile_min, tile_bounds.y_tile_max):
|
129
|
+
tile = np.array(get_tile(tile_bounds.zoom, x, y)) / 255
|
130
|
+
|
131
|
+
i = y - tile_bounds.y_tile_min
|
132
|
+
j = x - tile_bounds.x_tile_min
|
133
|
+
|
134
|
+
background[
|
135
|
+
i * OSM_TILE_SIZE : (i + 1) * OSM_TILE_SIZE,
|
136
|
+
j * OSM_TILE_SIZE : (j + 1) * OSM_TILE_SIZE,
|
137
|
+
:,
|
138
|
+
] = self._render_tile_image(x, y, tile_bounds.zoom)
|
159
139
|
|
160
140
|
f = io.BytesIO()
|
161
141
|
pl.imsave(f, background, format="png")
|
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
|
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
|