geo-activity-playground 0.33.3__py3-none-any.whl → 0.34.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geo_activity_playground/explorer/video.py +2 -1
- geo_activity_playground/importers/strava_checkout.py +8 -2
- geo_activity_playground/webui/activity/blueprint.py +7 -0
- geo_activity_playground/webui/activity/controller.py +85 -15
- geo_activity_playground/webui/activity/templates/activity/day.html.j2 +5 -1
- geo_activity_playground/webui/equipment/controller.py +1 -1
- geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +1 -0
- geo_activity_playground/webui/static/MarkerCluster.Default.css +60 -0
- geo_activity_playground/webui/static/MarkerCluster.css +14 -0
- geo_activity_playground/webui/static/bootstrap.min.css +6 -0
- geo_activity_playground/webui/static/fullscreen.png +0 -0
- geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
- geo_activity_playground/webui/static/leaflet.css +661 -0
- geo_activity_playground/webui/static/leaflet.fullscreen.css +40 -0
- geo_activity_playground/webui/static/leaflet.js +6 -0
- geo_activity_playground/webui/static/leaflet.markercluster.js +3 -0
- geo_activity_playground/webui/static/table-sort.min.js +8 -0
- geo_activity_playground/webui/static/vega-embed@6 +7 -0
- geo_activity_playground/webui/static/vega-lite@4 +2 -0
- geo_activity_playground/webui/static/vega@5 +2 -0
- geo_activity_playground/webui/summary/controller.py +11 -10
- geo_activity_playground/webui/templates/page.html.j2 +14 -16
- {geo_activity_playground-0.33.3.dist-info → geo_activity_playground-0.34.1.dist-info}/METADATA +5 -6
- {geo_activity_playground-0.33.3.dist-info → geo_activity_playground-0.34.1.dist-info}/RECORD +27 -13
- {geo_activity_playground-0.33.3.dist-info → geo_activity_playground-0.34.1.dist-info}/LICENSE +0 -0
- {geo_activity_playground-0.33.3.dist-info → geo_activity_playground-0.34.1.dist-info}/WHEEL +0 -0
- {geo_activity_playground-0.33.3.dist-info → geo_activity_playground-0.34.1.dist-info}/entry_points.txt +0 -0
@@ -9,13 +9,14 @@ from typing import Tuple
|
|
9
9
|
|
10
10
|
import numpy as np
|
11
11
|
import pandas as pd
|
12
|
-
import scipy.interpolate
|
13
12
|
from PIL import Image
|
14
13
|
from PIL import ImageEnhance
|
15
14
|
from tqdm import tqdm
|
16
15
|
|
17
16
|
from ..core.tiles import get_tile
|
18
17
|
|
18
|
+
# import scipy.interpolate
|
19
|
+
|
19
20
|
|
20
21
|
def build_image(
|
21
22
|
center_x: float,
|
@@ -146,7 +146,7 @@ def float_with_comma_or_period(x: str) -> Optional[float]:
|
|
146
146
|
|
147
147
|
def import_from_strava_checkout() -> None:
|
148
148
|
checkout_path = pathlib.Path("Strava Export")
|
149
|
-
with open(checkout_path / "activities.csv") as f:
|
149
|
+
with open(checkout_path / "activities.csv", encoding="utf-8") as f:
|
150
150
|
rows = parse_csv(f.read())
|
151
151
|
header = rows[0]
|
152
152
|
|
@@ -159,9 +159,15 @@ def import_from_strava_checkout() -> None:
|
|
159
159
|
|
160
160
|
if header[0] == EXPECTED_COLUMNS[0]:
|
161
161
|
dayfirst = False
|
162
|
-
|
162
|
+
elif header[0] == "Aktivitäts-ID":
|
163
163
|
header = EXPECTED_COLUMNS
|
164
164
|
dayfirst = True
|
165
|
+
else:
|
166
|
+
logger.error(
|
167
|
+
f"You are trying to import a Strava checkout where the `activities.csv` contains an unexpected header format. In order to import this, we need to map these to the English ones. Unfortunately Strava often changes the number of columns. This means that the program needs to be updated to match the new Strava export format. Please go to https://github.com/martin-ueding/geo-activity-playground/issues and open a new issue and share the following output in the ticket:"
|
168
|
+
)
|
169
|
+
print(header)
|
170
|
+
sys.exit(1)
|
165
171
|
|
166
172
|
table = {
|
167
173
|
header[i]: [rows[r][i] for r in range(1, len(rows))] for i in range(len(header))
|
@@ -55,6 +55,13 @@ def make_activity_blueprint(
|
|
55
55
|
**activity_controller.render_day(int(year), int(month), int(day)),
|
56
56
|
)
|
57
57
|
|
58
|
+
@blueprint.route("/day-sharepic/<year>/<month>/<day>/sharepic.png")
|
59
|
+
def day_sharepic(year: str, month: str, day: str):
|
60
|
+
return Response(
|
61
|
+
activity_controller.render_day_sharepic(int(year), int(month), int(day)),
|
62
|
+
mimetype="image/png",
|
63
|
+
)
|
64
|
+
|
58
65
|
@blueprint.route("/name/<name>")
|
59
66
|
def name(name: str):
|
60
67
|
return render_template(
|
@@ -170,8 +170,24 @@ class ActivityController:
|
|
170
170
|
"date": datetime.date(year, month, day).isoformat(),
|
171
171
|
"total_distance": activities_that_day["distance_km"].sum(),
|
172
172
|
"total_elapsed_time": activities_that_day["elapsed_time"].sum(),
|
173
|
+
"day": day,
|
174
|
+
"month": month,
|
175
|
+
"year": year,
|
173
176
|
}
|
174
177
|
|
178
|
+
def render_day_sharepic(self, year: int, month: int, day: int) -> bytes:
|
179
|
+
meta = self._repository.meta
|
180
|
+
selection = meta["start"].dt.date == datetime.date(year, month, day)
|
181
|
+
activities_that_day = meta.loc[selection]
|
182
|
+
|
183
|
+
time_series = [
|
184
|
+
self._repository.get_time_series(activity_id)
|
185
|
+
for activity_id in activities_that_day["id"]
|
186
|
+
]
|
187
|
+
assert len(activities_that_day) > 0
|
188
|
+
assert len(time_series) > 0
|
189
|
+
return (make_day_sharepic(activities_that_day, time_series, self._config),)
|
190
|
+
|
175
191
|
def render_all(self) -> dict:
|
176
192
|
cmap = matplotlib.colormaps["Dark2"]
|
177
193
|
fc = geojson.FeatureCollection(
|
@@ -452,14 +468,10 @@ def pixels_in_bounds(bounds: PixelBounds) -> int:
|
|
452
468
|
return (bounds.x_max - bounds.x_min) * (bounds.y_max - bounds.y_min)
|
453
469
|
|
454
470
|
|
455
|
-
def
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
config: Config,
|
460
|
-
) -> bytes:
|
461
|
-
tile_x = time_series["x"]
|
462
|
-
tile_y = time_series["y"]
|
471
|
+
def make_sharepic_base(time_series_list: list[pd.DataFrame], config: Config):
|
472
|
+
all_time_series = pd.concat(time_series_list)
|
473
|
+
tile_x = all_time_series["x"]
|
474
|
+
tile_y = all_time_series["y"]
|
463
475
|
tile_width = tile_x.max() - tile_x.min()
|
464
476
|
tile_height = tile_y.max() - tile_y.min()
|
465
477
|
|
@@ -495,16 +507,32 @@ def make_sharepic(
|
|
495
507
|
img = Image.fromarray((background * 255).astype("uint8"), "RGB")
|
496
508
|
draw = ImageDraw.Draw(img, mode="RGBA")
|
497
509
|
|
498
|
-
for
|
499
|
-
|
500
|
-
|
501
|
-
(
|
502
|
-
|
510
|
+
for time_series in time_series_list:
|
511
|
+
for _, group in time_series.groupby("segment_id"):
|
512
|
+
yx = list(
|
513
|
+
zip(
|
514
|
+
(tile_xz - tile_xz_center[0]) * OSM_TILE_SIZE + target_width / 2,
|
515
|
+
(tile_yz - tile_xz_center[1]) * OSM_TILE_SIZE
|
516
|
+
+ target_map_height / 2,
|
517
|
+
)
|
503
518
|
)
|
504
|
-
)
|
505
519
|
|
506
|
-
|
520
|
+
draw.line(yx, fill="red", width=4)
|
521
|
+
|
522
|
+
return img
|
523
|
+
|
507
524
|
|
525
|
+
def make_sharepic(
|
526
|
+
activity: ActivityMeta,
|
527
|
+
time_series: pd.DataFrame,
|
528
|
+
sharepic_suppressed_fields: list[str],
|
529
|
+
config: Config,
|
530
|
+
) -> bytes:
|
531
|
+
footer_height = 100
|
532
|
+
|
533
|
+
img = make_sharepic_base([time_series], config)
|
534
|
+
|
535
|
+
draw = ImageDraw.Draw(img, mode="RGBA")
|
508
536
|
draw.rectangle(
|
509
537
|
[0, img.height - footer_height, img.width, img.height], fill=(0, 0, 0, 180)
|
510
538
|
)
|
@@ -545,6 +573,48 @@ def make_sharepic(
|
|
545
573
|
return bytes(f.getbuffer())
|
546
574
|
|
547
575
|
|
576
|
+
def make_day_sharepic(
|
577
|
+
activities: pd.DataFrame,
|
578
|
+
time_series_list: list[pd.DataFrame],
|
579
|
+
config: Config,
|
580
|
+
) -> bytes:
|
581
|
+
footer_height = 100
|
582
|
+
|
583
|
+
img = make_sharepic_base(time_series_list, config)
|
584
|
+
|
585
|
+
draw = ImageDraw.Draw(img, mode="RGBA")
|
586
|
+
draw.rectangle(
|
587
|
+
[0, img.height - footer_height, img.width, img.height], fill=(0, 0, 0, 180)
|
588
|
+
)
|
589
|
+
|
590
|
+
date = activities.iloc[0]["start"].date()
|
591
|
+
distance_km = activities["distance_km"].sum()
|
592
|
+
elapsed_time: pd.Timedelta = activities["elapsed_time"].sum()
|
593
|
+
elapsed_time = elapsed_time.round("s")
|
594
|
+
|
595
|
+
facts = {
|
596
|
+
"date": f"{date}",
|
597
|
+
"distance_km": f"{distance_km:.1f} km",
|
598
|
+
"elapsed_time": re.sub(r"^0 days ", "", f"{elapsed_time}"),
|
599
|
+
}
|
600
|
+
|
601
|
+
draw.text(
|
602
|
+
(35, img.height - footer_height + 10),
|
603
|
+
" ".join(facts.values()),
|
604
|
+
font_size=20,
|
605
|
+
)
|
606
|
+
|
607
|
+
draw.text(
|
608
|
+
(img.width - 250, img.height - 20),
|
609
|
+
"Map: © Open Street Map Contributors",
|
610
|
+
font_size=14,
|
611
|
+
)
|
612
|
+
|
613
|
+
f = io.BytesIO()
|
614
|
+
img.save(f, format="png")
|
615
|
+
return bytes(f.getbuffer())
|
616
|
+
|
617
|
+
|
548
618
|
def _extract_heart_rate_zones(
|
549
619
|
time_series: pd.DataFrame, heart_rate_zone_computer: HeartRateZoneComputer
|
550
620
|
) -> Optional[pd.DataFrame]:
|
@@ -30,7 +30,7 @@
|
|
30
30
|
<ol>
|
31
31
|
{% for activity in activities %}
|
32
32
|
<li><span style="color: {{ activity['color'] }};">█</span> <a
|
33
|
-
href="{{ url_for('
|
33
|
+
href="{{ url_for('.show', id=activity.id) }}">{{
|
34
34
|
activity.name }}</a></li>
|
35
35
|
{% endfor %}
|
36
36
|
</ol>
|
@@ -80,4 +80,8 @@
|
|
80
80
|
</div>
|
81
81
|
</div>
|
82
82
|
|
83
|
+
<h2>Share picture</h2>
|
84
|
+
|
85
|
+
<p><img src="{{ url_for('.day_sharepic', year=year, month=month, day=day) }}" /></p>
|
86
|
+
|
83
87
|
{% endblock %}
|
@@ -0,0 +1 @@
|
|
1
|
+
L.Control.Fullscreen=L.Control.extend({options:{position:"topleft",title:{"false":"View Fullscreen","true":"Exit Fullscreen"}},onAdd:function(map){var container=L.DomUtil.create("div","leaflet-control-fullscreen leaflet-bar leaflet-control");this.link=L.DomUtil.create("a","leaflet-control-fullscreen-button leaflet-bar-part",container);this.link.href="#";this._map=map;this._map.on("fullscreenchange",this._toggleTitle,this);this._toggleTitle();L.DomEvent.on(this.link,"click",this._click,this);return container},_click:function(e){L.DomEvent.stopPropagation(e);L.DomEvent.preventDefault(e);this._map.toggleFullscreen(this.options)},_toggleTitle:function(){this.link.title=this.options.title[this._map.isFullscreen()]}});L.Map.include({isFullscreen:function(){return this._isFullscreen||false},toggleFullscreen:function(options){var container=this.getContainer();if(this.isFullscreen()){if(options&&options.pseudoFullscreen){this._disablePseudoFullscreen(container)}else if(document.exitFullscreen){document.exitFullscreen()}else if(document.mozCancelFullScreen){document.mozCancelFullScreen()}else if(document.webkitCancelFullScreen){document.webkitCancelFullScreen()}else if(document.msExitFullscreen){document.msExitFullscreen()}else{this._disablePseudoFullscreen(container)}}else{if(options&&options.pseudoFullscreen){this._enablePseudoFullscreen(container)}else if(container.requestFullscreen){container.requestFullscreen()}else if(container.mozRequestFullScreen){container.mozRequestFullScreen()}else if(container.webkitRequestFullscreen){container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}else if(container.msRequestFullscreen){container.msRequestFullscreen()}else{this._enablePseudoFullscreen(container)}}},_enablePseudoFullscreen:function(container){L.DomUtil.addClass(container,"leaflet-pseudo-fullscreen");this._setFullscreen(true);this.invalidateSize();this.fire("fullscreenchange")},_disablePseudoFullscreen:function(container){L.DomUtil.removeClass(container,"leaflet-pseudo-fullscreen");this._setFullscreen(false);this.invalidateSize();this.fire("fullscreenchange")},_setFullscreen:function(fullscreen){this._isFullscreen=fullscreen;var container=this.getContainer();if(fullscreen){L.DomUtil.addClass(container,"leaflet-fullscreen-on")}else{L.DomUtil.removeClass(container,"leaflet-fullscreen-on")}},_onFullscreenChange:function(e){var fullscreenElement=document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement;if(fullscreenElement===this.getContainer()&&!this._isFullscreen){this._setFullscreen(true);this.fire("fullscreenchange")}else if(fullscreenElement!==this.getContainer()&&this._isFullscreen){this._setFullscreen(false);this.fire("fullscreenchange")}}});L.Map.mergeOptions({fullscreenControl:false});L.Map.addInitHook(function(){if(this.options.fullscreenControl){this.fullscreenControl=new L.Control.Fullscreen(this.options.fullscreenControl);this.addControl(this.fullscreenControl)}var fullscreenchange;if("onfullscreenchange"in document){fullscreenchange="fullscreenchange"}else if("onmozfullscreenchange"in document){fullscreenchange="mozfullscreenchange"}else if("onwebkitfullscreenchange"in document){fullscreenchange="webkitfullscreenchange"}else if("onmsfullscreenchange"in document){fullscreenchange="MSFullscreenChange"}if(fullscreenchange){var onFullscreenChange=L.bind(this._onFullscreenChange,this);this.whenReady(function(){L.DomEvent.on(document,fullscreenchange,onFullscreenChange)});this.on("unload",function(){L.DomEvent.off(document,fullscreenchange,onFullscreenChange)})}});L.control.fullscreen=function(options){return new L.Control.Fullscreen(options)};
|
@@ -0,0 +1,60 @@
|
|
1
|
+
.marker-cluster-small {
|
2
|
+
background-color: rgba(181, 226, 140, 0.6);
|
3
|
+
}
|
4
|
+
.marker-cluster-small div {
|
5
|
+
background-color: rgba(110, 204, 57, 0.6);
|
6
|
+
}
|
7
|
+
|
8
|
+
.marker-cluster-medium {
|
9
|
+
background-color: rgba(241, 211, 87, 0.6);
|
10
|
+
}
|
11
|
+
.marker-cluster-medium div {
|
12
|
+
background-color: rgba(240, 194, 12, 0.6);
|
13
|
+
}
|
14
|
+
|
15
|
+
.marker-cluster-large {
|
16
|
+
background-color: rgba(253, 156, 115, 0.6);
|
17
|
+
}
|
18
|
+
.marker-cluster-large div {
|
19
|
+
background-color: rgba(241, 128, 23, 0.6);
|
20
|
+
}
|
21
|
+
|
22
|
+
/* IE 6-8 fallback colors */
|
23
|
+
.leaflet-oldie .marker-cluster-small {
|
24
|
+
background-color: rgb(181, 226, 140);
|
25
|
+
}
|
26
|
+
.leaflet-oldie .marker-cluster-small div {
|
27
|
+
background-color: rgb(110, 204, 57);
|
28
|
+
}
|
29
|
+
|
30
|
+
.leaflet-oldie .marker-cluster-medium {
|
31
|
+
background-color: rgb(241, 211, 87);
|
32
|
+
}
|
33
|
+
.leaflet-oldie .marker-cluster-medium div {
|
34
|
+
background-color: rgb(240, 194, 12);
|
35
|
+
}
|
36
|
+
|
37
|
+
.leaflet-oldie .marker-cluster-large {
|
38
|
+
background-color: rgb(253, 156, 115);
|
39
|
+
}
|
40
|
+
.leaflet-oldie .marker-cluster-large div {
|
41
|
+
background-color: rgb(241, 128, 23);
|
42
|
+
}
|
43
|
+
|
44
|
+
.marker-cluster {
|
45
|
+
background-clip: padding-box;
|
46
|
+
border-radius: 20px;
|
47
|
+
}
|
48
|
+
.marker-cluster div {
|
49
|
+
width: 30px;
|
50
|
+
height: 30px;
|
51
|
+
margin-left: 5px;
|
52
|
+
margin-top: 5px;
|
53
|
+
|
54
|
+
text-align: center;
|
55
|
+
border-radius: 15px;
|
56
|
+
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
|
57
|
+
}
|
58
|
+
.marker-cluster span {
|
59
|
+
line-height: 30px;
|
60
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
|
2
|
+
-webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
|
3
|
+
-moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
|
4
|
+
-o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
|
5
|
+
transition: transform 0.3s ease-out, opacity 0.3s ease-in;
|
6
|
+
}
|
7
|
+
|
8
|
+
.leaflet-cluster-spider-leg {
|
9
|
+
/* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
|
10
|
+
-webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
|
11
|
+
-moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
|
12
|
+
-o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
|
13
|
+
transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
|
14
|
+
}
|