geo-activity-playground 0.43.3__py3-none-any.whl → 0.45.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/alembic/versions/0f02b92c4f94_add_tag_color.py +28 -0
- geo_activity_playground/core/config.py +1 -0
- geo_activity_playground/core/datamodel.py +16 -2
- geo_activity_playground/core/enrichment.py +2 -2
- geo_activity_playground/core/meta_search.py +1 -1
- geo_activity_playground/core/paths.py +1 -1
- geo_activity_playground/importers/directory.py +3 -2
- geo_activity_playground/importers/strava_checkout.py +2 -1
- geo_activity_playground/webui/blueprints/activity_blueprint.py +22 -0
- geo_activity_playground/webui/blueprints/eddington_blueprints.py +17 -8
- geo_activity_playground/webui/blueprints/entry_views.py +5 -3
- geo_activity_playground/webui/blueprints/equipment_blueprint.py +1 -1
- geo_activity_playground/webui/blueprints/settings_blueprint.py +83 -3
- geo_activity_playground/webui/blueprints/upload_blueprint.py +7 -3
- geo_activity_playground/webui/templates/activity/show.html.j2 +6 -2
- geo_activity_playground/webui/templates/home.html.j2 +2 -0
- geo_activity_playground/webui/templates/page.html.j2 +4 -0
- geo_activity_playground/webui/templates/search_form.html.j2 +1 -1
- geo_activity_playground/webui/templates/settings/tags-edit.html.j2 +5 -0
- geo_activity_playground/webui/templates/settings/tags-list.html.j2 +1 -1
- geo_activity_playground/webui/templates/summary/index.html.j2 +2 -0
- {geo_activity_playground-0.43.3.dist-info → geo_activity_playground-0.45.0.dist-info}/METADATA +1 -1
- {geo_activity_playground-0.43.3.dist-info → geo_activity_playground-0.45.0.dist-info}/RECORD +26 -25
- {geo_activity_playground-0.43.3.dist-info → geo_activity_playground-0.45.0.dist-info}/LICENSE +0 -0
- {geo_activity_playground-0.43.3.dist-info → geo_activity_playground-0.45.0.dist-info}/WHEEL +0 -0
- {geo_activity_playground-0.43.3.dist-info → geo_activity_playground-0.45.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
from typing import Sequence
|
2
|
+
from typing import Union
|
3
|
+
|
4
|
+
import sqlalchemy as sa
|
5
|
+
from alembic import op
|
6
|
+
|
7
|
+
|
8
|
+
# revision identifiers, used by Alembic.
|
9
|
+
revision: str = "0f02b92c4f94"
|
10
|
+
down_revision: Union[str, None] = "da2cba03b71d"
|
11
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
12
|
+
depends_on: Union[str, Sequence[str], None] = None
|
13
|
+
|
14
|
+
|
15
|
+
def upgrade() -> None:
|
16
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
17
|
+
with op.batch_alter_table("tags", schema=None) as batch_op:
|
18
|
+
batch_op.add_column(sa.Column("color", sa.String(), nullable=True))
|
19
|
+
|
20
|
+
# ### end Alembic commands ###
|
21
|
+
|
22
|
+
|
23
|
+
def downgrade() -> None:
|
24
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
25
|
+
with op.batch_alter_table("tags", schema=None) as batch_op:
|
26
|
+
batch_op.drop_column("color")
|
27
|
+
|
28
|
+
# ### end Alembic commands ###
|
@@ -38,6 +38,7 @@ class Config:
|
|
38
38
|
privacy_zones: dict[str, list[list[float]]] = dataclasses.field(
|
39
39
|
default_factory=dict
|
40
40
|
)
|
41
|
+
reliable_elevation_measurements: bool = True
|
41
42
|
sharepic_suppressed_fields: list[str] = dataclasses.field(default_factory=list)
|
42
43
|
strava_client_id: int = 131693
|
43
44
|
strava_client_secret: str = "0ccc0100a2c218512a7ef0cea3b0e322fb4b4365"
|
@@ -21,12 +21,17 @@ from sqlalchemy.orm import mapped_column
|
|
21
21
|
from sqlalchemy.orm import relationship
|
22
22
|
|
23
23
|
from .config import Config
|
24
|
-
from .paths import
|
24
|
+
from .paths import activity_extracted_meta_dir
|
25
|
+
from .paths import activity_extracted_time_series_dir
|
26
|
+
from .paths import TIME_SERIES_DIR
|
25
27
|
|
26
28
|
|
27
29
|
logger = logging.getLogger(__name__)
|
28
30
|
|
29
31
|
|
32
|
+
DEFAULT_UNKNOWN_NAME = "Unknown"
|
33
|
+
|
34
|
+
|
30
35
|
class ActivityMeta(TypedDict):
|
31
36
|
average_speed_elapsed_kmh: float
|
32
37
|
average_speed_moving_kmh: float
|
@@ -141,7 +146,7 @@ class Activity(DB.Model):
|
|
141
146
|
|
142
147
|
@property
|
143
148
|
def raw_time_series(self) -> pd.DataFrame:
|
144
|
-
path =
|
149
|
+
path = TIME_SERIES_DIR() / f"{self.id}.parquet"
|
145
150
|
try:
|
146
151
|
time_series = pd.read_parquet(path)
|
147
152
|
if "altitude" in time_series.columns:
|
@@ -160,6 +165,14 @@ class Activity(DB.Model):
|
|
160
165
|
else:
|
161
166
|
return self.raw_time_series
|
162
167
|
|
168
|
+
def delete_data(self) -> None:
|
169
|
+
for path in [
|
170
|
+
TIME_SERIES_DIR() / f"{self.id}.parquet",
|
171
|
+
activity_extracted_meta_dir() / f"{self.upstream_id}.pickle",
|
172
|
+
activity_extracted_time_series_dir() / f"{self.upstream_id}.pickle",
|
173
|
+
]:
|
174
|
+
path.unlink(missing_ok=True)
|
175
|
+
|
163
176
|
|
164
177
|
class Tag(DB.Model):
|
165
178
|
__tablename__ = "tags"
|
@@ -167,6 +180,7 @@ class Tag(DB.Model):
|
|
167
180
|
|
168
181
|
id: Mapped[int] = mapped_column(primary_key=True)
|
169
182
|
tag: Mapped[str] = mapped_column(String, unique=True)
|
183
|
+
color: Mapped[str] = mapped_column(String, nullable=True)
|
170
184
|
|
171
185
|
activities: Mapped[list[Activity]] = relationship(
|
172
186
|
secondary=activity_tag_association_table, back_populates="tags"
|
@@ -18,7 +18,7 @@ from .datamodel import get_or_make_kind
|
|
18
18
|
from .missing_values import some
|
19
19
|
from .paths import activity_extracted_meta_dir
|
20
20
|
from .paths import activity_extracted_time_series_dir
|
21
|
-
from .paths import
|
21
|
+
from .paths import TIME_SERIES_DIR
|
22
22
|
from .tiles import compute_tile_float
|
23
23
|
from .time_conversion import convert_to_datetime_ns
|
24
24
|
|
@@ -101,7 +101,7 @@ def populate_database_from_extracted(config: Config) -> None:
|
|
101
101
|
)
|
102
102
|
raise
|
103
103
|
|
104
|
-
enriched_time_series_path =
|
104
|
+
enriched_time_series_path = TIME_SERIES_DIR() / f"{activity.id}.parquet"
|
105
105
|
time_series.to_parquet(enriched_time_series_path)
|
106
106
|
|
107
107
|
|
@@ -120,7 +120,7 @@ class SearchQuery:
|
|
120
120
|
variables.append(("distance_km_max", self.distance_km_max))
|
121
121
|
|
122
122
|
return "&".join(
|
123
|
-
f"{key}={urllib.parse.quote_plus(value)}" for key, value in variables
|
123
|
+
f"{key}={urllib.parse.quote_plus(str(value))}" for key, value in variables
|
124
124
|
)
|
125
125
|
|
126
126
|
|
@@ -65,7 +65,7 @@ activity_enriched_time_series_dir = dir_wrapper(_activity_enriched_time_series_d
|
|
65
65
|
tiles_per_time_series = dir_wrapper(_tiles_per_time_series)
|
66
66
|
strava_api_dir = dir_wrapper(_strava_api_dir)
|
67
67
|
activity_meta_override_dir = dir_wrapper(_activity_meta_override_dir)
|
68
|
-
|
68
|
+
TIME_SERIES_DIR = dir_wrapper(_time_series_dir)
|
69
69
|
PHOTOS_DIR = dir_wrapper(_photos_dir)
|
70
70
|
|
71
71
|
activities_file = file_wrapper(_activities_file)
|
@@ -10,6 +10,7 @@ from tqdm import tqdm
|
|
10
10
|
|
11
11
|
from ..core.config import Config
|
12
12
|
from ..core.datamodel import ActivityMeta
|
13
|
+
from ..core.datamodel import DEFAULT_UNKNOWN_NAME
|
13
14
|
from ..core.paths import activity_extracted_dir
|
14
15
|
from ..core.paths import activity_extracted_meta_dir
|
15
16
|
from ..core.paths import activity_extracted_time_series_dir
|
@@ -84,8 +85,8 @@ def import_from_directory(
|
|
84
85
|
# https://stackoverflow.com/a/74718395/653152
|
85
86
|
name=path.name.removesuffix("".join(path.suffixes)),
|
86
87
|
path=str(path),
|
87
|
-
kind=
|
88
|
-
equipment=
|
88
|
+
kind=DEFAULT_UNKNOWN_NAME,
|
89
|
+
equipment=DEFAULT_UNKNOWN_NAME,
|
89
90
|
consider_for_achievements=True,
|
90
91
|
)
|
91
92
|
activity_meta.update(activity_meta_from_file)
|
@@ -13,6 +13,7 @@ import pandas as pd
|
|
13
13
|
from tqdm import tqdm
|
14
14
|
|
15
15
|
from ..core.datamodel import ActivityMeta
|
16
|
+
from ..core.datamodel import DEFAULT_UNKNOWN_NAME
|
16
17
|
from ..core.paths import activity_extracted_meta_dir
|
17
18
|
from ..core.paths import activity_extracted_time_series_dir
|
18
19
|
from ..core.paths import strava_last_activity_date_path
|
@@ -277,7 +278,7 @@ def convert_strava_checkout(
|
|
277
278
|
nan_as_none(row["Activity Gear"])
|
278
279
|
or nan_as_none(row["Bike"])
|
279
280
|
or nan_as_none(row["Gear"])
|
280
|
-
or
|
281
|
+
or DEFAULT_UNKNOWN_NAME
|
281
282
|
)
|
282
283
|
activity_file = checkout_path / row["Filename"]
|
283
284
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import datetime
|
2
2
|
import io
|
3
3
|
import logging
|
4
|
+
import pathlib
|
4
5
|
import re
|
5
6
|
from typing import Optional
|
6
7
|
|
@@ -431,6 +432,27 @@ def make_activity_blueprint(
|
|
431
432
|
color_line_geojson=geojson.dumps(fc),
|
432
433
|
)
|
433
434
|
|
435
|
+
@blueprint.route("/delete/<id>")
|
436
|
+
@needs_authentication(authenticator)
|
437
|
+
def delete(id: int):
|
438
|
+
activity = DB.session.get_one(Activity, id)
|
439
|
+
activity.delete_data()
|
440
|
+
DB.session.delete(activity)
|
441
|
+
DB.session.commit()
|
442
|
+
return redirect(url_for("index"))
|
443
|
+
|
444
|
+
@blueprint.route("/download-original/<id>")
|
445
|
+
@needs_authentication(authenticator)
|
446
|
+
def download_original(id: int):
|
447
|
+
activity = DB.session.get_one(Activity, id)
|
448
|
+
path = pathlib.Path(activity.path)
|
449
|
+
with open(path) as f:
|
450
|
+
return Response(
|
451
|
+
f.read(),
|
452
|
+
mimetype="application/octet-stream",
|
453
|
+
headers={"Content-disposition": f'attachment; filename="{path.name}"'},
|
454
|
+
)
|
455
|
+
|
434
456
|
return blueprint
|
435
457
|
|
436
458
|
|
@@ -26,7 +26,12 @@ def register_eddington_blueprint(
|
|
26
26
|
@blueprint.route("/")
|
27
27
|
def distance():
|
28
28
|
return _render_eddington_template(
|
29
|
-
repository,
|
29
|
+
repository,
|
30
|
+
request,
|
31
|
+
search_query_history,
|
32
|
+
"distance",
|
33
|
+
column_distance,
|
34
|
+
[1],
|
30
35
|
)
|
31
36
|
|
32
37
|
@blueprint.route("/elevation_gain")
|
@@ -64,6 +69,10 @@ def _render_eddington_template(
|
|
64
69
|
.copy()
|
65
70
|
)
|
66
71
|
|
72
|
+
assert (
|
73
|
+
len(activities) > 0
|
74
|
+
), "The filter has selected zero elements, that cannot work here."
|
75
|
+
|
67
76
|
activities["year"] = [start.year for start in activities["start"]]
|
68
77
|
activities["date"] = [start.date() for start in activities["start"]]
|
69
78
|
activities["isoyear"] = [start.isocalendar().year for start in activities["start"]]
|
@@ -111,24 +120,24 @@ def _render_eddington_template(
|
|
111
120
|
)
|
112
121
|
|
113
122
|
|
114
|
-
def _get_values_per_group(grouped,
|
123
|
+
def _get_values_per_group(grouped, column_name, divisor) -> tuple[int, pd.DataFrame]:
|
115
124
|
sum_per_group = grouped.apply(
|
116
|
-
lambda group: int(sum(group[
|
125
|
+
lambda group: int(sum(group[column_name])), include_groups=False
|
117
126
|
)
|
118
127
|
counts = dict(zip(*np.unique(sorted(sum_per_group), return_counts=True)))
|
119
128
|
eddington = pd.DataFrame(
|
120
|
-
{
|
129
|
+
{column_name: d, "count": counts.get(d, 0)}
|
121
130
|
for d in range(max(counts.keys()) + 1)
|
122
131
|
)
|
123
132
|
eddington["total"] = eddington["count"][::-1].cumsum()[::-1]
|
124
|
-
eddington[f"{
|
133
|
+
eddington[f"{column_name}_div"] = eddington[column_name] // divisor
|
125
134
|
en = (
|
126
|
-
eddington.loc[eddington["total"] >= eddington[f"{
|
127
|
-
"
|
135
|
+
eddington.loc[eddington["total"] >= eddington[f"{column_name}_div"]][
|
136
|
+
f"{column_name}_div"
|
128
137
|
].iloc[-1]
|
129
138
|
* divisor
|
130
139
|
)
|
131
|
-
eddington["missing"] = eddington[f"{
|
140
|
+
eddington["missing"] = eddington[f"{column_name}_div"] - eddington["total"]
|
132
141
|
|
133
142
|
return en, eddington
|
134
143
|
|
@@ -31,9 +31,11 @@ def register_entry_views(
|
|
31
31
|
context["distance_last_30_days_plot"] = _last_30_days_meta_plot(
|
32
32
|
repository.meta, kind_scale, column_distance
|
33
33
|
)
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
|
35
|
+
if config.reliable_elevation_measurements:
|
36
|
+
context["elevation_gain_last_30_days_plot"] = _last_30_days_meta_plot(
|
37
|
+
repository.meta, kind_scale, column_elevation_gain
|
38
|
+
)
|
37
39
|
|
38
40
|
context["latest_activities"] = collections.defaultdict(list)
|
39
41
|
for activity in DB.session.scalars(
|
@@ -21,7 +21,7 @@ def make_equipment_blueprint(
|
|
21
21
|
)
|
22
22
|
|
23
23
|
# Prepare data for the stacked area chart
|
24
|
-
activities = repository.meta
|
24
|
+
activities = repository.meta.dropna(subset=["start"])
|
25
25
|
activities["month"] = (
|
26
26
|
activities["start"].dt.to_period("M").apply(lambda r: r.start_time)
|
27
27
|
)
|
@@ -13,14 +13,19 @@ from flask import render_template
|
|
13
13
|
from flask import request
|
14
14
|
from flask import Response
|
15
15
|
from flask import url_for
|
16
|
+
from tqdm import tqdm
|
16
17
|
|
17
18
|
from ...core.config import ConfigAccessor
|
19
|
+
from ...core.datamodel import Activity
|
18
20
|
from ...core.datamodel import DB
|
19
21
|
from ...core.datamodel import Equipment
|
20
22
|
from ...core.datamodel import Kind
|
21
23
|
from ...core.datamodel import Tag
|
24
|
+
from ...core.enrichment import _embellish_single_time_series
|
25
|
+
from ...core.enrichment import update_via_time_series
|
22
26
|
from ...core.heart_rate import HeartRateZoneComputer
|
23
27
|
from ...core.paths import _activity_enriched_dir
|
28
|
+
from ...core.paths import TIME_SERIES_DIR
|
24
29
|
from ..authenticator import Authenticator
|
25
30
|
from ..authenticator import needs_authentication
|
26
31
|
from ..flasher import Flasher
|
@@ -272,7 +277,7 @@ def make_settings_blueprint(
|
|
272
277
|
if request.method == "POST":
|
273
278
|
zone_names = request.form.getlist("zone_name")
|
274
279
|
zone_geojsons = request.form.getlist("zone_geojson")
|
275
|
-
|
280
|
+
save_privacy_zones(zone_names, zone_geojsons, config_accessor)
|
276
281
|
|
277
282
|
assert len(zone_names) == len(zone_geojsons)
|
278
283
|
new_zone_config = {}
|
@@ -348,8 +353,19 @@ def make_settings_blueprint(
|
|
348
353
|
config_accessor().time_diff_threshold_seconds = threshold
|
349
354
|
config_accessor.save()
|
350
355
|
flash(f"Threshold set to {threshold}.", category="success")
|
351
|
-
|
352
|
-
|
356
|
+
for activity in tqdm(
|
357
|
+
DB.session.scalars(sqlalchemy.select(Activity)).all(),
|
358
|
+
desc="Recomputing segments",
|
359
|
+
):
|
360
|
+
time_series = _embellish_single_time_series(
|
361
|
+
activity.raw_time_series,
|
362
|
+
None,
|
363
|
+
threshold,
|
364
|
+
)
|
365
|
+
update_via_time_series(activity, time_series)
|
366
|
+
enriched_time_series_path = TIME_SERIES_DIR() / f"{activity.id}.parquet"
|
367
|
+
time_series.to_parquet(enriched_time_series_path)
|
368
|
+
DB.session.commit()
|
353
369
|
return render_template(
|
354
370
|
"settings/segmentation.html.j2",
|
355
371
|
threshold=config_accessor().time_diff_threshold_seconds,
|
@@ -395,6 +411,7 @@ def make_settings_blueprint(
|
|
395
411
|
@needs_authentication(authenticator)
|
396
412
|
def strava_callback():
|
397
413
|
code = request.args.get("code", type=str)
|
414
|
+
assert code
|
398
415
|
strava_login_helper.save_strava_code(code)
|
399
416
|
return redirect(url_for(".strava"))
|
400
417
|
|
@@ -424,6 +441,7 @@ def make_settings_blueprint(
|
|
424
441
|
tag = DB.session.get_one(Tag, id)
|
425
442
|
if request.method == "POST":
|
426
443
|
tag.tag = request.form["tag"]
|
444
|
+
tag.color = request.form["color"]
|
427
445
|
DB.session.commit()
|
428
446
|
return redirect(url_for(".tags_list"))
|
429
447
|
else:
|
@@ -478,3 +496,65 @@ class StravaLoginHelper:
|
|
478
496
|
self._config_accessor().strava_client_code = code
|
479
497
|
self._config_accessor.save()
|
480
498
|
flash("Connected to Strava API", category="success")
|
499
|
+
|
500
|
+
|
501
|
+
def save_privacy_zones(
|
502
|
+
zone_names: list[str], zone_geojsons: list[str], config_accessor: ConfigAccessor
|
503
|
+
) -> None:
|
504
|
+
assert len(zone_names) == len(zone_geojsons)
|
505
|
+
new_zone_config = {}
|
506
|
+
|
507
|
+
for zone_name, zone_geojson_str in zip(zone_names, zone_geojsons):
|
508
|
+
if not zone_name or not zone_geojson_str:
|
509
|
+
continue
|
510
|
+
|
511
|
+
try:
|
512
|
+
zone_geojson = json.loads(zone_geojson_str)
|
513
|
+
except json.decoder.JSONDecodeError as e:
|
514
|
+
flash(
|
515
|
+
f"Could not parse GeoJSON for {zone_name} due to the following error: {e}"
|
516
|
+
)
|
517
|
+
continue
|
518
|
+
|
519
|
+
if not zone_geojson["type"] == "FeatureCollection":
|
520
|
+
flash(
|
521
|
+
f"Pasted GeoJSON for {zone_name} must be of type 'FeatureCollection'.",
|
522
|
+
category="danger",
|
523
|
+
)
|
524
|
+
continue
|
525
|
+
|
526
|
+
features = zone_geojson["features"]
|
527
|
+
|
528
|
+
if not len(features) == 1:
|
529
|
+
flash(
|
530
|
+
f"Pasted GeoJSON for {zone_name} must contain exactly one feature. You cannot have multiple shapes for one privacy zone",
|
531
|
+
category="danger",
|
532
|
+
)
|
533
|
+
continue
|
534
|
+
|
535
|
+
feature = features[0]
|
536
|
+
geometry = feature["geometry"]
|
537
|
+
|
538
|
+
if not geometry["type"] == "Polygon":
|
539
|
+
flash(
|
540
|
+
f"Geometry for {zone_name} is not a polygon. You need to create a polygon (or circle or rectangle).",
|
541
|
+
category="danger",
|
542
|
+
)
|
543
|
+
continue
|
544
|
+
|
545
|
+
coordinates = geometry["coordinates"]
|
546
|
+
|
547
|
+
if not len(coordinates) == 1:
|
548
|
+
flash(
|
549
|
+
f"Polygon for {zone_name} consists of multiple polygons. Please supply a simple one.",
|
550
|
+
category="danger",
|
551
|
+
)
|
552
|
+
continue
|
553
|
+
|
554
|
+
points = coordinates[0]
|
555
|
+
|
556
|
+
new_zone_config[zone_name] = points
|
557
|
+
|
558
|
+
config_accessor().privacy_zones = new_zone_config
|
559
|
+
config_accessor.save()
|
560
|
+
flash("Updated privacy zones.", category="success")
|
@@ -11,6 +11,7 @@ from flask import url_for
|
|
11
11
|
|
12
12
|
from ...core.activities import ActivityRepository
|
13
13
|
from ...core.config import Config
|
14
|
+
from ...core.datamodel import Activity
|
14
15
|
from ...core.datamodel import DB
|
15
16
|
from ...core.datamodel import Kind
|
16
17
|
from ...core.enrichment import populate_database_from_extracted
|
@@ -78,9 +79,12 @@ def make_upload_blueprint(
|
|
78
79
|
config,
|
79
80
|
skip_strava=True,
|
80
81
|
)
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
latest_activity = DB.session.scalar(
|
83
|
+
sqlalchemy.select(Activity).order_by(Activity.id.desc()).limit(1)
|
84
|
+
)
|
85
|
+
assert latest_activity is not None
|
86
|
+
flash(f"Activity was saved with ID {latest_activity.id}.", "success")
|
87
|
+
return redirect(f"/activity/{latest_activity.id}")
|
84
88
|
|
85
89
|
@blueprint.route("/refresh")
|
86
90
|
@needs_authentication(authenticator)
|
@@ -24,7 +24,7 @@
|
|
24
24
|
<dt>Tags</dt>
|
25
25
|
<dd>
|
26
26
|
{% for tag in activity.tags %}
|
27
|
-
|
27
|
+
{{ activity_tag(tag) }}
|
28
28
|
{% endfor %}
|
29
29
|
</dd>
|
30
30
|
{% endif %}
|
@@ -75,12 +75,16 @@
|
|
75
75
|
|
76
76
|
<dt>ID</dt>
|
77
77
|
<dd>{{ activity.id }}</dd>
|
78
|
+
<dt>Upstream ID</dt>
|
79
|
+
<dd>{{ activity.upstream_id }}</dd>
|
78
80
|
<dt>Source path</dt>
|
79
|
-
<dd>{{ activity.path }}</dd>
|
81
|
+
<dd><a href="{{ url_for('.download_original', id=activity.id) }}">{{ activity.path }}</a></dd>
|
80
82
|
</dl>
|
81
83
|
|
82
84
|
<a href="{{ url_for('.edit', id=activity['id']) }}" class="btn btn-secondary btn-small">Edit</a>
|
83
85
|
<a href="{{ url_for('.trim', id=activity['id']) }}" class="btn btn-secondary btn-small">Trim</a>
|
86
|
+
<a class="btn btn-small btn-danger" href="{{ url_for('.delete', id=activity.id) }}"
|
87
|
+
onclick="if(!confirm('Are you sure to Delete This?')){ event.preventDefault() }">Delete</a>
|
84
88
|
</div>
|
85
89
|
<div class="col-sm-12 col-md-8">
|
86
90
|
<div id="activity-map" style="height: 500px;" class="mb-3"></div>
|
@@ -9,7 +9,9 @@
|
|
9
9
|
<div class="col">
|
10
10
|
<h2>Last 30 days</h2>
|
11
11
|
{{ vega_direct("distance-last-30-days", distance_last_30_days_plot) }}
|
12
|
+
{% if elevation_gain_last_30_days_plot %}
|
12
13
|
{{ vega_direct("elevation-gain-last-30-days", elevation_gain_last_30_days_plot) }}
|
14
|
+
{% endif %}
|
13
15
|
</div>
|
14
16
|
</div>
|
15
17
|
|
@@ -200,6 +200,10 @@
|
|
200
200
|
</script>
|
201
201
|
{% endmacro %}
|
202
202
|
|
203
|
+
{% macro activity_tag(tag) %}
|
204
|
+
<span class="badge" style="background-color: {{ tag.color or '#0d6efd' }}">{{ tag.tag }}</span>
|
205
|
+
{% endmacro %}
|
206
|
+
|
203
207
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
204
208
|
{% if messages %}
|
205
209
|
{% for category, message in messages %}
|
@@ -10,6 +10,11 @@
|
|
10
10
|
<input type="text" class="form-control" id="tag" name="tag" value="{{ tag.tag }}" />
|
11
11
|
</div>
|
12
12
|
|
13
|
+
<div class="mb-3">
|
14
|
+
<label for="color" class="form-label">Color</label>
|
15
|
+
<input type="color" class="form-control" id="color" name="color" value="{{ tag.color or '#0d6efd' }}" />
|
16
|
+
</div>
|
17
|
+
|
13
18
|
<button type="submit" class="btn btn-primary">Save</button>
|
14
19
|
</form>
|
15
20
|
|
@@ -103,10 +103,12 @@
|
|
103
103
|
|
104
104
|
<div class="tab-content mb-3" id="myTabContent">
|
105
105
|
{% for year, plot in plot_distance_heatmaps.items() %}
|
106
|
+
{% if year %}
|
106
107
|
<div class="tab-pane fade {% if loop.last %} show active {% endif %}" id="heatmap-{{ year }}-pane" role="tabpanel"
|
107
108
|
aria-labelledby="heatmap-{{ year }}" tabindex="0">
|
108
109
|
{{ vega_direct("plot_distance_heatmap_%d"|format(year), plot) }}
|
109
110
|
</div>
|
111
|
+
{% endif %}
|
110
112
|
{% endfor %}
|
111
113
|
</div>
|
112
114
|
|
{geo_activity_playground-0.43.3.dist-info → geo_activity_playground-0.45.0.dist-info}/RECORD
RENAMED
@@ -3,6 +3,7 @@ geo_activity_playground/__main__.py,sha256=eL7NlKydYrzi4ikTvvKmlwkEFxx0V6CXOHOhR
|
|
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
|
6
|
+
geo_activity_playground/alembic/versions/0f02b92c4f94_add_tag_color.py,sha256=gWcRPLIo1jH3X_x0hh1rMRuiz2wtS10MEIsG6voBCX4,827
|
6
7
|
geo_activity_playground/alembic/versions/38882503dc7c_add_tags_to_activities.py,sha256=HmvYgHlVodHB7xyigg7zHkFXSi1znWqKfOHcd6y9sZE,3157
|
7
8
|
geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py,sha256=WrmlDllnJECg6cSOeS05wYCa977_SXbJUV5khDSzntw,1082
|
8
9
|
geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py,sha256=YTnnENkQ8WqLz7PFof7tUWNkWcoHGkAfAM52x1N9umo,3029
|
@@ -14,15 +15,15 @@ geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmar
|
|
14
15
|
geo_activity_playground/alembic/versions/script.py.mako,sha256=3qBrHBf7F7ChKDUIdiNItiSXrDpgQdM7sR0YKzpaC50,689
|
15
16
|
geo_activity_playground/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
17
|
geo_activity_playground/core/activities.py,sha256=apP_-Rg1ub3lh7RARMGXf2BOmJTiahxqpX_soEnYF3E,4681
|
17
|
-
geo_activity_playground/core/config.py,sha256=
|
18
|
+
geo_activity_playground/core/config.py,sha256=MhNaVS04rH_1KTUE1IrCTVsKtrSqKgMT7mzgPAri3vk,5305
|
18
19
|
geo_activity_playground/core/coordinates.py,sha256=tDfr9mlXhK6E_MMIJ0vYWVCoH0Lq8uyuaqUgaa8i0jg,966
|
19
|
-
geo_activity_playground/core/datamodel.py,sha256=
|
20
|
-
geo_activity_playground/core/enrichment.py,sha256=
|
20
|
+
geo_activity_playground/core/datamodel.py,sha256=Bx15g0TJun1yu_deHs3idSNJDAQpvuU39_VIsWD96MY,13303
|
21
|
+
geo_activity_playground/core/enrichment.py,sha256=Tju9sKI-V40CmsS9RiNeGz-Zhp_hx1xjlaWzJMrasXI,7640
|
21
22
|
geo_activity_playground/core/heart_rate.py,sha256=-S3WAhS7AOywrw_Lk5jfuo_fu6zvZQ1VtjwEKSycWpU,1542
|
22
|
-
geo_activity_playground/core/meta_search.py,sha256=
|
23
|
+
geo_activity_playground/core/meta_search.py,sha256=dFWBMnLhdQgUJz2TPYMNFW7XVpyW4QhsH3kPVIhWFJc,6662
|
23
24
|
geo_activity_playground/core/missing_values.py,sha256=HjonaLV0PFMICnuMrbdUNnK9uy_8PBh_RxI5GuEMQK0,250
|
24
25
|
geo_activity_playground/core/parametric_plot.py,sha256=IefPc6lwthxowvjUDA5wu23oBSw9jq399l04gSaNrOQ,3880
|
25
|
-
geo_activity_playground/core/paths.py,sha256=
|
26
|
+
geo_activity_playground/core/paths.py,sha256=GGNpqhQL7etn8-NV6hVGLT7yBZYq7AwXnWfX3l_Cj48,2670
|
26
27
|
geo_activity_playground/core/privacy_zones.py,sha256=4TumHsVUN1uW6RG3ArqTXDykPVipF98DCxVBe7YNdO8,512
|
27
28
|
geo_activity_playground/core/raster_map.py,sha256=Cq8dNLdxVQg3Agzn2bmXVu0-8kZf56QrSe-LKNn3jaU,7994
|
28
29
|
geo_activity_playground/core/similarity.py,sha256=L2de3DPRdDeDY5AxZwLDcH7FjHWRWklr41VNU06q9kQ,3117
|
@@ -44,9 +45,9 @@ geo_activity_playground/heatmap_video.py,sha256=I8i1uVvbbPUXVtvLAROaLy58nQoUPnuM
|
|
44
45
|
geo_activity_playground/importers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
46
|
geo_activity_playground/importers/activity_parsers.py,sha256=yD7L5eDOpiLWf6RHSQf4-Nk2S3vgfVHngc9ZlFSrioM,11090
|
46
47
|
geo_activity_playground/importers/csv_parser.py,sha256=O1pP5GLhWhnWcy2Lsrr9g17Zspuibpt-GtZ3ZS5eZF4,2143
|
47
|
-
geo_activity_playground/importers/directory.py,sha256=
|
48
|
+
geo_activity_playground/importers/directory.py,sha256=eTLfcMdc5mt9iDBinuaXjRIA688i6QZQjXNNhm1PKz4,5282
|
48
49
|
geo_activity_playground/importers/strava_api.py,sha256=J0-VXNrLq22fhTcWkQPE5AVrzy5aegC7SBi-UXFtAy4,7576
|
49
|
-
geo_activity_playground/importers/strava_checkout.py,sha256=
|
50
|
+
geo_activity_playground/importers/strava_checkout.py,sha256=NwcOje0XxkccXRY5-3CJorYLFuniaFm8VHg4nHwkji4,10045
|
50
51
|
geo_activity_playground/importers/test_csv_parser.py,sha256=nOTVTdlzIY0TDcbWp7xNyNaIO6Mkeu55hVziVl22QE4,1092
|
51
52
|
geo_activity_playground/importers/test_directory.py,sha256=_fn_-y98ZyElbG0BRxAmGFdtGobUShPU86SdEOpuv-A,691
|
52
53
|
geo_activity_playground/importers/test_strava_api.py,sha256=7b8bl5Rh2BctCmvTPEhCadxtUOq3mfzuadD6F5XxRio,398
|
@@ -54,23 +55,23 @@ geo_activity_playground/webui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
|
|
54
55
|
geo_activity_playground/webui/app.py,sha256=SyRztm6P0pQuq3Vn4g7gFwg1pwyZsElVNZOeljN5jEY,8139
|
55
56
|
geo_activity_playground/webui/authenticator.py,sha256=jtQqvpVHa_eLTAulmvvJgDRoCWOEege49G9zn3MfYk8,1394
|
56
57
|
geo_activity_playground/webui/blueprints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
|
-
geo_activity_playground/webui/blueprints/activity_blueprint.py,sha256=
|
58
|
+
geo_activity_playground/webui/blueprints/activity_blueprint.py,sha256=EjaTe6VFkRpO9DEkHHurt5GDzAvHHUqgQgfBdiN239Q,26457
|
58
59
|
geo_activity_playground/webui/blueprints/auth_blueprint.py,sha256=_VZeP3VN626BoOOZUkNVnuw9v-cEOrkHz5lhFPmxqMY,784
|
59
60
|
geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py,sha256=xESHzYxlbhz4oNDuxV0A70eVKpFwz84pYC3q_YVZZg8,2812
|
60
61
|
geo_activity_playground/webui/blueprints/calendar_blueprint.py,sha256=4EIBZ8rdXEu3tbl1faVlRwHb8Qp0JuMc3eyxwMkq6g8,2848
|
61
|
-
geo_activity_playground/webui/blueprints/eddington_blueprints.py,sha256=
|
62
|
-
geo_activity_playground/webui/blueprints/entry_views.py,sha256=
|
63
|
-
geo_activity_playground/webui/blueprints/equipment_blueprint.py,sha256=
|
62
|
+
geo_activity_playground/webui/blueprints/eddington_blueprints.py,sha256=8ctEZWIc0hWjVr3ezNTjxwDHO5M-XzUoupA69RrovB0,8896
|
63
|
+
geo_activity_playground/webui/blueprints/entry_views.py,sha256=Lzd-hkflmN3GY24qm6dHazIe78wxnQFdFTUsoZOUH-M,2978
|
64
|
+
geo_activity_playground/webui/blueprints/equipment_blueprint.py,sha256=26L2BM7lZo8rDf5Ipara5zA4tfSrvE18POqAZqjzH38,5659
|
64
65
|
geo_activity_playground/webui/blueprints/explorer_blueprint.py,sha256=jMjLxmUhy9ip4Jjw9ABfkdD3CfqXqcE2C0VwQUBZ3WY,15598
|
65
66
|
geo_activity_playground/webui/blueprints/heatmap_blueprint.py,sha256=iHI5YJYhX7ZOlzTgzl2efIRDzt3UMYCx7X4-LVd0MWk,8702
|
66
67
|
geo_activity_playground/webui/blueprints/photo_blueprint.py,sha256=sYGp2XVGodkAifGHbEqpIY-7bBH5R7G7Dwg4HqgxMSY,7269
|
67
68
|
geo_activity_playground/webui/blueprints/plot_builder_blueprint.py,sha256=7HrjpBM-608HSOh0i31Lmt7yDNMfWlEn6G7DlYqlV9w,3031
|
68
69
|
geo_activity_playground/webui/blueprints/search_blueprint.py,sha256=Sv_KL1Cdai26y51qVfI-5jZLhtElREsEar1dbR_VAC4,2275
|
69
|
-
geo_activity_playground/webui/blueprints/settings_blueprint.py,sha256=
|
70
|
+
geo_activity_playground/webui/blueprints/settings_blueprint.py,sha256=FLKQ0JTThUlsCBR2TqRvdDGIJpPDycy47iiQof9gbHA,20077
|
70
71
|
geo_activity_playground/webui/blueprints/square_planner_blueprint.py,sha256=xVaxJxmt8Dysl3UL9f2y__LVLtTH2Np1Ust4OSXKRAk,4746
|
71
72
|
geo_activity_playground/webui/blueprints/summary_blueprint.py,sha256=rK4LGR2Rpioy4wSqNYuyRn4WxaWeBLensJ3PmAd-ouY,11469
|
72
73
|
geo_activity_playground/webui/blueprints/tile_blueprint.py,sha256=YzZf9OrNdjhc1_j4MtO1DMcw1uCv29ueNsYd-mWqgbg,837
|
73
|
-
geo_activity_playground/webui/blueprints/upload_blueprint.py,sha256=
|
74
|
+
geo_activity_playground/webui/blueprints/upload_blueprint.py,sha256=ZPeUFGQ0gHeX71zVLLrHWpPq1NzCk4GlZIxYSdR1rhA,4799
|
74
75
|
geo_activity_playground/webui/columns.py,sha256=hSW8bFVKRUMBlWZYAN-tZ_tfED3VDQK75e4Zzw7_jZ0,642
|
75
76
|
geo_activity_playground/webui/flasher.py,sha256=Covc1D9cO_jjokRWnvyiXCc2tfp3aZ8XkNqFdA1AXtk,500
|
76
77
|
geo_activity_playground/webui/plot_util.py,sha256=5Uesjj-xcMskQX2z9viDZYHSxLGrH2a5dHA1ogsJW9U,261
|
@@ -113,7 +114,7 @@ geo_activity_playground/webui/templates/activity/day.html.j2,sha256=CHEvxlZralCm
|
|
113
114
|
geo_activity_playground/webui/templates/activity/edit.html.j2,sha256=r979JPqaZi_2ymTykxpkjdpw0D2tsB9VJaf7OaGPaME,1961
|
114
115
|
geo_activity_playground/webui/templates/activity/lines.html.j2,sha256=_ZDg1ruW-9UMJfOudy1-uY_-IcSSaagq7tPCih5Bb8g,1079
|
115
116
|
geo_activity_playground/webui/templates/activity/name.html.j2,sha256=7Wbh3IrVL5lMRve467H0P10Shn5FzGpaXLhV0H-X4Hk,2725
|
116
|
-
geo_activity_playground/webui/templates/activity/show.html.j2,sha256=
|
117
|
+
geo_activity_playground/webui/templates/activity/show.html.j2,sha256=obKXSvRJxpeo53k7aCUrrhJSus9msuPGStIQJQQHpes,10975
|
117
118
|
geo_activity_playground/webui/templates/activity/trim.html.j2,sha256=3oAXQab6QqWjGBC9KCvWNOVn8uRmxoDLj3hx_O63TXc,1836
|
118
119
|
geo_activity_playground/webui/templates/auth/index.html.j2,sha256=ILQ5HvTEYc3OrtOAIFt1VrqWorVD70V9DC342znmP70,579
|
119
120
|
geo_activity_playground/webui/templates/bubble_chart/index.html.j2,sha256=yd7lWjtxxVmJZqCiXb0Y1gMEOQ7LQYJXEdpE7JB1OZY,1616
|
@@ -126,14 +127,14 @@ geo_activity_playground/webui/templates/equipment/index.html.j2,sha256=wwrGmfCCB
|
|
126
127
|
geo_activity_playground/webui/templates/explorer/index.html.j2,sha256=3t9ikAF6oMvEaVlS3Kb1tj9ngomIQlatzqPnqVsEDKA,6908
|
127
128
|
geo_activity_playground/webui/templates/explorer/server-side.html.j2,sha256=3eYh3mmgPDmxFjH6Ofx1iw-jptcD9ctZixYh59mka3w,2496
|
128
129
|
geo_activity_playground/webui/templates/heatmap/index.html.j2,sha256=uM-l4gmDKw6307ZH_zb8zroMTKBuOkrR0Bu4fTEJE0s,1231
|
129
|
-
geo_activity_playground/webui/templates/home.html.j2,sha256=
|
130
|
-
geo_activity_playground/webui/templates/page.html.j2,sha256=
|
130
|
+
geo_activity_playground/webui/templates/home.html.j2,sha256=EBLKVNgLYgTPzdFutoSkdlbu_UCCat5ItDFlYyRqcAg,2767
|
131
|
+
geo_activity_playground/webui/templates/page.html.j2,sha256=QJtRcHciEEWUUf6LHll2RUm0NRXSkNGIYn1FHOKSrM4,12534
|
131
132
|
geo_activity_playground/webui/templates/photo/map.html.j2,sha256=MWhqt5Q8ExiRhgxndcEnwngOj1qw0E0u4hKuiuY24Gg,1437
|
132
133
|
geo_activity_playground/webui/templates/photo/new.html.j2,sha256=GGLejO4ap6ZMe54jZP39ktSLkdw5j67bf5PTlHEK7qc,383
|
133
134
|
geo_activity_playground/webui/templates/plot_builder/edit.html.j2,sha256=x5Ki425me3HY6CcBQ37le9g8rCpbOxFVkdr0N_L84-g,2230
|
134
135
|
geo_activity_playground/webui/templates/plot_builder/index.html.j2,sha256=fBuGLT2HIwlgz5eGeKXOdIDqzDSQoY99w-hyt_0JP-w,832
|
135
136
|
geo_activity_playground/webui/templates/search/index.html.j2,sha256=tZ2RwiaC1cLCLfcxbDvpnLSjPeqnTkByAT6ncVitnLw,1318
|
136
|
-
geo_activity_playground/webui/templates/search_form.html.j2,sha256=
|
137
|
+
geo_activity_playground/webui/templates/search_form.html.j2,sha256=BBxT2aAUlOZ41d2hE9EKX0Jcr0FKLCp_9cgWYyrVtE8,8261
|
137
138
|
geo_activity_playground/webui/templates/settings/admin-password.html.j2,sha256=VYwddpObD1RpeTH5Dm4y7VtmT7kwURDCIjxyzJeq08c,495
|
138
139
|
geo_activity_playground/webui/templates/settings/color-schemes.html.j2,sha256=iR91Wxd2_TMuIo9dBDZBrWSUGHNwTwzC6O8oNH-XBt4,1653
|
139
140
|
geo_activity_playground/webui/templates/settings/heart-rate.html.j2,sha256=UPT3MegRgSeff36lhCo0l3ZwhqNSIg5gM6h2s32GkCY,4255
|
@@ -145,15 +146,15 @@ geo_activity_playground/webui/templates/settings/privacy-zones.html.j2,sha256=Kp
|
|
145
146
|
geo_activity_playground/webui/templates/settings/segmentation.html.j2,sha256=QV72TZcIxqql-vEsq2lKHzo5UxoxeeXkRA9se46GWKU,1187
|
146
147
|
geo_activity_playground/webui/templates/settings/sharepic.html.j2,sha256=qZkfEpd4CtKKMaSSVadqvNEgMRYLV-0X-pw5-nJvukk,678
|
147
148
|
geo_activity_playground/webui/templates/settings/strava.html.j2,sha256=GCE5gskQ6xJ8AM1qGrrUVLDOiuqg510mWzzsZjia0gk,2211
|
148
|
-
geo_activity_playground/webui/templates/settings/tags-edit.html.j2,sha256=
|
149
|
-
geo_activity_playground/webui/templates/settings/tags-list.html.j2,sha256=
|
149
|
+
geo_activity_playground/webui/templates/settings/tags-edit.html.j2,sha256=Lna2QBacuMwaFODGCVulOpHSHHjqCdhNJg2c6i-ogY0,586
|
150
|
+
geo_activity_playground/webui/templates/settings/tags-list.html.j2,sha256=6giFWtVCTLXLC_Ojh56XhB_1Rouit9YzIs_YHIiayLg,369
|
150
151
|
geo_activity_playground/webui/templates/settings/tags-new.html.j2,sha256=xi6KbwydDVrUJM4_ty4KbMa74k3QaoyZhZAn2paERnM,358
|
151
152
|
geo_activity_playground/webui/templates/square_planner/index.html.j2,sha256=-OnY2nQCgZCslOzf28ogZwFykwF8tZm7PgFwOE3eBDk,8176
|
152
|
-
geo_activity_playground/webui/templates/summary/index.html.j2,sha256=
|
153
|
+
geo_activity_playground/webui/templates/summary/index.html.j2,sha256=1kTuwWvOM3McEdY8N5rKj-R4CC6uqswl7NsGVF8hsDA,9185
|
153
154
|
geo_activity_playground/webui/templates/upload/index.html.j2,sha256=I1Ix8tDS3YBdi-HdaNfjkzYXVVCjfUTe5PFTnap1ydc,775
|
154
155
|
geo_activity_playground/webui/templates/upload/reload.html.j2,sha256=YZWX5eDeNyqKJdQAywDBcU8DZBm22rRBbZqFjrFrCvQ,556
|
155
|
-
geo_activity_playground-0.
|
156
|
-
geo_activity_playground-0.
|
157
|
-
geo_activity_playground-0.
|
158
|
-
geo_activity_playground-0.
|
159
|
-
geo_activity_playground-0.
|
156
|
+
geo_activity_playground-0.45.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
|
157
|
+
geo_activity_playground-0.45.0.dist-info/METADATA,sha256=z-NGEoMJeCU-Zknez48tKzYt4az7GzkQalxWAEpp6JU,1850
|
158
|
+
geo_activity_playground-0.45.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
159
|
+
geo_activity_playground-0.45.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
|
160
|
+
geo_activity_playground-0.45.0.dist-info/RECORD,,
|
{geo_activity_playground-0.43.3.dist-info → geo_activity_playground-0.45.0.dist-info}/LICENSE
RENAMED
File without changes
|
File without changes
|
File without changes
|