geo-activity-playground 0.31.0__tar.gz → 0.33.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.31.0 → geo_activity_playground-0.33.0}/PKG-INFO +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/config.py +5 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/heatmap.py +61 -15
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/tiles.py +8 -5
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/directory.py +6 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/controller.py +50 -36
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/templates/activity/name.html.j2 +3 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/templates/activity/show.html.j2 +2 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/app.py +14 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +20 -44
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/heatmap/blueprint.py +5 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/heatmap/heatmap_controller.py +14 -4
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/blueprint.py +44 -33
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +11 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2 +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/summary/controller.py +9 -15
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/summary/templates/summary/index.html.j2 +18 -5
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/templates/home.html.j2 +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/tile/blueprint.py +3 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/tile/controller.py +7 -3
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/upload/controller.py +1 -2
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/pyproject.toml +1 -1
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/LICENSE +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/__main__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/activities.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/coordinates.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/enrichment.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/heart_rate.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/paths.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/privacy_zones.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/similarity.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/tasks.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/test_tiles.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/test_time_conversion.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/core/time_conversion.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/explorer/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/explorer/grid_file.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/explorer/tile_visits.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/explorer/video.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/activity_parsers.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/csv_parser.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/strava_api.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/strava_checkout.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/test_csv_parser.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/test_directory.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/importers/test_strava_api.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/auth/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/auth/templates/auth/index.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/authenticator.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/calendar/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/calendar/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/calendar/controller.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/eddington/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/eddington/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/eddington/controller.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/eddington/templates/eddington/index.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/entry_controller.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/equipment/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/equipment/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/equipment/controller.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/equipment/templates/equipment/index.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/explorer/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/explorer/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/explorer/controller.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/heatmap/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/plot_util.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/search/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/search/templates/search/index.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/controller.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/index.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/segmentation.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/settings/templates/settings/strava.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/square_planner/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/square_planner/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/square_planner/controller.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/favicon.ico +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/favicon.svg +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/site.webmanifest +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/summary/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/summary/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/templates/page.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/tile/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/upload/__init__.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/upload/blueprint.py +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/upload/templates/upload/index.html.j2 +0 -0
- {geo_activity_playground-0.31.0 → geo_activity_playground-0.33.0}/geo_activity_playground/webui/upload/templates/upload/reload.html.j2 +0 -0
@@ -21,14 +21,16 @@ logger = logging.getLogger(__name__)
|
|
21
21
|
@dataclasses.dataclass
|
22
22
|
class Config:
|
23
23
|
birth_year: Optional[int] = None
|
24
|
-
color_scheme_for_counts: str = "
|
24
|
+
color_scheme_for_counts: str = "teals"
|
25
25
|
color_scheme_for_kind: str = "category10"
|
26
|
+
color_scheme_for_heatmap: str = "hot"
|
26
27
|
equipment_offsets: dict[str, float] = dataclasses.field(default_factory=dict)
|
27
28
|
explorer_zoom_levels: list[int] = dataclasses.field(
|
28
29
|
default_factory=lambda: [14, 17]
|
29
30
|
)
|
30
31
|
heart_rate_resting: int = 0
|
31
32
|
heart_rate_maximum: Optional[int] = None
|
33
|
+
ignore_suffixes: list[str] = dataclasses.field(default_factory=list)
|
32
34
|
kind_renames: dict[str, str] = dataclasses.field(default_factory=dict)
|
33
35
|
kinds_without_achievements: list[str] = dataclasses.field(default_factory=list)
|
34
36
|
metadata_extraction_regexes: list[str] = dataclasses.field(default_factory=list)
|
@@ -42,6 +44,8 @@ class Config:
|
|
42
44
|
strava_client_code: Optional[str] = None
|
43
45
|
time_diff_threshold_seconds: Optional[int] = 30
|
44
46
|
upload_password: Optional[str] = None
|
47
|
+
map_tile_url: str = "https://tile.openstreetmap.org/{zoom}/{x}/{y}.png"
|
48
|
+
map_tile_attribution: str = '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> | <a href="https://www.openstreetmap.org/fixthemap">Correct Map</a>'
|
45
49
|
|
46
50
|
|
47
51
|
class ConfigAccessor:
|
@@ -6,6 +6,7 @@ import logging
|
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
|
+
from geo_activity_playground.core.config import Config
|
9
10
|
from geo_activity_playground.core.tiles import compute_tile_float
|
10
11
|
from geo_activity_playground.core.tiles import get_tile
|
11
12
|
from geo_activity_playground.core.tiles import get_tile_upper_left_lat_lon
|
@@ -123,21 +124,66 @@ def get_sensible_zoom_level(
|
|
123
124
|
)
|
124
125
|
|
125
126
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
127
|
+
def build_map_from_tiles_around_center(
|
128
|
+
center: tuple[float, float],
|
129
|
+
zoom: int,
|
130
|
+
target: tuple[int, int],
|
131
|
+
inner_target: tuple[int, int],
|
132
|
+
config: Config,
|
133
|
+
) -> np.ndarray:
|
134
|
+
background = np.zeros((target[1], target[0], 3))
|
135
|
+
|
136
|
+
# We will work with the center point and have it in terms of tiles `t` and also in terms of pixels `p`. At the start we know that the tile center must be in the middle of the image.
|
137
|
+
t = np.array(center)
|
138
|
+
p = np.array([inner_target[0] / 2, inner_target[1] / 2])
|
139
|
+
|
140
|
+
# Shift both such that they are in the top-left corner of an even tile.
|
141
|
+
t_offset = np.array([center[0] % 1, center[1] % 1])
|
142
|
+
t -= t_offset
|
143
|
+
p -= t_offset * OSM_TILE_SIZE
|
144
|
+
|
145
|
+
# Shift until we have left the image.
|
146
|
+
shift = np.ceil(p / OSM_TILE_SIZE)
|
147
|
+
p -= shift * OSM_TILE_SIZE
|
148
|
+
t -= shift
|
149
|
+
|
150
|
+
num_tiles = np.ceil(np.array(target) / OSM_TILE_SIZE) + 1
|
151
|
+
|
152
|
+
for x in range(int(t[0]), int(t[0] + num_tiles[0])):
|
153
|
+
for y in range(int(t[1]), int(t[1]) + int(num_tiles[1])):
|
154
|
+
source_x_min = 0
|
155
|
+
source_y_min = 0
|
156
|
+
source_x_max = source_x_min + OSM_TILE_SIZE
|
157
|
+
source_y_max = source_y_min + OSM_TILE_SIZE
|
158
|
+
|
159
|
+
target_x_min = (x - int(t[0])) * OSM_TILE_SIZE + int(p[0])
|
160
|
+
target_y_min = (y - int(t[1])) * OSM_TILE_SIZE + int(p[1])
|
161
|
+
target_x_max = target_x_min + OSM_TILE_SIZE
|
162
|
+
target_y_max = target_y_min + OSM_TILE_SIZE
|
163
|
+
|
164
|
+
if target_x_min < 0:
|
165
|
+
source_x_min -= target_x_min
|
166
|
+
target_x_min = 0
|
167
|
+
if target_y_min < 0:
|
168
|
+
source_y_min -= target_y_min
|
169
|
+
target_y_min = 0
|
170
|
+
if target_x_max > target[0]:
|
171
|
+
a = target_x_max - target[0]
|
172
|
+
target_x_max -= a
|
173
|
+
source_x_max -= a
|
174
|
+
if target_y_max > target[1]:
|
175
|
+
a = target_y_max - target[1]
|
176
|
+
target_y_max -= a
|
177
|
+
source_y_max -= a
|
178
|
+
|
179
|
+
if source_x_max < 0 or source_y_max < 0:
|
180
|
+
continue
|
181
|
+
|
182
|
+
tile = np.array(get_tile(zoom, x, y, config.map_tile_url)) / 255
|
183
|
+
|
184
|
+
background[target_y_min:target_y_max, target_x_min:target_x_max] = tile[
|
185
|
+
source_y_min:source_y_max, source_x_min:source_x_max, :3
|
186
|
+
]
|
141
187
|
|
142
188
|
return background
|
143
189
|
|
@@ -3,6 +3,7 @@ import logging
|
|
3
3
|
import math
|
4
4
|
import pathlib
|
5
5
|
import time
|
6
|
+
import urllib.parse
|
6
7
|
from typing import Iterator
|
7
8
|
from typing import Optional
|
8
9
|
|
@@ -13,8 +14,10 @@ from PIL import Image
|
|
13
14
|
logger = logging.getLogger(__name__)
|
14
15
|
|
15
16
|
|
16
|
-
def osm_tile_path(x: int, y: int, zoom: int) -> pathlib.Path:
|
17
|
-
|
17
|
+
def osm_tile_path(x: int, y: int, zoom: int, url_template: str) -> pathlib.Path:
|
18
|
+
base_dir = pathlib.Path("Open Street Map Tiles")
|
19
|
+
dir_for_source = base_dir / urllib.parse.quote_plus(url_template)
|
20
|
+
path = dir_for_source / f"{zoom}/{x}/{y}.png"
|
18
21
|
path.parent.mkdir(parents=True, exist_ok=True)
|
19
22
|
return path
|
20
23
|
|
@@ -62,11 +65,11 @@ def download_file(url: str, destination: pathlib.Path):
|
|
62
65
|
|
63
66
|
|
64
67
|
@functools.lru_cache()
|
65
|
-
def get_tile(zoom: int, x: int, y: int) -> Image.Image:
|
66
|
-
destination = osm_tile_path(x, y, zoom)
|
68
|
+
def get_tile(zoom: int, x: int, y: int, url_template: str) -> Image.Image:
|
69
|
+
destination = osm_tile_path(x, y, zoom, url_template)
|
67
70
|
if not destination.exists():
|
68
71
|
logger.info(f"Downloading OSM tile {x=}, {y=}, {zoom=} …")
|
69
|
-
url =
|
72
|
+
url = url_template.format(x=x, y=y, zoom=zoom)
|
70
73
|
download_file(url, destination)
|
71
74
|
with Image.open(destination) as image:
|
72
75
|
image.load()
|
@@ -10,6 +10,7 @@ from typing import Optional
|
|
10
10
|
from tqdm import tqdm
|
11
11
|
|
12
12
|
from geo_activity_playground.core.activities import ActivityMeta
|
13
|
+
from geo_activity_playground.core.config import Config
|
13
14
|
from geo_activity_playground.core.paths import activity_extracted_dir
|
14
15
|
from geo_activity_playground.core.paths import activity_extracted_meta_dir
|
15
16
|
from geo_activity_playground.core.paths import activity_extracted_time_series_dir
|
@@ -24,13 +25,16 @@ ACTIVITY_DIR = pathlib.Path("Activities")
|
|
24
25
|
|
25
26
|
|
26
27
|
def import_from_directory(
|
27
|
-
metadata_extraction_regexes: list[str], num_processes: Optional[int]
|
28
|
+
metadata_extraction_regexes: list[str], num_processes: Optional[int], config: Config
|
28
29
|
) -> None:
|
29
30
|
|
30
31
|
activity_paths = [
|
31
32
|
path
|
32
33
|
for path in ACTIVITY_DIR.rglob("*.*")
|
33
|
-
if path.is_file()
|
34
|
+
if path.is_file()
|
35
|
+
and path.suffixes
|
36
|
+
and not path.stem.startswith(".")
|
37
|
+
and not path.suffix in config.ignore_suffixes
|
34
38
|
]
|
35
39
|
work_tracker = WorkTracker(activity_extracted_dir() / "work-tracker-extract.pickle")
|
36
40
|
new_activity_paths = work_tracker.filter(activity_paths)
|
@@ -21,11 +21,9 @@ from geo_activity_playground.core.activities import make_geojson_from_time_serie
|
|
21
21
|
from geo_activity_playground.core.activities import make_speed_color_bar
|
22
22
|
from geo_activity_playground.core.config import Config
|
23
23
|
from geo_activity_playground.core.heart_rate import HeartRateZoneComputer
|
24
|
-
from geo_activity_playground.core.heatmap import
|
25
|
-
from geo_activity_playground.core.heatmap import build_map_from_tiles
|
24
|
+
from geo_activity_playground.core.heatmap import build_map_from_tiles_around_center
|
26
25
|
from geo_activity_playground.core.heatmap import GeoBounds
|
27
|
-
from geo_activity_playground.core.heatmap import
|
28
|
-
from geo_activity_playground.core.heatmap import get_sensible_zoom_level
|
26
|
+
from geo_activity_playground.core.heatmap import OSM_MAX_ZOOM
|
29
27
|
from geo_activity_playground.core.heatmap import OSM_TILE_SIZE
|
30
28
|
from geo_activity_playground.core.heatmap import PixelBounds
|
31
29
|
from geo_activity_playground.core.heatmap import TileBounds
|
@@ -128,7 +126,7 @@ class ActivityController:
|
|
128
126
|
if len(time_series) == 0:
|
129
127
|
time_series = self._repository.get_time_series(id)
|
130
128
|
return make_sharepic(
|
131
|
-
activity, time_series, self._config.sharepic_suppressed_fields
|
129
|
+
activity, time_series, self._config.sharepic_suppressed_fields, self._config
|
132
130
|
)
|
133
131
|
|
134
132
|
def render_day(self, year: int, month: int, day: int) -> dict:
|
@@ -458,43 +456,58 @@ def make_sharepic(
|
|
458
456
|
activity: ActivityMeta,
|
459
457
|
time_series: pd.DataFrame,
|
460
458
|
sharepic_suppressed_fields: list[str],
|
459
|
+
config: Config,
|
461
460
|
) -> bytes:
|
462
|
-
|
461
|
+
tile_x = time_series["x"]
|
462
|
+
tile_y = time_series["y"]
|
463
|
+
tile_width = tile_x.max() - tile_x.min()
|
464
|
+
tile_height = tile_y.max() - tile_y.min()
|
465
|
+
|
466
|
+
target_width = 600
|
467
|
+
target_height = 600
|
468
|
+
footer_height = 100
|
469
|
+
target_map_height = target_height - footer_height
|
470
|
+
|
471
|
+
zoom = int(
|
472
|
+
min(
|
473
|
+
np.log2(target_width / tile_width / OSM_TILE_SIZE),
|
474
|
+
np.log2(target_map_height / tile_height / OSM_TILE_SIZE),
|
475
|
+
OSM_MAX_ZOOM,
|
476
|
+
)
|
477
|
+
)
|
463
478
|
|
464
|
-
|
465
|
-
|
466
|
-
tile_bounds = get_sensible_zoom_level(geo_bounds, (1500, 1500))
|
467
|
-
tile_bounds = make_tile_bounds_square(tile_bounds)
|
468
|
-
background = build_map_from_tiles(tile_bounds)
|
469
|
-
# background = convert_to_grayscale(background)
|
479
|
+
tile_xz = tile_x * 2**zoom
|
480
|
+
tile_yz = tile_y * 2**zoom
|
470
481
|
|
471
|
-
|
472
|
-
|
482
|
+
tile_xz_center = (
|
483
|
+
(tile_xz.max() + tile_xz.min()) / 2,
|
484
|
+
(tile_yz.max() + tile_yz.min()) / 2,
|
485
|
+
)
|
473
486
|
|
474
|
-
background =
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
487
|
+
background = build_map_from_tiles_around_center(
|
488
|
+
tile_xz_center,
|
489
|
+
zoom,
|
490
|
+
(target_width, target_height),
|
491
|
+
(target_width, target_map_height),
|
492
|
+
config,
|
493
|
+
)
|
479
494
|
|
480
495
|
img = Image.fromarray((background * 255).astype("uint8"), "RGB")
|
481
496
|
draw = ImageDraw.Draw(img, mode="RGBA")
|
482
497
|
|
483
498
|
for _, group in time_series.groupby("segment_id"):
|
484
|
-
xs, ys = compute_tile_float(
|
485
|
-
group["latitude"], group["longitude"], tile_bounds.zoom
|
486
|
-
)
|
487
499
|
yx = list(
|
488
|
-
(
|
489
|
-
|
490
|
-
|
500
|
+
zip(
|
501
|
+
(tile_xz - tile_xz_center[0]) * OSM_TILE_SIZE + target_width / 2,
|
502
|
+
(tile_yz - tile_xz_center[1]) * OSM_TILE_SIZE + target_map_height / 2,
|
491
503
|
)
|
492
|
-
for x, y in zip(xs, ys)
|
493
504
|
)
|
494
505
|
|
495
506
|
draw.line(yx, fill="red", width=4)
|
496
507
|
|
497
|
-
draw.rectangle(
|
508
|
+
draw.rectangle(
|
509
|
+
[0, img.height - footer_height, img.width, img.height], fill=(0, 0, 0, 180)
|
510
|
+
)
|
498
511
|
|
499
512
|
facts = {
|
500
513
|
"kind": f"{activity['kind']}",
|
@@ -515,19 +528,20 @@ def make_sharepic(
|
|
515
528
|
if not key in sharepic_suppressed_fields
|
516
529
|
}
|
517
530
|
|
518
|
-
draw.text(
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
531
|
+
draw.text(
|
532
|
+
(35, img.height - footer_height + 10),
|
533
|
+
" ".join(facts.values()),
|
534
|
+
font_size=20,
|
535
|
+
)
|
523
536
|
|
524
|
-
|
525
|
-
|
526
|
-
|
537
|
+
draw.text(
|
538
|
+
(img.width - 250, img.height - 20),
|
539
|
+
"Map: © Open Street Map Contributors",
|
540
|
+
font_size=14,
|
541
|
+
)
|
527
542
|
|
528
543
|
f = io.BytesIO()
|
529
544
|
img.save(f, format="png")
|
530
|
-
# pl.imsave(f, background, format="png")
|
531
545
|
return bytes(f.getbuffer())
|
532
546
|
|
533
547
|
|
@@ -17,7 +17,7 @@
|
|
17
17
|
});
|
18
18
|
L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
|
19
19
|
maxZoom: 19,
|
20
|
-
attribution: '
|
20
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
21
21
|
}).addTo(map);
|
22
22
|
|
23
23
|
let geojson = L.geoJSON({{ geojson| safe }}, {
|
@@ -21,7 +21,7 @@
|
|
21
21
|
});
|
22
22
|
L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
|
23
23
|
maxZoom: 19,
|
24
|
-
attribution: '
|
24
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
25
25
|
}).addTo(map);
|
26
26
|
|
27
27
|
let geojson = L.geoJSON({{ geojson| safe }}, {
|
@@ -17,7 +17,7 @@
|
|
17
17
|
});
|
18
18
|
L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
|
19
19
|
maxZoom: 19,
|
20
|
-
attribution: '
|
20
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
21
21
|
}).addTo(map);
|
22
22
|
|
23
23
|
let geojson = L.geoJSON({{ geojson| safe }}, {
|
@@ -64,7 +64,8 @@
|
|
64
64
|
<tbody>
|
65
65
|
{% for activity in activities %}
|
66
66
|
<tr>
|
67
|
-
<td><span style="color: {{ activity['color'] }};">█</span> <a
|
67
|
+
<td><span style="color: {{ activity['color'] }};">█</span> <a
|
68
|
+
href="{{ url_for('activity.show', id=activity.id) }}">{{
|
68
69
|
activity.name }}</a></td>
|
69
70
|
<td>{{ activity.start|dt }}</td>
|
70
71
|
<td>{{ activity.distance_km | round(1) }}</td>
|
@@ -52,7 +52,7 @@
|
|
52
52
|
});
|
53
53
|
L.tileLayer('/tile/pastel/{z}/{x}/{y}.png', {
|
54
54
|
maxZoom: 19,
|
55
|
-
attribution: '
|
55
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
56
56
|
}).addTo(map);
|
57
57
|
|
58
58
|
let geojson = L.geoJSON({{ color_line_geojson| safe }}, {
|
@@ -160,7 +160,7 @@
|
|
160
160
|
})
|
161
161
|
L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
|
162
162
|
maxZoom: 19,
|
163
|
-
attribution: '
|
163
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
164
164
|
}).addTo(map)
|
165
165
|
|
166
166
|
let geojson_layer = L.geoJSON(geojson).addTo(map)
|
@@ -3,6 +3,8 @@ import importlib
|
|
3
3
|
import json
|
4
4
|
import pathlib
|
5
5
|
import secrets
|
6
|
+
import shutil
|
7
|
+
import urllib.parse
|
6
8
|
|
7
9
|
from flask import Flask
|
8
10
|
from flask import render_template
|
@@ -97,7 +99,8 @@ def web_ui_main(
|
|
97
99
|
url_prefix="/explorer",
|
98
100
|
)
|
99
101
|
app.register_blueprint(
|
100
|
-
make_heatmap_blueprint(repository, tile_visit_accessor),
|
102
|
+
make_heatmap_blueprint(repository, tile_visit_accessor, config_accessor()),
|
103
|
+
url_prefix="/heatmap",
|
101
104
|
)
|
102
105
|
app.register_blueprint(
|
103
106
|
make_settings_blueprint(config_accessor, authenticator),
|
@@ -115,7 +118,7 @@ def web_ui_main(
|
|
115
118
|
make_summary_blueprint(repository, config_accessor()),
|
116
119
|
url_prefix="/summary",
|
117
120
|
)
|
118
|
-
app.register_blueprint(make_tile_blueprint(), url_prefix="/tile")
|
121
|
+
app.register_blueprint(make_tile_blueprint(config_accessor()), url_prefix="/tile")
|
119
122
|
app.register_blueprint(
|
120
123
|
make_upload_blueprint(
|
121
124
|
repository, tile_visit_accessor, config_accessor(), authenticator
|
@@ -123,11 +126,20 @@ def web_ui_main(
|
|
123
126
|
url_prefix="/upload",
|
124
127
|
)
|
125
128
|
|
129
|
+
base_dir = pathlib.Path("Open Street Map Tiles")
|
130
|
+
dir_for_source = base_dir / urllib.parse.quote_plus(config_accessor().map_tile_url)
|
131
|
+
if base_dir.exists() and not dir_for_source.exists():
|
132
|
+
subdirs = base_dir.glob("*")
|
133
|
+
dir_for_source.mkdir()
|
134
|
+
for subdir in subdirs:
|
135
|
+
shutil.move(subdir, dir_for_source)
|
136
|
+
|
126
137
|
@app.context_processor
|
127
138
|
def inject_global_variables() -> dict:
|
128
139
|
return {
|
129
140
|
"version": _try_get_version(),
|
130
141
|
"num_activities": len(repository),
|
142
|
+
"map_tile_attribution": config_accessor().map_tile_attribution,
|
131
143
|
}
|
132
144
|
|
133
145
|
app.run(host=host, port=port)
|
@@ -73,7 +73,7 @@
|
|
73
73
|
})
|
74
74
|
L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
|
75
75
|
maxZoom: 19,
|
76
|
-
attribution: '
|
76
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
77
77
|
}).addTo(map)
|
78
78
|
let explorer_layer_cluster_color = L.geoJSON(explorer_geojson, {
|
79
79
|
style: function (feature) {
|
@@ -87,58 +87,34 @@
|
|
87
87
|
let explorer_layer_first_age_color = L.geoJSON(explorer_geojson, {
|
88
88
|
style: function (feature) {
|
89
89
|
return {
|
90
|
-
color: "#440154", fillColor: feature.properties.first_age_color,
|
91
|
-
weight: 0.5
|
90
|
+
color: " #440154", fillColor: feature.properties.first_age_color, weight: 0.5
|
92
91
|
}
|
93
92
|
},
|
94
93
|
onEachFeature: onEachFeature
|
95
|
-
})
|
96
|
-
|
97
|
-
|
98
|
-
return {
|
99
|
-
color: "#440154", fillColor: feature.properties.last_age_color, weight:
|
100
|
-
0.5
|
101
|
-
}
|
102
|
-
},
|
103
|
-
onEachFeature: onEachFeature
|
104
|
-
})
|
105
|
-
|
106
|
-
let bbox = {{ center.bbox| safe }}
|
107
|
-
if (bbox) {
|
108
|
-
map.fitBounds(L.geoJSON(bbox).getBounds())
|
109
|
-
}
|
110
|
-
|
111
|
-
let explorer_square_layer = L.geoJSON(square_geojson,
|
112
|
-
{
|
113
|
-
style: function (feature) {
|
94
|
+
}) let explorer_layer_last_age_color = L.geoJSON(explorer_geojson, {
|
95
|
+
style:
|
96
|
+
function (feature) {
|
114
97
|
return {
|
115
|
-
color: "
|
98
|
+
color: "#440154", fillColor: feature.properties.last_age_color, weight:
|
99
|
+
0.5
|
116
100
|
}
|
117
|
-
}
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
function changeColor(method) {
|
101
|
+
}, onEachFeature: onEachFeature
|
102
|
+
}) let bbox = {{ center.bbox| safe }} if (bbox) {
|
103
|
+
map.fitBounds(L.geoJSON(bbox).getBounds())
|
104
|
+
} let explorer_square_layer = L.geoJSON(square_geojson, {
|
105
|
+
style: function (feature) { return { color: "blue", fill: false, weight: 2 } }
|
106
|
+
}).addTo(map)
|
107
|
+
active_layer = explorer_layer_cluster_color function changeColor(method) {
|
124
108
|
map.removeLayer(active_layer)
|
125
|
-
if (method == "cluster") {
|
126
|
-
active_layer = explorer_layer_cluster_color
|
127
|
-
} else if (method == "first") {
|
109
|
+
if (method == "cluster") { active_layer = explorer_layer_cluster_color } else if (method == "first") {
|
128
110
|
active_layer = explorer_layer_first_age_color
|
129
111
|
} else if (method == "last") {
|
130
112
|
active_layer = explorer_layer_last_age_color
|
131
|
-
}
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
bounds = map.getBounds()
|
137
|
-
zoom = "{{ zoom }}"
|
138
|
-
window.location.href =
|
139
|
-
`/explorer/${zoom}/${bounds.getNorth()}/${bounds.getEast()}/${bounds.getSouth()}/${bounds.getWest()}/${suffix}`
|
140
|
-
}
|
141
|
-
</script>
|
113
|
+
} map.addLayer(active_layer)
|
114
|
+
} function downloadAs(suffix) {
|
115
|
+
bounds = map.getBounds() zoom = "{{ zoom }}"
|
116
|
+
window.location.href = `/explorer/${zoom}/${bounds.getNorth()}/${bounds.getEast()}/${bounds.getSouth()}/${bounds.getWest()}/${suffix}`
|
117
|
+
} </script>
|
142
118
|
</div>
|
143
119
|
</div>
|
144
120
|
|
@@ -6,12 +6,15 @@ from flask import Response
|
|
6
6
|
from ...core.activities import ActivityRepository
|
7
7
|
from ...explorer.tile_visits import TileVisitAccessor
|
8
8
|
from .heatmap_controller import HeatmapController
|
9
|
+
from geo_activity_playground.core.config import Config
|
9
10
|
|
10
11
|
|
11
12
|
def make_heatmap_blueprint(
|
12
|
-
repository: ActivityRepository,
|
13
|
+
repository: ActivityRepository,
|
14
|
+
tile_visit_accessor: TileVisitAccessor,
|
15
|
+
config: Config,
|
13
16
|
) -> Blueprint:
|
14
|
-
heatmap_controller = HeatmapController(repository, tile_visit_accessor)
|
17
|
+
heatmap_controller = HeatmapController(repository, tile_visit_accessor, config)
|
15
18
|
blueprint = Blueprint("heatmap", __name__, template_folder="templates")
|
16
19
|
|
17
20
|
@blueprint.route("/")
|
@@ -8,6 +8,7 @@ from PIL import Image
|
|
8
8
|
from PIL import ImageDraw
|
9
9
|
|
10
10
|
from geo_activity_playground.core.activities import ActivityRepository
|
11
|
+
from geo_activity_playground.core.config import Config
|
11
12
|
from geo_activity_playground.core.heatmap import convert_to_grayscale
|
12
13
|
from geo_activity_playground.core.heatmap import GeoBounds
|
13
14
|
from geo_activity_playground.core.heatmap import get_sensible_zoom_level
|
@@ -29,10 +30,14 @@ OSM_TILE_SIZE = 256 # OSM tile size in pixel
|
|
29
30
|
|
30
31
|
class HeatmapController:
|
31
32
|
def __init__(
|
32
|
-
self,
|
33
|
+
self,
|
34
|
+
repository: ActivityRepository,
|
35
|
+
tile_visit_accessor: TileVisitAccessor,
|
36
|
+
config: Config,
|
33
37
|
) -> None:
|
34
38
|
self._repository = repository
|
35
39
|
self._tile_visit_accessor = tile_visit_accessor
|
40
|
+
self._config = config
|
36
41
|
|
37
42
|
self.tile_histories = self._tile_visit_accessor.tile_state["tile_history"]
|
38
43
|
self.tile_evolution_states = self._tile_visit_accessor.tile_state[
|
@@ -140,11 +145,11 @@ class HeatmapController:
|
|
140
145
|
tile_counts = np.sqrt(tile_counts) / 5
|
141
146
|
tile_counts[tile_counts > 1.0] = 1.0
|
142
147
|
|
143
|
-
cmap = pl.get_cmap(
|
148
|
+
cmap = pl.get_cmap(self._config.color_scheme_for_heatmap)
|
144
149
|
data_color = cmap(tile_counts)
|
145
150
|
data_color[data_color == cmap(0.0)] = 0.0 # remove background color
|
146
151
|
|
147
|
-
map_tile = np.array(get_tile(z, x, y)) / 255
|
152
|
+
map_tile = np.array(get_tile(z, x, y, self._config.map_tile_url)) / 255
|
148
153
|
map_tile = convert_to_grayscale(map_tile)
|
149
154
|
map_tile = 1.0 - map_tile # invert colors
|
150
155
|
for c in range(3):
|
@@ -168,7 +173,12 @@ class HeatmapController:
|
|
168
173
|
background = np.zeros((*pixel_bounds.shape, 3))
|
169
174
|
for x in range(tile_bounds.x_tile_min, tile_bounds.x_tile_max):
|
170
175
|
for y in range(tile_bounds.y_tile_min, tile_bounds.y_tile_max):
|
171
|
-
tile =
|
176
|
+
tile = (
|
177
|
+
np.array(
|
178
|
+
get_tile(tile_bounds.zoom, x, y, self._config.map_tile_url)
|
179
|
+
)
|
180
|
+
/ 255
|
181
|
+
)
|
172
182
|
|
173
183
|
i = y - tile_bounds.y_tile_min
|
174
184
|
j = x - tile_bounds.x_tile_min
|
@@ -35,7 +35,7 @@
|
|
35
35
|
});
|
36
36
|
L.tileLayer('/heatmap/tile/{z}/{x}/{y}/{{ kinds_str }}.png', {
|
37
37
|
maxZoom: 19,
|
38
|
-
attribution: '
|
38
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
39
39
|
}).addTo(map)
|
40
40
|
|
41
41
|
let bbox = {{ center.bbox| safe }}
|