geo-activity-playground 0.31.0__py3-none-any.whl → 0.33.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.
Files changed (29) hide show
  1. geo_activity_playground/core/config.py +5 -1
  2. geo_activity_playground/core/heatmap.py +61 -15
  3. geo_activity_playground/core/tiles.py +8 -5
  4. geo_activity_playground/importers/directory.py +6 -2
  5. geo_activity_playground/webui/activity/controller.py +50 -36
  6. geo_activity_playground/webui/activity/templates/activity/day.html.j2 +1 -1
  7. geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +1 -1
  8. geo_activity_playground/webui/activity/templates/activity/name.html.j2 +3 -2
  9. geo_activity_playground/webui/activity/templates/activity/show.html.j2 +2 -2
  10. geo_activity_playground/webui/app.py +14 -2
  11. geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +20 -44
  12. geo_activity_playground/webui/heatmap/blueprint.py +5 -2
  13. geo_activity_playground/webui/heatmap/heatmap_controller.py +14 -4
  14. geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +1 -1
  15. geo_activity_playground/webui/settings/blueprint.py +44 -33
  16. geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +11 -2
  17. geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +1 -1
  18. geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2 +1 -1
  19. geo_activity_playground/webui/summary/controller.py +9 -15
  20. geo_activity_playground/webui/summary/templates/summary/index.html.j2 +18 -5
  21. geo_activity_playground/webui/templates/home.html.j2 +1 -1
  22. geo_activity_playground/webui/tile/blueprint.py +3 -2
  23. geo_activity_playground/webui/tile/controller.py +7 -3
  24. geo_activity_playground/webui/upload/controller.py +1 -2
  25. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.33.0.dist-info}/METADATA +1 -1
  26. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.33.0.dist-info}/RECORD +29 -29
  27. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.33.0.dist-info}/LICENSE +0 -0
  28. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.33.0.dist-info}/WHEEL +0 -0
  29. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.33.0.dist-info}/entry_points.txt +0 -0
@@ -21,14 +21,16 @@ logger = logging.getLogger(__name__)
21
21
  @dataclasses.dataclass
22
22
  class Config:
23
23
  birth_year: Optional[int] = None
24
- color_scheme_for_counts: str = "viridis"
24
+ color_scheme_for_counts: str = "teals"
25
25
  color_scheme_for_kind: str = "category10"
26
+ color_scheme_for_heatmap: str = "hot"
26
27
  equipment_offsets: dict[str, float] = dataclasses.field(default_factory=dict)
27
28
  explorer_zoom_levels: list[int] = dataclasses.field(
28
29
  default_factory=lambda: [14, 17]
29
30
  )
30
31
  heart_rate_resting: int = 0
31
32
  heart_rate_maximum: Optional[int] = None
33
+ ignore_suffixes: list[str] = dataclasses.field(default_factory=list)
32
34
  kind_renames: dict[str, str] = dataclasses.field(default_factory=dict)
33
35
  kinds_without_achievements: list[str] = dataclasses.field(default_factory=list)
34
36
  metadata_extraction_regexes: list[str] = dataclasses.field(default_factory=list)
@@ -42,6 +44,8 @@ class Config:
42
44
  strava_client_code: Optional[str] = None
43
45
  time_diff_threshold_seconds: Optional[int] = 30
44
46
  upload_password: Optional[str] = None
47
+ map_tile_url: str = "https://tile.openstreetmap.org/{zoom}/{x}/{y}.png"
48
+ map_tile_attribution: str = '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> | <a href="https://www.openstreetmap.org/fixthemap">Correct Map</a>'
45
49
 
46
50
 
47
51
  class ConfigAccessor:
@@ -6,6 +6,7 @@ import logging
6
6
 
7
7
  import numpy as np
8
8
 
9
+ from geo_activity_playground.core.config import Config
9
10
  from geo_activity_playground.core.tiles import compute_tile_float
10
11
  from geo_activity_playground.core.tiles import get_tile
11
12
  from geo_activity_playground.core.tiles import get_tile_upper_left_lat_lon
@@ -123,21 +124,66 @@ def get_sensible_zoom_level(
123
124
  )
124
125
 
125
126
 
126
- def build_map_from_tiles(tile_bounds: TileBounds) -> np.ndarray:
127
- background = np.zeros((*PixelBounds.from_tile_bounds(tile_bounds).shape, 3))
128
-
129
- for x in range(tile_bounds.x_tile_min, tile_bounds.x_tile_max):
130
- for y in range(tile_bounds.y_tile_min, tile_bounds.y_tile_max):
131
- tile = np.array(get_tile(tile_bounds.zoom, x, y)) / 255
132
-
133
- i = y - tile_bounds.y_tile_min
134
- j = x - tile_bounds.x_tile_min
135
-
136
- background[
137
- i * OSM_TILE_SIZE : (i + 1) * OSM_TILE_SIZE,
138
- j * OSM_TILE_SIZE : (j + 1) * OSM_TILE_SIZE,
139
- :,
140
- ] = tile[:, :, :3]
127
+ def build_map_from_tiles_around_center(
128
+ center: tuple[float, float],
129
+ zoom: int,
130
+ target: tuple[int, int],
131
+ inner_target: tuple[int, int],
132
+ config: Config,
133
+ ) -> np.ndarray:
134
+ background = np.zeros((target[1], target[0], 3))
135
+
136
+ # We will work with the center point and have it in terms of tiles `t` and also in terms of pixels `p`. At the start we know that the tile center must be in the middle of the image.
137
+ t = np.array(center)
138
+ p = np.array([inner_target[0] / 2, inner_target[1] / 2])
139
+
140
+ # Shift both such that they are in the top-left corner of an even tile.
141
+ t_offset = np.array([center[0] % 1, center[1] % 1])
142
+ t -= t_offset
143
+ p -= t_offset * OSM_TILE_SIZE
144
+
145
+ # Shift until we have left the image.
146
+ shift = np.ceil(p / OSM_TILE_SIZE)
147
+ p -= shift * OSM_TILE_SIZE
148
+ t -= shift
149
+
150
+ num_tiles = np.ceil(np.array(target) / OSM_TILE_SIZE) + 1
151
+
152
+ for x in range(int(t[0]), int(t[0] + num_tiles[0])):
153
+ for y in range(int(t[1]), int(t[1]) + int(num_tiles[1])):
154
+ source_x_min = 0
155
+ source_y_min = 0
156
+ source_x_max = source_x_min + OSM_TILE_SIZE
157
+ source_y_max = source_y_min + OSM_TILE_SIZE
158
+
159
+ target_x_min = (x - int(t[0])) * OSM_TILE_SIZE + int(p[0])
160
+ target_y_min = (y - int(t[1])) * OSM_TILE_SIZE + int(p[1])
161
+ target_x_max = target_x_min + OSM_TILE_SIZE
162
+ target_y_max = target_y_min + OSM_TILE_SIZE
163
+
164
+ if target_x_min < 0:
165
+ source_x_min -= target_x_min
166
+ target_x_min = 0
167
+ if target_y_min < 0:
168
+ source_y_min -= target_y_min
169
+ target_y_min = 0
170
+ if target_x_max > target[0]:
171
+ a = target_x_max - target[0]
172
+ target_x_max -= a
173
+ source_x_max -= a
174
+ if target_y_max > target[1]:
175
+ a = target_y_max - target[1]
176
+ target_y_max -= a
177
+ source_y_max -= a
178
+
179
+ if source_x_max < 0 or source_y_max < 0:
180
+ continue
181
+
182
+ tile = np.array(get_tile(zoom, x, y, config.map_tile_url)) / 255
183
+
184
+ background[target_y_min:target_y_max, target_x_min:target_x_max] = tile[
185
+ source_y_min:source_y_max, source_x_min:source_x_max, :3
186
+ ]
141
187
 
