geo-activity-playground 0.36.2__py3-none-any.whl → 0.37.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/core/meta_search.py +117 -0
- geo_activity_playground/core/summary_stats.py +30 -0
- geo_activity_playground/core/test_meta_search.py +91 -0
- geo_activity_playground/core/test_summary_stats.py +108 -0
- geo_activity_playground/webui/app.py +9 -5
- geo_activity_playground/webui/eddington_blueprint.py +8 -3
- geo_activity_playground/webui/{equipment/controller.py → equipment_blueprint.py} +19 -41
- geo_activity_playground/webui/heatmap/blueprint.py +7 -29
- geo_activity_playground/webui/heatmap/heatmap_controller.py +45 -103
- geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +5 -37
- geo_activity_playground/webui/search_blueprint.py +6 -69
- geo_activity_playground/webui/search_util.py +31 -0
- geo_activity_playground/webui/summary_blueprint.py +15 -11
- geo_activity_playground/webui/templates/eddington/index.html.j2 +5 -0
- geo_activity_playground/webui/{equipment/templates → templates}/equipment/index.html.j2 +3 -5
- geo_activity_playground/webui/templates/search/index.html.j2 +30 -87
- geo_activity_playground/webui/templates/search_form.html.j2 +82 -0
- geo_activity_playground/webui/templates/summary/index.html.j2 +5 -1
- {geo_activity_playground-0.36.2.dist-info → geo_activity_playground-0.37.0.dist-info}/METADATA +1 -1
- {geo_activity_playground-0.36.2.dist-info → geo_activity_playground-0.37.0.dist-info}/RECORD +23 -19
- geo_activity_playground/webui/equipment/__init__.py +0 -0
- geo_activity_playground/webui/equipment/blueprint.py +0 -16
- {geo_activity_playground-0.36.2.dist-info → geo_activity_playground-0.37.0.dist-info}/LICENSE +0 -0
- {geo_activity_playground-0.36.2.dist-info → geo_activity_playground-0.37.0.dist-info}/WHEEL +0 -0
- {geo_activity_playground-0.36.2.dist-info → geo_activity_playground-0.37.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
import dataclasses
|
2
|
+
import datetime
|
3
|
+
import re
|
4
|
+
import urllib.parse
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
import pandas as pd
|
9
|
+
|
10
|
+
|
11
|
+
@dataclasses.dataclass
|
12
|
+
class SearchQuery:
|
13
|
+
equipment: list[str] = dataclasses.field(default_factory=list)
|
14
|
+
kind: list[str] = dataclasses.field(default_factory=list)
|
15
|
+
name: Optional[str] = None
|
16
|
+
name_case_sensitive: bool = False
|
17
|
+
start_begin: Optional[datetime.date] = None
|
18
|
+
start_end: Optional[datetime.date] = None
|
19
|
+
|
20
|
+
@property
|
21
|
+
def active(self) -> bool:
|
22
|
+
return (
|
23
|
+
self.equipment
|
24
|
+
or self.kind
|
25
|
+
or self.name
|
26
|
+
or self.start_begin
|
27
|
+
or self.start_end
|
28
|
+
)
|
29
|
+
|
30
|
+
def to_jinja(self) -> dict:
|
31
|
+
return {
|
32
|
+
"equipment": self.equipment,
|
33
|
+
"kind": self.kind,
|
34
|
+
"name": self.name or "",
|
35
|
+
"name_case_sensitive": self.name_case_sensitive,
|
36
|
+
"start_begin": _format_optional_date(self.start_begin),
|
37
|
+
"start_end": _format_optional_date(self.start_end),
|
38
|
+
"active": self.active,
|
39
|
+
}
|
40
|
+
|
41
|
+
def to_url_str(self) -> str:
|
42
|
+
variables = []
|
43
|
+
for equipment in self.equipment:
|
44
|
+
variables.append(("equipment", equipment))
|
45
|
+
for kind in self.kind:
|
46
|
+
variables.append(("kind", kind))
|
47
|
+
if self.name:
|
48
|
+
variables.append(("name", self.name))
|
49
|
+
if self.name_case_sensitive:
|
50
|
+
variables.append(("name_case_sensitive", "true"))
|
51
|
+
if self.start_begin:
|
52
|
+
variables.append(("start_begin", self.start_begin.isoformat()))
|
53
|
+
if self.start_end:
|
54
|
+
variables.append(("start_end", self.start_end.isoformat()))
|
55
|
+
|
56
|
+
return "&".join(
|
57
|
+
f"{key}={urllib.parse.quote_plus(value)}" for key, value in variables
|
58
|
+
)
|
59
|
+
|
60
|
+
|
61
|
+
def _format_optional_date(date: Optional[datetime.date]) -> str:
|
62
|
+
if date is None:
|
63
|
+
return ""
|
64
|
+
else:
|
65
|
+
return date.isoformat()
|
66
|
+
|
67
|
+
|
68
|
+
def apply_search_query(
|
69
|
+
activity_meta: pd.DataFrame, search_query: SearchQuery
|
70
|
+
) -> pd.DataFrame:
|
71
|
+
mask = _make_mask(activity_meta.index, True)
|
72
|
+
|
73
|
+
if search_query.equipment:
|
74
|
+
mask &= _filter_column(activity_meta["equipment"], search_query.equipment)
|
75
|
+
if search_query.kind:
|
76
|
+
mask &= _filter_column(activity_meta["kind"], search_query.kind)
|
77
|
+
if search_query.name:
|
78
|
+
mask &= pd.Series(
|
79
|
+
[
|
80
|
+
bool(
|
81
|
+
re.search(
|
82
|
+
search_query.name,
|
83
|
+
activity_name,
|
84
|
+
0 if search_query.name_case_sensitive else re.IGNORECASE,
|
85
|
+
)
|
86
|
+
)
|
87
|
+
for activity_name in activity_meta["name"]
|
88
|
+
],
|
89
|
+
index=activity_meta.index,
|
90
|
+
)
|
91
|
+
if search_query.start_begin is not None:
|
92
|
+
start_begin = datetime.datetime.combine(
|
93
|
+
search_query.start_begin, datetime.time.min
|
94
|
+
)
|
95
|
+
mask &= start_begin <= activity_meta["start"]
|
96
|
+
if search_query.start_end is not None:
|
97
|
+
start_end = datetime.datetime.combine(search_query.start_end, datetime.time.max)
|
98
|
+
mask &= activity_meta["start"] <= start_end
|
99
|
+
|
100
|
+
return activity_meta.loc[mask]
|
101
|
+
|
102
|
+
|
103
|
+
def _make_mask(
|
104
|
+
index: pd.Index,
|
105
|
+
default: bool,
|
106
|
+
) -> pd.Series:
|
107
|
+
if default:
|
108
|
+
return pd.Series(np.ones((len(index),), dtype=np.bool), index=index)
|
109
|
+
else:
|
110
|
+
return pd.Series(np.zeros((len(index),), dtype=np.bool), index=index)
|
111
|
+
|
112
|
+
|
113
|
+
def _filter_column(column: pd.Series, values: list):
|
114
|
+
sub_mask = _make_mask(column.index, False)
|
115
|
+
for equipment in values:
|
116
|
+
sub_mask |= column == equipment
|
117
|
+
return sub_mask
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
|
3
|
+
|
4
|
+
def get_equipment_use_table(
|
5
|
+
activity_meta: pd.DataFrame, offsets: dict[str, float]
|
6
|
+
) -> pd.DataFrame:
|
7
|
+
result = (
|
8
|
+
activity_meta.groupby("equipment")
|
9
|
+
.apply(
|
10
|
+
lambda group: pd.Series(
|
11
|
+
{
|
12
|
+
"total_distance_km": group["distance_km"].sum(),
|
13
|
+
"first_use": group["start"].min(skipna=True),
|
14
|
+
"last_use": group["start"].max(skipna=True),
|
15
|
+
},
|
16
|
+
),
|
17
|
+
include_groups=False,
|
18
|
+
)
|
19
|
+
.sort_values("last_use", ascending=False)
|
20
|
+
)
|
21
|
+
for equipment, offset in offsets.items():
|
22
|
+
result.loc[equipment, "total_distance_km"] += offset
|
23
|
+
|
24
|
+
result["total_distance_km"] = [
|
25
|
+
int(round(elem)) for elem in result["total_distance_km"]
|
26
|
+
]
|
27
|
+
result["first_use"] = [date.date().isoformat() for date in result["first_use"]]
|
28
|
+
result["last_use"] = [date.date().isoformat() for date in result["last_use"]]
|
29
|
+
|
30
|
+
return result.reset_index().to_dict(orient="records")
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import datetime
|
2
|
+
|
3
|
+
import pandas as pd
|
4
|
+
|
5
|
+
from geo_activity_playground.core.meta_search import _make_mask
|
6
|
+
from geo_activity_playground.core.meta_search import apply_search_query
|
7
|
+
from geo_activity_playground.core.meta_search import SearchQuery
|
8
|
+
|
9
|
+
|
10
|
+
def test_empty_query() -> None:
|
11
|
+
activity_meta = pd.DataFrame(
|
12
|
+
{
|
13
|
+
"equipment": pd.Series(["A", "B", "B"]),
|
14
|
+
"id": pd.Series([1, 2, 3]),
|
15
|
+
"kind": pd.Series(["X", "X", "Y"]),
|
16
|
+
"name": ["Test1", "Test2", "Test3"],
|
17
|
+
"start": [
|
18
|
+
datetime.datetime(2024, 12, 24, 10),
|
19
|
+
datetime.datetime(2025, 1, 1, 10),
|
20
|
+
None,
|
21
|
+
],
|
22
|
+
}
|
23
|
+
)
|
24
|
+
|
25
|
+
search_query = SearchQuery()
|
26
|
+
|
27
|
+
actual = apply_search_query(activity_meta, search_query)
|
28
|
+
assert (actual["id"] == activity_meta["id"]).all()
|
29
|
+
|
30
|
+
|
31
|
+
def test_equipment_query() -> None:
|
32
|
+
activity_meta = pd.DataFrame(
|
33
|
+
{
|
34
|
+
"equipment": pd.Series(["A", "B", "B"]),
|
35
|
+
"id": pd.Series([1, 2, 3]),
|
36
|
+
"kind": pd.Series(["X", "X", "Y"]),
|
37
|
+
"name": ["Test1", "Test2", "Test3"],
|
38
|
+
"start": [
|
39
|
+
datetime.datetime(2024, 12, 24, 10),
|
40
|
+
datetime.datetime(2025, 1, 1, 10),
|
41
|
+
None,
|
42
|
+
],
|
43
|
+
}
|
44
|
+
)
|
45
|
+
search_query = SearchQuery(equipment=["B"])
|
46
|
+
actual = apply_search_query(activity_meta, search_query)
|
47
|
+
assert set(actual["id"]) == {2, 3}
|
48
|
+
|
49
|
+
|
50
|
+
def test_date_query() -> None:
|
51
|
+
activity_meta = pd.DataFrame(
|
52
|
+
{
|
53
|
+
"equipment": pd.Series(["A", "B", "B"]),
|
54
|
+
"id": pd.Series([1, 2, 3]),
|
55
|
+
"kind": pd.Series(["X", "X", "Y"]),
|
56
|
+
"name": ["Test1", "Test2", "Test3"],
|
57
|
+
"start": [
|
58
|
+
datetime.datetime(2024, 12, 24, 10),
|
59
|
+
datetime.datetime(2025, 1, 1, 10),
|
60
|
+
None,
|
61
|
+
],
|
62
|
+
}
|
63
|
+
)
|
64
|
+
search_query = SearchQuery(start_begin=datetime.date(2024, 12, 31))
|
65
|
+
actual = apply_search_query(activity_meta, search_query)
|
66
|
+
assert set(actual["id"]) == {2}
|
67
|
+
|
68
|
+
|
69
|
+
def test_name_query() -> None:
|
70
|
+
activity_meta = pd.DataFrame(
|
71
|
+
{
|
72
|
+
"equipment": pd.Series(["A", "B", "B"]),
|
73
|
+
"id": pd.Series([1, 2, 3]),
|
74
|
+
"kind": pd.Series(["X", "X", "Y"]),
|
75
|
+
"name": ["Test1", "Test2", "Test3"],
|
76
|
+
"start": [
|
77
|
+
datetime.datetime(2024, 12, 24, 10),
|
78
|
+
datetime.datetime(2025, 1, 1, 10),
|
79
|
+
None,
|
80
|
+
],
|
81
|
+
}
|
82
|
+
)
|
83
|
+
search_query = SearchQuery(name="Test1")
|
84
|
+
actual = apply_search_query(activity_meta, search_query)
|
85
|
+
assert set(actual["id"]) == {1}
|
86
|
+
|
87
|
+
|
88
|
+
def test_make_mask() -> None:
|
89
|
+
index = [1, 2]
|
90
|
+
assert (_make_mask(index, True) == pd.Series([True, True], index=index)).all()
|
91
|
+
assert (_make_mask(index, False) == pd.Series([False, False], index=index)).all()
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import datetime
|
2
|
+
|
3
|
+
import pandas as pd
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from geo_activity_playground.core.summary_stats import get_equipment_use_table
|
7
|
+
|
8
|
+
|
9
|
+
@pytest.fixture
|
10
|
+
def activity_meta() -> pd.DataFrame:
|
11
|
+
"""
|
12
|
+
calories: float
|
13
|
+
commute: bool
|
14
|
+
consider_for_achievements: bool
|
15
|
+
distance_km: float
|
16
|
+
elapsed_time: datetime.timedelta
|
17
|
+
end_latitude: float
|
18
|
+
end_longitude: float
|
19
|
+
equipment: str
|
20
|
+
id: int
|
21
|
+
kind: str
|
22
|
+
moving_time: datetime.timedelta
|
23
|
+
name: str
|
24
|
+
path: str
|
25
|
+
start_latitude: float
|
26
|
+
start_longitude: float
|
27
|
+
start: np.datetime64
|
28
|
+
steps: int
|
29
|
+
"""
|
30
|
+
return pd.DataFrame(
|
31
|
+
{
|
32
|
+
"calories": pd.Series([None, 1000, 2000]),
|
33
|
+
"commute": pd.Series([True, False, True]),
|
34
|
+
"consider_for_achievements": pd.Series([True, True, False]),
|
35
|
+
"distance_km": pd.Series([9.8, 4.4, 4.3]),
|
36
|
+
"elapsed_time": pd.Series(
|
37
|
+
[
|
38
|
+
datetime.timedelta(minutes=0.34),
|
39
|
+
datetime.timedelta(minutes=0.67),
|
40
|
+
None,
|
41
|
+
]
|
42
|
+
),
|
43
|
+
"end_latitude": pd.Series([0.58, 0.5, 0.19]),
|
44
|
+
"end_longitude": pd.Series([0.2, 0.94, 0.69]),
|
45
|
+
"equipment": pd.Series(["A", "B", "B"]),
|
46
|
+
"id": pd.Series([1, 2, 3]),
|
47
|
+
"kind": pd.Series(["X", "X", "Y"]),
|
48
|
+
"moving_time": pd.Series(
|
49
|
+
[
|
50
|
+
datetime.timedelta(minutes=0.32),
|
51
|
+
datetime.timedelta(minutes=0.83),
|
52
|
+
None,
|
53
|
+
]
|
54
|
+
),
|
55
|
+
"name": pd.Series(["Test1", "Test2", "Test1"]),
|
56
|
+
"path": pd.Series(["Test1.fit", "Test2.gpx", "Test1.kml"]),
|
57
|
+
"start_latitude": pd.Series([0.22, 0.02, 0.35]),
|
58
|
+
"start_longitude": pd.Series([0.95, 0.95, 0.81]),
|
59
|
+
"start": pd.Series(
|
60
|
+
[
|
61
|
+
datetime.datetime(2024, 12, 24, 10),
|
62
|
+
datetime.datetime(2025, 1, 1, 10),
|
63
|
+
None,
|
64
|
+
]
|
65
|
+
),
|
66
|
+
"steps": pd.Series([1234, None, 5432]),
|
67
|
+
}
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
def test_activity_meta(activity_meta) -> None:
|
72
|
+
print()
|
73
|
+
print(activity_meta)
|
74
|
+
|
75
|
+
|
76
|
+
def test_equipment_use_table(activity_meta) -> None:
|
77
|
+
activity_meta = pd.DataFrame(
|
78
|
+
{
|
79
|
+
"distance_km": pd.Series([9.8, 4.4, 4.3]),
|
80
|
+
"equipment": pd.Series(["A", "B", "B"]),
|
81
|
+
"start": pd.Series(
|
82
|
+
[
|
83
|
+
datetime.datetime(2024, 12, 24, 10),
|
84
|
+
datetime.datetime(2025, 1, 1, 10),
|
85
|
+
None,
|
86
|
+
]
|
87
|
+
),
|
88
|
+
}
|
89
|
+
)
|
90
|
+
|
91
|
+
offsets = {"A": 4.0}
|
92
|
+
|
93
|
+
expected = [
|
94
|
+
{
|
95
|
+
"equipment": "B",
|
96
|
+
"total_distance_km": 9,
|
97
|
+
"first_use": "2025-01-01",
|
98
|
+
"last_use": "2025-01-01",
|
99
|
+
},
|
100
|
+
{
|
101
|
+
"equipment": "A",
|
102
|
+
"total_distance_km": 14,
|
103
|
+
"first_use": "2024-12-24",
|
104
|
+
"last_use": "2024-12-24",
|
105
|
+
},
|
106
|
+
]
|
107
|
+
actual = get_equipment_use_table(activity_meta, offsets)
|
108
|
+
assert actual == expected
|
@@ -21,8 +21,7 @@ from .calendar.blueprint import make_calendar_blueprint
|
|
21
21
|
from .calendar.controller import CalendarController
|
22
22
|
from .eddington_blueprint import make_eddington_blueprint
|
23
23
|
from .entry_controller import EntryController
|
24
|
-
from .
|
25
|
-
from .equipment.controller import EquipmentController
|
24
|
+
from .equipment_blueprint import make_equipment_blueprint
|
26
25
|
from .explorer.blueprint import make_explorer_blueprint
|
27
26
|
from .explorer.controller import ExplorerController
|
28
27
|
from .heatmap.blueprint import make_heatmap_blueprint
|
@@ -84,7 +83,6 @@ def web_ui_main(
|
|
84
83
|
config = config_accessor()
|
85
84
|
activity_controller = ActivityController(repository, tile_visit_accessor, config)
|
86
85
|
calendar_controller = CalendarController(repository)
|
87
|
-
equipment_controller = EquipmentController(repository, config)
|
88
86
|
explorer_controller = ExplorerController(
|
89
87
|
repository, tile_visit_accessor, config_accessor
|
90
88
|
)
|
@@ -103,7 +101,7 @@ def web_ui_main(
|
|
103
101
|
make_eddington_blueprint(repository), url_prefix="/eddington"
|
104
102
|
)
|
105
103
|
app.register_blueprint(
|
106
|
-
make_equipment_blueprint(
|
104
|
+
make_equipment_blueprint(repository, config), url_prefix="/equipment"
|
107
105
|
)
|
108
106
|
app.register_blueprint(
|
109
107
|
make_explorer_blueprint(explorer_controller), url_prefix="/explorer"
|
@@ -146,11 +144,17 @@ def web_ui_main(
|
|
146
144
|
|
147
145
|
@app.context_processor
|
148
146
|
def inject_global_variables() -> dict:
|
149
|
-
|
147
|
+
variables = {
|
150
148
|
"version": _try_get_version(),
|
151
149
|
"num_activities": len(repository),
|
152
150
|
"map_tile_attribution": config_accessor().map_tile_attribution,
|
153
151
|
}
|
152
|
+
if len(repository):
|
153
|
+
variables["equipments_avail"] = sorted(
|
154
|
+
repository.meta["equipment"].unique()
|
155
|
+
)
|
156
|
+
variables["kinds_avail"] = sorted(repository.meta["kind"].unique())
|
157
|
+
return variables
|
154
158
|
|
155
159
|
app.run(host=host, port=port)
|
156
160
|
|
@@ -3,8 +3,11 @@ import numpy as np
|
|
3
3
|
import pandas as pd
|
4
4
|
from flask import Blueprint
|
5
5
|
from flask import render_template
|
6
|
+
from flask import request
|
6
7
|
|
7
8
|
from geo_activity_playground.core.activities import ActivityRepository
|
9
|
+
from geo_activity_playground.core.meta_search import apply_search_query
|
10
|
+
from geo_activity_playground.webui.search_util import search_query_from_form
|
8
11
|
|
9
12
|
|
10
13
|
def make_eddington_blueprint(repository: ActivityRepository) -> Blueprint:
|
@@ -12,9 +15,10 @@ def make_eddington_blueprint(repository: ActivityRepository) -> Blueprint:
|
|
12
15
|
|
13
16
|
@blueprint.route("/")
|
14
17
|
def index():
|
15
|
-
|
16
|
-
|
17
|
-
].copy()
|
18
|
+
query = search_query_from_form(request.args)
|
19
|
+
activities = apply_search_query(repository.meta, query)
|
20
|
+
activities = activities.loc[activities["consider_for_achievements"]].copy()
|
21
|
+
|
18
22
|
activities["day"] = [start.date() for start in activities["start"]]
|
19
23
|
|
20
24
|
sum_per_day = activities.groupby("day").apply(
|
@@ -76,6 +80,7 @@ def make_eddington_blueprint(repository: ActivityRepository) -> Blueprint:
|
|
76
80
|
eddington_table=eddington.loc[
|
77
81
|
(eddington["distance_km"] > en) & (eddington["distance_km"] <= en + 10)
|
78
82
|
].to_dict(orient="records"),
|
83
|
+
query=query.to_jinja(),
|
79
84
|
)
|
80
85
|
|
81
86
|
return blueprint
|
@@ -1,47 +1,27 @@
|
|
1
1
|
import altair as alt
|
2
2
|
import pandas as pd
|
3
|
+
from flask import Blueprint
|
4
|
+
from flask import render_template
|
3
5
|
|
4
6
|
from geo_activity_playground.core.activities import ActivityRepository
|
5
7
|
from geo_activity_playground.core.config import Config
|
8
|
+
from geo_activity_playground.core.summary_stats import get_equipment_use_table
|
6
9
|
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
def make_equipment_blueprint(
|
12
|
+
repository: ActivityRepository, config: Config
|
13
|
+
) -> Blueprint:
|
14
|
+
blueprint = Blueprint("equipment", __name__, template_folder="templates")
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
.
|
17
|
-
.idxmax(),
|
18
|
-
include_groups=False,
|
16
|
+
@blueprint.route("/")
|
17
|
+
def index():
|
18
|
+
equipment_summary = get_equipment_use_table(
|
19
|
+
repository.meta, config.equipment_offsets
|
19
20
|
)
|
20
21
|
|
21
|
-
equipment_summary = (
|
22
|
-
self._repository.meta.groupby("equipment")
|
23
|
-
.apply(
|
24
|
-
lambda group: pd.Series(
|
25
|
-
{
|
26
|
-
"total_distance_km": group["distance_km"].sum(),
|
27
|
-
"first_use": group["start"].iloc[0],
|
28
|
-
"last_use": group["start"].iloc[-1],
|
29
|
-
},
|
30
|
-
),
|
31
|
-
include_groups=False,
|
32
|
-
)
|
33
|
-
.sort_values("last_use", ascending=False)
|
34
|
-
)
|
35
|
-
|
36
|
-
equipment_summary["primarily_used_for"] = None
|
37
|
-
for equipment, kind in kind_per_equipment.items():
|
38
|
-
equipment_summary.loc[equipment, "primarily_used_for"] = kind
|
39
|
-
|
40
22
|
equipment_variables = {}
|
41
|
-
for equipment in
|
42
|
-
selection =
|
43
|
-
self._repository.meta["equipment"] == equipment
|
44
|
-
]
|
23
|
+
for equipment in repository.meta["equipment"].unique():
|
24
|
+
selection = repository.meta.loc[repository.meta["equipment"] == equipment]
|
45
25
|
total_distances = pd.DataFrame(
|
46
26
|
{
|
47
27
|
"time": selection["start"],
|
@@ -102,13 +82,11 @@ class EquipmentController:
|
|
102
82
|
"usages_plot_id": f"usages_plot_{hash(equipment)}",
|
103
83
|
}
|
104
84
|
|
105
|
-
|
106
|
-
if equipment in equipment_summary.index:
|
107
|
-
equipment_summary.loc[equipment, "total_distance_km"] += offset
|
108
|
-
|
109
|
-
return {
|
85
|
+
variables = {
|
110
86
|
"equipment_variables": equipment_variables,
|
111
|
-
"equipment_summary": equipment_summary
|
112
|
-
orient="records"
|
113
|
-
),
|
87
|
+
"equipment_summary": equipment_summary,
|
114
88
|
}
|
89
|
+
|
90
|
+
return render_template("equipment/index.html.j2", **variables)
|
91
|
+
|
92
|
+
return blueprint
|
@@ -8,6 +8,7 @@ from geo_activity_playground.core.activities import ActivityRepository
|
|
8
8
|
from geo_activity_playground.core.config import Config
|
9
9
|
from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
|
10
10
|
from geo_activity_playground.webui.heatmap.heatmap_controller import HeatmapController
|
11
|
+
from geo_activity_playground.webui.search_util import search_query_from_form
|
11
12
|
|
12
13
|
|
13
14
|
def make_heatmap_blueprint(
|
@@ -20,30 +21,16 @@ def make_heatmap_blueprint(
|
|
20
21
|
|
21
22
|
@blueprint.route("/")
|
22
23
|
def index():
|
24
|
+
query = search_query_from_form(request.args)
|
23
25
|
return render_template(
|
24
|
-
"heatmap/index.html.j2",
|
25
|
-
**heatmap_controller.render(
|
26
|
-
[int(k) for k in request.args.getlist("kind")],
|
27
|
-
request.args.get(
|
28
|
-
"date-start", type=dateutil.parser.parse, default=None
|
29
|
-
),
|
30
|
-
request.args.get("date-end", type=dateutil.parser.parse, default=None),
|
31
|
-
)
|
26
|
+
"heatmap/index.html.j2", **heatmap_controller.render(query)
|
32
27
|
)
|
33
28
|
|
34
29
|
@blueprint.route("/tile/<int:z>/<int:x>/<int:y>.png")
|
35
30
|
def tile(x: int, y: int, z: int):
|
31
|
+
query = search_query_from_form(request.args)
|
36
32
|
return Response(
|
37
|
-
heatmap_controller.render_tile(
|
38
|
-
x,
|
39
|
-
y,
|
40
|
-
z,
|
41
|
-
[int(k) for k in request.args.getlist("kind")],
|
42
|
-
request.args.get(
|
43
|
-
"date-start", type=dateutil.parser.parse, default=None
|
44
|
-
),
|
45
|
-
request.args.get("date-end", type=dateutil.parser.parse, default=None),
|
46
|
-
),
|
33
|
+
heatmap_controller.render_tile(x, y, z, query),
|
47
34
|
mimetype="image/png",
|
48
35
|
)
|
49
36
|
|
@@ -51,18 +38,9 @@ def make_heatmap_blueprint(
|
|
51
38
|
"/download/<float:north>/<float:east>/<float:south>/<float:west>/heatmap.png"
|
52
39
|
)
|
53
40
|
def download(north: float, east: float, south: float, west: float):
|
41
|
+
query = search_query_from_form(request.args)
|
54
42
|
return Response(
|
55
|
-
heatmap_controller.download_heatmap(
|
56
|
-
north,
|
57
|
-
east,
|
58
|
-
south,
|
59
|
-
west,
|
60
|
-
[int(k) for k in request.args.getlist("kind")],
|
61
|
-
request.args.get(
|
62
|
-
"date-start", type=dateutil.parser.parse, default=None
|
63
|
-
),
|
64
|
-
request.args.get("date-end", type=dateutil.parser.parse, default=None),
|
65
|
-
),
|
43
|
+
heatmap_controller.download_heatmap(north, east, south, west, query),
|
66
44
|
mimetype="image/png",
|
67
45
|
headers={"Content-disposition": 'attachment; filename="heatmap.png"'},
|
68
46
|
)
|