geo-activity-playground 0.35.0__py3-none-any.whl → 0.35.1__py3-none-any.whl
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/__main__.py +12 -0
- geo_activity_playground/core/raster_map.py +250 -0
- geo_activity_playground/core/tiles.py +6 -50
- geo_activity_playground/explorer/video.py +1 -1
- geo_activity_playground/heatmap_video.py +93 -0
- geo_activity_playground/importers/activity_parsers.py +1 -1
- geo_activity_playground/webui/activity/blueprint.py +3 -10
- geo_activity_playground/webui/activity/controller.py +10 -71
- geo_activity_playground/webui/app.py +44 -26
- geo_activity_playground/webui/calendar/blueprint.py +2 -5
- geo_activity_playground/webui/eddington/blueprint.py +1 -4
- geo_activity_playground/webui/equipment/blueprint.py +2 -8
- geo_activity_playground/webui/explorer/blueprint.py +2 -10
- geo_activity_playground/webui/heatmap/heatmap_controller.py +10 -12
- geo_activity_playground/webui/summary/blueprint.py +1 -4
- geo_activity_playground/webui/summary/controller.py +0 -1
- geo_activity_playground/webui/tile/blueprint.py +1 -4
- geo_activity_playground/webui/tile/controller.py +1 -1
- {geo_activity_playground-0.35.0.dist-info → geo_activity_playground-0.35.1.dist-info}/METADATA +1 -1
- {geo_activity_playground-0.35.0.dist-info → geo_activity_playground-0.35.1.dist-info}/RECORD +23 -22
- geo_activity_playground/core/heatmap.py +0 -194
- {geo_activity_playground-0.35.0.dist-info → geo_activity_playground-0.35.1.dist-info}/LICENSE +0 -0
- {geo_activity_playground-0.35.0.dist-info → geo_activity_playground-0.35.1.dist-info}/WHEEL +0 -0
- {geo_activity_playground-0.35.0.dist-info → geo_activity_playground-0.35.1.dist-info}/entry_points.txt +0 -0
@@ -12,6 +12,7 @@ from geo_activity_playground.core.config import import_old_config
|
|
12
12
|
from geo_activity_playground.core.config import import_old_strava_config
|
13
13
|
from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
|
14
14
|
from geo_activity_playground.explorer.video import explorer_video_main
|
15
|
+
from geo_activity_playground.heatmap_video import main_heatmap_video
|
15
16
|
from geo_activity_playground.webui.app import web_ui_main
|
16
17
|
from geo_activity_playground.webui.upload_blueprint import scan_for_activities
|
17
18
|
|
@@ -80,6 +81,17 @@ def main() -> None:
|
|
80
81
|
subparser = subparsers.add_parser("cache", help="Cache stuff")
|
81
82
|
subparser.set_defaults(func=lambda options: main_cache(options.basedir))
|
82
83
|
|
84
|
+
subparser = subparsers.add_parser(
|
85
|
+
"heatmap-video", help="Create a video with the evolution of the heatmap"
|
86
|
+
)
|
87
|
+
subparser.add_argument("latitude", type=float)
|
88
|
+
subparser.add_argument("longitude", type=float)
|
89
|
+
subparser.add_argument("zoom", type=int)
|
90
|
+
subparser.add_argument("--decay", type=float, default=0.05)
|
91
|
+
subparser.add_argument("--video-width", type=int, default=1920)
|
92
|
+
subparser.add_argument("--video-height", type=int, default=1080)
|
93
|
+
subparser.set_defaults(func=main_heatmap_video)
|
94
|
+
|
83
95
|
options = parser.parse_args()
|
84
96
|
coloredlogs.install(
|
85
97
|
fmt="%(asctime)s %(name)s %(levelname)s %(message)s",
|
@@ -0,0 +1,250 @@
|
|
1
|
+
import collections
|
2
|
+
import dataclasses
|
3
|
+
import functools
|
4
|
+
import logging
|
5
|
+
import pathlib
|
6
|
+
import time
|
7
|
+
import urllib.parse
|
8
|
+
|
9
|
+
import numpy as np
|
10
|
+
import requests
|
11
|
+
from PIL import Image
|
12
|
+
|
13
|
+
from geo_activity_playground.core.config import Config
|
14
|
+
from geo_activity_playground.core.tiles import compute_tile_float
|
15
|
+
from geo_activity_playground.core.tiles import get_tile_upper_left_lat_lon
|
16
|
+
|
17
|
+
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
OSM_TILE_SIZE = 256 # OSM tile size in pixel
|
22
|
+
OSM_MAX_ZOOM = 19 # OSM maximum zoom level
|
23
|
+
MAX_TILE_COUNT = 2000 # maximum number of tiles to download
|
24
|
+
|
25
|
+
## Basic data types ##
|
26
|
+
|
27
|
+
|
28
|
+
@dataclasses.dataclass
|
29
|
+
class GeoBounds:
|
30
|
+
"""
|
31
|
+
Models an area on the globe as a rectangle of latitude and longitude.
|
32
|
+
|
33
|
+
Latitude goes from South Pole (-90°) to North Pole (+90°). Longitude goes from West (-180°) to East (+180°). Be careful when converting latitude to Y-coordinates as increasing latitude will mean decreasing Y.
|
34
|
+
"""
|
35
|
+
|
36
|
+
lat_min: float
|
37
|
+
lon_min: float
|
38
|
+
lat_max: float
|
39
|
+
lon_max: float
|
40
|
+
|
41
|
+
|
42
|
+
@dataclasses.dataclass
|
43
|
+
class TileBounds:
|
44
|
+
zoom: int
|
45
|
+
x1: int
|
46
|
+
y1: int
|
47
|
+
x2: int
|
48
|
+
y2: int
|
49
|
+
|
50
|
+
@property
|
51
|
+
def width(self) -> int:
|
52
|
+
return self.x2 - self.x1
|
53
|
+
|
54
|
+
@property
|
55
|
+
def height(self) -> int:
|
56
|
+
return self.y2 - self.y1
|
57
|
+
|
58
|
+
|
59
|
+
@dataclasses.dataclass
|
60
|
+
class PixelBounds:
|
61
|
+
x1: int
|
62
|
+
y1: int
|
63
|
+
x2: int
|
64
|
+
y2: int
|
65
|
+
|
66
|
+
@classmethod
|
67
|
+
def from_tile_bounds(cls, tile_bounds: TileBounds) -> "PixelBounds":
|
68
|
+
return pixel_bounds_from_tile_bounds(tile_bounds)
|
69
|
+
|
70
|
+
@property
|
71
|
+
def width(self) -> int:
|
72
|
+
return self.x2 - self.x1
|
73
|
+
|
74
|
+
@property
|
75
|
+
def height(self) -> int:
|
76
|
+
return self.y2 - self.y1
|
77
|
+
|
78
|
+
@property
|
79
|
+
def shape(self) -> tuple[int, int]:
|
80
|
+
return self.height, self.width
|
81
|
+
|
82
|
+
|
83
|
+
@dataclasses.dataclass
|
84
|
+
class RasterMapImage:
|
85
|
+
image: np.ndarray
|
86
|
+
tile_bounds: TileBounds
|
87
|
+
geo_bounds: GeoBounds
|
88
|
+
pixel_bounds: PixelBounds
|
89
|
+
|
90
|
+
|
91
|
+
## Converter functions ##
|
92
|
+
|
93
|
+
|
94
|
+
def tile_bounds_from_geo_bounds(geo_bounds: GeoBounds) -> TileBounds:
|
95
|
+
x1, y1 = compute_tile_float(geo_bounds.lat_max, geo_bounds.lon_min)
|
96
|
+
x2, y2 = compute_tile_float(geo_bounds.lat_min, geo_bounds.lon_min)
|
97
|
+
return TileBounds(x1, y1, x2, y2)
|
98
|
+
|
99
|
+
|
100
|
+
def pixel_bounds_from_tile_bounds(tile_bounds: TileBounds) -> PixelBounds:
|
101
|
+
return PixelBounds(
|
102
|
+
int(tile_bounds.x1 * OSM_TILE_SIZE),
|
103
|
+
int(tile_bounds.y1 * OSM_TILE_SIZE),
|
104
|
+
int(tile_bounds.x2 * OSM_TILE_SIZE),
|
105
|
+
int(tile_bounds.y2 * OSM_TILE_SIZE),
|
106
|
+
)
|
107
|
+
|
108
|
+
|
109
|
+
def get_sensible_zoom_level(
|
110
|
+
bounds: GeoBounds, picture_size: tuple[int, int]
|
111
|
+
) -> TileBounds:
|
112
|
+
zoom = OSM_MAX_ZOOM
|
113
|
+
|
114
|
+
while True:
|
115
|
+
x_tile_min, y_tile_max = map(
|
116
|
+
int, compute_tile_float(bounds.lat_min, bounds.lon_min, zoom)
|
117
|
+
)
|
118
|
+
x_tile_max, y_tile_min = map(
|
119
|
+
int, compute_tile_float(bounds.lat_max, bounds.lon_max, zoom)
|
120
|
+
)
|
121
|
+
|
122
|
+
x_tile_max += 1
|
123
|
+
y_tile_max += 1
|
124
|
+
|
125
|
+
if (x_tile_max - x_tile_min) * OSM_TILE_SIZE <= picture_size[0] and (
|
126
|
+
y_tile_max - y_tile_min
|
127
|
+
) * OSM_TILE_SIZE <= picture_size[1]:
|
128
|
+
break
|
129
|
+
|
130
|
+
zoom -= 1
|
131
|
+
|
132
|
+
tile_count = (x_tile_max - x_tile_min) * (y_tile_max - y_tile_min)
|
133
|
+
|
134
|
+
if tile_count > MAX_TILE_COUNT:
|
135
|
+
raise RuntimeError("Zoom value too high, too many tiles to download")
|
136
|
+
|
137
|
+
return TileBounds(zoom, x_tile_min, y_tile_min, x_tile_max, y_tile_max)
|
138
|
+
|
139
|
+
|
140
|
+
@functools.lru_cache()
|
141
|
+
def get_tile(zoom: int, x: int, y: int, url_template: str) -> Image.Image:
|
142
|
+
destination = osm_tile_path(x, y, zoom, url_template)
|
143
|
+
if not destination.exists():
|
144
|
+
logger.info(f"Downloading OSM tile {x=}, {y=}, {zoom=} …")
|
145
|
+
url = url_template.format(x=x, y=y, zoom=zoom)
|
146
|
+
download_file(url, destination)
|
147
|
+
with Image.open(destination) as image:
|
148
|
+
image.load()
|
149
|
+
image = image.convert("RGB")
|
150
|
+
return image
|
151
|
+
|
152
|
+
|
153
|
+
def tile_bounds_around_center(
|
154
|
+
tile_center: tuple[float, float], pixel_size: tuple[int, int], zoom: int
|
155
|
+
) -> TileBounds:
|
156
|
+
x, y = tile_center
|
157
|
+
width = pixel_size[0] / OSM_TILE_SIZE
|
158
|
+
height = pixel_size[1] / OSM_TILE_SIZE
|
159
|
+
return TileBounds(
|
160
|
+
zoom, x - width / 2, y - height / 2, x + width / 2, y + height / 2
|
161
|
+
)
|
162
|
+
|
163
|
+
|
164
|
+
def _paste_array(
|
165
|
+
target: np.ndarray, source: np.ndarray, offset_0: int, offset_1: int
|
166
|
+
) -> None:
|
167
|
+
source_min_0 = 0
|
168
|
+
source_min_1 = 0
|
169
|
+
source_max_0 = source.shape[0]
|
170
|
+
source_max_1 = source.shape[1]
|
171
|
+
|
172
|
+
target_min_0 = offset_0
|
173
|
+
target_min_1 = offset_1
|
174
|
+
target_max_0 = offset_0 + source.shape[0]
|
175
|
+
target_max_1 = offset_1 + source.shape[1]
|
176
|
+
|
177
|
+
if target_min_1 < 0:
|
178
|
+
source_min_1 -= target_min_1
|
179
|
+
target_min_1 = 0
|
180
|
+
if target_min_0 < 0:
|
181
|
+
source_min_0 -= target_min_0
|
182
|
+
target_min_0 = 0
|
183
|
+
if target_max_1 > target.shape[1]:
|
184
|
+
a = target_max_1 - target.shape[1]
|
185
|
+
target_max_1 -= a
|
186
|
+
source_max_1 -= a
|
187
|
+
if target_max_0 > target.shape[0]:
|
188
|
+
a = target_max_0 - target.shape[0]
|
189
|
+
target_max_0 -= a
|
190
|
+
source_max_0 -= a
|
191
|
+
|
192
|
+
if source_max_1 < 0 or source_max_0 < 0:
|
193
|
+
return
|
194
|
+
|
195
|
+
target[target_min_0:target_max_0, target_min_1:target_max_1] = source[
|
196
|
+
source_min_0:source_max_0, source_min_1:source_max_1
|
197
|
+
]
|
198
|
+
|
199
|
+
|
200
|
+
def map_image_from_tile_bounds(tile_bounds: TileBounds, config: Config) -> np.ndarray:
|
201
|
+
pixel_bounds = pixel_bounds_from_tile_bounds(tile_bounds)
|
202
|
+
background = np.zeros((pixel_bounds.height, pixel_bounds.width, 3))
|
203
|
+
|
204
|
+
north_west = np.array([tile_bounds.x1, tile_bounds.y1])
|
205
|
+
offset = north_west % 1
|
206
|
+
tile_anchor = north_west - offset
|
207
|
+
pixel_anchor = np.array([0, 0]) - np.array(offset * OSM_TILE_SIZE, dtype=np.int64)
|
208
|
+
|
209
|
+
num_tile_x = int(np.ceil(tile_bounds.width)) + 1
|
210
|
+
num_tile_y = int(np.ceil(tile_bounds.height)) + 1
|
211
|
+
|
212
|
+
for x in range(int(tile_anchor[0]), int(tile_anchor[0] + num_tile_x)):
|
213
|
+
for y in range(int(tile_anchor[1]), int(tile_anchor[1]) + num_tile_y):
|
214
|
+
tile = np.array(get_tile(tile_bounds.zoom, x, y, config.map_tile_url)) / 255
|
215
|
+
_paste_array(
|
216
|
+
background,
|
217
|
+
tile,
|
218
|
+
(y - int(tile_anchor[1])) * OSM_TILE_SIZE + int(pixel_anchor[1]),
|
219
|
+
(x - int(tile_anchor[0])) * OSM_TILE_SIZE + int(pixel_anchor[0]),
|
220
|
+
)
|
221
|
+
|
222
|
+
return background
|
223
|
+
|
224
|
+
|
225
|
+
def convert_to_grayscale(image: np.ndarray) -> np.ndarray:
|
226
|
+
image = np.sum(image * [0.2126, 0.7152, 0.0722], axis=2)
|
227
|
+
image = np.dstack((image, image, image))
|
228
|
+
return image
|
229
|
+
|
230
|
+
|
231
|
+
def osm_tile_path(x: int, y: int, zoom: int, url_template: str) -> pathlib.Path:
|
232
|
+
base_dir = pathlib.Path("Open Street Map Tiles")
|
233
|
+
dir_for_source = base_dir / urllib.parse.quote_plus(url_template)
|
234
|
+
path = dir_for_source / f"{zoom}/{x}/{y}.png"
|
235
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
236
|
+
return path
|
237
|
+
|
238
|
+
|
239
|
+
def download_file(url: str, destination: pathlib.Path):
|
240
|
+
if not destination.parent.exists():
|
241
|
+
destination.parent.mkdir(exist_ok=True, parents=True)
|
242
|
+
r = requests.get(
|
243
|
+
url,
|
244
|
+
allow_redirects=True,
|
245
|
+
headers={"User-Agent": "Martin's Geo Activity Playground"},
|
246
|
+
)
|
247
|
+
assert r.ok
|
248
|
+
with open(destination, "wb") as f:
|
249
|
+
f.write(r.content)
|
250
|
+
time.sleep(0.1)
|
@@ -1,34 +1,12 @@
|
|
1
|
-
import functools
|
2
1
|
import logging
|
3
2
|
import math
|
4
|
-
import pathlib
|
5
|
-
import time
|
6
|
-
import urllib.parse
|
7
3
|
from typing import Iterator
|
8
4
|
from typing import Optional
|
9
5
|
|
10
6
|
import numpy as np
|
11
|
-
import requests
|
12
|
-
from PIL import Image
|
13
|
-
|
14
|
-
logger = logging.getLogger(__name__)
|
15
|
-
|
16
|
-
|
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"
|
21
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
22
|
-
return path
|
23
7
|
|
24
8
|
|
25
|
-
|
26
|
-
x = np.radians(lon)
|
27
|
-
y = np.arcsinh(np.tan(np.radians(lat)))
|
28
|
-
x = (1 + x / np.pi) / 2
|
29
|
-
y = (1 - y / np.pi) / 2
|
30
|
-
n = 2**zoom
|
31
|
-
return int(x * n), int(y * n)
|
9
|
+
logger = logging.getLogger(__name__)
|
32
10
|
|
33
11
|
|
34
12
|
def compute_tile_float(lat: float, lon: float, zoom: int) -> tuple[float, float]:
|
@@ -40,6 +18,11 @@ def compute_tile_float(lat: float, lon: float, zoom: int) -> tuple[float, float]
|
|
40
18
|
return x * n, y * n
|
41
19
|
|
42
20
|
|
21
|
+
def compute_tile(lat: float, lon: float, zoom: int) -> tuple[int, int]:
|
22
|
+
x, y = compute_tile_float(lat, lon, zoom)
|
23
|
+
return int(x), int(y)
|
24
|
+
|
25
|
+
|
43
26
|
def get_tile_upper_left_lat_lon(
|
44
27
|
tile_x: int, tile_y: int, zoom: int
|
45
28
|
) -> tuple[float, float]:
|
@@ -50,33 +33,6 @@ def get_tile_upper_left_lat_lon(
|
|
50
33
|
return lat_deg, lon_deg
|
51
34
|
|
52
35
|
|
53
|
-
def download_file(url: str, destination: pathlib.Path):
|
54
|
-
if not destination.parent.exists():
|
55
|
-
destination.parent.mkdir(exist_ok=True, parents=True)
|
56
|
-
r = requests.get(
|
57
|
-
url,
|
58
|
-
allow_redirects=True,
|
59
|
-
headers={"User-Agent": "Martin's Geo Activity Playground"},
|
60
|
-
)
|
61
|
-
assert r.ok
|
62
|
-
with open(destination, "wb") as f:
|
63
|
-
f.write(r.content)
|
64
|
-
time.sleep(0.1)
|
65
|
-
|
66
|
-
|
67
|
-
@functools.lru_cache()
|
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)
|
70
|
-
if not destination.exists():
|
71
|
-
logger.info(f"Downloading OSM tile {x=}, {y=}, {zoom=} …")
|
72
|
-
url = url_template.format(x=x, y=y, zoom=zoom)
|
73
|
-
download_file(url, destination)
|
74
|
-
with Image.open(destination) as image:
|
75
|
-
image.load()
|
76
|
-
image = image.convert("RGB")
|
77
|
-
return image
|
78
|
-
|
79
|
-
|
80
36
|
def xy_to_latlon(x: float, y: float, zoom: int) -> tuple[float, float]:
|
81
37
|
"""
|
82
38
|
Returns (lat, lon) in degree from OSM coordinates (x,y) rom https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import collections
|
2
|
+
import os
|
3
|
+
import pathlib
|
4
|
+
|
5
|
+
import matplotlib.pyplot as pl
|
6
|
+
import numpy as np
|
7
|
+
import pandas as pd
|
8
|
+
from PIL import Image
|
9
|
+
from PIL import ImageDraw
|
10
|
+
from tqdm import tqdm
|
11
|
+
|
12
|
+
from geo_activity_playground.core.activities import ActivityRepository
|
13
|
+
from geo_activity_playground.core.config import ConfigAccessor
|
14
|
+
from geo_activity_playground.core.raster_map import convert_to_grayscale
|
15
|
+
from geo_activity_playground.core.raster_map import map_image_from_tile_bounds
|
16
|
+
from geo_activity_playground.core.raster_map import OSM_TILE_SIZE
|
17
|
+
from geo_activity_playground.core.raster_map import tile_bounds_around_center
|
18
|
+
from geo_activity_playground.core.tiles import compute_tile_float
|
19
|
+
|
20
|
+
|
21
|
+
def main_heatmap_video(options) -> None:
|
22
|
+
zoom: int = options.zoom
|
23
|
+
print(options)
|
24
|
+
video_size = options.video_width, options.video_height
|
25
|
+
os.chdir(options.basedir)
|
26
|
+
|
27
|
+
repository = ActivityRepository()
|
28
|
+
repository.reload()
|
29
|
+
assert len(repository) > 0
|
30
|
+
config_accessor = ConfigAccessor()
|
31
|
+
|
32
|
+
center_xy = compute_tile_float(options.latitude, options.longitude, zoom)
|
33
|
+
|
34
|
+
tile_bounds = tile_bounds_around_center(center_xy, video_size, zoom)
|
35
|
+
background = map_image_from_tile_bounds(tile_bounds, config_accessor())
|
36
|
+
|
37
|
+
background = convert_to_grayscale(background)
|
38
|
+
background = 1.0 - background # invert colors
|
39
|
+
|
40
|
+
activities_per_day = collections.defaultdict(set)
|
41
|
+
for activity in tqdm(
|
42
|
+
repository.iter_activities(), desc="Gather activities per day"
|
43
|
+
):
|
44
|
+
activities_per_day[activity["start"].date()].add(activity["id"])
|
45
|
+
|
46
|
+
running_counts = np.zeros(background.shape[:2], np.float64)
|
47
|
+
|
48
|
+
output_dir = pathlib.Path("Heatmap Video")
|
49
|
+
output_dir.mkdir(exist_ok=True)
|
50
|
+
|
51
|
+
first_day = min(activities_per_day)
|
52
|
+
last_day = max(activities_per_day)
|
53
|
+
days = pd.date_range(first_day, last_day)
|
54
|
+
for current_day in tqdm(days, desc="Generate video frames"):
|
55
|
+
for activity_id in activities_per_day[current_day.date()]:
|
56
|
+
im = Image.new("L", video_size)
|
57
|
+
draw = ImageDraw.Draw(im)
|
58
|
+
|
59
|
+
time_series = repository.get_time_series(activity_id)
|
60
|
+
for _, group in time_series.groupby("segment_id"):
|
61
|
+
tile_xz = group["x"] * 2**zoom
|
62
|
+
tile_yz = group["y"] * 2**zoom
|
63
|
+
|
64
|
+
xy_pixels = list(
|
65
|
+
zip(
|
66
|
+
(tile_xz - center_xy[0]) * OSM_TILE_SIZE
|
67
|
+
+ options.video_width / 2,
|
68
|
+
(tile_yz - center_xy[1]) * OSM_TILE_SIZE
|
69
|
+
+ options.video_height / 2,
|
70
|
+
)
|
71
|
+
)
|
72
|
+
pixels = [int(value) for t in xy_pixels for value in t]
|
73
|
+
draw.line(pixels, fill=1, width=max(3, 6 * (zoom - 17)))
|
74
|
+
aim = np.array(im)
|
75
|
+
running_counts += aim
|
76
|
+
|
77
|
+
tile_counts = np.sqrt(running_counts) / 5
|
78
|
+
tile_counts[tile_counts > 1.0] = 1.0
|
79
|
+
|
80
|
+
cmap = pl.get_cmap(config_accessor().color_scheme_for_heatmap)
|
81
|
+
data_color = cmap(tile_counts)
|
82
|
+
data_color[data_color == cmap(0.0)] = 0.0 # remove background color
|
83
|
+
|
84
|
+
rendered = np.zeros_like(background)
|
85
|
+
for c in range(3):
|
86
|
+
rendered[:, :, c] = (1.0 - data_color[:, :, c]) * background[
|
87
|
+
:, :, c
|
88
|
+
] + data_color[:, :, c]
|
89
|
+
|
90
|
+
img = Image.fromarray((rendered * 255).astype("uint8"), "RGB")
|
91
|
+
img.save(output_dir / f"{current_day.date()}.png", format="png")
|
92
|
+
|
93
|
+
running_counts *= 1 - options.decay
|
@@ -24,7 +24,7 @@ class ActivityParseError(BaseException):
|
|
24
24
|
|
25
25
|
|
26
26
|
def read_activity(path: pathlib.Path) -> tuple[ActivityMeta, pd.DataFrame]:
|
27
|
-
suffixes = path.suffixes
|
27
|
+
suffixes = [s.lower() for s in path.suffixes]
|
28
28
|
metadata = ActivityMeta()
|
29
29
|
|
30
30
|
if suffixes[-1] == ".gz":
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import json
|
2
2
|
import urllib.parse
|
3
|
-
from collections.abc import Collection
|
4
3
|
|
5
4
|
from flask import Blueprint
|
6
5
|
from flask import redirect
|
@@ -9,26 +8,20 @@ from flask import request
|
|
9
8
|
from flask import Response
|
10
9
|
from flask import url_for
|
11
10
|
|
12
|
-
from
|
13
|
-
from ...explorer.tile_visits import TileVisitAccessor
|
14
|
-
from .controller import ActivityController
|
15
|
-
from geo_activity_playground.core.config import Config
|
11
|
+
from geo_activity_playground.core.activities import ActivityRepository
|
16
12
|
from geo_activity_playground.core.paths import activity_meta_override_dir
|
17
|
-
from geo_activity_playground.
|
13
|
+
from geo_activity_playground.webui.activity.controller import ActivityController
|
18
14
|
from geo_activity_playground.webui.authenticator import Authenticator
|
19
15
|
from geo_activity_playground.webui.authenticator import needs_authentication
|
20
16
|
|
21
17
|
|
22
18
|
def make_activity_blueprint(
|
19
|
+
activity_controller: ActivityController,
|
23
20
|
repository: ActivityRepository,
|
24
|
-
tile_visit_accessor: TileVisitAccessor,
|
25
|
-
config: Config,
|
26
21
|
authenticator: Authenticator,
|
27
22
|
) -> Blueprint:
|
28
23
|
blueprint = Blueprint("activity", __name__, template_folder="templates")
|
29
24
|
|
30
|
-
activity_controller = ActivityController(repository, tile_visit_accessor, config)
|
31
|
-
|
32
25
|
@blueprint.route("/all")
|
33
26
|
def all():
|
34
27
|
return render_template(
|
@@ -12,8 +12,6 @@ import pandas as pd
|
|
12
12
|
from PIL import Image
|
13
13
|
from PIL import ImageDraw
|
14
14
|
|
15
|
-
from ...explorer.grid_file import make_grid_file_geojson
|
16
|
-
from ...explorer.grid_file import make_grid_points
|
17
15
|
from geo_activity_playground.core.activities import ActivityMeta
|
18
16
|
from geo_activity_playground.core.activities import ActivityRepository
|
19
17
|
from geo_activity_playground.core.activities import make_geojson_color_line
|
@@ -21,14 +19,13 @@ from geo_activity_playground.core.activities import make_geojson_from_time_serie
|
|
21
19
|
from geo_activity_playground.core.activities import make_speed_color_bar
|
22
20
|
from geo_activity_playground.core.config import Config
|
23
21
|
from geo_activity_playground.core.heart_rate import HeartRateZoneComputer
|
24
|
-
from geo_activity_playground.core.heatmap import build_map_from_tiles_around_center
|
25
|
-
from geo_activity_playground.core.heatmap import GeoBounds
|
26
|
-
from geo_activity_playground.core.heatmap import OSM_MAX_ZOOM
|
27
|
-
from geo_activity_playground.core.heatmap import OSM_TILE_SIZE
|
28
|
-
from geo_activity_playground.core.heatmap import PixelBounds
|
29
|
-
from geo_activity_playground.core.heatmap import TileBounds
|
30
22
|
from geo_activity_playground.core.privacy_zones import PrivacyZone
|
31
|
-
from geo_activity_playground.core.
|
23
|
+
from geo_activity_playground.core.raster_map import map_image_from_tile_bounds
|
24
|
+
from geo_activity_playground.core.raster_map import OSM_MAX_ZOOM
|
25
|
+
from geo_activity_playground.core.raster_map import OSM_TILE_SIZE
|
26
|
+
from geo_activity_playground.core.raster_map import tile_bounds_around_center
|
27
|
+
from geo_activity_playground.explorer.grid_file import make_grid_file_geojson
|
28
|
+
from geo_activity_playground.explorer.grid_file import make_grid_points
|
32
29
|
from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
|
33
30
|
|
34
31
|
logger = logging.getLogger(__name__)
|
@@ -412,62 +409,6 @@ def name_minutes_plot(meta: pd.DataFrame) -> str:
|
|
412
409
|
)
|
413
410
|
|
414
411
|
|
415
|
-
def make_pixel_bounds_square(bounds: PixelBounds) -> PixelBounds:
|
416
|
-
x_radius = (bounds.x_max - bounds.x_min) // 2
|
417
|
-
y_radius = (bounds.y_max - bounds.y_min) // 2
|
418
|
-
x_center = (bounds.x_max + bounds.x_min) // 2
|
419
|
-
y_center = (bounds.y_max + bounds.y_min) // 2
|
420
|
-
|
421
|
-
radius = max(x_radius, y_radius)
|
422
|
-
|
423
|
-
return PixelBounds(
|
424
|
-
x_min=x_center - radius,
|
425
|
-
y_min=y_center - radius,
|
426
|
-
x_max=x_center + radius,
|
427
|
-
y_max=y_center + radius,
|
428
|
-
)
|
429
|
-
|
430
|
-
|
431
|
-
def make_tile_bounds_square(bounds: TileBounds) -> TileBounds:
|
432
|
-
x_radius = (bounds.x_tile_max - bounds.x_tile_min) / 2
|
433
|
-
y_radius = (bounds.y_tile_max - bounds.y_tile_min) / 2
|
434
|
-
x_center = (bounds.x_tile_max + bounds.x_tile_min) / 2
|
435
|
-
y_center = (bounds.y_tile_max + bounds.y_tile_min) / 2
|
436
|
-
|
437
|
-
radius = max(x_radius, y_radius)
|
438
|
-
|
439
|
-
return TileBounds(
|
440
|
-
zoom=bounds.zoom,
|
441
|
-
x_tile_min=int(x_center - radius),
|
442
|
-
y_tile_min=int(y_center - radius),
|
443
|
-
x_tile_max=int(np.ceil(x_center + radius)),
|
444
|
-
y_tile_max=int(np.ceil(y_center + radius)),
|
445
|
-
)
|
446
|
-
|
447
|
-
|
448
|
-
def get_crop_mask(geo_bounds: GeoBounds, tile_bounds: TileBounds) -> PixelBounds:
|
449
|
-
min_x, min_y = compute_tile_float(
|
450
|
-
geo_bounds.lat_max, geo_bounds.lon_min, tile_bounds.zoom
|
451
|
-
)
|
452
|
-
max_x, max_y = compute_tile_float(
|
453
|
-
geo_bounds.lat_min, geo_bounds.lon_max, tile_bounds.zoom
|
454
|
-
)
|
455
|
-
|
456
|
-
crop_mask = PixelBounds(
|
457
|
-
int((min_x - tile_bounds.x_tile_min) * OSM_TILE_SIZE),
|
458
|
-
int((max_x - tile_bounds.x_tile_min) * OSM_TILE_SIZE),
|
459
|
-
int((min_y - tile_bounds.y_tile_min) * OSM_TILE_SIZE),
|
460
|
-
int((max_y - tile_bounds.y_tile_min) * OSM_TILE_SIZE),
|
461
|
-
)
|
462
|
-
crop_mask = make_pixel_bounds_square(crop_mask)
|
463
|
-
|
464
|
-
return crop_mask
|
465
|
-
|
466
|
-
|
467
|
-
def pixels_in_bounds(bounds: PixelBounds) -> int:
|
468
|
-
return (bounds.x_max - bounds.x_min) * (bounds.y_max - bounds.y_min)
|
469
|
-
|
470
|
-
|
471
412
|
def make_sharepic_base(time_series_list: list[pd.DataFrame], config: Config):
|
472
413
|
all_time_series = pd.concat(time_series_list)
|
473
414
|
tile_x = all_time_series["x"]
|
@@ -496,13 +437,11 @@ def make_sharepic_base(time_series_list: list[pd.DataFrame], config: Config):
|
|
496
437
|
(tile_yz.max() + tile_yz.min()) / 2,
|
497
438
|
)
|
498
439
|
|
499
|
-
|
500
|
-
tile_xz_center,
|
501
|
-
zoom,
|
502
|
-
(target_width, target_height),
|
503
|
-
(target_width, target_map_height),
|
504
|
-
config,
|
440
|
+
tile_bounds = tile_bounds_around_center(
|
441
|
+
tile_xz_center, (target_width, target_height - footer_height), zoom
|
505
442
|
)
|
443
|
+
tile_bounds.y2 += footer_height / OSM_TILE_SIZE
|
444
|
+
background = map_image_from_tile_bounds(tile_bounds, config)
|
506
445
|
|
507
446
|
img = Image.fromarray((background * 255).astype("uint8"), "RGB")
|
508
447
|
draw = ImageDraw.Draw(img, mode="RGBA")
|
@@ -9,25 +9,34 @@ import urllib.parse
|
|
9
9
|
from flask import Flask
|
10
10
|
from flask import render_template
|
11
11
|
|
12
|
-
from
|
13
|
-
from ..explorer.tile_visits import TileVisitAccessor
|
14
|
-
from .activity.blueprint import make_activity_blueprint
|
15
|
-
from .calendar.blueprint import make_calendar_blueprint
|
16
|
-
from .eddington.blueprint import make_eddington_blueprint
|
17
|
-
from .entry_controller import EntryController
|
18
|
-
from .equipment.blueprint import make_equipment_blueprint
|
19
|
-
from .explorer.blueprint import make_explorer_blueprint
|
20
|
-
from .heatmap.blueprint import make_heatmap_blueprint
|
21
|
-
from .search.blueprint import make_search_blueprint
|
22
|
-
from .square_planner.blueprint import make_square_planner_blueprint
|
23
|
-
from .summary.blueprint import make_summary_blueprint
|
24
|
-
from .tile.blueprint import make_tile_blueprint
|
25
|
-
from .upload_blueprint import make_upload_blueprint
|
12
|
+
from geo_activity_playground.core.activities import ActivityRepository
|
26
13
|
from geo_activity_playground.core.config import Config
|
27
14
|
from geo_activity_playground.core.config import ConfigAccessor
|
15
|
+
from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
|
16
|
+
from geo_activity_playground.webui.activity.blueprint import make_activity_blueprint
|
17
|
+
from geo_activity_playground.webui.activity.controller import ActivityController
|
28
18
|
from geo_activity_playground.webui.auth.blueprint import make_auth_blueprint
|
29
19
|
from geo_activity_playground.webui.authenticator import Authenticator
|
20
|
+
from geo_activity_playground.webui.calendar.blueprint import make_calendar_blueprint
|
21
|
+
from geo_activity_playground.webui.calendar.controller import CalendarController
|
22
|
+
from geo_activity_playground.webui.eddington.blueprint import make_eddington_blueprint
|
23
|
+
from geo_activity_playground.webui.eddington.controller import EddingtonController
|
24
|
+
from geo_activity_playground.webui.entry_controller import EntryController
|
25
|
+
from geo_activity_playground.webui.equipment.blueprint import make_equipment_blueprint
|
26
|
+
from geo_activity_playground.webui.equipment.controller import EquipmentController
|
27
|
+
from geo_activity_playground.webui.explorer.blueprint import make_explorer_blueprint
|
28
|
+
from geo_activity_playground.webui.explorer.controller import ExplorerController
|
29
|
+
from geo_activity_playground.webui.heatmap.blueprint import make_heatmap_blueprint
|
30
|
+
from geo_activity_playground.webui.search.blueprint import make_search_blueprint
|
30
31
|
from geo_activity_playground.webui.settings.blueprint import make_settings_blueprint
|
32
|
+
from geo_activity_playground.webui.square_planner.blueprint import (
|
33
|
+
make_square_planner_blueprint,
|
34
|
+
)
|
35
|
+
from geo_activity_playground.webui.summary.blueprint import make_summary_blueprint
|
36
|
+
from geo_activity_playground.webui.summary.controller import SummaryController
|
37
|
+
from geo_activity_playground.webui.tile.blueprint import make_tile_blueprint
|
38
|
+
from geo_activity_playground.webui.tile.controller import TileController
|
39
|
+
from geo_activity_playground.webui.upload_blueprint import make_upload_blueprint
|
31
40
|
|
32
41
|
|
33
42
|
def route_start(app: Flask, repository: ActivityRepository, config: Config) -> None:
|
@@ -77,26 +86,35 @@ def web_ui_main(
|
|
77
86
|
|
78
87
|
authenticator = Authenticator(config_accessor())
|
79
88
|
|
89
|
+
config = config_accessor()
|
90
|
+
summary_controller = SummaryController(repository, config)
|
91
|
+
tile_controller = TileController(config)
|
92
|
+
activity_controller = ActivityController(repository, tile_visit_accessor, config)
|
93
|
+
calendar_controller = CalendarController(repository)
|
94
|
+
equipment_controller = EquipmentController(repository, config)
|
95
|
+
eddington_controller = EddingtonController(repository)
|
96
|
+
explorer_controller = ExplorerController(
|
97
|
+
repository, tile_visit_accessor, config_accessor
|
98
|
+
)
|
99
|
+
|
80
100
|
route_start(app, repository, config_accessor())
|
81
101
|
|
82
102
|
app.register_blueprint(make_auth_blueprint(authenticator), url_prefix="/auth")
|
83
|
-
|
84
103
|
app.register_blueprint(
|
85
|
-
make_activity_blueprint(
|
86
|
-
repository, tile_visit_accessor, config_accessor(), authenticator
|
87
|
-
),
|
104
|
+
make_activity_blueprint(activity_controller, repository, authenticator),
|
88
105
|
url_prefix="/activity",
|
89
106
|
)
|
90
|
-
app.register_blueprint(make_calendar_blueprint(repository), url_prefix="/calendar")
|
91
107
|
app.register_blueprint(
|
92
|
-
|
108
|
+
make_calendar_blueprint(calendar_controller), url_prefix="/calendar"
|
109
|
+
)
|
110
|
+
app.register_blueprint(
|
111
|
+
make_eddington_blueprint(eddington_controller), url_prefix="/eddington"
|
93
112
|
)
|
94
113
|
app.register_blueprint(
|
95
|
-
make_equipment_blueprint(
|
114
|
+
make_equipment_blueprint(equipment_controller), url_prefix="/equipment"
|
96
115
|
)
|
97
116
|
app.register_blueprint(
|
98
|
-
make_explorer_blueprint(
|
99
|
-
url_prefix="/explorer",
|
117
|
+
make_explorer_blueprint(explorer_controller), url_prefix="/explorer"
|
100
118
|
)
|
101
119
|
app.register_blueprint(
|
102
120
|
make_heatmap_blueprint(repository, tile_visit_accessor, config_accessor()),
|
@@ -115,10 +133,10 @@ def web_ui_main(
|
|
115
133
|
url_prefix="/search",
|
116
134
|
)
|
117
135
|
app.register_blueprint(
|
118
|
-
make_summary_blueprint(
|
119
|
-
url_prefix="/summary",
|
136
|
+
make_summary_blueprint(summary_controller), url_prefix="/summary"
|
120
137
|
)
|
121
|
-
|
138
|
+
|
139
|
+
app.register_blueprint(make_tile_blueprint(tile_controller), url_prefix="/tile")
|
122
140
|
app.register_blueprint(
|
123
141
|
make_upload_blueprint(
|
124
142
|
repository, tile_visit_accessor, config_accessor(), authenticator
|
@@ -1,15 +1,12 @@
|
|
1
1
|
from flask import Blueprint
|
2
2
|
from flask import render_template
|
3
3
|
|
4
|
-
from
|
5
|
-
from .controller import CalendarController
|
4
|
+
from geo_activity_playground.webui.calendar.controller import CalendarController
|
6
5
|
|
7
6
|
|
8
|
-
def make_calendar_blueprint(
|
7
|
+
def make_calendar_blueprint(calendar_controller: CalendarController) -> Blueprint:
|
9
8
|
blueprint = Blueprint("calendar", __name__, template_folder="templates")
|
10
9
|
|
11
|
-
calendar_controller = CalendarController(repository)
|
12
|
-
|
13
10
|
@blueprint.route("/")
|
14
11
|
def index():
|
15
12
|
return render_template(
|
@@ -1,15 +1,12 @@
|
|
1
1
|
from flask import Blueprint
|
2
2
|
from flask import render_template
|
3
3
|
|
4
|
-
from ...core.activities import ActivityRepository
|
5
4
|
from .controller import EddingtonController
|
6
5
|
|
7
6
|
|
8
|
-
def make_eddington_blueprint(
|
7
|
+
def make_eddington_blueprint(eddington_controller: EddingtonController) -> Blueprint:
|
9
8
|
blueprint = Blueprint("eddington", __name__, template_folder="templates")
|
10
9
|
|
11
|
-
eddington_controller = EddingtonController(repository)
|
12
|
-
|
13
10
|
@blueprint.route("/")
|
14
11
|
def index():
|
15
12
|
return render_template(
|
@@ -1,18 +1,12 @@
|
|
1
1
|
from flask import Blueprint
|
2
2
|
from flask import render_template
|
3
3
|
|
4
|
-
from
|
5
|
-
from .controller import EquipmentController
|
6
|
-
from geo_activity_playground.core.config import Config
|
4
|
+
from geo_activity_playground.webui.equipment.controller import EquipmentController
|
7
5
|
|
8
6
|
|
9
|
-
def make_equipment_blueprint(
|
10
|
-
repository: ActivityRepository, config: Config
|
11
|
-
) -> Blueprint:
|
7
|
+
def make_equipment_blueprint(equipment_controller: EquipmentController) -> Blueprint:
|
12
8
|
blueprint = Blueprint("equipment", __name__, template_folder="templates")
|
13
9
|
|
14
|
-
equipment_controller = EquipmentController(repository, config)
|
15
|
-
|
16
10
|
@blueprint.route("/")
|
17
11
|
def index():
|
18
12
|
return render_template(
|
@@ -4,20 +4,12 @@ from flask import render_template
|
|
4
4
|
from flask import Response
|
5
5
|
from flask import url_for
|
6
6
|
|
7
|
-
from
|
8
|
-
from ...explorer.tile_visits import TileVisitAccessor
|
9
|
-
from .controller import ExplorerController
|
10
|
-
from geo_activity_playground.core.config import ConfigAccessor
|
7
|
+
from geo_activity_playground.webui.explorer.controller import ExplorerController
|
11
8
|
|
12
9
|
|
13
10
|
def make_explorer_blueprint(
|
14
|
-
|
15
|
-
tile_visit_accessor: TileVisitAccessor,
|
16
|
-
config_accessor: ConfigAccessor,
|
11
|
+
explorer_controller: ExplorerController,
|
17
12
|
) -> Blueprint:
|
18
|
-
explorer_controller = ExplorerController(
|
19
|
-
repository, tile_visit_accessor, config_accessor
|
20
|
-
)
|
21
13
|
blueprint = Blueprint("explorer", __name__, template_folder="templates")
|
22
14
|
|
23
15
|
@blueprint.route("/<zoom>")
|
@@ -9,12 +9,12 @@ from PIL import ImageDraw
|
|
9
9
|
|
10
10
|
from geo_activity_playground.core.activities import ActivityRepository
|
11
11
|
from geo_activity_playground.core.config import Config
|
12
|
-
from geo_activity_playground.core.
|
13
|
-
from geo_activity_playground.core.
|
14
|
-
from geo_activity_playground.core.
|
15
|
-
from geo_activity_playground.core.
|
12
|
+
from geo_activity_playground.core.raster_map import convert_to_grayscale
|
13
|
+
from geo_activity_playground.core.raster_map import GeoBounds
|
14
|
+
from geo_activity_playground.core.raster_map import get_sensible_zoom_level
|
15
|
+
from geo_activity_playground.core.raster_map import get_tile
|
16
|
+
from geo_activity_playground.core.raster_map import PixelBounds
|
16
17
|
from geo_activity_playground.core.tasks import work_tracker
|
17
|
-
from geo_activity_playground.core.tiles import get_tile
|
18
18
|
from geo_activity_playground.core.tiles import get_tile_upper_left_lat_lon
|
19
19
|
from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
|
20
20
|
from geo_activity_playground.webui.explorer.controller import (
|
@@ -117,9 +117,7 @@ class HeatmapController:
|
|
117
117
|
time_series = self._repository.get_time_series(activity_id)
|
118
118
|
for _, group in time_series.groupby("segment_id"):
|
119
119
|
xy_pixels = (
|
120
|
-
np.array(
|
121
|
-
[group["x"] * 2**z - x, group["y"] * 2**z - y]
|
122
|
-
).T
|
120
|
+
np.array([group["x"] * 2**z - x, group["y"] * 2**z - y]).T
|
123
121
|
* OSM_TILE_SIZE
|
124
122
|
)
|
125
123
|
im = Image.new("L", tile_pixels)
|
@@ -171,8 +169,8 @@ class HeatmapController:
|
|
171
169
|
pixel_bounds = PixelBounds.from_tile_bounds(tile_bounds)
|
172
170
|
|
173
171
|
background = np.zeros((*pixel_bounds.shape, 3))
|
174
|
-
for x in range(tile_bounds.
|
175
|
-
for y in range(tile_bounds.
|
172
|
+
for x in range(tile_bounds.x1, tile_bounds.x2):
|
173
|
+
for y in range(tile_bounds.y1, tile_bounds.y2):
|
176
174
|
tile = (
|
177
175
|
np.array(
|
178
176
|
get_tile(tile_bounds.zoom, x, y, self._config.map_tile_url)
|
@@ -180,8 +178,8 @@ class HeatmapController:
|
|
180
178
|
/ 255
|
181
179
|
)
|
182
180
|
|
183
|
-
i = y - tile_bounds.
|
184
|
-
j = x - tile_bounds.
|
181
|
+
i = y - tile_bounds.y1
|
182
|
+
j = x - tile_bounds.x1
|
185
183
|
|
186
184
|
background[
|
187
185
|
i * OSM_TILE_SIZE : (i + 1) * OSM_TILE_SIZE,
|
@@ -1,13 +1,10 @@
|
|
1
1
|
from flask import Blueprint
|
2
2
|
from flask import render_template
|
3
3
|
|
4
|
-
from ...core.activities import ActivityRepository
|
5
4
|
from .controller import SummaryController
|
6
|
-
from geo_activity_playground.core.config import Config
|
7
5
|
|
8
6
|
|
9
|
-
def make_summary_blueprint(
|
10
|
-
summary_controller = SummaryController(repository, config)
|
7
|
+
def make_summary_blueprint(summary_controller: SummaryController) -> Blueprint:
|
11
8
|
blueprint = Blueprint("summary", __name__, template_folder="templates")
|
12
9
|
|
13
10
|
@blueprint.route("/")
|
@@ -2,14 +2,11 @@ from flask import Blueprint
|
|
2
2
|
from flask import Response
|
3
3
|
|
4
4
|
from .controller import TileController
|
5
|
-
from geo_activity_playground.core.config import Config
|
6
5
|
|
7
6
|
|
8
|
-
def make_tile_blueprint(
|
7
|
+
def make_tile_blueprint(tile_controller: TileController) -> Blueprint:
|
9
8
|
blueprint = Blueprint("tiles", __name__, template_folder="templates")
|
10
9
|
|
11
|
-
tile_controller = TileController(config)
|
12
|
-
|
13
10
|
@blueprint.route("/color/<z>/<x>/<y>.png")
|
14
11
|
def tile_color(x: str, y: str, z: str):
|
15
12
|
return Response(
|
{geo_activity_playground-0.35.0.dist-info → geo_activity_playground-0.35.1.dist-info}/RECORD
RENAMED
@@ -1,26 +1,27 @@
|
|
1
1
|
geo_activity_playground/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
geo_activity_playground/__main__.py,sha256=
|
2
|
+
geo_activity_playground/__main__.py,sha256=vFKB0Jjk8ImDHqok6Fddb5CRWHEnxyn5r4Zs0A1EsCQ,4585
|
3
3
|
geo_activity_playground/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
geo_activity_playground/core/activities.py,sha256=MiQev1L7NnSVnxgbArlT-FpWTESTwnKh7pyT1tcEHqU,6926
|
5
5
|
geo_activity_playground/core/config.py,sha256=uqiwk7CgcuGx8JemHSsRKjRwyNT1YTb7V0gX0OJhfaI,5109
|
6
6
|
geo_activity_playground/core/coordinates.py,sha256=tDfr9mlXhK6E_MMIJ0vYWVCoH0Lq8uyuaqUgaa8i0jg,966
|
7
7
|
geo_activity_playground/core/enrichment.py,sha256=fUmk6avy_rqePlHmJQFTQhAxjgIRaxxmq18N2OSXBBg,7771
|
8
8
|
geo_activity_playground/core/heart_rate.py,sha256=IwMt58TpjOYqpAxtsj07zP2ttpN_J3GZeiv-qGhYyJc,1598
|
9
|
-
geo_activity_playground/core/heatmap.py,sha256=iTxefUTjTToPrKpVbauJHXkqxpNppXOEK6vvKuNkHkk,5906
|
10
9
|
geo_activity_playground/core/paths.py,sha256=RBeUi38riP_msTGPy1TsPRNiblzE-lFivaJSLULE8b0,2503
|
11
10
|
geo_activity_playground/core/privacy_zones.py,sha256=4TumHsVUN1uW6RG3ArqTXDykPVipF98DCxVBe7YNdO8,512
|
11
|
+
geo_activity_playground/core/raster_map.py,sha256=yoq5s883BEA2v5C6EYVynkOwynJMka9d2rmoa-W1uIE,7178
|
12
12
|
geo_activity_playground/core/similarity.py,sha256=Jo8jRViuORCxdIGvyaflgsQhwu9S_jn10a450FRL18A,3159
|
13
13
|
geo_activity_playground/core/tasks.py,sha256=aMDBWJqp6ek2ao6G6Xa8GOSZbcQqXoWL74SGRowRPIk,2942
|
14
14
|
geo_activity_playground/core/test_tiles.py,sha256=zce1FxNfsSpOQt66jMehdQRVoNdl-oiFydx6iVBHZXM,764
|
15
15
|
geo_activity_playground/core/test_time_conversion.py,sha256=Sh6nZA3uCTOdZTZa3yOijtR0m74QtZu2mcWXsDNnyQI,984
|
16
|
-
geo_activity_playground/core/tiles.py,sha256=
|
16
|
+
geo_activity_playground/core/tiles.py,sha256=lV6X1Uc9XQecu2LALIvxpnMcLsVtWx7JczJ5a_S1eZE,2139
|
17
17
|
geo_activity_playground/core/time_conversion.py,sha256=x5mXG6Y4GtdX7CBmwucGNSWBp9JQJDbZ7u0JkdUY1Vs,379
|
18
18
|
geo_activity_playground/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
geo_activity_playground/explorer/grid_file.py,sha256=k6j6KBEk2a2BY-onE8SV5TJsERGGyOrlY4as__meWpA,3304
|
20
20
|
geo_activity_playground/explorer/tile_visits.py,sha256=CSHAjgzKWe1iB-zvaqgsR5Z_lFycpWqUfxnPCAWvYaU,14173
|
21
|
-
geo_activity_playground/explorer/video.py,sha256=
|
21
|
+
geo_activity_playground/explorer/video.py,sha256=7j6Qv3HG6On7Tn7xh7Olwrx_fbQnfzS7CeRg3TEApHg,4397
|
22
|
+
geo_activity_playground/heatmap_video.py,sha256=Oc3EAAsW27zhG28Rmy3i2GoN1rjm1UFgb53eSHr9GP8,3503
|
22
23
|
geo_activity_playground/importers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
-
geo_activity_playground/importers/activity_parsers.py,sha256=
|
24
|
+
geo_activity_playground/importers/activity_parsers.py,sha256=5MvtjmXruPAoUO1KlDycoj677NeETgODiyxsFjXNppg,11103
|
24
25
|
geo_activity_playground/importers/csv_parser.py,sha256=O1pP5GLhWhnWcy2Lsrr9g17Zspuibpt-GtZ3ZS5eZF4,2143
|
25
26
|
geo_activity_playground/importers/directory.py,sha256=CA-vFOMm8G4MSM_Q09OwQKduCApL2PWaxLTVxgw_xpw,5908
|
26
27
|
geo_activity_playground/importers/strava_api.py,sha256=cJCZsLemhOlxTtZh0z_npidgae9SD5HyEUry2uvem_A,7775
|
@@ -30,38 +31,38 @@ geo_activity_playground/importers/test_directory.py,sha256=ljXokx7q0OgtHvEdHftcQ
|
|
30
31
|
geo_activity_playground/importers/test_strava_api.py,sha256=4vX7wDr1a9aRh8myxNrIq6RwDBbP8ZeoXXPc10CAbW4,431
|
31
32
|
geo_activity_playground/webui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
33
|
geo_activity_playground/webui/activity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
-
geo_activity_playground/webui/activity/blueprint.py,sha256=
|
34
|
-
geo_activity_playground/webui/activity/controller.py,sha256=
|
34
|
+
geo_activity_playground/webui/activity/blueprint.py,sha256=J2f6zzBtwkrem51WDakLXKahVcOxMT2JKbbFgu0VFws,3914
|
35
|
+
geo_activity_playground/webui/activity/controller.py,sha256=Gps0qGa3vd3xvBW4EIpwoQxYbkYItFfzlz0tzaj8HCA,19508
|
35
36
|
geo_activity_playground/webui/activity/templates/activity/day.html.j2,sha256=wkYmcnIsMlvE9wgKemYCNU6jwsk5IJvg8pcBA2OMh00,2795
|
36
37
|
geo_activity_playground/webui/activity/templates/activity/edit.html.j2,sha256=ckcTTxcQOhmvvAGNTEOtWCUG4LhvO4HfQImlIa5qKs8,1530
|
37
38
|
geo_activity_playground/webui/activity/templates/activity/lines.html.j2,sha256=_ZDg1ruW-9UMJfOudy1-uY_-IcSSaagq7tPCih5Bb8g,1079
|
38
39
|
geo_activity_playground/webui/activity/templates/activity/name.html.j2,sha256=tKviMqMouHEGv3xBQVIsJgwj_hjwAsmGVefM3UMqlYg,2437
|
39
40
|
geo_activity_playground/webui/activity/templates/activity/show.html.j2,sha256=bEx37UGSTeeJl7gN4fjyOpINFQwZ5Zm-HOKpLqcJGfs,6905
|
40
|
-
geo_activity_playground/webui/app.py,sha256=
|
41
|
+
geo_activity_playground/webui/app.py,sha256=MXjd5iOYY7q_wGYgMKOj0T2z7cJ9KxpevVGHD5V95ws,6521
|
41
42
|
geo_activity_playground/webui/auth/blueprint.py,sha256=Lx-ZvMnfHLC1CMre1xPQI3k_pCtQoZvgRhtmafULzoE,812
|
42
43
|
geo_activity_playground/webui/auth/templates/auth/index.html.j2,sha256=ILQ5HvTEYc3OrtOAIFt1VrqWorVD70V9DC342znmP70,579
|
43
44
|
geo_activity_playground/webui/authenticator.py,sha256=k278OEVuOfAmTGT4F2X4pqSTwwkK_FA87EIhAeysEqc,1416
|
44
45
|
geo_activity_playground/webui/calendar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
|
-
geo_activity_playground/webui/calendar/blueprint.py,sha256=
|
46
|
+
geo_activity_playground/webui/calendar/blueprint.py,sha256=Kjyx9lkJoM5tL8vNba0Y7HhJuFRVId4F-ONZsXpKyyg,721
|
46
47
|
geo_activity_playground/webui/calendar/controller.py,sha256=QpSAkR2s1sbLSu6P_fNNTccgGglOzEH2PIv1XwKxeVY,2778
|
47
48
|
geo_activity_playground/webui/calendar/templates/calendar/index.html.j2,sha256=xoR6R4cUgTQNRHQv3m3f4Bc-yCjJEsJj5d4_CWlJsRo,1427
|
48
49
|
geo_activity_playground/webui/calendar/templates/calendar/month.html.j2,sha256=sRIiNo_Rp9CHary6e-lnpKJKOuAonoDEBvKMxzbTLQE,1802
|
49
50
|
geo_activity_playground/webui/eddington/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
50
|
-
geo_activity_playground/webui/eddington/blueprint.py,sha256=
|
51
|
+
geo_activity_playground/webui/eddington/blueprint.py,sha256=2VUiH0HXqp4hK86PmcyOtZ2WTlunJqmsArOYRpVLaAc,452
|
51
52
|
geo_activity_playground/webui/eddington/controller.py,sha256=ly7JSkSS79kO4CL_xugB62uRuuWKVqOjbN-pheelv94,2910
|
52
53
|
geo_activity_playground/webui/eddington/templates/eddington/index.html.j2,sha256=XHKeUymQMS5x00PLOVlg-nSRCz_jHB2pvD8QunULWJ4,1839
|
53
54
|
geo_activity_playground/webui/entry_controller.py,sha256=McxbyouKWHJ3a2R9agPazZoG7VHiFO1RvnkBr08dMH8,2168
|
54
55
|
geo_activity_playground/webui/equipment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
-
geo_activity_playground/webui/equipment/blueprint.py,sha256=
|
56
|
+
geo_activity_playground/webui/equipment/blueprint.py,sha256=kJhCSdakke_UhT2RIP-fMoAMaC1oFttRoCPeiAIaB6g,491
|
56
57
|
geo_activity_playground/webui/equipment/controller.py,sha256=lMivui3EBUnkYZf9Lgv1kHZ0c7IxRAza-ox8YOz3ONY,4079
|
57
58
|
geo_activity_playground/webui/equipment/templates/equipment/index.html.j2,sha256=fvRaDbCuiSZ8AzJTpu1dk8FTAGZ2yfsLhprtVYHFZWo,1802
|
58
59
|
geo_activity_playground/webui/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
-
geo_activity_playground/webui/explorer/blueprint.py,sha256=
|
60
|
+
geo_activity_playground/webui/explorer/blueprint.py,sha256=iLmdayavR7B1_l67VvkMGsaYeABSXEe9IwTydK_-Owg,2027
|
60
61
|
geo_activity_playground/webui/explorer/controller.py,sha256=pIzWh0TpLJgKQZlS325-QT7nA1q9ms7fRqQIp24PNfo,11705
|
61
62
|
geo_activity_playground/webui/explorer/templates/explorer/index.html.j2,sha256=3t9ikAF6oMvEaVlS3Kb1tj9ngomIQlatzqPnqVsEDKA,6908
|
62
63
|
geo_activity_playground/webui/heatmap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
63
64
|
geo_activity_playground/webui/heatmap/blueprint.py,sha256=ZEImDIwT3uiDIKapqCU49llvyqG79n7ZEu1GHgoLZqo,1558
|
64
|
-
geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256=
|
65
|
+
geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256=sUTfM-wwQXe4B4tyuQka9s9bfXLVyexaF0yyM8_q2xk,7728
|
65
66
|
geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2,sha256=BMjqbZ-btSFawuNDxgZxOkF5JvhD_p9DOBJ-4-1IKnU,1833
|
66
67
|
geo_activity_playground/webui/plot_util.py,sha256=pTTQoqOCkLVjkgOit7mbry28kMruZIL8amZozSzEpxQ,283
|
67
68
|
geo_activity_playground/webui/search/blueprint.py,sha256=7TDsiqEowMyHNlFImk-hCGso69KOieG4rfJnLRHpRz8,3300
|
@@ -114,19 +115,19 @@ geo_activity_playground/webui/static/vega@5,sha256=5DLHUaY2P0ph2mKSDMfX69E88J2Cl
|
|
114
115
|
geo_activity_playground/webui/static/web-app-manifest-192x192.png,sha256=eEImN6iWfSv-EnSNPL5WbX84PKakse_8VZMBPWWye3o,13582
|
115
116
|
geo_activity_playground/webui/static/web-app-manifest-512x512.png,sha256=vU9oQ4HnQerFDZVzcAT9twj4_Doc6_9v9wVvoRI-f_E,48318
|
116
117
|
geo_activity_playground/webui/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
117
|
-
geo_activity_playground/webui/summary/blueprint.py,sha256=
|
118
|
-
geo_activity_playground/webui/summary/controller.py,sha256=
|
118
|
+
geo_activity_playground/webui/summary/blueprint.py,sha256=7Yc8aQ9zTy8Rd0-yI_jV3cvQi5cwSe_Wn5O1JSvHq0s,416
|
119
|
+
geo_activity_playground/webui/summary/controller.py,sha256=CJZKGaVXJenuoIbypzbH_MwMKBICGr_F4Adu9LMWtPU,9397
|
119
120
|
geo_activity_playground/webui/summary/templates/summary/index.html.j2,sha256=ctOx3Qjx6nRDpUtFf1DlJhK_gtU77Vwx_S6euLz9-W4,5183
|
120
121
|
geo_activity_playground/webui/templates/home.html.j2,sha256=IdCqI_LLcYrpUjjCO-tbXR4s05XYrPOateiJ4idF3bo,2202
|
121
122
|
geo_activity_playground/webui/templates/page.html.j2,sha256=Mt1M0hw1V7sPnQmo6kRO_e49B4to30eDkAf-J35xXas,10624
|
122
123
|
geo_activity_playground/webui/templates/upload/index.html.j2,sha256=I1Ix8tDS3YBdi-HdaNfjkzYXVVCjfUTe5PFTnap1ydc,775
|
123
124
|
geo_activity_playground/webui/templates/upload/reload.html.j2,sha256=YZWX5eDeNyqKJdQAywDBcU8DZBm22rRBbZqFjrFrCvQ,556
|
124
125
|
geo_activity_playground/webui/tile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
125
|
-
geo_activity_playground/webui/tile/blueprint.py,sha256=
|
126
|
-
geo_activity_playground/webui/tile/controller.py,sha256=
|
126
|
+
geo_activity_playground/webui/tile/blueprint.py,sha256=WTkqeOhagnDCquyBtOsZQLzI41arIEdGBe46jnMEkNg,934
|
127
|
+
geo_activity_playground/webui/tile/controller.py,sha256=UY_dVQrjSYCXTkVjgQwQumltPPIUOQfdnN8nJv1m54I,1433
|
127
128
|
geo_activity_playground/webui/upload_blueprint.py,sha256=topLI9ytDUFkqCc9AlOqDkjhABUwnPJ1tX_7XrBPbxc,4412
|
128
|
-
geo_activity_playground-0.35.
|
129
|
-
geo_activity_playground-0.35.
|
130
|
-
geo_activity_playground-0.35.
|
131
|
-
geo_activity_playground-0.35.
|
132
|
-
geo_activity_playground-0.35.
|
129
|
+
geo_activity_playground-0.35.1.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
|
130
|
+
geo_activity_playground-0.35.1.dist-info/METADATA,sha256=tXXmYUBsCfdgXnO_D5nXqvc8xIv237xH0Upkgdw6r_A,1573
|
131
|
+
geo_activity_playground-0.35.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
132
|
+
geo_activity_playground-0.35.1.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
|
133
|
+
geo_activity_playground-0.35.1.dist-info/RECORD,,
|
@@ -1,194 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This code is based on https://github.com/remisalmon/Strava-local-heatmap.
|
3
|
-
"""
|
4
|
-
import dataclasses
|
5
|
-
import logging
|
6
|
-
|
7
|
-
import numpy as np
|
8
|
-
|
9
|
-
from geo_activity_playground.core.config import Config
|
10
|
-
from geo_activity_playground.core.tiles import compute_tile_float
|
11
|
-
from geo_activity_playground.core.tiles import get_tile
|
12
|
-
from geo_activity_playground.core.tiles import get_tile_upper_left_lat_lon
|
13
|
-
|
14
|
-
|
15
|
-
logger = logging.getLogger(__name__)
|
16
|
-
|
17
|
-
|
18
|
-
@dataclasses.dataclass
|
19
|
-
class GeoBounds:
|
20
|
-
lat_min: float
|
21
|
-
lon_min: float
|
22
|
-
lat_max: float
|
23
|
-
lon_max: float
|
24
|
-
|
25
|
-
|
26
|
-
def get_bounds(lat_lon_data: np.ndarray) -> GeoBounds:
|
27
|
-
return GeoBounds(*np.min(lat_lon_data, axis=0), *np.max(lat_lon_data, axis=0))
|
28
|
-
|
29
|
-
|
30
|
-
def add_margin(lower: float, upper: float) -> tuple[float, float]:
|
31
|
-
spread = upper - lower
|
32
|
-
margin = spread / 20
|
33
|
-
return max(0.0, lower - margin), upper + margin
|
34
|
-
|
35
|
-
|
36
|
-
def add_margin_to_geo_bounds(bounds: GeoBounds) -> GeoBounds:
|
37
|
-
lat_min, lat_max = add_margin(bounds.lat_min, bounds.lat_max)
|
38
|
-
lon_min, lon_max = add_margin(bounds.lon_min, bounds.lon_max)
|
39
|
-
return GeoBounds(lat_min, lon_min, lat_max, lon_max)
|
40
|
-
|
41
|
-
|
42
|
-
OSM_TILE_SIZE = 256 # OSM tile size in pixel
|
43
|
-
OSM_MAX_ZOOM = 19 # OSM maximum zoom level
|
44
|
-
MAX_TILE_COUNT = 2000 # maximum number of tiles to download
|
45
|
-
|
46
|
-
|
47
|
-
@dataclasses.dataclass
|
48
|
-
class TileBounds:
|
49
|
-
zoom: int
|
50
|
-
x_tile_min: int
|
51
|
-
x_tile_max: int
|
52
|
-
y_tile_min: int
|
53
|
-
y_tile_max: int
|
54
|
-
|
55
|
-
|
56
|
-
@dataclasses.dataclass
|
57
|
-
class PixelBounds:
|
58
|
-
x_min: int
|
59
|
-
x_max: int
|
60
|
-
y_min: int
|
61
|
-
y_max: int
|
62
|
-
|
63
|
-
@classmethod
|
64
|
-
def from_tile_bounds(cls, tile_bounds: TileBounds) -> "PixelBounds":
|
65
|
-
return cls(
|
66
|
-
int(tile_bounds.x_tile_min) * OSM_TILE_SIZE,
|
67
|
-
int(tile_bounds.x_tile_max) * OSM_TILE_SIZE,
|
68
|
-
int(tile_bounds.y_tile_min) * OSM_TILE_SIZE,
|
69
|
-
int(tile_bounds.y_tile_max) * OSM_TILE_SIZE,
|
70
|
-
)
|
71
|
-
|
72
|
-
@property
|
73
|
-
def shape(self) -> tuple[int, int]:
|
74
|
-
return (
|
75
|
-
self.y_max - self.y_min,
|
76
|
-
self.x_max - self.x_min,
|
77
|
-
)
|
78
|
-
|
79
|
-
|
80
|
-
def geo_bounds_from_tile_bounds(tile_bounds: TileBounds) -> GeoBounds:
|
81
|
-
lat_max, lon_min = get_tile_upper_left_lat_lon(
|
82
|
-
tile_bounds.x_tile_min, tile_bounds.y_tile_min, tile_bounds.zoom
|
83
|
-
)
|
84
|
-
lat_min, lon_max = get_tile_upper_left_lat_lon(
|
85
|
-
tile_bounds.x_tile_max, tile_bounds.y_tile_max, tile_bounds.zoom
|
86
|
-
)
|
87
|
-
return GeoBounds(lat_min, lon_min, lat_max, lon_max)
|
88
|
-
|
89
|
-
|
90
|
-
def get_sensible_zoom_level(
|
91
|
-
bounds: GeoBounds, picture_size: tuple[int, int]
|
92
|
-
) -> TileBounds:
|
93
|
-
zoom = OSM_MAX_ZOOM
|
94
|
-
|
95
|
-
while True:
|
96
|
-
x_tile_min, y_tile_max = map(
|
97
|
-
int, compute_tile_float(bounds.lat_min, bounds.lon_min, zoom)
|
98
|
-
)
|
99
|
-
x_tile_max, y_tile_min = map(
|
100
|
-
int, compute_tile_float(bounds.lat_max, bounds.lon_max, zoom)
|
101
|
-
)
|
102
|
-
|
103
|
-
x_tile_max += 1
|
104
|
-
y_tile_max += 1
|
105
|
-
|
106
|
-
if (x_tile_max - x_tile_min) * OSM_TILE_SIZE <= picture_size[0] and (
|
107
|
-
y_tile_max - y_tile_min
|
108
|
-
) * OSM_TILE_SIZE <= picture_size[1]:
|
109
|
-
break
|
110
|
-
|
111
|
-
zoom -= 1
|
112
|
-
|
113
|
-
tile_count = (x_tile_max - x_tile_min) * (y_tile_max - y_tile_min)
|
114
|
-
|
115
|
-
if tile_count > MAX_TILE_COUNT:
|
116
|
-
raise RuntimeError("Zoom value too high, too many tiles to download")
|
117
|
-
|
118
|
-
return TileBounds(
|
119
|
-
zoom=zoom,
|
120
|
-
x_tile_min=x_tile_min,
|
121
|
-
x_tile_max=x_tile_max,
|
122
|
-
y_tile_min=y_tile_min,
|
123
|
-
y_tile_max=y_tile_max,
|
124
|
-
)
|
125
|
-
|
126
|
-
|
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
|
-
]
|
187
|
-
|
188
|
-
return background
|
189
|
-
|
190
|
-
|
191
|
-
def convert_to_grayscale(image: np.ndarray) -> np.ndarray:
|
192
|
-
image = np.sum(image * [0.2126, 0.7152, 0.0722], axis=2)
|
193
|
-
image = np.dstack((image, image, image))
|
194
|
-
return image
|
{geo_activity_playground-0.35.0.dist-info → geo_activity_playground-0.35.1.dist-info}/LICENSE
RENAMED
File without changes
|
File without changes
|
File without changes
|