142
188
  return background
143
189
 
@@ -3,6 +3,7 @@ import logging
3
3
  import math
4
4
  import pathlib
5
5
  import time
6
+ import urllib.parse
6
7
  from typing import Iterator
7
8
  from typing import Optional
8
9
 
@@ -13,8 +14,10 @@ from PIL import Image
13
14
  logger = logging.getLogger(__name__)
14
15
 
15
16
 
16
- def osm_tile_path(x: int, y: int, zoom: int) -> pathlib.Path:
17
- path = pathlib.Path("Open Street Map Tiles") / f"{zoom}/{x}/{y}.png"
17
+ def osm_tile_path(x: int, y: int, zoom: int, url_template: str) -> pathlib.Path:
18
+ base_dir = pathlib.Path("Open Street Map Tiles")
19
+ dir_for_source = base_dir / urllib.parse.quote_plus(url_template)
20
+ path = dir_for_source / f"{zoom}/{x}/{y}.png"
18
21
  path.parent.mkdir(parents=True, exist_ok=True)
19
22
  return path
20
23
 
@@ -62,11 +65,11 @@ def download_file(url: str, destination: pathlib.Path):
62
65
 
63
66
 
64
67
  @functools.lru_cache()
65
- def get_tile(zoom: int, x: int, y: int) -> Image.Image:
66
- destination = osm_tile_path(x, y, zoom)
68
+ def get_tile(zoom: int, x: int, y: int, url_template: str) -> Image.Image:
69
+ destination = osm_tile_path(x, y, zoom, url_template)
67
70
  if not destination.exists():
68
71
  logger.info(f"Downloading OSM tile {x=}, {y=}, {zoom=} …")
69
- url = f"https://tile.openstreetmap.org/{zoom}/{x}/{y}.png"
72
+ url = url_template.format(x=x, y=y, zoom=zoom)
70
73
  download_file(url, destination)
71
74
  with Image.open(destination) as image:
72
75
  image.load()
@@ -10,6 +10,7 @@ from typing import Optional
10
10
  from tqdm import tqdm
11
11
 
12
12
  from geo_activity_playground.core.activities import ActivityMeta
13
+ from geo_activity_playground.core.config import Config
13
14
  from geo_activity_playground.core.paths import activity_extracted_dir
14
15
  from geo_activity_playground.core.paths import activity_extracted_meta_dir
15
16
  from geo_activity_playground.core.paths import activity_extracted_time_series_dir
@@ -24,13 +25,16 @@ ACTIVITY_DIR = pathlib.Path("Activities")
24
25
 
25
26
 
26
27
  def import_from_directory(
27
- metadata_extraction_regexes: list[str], num_processes: Optional[int]
28
+ metadata_extraction_regexes: list[str], num_processes: Optional[int], config: Config
28
29
  ) -> None:
29
30
 
30
31
  activity_paths = [
31
32
  path
32
33
  for path in ACTIVITY_DIR.rglob("*.*")
33
- if path.is_file() and path.suffixes and not path.stem.startswith(".")
34
+ if path.is_file()
35
+ and path.suffixes
36
+ and not path.stem.startswith(".")
37
+ and not path.suffix in config.ignore_suffixes
34
38
  ]
35
39
  work_tracker = WorkTracker(activity_extracted_dir() / "work-tracker-extract.pickle")
36
40
  new_activity_paths = work_tracker.filter(activity_paths)
@@ -21,11 +21,9 @@ from geo_activity_playground.core.activities import make_geojson_from_time_serie
21
21
  from geo_activity_playground.core.activities import make_speed_color_bar
22
22
  from geo_activity_playground.core.config import Config
23
23
  from geo_activity_playground.core.heart_rate import HeartRateZoneComputer
24
- from geo_activity_playground.core.heatmap import add_margin_to_geo_bounds
25
- from geo_activity_playground.core.heatmap import build_map_from_tiles
24
+ from geo_activity_playground.core.heatmap import build_map_from_tiles_around_center
26
25
  from geo_activity_playground.core.heatmap import GeoBounds
27
- from geo_activity_playground.core.heatmap import get_bounds
28
- from geo_activity_playground.core.heatmap import get_sensible_zoom_level
26
+ from geo_activity_playground.core.heatmap import OSM_MAX_ZOOM
29
27
  from geo_activity_playground.core.heatmap import OSM_TILE_SIZE
30
28
  from geo_activity_playground.core.heatmap import PixelBounds
31
29
  from geo_activity_playground.core.heatmap import TileBounds
@@ -128,7 +126,7 @@ class ActivityController:
128
126
  if len(time_series) == 0:
129
127
  time_series = self._repository.get_time_series(id)
130
128
  return make_sharepic(
131
- activity, time_series, self._config.sharepic_suppressed_fields
129
+ activity, time_series, self._config.sharepic_suppressed_fields, self._config
132
130
  )
133
131
 
134
132
  def render_day(self, year: int, month: int, day: int) -> dict:
@@ -458,43 +456,58 @@ def make_sharepic(
458
456
  activity: ActivityMeta,
459
457
  time_series: pd.DataFrame,
460
458
  sharepic_suppressed_fields: list[str],
459
+ config: Config,
461
460
  ) -> bytes:
462
- lat_lon_data = np.array([time_series["latitude"], time_series["longitude"]]).T
461
+ tile_x = time_series["x"]
462
+ tile_y = time_series["y"]
463
+ tile_width = tile_x.max() - tile_x.min()
464
+ tile_height = tile_y.max() - tile_y.min()
465
+
466
+ target_width = 600
467
+ target_height = 600
468
+ footer_height = 100
469
+ target_map_height = target_height - footer_height
470
+
471
+ zoom = int(
472
+ min(
473
+ np.log2(target_width / tile_width / OSM_TILE_SIZE),
474
+ np.log2(target_map_height / tile_height / OSM_TILE_SIZE),
475
+ OSM_MAX_ZOOM,
476
+ )
477
+ )
463
478
 
464
- geo_bounds = get_bounds(lat_lon_data)
465
- geo_bounds = add_margin_to_geo_bounds(geo_bounds)
466
- tile_bounds = get_sensible_zoom_level(geo_bounds, (1500, 1500))
467
- tile_bounds = make_tile_bounds_square(tile_bounds)
468
- background = build_map_from_tiles(tile_bounds)
469
- # background = convert_to_grayscale(background)
479
+ tile_xz = tile_x * 2**zoom
480
+ tile_yz = tile_y * 2**zoom
470
481
 
471
- crop_mask = get_crop_mask(geo_bounds, tile_bounds)
472
- assert pixels_in_bounds(crop_mask) <= 10_000_000, crop_mask
482
+ tile_xz_center = (
483
+ (tile_xz.max() + tile_xz.min()) / 2,
484
+ (tile_yz.max() + tile_yz.min()) / 2,
485
+ )
473
486
 
474
- background = background[
475
- crop_mask.y_min : crop_mask.y_max,
476
- crop_mask.x_min : crop_mask.x_max,
477
- :,
478
- ]
487
+ background = build_map_from_tiles_around_center(
488
+ tile_xz_center,
489
+ zoom,
490
+ (target_width, target_height),
491
+ (target_width, target_map_height),
492
+ config,
493
+ )
479
494
 
480
495
  img = Image.fromarray((background * 255).astype("uint8"), "RGB")
481
496
  draw = ImageDraw.Draw(img, mode="RGBA")
