geo-activity-playground 0.34.2__tar.gz → 0.35.1__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.34.2 → geo_activity_playground-0.35.1}/PKG-INFO +1 -1
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/__main__.py +12 -0
- geo_activity_playground-0.35.1/geo_activity_playground/core/raster_map.py +250 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/tiles.py +6 -50
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/explorer/video.py +1 -1
- geo_activity_playground-0.35.1/geo_activity_playground/heatmap_video.py +93 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/activity_parsers.py +1 -1
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/blueprint.py +3 -10
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/controller.py +10 -71
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/app.py +44 -26
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/calendar/blueprint.py +2 -5
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/eddington/blueprint.py +1 -4
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/equipment/blueprint.py +2 -8
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/explorer/blueprint.py +2 -10
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +8 -6
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/heatmap/heatmap_controller.py +10 -12
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/summary/blueprint.py +1 -4
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/summary/controller.py +0 -1
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/tile/blueprint.py +1 -4
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/tile/controller.py +1 -1
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/pyproject.toml +1 -1
- geo_activity_playground-0.34.2/geo_activity_playground/core/heatmap.py +0 -194
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/LICENSE +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/activities.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/config.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/coordinates.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/enrichment.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/heart_rate.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/paths.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/privacy_zones.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/similarity.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/tasks.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/test_tiles.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/test_time_conversion.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/core/time_conversion.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/explorer/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/explorer/grid_file.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/explorer/tile_visits.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/csv_parser.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/directory.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/strava_api.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/strava_checkout.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/test_csv_parser.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/test_directory.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/importers/test_strava_api.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/templates/activity/name.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/activity/templates/activity/show.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/auth/blueprint.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/auth/templates/auth/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/authenticator.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/calendar/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/calendar/controller.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/eddington/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/eddington/controller.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/eddington/templates/eddington/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/entry_controller.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/equipment/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/equipment/controller.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/equipment/templates/equipment/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/explorer/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/explorer/controller.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/heatmap/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/heatmap/blueprint.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/plot_util.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/search/blueprint.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/search/templates/search/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/blueprint.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/controller.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/segmentation.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/settings/templates/settings/strava.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/square_planner/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/square_planner/blueprint.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/square_planner/controller.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/MarkerCluster.Default.css +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/MarkerCluster.css +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/bootstrap.bundle.min.js +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/bootstrap.min.css +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/favicon.ico +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/favicon.svg +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/fullscreen.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/leaflet.css +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/leaflet.fullscreen.css +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/leaflet.js +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/leaflet.markercluster.js +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/site.webmanifest +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/table-sort.min.js +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/vega-embed@6 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/vega-lite@4 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/vega@5 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/summary/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/summary/templates/summary/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/templates/home.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/templates/page.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/templates/upload/index.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/templates/upload/reload.html.j2 +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/tile/__init__.py +0 -0
- {geo_activity_playground-0.34.2 → geo_activity_playground-0.35.1}/geo_activity_playground/webui/upload_blueprint.py +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")
|