geo-activity-playground 1.3.1__py3-none-any.whl → 1.4.0__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 +8 -0
- geo_activity_playground/core/copernicus_dem.py +9 -7
- geo_activity_playground/core/enrichment.py +3 -2
- geo_activity_playground/core/photos.py +39 -0
- geo_activity_playground/core/raster_map.py +6 -0
- geo_activity_playground/explorer/tile_visits.py +64 -5
- geo_activity_playground/importers/strava_checkout.py +11 -2
- geo_activity_playground/webui/app.py +7 -0
- geo_activity_playground/webui/blueprints/activity_blueprint.py +2 -2
- geo_activity_playground/webui/blueprints/explorer_blueprint.py +34 -64
- geo_activity_playground/webui/blueprints/hall_of_fame_blueprint.py +15 -4
- geo_activity_playground/webui/blueprints/heatmap_blueprint.py +3 -10
- geo_activity_playground/webui/blueprints/photo_blueprint.py +1 -26
- geo_activity_playground/webui/static/leaflet/images/layers-2x.png +0 -0
- geo_activity_playground/webui/static/leaflet/images/layers.png +0 -0
- geo_activity_playground/webui/static/server-side-explorer.js +49 -13
- geo_activity_playground/webui/templates/explorer/server-side.html.j2 +0 -8
- geo_activity_playground/webui/templates/heatmap/index.html.j2 +6 -0
- geo_activity_playground/webui/templates/page.html.j2 +4 -1
- {geo_activity_playground-1.3.1.dist-info → geo_activity_playground-1.4.0.dist-info}/METADATA +3 -8
- {geo_activity_playground-1.3.1.dist-info → geo_activity_playground-1.4.0.dist-info}/RECORD +24 -21
- {geo_activity_playground-1.3.1.dist-info → geo_activity_playground-1.4.0.dist-info}/LICENSE +0 -0
- {geo_activity_playground-1.3.1.dist-info → geo_activity_playground-1.4.0.dist-info}/WHEEL +0 -0
- {geo_activity_playground-1.3.1.dist-info → geo_activity_playground-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -4,6 +4,7 @@ import pathlib
|
|
4
4
|
|
5
5
|
import coloredlogs
|
6
6
|
|
7
|
+
from .core.photos import main_inspect_photo
|
7
8
|
from .explorer.video import explorer_video_main
|
8
9
|
from .heatmap_video import main_heatmap_video
|
9
10
|
from .importers.strava_checkout import convert_strava_checkout
|
@@ -73,6 +74,13 @@ def main() -> None:
|
|
73
74
|
subparser.add_argument("--video-height", type=int, default=1080)
|
74
75
|
subparser.set_defaults(func=main_heatmap_video)
|
75
76
|
|
77
|
+
subparser = subparsers.add_parser(
|
78
|
+
"inspect-photo",
|
79
|
+
help="Extract EXIF data from the image to see how it would be imported",
|
80
|
+
)
|
81
|
+
subparser.add_argument("path", type=pathlib.Path)
|
82
|
+
subparser.set_defaults(func=main_inspect_photo)
|
83
|
+
|
76
84
|
options = parser.parse_args()
|
77
85
|
coloredlogs.install(
|
78
86
|
fmt="%(asctime)s %(name)s %(levelname)s %(message)s",
|
@@ -13,7 +13,7 @@ from scipy.interpolate import RegularGridInterpolator
|
|
13
13
|
from .paths import USER_CACHE_DIR
|
14
14
|
|
15
15
|
|
16
|
-
def
|
16
|
+
def _s3_path(lat: int, lon: int) -> pathlib.Path:
|
17
17
|
lat_str = f"N{(lat):02d}" if lat >= 0 else f"S{(-lat):02d}"
|
18
18
|
lon_str = f"E{(lon):03d}" if lon >= 0 else f"W{(-lon):03d}"
|
19
19
|
result = (
|
@@ -26,7 +26,7 @@ def s3_path(lat: int, lon: int) -> pathlib.Path:
|
|
26
26
|
return result
|
27
27
|
|
28
28
|
|
29
|
-
def
|
29
|
+
def _ensure_copernicus_file(p: pathlib.Path) -> None:
|
30
30
|
if p.exists():
|
31
31
|
return
|
32
32
|
s3 = boto3.client(
|
@@ -39,10 +39,12 @@ def ensure_copernicus_file(p: pathlib.Path) -> None:
|
|
39
39
|
|
40
40
|
|
41
41
|
@functools.lru_cache(9)
|
42
|
-
def
|
43
|
-
|
42
|
+
def _get_elevation_arrays(p: pathlib.Path) -> Optional[np.ndarray]:
|
43
|
+
|
44
|
+
_ensure_copernicus_file(p)
|
44
45
|
if not p.exists():
|
45
46
|
return None
|
47
|
+
|
46
48
|
gt = geotiff.GeoTiff(p)
|
47
49
|
a = np.array(gt.read())
|
48
50
|
lon_array, lat_array = gt.get_coord_arrays()
|
@@ -50,8 +52,8 @@ def get_elevation_arrays(p: pathlib.Path) -> Optional[np.ndarray]:
|
|
50
52
|
|
51
53
|
|
52
54
|
@functools.lru_cache(1)
|
53
|
-
def
|
54
|
-
arrays =
|
55
|
+
def _get_interpolator(lat: int, lon: int) -> Optional[RegularGridInterpolator]:
|
56
|
+
arrays = _get_elevation_arrays(_s3_path(lat, lon))
|
55
57
|
# If we don't have data for the current center, we cannot do anything.
|
56
58
|
if arrays is None:
|
57
59
|
return None
|
@@ -88,7 +90,7 @@ def get_interpolator(lat: int, lon: int) -> Optional[RegularGridInterpolator]:
|
|
88
90
|
|
89
91
|
|
90
92
|
def get_elevation(lat: float, lon: float) -> float:
|
91
|
-
interpolator =
|
93
|
+
interpolator = _get_interpolator(math.floor(lat), math.floor(lon))
|
92
94
|
if interpolator is not None:
|
93
95
|
return float(interpolator((lat, lon)))
|
94
96
|
else:
|
@@ -9,7 +9,6 @@ import pandas as pd
|
|
9
9
|
|
10
10
|
from .config import Config
|
11
11
|
from .coordinates import get_distance
|
12
|
-
from .copernicus_dem import get_elevation
|
13
12
|
from .datamodel import Activity
|
14
13
|
from .datamodel import DB
|
15
14
|
from .missing_values import some
|
@@ -109,6 +108,8 @@ def enrichment_compute_tile_xy(
|
|
109
108
|
def enrichment_copernicus_elevation(
|
110
109
|
activity: Activity, time_series: pd.DataFrame, config: Config
|
111
110
|
) -> bool:
|
111
|
+
from .copernicus_dem import get_elevation
|
112
|
+
|
112
113
|
if "copernicus_elevation" not in time_series.columns:
|
113
114
|
time_series["copernicus_elevation"] = [
|
114
115
|
get_elevation(lat, lon)
|
@@ -247,7 +248,7 @@ enrichments: list[Callable[[Activity, pd.DataFrame, Config], bool]] = [
|
|
247
248
|
enrichment_normalize_time,
|
248
249
|
enrichment_rename_altitude,
|
249
250
|
enrichment_compute_tile_xy,
|
250
|
-
enrichment_copernicus_elevation,
|
251
|
+
# enrichment_copernicus_elevation,
|
251
252
|
enrichment_elevation_gain,
|
252
253
|
enrichment_add_calories,
|
253
254
|
enrichment_distance,
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import argparse
|
2
|
+
import datetime
|
3
|
+
import pathlib
|
4
|
+
import pprint
|
5
|
+
import zoneinfo
|
6
|
+
|
7
|
+
import dateutil.parser
|
8
|
+
import exifread
|
9
|
+
|
10
|
+
|
11
|
+
def ratio_to_decimal(numbers: list[exifread.utils.Ratio]) -> float:
|
12
|
+
deg, min, sec = numbers.values
|
13
|
+
return deg.decimal() + min.decimal() / 60 + sec.decimal() / 3600
|
14
|
+
|
15
|
+
|
16
|
+
def get_metadata_from_image(path: pathlib.Path) -> dict:
|
17
|
+
with open(path, "rb") as f:
|
18
|
+
tags = exifread.process_file(f)
|
19
|
+
metadata = {}
|
20
|
+
try:
|
21
|
+
metadata["latitude"] = ratio_to_decimal(tags["GPS GPSLatitude"])
|
22
|
+
metadata["longitude"] = ratio_to_decimal(tags["GPS GPSLongitude"])
|
23
|
+
except KeyError:
|
24
|
+
pass
|
25
|
+
try:
|
26
|
+
metadata["time"] = dateutil.parser.parse(
|
27
|
+
str(tags["EXIF DateTimeOriginal"])
|
28
|
+
+ str(tags.get("EXIF OffsetTime", "+00:00"))
|
29
|
+
).astimezone(zoneinfo.ZoneInfo("UTC"))
|
30
|
+
except KeyError:
|
31
|
+
pass
|
32
|
+
|
33
|
+
return metadata
|
34
|
+
|
35
|
+
|
36
|
+
def main_inspect_photo(options: argparse.Namespace) -> None:
|
37
|
+
path: pathlib.Path = options.path
|
38
|
+
metadata = get_metadata_from_image(path)
|
39
|
+
pprint.pprint(metadata)
|
@@ -283,3 +283,9 @@ class PastelImageTransform(ImageTransform):
|
|
283
283
|
averaged_tile = np.sum(image * [0.2126, 0.7152, 0.0722], axis=2)
|
284
284
|
grayscale_tile = np.dstack((averaged_tile, averaged_tile, averaged_tile))
|
285
285
|
return self._factor * grayscale_tile + (1 - self._factor) * image
|
286
|
+
|
287
|
+
|
288
|
+
class InverseGrayscaleImageTransform(ImageTransform):
|
289
|
+
def transform_image(self, image: np.ndarray) -> np.ndarray:
|
290
|
+
image = np.sum(image * [0.2126, 0.7152, 0.0722], axis=2) # to grayscale
|
291
|
+
return 1 - np.dstack((image, image, image)) # to rgb
|
@@ -13,6 +13,8 @@ from tqdm import tqdm
|
|
13
13
|
|
14
14
|
from ..core.activities import ActivityRepository
|
15
15
|
from ..core.config import Config
|
16
|
+
from ..core.datamodel import Activity
|
17
|
+
from ..core.datamodel import DB
|
16
18
|
from ..core.paths import atomic_open
|
17
19
|
from ..core.tasks import try_load_pickle
|
18
20
|
from ..core.tasks import work_tracker_path
|
@@ -20,10 +22,41 @@ from ..core.tasks import WorkTracker
|
|
20
22
|
from ..core.tiles import adjacent_to
|
21
23
|
from ..core.tiles import interpolate_missing_tile
|
22
24
|
|
25
|
+
# import sqlalchemy as sa
|
26
|
+
|
27
|
+
# from sqlalchemy import Column
|
28
|
+
# from sqlalchemy import ForeignKey
|
29
|
+
# from sqlalchemy import String
|
30
|
+
# from sqlalchemy import Table
|
31
|
+
# from sqlalchemy.orm import DeclarativeBase
|
32
|
+
# from sqlalchemy.orm import Mapped
|
33
|
+
# from sqlalchemy.orm import mapped_column
|
34
|
+
# from sqlalchemy.orm import relationship
|
23
35
|
|
24
36
|
logger = logging.getLogger(__name__)
|
25
37
|
|
26
38
|
|
39
|
+
# class VisitedTile(DB.Model):
|
40
|
+
# __tablename__ = "visited_tiles"
|
41
|
+
|
42
|
+
# zoom: Mapped[int] = mapped_column(primary_key=True)
|
43
|
+
# x: Mapped[int] = mapped_column(primary_key=True)
|
44
|
+
# y: Mapped[int] = mapped_column(primary_key=True)
|
45
|
+
|
46
|
+
# first_activity_id: Mapped[int] = mapped_column(
|
47
|
+
# ForeignKey("Activity.id", name="first_activity_id"), nullable=True
|
48
|
+
# )
|
49
|
+
# first_activity: Mapped[Activity] = relationship(back_populates="activities")
|
50
|
+
# first_visit: Mapped[Optional[datetime.datetime]] = mapped_column(
|
51
|
+
# sa.DateTime, nullable=True
|
52
|
+
# )
|
53
|
+
|
54
|
+
# __table_args__ = (
|
55
|
+
# sa.PrimaryKeyConstraint(zoom, x, y),
|
56
|
+
# {},
|
57
|
+
# )
|
58
|
+
|
59
|
+
|
27
60
|
class TileInfo(TypedDict):
|
28
61
|
activity_ids: set[int]
|
29
62
|
first_time: datetime.datetime
|
@@ -58,7 +91,6 @@ class TileState(TypedDict):
|
|
58
91
|
tile_visits: dict[int, dict[tuple[int, int], TileInfo]]
|
59
92
|
tile_history: dict[int, pd.DataFrame]
|
60
93
|
activities_per_tile: dict[int, dict[tuple[int, int], set[int]]]
|
61
|
-
processed_activities: set[int]
|
62
94
|
evolution_state: dict[int, TileEvolutionState]
|
63
95
|
version: int
|
64
96
|
|
@@ -99,7 +131,6 @@ def make_tile_state() -> TileState:
|
|
99
131
|
"tile_visits": collections.defaultdict(make_defaultdict_dict),
|
100
132
|
"tile_history": collections.defaultdict(pd.DataFrame),
|
101
133
|
"activities_per_tile": collections.defaultdict(make_defaultdict_set),
|
102
|
-
"processed_activities": set(),
|
103
134
|
"evolution_state": collections.defaultdict(TileEvolutionState),
|
104
135
|
"version": TILE_STATE_VERSION,
|
105
136
|
}
|
@@ -122,6 +153,9 @@ def _consistency_check(
|
|
122
153
|
|
123
154
|
for zoom, tile_visits in tile_visit_accessor.tile_state["tile_visits"].items():
|
124
155
|
for tile, meta in tile_visits.items():
|
156
|
+
if not pd.isna(meta["first_time"]) and meta["first_time"].tzinfo is None:
|
157
|
+
logger.info("Tile visits are stored without time zone.")
|
158
|
+
return False
|
125
159
|
if meta["first_id"] not in present_activity_ids:
|
126
160
|
logger.info(f"Activity {meta['first_id']} have been deleted.")
|
127
161
|
return False
|
@@ -143,10 +177,35 @@ def compute_tile_visits_new(
|
|
143
177
|
work_tracker.reset()
|
144
178
|
|
145
179
|
for activity_id in tqdm(
|
146
|
-
work_tracker.filter(repository.get_activity_ids()), desc="Tile visits", delay=
|
180
|
+
work_tracker.filter(repository.get_activity_ids()), desc="Tile visits", delay=1
|
147
181
|
):
|
148
182
|
_process_activity(repository, tile_visit_accessor.tile_state, activity_id)
|
149
183
|
work_tracker.mark_done(activity_id)
|
184
|
+
|
185
|
+
for zoom in reversed(range(20)):
|
186
|
+
tile_state = tile_visit_accessor.tile_state
|
187
|
+
if not (
|
188
|
+
tile_state["tile_history"][zoom]["time"].diff().dropna()
|
189
|
+
>= datetime.timedelta(seconds=0)
|
190
|
+
).all():
|
191
|
+
logger.warning(
|
192
|
+
f"The order of the tile history at {zoom=} is not chronological, resetting."
|
193
|
+
)
|
194
|
+
new_tile_history_soa: dict[str, list] = {
|
195
|
+
"activity_id": [],
|
196
|
+
"time": [],
|
197
|
+
"tile_x": [],
|
198
|
+
"tile_y": [],
|
199
|
+
}
|
200
|
+
for tile, visit in tile_state["tile_visits"][zoom].items():
|
201
|
+
new_tile_history_soa["activity_id"].append(visit["first_id"])
|
202
|
+
new_tile_history_soa["time"].append(visit["first_time"])
|
203
|
+
new_tile_history_soa["tile_x"].append(tile[0])
|
204
|
+
new_tile_history_soa["tile_y"].append(tile[1])
|
205
|
+
tile_state["tile_history"][zoom] = pd.DataFrame(new_tile_history_soa)
|
206
|
+
tile_state["tile_history"][zoom].sort_values("time", inplace=True)
|
207
|
+
# Reset the evolution state.
|
208
|
+
tile_state["evolution_state"] = collections.defaultdict(TileEvolutionState)
|
150
209
|
tile_visit_accessor.save()
|
151
210
|
work_tracker.close()
|
152
211
|
|
@@ -258,7 +317,7 @@ def _compute_cluster_evolution(
|
|
258
317
|
for index, row in tqdm(
|
259
318
|
tiles.iloc[s.cluster_start :].iterrows(),
|
260
319
|
desc=f"Cluster evolution for {zoom=}",
|
261
|
-
delay=
|
320
|
+
delay=1,
|
262
321
|
):
|
263
322
|
new_clusters = False
|
264
323
|
# Current tile.
|
@@ -337,7 +396,7 @@ def _compute_square_history(
|
|
337
396
|
for index, row in tqdm(
|
338
397
|
tiles.iloc[s.square_start :].iterrows(),
|
339
398
|
desc=f"Square evolution for {zoom=}",
|
340
|
-
delay=
|
399
|
+
delay=1,
|
341
400
|
):
|
342
401
|
tile = (row["tile_x"], row["tile_y"])
|
343
402
|
x, y = tile
|
@@ -3,6 +3,7 @@ import logging
|
|
3
3
|
import pathlib
|
4
4
|
import shutil
|
5
5
|
import sys
|
6
|
+
import traceback
|
6
7
|
import urllib.parse
|
7
8
|
import zoneinfo
|
8
9
|
from typing import Optional
|
@@ -22,6 +23,7 @@ from ..core.tasks import get_state
|
|
22
23
|
from ..core.tasks import set_state
|
23
24
|
from ..core.tasks import work_tracker_path
|
24
25
|
from ..core.tasks import WorkTracker
|
26
|
+
from .activity_parsers import ActivityParseError
|
25
27
|
from .activity_parsers import read_activity
|
26
28
|
from .csv_parser import parse_csv
|
27
29
|
|
@@ -182,12 +184,12 @@ def import_from_strava_checkout(config: Config) -> None:
|
|
182
184
|
]
|
183
185
|
|
184
186
|
for activity_id in tqdm(activities_ids_to_parse, desc="Import from Strava export"):
|
185
|
-
work_tracker.mark_done(activity_id)
|
186
187
|
index = all_activity_ids.index(activity_id)
|
187
188
|
row = {column: table[column][index] for column in header}
|
188
189
|
|
189
190
|
# Some manually recorded activities have no file name. Pandas reads that as a float. We skip those.
|
190
191
|
if not row["Filename"]:
|
192
|
+
work_tracker.mark_done(activity_id)
|
191
193
|
continue
|
192
194
|
|
193
195
|
start_datetime = dateutil.parser.parse(
|
@@ -196,7 +198,14 @@ def import_from_strava_checkout(config: Config) -> None:
|
|
196
198
|
|
197
199
|
activity_file = checkout_path / row["Filename"]
|
198
200
|
|
199
|
-
|
201
|
+
try:
|
202
|
+
activity, time_series = read_activity(activity_file)
|
203
|
+
except ActivityParseError as e:
|
204
|
+
logger.error(f"Error while parsing `{activity_file}`:")
|
205
|
+
traceback.print_exc()
|
206
|
+
continue
|
207
|
+
|
208
|
+
work_tracker.mark_done(activity_id)
|
200
209
|
|
201
210
|
if not len(time_series):
|
202
211
|
continue
|
@@ -6,6 +6,7 @@ import os
|
|
6
6
|
import pathlib
|
7
7
|
import secrets
|
8
8
|
import shutil
|
9
|
+
import sys
|
9
10
|
import threading
|
10
11
|
import urllib.parse
|
11
12
|
import uuid
|
@@ -32,6 +33,7 @@ from ..core.heart_rate import HeartRateZoneComputer
|
|
32
33
|
from ..core.paths import TIME_SERIES_DIR
|
33
34
|
from ..core.raster_map import GrayscaleImageTransform
|
34
35
|
from ..core.raster_map import IdentityImageTransform
|
36
|
+
from ..core.raster_map import InverseGrayscaleImageTransform
|
35
37
|
from ..core.raster_map import PastelImageTransform
|
36
38
|
from ..core.raster_map import TileGetter
|
37
39
|
from ..explorer.tile_visits import TileVisitAccessor
|
@@ -85,6 +87,7 @@ def importer_thread(
|
|
85
87
|
) -> None:
|
86
88
|
with app.app_context():
|
87
89
|
scan_for_activities(repository, tile_visit_accessor, config)
|
90
|
+
logger.info("Importer thread is done.")
|
88
91
|
|
89
92
|
|
90
93
|
def web_ui_main(
|
@@ -182,6 +185,7 @@ def web_ui_main(
|
|
182
185
|
"color": IdentityImageTransform(),
|
183
186
|
"grayscale": GrayscaleImageTransform(),
|
184
187
|
"pastel": PastelImageTransform(),
|
188
|
+
"inverse_grayscale": InverseGrayscaleImageTransform(),
|
185
189
|
}
|
186
190
|
flasher = FlaskFlasher()
|
187
191
|
heart_rate_zone_computer = HeartRateZoneComputer(config)
|
@@ -265,6 +269,9 @@ def web_ui_main(
|
|
265
269
|
variables["photo_count"] = DB.session.scalar(
|
266
270
|
sqlalchemy.select(sqlalchemy.func.count()).select_from(Photo)
|
267
271
|
)
|
272
|
+
variables["python_version"] = (
|
273
|
+
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
274
|
+
)
|
268
275
|
return variables
|
269
276
|
|
270
277
|
app.run(host=host, port=port)
|
@@ -177,7 +177,7 @@ def make_activity_blueprint(
|
|
177
177
|
)
|
178
178
|
) is not None:
|
179
179
|
context["heart_zones_plot"] = heart_rate_zone_plot(heart_zones)
|
180
|
-
if "
|
180
|
+
if "elevation" in time_series.columns:
|
181
181
|
context["elevation_time_plot"] = elevation_time_plot(time_series)
|
182
182
|
if "elevation_gain_cum" in time_series.columns:
|
183
183
|
context["elevation_gain_cum_plot"] = elevation_gain_cum_plot(time_series)
|
@@ -508,7 +508,7 @@ def elevation_time_plot(time_series: pd.DataFrame) -> str:
|
|
508
508
|
.encode(
|
509
509
|
alt.X("time", title="Time"),
|
510
510
|
alt.Y(
|
511
|
-
"
|
511
|
+
"elevation",
|
512
512
|
scale=alt.Scale(zero=False),
|
513
513
|
title="Elevation / m",
|
514
514
|
),
|
@@ -26,6 +26,7 @@ from ...core.coordinates import Bounds
|
|
26
26
|
from ...core.datamodel import Activity
|
27
27
|
from ...core.datamodel import DB
|
28
28
|
from ...core.raster_map import ImageTransform
|
29
|
+
from ...core.raster_map import OSM_TILE_SIZE
|
29
30
|
from ...core.raster_map import TileGetter
|
30
31
|
from ...core.tiles import compute_tile
|
31
32
|
from ...core.tiles import get_tile_upper_left_lat_lon
|
@@ -71,13 +72,14 @@ class MaxClusterColorStrategy(ColorStrategy):
|
|
71
72
|
self, tile_xy: tuple[int, int], grayscale: np.ndarray
|
72
73
|
) -> np.ndarray:
|
73
74
|
if tile_xy in self.max_cluster_members:
|
74
|
-
|
75
|
+
color = np.array([[[55, 126, 184, 70]]]) / 256
|
75
76
|
elif tile_xy in self.evolution_state.memberships:
|
76
|
-
|
77
|
+
color = np.array([[[77, 175, 74, 70]]]) / 256
|
77
78
|
elif tile_xy in self.tile_visits:
|
78
|
-
|
79
|
+
color = np.array([[[0, 0, 0, 70]]]) / 256
|
79
80
|
else:
|
80
|
-
|
81
|
+
color = np.array([[[0, 0, 0, 0]]]) / 256
|
82
|
+
return np.broadcast_to(color, grayscale.shape)
|
81
83
|
|
82
84
|
|
83
85
|
class ColorfulClusterColorStrategy(ColorStrategy):
|
@@ -98,11 +100,12 @@ class ColorfulClusterColorStrategy(ColorStrategy):
|
|
98
100
|
m = hashlib.sha256()
|
99
101
|
m.update(str(cluster_id).encode())
|
100
102
|
d = int(m.hexdigest(), base=16) / (256.0**m.digest_size)
|
101
|
-
|
103
|
+
color = np.array([[self._cmap(d)[:3] + (0.5,)]])
|
102
104
|
elif tile_xy in self.tile_visits:
|
103
|
-
|
105
|
+
color = np.array([[[0, 0, 0, 70]]]) / 256
|
104
106
|
else:
|
105
|
-
|
107
|
+
color = np.array([[[0, 0, 0, 0]]]) / 256
|
108
|
+
return np.broadcast_to(color, grayscale.shape)
|
106
109
|
|
107
110
|
|
108
111
|
class VisitTimeColorStrategy(ColorStrategy):
|
@@ -120,11 +123,15 @@ class VisitTimeColorStrategy(ColorStrategy):
|
|
120
123
|
relevant_time = (
|
121
124
|
tile_info["first_time"] if self.use_first else tile_info["last_time"]
|
122
125
|
)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
+
if pd.isna(relevant_time):
|
127
|
+
color = np.array([[[0, 0, 0, 70]]]) / 256
|
128
|
+
else:
|
129
|
+
last_age_days = (today - relevant_time.date()).days
|
130
|
+
color = cmap(max(1 - last_age_days / (2 * 365), 0.0))
|
131
|
+
color = np.array([[color[:3] + (0.5,)]])
|
126
132
|
else:
|
127
|
-
|
133
|
+
color = np.array([[[0, 0, 0, 0]]]) / 256
|
134
|
+
return np.broadcast_to(color, grayscale.shape)
|
128
135
|
|
129
136
|
|
130
137
|
class NumVisitsColorStrategy(ColorStrategy):
|
@@ -138,9 +145,10 @@ class NumVisitsColorStrategy(ColorStrategy):
|
|
138
145
|
cmap = matplotlib.colormaps["viridis"]
|
139
146
|
tile_info = self.tile_visits[tile_xy]
|
140
147
|
color = cmap(min(len(tile_info["activity_ids"]) / 50, 1.0))
|
141
|
-
|
148
|
+
color = np.array([[color[:3] + (0.5,)]])
|
142
149
|
else:
|
143
|
-
|
150
|
+
color = np.array([[[0, 0, 0, 0]]]) / 256
|
151
|
+
return np.broadcast_to(color, grayscale.shape)
|
144
152
|
|
145
153
|
|
146
154
|
def make_explorer_blueprint(
|
@@ -159,7 +167,7 @@ def make_explorer_blueprint(
|
|
159
167
|
config_accessor().explorer_zoom_levels.append(zoom)
|
160
168
|
config_accessor().explorer_zoom_levels.sort()
|
161
169
|
config_accessor.save()
|
162
|
-
compute_tile_evolution(tile_visit_accessor, config_accessor())
|
170
|
+
compute_tile_evolution(tile_visit_accessor.tile_state, config_accessor())
|
163
171
|
flash(f"Enabled {zoom=} for explorer tiles.", category="success")
|
164
172
|
else:
|
165
173
|
flash(f"{zoom=} is not valid, must be between 0 and 19.", category="danger")
|
@@ -264,10 +272,11 @@ def make_explorer_blueprint(
|
|
264
272
|
tile_visits = tile_visit_accessor.tile_state["tile_visits"][zoom]
|
265
273
|
evolution_state = tile_visit_accessor.tile_state["evolution_state"][zoom]
|
266
274
|
|
267
|
-
map_tile = np.array(tile_getter.get_tile(z, x, y)) / 255
|
268
|
-
grayscale = image_transforms["grayscale"].transform_image(map_tile)
|
275
|
+
# map_tile = np.array(tile_getter.get_tile(z, x, y)) / 255
|
276
|
+
# grayscale = image_transforms["grayscale"].transform_image(map_tile)
|
277
|
+
grayscale = np.zeros((OSM_TILE_SIZE, OSM_TILE_SIZE, 4), dtype=np.float32)
|
269
278
|
square_line_width = 3
|
270
|
-
square_color = np.array([[[228, 26, 28]]]) / 256
|
279
|
+
square_color = np.array([[[228, 26, 28, 255]]]) / 256
|
271
280
|
|
272
281
|
color_strategy_name = request.args.get("color_strategy", "colorful_cluster")
|
273
282
|
if color_strategy_name == "default":
|
@@ -311,9 +320,7 @@ def make_explorer_blueprint(
|
|
311
320
|
<= tile_y
|
312
321
|
< evolution_state.square_y + evolution_state.max_square_size
|
313
322
|
):
|
314
|
-
result[:, 0:square_line_width] =
|
315
|
-
result[:, 0:square_line_width], square_color, 0.5
|
316
|
-
)
|
323
|
+
result[:, 0:square_line_width] = square_color
|
317
324
|
if (
|
318
325
|
y % factor == 0
|
319
326
|
and tile_y == evolution_state.square_y
|
@@ -321,10 +328,7 @@ def make_explorer_blueprint(
|
|
321
328
|
<= tile_x
|
322
329
|
< evolution_state.square_x + evolution_state.max_square_size
|
323
330
|
):
|
324
|
-
result[0:square_line_width, :] =
|
325
|
-
result[0:square_line_width, :], square_color, 0.5
|
326
|
-
)
|
327
|
-
|
331
|
+
result[0:square_line_width, :] = square_color
|
328
332
|
if (
|
329
333
|
(x + 1) % factor == 0
|
330
334
|
and (x + 1) // factor
|
@@ -333,9 +337,7 @@ def make_explorer_blueprint(
|
|
333
337
|
<= tile_y
|
334
338
|
< evolution_state.square_y + evolution_state.max_square_size
|
335
339
|
):
|
336
|
-
result[:, -square_line_width:] =
|
337
|
-
result[:, -square_line_width:], square_color, 0.5
|
338
|
-
)
|
340
|
+
result[:, -square_line_width:] = square_color
|
339
341
|
if (
|
340
342
|
(y + 1) % factor == 0
|
341
343
|
and (y + 1) // factor
|
@@ -344,9 +346,7 @@ def make_explorer_blueprint(
|
|
344
346
|
<= tile_x
|
345
347
|
< evolution_state.square_x + evolution_state.max_square_size
|
346
348
|
):
|
347
|
-
result[-square_line_width:, :] =
|
348
|
-
result[-square_line_width:, :], square_color, 0.5
|
349
|
-
)
|
349
|
+
result[-square_line_width:, :] = square_color
|
350
350
|
else:
|
351
351
|
result = grayscale
|
352
352
|
factor = 2 ** (zoom - z)
|
@@ -381,14 +381,7 @@ def make_explorer_blueprint(
|
|
381
381
|
result[
|
382
382
|
yo * width : (yo + 1) * width,
|
383
383
|
xo * width : xo * width + square_line_width,
|
384
|
-
] =
|
385
|
-
result[
|
386
|
-
yo * width : (yo + 1) * width,
|
387
|
-
xo * width : xo * width + square_line_width,
|
388
|
-
],
|
389
|
-
square_color,
|
390
|
-
0.5,
|
391
|
-
)
|
384
|
+
] = square_color
|
392
385
|
if (
|
393
386
|
tile_y == evolution_state.square_y
|
394
387
|
and evolution_state.square_x
|
@@ -399,14 +392,7 @@ def make_explorer_blueprint(
|
|
399
392
|
result[
|
400
393
|
yo * width : yo * width + square_line_width,
|
401
394
|
xo * width : (xo + 1) * width,
|
402
|
-
] =
|
403
|
-
result[
|
404
|
-
yo * width : yo * width + square_line_width,
|
405
|
-
xo * width : (xo + 1) * width,
|
406
|
-
],
|
407
|
-
square_color,
|
408
|
-
0.5,
|
409
|
-
)
|
395
|
+
] = square_color
|
410
396
|
|
411
397
|
if (
|
412
398
|
tile_x + 1
|
@@ -421,15 +407,7 @@ def make_explorer_blueprint(
|
|
421
407
|
yo * width : (yo + 1) * width,
|
422
408
|
(xo + 1) * width
|
423
409
|
- square_line_width : (xo + 1) * width,
|
424
|
-
] =
|
425
|
-
result[
|
426
|
-
yo * width : (yo + 1) * width,
|
427
|
-
(xo + 1) * width
|
428
|
-
- square_line_width : (xo + 1) * width,
|
429
|
-
],
|
430
|
-
square_color,
|
431
|
-
0.5,
|
432
|
-
)
|
410
|
+
] = square_color
|
433
411
|
|
434
412
|
if (
|
435
413
|
tile_y + 1
|
@@ -444,15 +422,7 @@ def make_explorer_blueprint(
|
|
444
422
|
(yo + 1) * width
|
445
423
|
- square_line_width : (yo + 1) * width,
|
446
424
|
xo * width : (xo + 1) * width,
|
447
|
-
] =
|
448
|
-
result[
|
449
|
-
(yo + 1) * width
|
450
|
-
- square_line_width : (yo + 1) * width,
|
451
|
-
xo * width : (xo + 1) * width,
|
452
|
-
],
|
453
|
-
square_color,
|
454
|
-
0.5,
|
455
|
-
)
|
425
|
+
] = square_color
|
456
426
|
if width >= 64:
|
457
427
|
result[yo * width, :, :] = 0.5
|
458
428
|
result[:, xo * width, :] = 0.5
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import collections
|
2
|
+
import logging
|
2
3
|
|
3
4
|
import pandas as pd
|
4
5
|
from flask import Blueprint
|
@@ -12,6 +13,9 @@ from ..search_util import search_query_from_form
|
|
12
13
|
from ..search_util import SearchQueryHistory
|
13
14
|
|
14
15
|
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
15
19
|
def make_hall_of_fame_blueprint(
|
16
20
|
repository: ActivityRepository,
|
17
21
|
search_query_history: SearchQueryHistory,
|
@@ -73,7 +77,14 @@ def _nominate_activities_inner(
|
|
73
77
|
|
74
78
|
for variable, title, format_str in ratings:
|
75
79
|
if variable in meta.columns and not pd.isna(meta[variable]).all():
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
+
try:
|
81
|
+
i = meta[variable].idxmax()
|
82
|
+
except ValueError as e:
|
83
|
+
print(meta[variable].tolist())
|
84
|
+
print(f"{meta[variable].dtype=}")
|
85
|
+
logger.error(f"Trying to work with {variable=}.")
|
86
|
+
logger.error(f"We got a ValueError: {e}")
|
87
|
+
else:
|
88
|
+
value = meta.loc[i, variable]
|
89
|
+
format_applied = format_str.format(value)
|
90
|
+
nominations[i].append(f"{title}{title_suffix}: {format_applied}")
|
@@ -221,13 +221,6 @@ def _render_tile_image(
|
|
221
221
|
|
222
222
|
cmap = pl.get_cmap(config.color_scheme_for_heatmap)
|
223
223
|
data_color = cmap(tile_counts)
|
224
|
-
data_color[
|
225
|
-
|
226
|
-
|
227
|
-
map_tile = convert_to_grayscale(map_tile)
|
228
|
-
map_tile = 1.0 - map_tile # invert colors
|
229
|
-
for c in range(3):
|
230
|
-
map_tile[:, :, c] = (1.0 - data_color[:, :, c]) * map_tile[
|
231
|
-
:, :, c
|
232
|
-
] + data_color[:, :, c]
|
233
|
-
return map_tile
|
224
|
+
data_color[tile_counts > 0, 3] = 0.8
|
225
|
+
data_color[tile_counts == 0, 3] = 0.0
|
226
|
+
return data_color
|
@@ -2,8 +2,6 @@ import datetime
|
|
2
2
|
import pathlib
|
3
3
|
import uuid
|
4
4
|
|
5
|
-
import dateutil.parser
|
6
|
-
import exifread
|
7
5
|
import geojson
|
8
6
|
import sqlalchemy
|
9
7
|
from flask import Blueprint
|
@@ -21,36 +19,13 @@ from ...core.datamodel import Activity
|
|
21
19
|
from ...core.datamodel import DB
|
22
20
|
from ...core.datamodel import Photo
|
23
21
|
from ...core.paths import PHOTOS_DIR
|
22
|
+
from ...core.photos import get_metadata_from_image
|
24
23
|
from ..authenticator import Authenticator
|
25
24
|
from ..authenticator import needs_authentication
|
26
25
|
from ..flasher import Flasher
|
27
26
|
from ..flasher import FlashTypes
|
28
27
|
|
29
28
|
|
30
|
-
def ratio_to_decimal(numbers: list[exifread.utils.Ratio]) -> float:
|
31
|
-
deg, min, sec = numbers.values
|
32
|
-
return deg.decimal() + min.decimal() / 60 + sec.decimal() / 3600
|
33
|
-
|
34
|
-
|
35
|
-
def get_metadata_from_image(path: pathlib.Path) -> dict:
|
36
|
-
with open(path, "rb") as f:
|
37
|
-
tags = exifread.process_file(f)
|
38
|
-
metadata = {}
|
39
|
-
try:
|
40
|
-
metadata["latitude"] = ratio_to_decimal(tags["GPS GPSLatitude"])
|
41
|
-
metadata["longitude"] = ratio_to_decimal(tags["GPS GPSLongitude"])
|
42
|
-
except KeyError:
|
43
|
-
pass
|
44
|
-
try:
|
45
|
-
metadata["time"] = datetime.datetime.strptime(
|
46
|
-
str(tags["EXIF DateTimeOriginal"]), "%Y:%m:%d %H:%M:%S"
|
47
|
-
)
|
48
|
-
except KeyError:
|
49
|
-
pass
|
50
|
-
|
51
|
-
return metadata
|
52
|
-
|
53
|
-
|
54
29
|
def make_photo_blueprint(
|
55
30
|
config_accessor: ConfigAccessor, authenticator: Authenticator, flasher: Flasher
|
56
31
|
) -> Blueprint:
|
Binary file
|
Binary file
|
@@ -1,23 +1,59 @@
|
|
1
|
+
let map = L.map('explorer-map', {
|
2
|
+
fullscreenControl: true,
|
3
|
+
center: [center_latitude, center_longitude],
|
4
|
+
zoom: 12
|
5
|
+
});
|
1
6
|
|
2
|
-
let
|
7
|
+
let base_maps = {
|
8
|
+
"Grayscale": L.tileLayer("/tile/grayscale/{z}/{x}/{y}.png", {
|
9
|
+
maxZoom: 19,
|
10
|
+
attribution: map_tile_attribution
|
11
|
+
}),
|
12
|
+
"Pastel": L.tileLayer("/tile/pastel/{z}/{x}/{y}.png", {
|
13
|
+
maxZoom: 19,
|
14
|
+
attribution: map_tile_attribution
|
15
|
+
}),
|
16
|
+
"Color": L.tileLayer("/tile/color/{z}/{x}/{y}.png", {
|
17
|
+
maxZoom: 19,
|
18
|
+
attribution: map_tile_attribution
|
19
|
+
}),
|
20
|
+
"Inverse Grayscale": L.tileLayer("/tile/inverse_grayscale/{z}/{x}/{y}.png", {
|
21
|
+
maxZoom: 19,
|
22
|
+
attribution: map_tile_attribution
|
23
|
+
}),
|
24
|
+
}
|
3
25
|
|
4
|
-
|
5
|
-
|
6
|
-
map.removeLayer(tile_layer)
|
7
|
-
}
|
8
|
-
tile_layer = L.tileLayer(`/explorer/${zoom}/tile/{z}/{x}/{y}.png?color_strategy=${method}`, {
|
26
|
+
let overlay_maps = {
|
27
|
+
"Colorful Cluster": L.tileLayer(`/explorer/${zoom}/tile/{z}/{x}/{y}.png?color_strategy=colorful_cluster`, {
|
9
28
|
maxZoom: 19,
|
10
29
|
attribution: map_tile_attribution
|
11
|
-
})
|
30
|
+
}),
|
31
|
+
"Max Cluster": L.tileLayer(`/explorer/${zoom}/tile/{z}/{x}/{y}.png?color_strategy=max_cluster`, {
|
32
|
+
maxZoom: 19,
|
33
|
+
attribution: map_tile_attribution
|
34
|
+
}),
|
35
|
+
"First Visit": L.tileLayer(`/explorer/${zoom}/tile/{z}/{x}/{y}.png?color_strategy=first`, {
|
36
|
+
maxZoom: 19,
|
37
|
+
attribution: map_tile_attribution
|
38
|
+
}),
|
39
|
+
"Last Visit": L.tileLayer(`/explorer/${zoom}/tile/{z}/{x}/{y}.png?color_strategy=last`, {
|
40
|
+
maxZoom: 19,
|
41
|
+
attribution: map_tile_attribution
|
42
|
+
}),
|
43
|
+
"Number of Visits": L.tileLayer(`/explorer/${zoom}/tile/{z}/{x}/{y}.png?color_strategy=visits`, {
|
44
|
+
maxZoom: 19,
|
45
|
+
attribution: map_tile_attribution
|
46
|
+
}),
|
47
|
+
"Heatmap": L.tileLayer("/heatmap/tile/{z}/{x}/{y}.png", {
|
48
|
+
maxZoom: 19,
|
49
|
+
attribution: map_tile_attribution
|
50
|
+
}),
|
12
51
|
}
|
13
52
|
|
14
|
-
|
15
|
-
|
16
|
-
center: [center_latitude, center_longitude],
|
17
|
-
zoom: 12
|
18
|
-
});
|
53
|
+
base_maps['Grayscale'].addTo(map)
|
54
|
+
overlay_maps["Colorful Cluster"].addTo(map)
|
19
55
|
|
20
|
-
|
56
|
+
var layerControl = L.control.layers(base_maps, overlay_maps).addTo(map);
|
21
57
|
|
22
58
|
if (bbox) {
|
23
59
|
map.fitBounds(L.geoJSON(bbox).getBounds())
|
@@ -24,14 +24,6 @@
|
|
24
24
|
</div>
|
25
25
|
</div>
|
26
26
|
|
27
|
-
<div class="btn-group mb-3" role="group">
|
28
|
-
<button type="button" class="btn btn-primary" onclick="changeColor('colorful_cluster')">Colorful Cluster</button>
|
29
|
-
<button type="button" class="btn btn-primary" onclick="changeColor('max_cluster')">Max Cluster</button>
|
30
|
-
<button type="button" class="btn btn-primary" onclick="changeColor('first')">First Visit</button>
|
31
|
-
<button type="button" class="btn btn-primary" onclick="changeColor('last')">Last Visit</button>
|
32
|
-
<button type="button" class="btn btn-primary" onclick="changeColor('visits')">Number of Visits</button>
|
33
|
-
</div>
|
34
|
-
|
35
27
|
<div class="row mb-3">
|
36
28
|
<div class="col">
|
37
29
|
<div id="explorer-map" class="mb-1" style="height: 800px;"></div>
|
@@ -18,6 +18,12 @@
|
|
18
18
|
center: [{{ center.latitude }}, {{ center.longitude }}],
|
19
19
|
zoom: 12
|
20
20
|
});
|
21
|
+
|
22
|
+
L.tileLayer("/tile/inverse_grayscale/{z}/{x}/{y}.png", {
|
23
|
+
maxZoom: 19,
|
24
|
+
attribution: '{{ map_tile_attribution|safe }}'
|
25
|
+
}).addTo(map)
|
26
|
+
|
21
27
|
L.tileLayer('/heatmap/tile/{z}/{x}/{y}.png?{{ extra_args|safe }}', {
|
22
28
|
maxZoom: 19,
|
23
29
|
attribution: '{{ map_tile_attribution|safe }}'
|
@@ -204,9 +204,12 @@
|
|
204
204
|
|
205
205
|
<div class="row border-top py-3 my-4">
|
206
206
|
<ul class="nav col-4">
|
207
|
-
<li class="nav-item
|
207
|
+
<li class="nav-item"><a
|
208
208
|
href="https://github.com/martin-ueding/geo-activity-playground/blob/main/docs/changelog.md"
|
209
209
|
class="nav-link px-2 text-muted" target="_blank">Version {{ version }}</a></li>
|
210
|
+
|
211
|
+
<li class="nav-item"><span class="nav-link px-2 text-muted">Python {{ python_version
|
212
|
+
}}</span></li>
|
210
213
|
</ul>
|
211
214
|
<ul class="nav col-8 justify-content-end">
|
212
215
|
<li class="nav-item"><a href="https://github.com/martin-ueding/geo-activity-playground"
|
{geo_activity_playground-1.3.1.dist-info → geo_activity_playground-1.4.0.dist-info}/METADATA
RENAMED
@@ -1,13 +1,14 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: geo-activity-playground
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: Analysis of geo data activities like rides, runs or hikes.
|
5
5
|
License: MIT
|
6
6
|
Author: Martin Ueding
|
7
7
|
Author-email: mu@martin-ueding.de
|
8
|
-
Requires-Python: >=3.
|
8
|
+
Requires-Python: >=3.10,<3.14
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
11
12
|
Classifier: Programming Language :: Python :: 3.11
|
12
13
|
Classifier: Programming Language :: Python :: 3.12
|
13
14
|
Classifier: Programming Language :: Python :: 3.13
|
@@ -15,7 +16,6 @@ Requires-Dist: Pillow (>=11.0.0,<12.0.0)
|
|
15
16
|
Requires-Dist: alembic (>=1.15.2,<2.0.0)
|
16
17
|
Requires-Dist: altair (>=5.5.0,<6.0.0)
|
17
18
|
Requires-Dist: appdirs (>=1.4.4,<2.0.0)
|
18
|
-
Requires-Dist: boto3 (>=1.38.45,<2.0.0)
|
19
19
|
Requires-Dist: charset-normalizer (>=3.3.2,<4.0.0)
|
20
20
|
Requires-Dist: coloredlogs (>=15.0.1,<16.0.0)
|
21
21
|
Requires-Dist: exifread (>=3.2.0,<4.0.0)
|
@@ -24,24 +24,19 @@ Requires-Dist: flask (>=3.0.0,<4.0.0)
|
|
24
24
|
Requires-Dist: flask-alembic (>=3.1.1,<4.0.0)
|
25
25
|
Requires-Dist: flask-sqlalchemy (>=3.1.1,<4.0.0)
|
26
26
|
Requires-Dist: geojson (>=3.0.1,<4.0.0)
|
27
|
-
Requires-Dist: geotiff (>=0.2.10,<0.3.0)
|
28
27
|
Requires-Dist: gpxpy (>=1.5.0,<2.0.0)
|
29
|
-
Requires-Dist: imagecodecs (>=2025.3.30,<2026.0.0)
|
30
28
|
Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
|
31
29
|
Requires-Dist: matplotlib (>=3.10.1,<4.0.0)
|
32
|
-
Requires-Dist: numcodecs (<0.15.0)
|
33
30
|
Requires-Dist: numpy (>=2.2.3,<3.0.0)
|
34
31
|
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
|
35
32
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
36
33
|
Requires-Dist: pyarrow (>=19.0.1,<20.0.0)
|
37
34
|
Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
|
38
35
|
Requires-Dist: requests (>=2.28.1,<3.0.0)
|
39
|
-
Requires-Dist: scipy (>=1.16.0,<2.0.0)
|
40
36
|
Requires-Dist: shapely (>=2.0.5,<3.0.0)
|
41
37
|
Requires-Dist: sqlalchemy (>=2.0.40,<3.0.0)
|
42
38
|
Requires-Dist: stravalib (>=2.0,<3.0)
|
43
39
|
Requires-Dist: tcxreader (>=0.4.5,<0.5.0)
|
44
|
-
Requires-Dist: tifffile (==2025.5.10)
|
45
40
|
Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
|
46
41
|
Requires-Dist: tqdm (>=4.64.0,<5.0.0)
|
47
42
|
Requires-Dist: vegafusion-python-embed (>=1.4.3,<2.0.0)
|
@@ -1,5 +1,5 @@
|
|
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=y49XBk0PmAiunx2pZvxLjng1C-AMgAB-f_DljtTCb34,3209
|
3
3
|
geo_activity_playground/alembic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
|
4
4
|
geo_activity_playground/alembic/env.py,sha256=46oMzwSROaAsYuYWTd46txFdRLD3adm_SCn01A_ex8Q,2081
|
5
5
|
geo_activity_playground/alembic/script.py.mako,sha256=g1k4U3D8y4PPYRzW3DH7GEu6yN4EiAr62GgCu6cRpBo,524
|
@@ -20,17 +20,18 @@ geo_activity_playground/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
20
20
|
geo_activity_playground/core/activities.py,sha256=apP_-Rg1ub3lh7RARMGXf2BOmJTiahxqpX_soEnYF3E,4681
|
21
21
|
geo_activity_playground/core/config.py,sha256=mmdMQ5iCLNGnAlriT1ETEVS-gM6Aq_9sg22QECHj4n8,5358
|
22
22
|
geo_activity_playground/core/coordinates.py,sha256=rW_OmMRpTUyIsQwrT6mgT9Y6uPGuwqTo5XDDMS7mGuo,1140
|
23
|
-
geo_activity_playground/core/copernicus_dem.py,sha256=
|
23
|
+
geo_activity_playground/core/copernicus_dem.py,sha256=t6Bc9fsyGyx1awdePXvlN-Zc-tiT2eGSJ80SV5B1Z9A,2944
|
24
24
|
geo_activity_playground/core/datamodel.py,sha256=3LdTm7lqykeLM6KYviW9WamgnS61nGNl-NHSEQdwTXY,15765
|
25
|
-
geo_activity_playground/core/enrichment.py,sha256=
|
25
|
+
geo_activity_playground/core/enrichment.py,sha256=pw9VEyDAtdNbjQ1HOPYyXCXT8SLL5i3Cp6KWjKK7puM,8708
|
26
26
|
geo_activity_playground/core/export.py,sha256=ayOmhWL72263oP9NLIZRYCg_Db0GLUFhgNIL_MCrV-E,4435
|
27
27
|
geo_activity_playground/core/heart_rate.py,sha256=-S3WAhS7AOywrw_Lk5jfuo_fu6zvZQ1VtjwEKSycWpU,1542
|
28
28
|
geo_activity_playground/core/meta_search.py,sha256=nyvCuR7v0pd6KjA8W5Kr71bBafRdE_ol7uSFRJs4eAM,6662
|
29
29
|
geo_activity_playground/core/missing_values.py,sha256=HjonaLV0PFMICnuMrbdUNnK9uy_8PBh_RxI5GuEMQK0,250
|
30
30
|
geo_activity_playground/core/parametric_plot.py,sha256=8CKB8dey7EmZtQnl6IOgBhpxkw0UCpQPWeiBw5PqW8k,5737
|
31
31
|
geo_activity_playground/core/paths.py,sha256=qQ4ujaIHmsxTGEWzf-76XS8FclEI2RC5COTUeuLEbDI,2938
|
32
|
+
geo_activity_playground/core/photos.py,sha256=pUnEfVesEKESlew6_KkYAF582ejLupnWTqs_RcvnRsA,1070
|
32
33
|
geo_activity_playground/core/privacy_zones.py,sha256=4TumHsVUN1uW6RG3ArqTXDykPVipF98DCxVBe7YNdO8,512
|
33
|
-
geo_activity_playground/core/raster_map.py,sha256=
|
34
|
+
geo_activity_playground/core/raster_map.py,sha256=y7maiC_bmUwXsULC_XCZ1m8nGgU2jFe47QFB7TpC_V4,8257
|
34
35
|
geo_activity_playground/core/similarity.py,sha256=L2de3DPRdDeDY5AxZwLDcH7FjHWRWklr41VNU06q9kQ,3117
|
35
36
|
geo_activity_playground/core/summary_stats.py,sha256=v5FtWnE1imDF5axI6asVN55wCrlD73oZ6lvqzxsTN2c,1006
|
36
37
|
geo_activity_playground/core/tasks.py,sha256=-_9cxekoHSWzCW4XblNeqrwi2tTqr5AE7_-p8fdqhwc,2886
|
@@ -47,7 +48,7 @@ geo_activity_playground/core/tiles.py,sha256=LBn2V6WAvMxZeXSIQ8ruZL71iyvOXoFZMz7
|
|
47
48
|
geo_activity_playground/core/time_conversion.py,sha256=F-vQ-MdbTOqPTAELplDjT5m7kdaf1RsqBXELfsR5eOU,1329
|
48
49
|
geo_activity_playground/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
50
|
geo_activity_playground/explorer/grid_file.py,sha256=YNL_c4O1-kxaajATJwj4ZLywCL5Hpj9qy2h-F7rk8Yg,3260
|
50
|
-
geo_activity_playground/explorer/tile_visits.py,sha256=
|
51
|
+
geo_activity_playground/explorer/tile_visits.py,sha256=NUzC4jNb_vQExAIALrO2H1MiNs5JJKsOQKGietAcATE,16271
|
51
52
|
geo_activity_playground/explorer/video.py,sha256=7j6Qv3HG6On7Tn7xh7Olwrx_fbQnfzS7CeRg3TEApHg,4397
|
52
53
|
geo_activity_playground/heatmap_video.py,sha256=I8i1uVvbbPUXVtvLAROaLy58nQoUPnuMCZkERWNkQjg,3318
|
53
54
|
geo_activity_playground/importers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -55,26 +56,26 @@ geo_activity_playground/importers/activity_parsers.py,sha256=zWgLkHHd8rjWnKs-COB
|
|
55
56
|
geo_activity_playground/importers/csv_parser.py,sha256=O1pP5GLhWhnWcy2Lsrr9g17Zspuibpt-GtZ3ZS5eZF4,2143
|
56
57
|
geo_activity_playground/importers/directory.py,sha256=ucnB5sPBvXzLdaza2v8GVU75ArfGG4E7d5OXrCgoFq4,3562
|
57
58
|
geo_activity_playground/importers/strava_api.py,sha256=Fiqlc-VeuzsvgDcWt71JoPMri221cMjkeL4SH80gC5s,8426
|
58
|
-
geo_activity_playground/importers/strava_checkout.py,sha256=
|
59
|
+
geo_activity_playground/importers/strava_checkout.py,sha256=Pugtv0nbgfuVzBDC5e5Tfv1jShUYmMcDbpCQp2ULXow,9232
|
59
60
|
geo_activity_playground/importers/test_csv_parser.py,sha256=nOTVTdlzIY0TDcbWp7xNyNaIO6Mkeu55hVziVl22QE4,1092
|
60
61
|
geo_activity_playground/importers/test_directory.py,sha256=_fn_-y98ZyElbG0BRxAmGFdtGobUShPU86SdEOpuv-A,691
|
61
62
|
geo_activity_playground/importers/test_strava_api.py,sha256=7b8bl5Rh2BctCmvTPEhCadxtUOq3mfzuadD6F5XxRio,398
|
62
63
|
geo_activity_playground/webui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
63
|
-
geo_activity_playground/webui/app.py,sha256=
|
64
|
+
geo_activity_playground/webui/app.py,sha256=qsnwE20S-tWBbD_MgkIS8HVsRh6TO6mrwW_gWYHk0Bo,10653
|
64
65
|
geo_activity_playground/webui/authenticator.py,sha256=dhREYOu_TCD_nzFNuSlHIbf5K6TmwKdXtr1wxD8fBcc,1491
|
65
66
|
geo_activity_playground/webui/blueprints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
66
|
-
geo_activity_playground/webui/blueprints/activity_blueprint.py,sha256=
|
67
|
+
geo_activity_playground/webui/blueprints/activity_blueprint.py,sha256=tFy0GpOBhIP8xlmYc9PF4kAng-0MosXMJudVupGz2Yw,26771
|
67
68
|
geo_activity_playground/webui/blueprints/auth_blueprint.py,sha256=iCm3hZphQKR9qFgytOrfnSmr-Og1gHuQ1Djiv2o_bkE,1031
|
68
69
|
geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py,sha256=8R1rUVoyofGhUgesPunys1HoLPYinvhA46BBnMvEn9Q,2880
|
69
70
|
geo_activity_playground/webui/blueprints/calendar_blueprint.py,sha256=SmOu5AfNNoWcJJNduEfPtaPRvr4EZLYAeIDLUK9P1LY,2939
|
70
71
|
geo_activity_playground/webui/blueprints/eddington_blueprints.py,sha256=Ya5GJxfVESwmRlgMTYe9g75g8JHHTAAvYFmSD-3Uz4Q,8987
|
71
72
|
geo_activity_playground/webui/blueprints/entry_views.py,sha256=SDCzpUSb1FAb84tM0SnmrZQvtaTlO-Rqdj94hyIMDSc,2936
|
72
73
|
geo_activity_playground/webui/blueprints/equipment_blueprint.py,sha256=8L_7NZGErvu4jyigi2gg7HN_gegZRdsSFahUH7Dz6Lw,5727
|
73
|
-
geo_activity_playground/webui/blueprints/explorer_blueprint.py,sha256=
|
74
|
+
geo_activity_playground/webui/blueprints/explorer_blueprint.py,sha256=l-rMutpEcde1PaVETQ2gxtY6hgXZz6MHvb1eStxZTsQ,21497
|
74
75
|
geo_activity_playground/webui/blueprints/export_blueprint.py,sha256=C9yFH5gEJs2YtWE-EhcGDEyGwwaLgC1umybgIRi6duE,1036
|
75
|
-
geo_activity_playground/webui/blueprints/hall_of_fame_blueprint.py,sha256=
|
76
|
-
geo_activity_playground/webui/blueprints/heatmap_blueprint.py,sha256=
|
77
|
-
geo_activity_playground/webui/blueprints/photo_blueprint.py,sha256=
|
76
|
+
geo_activity_playground/webui/blueprints/hall_of_fame_blueprint.py,sha256=bOJ6ejDS6rw8-GEGo1Lihn5DS6j0t9e8CbcbRi44Pts,3168
|
77
|
+
geo_activity_playground/webui/blueprints/heatmap_blueprint.py,sha256=5LlYKMeOMIE7c3xGRZ52ld4Jxtdc3GNcb6lvt3v7NVA,8435
|
78
|
+
geo_activity_playground/webui/blueprints/photo_blueprint.py,sha256=eK3JSvOAsiTVDy5wardtqTDGIZ79jmwuXpdIPBI-GjU,7186
|
78
79
|
geo_activity_playground/webui/blueprints/plot_builder_blueprint.py,sha256=nGtYblRTJ0rasJvl_L35cs1Iry4LONPy_9TY4ytXB-Q,3838
|
79
80
|
geo_activity_playground/webui/blueprints/search_blueprint.py,sha256=Sv_KL1Cdai26y51qVfI-5jZLhtElREsEar1dbR_VAC4,2275
|
80
81
|
geo_activity_playground/webui/blueprints/settings_blueprint.py,sha256=cwes3QmRrC_HMP1g-Yc-x2BJycF4jF3StJl75v9acWo,20377
|
@@ -113,11 +114,13 @@ geo_activity_playground/webui/static/leaflet/MarkerCluster.Default.css,sha256=LW
|
|
113
114
|
geo_activity_playground/webui/static/leaflet/MarkerCluster.css,sha256=-bdWuWOXMFkX0v9Cvr3OWClPiYefDQz9GGZP_7xZxdc,886
|
114
115
|
geo_activity_playground/webui/static/leaflet/fullscreen.png,sha256=yDtz-dhjuAoo6q9xc00-_XNTrGwEWrN80pOneFdol4g,299
|
115
116
|
geo_activity_playground/webui/static/leaflet/fullscreen@2x.png,sha256=HVi2guZO6sekf2NggilbzjUTvJDweXpSMBS81fhtnX0,420
|
117
|
+
geo_activity_playground/webui/static/leaflet/images/layers-2x.png,sha256=Bm2sqFDY_77wB68AsG6sABVyje4nnFHzy2xxbffELt8,1259
|
118
|
+
geo_activity_playground/webui/static/leaflet/images/layers.png,sha256=Hbvp0CjikvNvy6j4s6KNXokydU_CIVuaxp5M3s9RB8Y,696
|
116
119
|
geo_activity_playground/webui/static/leaflet/leaflet.css,sha256=p4NxAoJBhIIN-hmNHrzRCf9tD_miZyoHS5obTRR9BMY,14806
|
117
120
|
geo_activity_playground/webui/static/leaflet/leaflet.fullscreen.css,sha256=YTbhDGEH5amI_JfotPMN7IByFpsN9e4tCBnv5oNdvHU,994
|
118
121
|
geo_activity_playground/webui/static/leaflet/leaflet.js,sha256=20nQCchB9co0qIjJZRGuk2_Z9VM-kNiyxNV1lvTlZBo,147552
|
119
122
|
geo_activity_playground/webui/static/leaflet/leaflet.markercluster.js,sha256=WL6HHfYfbFEkZOFdsJQeY7lJG_E5airjvqbznghUzRw,33724
|
120
|
-
geo_activity_playground/webui/static/server-side-explorer.js,sha256=
|
123
|
+
geo_activity_playground/webui/static/server-side-explorer.js,sha256=dmGTTCZ9OchOc18SJM6qE_EgG-6LEZhckpVIsBvCkv8,3262
|
121
124
|
geo_activity_playground/webui/static/table-sort.min.js,sha256=sFeDrgkXTePr2ciJU9_mLh-Z8qtYhPIQMgOZtj0LwBY,8506
|
122
125
|
geo_activity_playground/webui/static/vega/vega-embed@6.js,sha256=EtAqz74-xZ75o33UgiouBOKWG1u7Zxu-Zh0iIXFbmdo,60630
|
123
126
|
geo_activity_playground/webui/static/vega/vega-lite@4.js,sha256=roXmcY9bUF91uB9V-eSEUHEgfwoXe6B1xoDPuIe5ou8,267999
|
@@ -136,12 +139,12 @@ geo_activity_playground/webui/templates/eddington/distance.html.j2,sha256=9cLlIr
|
|
136
139
|
geo_activity_playground/webui/templates/eddington/elevation_gain.html.j2,sha256=h2mI1Uc1-P7rN_SeCVP_uadpQqX09ZpBG3Z6N8QWNLw,4723
|
137
140
|
geo_activity_playground/webui/templates/elevation_eddington/index.html.j2,sha256=WjquRFWaMzIZrvByhRIuhJbSCUW2HTfMck6THQHZI-I,4743
|
138
141
|
geo_activity_playground/webui/templates/equipment/index.html.j2,sha256=6pzSCJACMXA1fKgsO_KrCTvpumAKlelzj5f9dReey14,1742
|
139
|
-
geo_activity_playground/webui/templates/explorer/server-side.html.j2,sha256=
|
142
|
+
geo_activity_playground/webui/templates/explorer/server-side.html.j2,sha256=CrTFVghloFJcN25H18FZ6KQY7QgIqvyCauFzgYG_yCo,2547
|
140
143
|
geo_activity_playground/webui/templates/export/index.html.j2,sha256=vxqpAm9KnT405Qz7q0_td-HZ4mCjcPR4Lp6EnIEWisg,1652
|
141
144
|
geo_activity_playground/webui/templates/hall_of_fame/index.html.j2,sha256=P15fVPjXf0Wf6K_hd_lCMuw6-Q8_qfNqsBOWNpMfoXw,1804
|
142
|
-
geo_activity_playground/webui/templates/heatmap/index.html.j2,sha256=
|
145
|
+
geo_activity_playground/webui/templates/heatmap/index.html.j2,sha256=Q99v4LP5EnuvDYKayL52qujWaIroLsD89ly2cM2YvTI,1420
|
143
146
|
geo_activity_playground/webui/templates/home.html.j2,sha256=L6U44p05IagWERPxJ99cUMZzZCyhmA5IV7PL-QaO_mg,2258
|
144
|
-
geo_activity_playground/webui/templates/page.html.j2,sha256=
|
147
|
+
geo_activity_playground/webui/templates/page.html.j2,sha256=4vtvq7cV5YIXIUsjG44grHMqnjKWUlVy0eBwqW8UIzg,12315
|
145
148
|
geo_activity_playground/webui/templates/photo/map.html.j2,sha256=MWhqt5Q8ExiRhgxndcEnwngOj1qw0E0u4hKuiuY24Gg,1437
|
146
149
|
geo_activity_playground/webui/templates/photo/new.html.j2,sha256=0BO4ZJgJQM1Hlp9SHylEOfthpQlywDc-xFs8K_Spptc,392
|
147
150
|
geo_activity_playground/webui/templates/plot-macros.html.j2,sha256=lzsu8c8fcsVjgpdcmpwCa1e6EPALZtCS9RbvQ-DAtAs,2861
|
@@ -171,8 +174,8 @@ geo_activity_playground/webui/templates/summary/vega-chart.html.j2,sha256=mw8Hti
|
|
171
174
|
geo_activity_playground/webui/templates/time_zone_fixer/index.html.j2,sha256=s9r6BJMXmd7kLSyjkvH4xLi6e01S5bpGRcMgMMJyCAE,1760
|
172
175
|
geo_activity_playground/webui/templates/upload/index.html.j2,sha256=I1Ix8tDS3YBdi-HdaNfjkzYXVVCjfUTe5PFTnap1ydc,775
|
173
176
|
geo_activity_playground/webui/templates/upload/reload.html.j2,sha256=YZWX5eDeNyqKJdQAywDBcU8DZBm22rRBbZqFjrFrCvQ,556
|
174
|
-
geo_activity_playground-1.
|
175
|
-
geo_activity_playground-1.
|
176
|
-
geo_activity_playground-1.
|
177
|
-
geo_activity_playground-1.
|
178
|
-
geo_activity_playground-1.
|
177
|
+
geo_activity_playground-1.4.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
|
178
|
+
geo_activity_playground-1.4.0.dist-info/METADATA,sha256=emNPjWxE-mMtKn0osd1sWZ7QeK_kFYavMpMtftmjhGs,1890
|
179
|
+
geo_activity_playground-1.4.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
180
|
+
geo_activity_playground-1.4.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
|
181
|
+
geo_activity_playground-1.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{geo_activity_playground-1.3.1.dist-info → geo_activity_playground-1.4.0.dist-info}/entry_points.txt
RENAMED
File without changes
|