482
497
 
483
498
  for _, group in time_series.groupby("segment_id"):
484
- xs, ys = compute_tile_float(
485
- group["latitude"], group["longitude"], tile_bounds.zoom
486
- )
487
499
  yx = list(
488
- (
489
- int((x - tile_bounds.x_tile_min) * OSM_TILE_SIZE - crop_mask.x_min),
490
- int((y - tile_bounds.y_tile_min) * OSM_TILE_SIZE - crop_mask.y_min),
500
+ zip(
501
+ (tile_xz - tile_xz_center[0]) * OSM_TILE_SIZE + target_width / 2,
502
+ (tile_yz - tile_xz_center[1]) * OSM_TILE_SIZE + target_map_height / 2,
491
503
  )
492
- for x, y in zip(xs, ys)
493
504
  )
494
505
 
495
506
  draw.line(yx, fill="red", width=4)
496
507
 
497
- draw.rectangle([0, img.height - 70, img.width, img.height], fill=(0, 0, 0, 128))
508
+ draw.rectangle(
509
+ [0, img.height - footer_height, img.width, img.height], fill=(0, 0, 0, 180)
510
+ )
498
511
 
499
512
  facts = {
500
513
  "kind": f"{activity['kind']}",
@@ -515,19 +528,20 @@ def make_sharepic(
515
528
  if not key in sharepic_suppressed_fields
516
529
  }
517
530
 
518
- draw.text((35, img.height - 70 + 10), " ".join(facts.values()), font_size=20)
519
-
520
- # img_array = np.array(img) / 255
521
-
522
- # weight = np.dstack([img_array[:, :, 0]] * 3)
531
+ draw.text(
532
+ (35, img.height - footer_height + 10),
533
+ " ".join(facts.values()),
534
+ font_size=20,
535
+ )
523
536
 
524
- # background = (1 - weight) * background + img_array
525
- # background[background > 1.0] = 1.0
526
- # background[background < 0.0] = 0.0
537
+ draw.text(
538
+ (img.width - 250, img.height - 20),
539
+ "Map: © Open Street Map Contributors",
540
+ font_size=14,
541
+ )
527
542
 
528
543
  f = io.BytesIO()
529
544
  img.save(f, format="png")
530
- # pl.imsave(f, background, format="png")
531
545
  return bytes(f.getbuffer())
532
546
 
533
547
 
@@ -17,7 +17,7 @@
17
17
  });
18
18
  L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
19
19
  maxZoom: 19,
20
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
20
+ attribution: '{{ map_tile_attribution|safe }}'
21
21
  }).addTo(map);
22
22
 
23
23
  let geojson = L.geoJSON({{ geojson| safe }}, {
@@ -21,7 +21,7 @@
21
21
  });
22
22
  L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
23
23
  maxZoom: 19,
24
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
24
+ attribution: '{{ map_tile_attribution|safe }}'
25
25
  }).addTo(map);
26
26
 
27
27
  let geojson = L.geoJSON({{ geojson| safe }}, {
@@ -17,7 +17,7 @@
17
17
  });
18
18
  L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
19
19
  maxZoom: 19,
20
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
20
+ attribution: '{{ map_tile_attribution|safe }}'
21
21
  }).addTo(map);
22
22
 
23
23
  let geojson = L.geoJSON({{ geojson| safe }}, {
@@ -64,7 +64,8 @@
64
64
  <tbody>
65
65
  {% for activity in activities %}
66
66
  <tr>
67
- <td><span style="color: {{ activity['color'] }};">█</span> <a href="{{ url_for('activity.show', id=activity.id) }}">{{
67
+ <td><span style="color: {{ activity['color'] }};">█</span> <a
68
+ href="{{ url_for('activity.show', id=activity.id) }}">{{
68
69
  activity.name }}</a></td>
69
70
  <td>{{ activity.start|dt }}</td>
70
71
  <td>{{ activity.distance_km | round(1) }}</td>
@@ -52,7 +52,7 @@
52
52
  });
53
53
  L.tileLayer('/tile/pastel/{z}/{x}/{y}.png', {
54
54
  maxZoom: 19,
55
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
55
+ attribution: '{{ map_tile_attribution|safe }}'
56
56
  }).addTo(map);
57
57
 
58
58
  let geojson = L.geoJSON({{ color_line_geojson| safe }}, {
@@ -160,7 +160,7 @@
160
160
  })
161
161
  L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
162
162
  maxZoom: 19,
163
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
163
+ attribution: '{{ map_tile_attribution|safe }}'
164
164
  }).addTo(map)
165
165
 
166
166
  let geojson_layer = L.geoJSON(geojson).addTo(map)
@@ -3,6 +3,8 @@ import importlib
3
3
  import json
4
4
  import pathlib
5
5
  import secrets
6
+ import shutil
7
+ import urllib.parse
6
8
 
7
9
  from flask import Flask
8
10
  from flask import render_template
@@ -97,7 +99,8 @@ def web_ui_main(
97
99
  url_prefix="/explorer",
98
100
  )
99
101
  app.register_blueprint(
100
- make_heatmap_blueprint(repository, tile_visit_accessor), url_prefix="/heatmap"
102
+ make_heatmap_blueprint(repository, tile_visit_accessor, config_accessor()),
103
+ url_prefix="/heatmap",
101
104
  )
102
105
  app.register_blueprint(
103
106
  make_settings_blueprint(config_accessor, authenticator),
@@ -115,7 +118,7 @@ def web_ui_main(
115
118
  make_summary_blueprint(repository, config_accessor()),
116
119
  url_prefix="/summary",
117
120
  )
118
- app.register_blueprint(make_tile_blueprint(), url_prefix="/tile")
121
+ app.register_blueprint(make_tile_blueprint(config_accessor()), url_prefix="/tile")
119
122
  app.register_blueprint(
120
123
  make_upload_blueprint(
121
124
  repository, tile_visit_accessor, config_accessor(), authenticator
@@ -123,11 +126,20 @@ def web_ui_main(
123
126
  url_prefix="/upload",
124
127
  )
125
128
 
129
+ base_dir = pathlib.Path("Open Street Map Tiles")
130
+ dir_for_source = base_dir / urllib.parse.quote_plus(config_accessor().map_tile_url)
131
+ if base_dir.exists() and not dir_for_source.exists():
132
+ subdirs = base_dir.glob("*")
133
+ dir_for_source.mkdir()
134
+ for subdir in subdirs:
135
+ shutil.move(subdir, dir_for_source)
136
+
126
137
  @app.context_processor
127
138
  def inject_global_variables() -> dict:
128
139
  return {
129
140
  "version": _try_get_version(),
130
141
  "num_activities": len(repository),
142
+ "map_tile_attribution": config_accessor().map_tile_attribution,
131
143
  }
132
144
 
133
145
  app.run(host=host, port=port)
@@ -73,7 +73,7 @@
73
73
  })
74
74
  L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
75
75
  maxZoom: 19,
76
- attribution: '&copy; <a href=" http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
76
+ attribution: '{{ map_tile_attribution|safe }}'
77
77
  }).addTo(map)
78
78
  let explorer_layer_cluster_color = L.geoJSON(explorer_geojson, {
79
79
  style: function (feature) {
@@ -87,58 +87,34 @@
87
87
  let explorer_layer_first_age_color = L.geoJSON(explorer_geojson, {
88
88
  style: function (feature) {
89
89
  return {
90
- color: "#440154", fillColor: feature.properties.first_age_color,
91
- weight: 0.5
90
+ color: " #440154", fillColor: feature.properties.first_age_color, weight: 0.5
92
91
  }
93
92
  },
94
93
  onEachFeature: onEachFeature
95
- })
96
- let explorer_layer_last_age_color = L.geoJSON(explorer_geojson, {
97
- style: function (feature) {
98
- return {
99
- color: "#440154", fillColor: feature.properties.last_age_color, weight:
100
- 0.5
101
- }
102
- },
103
- onEachFeature: onEachFeature
104
- })
105
-
106
- let bbox = {{ center.bbox| safe }}
107
- if (bbox) {
108
- map.fitBounds(L.geoJSON(bbox).getBounds())
109
- }
110
-
111
- let explorer_square_layer = L.geoJSON(square_geojson,
112
- {
113
- style: function (feature) {
94
+ }) let explorer_layer_last_age_color = L.geoJSON(explorer_geojson, {
95
+ style:
96
+ function (feature) {
114
97
  return {
115
- color: "blue", fill: false, weight: 2
98
+ color: "#440154", fillColor: feature.properties.last_age_color, weight:
99
+ 0.5
116
100
  }
117
- }
118
- }
119
- ).addTo(map)
120
-
121
- active_layer = explorer_layer_cluster_color
122
-
123
- function changeColor(method) {
101
+ }, onEachFeature: onEachFeature
102
+ }) let bbox = {{ center.bbox| safe }} if (bbox) {
103
+ map.fitBounds(L.geoJSON(bbox).getBounds())
104
+ } let explorer_square_layer = L.geoJSON(square_geojson, {
105
+ style: function (feature) { return { color: "blue", fill: false, weight: 2 } }
106
+ }).addTo(map)
107
+ active_layer = explorer_layer_cluster_color function changeColor(method) {
124
108
  map.removeLayer(active_layer)
125
- if (method == "cluster") {
126
- active_layer = explorer_layer_cluster_color
127
- } else if (method == "first") {
109
+ if (method == "cluster") { active_layer = explorer_layer_cluster_color } else if (method == "first") {
128
110
  active_layer = explorer_layer_first_age_color
129
111
  } else if (method == "last") {
130
112
  active_layer = explorer_layer_last_age_color
131
- }
132
- map.addLayer(active_layer)
133
- }
134
-
135
- function downloadAs(suffix) {
136
- bounds = map.getBounds()
137
- zoom = "{{ zoom }}"
138
- window.location.href =
139
- `/explorer/${zoom}/${bounds.getNorth()}/${bounds.getEast()}/${bounds.getSouth()}/${bounds.getWest()}/${suffix}`
140
- }
141
- </script>
113
+ } map.addLayer(active_layer)
114
+ } function downloadAs(suffix) {
115
+ bounds = map.getBounds() zoom = "{{ zoom }}"
116
+ window.location.href = `/explorer/${zoom}/${bounds.getNorth()}/${bounds.getEast()}/${bounds.getSouth()}/${bounds.getWest()}/${suffix}`
117
+ } </script>
142
118
  </div>
143
119
  </div>
144
120
 
@@ -6,12 +6,15 @@ from flask import Response
6
6
  from ...core.activities import ActivityRepository
7
7
  from ...explorer.tile_visits import TileVisitAccessor
8
8
  from .heatmap_controller import HeatmapController
9
+ from geo_activity_playground.core.config import Config
9
10
 
10
11
 
11
12
  def make_heatmap_blueprint(
12
- repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
13
+ repository: ActivityRepository,
14
+ tile_visit_accessor: TileVisitAccessor,
15
+ config: Config,
13
16
  ) -> Blueprint:
14
- heatmap_controller = HeatmapController(repository, tile_visit_accessor)
17
+ heatmap_controller = HeatmapController(repository, tile_visit_accessor, config)
15
18
  blueprint = Blueprint("heatmap", __name__, template_folder="templates")
16
19
 
17
20
  @blueprint.route("/")
@@ -8,6 +8,7 @@ from PIL import Image
8
8
  from PIL import ImageDraw
9
9
 
10
10
  from geo_activity_playground.core.activities import ActivityRepository
11
+ from geo_activity_playground.core.config import Config
11
12
  from geo_activity_playground.core.heatmap import convert_to_grayscale
12
13
  from geo_activity_playground.core.heatmap import GeoBounds
13
14
  from geo_activity_playground.core.heatmap import get_sensible_zoom_level
@@ -29,10 +30,14 @@ OSM_TILE_SIZE = 256 # OSM tile size in pixel
29
30
 
30
31
  class HeatmapController:
31
32
  def __init__(
32
- self, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
33
+ self,
34
+ repository: ActivityRepository,
35
+ tile_visit_accessor: TileVisitAccessor,
36
+ config: Config,
33
37
  ) -> None:
34
38
  self._repository = repository
35
39
  self._tile_visit_accessor = tile_visit_accessor
40
+ self._config = config
36
41
 
37
42
  self.tile_histories = self._tile_visit_accessor.tile_state["tile_history"]
38
43
  self.tile_evolution_states = self._tile_visit_accessor.tile_state[
@@ -140,11 +145,11 @@ class HeatmapController:
140
145
  tile_counts = np.sqrt(tile_counts) / 5
141
146
  tile_counts[tile_counts > 1.0] = 1.0
142
147
 
143
- cmap = pl.get_cmap("hot")
148
+ cmap = pl.get_cmap(self._config.color_scheme_for_heatmap)
144
149
  data_color = cmap(tile_counts)
145
150
  data_color[data_color == cmap(0.0)] = 0.0 # remove background color
146
151
 
147
- map_tile = np.array(get_tile(z, x, y)) / 255
152
+ map_tile = np.array(get_tile(z, x, y, self._config.map_tile_url)) / 255
148
153
  map_tile = convert_to_grayscale(map_tile)
149
154
  map_tile = 1.0 - map_tile # invert colors
150
155
  for c in range(3):
@@ -168,7 +173,12 @@ class HeatmapController:
168
173
  background = np.zeros((*pixel_bounds.shape, 3))
169
174
  for x in range(tile_bounds.x_tile_min, tile_bounds.x_tile_max):
170
175
  for y in range(tile_bounds.y_tile_min, tile_bounds.y_tile_max):
171
- tile = np.array(get_tile(tile_bounds.zoom, x, y)) / 255
176
+ tile = (
177
+ np.array(
178
+ get_tile(tile_bounds.zoom, x, y, self._config.map_tile_url)
179
+ )
180
+ / 255
181
+ )
172
182
 
173
183
  i = y - tile_bounds.y_tile_min
174
184
  j = x - tile_bounds.x_tile_min
@@ -35,7 +35,7 @@
35
35
  });
36
36
  L.tileLayer('/heatmap/tile/{z}/{x}/{y}/{{ kinds_str }}.png', {
37
37
  maxZoom: 19,
38
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
38
+ attribution: '{{ map_tile_attribution|safe }}'
39
39
  }).addTo(map)
40
40
 
41
41
  let bbox = {{ center.bbox| safe }}
@@ -15,6 +15,43 @@ from geo_activity_playground.webui.authenticator import needs_authentication
15
15
  from geo_activity_playground.webui.settings.controller import SettingsController
16
16
 
17
17
 
18
+ VEGA_COLOR_SCHEMES_CONTINUOUS = [
19
+ "lightgreyred",
20
+ "lightgreyteal",
21
+ "lightmulti",
22
+ "lightorange",
23
+ "lighttealblue",
24
+ "blues",
25
+ "tealblues",
26
+ "teals",
27
+ "greens",
28
+ "browns",
29
+ "oranges",
30
+ "reds",
31
+ "purples",
32
+ "warmgreys",
33
+ "greys",
34
+ ]
35
+
36
+ MATPLOTLIB_COLOR_SCHEMES_CONTINUOUS = [
37
+ "afmhot",
38
+ "bone",
39
+ "cividis",
40
+ "copper",
41
+ "gist_gray",
42
+ "gist_heat",
43
+ "gnuplot2",
44
+ "gray",
45
+ "Greys_r",
46
+ "hot",
47
+ "inferno",
48
+ "magma",
49
+ "pink",
50
+ "plasma",
51
+ "viridis",
52
+ ]
53
+
54
+
18
55
  def int_or_none(s: str) -> Optional[int]:
19
56
  if s:
20
57
  try:
@@ -56,44 +93,16 @@ def make_settings_blueprint(
56
93
  config_accessor().color_scheme_for_kind = request.form[
57
94
  "color_scheme_for_kind"
58
95
  ]
96
+ config_accessor().color_scheme_for_heatmap = request.form[
97
+ "color_scheme_for_heatmap"
98
+ ]
59
99
  config_accessor.save()
60
100
  flash("Updated color schemes.", category="success")
101
+
61
102
  return render_template(
62
103
  "settings/color-schemes.html.j2",
63
104
  color_scheme_for_counts=config_accessor().color_scheme_for_counts,
64
- color_scheme_for_counts_avail=[
65
- "viridis",
66
- "magma",
67
- "inferno",
68
- "plasma",
69
- "cividis",
70
- "turbo",
71
- "bluegreen",
72
- "bluepurple",
73
- "goldgreen",
74
- "goldorange",
75
- "goldred",
76
- "greenblue",
77
- "orangered",
78
- "purplebluegreen",
79
- "purpleblue",
80
- "purplered",
81
- "redpurple",
82
- "yellowgreenblue",
83
- "yellowgreen",
84
- "yelloworangebrown",
85
- "yelloworangered",
86
- "darkblue",
87
- "darkgold",
88
- "darkgreen",
89
- "darkmulti",
90
- "darkred",
91
- "lightgreyred",
92
- "lightgreyteal",
93
- "lightmulti",
94
- "lightorange",
95
- "lighttealblue",
96
- ],
105
+ color_scheme_for_counts_avail=VEGA_COLOR_SCHEMES_CONTINUOUS,
97
106
  color_scheme_for_kind=config_accessor().color_scheme_for_kind,
98
107
  color_scheme_for_kind_avail=[
99
108
  "accent",
@@ -111,6 +120,8 @@ def make_settings_blueprint(
111
120
  "tableau10",
112
121
  "tableau20",
113
122
  ],
123
+ color_scheme_for_heatmap=config_accessor().color_scheme_for_heatmap,
124
+ color_scheme_for_heatmap_avail=MATPLOTLIB_COLOR_SCHEMES_CONTINUOUS,
114
125
  )
115
126
 
116
127
  @blueprint.route("/equipment-offsets", methods=["GET", "POST"])
@@ -18,8 +18,17 @@
18
18
  </div>
19
19
 
20
20
  <div class="mb-3">
21
- <label class="form-label">Color scheme for heatmaps</label>
22
- <select class="form-select" aria-label="Color scheme for heatmaps" name="color_scheme_for_counts">
21
+ <label class="form-label">Color scheme for interactive heatmap</label>
22
+ <select class="form-select" aria-label="Color scheme for heatmaps" name="color_scheme_for_heatmap">
23
+ {% for cs in color_scheme_for_heatmap_avail %}
24
+ <option {% if cs==color_scheme_for_heatmap %} selected {% endif %}>{{ cs }}</option>
25
+ {% endfor %}
26
+ </select>
27
+ </div>
28
+
29
+ <div class="mb-3">
30
+ <label class="form-label">Color scheme for count plots</label>
31
+ <select class="form-select" aria-label="Color scheme for count plots" name="color_scheme_for_counts">
23
32
  {% for cs in color_scheme_for_counts_avail %}
24
33
  <option {% if cs==color_scheme_for_counts %} selected {% endif %}>{{ cs }}</option>
25
34
  {% endfor %}
@@ -20,7 +20,7 @@
20
20
  })
21
21
  L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
22
22
  maxZoom: 19,
23
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
23
+ attribution: '{{ map_tile_attribution|safe }}'
24
24
  }).addTo(map)
25
25
 
26
26
  let geojson_layer = L.geoJSON(geojson).addTo(map)
@@ -106,7 +106,7 @@
106
106
 
107
107
  L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
108
108
  maxZoom: 19,
109
- attribution: '&copy; <a href=" http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
109
+ attribution: '{{ map_tile_attribution|safe }}'
110
110
  }).addTo(map)
111
111
 
112
112
  let explorer_geojson = {{ explored_geojson| safe }}
@@ -21,7 +21,7 @@ class SummaryController:
21
21
  def render(self) -> dict:
22
22
  kind_scale = make_kind_scale(self._repository.meta, self._config)
23
23
  df = embellished_activities(self._repository.meta)
24
- df = df.loc[df["consider_for_achievements"]]
24
+ # df = df.loc[df["consider_for_achievements"]]
25
25
 
26
26
  year_kind_total = (
27
27
  df[["year", "kind", "distance_km", "hours"]]
@@ -31,7 +31,7 @@ class SummaryController:
31
31
  )
32
32
 
33
33
  return {
34
- "plot_distance_heatmap": plot_distance_heatmap(df, self._config),
34
+ "plot_distance_heatmaps": plot_distance_heatmaps(df, self._config),
35
35
  "plot_monthly_distance": plot_monthly_distance(df, kind_scale),
36
36
  "plot_yearly_distance": plot_yearly_distance(year_kind_total, kind_scale),
37
37
  "plot_year_cumulative": plot_year_cumulative(df),
@@ -112,17 +112,10 @@ def embellished_activities(meta: pd.DataFrame) -> pd.DataFrame:
112
112
  return df
113
113
 
114
114
 
115
- def plot_distance_heatmap(meta: pd.DataFrame, config: Config) -> str:
116
- return (
117
- alt.Chart(
118
- meta.loc[
119
- (
120
- meta["start"]
121
- >= pd.to_datetime(
122
- datetime.datetime.now() - datetime.timedelta(days=2 * 365)
123
- )
124
- )
125
- ],
115
+ def plot_distance_heatmaps(meta: pd.DataFrame, config: Config) -> dict[int, str]:
116
+ return {
117
+ year: alt.Chart(
118
+ meta.loc[(meta["year"] == year)],
126
119
  title="Daily Distance Heatmap",
127
120
  )
128
121
  .mark_rect()
@@ -130,7 +123,7 @@ def plot_distance_heatmap(meta: pd.DataFrame, config: Config) -> str:
130
123
  alt.X("date(start):O", title="Day of month"),
131
124
  alt.Y(
132
125
  "yearmonth(start):O",
133
- scale=alt.Scale(reverse=True),
126
+ # scale=alt.Scale(reverse=True),
134
127
  title="Year and month",
135
128
  ),
136
129
  alt.Color(
@@ -146,7 +139,8 @@ def plot_distance_heatmap(meta: pd.DataFrame, config: Config) -> str:
146
139
  ],
147
140
  )
148
141
  .to_json(format="vega")
149
- )
142
+ for year in sorted(meta["year"].unique())
143
+ }
150
144
 
151
145
 
152
146
  def plot_monthly_distance(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
@@ -76,13 +76,26 @@
76
76
  <p>Next we take one row per month, each column is a day-of-month. The brighter a box, the more distance you have covered
77
77
  on that day. This makes it easy to spot those days where you really covered a lot of distance!</p>
78
78
 
79
- <div class="row mb-3">
80
- <div class="col">
81
- {{ vega_direct("plot_distance_heatmap", plot_distance_heatmap) }}
79
+
80
+ <ul class="nav nav-pills mb-3" id="myTab" role="tablist">
81
+ {% for year, plot in plot_distance_heatmaps.items() %}
82
+ <li class="nav-item" role="presentation">
83
+ <button class="nav-link {% if loop.last %} active {% endif %}" id="heatmap-{{ year }}" data-bs-toggle="tab"
84
+ data-bs-target="#heatmap-{{ year }}-pane" type="button" role="tab" aria-controls="heatmap-{{ year }}-pane"
85
+ aria-selected="{{ loop.last }}">{{ year }}</button>
86
+ </li>
87
+ {% endfor %}
88
+ </ul>
89
+
90
+ <div class="tab-content mb-3" id="myTabContent">
91
+ {% for year, plot in plot_distance_heatmaps.items() %}
92
+ <div class="tab-pane fade {% if loop.last %} show active {% endif %}" id="heatmap-{{ year }}-pane" role="tabpanel"
93
+ aria-labelledby="heatmap-{{ year }}" tabindex="0">
94
+ {{ vega_direct("plot_distance_heatmap_%d"|format(year), plot) }}
82
95
  </div>
96
+ {% endfor %}
83
97
  </div>
84
98
 
85
-
86
99
  <h2>Hall of Fame</h2>
87
100
 
88
101
  <script>
@@ -92,7 +105,7 @@
92
105
  })
93
106
  L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
94
107
  maxZoom: 19,
95
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
108
+ attribution: '{{ map_tile_attribution|safe }}'
96
109
  }).addTo(map)
97
110
 
98
111
  let geojson_layer = L.geoJSON(geojson).addTo(map)
@@ -26,7 +26,7 @@
26
26
  })
27
27
  L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
28
28
  maxZoom: 19,
29
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
29
+ attribution: '{{ map_tile_attribution|safe }}'
30
30
  }).addTo(map)
31
31
 
32
32
  let geojson_layer = L.geoJSON(geojson).addTo(map)
@@ -2,12 +2,13 @@ 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
5
6
 
6
7
 
7
- def make_tile_blueprint() -> Blueprint:
8
+ def make_tile_blueprint(config: Config) -> Blueprint:
8
9
  blueprint = Blueprint("tiles", __name__, template_folder="templates")
9
10
 
10
- tile_controller = TileController()
11
+ tile_controller = TileController(config)
11
12
 
12
13
  @blueprint.route("/color/<z>/<x>/<y>.png")
13
14
  def tile_color(x: str, y: str, z: str):
@@ -3,18 +3,22 @@ import io
3
3
  import matplotlib.pyplot as pl
4
4
  import numpy as np
5
5
 
6
+ from geo_activity_playground.core.config import Config
6
7
  from geo_activity_playground.core.tiles import get_tile
7
8
 
8
9
 
9
10
  class TileController:
11
+ def __init__(self, config: Config):
12
+ self._config = config
13
+
10
14
  def render_color(self, x: int, y: int, z: int) -> bytes:
11
- map_tile = np.array(get_tile(z, x, y)) / 255
15
+ map_tile = np.array(get_tile(z, x, y, self._config.map_tile_url)) / 255
12
16
  f = io.BytesIO()
13
17
  pl.imsave(f, map_tile, format="png")
14
18
  return bytes(f.getbuffer())
15
19
 
16
20
  def render_grayscale(self, x: int, y: int, z: int) -> bytes:
17
- map_tile = np.array(get_tile(z, x, y)) / 255
21
+ map_tile = np.array(get_tile(z, x, y, self._config.map_tile_url)) / 255
18
22
  map_tile = np.sum(map_tile * [0.2126, 0.7152, 0.0722], axis=2) # to grayscale
19
23
  map_tile = np.dstack((map_tile, map_tile, map_tile)) # to rgb
20
24
  f = io.BytesIO()
@@ -22,7 +26,7 @@ class TileController:
22
26
  return bytes(f.getbuffer())
23
27
 
24
28
  def render_pastel(self, x: int, y: int, z: int) -> bytes:
25
- map_tile = np.array(get_tile(z, x, y)) / 255
29
+ map_tile = np.array(get_tile(z, x, y, self._config.map_tile_url)) / 255
26
30
  averaged_tile = np.sum(map_tile * [0.2126, 0.7152, 0.0722], axis=2)
27
31
  grayscale_tile = np.dstack((averaged_tile, averaged_tile, averaged_tile))
28
32
  factor = 0.7
@@ -102,8 +102,7 @@ def scan_for_activities(
102
102
  ) -> None:
103
103
  if pathlib.Path("Activities").exists():
104
104
  import_from_directory(
105
- config.metadata_extraction_regexes,
106
- config.num_processes,
105
+ config.metadata_extraction_regexes, config.num_processes, config
107
106
  )
108
107
  if pathlib.Path("Strava Export").exists():
109
108
  import_from_strava_checkout()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geo-activity-playground
3
- Version: 0.31.0
3
+ Version: 0.33.0
4
4
  Summary: Analysis of geo data activities like rides, runs or hikes.
5
5
  License: MIT
6
6
  Author: Martin Ueding
@@ -2,18 +2,18 @@ geo_activity_playground/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
2
2
  geo_activity_playground/__main__.py,sha256=MBZn9K1m3PofiPNTtpsSTVCyB_Gz95TjVP-nb9v_-JE,3989
3
3
  geo_activity_playground/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  geo_activity_playground/core/activities.py,sha256=soxMtdijnkZ1bYZU0q6wuM8NPNFoUpLwYp3IvBOaKJY,6927
5
- geo_activity_playground/core/config.py,sha256=3xSaPKmNT_h0MHDoYgqOtmqj-x831ETiUKdSFXqrzHs,4744
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=bRLQHzmTEsQbX8XWeg85x_lRGk272UoYRiCnoxZ5da0,4189
9
+ geo_activity_playground/core/heatmap.py,sha256=iTxefUTjTToPrKpVbauJHXkqxpNppXOEK6vvKuNkHkk,5906
10
10
  geo_activity_playground/core/paths.py,sha256=RBeUi38riP_msTGPy1TsPRNiblzE-lFivaJSLULE8b0,2503
11
11
  geo_activity_playground/core/privacy_zones.py,sha256=4TumHsVUN1uW6RG3ArqTXDykPVipF98DCxVBe7YNdO8,512
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=KpzD-h3kNzZ2ieLt6f2xHilSF3lHyfaEXPnrGvlIAz0,3379
16
+ geo_activity_playground/core/tiles.py,sha256=qUe4h3rzHJb8xThAgUKSLElt8S6zID_OuaWXDkWLwAU,3539
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
@@ -22,7 +22,7 @@ geo_activity_playground/explorer/video.py,sha256=ROAmV9shfJyqTgnXVD41KFORiwnRgVp
22
22
  geo_activity_playground/importers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  geo_activity_playground/importers/activity_parsers.py,sha256=XNQs0ziqAcVqIoiLAX5Ndmhb6v__29XdjUPvNvc7oBI,11082
24
24
  geo_activity_playground/importers/csv_parser.py,sha256=O1pP5GLhWhnWcy2Lsrr9g17Zspuibpt-GtZ3ZS5eZF4,2143
25
- geo_activity_playground/importers/directory.py,sha256=IPnr1xk3beznmPVjfWL6AQiEKA4WN5EDIfnzrM0Tzlc,5767
25
+ geo_activity_playground/importers/directory.py,sha256=CA-vFOMm8G4MSM_Q09OwQKduCApL2PWaxLTVxgw_xpw,5908
26
26
  geo_activity_playground/importers/strava_api.py,sha256=cJCZsLemhOlxTtZh0z_npidgae9SD5HyEUry2uvem_A,7775
27
27
  geo_activity_playground/importers/strava_checkout.py,sha256=N-uGTkhBJMC7cPYjRRXHOSLwpK3wc6aaSrY2RQfSitA,9419
28
28
  geo_activity_playground/importers/test_csv_parser.py,sha256=LXqva7GuSAfXYE2zZQrg-69lCtfy5MxLSq6BRwL_VyI,1191
@@ -31,13 +31,13 @@ geo_activity_playground/importers/test_strava_api.py,sha256=4vX7wDr1a9aRh8myxNrI
31
31
  geo_activity_playground/webui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  geo_activity_playground/webui/activity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  geo_activity_playground/webui/activity/blueprint.py,sha256=Ub2mC9S9TII7CJaaWahnbNtT76uOGKNDWE0-j2C56CA,3893
34
- geo_activity_playground/webui/activity/controller.py,sha256=iE9JILmfqOWgXWT4KgqrXLpQJ8xSqfiNKeJw6vnWDAc,19459
35
- geo_activity_playground/webui/activity/templates/activity/day.html.j2,sha256=o18e-TMtgCrY7iroInVhRA267l-o6uGNlstIwsvFnww,2735
34
+ geo_activity_playground/webui/activity/controller.py,sha256=t4-9-Gxchc959AaOOflSTYhNiGBuJT2nu1UZ0Vv_nxU,19343
35
+ geo_activity_playground/webui/activity/templates/activity/day.html.j2,sha256=Gitp3IR913YTjzlz_OiWIe3VSN4OmFGZVGPuMVINCg4,2693
36
36
  geo_activity_playground/webui/activity/templates/activity/edit.html.j2,sha256=ckcTTxcQOhmvvAGNTEOtWCUG4LhvO4HfQImlIa5qKs8,1530
37
- geo_activity_playground/webui/activity/templates/activity/lines.html.j2,sha256=5gB1aDjRgi_RventenRfC10_FtMT4ch_VuWvA9AMlBY,1121
38
- geo_activity_playground/webui/activity/templates/activity/name.html.j2,sha256=npciXBo7_94_tCE0X7RLFHws8mgHzs4VgbOnyA9rsOI,2451
39
- geo_activity_playground/webui/activity/templates/activity/show.html.j2,sha256=MndgjmSi6T2xbIietSI-5n1hi7E1tD4eCoQP0tD3Ing,6989
40
- geo_activity_playground/webui/app.py,sha256=zDpLVGa0-_uoy9KQDcY8Z52RENhziDOfxfWv744GQAw,4616
37
+ geo_activity_playground/webui/activity/templates/activity/lines.html.j2,sha256=_ZDg1ruW-9UMJfOudy1-uY_-IcSSaagq7tPCih5Bb8g,1079
38
+ geo_activity_playground/webui/activity/templates/activity/name.html.j2,sha256=tKviMqMouHEGv3xBQVIsJgwj_hjwAsmGVefM3UMqlYg,2437
39
+ geo_activity_playground/webui/activity/templates/activity/show.html.j2,sha256=bEx37UGSTeeJl7gN4fjyOpINFQwZ5Zm-HOKpLqcJGfs,6905
40
+ geo_activity_playground/webui/app.py,sha256=nWbgxRHg0DThi44VFb04OIVwxnqIVmkr3cnO9ucE1Oo,5118
41
41
  geo_activity_playground/webui/auth/blueprint.py,sha256=Lx-ZvMnfHLC1CMre1xPQI3k_pCtQoZvgRhtmafULzoE,812
42
42
  geo_activity_playground/webui/auth/templates/auth/index.html.j2,sha256=ILQ5HvTEYc3OrtOAIFt1VrqWorVD70V9DC342znmP70,579
43
43
  geo_activity_playground/webui/authenticator.py,sha256=k278OEVuOfAmTGT4F2X4pqSTwwkK_FA87EIhAeysEqc,1416
@@ -58,32 +58,32 @@ geo_activity_playground/webui/equipment/templates/equipment/index.html.j2,sha256
58
58
  geo_activity_playground/webui/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  geo_activity_playground/webui/explorer/blueprint.py,sha256=EKnBs8llqT6Wy1uac18dF2epp3TebF9p3iGlSbj6Vl0,2337
60
60
  geo_activity_playground/webui/explorer/controller.py,sha256=pIzWh0TpLJgKQZlS325-QT7nA1q9ms7fRqQIp24PNfo,11705
61
- geo_activity_playground/webui/explorer/templates/explorer/index.html.j2,sha256=u2htecx-XwINRiINHFN6EZDaRXVtiape1OCUZexTBU8,7049
61
+ geo_activity_playground/webui/explorer/templates/explorer/index.html.j2,sha256=PnAL0uDNjnziwbkCOpIWPb7KmS4c1dP-VLxjyXppUYc,6701
62
62
  geo_activity_playground/webui/heatmap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- geo_activity_playground/webui/heatmap/blueprint.py,sha256=bjQu-HL3QN5UpJ6tHOifhcLGlPr_hIKvaRu78md4JqM,1470
64
- geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256=LMBglDOhcLjvkq-7hr1B6IhW_TiBDZ1NIFP5dqhLCC4,7503
65
- geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2,sha256=YLeu6P4djl8G4qAXR6DhetseqrbOodN7aN4coocknc4,1875
63
+ geo_activity_playground/webui/heatmap/blueprint.py,sha256=ZEImDIwT3uiDIKapqCU49llvyqG79n7ZEu1GHgoLZqo,1558
64
+ geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256=Qw9MGW3TFWlG2JkA_r9RHgYq4hvPiJaZeAg5D9lIFC0,7821
65
+ geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2,sha256=BMjqbZ-btSFawuNDxgZxOkF5JvhD_p9DOBJ-4-1IKnU,1833
66
66
  geo_activity_playground/webui/plot_util.py,sha256=pTTQoqOCkLVjkgOit7mbry28kMruZIL8amZozSzEpxQ,283
67
67
  geo_activity_playground/webui/search/blueprint.py,sha256=7TDsiqEowMyHNlFImk-hCGso69KOieG4rfJnLRHpRz8,3300
68
68
  geo_activity_playground/webui/search/templates/search/index.html.j2,sha256=d39uhteoY6JOZePqhLIYLERqkckW3oghMnTUZa7X1J8,3798
69
- geo_activity_playground/webui/settings/blueprint.py,sha256=Jeh2MwRmCNF6BfwrHSUixLKOTPFlwZ4Mrb68Botr6Q8,9487
69
+ geo_activity_playground/webui/settings/blueprint.py,sha256=GIrCOBNw-uNWDmKj4uduYOiKpoTLEfFbGmUmXkoBqro,9405
70
70
  geo_activity_playground/webui/settings/controller.py,sha256=MIZVBfoGNvmJnB_ECV_x5eH2i6gDZvkWSQ4PcSKyLKs,9170
71
71
  geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2,sha256=VYwddpObD1RpeTH5Dm4y7VtmT7kwURDCIjxyzJeq08c,495
72
- geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2,sha256=CaFbYkkU1yGTOlAzGq97u3tVgS79RIo7PEmiVjuZiBc,1226
72
+ geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2,sha256=iR91Wxd2_TMuIo9dBDZBrWSUGHNwTwzC6O8oNH-XBt4,1653
73
73
  geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2,sha256=ltaYwFe8S8Mi72ddmIp1vwqlu8MEXXjBGfbpN2WBTC4,1728
74
74
  geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2,sha256=UPT3MegRgSeff36lhCo0l3ZwhqNSIg5gM6h2s32GkCY,4255
75
75
  geo_activity_playground/webui/settings/templates/settings/index.html.j2,sha256=r7HBq91BKBrWT2_TNR7OcLokuOcUK-Czyi_zOjveIRk,5287
76
76
  geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2,sha256=yWVIMJq0XOpeTLglEqLxlQAG0mVhaCVw9j0Iu4nIrgI,757
77
77
  geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2,sha256=IdUfXon1Pu8zX3NirKb28ypshLHOvZRpz2T4bJrzrak,1067
78
78
  geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2,sha256=EUHZ-MWnSPAkiad0CHSj9UUkRUQwItTbKYJcZz9TVi0,2314
79
- geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2,sha256=7BxFvCaVJOEqbImyK5vxCmhh-NGSFaRa9ARhqjZeYJ0,3093
79
+ geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2,sha256=OBKHlOezJauaDerLpOAJYq7wL-KxsWMn-BpSKJ93I0c,3051
80
80
  geo_activity_playground/webui/settings/templates/settings/segmentation.html.j2,sha256=QV72TZcIxqql-vEsq2lKHzo5UxoxeeXkRA9se46GWKU,1187
81
81
  geo_activity_playground/webui/settings/templates/settings/sharepic.html.j2,sha256=qZkfEpd4CtKKMaSSVadqvNEgMRYLV-0X-pw5-nJvukk,678
82
82
  geo_activity_playground/webui/settings/templates/settings/strava.html.j2,sha256=FrXgT-m1PgvsQWo9kMKpk8QenKeifSDBCZFqKgsHRxQ,1827
83
83
  geo_activity_playground/webui/square_planner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
84
  geo_activity_playground/webui/square_planner/blueprint.py,sha256=r2VkSM547chX85g6c1BQ8NC-tkdqGdYp-2ZALBiiDTc,1320
85
85
  geo_activity_playground/webui/square_planner/controller.py,sha256=ML6ftOyr3tTh7D4DBcRP76CvkyTxqI5QWgMeG9wC8FE,3561
86
- geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2,sha256=aIB0ql5qW4HXfp0ENksYYOk9vTgBitwyHJX5W7bqkeY,6512
86
+ geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2,sha256=xFbYBQtkOl3U4WGkvIuU_5uazGHn8ObvQfNgPGq0Gqg,6469
87
87
  geo_activity_playground/webui/static/android-chrome-192x192.png,sha256=yxZgo8Jw4hrgOgrn3tvi9G0AXWGFD29kjCuxC07WoT4,17610
88
88
  geo_activity_playground/webui/static/android-chrome-512x512.png,sha256=Uiv62gQpUjMOdp9d6exzd6IyOi5zgQdgjIVVWYw5m98,38891
89
89
  geo_activity_playground/webui/static/apple-touch-icon.png,sha256=TNLa0YIS1mbWajvIQthC2bGve6ET3DbJzrAbs6Pf3Ps,13046
@@ -100,20 +100,20 @@ geo_activity_playground/webui/static/web-app-manifest-192x192.png,sha256=eEImN6i
100
100
  geo_activity_playground/webui/static/web-app-manifest-512x512.png,sha256=vU9oQ4HnQerFDZVzcAT9twj4_Doc6_9v9wVvoRI-f_E,48318
101
101
  geo_activity_playground/webui/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
102
  geo_activity_playground/webui/summary/blueprint.py,sha256=tfA2aPF19yKwkQOb5lPQBySoQYYhTn49Iuh0SYvsGP8,593
103
- geo_activity_playground/webui/summary/controller.py,sha256=cWn5szA1o5Vjht0DyhRwBjmwqJryrLcmm4FUdmVpUoo,9443
104
- geo_activity_playground/webui/summary/templates/summary/index.html.j2,sha256=S_kpXPldrxIAEBdlG0YlXlvMHI4dQc4QZtejhHM4_N8,4472
105
- geo_activity_playground/webui/templates/home.html.j2,sha256=EvEgvr_JeppGqLEJzcDc0kL-8e4OUV8aleWTP5eDeh8,2173
103
+ geo_activity_playground/webui/summary/controller.py,sha256=V3zBLNL_Yc_Ft-ZBaWnlLXhr8VsvTpipPUZ-G9sgUXs,9312
104
+ geo_activity_playground/webui/summary/templates/summary/index.html.j2,sha256=ctOx3Qjx6nRDpUtFf1DlJhK_gtU77Vwx_S6euLz9-W4,5183
105
+ geo_activity_playground/webui/templates/home.html.j2,sha256=JNQxpjDfXhLAJbZ7RaNBNe9u48nbvlEBrnU_Zizt544,2131
106
106
  geo_activity_playground/webui/templates/page.html.j2,sha256=znTbtD2NALrhmUN_Q-F1ElGlKtecoUv8vOCcUtojrdI,11134
107
107
  geo_activity_playground/webui/tile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
- geo_activity_playground/webui/tile/blueprint.py,sha256=cK0o2Z3BrLycgF9zw0F8s9qF-JaYDbF5Gog-GXDtUZ8,943
109
- geo_activity_playground/webui/tile/controller.py,sha256=PISh4vKs27b-LxFfTARtr5RAwHFresA1Kw1MDcERSRU,1221
108
+ geo_activity_playground/webui/tile/blueprint.py,sha256=q0sw_F8L367Df01yjZijikEIglFBgg9lN61sbTAEOKQ,1018
109
+ geo_activity_playground/webui/tile/controller.py,sha256=XjUTbyAMeQET1D3mFtT8r5-xMcMOaELPZWtQ1Xp7Cuw,1428
110
110
  geo_activity_playground/webui/upload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
111
  geo_activity_playground/webui/upload/blueprint.py,sha256=xX9scEmleN_dL03jfhWRh5yI1WsFyhxUFiS_Ul2HWy4,1428
112
- geo_activity_playground/webui/upload/controller.py,sha256=AUcDotTw-h30XgY5Te0kAqRfL7xXqK74iVO13g20pD0,4085
112
+ geo_activity_playground/webui/upload/controller.py,sha256=EvoUnmKBo3QS2TLak7-yVZ16sEDyEB6Nf2MN_scHuhQ,4080
113
113
  geo_activity_playground/webui/upload/templates/upload/index.html.j2,sha256=I1Ix8tDS3YBdi-HdaNfjkzYXVVCjfUTe5PFTnap1ydc,775
114
114
  geo_activity_playground/webui/upload/templates/upload/reload.html.j2,sha256=YZWX5eDeNyqKJdQAywDBcU8DZBm22rRBbZqFjrFrCvQ,556
115
- geo_activity_playground-0.31.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
116
- geo_activity_playground-0.31.0.dist-info/METADATA,sha256=YTQL13ygmlqQilK_3F8ycowxmDAkxGuPbVG6mI5IJOA,1612
117
- geo_activity_playground-0.31.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
118
- geo_activity_playground-0.31.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
119
- geo_activity_playground-0.31.0.dist-info/RECORD,,
115
+ geo_activity_playground-0.33.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
116
+ geo_activity_playground-0.33.0.dist-info/METADATA,sha256=b7mpW0SMMsW1p2C-wZ0ZxpWXssdYXkNOmZ-43dnNwLI,1612
117
+ geo_activity_playground-0.33.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
118
+ geo_activity_playground-0.33.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
119
+ geo_activity_playground-0.33.0.dist-info/RECORD,,