geo-activity-playground 0.28.0__py3-none-any.whl → 0.29.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/config.py +3 -0
- geo_activity_playground/core/paths.py +10 -0
- geo_activity_playground/core/tasks.py +5 -4
- geo_activity_playground/explorer/tile_visits.py +41 -7
- geo_activity_playground/webui/activity/controller.py +22 -1
- geo_activity_playground/webui/activity/templates/activity/show.html.j2 +33 -0
- geo_activity_playground/webui/app.py +10 -20
- geo_activity_playground/webui/authenticator.py +0 -3
- geo_activity_playground/webui/entry_controller.py +8 -4
- geo_activity_playground/webui/explorer/controller.py +1 -0
- geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +2 -0
- geo_activity_playground/webui/heatmap/heatmap_controller.py +1 -0
- geo_activity_playground/webui/plot_util.py +9 -0
- geo_activity_playground/webui/search/blueprint.py +20 -0
- geo_activity_playground/webui/settings/blueprint.py +67 -0
- geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +33 -0
- geo_activity_playground/webui/settings/templates/settings/index.html.j2 +9 -0
- geo_activity_playground/webui/summary/blueprint.py +3 -2
- geo_activity_playground/webui/summary/controller.py +20 -13
- geo_activity_playground/webui/templates/home.html.j2 +1 -1
- geo_activity_playground/webui/templates/page.html.j2 +56 -28
- {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/METADATA +3 -4
- {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/RECORD +27 -25
- geo_activity_playground/webui/search_controller.py +0 -19
- /geo_activity_playground/webui/{templates/search.html.j2 → search/templates/search/index.html.j2} +0 -0
- {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/LICENSE +0 -0
- {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/WHEEL +0 -0
- {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/entry_points.txt +0 -0
| @@ -21,6 +21,8 @@ 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"
         | 
| 25 | 
            +
                color_scheme_for_kind: str = "category10"
         | 
| 24 26 | 
             
                equipment_offsets: dict[str, float] = dataclasses.field(default_factory=dict)
         | 
| 25 27 | 
             
                explorer_zoom_levels: list[int] = dataclasses.field(
         | 
| 26 28 | 
             
                    default_factory=lambda: [14, 17]
         | 
| @@ -52,6 +54,7 @@ class ConfigAccessor: | |
| 52 54 | 
             
                    return self._config
         | 
| 53 55 |  | 
| 54 56 | 
             
                def save(self) -> None:
         | 
| 57 | 
            +
                    print(self._config)
         | 
| 55 58 | 
             
                    with open(new_config_file(), "w") as f:
         | 
| 56 59 | 
             
                        json.dump(
         | 
| 57 60 | 
             
                            dataclasses.asdict(self._config),
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            """
         | 
| 2 2 | 
             
            Paths within the playground and cache.
         | 
| 3 3 | 
             
            """
         | 
| 4 | 
            +
            import contextlib
         | 
| 4 5 | 
             
            import functools
         | 
| 5 6 | 
             
            import pathlib
         | 
| 6 7 | 
             
            import typing
         | 
| @@ -24,6 +25,15 @@ def file_wrapper(path: pathlib.Path) -> typing.Callable[[], pathlib.Path]: | |
| 24 25 | 
             
                return wrapper
         | 
| 25 26 |  | 
| 26 27 |  | 
| 28 | 
            +
            @contextlib.contextmanager
         | 
| 29 | 
            +
            def atomic_open(path: pathlib.Path, mode: str):
         | 
| 30 | 
            +
                temp_path = path.with_stem(path.stem + "-temp")
         | 
| 31 | 
            +
                with open(temp_path, mode) as f:
         | 
| 32 | 
            +
                    yield f
         | 
| 33 | 
            +
                path.unlink(missing_ok=True)
         | 
| 34 | 
            +
                temp_path.rename(path)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
             | 
| 27 37 | 
             
            _cache_dir = pathlib.Path("Cache")
         | 
| 28 38 |  | 
| 29 39 | 
             
            _activity_dir = _cache_dir / "Activity"
         | 
| @@ -8,6 +8,7 @@ from typing import Generic | |
| 8 8 | 
             
            from typing import Sequence
         | 
| 9 9 | 
             
            from typing import TypeVar
         | 
| 10 10 |  | 
| 11 | 
            +
            from geo_activity_playground.core.paths import atomic_open
         | 
| 11 12 | 
             
            from geo_activity_playground.core.paths import cache_dir
         | 
| 12 13 |  | 
| 13 14 |  | 
| @@ -24,11 +25,8 @@ def stored_object(path: pathlib.Path, default): | |
| 24 25 |  | 
| 25 26 | 
             
                yield payload
         | 
| 26 27 |  | 
| 27 | 
            -
                 | 
| 28 | 
            -
                with open(temp_location, "wb") as f:
         | 
| 28 | 
            +
                with atomic_open(path, "wb") as f:
         | 
| 29 29 | 
             
                    pickle.dump(payload, f)
         | 
| 30 | 
            -
                path.unlink(missing_ok=True)
         | 
| 31 | 
            -
                temp_location.rename(path)
         | 
| 32 30 |  | 
| 33 31 |  | 
| 34 32 | 
             
            def work_tracker_path(name: str) -> pathlib.Path:
         | 
| @@ -68,6 +66,9 @@ class WorkTracker: | |
| 68 66 | 
             
                def discard(self, id) -> None:
         | 
| 69 67 | 
             
                    self._done.discard(id)
         | 
| 70 68 |  | 
| 69 | 
            +
                def reset(self) -> None:
         | 
| 70 | 
            +
                    self._done = set()
         | 
| 71 | 
            +
             | 
| 71 72 | 
             
                def close(self) -> None:
         | 
| 72 73 | 
             
                    with open(self._path, "wb") as f:
         | 
| 73 74 | 
             
                        pickle.dump(self._done, f)
         | 
| @@ -14,6 +14,7 @@ from tqdm import tqdm | |
| 14 14 |  | 
| 15 15 | 
             
            from geo_activity_playground.core.activities import ActivityRepository
         | 
| 16 16 | 
             
            from geo_activity_playground.core.config import Config
         | 
| 17 | 
            +
            from geo_activity_playground.core.paths import atomic_open
         | 
| 17 18 | 
             
            from geo_activity_playground.core.paths import tiles_per_time_series
         | 
| 18 19 | 
             
            from geo_activity_playground.core.tasks import try_load_pickle
         | 
| 19 20 | 
             
            from geo_activity_playground.core.tasks import work_tracker_path
         | 
| @@ -79,11 +80,12 @@ class TileVisitAccessor: | |
| 79 80 | 
             
                        self.tile_state = make_tile_state()
         | 
| 80 81 | 
             
                        # TODO: Reset work tracker
         | 
| 81 82 |  | 
| 83 | 
            +
                def reset(self) -> None:
         | 
| 84 | 
            +
                    self.tile_state = make_tile_state()
         | 
| 85 | 
            +
             | 
| 82 86 | 
             
                def save(self) -> None:
         | 
| 83 | 
            -
                     | 
| 84 | 
            -
                    with open(tmp_path, "wb") as f:
         | 
| 87 | 
            +
                    with atomic_open(self.PATH, "wb") as f:
         | 
| 85 88 | 
             
                        pickle.dump(self.tile_state, f)
         | 
| 86 | 
            -
                    tmp_path.rename(self.PATH)
         | 
| 87 89 |  | 
| 88 90 |  | 
| 89 91 | 
             
            def make_defaultdict_dict():
         | 
| @@ -106,20 +108,52 @@ def make_tile_state() -> TileState: | |
| 106 108 | 
             
                return tile_state
         | 
| 107 109 |  | 
| 108 110 |  | 
| 111 | 
            +
            def _consistency_check(
         | 
| 112 | 
            +
                repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
         | 
| 113 | 
            +
            ) -> bool:
         | 
| 114 | 
            +
                present_activity_ids = set(repository.get_activity_ids())
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                for zoom, activities_per_tile in tile_visit_accessor.tile_state[
         | 
| 117 | 
            +
                    "activities_per_tile"
         | 
| 118 | 
            +
                ].items():
         | 
| 119 | 
            +
                    for tile, tile_activity_ids in activities_per_tile.items():
         | 
| 120 | 
            +
                        deleted_activity_ids = tile_activity_ids - present_activity_ids
         | 
| 121 | 
            +
                        if deleted_activity_ids:
         | 
| 122 | 
            +
                            logger.info(f"Activities {deleted_activity_ids} have been deleted.")
         | 
| 123 | 
            +
                            return False
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                for zoom, tile_visits in tile_visit_accessor.tile_state["tile_visits"].items():
         | 
| 126 | 
            +
                    for tile, meta in tile_visits.items():
         | 
| 127 | 
            +
                        if meta["first_id"] not in present_activity_ids:
         | 
| 128 | 
            +
                            logger.info(f"Activity {meta['first_id']} have been deleted.")
         | 
| 129 | 
            +
                            return False
         | 
| 130 | 
            +
                        if meta["last_id"] not in present_activity_ids:
         | 
| 131 | 
            +
                            logger.info(f"Activity {meta['last_id']} have been deleted.")
         | 
| 132 | 
            +
                            return False
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                return True
         | 
| 135 | 
            +
             | 
| 136 | 
            +
             | 
| 109 137 | 
             
            def compute_tile_visits_new(
         | 
| 110 138 | 
             
                repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
         | 
| 111 139 | 
             
            ) -> None:
         | 
| 112 140 | 
             
                work_tracker = WorkTracker(work_tracker_path("tile-state"))
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                if not _consistency_check(repository, tile_visit_accessor):
         | 
| 143 | 
            +
                    logger.warning("Need to recompute Explorer Tiles due to deleted activities.")
         | 
| 144 | 
            +
                    tile_visit_accessor.reset()
         | 
| 145 | 
            +
                    work_tracker.reset()
         | 
| 146 | 
            +
             | 
| 113 147 | 
             
                for activity_id in tqdm(
         | 
| 114 | 
            -
                    work_tracker.filter(repository.get_activity_ids()), desc="Tile visits | 
| 148 | 
            +
                    work_tracker.filter(repository.get_activity_ids()), desc="Tile visits"
         | 
| 115 149 | 
             
                ):
         | 
| 116 | 
            -
                     | 
| 150 | 
            +
                    _process_activity(repository, tile_visit_accessor.tile_state, activity_id)
         | 
| 117 151 | 
             
                    work_tracker.mark_done(activity_id)
         | 
| 118 152 | 
             
                tile_visit_accessor.save()
         | 
| 119 153 | 
             
                work_tracker.close()
         | 
| 120 154 |  | 
| 121 155 |  | 
| 122 | 
            -
            def  | 
| 156 | 
            +
            def _process_activity(
         | 
| 123 157 | 
             
                repository: ActivityRepository, tile_state: TileState, activity_id: int
         | 
| 124 158 | 
             
            ) -> None:
         | 
| 125 159 | 
             
                activity = repository.get_activity_by_id(activity_id)
         | 
| @@ -145,7 +179,7 @@ def do_tile_stuff( | |
| 145 179 | 
             
                        zip(activity_tiles["tile_x"], activity_tiles["tile_y"]),
         | 
| 146 180 | 
             
                    ):
         | 
| 147 181 | 
             
                        if activity["consider_for_achievements"]:
         | 
| 148 | 
            -
                            if tile not in  | 
| 182 | 
            +
                            if tile not in tile_state["tile_visits"][zoom]:
         | 
| 149 183 | 
             
                                new_tile_history_soa["activity_id"].append(activity_id)
         | 
| 150 184 | 
             
                                new_tile_history_soa["time"].append(time)
         | 
| 151 185 | 
             
                                new_tile_history_soa["tile_x"].append(tile[0])
         | 
| @@ -12,6 +12,8 @@ import pandas as pd | |
| 12 12 | 
             
            from PIL import Image
         | 
| 13 13 | 
             
            from PIL import ImageDraw
         | 
| 14 14 |  | 
| 15 | 
            +
            from ...explorer.grid_file import make_grid_file_geojson
         | 
| 16 | 
            +
            from ...explorer.grid_file import make_grid_points
         | 
| 15 17 | 
             
            from geo_activity_playground.core.activities import ActivityMeta
         | 
| 16 18 | 
             
            from geo_activity_playground.core.activities import ActivityRepository
         | 
| 17 19 | 
             
            from geo_activity_playground.core.activities import make_geojson_color_line
         | 
| @@ -66,9 +68,27 @@ class ActivityController: | |
| 66 68 | 
             
                            ]
         | 
| 67 69 | 
             
                            == activity["id"]
         | 
| 68 70 | 
             
                        )
         | 
| 69 | 
            -
                        for zoom in  | 
| 71 | 
            +
                        for zoom in sorted(self._config.explorer_zoom_levels)
         | 
| 70 72 | 
             
                    }
         | 
| 71 73 |  | 
| 74 | 
            +
                    new_tiles_geojson = {}
         | 
| 75 | 
            +
                    for zoom in sorted(self._config.explorer_zoom_levels):
         | 
| 76 | 
            +
                        new_tiles = self._tile_visit_accessor.tile_state["tile_history"][zoom].loc[
         | 
| 77 | 
            +
                            self._tile_visit_accessor.tile_state["tile_history"][zoom][
         | 
| 78 | 
            +
                                "activity_id"
         | 
| 79 | 
            +
                            ]
         | 
| 80 | 
            +
                            == activity["id"]
         | 
| 81 | 
            +
                        ]
         | 
| 82 | 
            +
                        if len(new_tiles):
         | 
| 83 | 
            +
                            points = make_grid_points(
         | 
| 84 | 
            +
                                (
         | 
| 85 | 
            +
                                    (row["tile_x"], row["tile_y"])
         | 
| 86 | 
            +
                                    for index, row in new_tiles.iterrows()
         | 
| 87 | 
            +
                                ),
         | 
| 88 | 
            +
                                zoom,
         | 
| 89 | 
            +
                            )
         | 
| 90 | 
            +
                            new_tiles_geojson[zoom] = make_grid_file_geojson(points)
         | 
| 91 | 
            +
             | 
| 72 92 | 
             
                    result = {
         | 
| 73 93 | 
             
                        "activity": activity,
         | 
| 74 94 | 
             
                        "line_json": line_json,
         | 
| @@ -81,6 +101,7 @@ class ActivityController: | |
| 81 101 | 
             
                        "date": activity["start"].date(),
         | 
| 82 102 | 
             
                        "time": activity["start"].time(),
         | 
| 83 103 | 
             
                        "new_tiles": new_tiles,
         | 
| 104 | 
            +
                        "new_tiles_geojson": new_tiles_geojson,
         | 
| 84 105 | 
             
                    }
         | 
| 85 106 | 
             
                    if (
         | 
| 86 107 | 
             
                        heart_zones := _extract_heart_rate_zones(
         | 
| @@ -136,6 +136,39 @@ | |
| 136 136 | 
             
            <p>Not happy with the displayed data? <a href="{{ url_for('settings.sharepic') }}">Change share picture
         | 
| 137 137 | 
             
                    settings</a>.</p>
         | 
| 138 138 |  | 
| 139 | 
            +
            {% if new_tiles_geojson %}
         | 
| 140 | 
            +
            <h2>New explorer tiles</h2>
         | 
| 141 | 
            +
            <p>With this activity you have explored new explorer tiles. The following maps show the new tiles on the respective zoom
         | 
| 142 | 
            +
                levels.</p>
         | 
| 143 | 
            +
            <script>
         | 
| 144 | 
            +
                function add_map(id, geojson) {
         | 
| 145 | 
            +
                    let map = L.map(`map-${id}`, {
         | 
| 146 | 
            +
                        fullscreenControl: true
         | 
| 147 | 
            +
                    })
         | 
| 148 | 
            +
                    L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
         | 
| 149 | 
            +
                        maxZoom: 19,
         | 
| 150 | 
            +
                        attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
         | 
| 151 | 
            +
                    }).addTo(map)
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    let geojson_layer = L.geoJSON(geojson).addTo(map)
         | 
| 154 | 
            +
                    map.fitBounds(geojson_layer.getBounds())
         | 
| 155 | 
            +
                    return map
         | 
| 156 | 
            +
                }
         | 
| 157 | 
            +
            </script>
         | 
| 158 | 
            +
             | 
| 159 | 
            +
            <div class="row mb-3">
         | 
| 160 | 
            +
                {% for zoom, geojson in new_tiles_geojson.items() %}
         | 
| 161 | 
            +
                <div class="col-md-6">
         | 
| 162 | 
            +
                    <h3>Zoom {{ zoom }}</h3>
         | 
| 163 | 
            +
                    <div id="map-{{ zoom }}" style="height: 300px; width: 100%;"></div>
         | 
| 164 | 
            +
                    <script>
         | 
| 165 | 
            +
                        let map{{ zoom }} = add_map("{{ zoom }}", {{ geojson | safe }})
         | 
| 166 | 
            +
                    </script>
         | 
| 167 | 
            +
                </div>
         | 
| 168 | 
            +
                {% endfor %}
         | 
| 169 | 
            +
            </div>
         | 
| 170 | 
            +
            {% endif %}
         | 
| 171 | 
            +
             | 
| 139 172 | 
             
            {% if similar_activites|length > 0 %}
         | 
| 140 173 | 
             
            <div class="row mb-3">
         | 
| 141 174 | 
             
                <div class="col">
         | 
| @@ -5,7 +5,6 @@ import secrets | |
| 5 5 |  | 
| 6 6 | 
             
            from flask import Flask
         | 
| 7 7 | 
             
            from flask import render_template
         | 
| 8 | 
            -
            from flask import request
         | 
| 9 8 |  | 
| 10 9 | 
             
            from ..core.activities import ActivityRepository
         | 
| 11 10 | 
             
            from ..explorer.tile_visits import TileVisitAccessor
         | 
| @@ -16,31 +15,20 @@ from .entry_controller import EntryController | |
| 16 15 | 
             
            from .equipment.blueprint import make_equipment_blueprint
         | 
| 17 16 | 
             
            from .explorer.blueprint import make_explorer_blueprint
         | 
| 18 17 | 
             
            from .heatmap.blueprint import make_heatmap_blueprint
         | 
| 19 | 
            -
            from . | 
| 18 | 
            +
            from .search.blueprint import make_search_blueprint
         | 
| 20 19 | 
             
            from .square_planner.blueprint import make_square_planner_blueprint
         | 
| 21 20 | 
             
            from .summary.blueprint import make_summary_blueprint
         | 
| 22 21 | 
             
            from .tile.blueprint import make_tile_blueprint
         | 
| 23 22 | 
             
            from .upload.blueprint import make_upload_blueprint
         | 
| 23 | 
            +
            from geo_activity_playground.core.config import Config
         | 
| 24 24 | 
             
            from geo_activity_playground.core.config import ConfigAccessor
         | 
| 25 25 | 
             
            from geo_activity_playground.webui.auth.blueprint import make_auth_blueprint
         | 
| 26 26 | 
             
            from geo_activity_playground.webui.authenticator import Authenticator
         | 
| 27 27 | 
             
            from geo_activity_playground.webui.settings.blueprint import make_settings_blueprint
         | 
| 28 28 |  | 
| 29 29 |  | 
| 30 | 
            -
            def  | 
| 31 | 
            -
                 | 
| 32 | 
            -
             | 
| 33 | 
            -
                @app.route("/search", methods=["POST"])
         | 
| 34 | 
            -
                def search():
         | 
| 35 | 
            -
                    form_input = request.form
         | 
| 36 | 
            -
                    return render_template(
         | 
| 37 | 
            -
                        "search.html.j2",
         | 
| 38 | 
            -
                        **search_controller.render_search_results(form_input["name"])
         | 
| 39 | 
            -
                    )
         | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
            def route_start(app: Flask, repository: ActivityRepository) -> None:
         | 
| 43 | 
            -
                entry_controller = EntryController(repository)
         | 
| 30 | 
            +
            def route_start(app: Flask, repository: ActivityRepository, config: Config) -> None:
         | 
| 31 | 
            +
                entry_controller = EntryController(repository, config)
         | 
| 44 32 |  | 
| 45 33 | 
             
                @app.route("/")
         | 
| 46 34 | 
             
                def index():
         | 
| @@ -66,7 +54,6 @@ def web_ui_main( | |
| 66 54 | 
             
                host: str,
         | 
| 67 55 | 
             
                port: int,
         | 
| 68 56 | 
             
            ) -> None:
         | 
| 69 | 
            -
             | 
| 70 57 | 
             
                repository.reload()
         | 
| 71 58 |  | 
| 72 59 | 
             
                app = Flask(__name__)
         | 
| @@ -75,8 +62,7 @@ def web_ui_main( | |
| 75 62 |  | 
| 76 63 | 
             
                authenticator = Authenticator(config_accessor())
         | 
| 77 64 |  | 
| 78 | 
            -
                 | 
| 79 | 
            -
                route_start(app, repository)
         | 
| 65 | 
            +
                route_start(app, repository, config_accessor())
         | 
| 80 66 |  | 
| 81 67 | 
             
                app.register_blueprint(make_auth_blueprint(authenticator), url_prefix="/auth")
         | 
| 82 68 |  | 
| @@ -111,7 +97,11 @@ def web_ui_main( | |
| 111 97 | 
             
                    url_prefix="/square-planner",
         | 
| 112 98 | 
             
                )
         | 
| 113 99 | 
             
                app.register_blueprint(
         | 
| 114 | 
            -
                     | 
| 100 | 
            +
                    make_search_blueprint(repository),
         | 
| 101 | 
            +
                    url_prefix="/search",
         | 
| 102 | 
            +
                )
         | 
| 103 | 
            +
                app.register_blueprint(
         | 
| 104 | 
            +
                    make_summary_blueprint(repository, config_accessor()),
         | 
| 115 105 | 
             
                    url_prefix="/summary",
         | 
| 116 106 | 
             
                )
         | 
| 117 107 | 
             
                app.register_blueprint(make_tile_blueprint(), url_prefix="/tile")
         | 
| @@ -14,9 +14,6 @@ class Authenticator: | |
| 14 14 | 
             
                    self._config = config
         | 
| 15 15 |  | 
| 16 16 | 
             
                def is_authenticated(self) -> bool:
         | 
| 17 | 
            -
                    print(
         | 
| 18 | 
            -
                        f"Password={self._config.upload_password}, Session={session.get('is_authenticated', False)}"
         | 
| 19 | 
            -
                    )
         | 
| 20 17 | 
             
                    return not self._config.upload_password or session.get(
         | 
| 21 18 | 
             
                        "is_authenticated", False
         | 
| 22 19 | 
             
                    )
         | 
| @@ -6,18 +6,22 @@ import pandas as pd | |
| 6 6 |  | 
| 7 7 | 
             
            from geo_activity_playground.core.activities import ActivityRepository
         | 
| 8 8 | 
             
            from geo_activity_playground.core.activities import make_geojson_from_time_series
         | 
| 9 | 
            +
            from geo_activity_playground.core.config import Config
         | 
| 10 | 
            +
            from geo_activity_playground.webui.plot_util import make_kind_scale
         | 
| 9 11 |  | 
| 10 12 |  | 
| 11 13 | 
             
            class EntryController:
         | 
| 12 | 
            -
                def __init__(self, repository: ActivityRepository) -> None:
         | 
| 14 | 
            +
                def __init__(self, repository: ActivityRepository, config: Config) -> None:
         | 
| 13 15 | 
             
                    self._repository = repository
         | 
| 16 | 
            +
                    self._config = config
         | 
| 14 17 |  | 
| 15 18 | 
             
                def render(self) -> dict:
         | 
| 19 | 
            +
                    kind_scale = make_kind_scale(self._repository.meta, self._config)
         | 
| 16 20 | 
             
                    result = {"latest_activities": []}
         | 
| 17 21 |  | 
| 18 22 | 
             
                    if len(self._repository):
         | 
| 19 23 | 
             
                        result["distance_last_30_days_plot"] = distance_last_30_days_meta_plot(
         | 
| 20 | 
            -
                            self._repository.meta
         | 
| 24 | 
            +
                            self._repository.meta, kind_scale
         | 
| 21 25 | 
             
                        )
         | 
| 22 26 |  | 
| 23 27 | 
             
                    for activity in itertools.islice(
         | 
| @@ -33,7 +37,7 @@ class EntryController: | |
| 33 37 | 
             
                    return result
         | 
| 34 38 |  | 
| 35 39 |  | 
| 36 | 
            -
            def distance_last_30_days_meta_plot(meta: pd.DataFrame) -> str:
         | 
| 40 | 
            +
            def distance_last_30_days_meta_plot(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
         | 
| 37 41 | 
             
                before_30_days = pd.to_datetime(
         | 
| 38 42 | 
             
                    datetime.datetime.now() - datetime.timedelta(days=31)
         | 
| 39 43 | 
             
                )
         | 
| @@ -48,7 +52,7 @@ def distance_last_30_days_meta_plot(meta: pd.DataFrame) -> str: | |
| 48 52 | 
             
                    .encode(
         | 
| 49 53 | 
             
                        alt.X("yearmonthdate(start)", title="Date"),
         | 
| 50 54 | 
             
                        alt.Y("sum(distance_km)", title="Distance / km"),
         | 
| 51 | 
            -
                        alt.Color("kind", scale= | 
| 55 | 
            +
                        alt.Color("kind", scale=kind_scale, title="Kind"),
         | 
| 52 56 | 
             
                        [
         | 
| 53 57 | 
             
                            alt.Tooltip("yearmonthdate(start)", title="Date"),
         | 
| 54 58 | 
             
                            alt.Tooltip("kind", title="Kind"),
         | 
| @@ -48,6 +48,8 @@ | |
| 48 48 | 
             
                        function onEachFeature(feature, layer) {
         | 
| 49 49 | 
             
                            if (feature.properties && feature.properties.first_visit) {
         | 
| 50 50 | 
             
                                let lines = [
         | 
| 51 | 
            +
                                    `<dt>Tile</dt>`,
         | 
| 52 | 
            +
                                    `<dd>${feature.properties.tile}</dd>`,
         | 
| 51 53 | 
             
                                    `<dt>First visit</dt>`,
         | 
| 52 54 | 
             
                                    `<dd>${feature.properties.first_visit}</br><a href=/activity/${feature.properties.first_activity_id}>${feature.properties.first_activity_name}</a></dd>`,
         | 
| 53 55 | 
             
                                    `<dt>Last visit</dt>`,
         | 
| @@ -123,6 +123,7 @@ class HeatmapController: | |
| 123 123 | 
             
                                    tile_counts += aim
         | 
| 124 124 | 
             
                        tmp_path = tile_count_cache_path.with_suffix(".tmp.npy")
         | 
| 125 125 | 
             
                        np.save(tmp_path, tile_counts)
         | 
| 126 | 
            +
                        tile_count_cache_path.unlink(missing_ok=True)
         | 
| 126 127 | 
             
                        tmp_path.rename(tile_count_cache_path)
         | 
| 127 128 | 
             
                    return tile_counts
         | 
| 128 129 |  | 
| @@ -0,0 +1,9 @@ | |
| 1 | 
            +
            import altair as alt
         | 
| 2 | 
            +
            import pandas as pd
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            from geo_activity_playground.core.config import Config
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
            def make_kind_scale(meta: pd.DataFrame, config: Config) -> alt.Scale:
         | 
| 8 | 
            +
                kinds = sorted(meta["kind"].unique())
         | 
| 9 | 
            +
                return alt.Scale(domain=kinds, scheme=config.color_scheme_for_kind)
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            from flask import Blueprint
         | 
| 2 | 
            +
            from flask import render_template
         | 
| 3 | 
            +
            from flask import request
         | 
| 4 | 
            +
            from flask import Response
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            from ...core.activities import ActivityRepository
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            def make_search_blueprint(repository: ActivityRepository) -> Blueprint:
         | 
| 10 | 
            +
                blueprint = Blueprint("search", __name__, template_folder="templates")
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                @blueprint.route("/", methods=["POST"])
         | 
| 13 | 
            +
                def index():
         | 
| 14 | 
            +
                    activities = []
         | 
| 15 | 
            +
                    for _, row in repository.meta.iterrows():
         | 
| 16 | 
            +
                        if request.form["name"] in row["name"]:
         | 
| 17 | 
            +
                            activities.append(row)
         | 
| 18 | 
            +
                    return render_template("search/index.html.j2", activities=activities)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                return blueprint
         | 
| @@ -42,6 +42,73 @@ def make_settings_blueprint( | |
| 42 42 | 
             
                        **settings_controller.render_admin_password(),
         | 
| 43 43 | 
             
                    )
         | 
| 44 44 |  | 
| 45 | 
            +
                @blueprint.route("/color-schemes", methods=["GET", "POST"])
         | 
| 46 | 
            +
                @needs_authentication(authenticator)
         | 
| 47 | 
            +
                def color_schemes():
         | 
| 48 | 
            +
                    if request.method == "POST":
         | 
| 49 | 
            +
                        config_accessor().color_scheme_for_counts = request.form[
         | 
| 50 | 
            +
                            "color_scheme_for_counts"
         | 
| 51 | 
            +
                        ]
         | 
| 52 | 
            +
                        config_accessor().color_scheme_for_kind = request.form[
         | 
| 53 | 
            +
                            "color_scheme_for_kind"
         | 
| 54 | 
            +
                        ]
         | 
| 55 | 
            +
                        config_accessor.save()
         | 
| 56 | 
            +
                        flash("Updated color schemes.", category="success")
         | 
| 57 | 
            +
                    return render_template(
         | 
| 58 | 
            +
                        "settings/color-schemes.html.j2",
         | 
| 59 | 
            +
                        color_scheme_for_counts=config_accessor().color_scheme_for_counts,
         | 
| 60 | 
            +
                        color_scheme_for_counts_avail=[
         | 
| 61 | 
            +
                            "viridis",
         | 
| 62 | 
            +
                            "magma",
         | 
| 63 | 
            +
                            "inferno",
         | 
| 64 | 
            +
                            "plasma",
         | 
| 65 | 
            +
                            "cividis",
         | 
| 66 | 
            +
                            "turbo",
         | 
| 67 | 
            +
                            "bluegreen",
         | 
| 68 | 
            +
                            "bluepurple",
         | 
| 69 | 
            +
                            "goldgreen",
         | 
| 70 | 
            +
                            "goldorange",
         | 
| 71 | 
            +
                            "goldred",
         | 
| 72 | 
            +
                            "greenblue",
         | 
| 73 | 
            +
                            "orangered",
         | 
| 74 | 
            +
                            "purplebluegreen",
         | 
| 75 | 
            +
                            "purpleblue",
         | 
| 76 | 
            +
                            "purplered",
         | 
| 77 | 
            +
                            "redpurple",
         | 
| 78 | 
            +
                            "yellowgreenblue",
         | 
| 79 | 
            +
                            "yellowgreen",
         | 
| 80 | 
            +
                            "yelloworangebrown",
         | 
| 81 | 
            +
                            "yelloworangered",
         | 
| 82 | 
            +
                            "darkblue",
         | 
| 83 | 
            +
                            "darkgold",
         | 
| 84 | 
            +
                            "darkgreen",
         | 
| 85 | 
            +
                            "darkmulti",
         | 
| 86 | 
            +
                            "darkred",
         | 
| 87 | 
            +
                            "lightgreyred",
         | 
| 88 | 
            +
                            "lightgreyteal",
         | 
| 89 | 
            +
                            "lightmulti",
         | 
| 90 | 
            +
                            "lightorange",
         | 
| 91 | 
            +
                            "lighttealblue",
         | 
| 92 | 
            +
                        ],
         | 
| 93 | 
            +
                        color_scheme_for_kind=config_accessor().color_scheme_for_kind,
         | 
| 94 | 
            +
                        color_scheme_for_kind_avail=[
         | 
| 95 | 
            +
                            "accent",
         | 
| 96 | 
            +
                            "category10",
         | 
| 97 | 
            +
                            "category20",
         | 
| 98 | 
            +
                            "category20b",
         | 
| 99 | 
            +
                            "category20c",
         | 
| 100 | 
            +
                            "dark2",
         | 
| 101 | 
            +
                            "paired",
         | 
| 102 | 
            +
                            "pastel1",
         | 
| 103 | 
            +
                            "pastel2",
         | 
| 104 | 
            +
                            "set1",
         | 
| 105 | 
            +
                            "set2",
         | 
| 106 | 
            +
                            "set3",
         | 
| 107 | 
            +
                            "tableau10",
         | 
| 108 | 
            +
                            "tableau20",
         | 
| 109 | 
            +
                        ],
         | 
| 110 | 
            +
                    )
         | 
| 111 | 
            +
             | 
| 45 112 | 
             
                @blueprint.route("/equipment-offsets", methods=["GET", "POST"])
         | 
| 46 113 | 
             
                @needs_authentication(authenticator)
         | 
| 47 114 | 
             
                def equipment_offsets():
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            {% extends "page.html.j2" %}
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            {% block container %}
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            <h1 class="mb-3">Color Schemes for Plots</h1>
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            <p>Don't like color schemes in the plots? Have a look at the <a href="https://vega.github.io/vega/docs/schemes/"
         | 
| 8 | 
            +
                    target="_blank">colors schemes of Vega</a> and pick one that you like.</p>
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            <form method="POST">
         | 
| 11 | 
            +
                <div class="mb-3">
         | 
| 12 | 
            +
                    <label class="form-label">Color scheme for activity kinds</label>
         | 
| 13 | 
            +
                    <select class="form-select" aria-label="Color scheme for activity kinds" name="color_scheme_for_kind">
         | 
| 14 | 
            +
                        {% for cs in color_scheme_for_kind_avail %}
         | 
| 15 | 
            +
                        <option {% if cs==color_scheme_for_kind %} selected {% endif %}>{{ cs }}</option>
         | 
| 16 | 
            +
                        {% endfor %}
         | 
| 17 | 
            +
                    </select>
         | 
| 18 | 
            +
                </div>
         | 
| 19 | 
            +
             | 
| 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">
         | 
| 23 | 
            +
                        {% for cs in color_scheme_for_counts_avail %}
         | 
| 24 | 
            +
                        <option {% if cs==color_scheme_for_counts %} selected {% endif %}>{{ cs }}</option>
         | 
| 25 | 
            +
                        {% endfor %}
         | 
| 26 | 
            +
                    </select>
         | 
| 27 | 
            +
                </div>
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                <button type="submit" class="btn btn-primary">Save</button>
         | 
| 30 | 
            +
            </form>
         | 
| 31 | 
            +
             | 
| 32 | 
            +
             | 
| 33 | 
            +
            {% endblock %}
         | 
| @@ -20,6 +20,15 @@ | |
| 20 20 | 
             
                            </div>
         | 
| 21 21 | 
             
                        </div>
         | 
| 22 22 | 
             
                    </div>
         | 
| 23 | 
            +
                    <div class="col">
         | 
| 24 | 
            +
                        <div class="card">
         | 
| 25 | 
            +
                            <div class="card-body">
         | 
| 26 | 
            +
                                <h5 class="card-title">Color schemes</h5>
         | 
| 27 | 
            +
                                <p class="card-text">Don't like the colors in the plots?</p>
         | 
| 28 | 
            +
                                <a href="{{ url_for('.color_schemes') }}" class="btn btn-primary">Set up color schemes</a>
         | 
| 29 | 
            +
                            </div>
         | 
| 30 | 
            +
                        </div>
         | 
| 31 | 
            +
                    </div>
         | 
| 23 32 | 
             
                    <div class="col">
         | 
| 24 33 | 
             
                        <div class="card">
         | 
| 25 34 | 
             
                            <div class="card-body">
         | 
| @@ -3,10 +3,11 @@ from flask import render_template | |
| 3 3 |  | 
| 4 4 | 
             
            from ...core.activities import ActivityRepository
         | 
| 5 5 | 
             
            from .controller import SummaryController
         | 
| 6 | 
            +
            from geo_activity_playground.core.config import Config
         | 
| 6 7 |  | 
| 7 8 |  | 
| 8 | 
            -
            def make_summary_blueprint(repository: ActivityRepository) -> Blueprint:
         | 
| 9 | 
            -
                summary_controller = SummaryController(repository)
         | 
| 9 | 
            +
            def make_summary_blueprint(repository: ActivityRepository, config: Config) -> Blueprint:
         | 
| 10 | 
            +
                summary_controller = SummaryController(repository, config)
         | 
| 10 11 | 
             
                blueprint = Blueprint("summary", __name__, template_folder="templates")
         | 
| 11 12 |  | 
| 12 13 | 
             
                @blueprint.route("/")
         | 
| @@ -8,14 +8,18 @@ import pandas as pd | |
| 8 8 |  | 
| 9 9 | 
             
            from geo_activity_playground.core.activities import ActivityRepository
         | 
| 10 10 | 
             
            from geo_activity_playground.core.activities import make_geojson_from_time_series
         | 
| 11 | 
            +
            from geo_activity_playground.core.config import Config
         | 
| 12 | 
            +
            from geo_activity_playground.webui.plot_util import make_kind_scale
         | 
| 11 13 |  | 
| 12 14 |  | 
| 13 15 | 
             
            class SummaryController:
         | 
| 14 | 
            -
                def __init__(self, repository: ActivityRepository) -> None:
         | 
| 16 | 
            +
                def __init__(self, repository: ActivityRepository, config: Config) -> None:
         | 
| 15 17 | 
             
                    self._repository = repository
         | 
| 18 | 
            +
                    self._config = config
         | 
| 16 19 |  | 
| 17 20 | 
             
                @functools.cache
         | 
| 18 21 | 
             
                def render(self) -> dict:
         | 
| 22 | 
            +
                    kind_scale = make_kind_scale(self._repository.meta, self._config)
         | 
| 19 23 | 
             
                    df = embellished_activities(self._repository.meta)
         | 
| 20 24 | 
             
                    df = df.loc[df["consider_for_achievements"]]
         | 
| 21 25 |  | 
| @@ -27,14 +31,14 @@ class SummaryController: | |
| 27 31 | 
             
                    )
         | 
| 28 32 |  | 
| 29 33 | 
             
                    return {
         | 
| 30 | 
            -
                        "plot_distance_heatmap": plot_distance_heatmap(df),
         | 
| 31 | 
            -
                        "plot_monthly_distance": plot_monthly_distance(df),
         | 
| 32 | 
            -
                        "plot_yearly_distance": plot_yearly_distance(year_kind_total),
         | 
| 34 | 
            +
                        "plot_distance_heatmap": plot_distance_heatmap(df, self._config),
         | 
| 35 | 
            +
                        "plot_monthly_distance": plot_monthly_distance(df, kind_scale),
         | 
| 36 | 
            +
                        "plot_yearly_distance": plot_yearly_distance(year_kind_total, kind_scale),
         | 
| 33 37 | 
             
                        "plot_year_cumulative": plot_year_cumulative(df),
         | 
| 34 38 | 
             
                        "tabulate_year_kind_mean": tabulate_year_kind_mean(df)
         | 
| 35 39 | 
             
                        .reset_index()
         | 
| 36 40 | 
             
                        .to_dict(orient="split"),
         | 
| 37 | 
            -
                        "plot_weekly_distance": plot_weekly_distance(df),
         | 
| 41 | 
            +
                        "plot_weekly_distance": plot_weekly_distance(df, kind_scale),
         | 
| 38 42 | 
             
                        "nominations": [
         | 
| 39 43 | 
             
                            (
         | 
| 40 44 | 
             
                                self._repository.get_activity_by_id(activity_id),
         | 
| @@ -108,7 +112,7 @@ def embellished_activities(meta: pd.DataFrame) -> pd.DataFrame: | |
| 108 112 | 
             
                return df
         | 
| 109 113 |  | 
| 110 114 |  | 
| 111 | 
            -
            def plot_distance_heatmap(meta: pd.DataFrame) -> str:
         | 
| 115 | 
            +
            def plot_distance_heatmap(meta: pd.DataFrame, config: Config) -> str:
         | 
| 112 116 | 
             
                return (
         | 
| 113 117 | 
             
                    alt.Chart(
         | 
| 114 118 | 
             
                        meta.loc[
         | 
| @@ -129,7 +133,10 @@ def plot_distance_heatmap(meta: pd.DataFrame) -> str: | |
| 129 133 | 
             
                            scale=alt.Scale(reverse=True),
         | 
| 130 134 | 
             
                            title="Year and month",
         | 
| 131 135 | 
             
                        ),
         | 
| 132 | 
            -
                        alt.Color( | 
| 136 | 
            +
                        alt.Color(
         | 
| 137 | 
            +
                            "sum(distance_km)",
         | 
| 138 | 
            +
                            scale=alt.Scale(scheme=config.color_scheme_for_counts),
         | 
| 139 | 
            +
                        ),
         | 
| 133 140 | 
             
                        [
         | 
| 134 141 | 
             
                            alt.Tooltip("yearmonthdate(start)", title="Date"),
         | 
| 135 142 | 
             
                            alt.Tooltip(
         | 
| @@ -142,7 +149,7 @@ def plot_distance_heatmap(meta: pd.DataFrame) -> str: | |
| 142 149 | 
             
                )
         | 
| 143 150 |  | 
| 144 151 |  | 
| 145 | 
            -
            def plot_monthly_distance(meta: pd.DataFrame) -> str:
         | 
| 152 | 
            +
            def plot_monthly_distance(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
         | 
| 146 153 | 
             
                return (
         | 
| 147 154 | 
             
                    alt.Chart(
         | 
| 148 155 | 
             
                        meta.loc[
         | 
| @@ -159,7 +166,7 @@ def plot_monthly_distance(meta: pd.DataFrame) -> str: | |
| 159 166 | 
             
                    .encode(
         | 
| 160 167 | 
             
                        alt.X("month(start)", title="Month"),
         | 
| 161 168 | 
             
                        alt.Y("sum(distance_km)", title="Distance / km"),
         | 
| 162 | 
            -
                        alt.Color("kind", scale= | 
| 169 | 
            +
                        alt.Color("kind", scale=kind_scale, title="Kind"),
         | 
| 163 170 | 
             
                        alt.Column("year(start):O", title="Year"),
         | 
| 164 171 | 
             
                    )
         | 
| 165 172 | 
             
                    .resolve_axis(x="independent")
         | 
| @@ -167,14 +174,14 @@ def plot_monthly_distance(meta: pd.DataFrame) -> str: | |
| 167 174 | 
             
                )
         | 
| 168 175 |  | 
| 169 176 |  | 
| 170 | 
            -
            def plot_yearly_distance(year_kind_total: pd.DataFrame) -> str:
         | 
| 177 | 
            +
            def plot_yearly_distance(year_kind_total: pd.DataFrame, kind_scale: alt.Scale) -> str:
         | 
| 171 178 | 
             
                return (
         | 
| 172 179 | 
             
                    alt.Chart(year_kind_total, title="Total Distance per Year")
         | 
| 173 180 | 
             
                    .mark_bar()
         | 
| 174 181 | 
             
                    .encode(
         | 
| 175 182 | 
             
                        alt.X("year:O", title="Year"),
         | 
| 176 183 | 
             
                        alt.Y("distance_km", title="Distance / km"),
         | 
| 177 | 
            -
                        alt.Color("kind", title="Kind"),
         | 
| 184 | 
            +
                        alt.Color("kind", scale=kind_scale, title="Kind"),
         | 
| 178 185 | 
             
                        [
         | 
| 179 186 | 
             
                            alt.Tooltip("year:O", title="Year"),
         | 
| 180 187 | 
             
                            alt.Tooltip("kind", title="Kind"),
         | 
| @@ -231,7 +238,7 @@ def tabulate_year_kind_mean(df: pd.DataFrame) -> pd.DataFrame: | |
| 231 238 | 
             
                return year_kind_mean_distance
         | 
| 232 239 |  | 
| 233 240 |  | 
| 234 | 
            -
            def plot_weekly_distance(df: pd.DataFrame) -> str:
         | 
| 241 | 
            +
            def plot_weekly_distance(df: pd.DataFrame, kind_scale: alt.Scale) -> str:
         | 
| 235 242 | 
             
                week_kind_total_distance = (
         | 
| 236 243 | 
             
                    df[["year", "week", "kind", "distance_km"]]
         | 
| 237 244 | 
             
                    .groupby(["year", "week", "kind"])
         | 
| @@ -261,7 +268,7 @@ def plot_weekly_distance(df: pd.DataFrame) -> str: | |
| 261 268 | 
             
                    .encode(
         | 
| 262 269 | 
             
                        alt.X("year_week", title="Year and Week"),
         | 
| 263 270 | 
             
                        alt.Y("distance_km", title="Distance / km"),
         | 
| 264 | 
            -
                        alt.Color("kind", title="Kind"),
         | 
| 271 | 
            +
                        alt.Color("kind", scale=kind_scale, title="Kind"),
         | 
| 265 272 | 
             
                        [
         | 
| 266 273 | 
             
                            alt.Tooltip("year_week", title="Year and Week"),
         | 
| 267 274 | 
             
                            alt.Tooltip("kind", title="Kind"),
         | 
| @@ -12,7 +12,7 @@ | |
| 12 12 | 
             
                </div>
         | 
| 13 13 | 
             
                <div class="col-md-3">
         | 
| 14 14 | 
             
                    <h2>Search activities</h2>
         | 
| 15 | 
            -
                    <form method="post" action=" | 
| 15 | 
            +
                    <form method="post" action="{{ url_for('search.index') }}">
         | 
| 16 16 | 
             
                        <input type="search" name="name" />
         | 
| 17 17 | 
             
                        <input type="submit" class="button" />
         | 
| 18 18 | 
             
                    </form>
         | 
| @@ -62,42 +62,70 @@ | |
| 62 62 | 
             
                                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
         | 
| 63 63 |  | 
| 64 64 | 
             
                                    {% if num_activities > 0 %}
         | 
| 65 | 
            -
                                    <li class="nav-item">
         | 
| 66 | 
            -
                                        <a class="nav-link"  | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
                                         | 
| 65 | 
            +
                                    <li class="nav-item dropdown">
         | 
| 66 | 
            +
                                        <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
         | 
| 67 | 
            +
                                            aria-expanded="false">
         | 
| 68 | 
            +
                                            Activities
         | 
| 69 | 
            +
                                        </a>
         | 
| 70 | 
            +
                                        <ul class="dropdown-menu">
         | 
| 71 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('calendar.index') }}">Calendar</a></li>
         | 
| 72 | 
            +
                                            {# <li><a class="dropdown-item" href="{{ url_for('search.index') }}">Search</a></li> #}
         | 
| 73 | 
            +
                                        </ul>
         | 
| 70 74 | 
             
                                    </li>
         | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 75 | 
            +
             | 
| 76 | 
            +
                                    <li class="nav-item dropdown">
         | 
| 77 | 
            +
                                        <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
         | 
| 78 | 
            +
                                            aria-expanded="false">
         | 
| 79 | 
            +
                                            Statistics
         | 
| 80 | 
            +
                                        </a>
         | 
| 81 | 
            +
                                        <ul class="dropdown-menu">
         | 
| 82 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('summary.index') }}">Summary
         | 
| 83 | 
            +
                                                    Statistics</a>
         | 
| 84 | 
            +
                                            </li>
         | 
| 85 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('eddington.index') }}">Eddington
         | 
| 86 | 
            +
                                                    Number</a>
         | 
| 87 | 
            +
                                            </li>
         | 
| 88 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('equipment.index') }}">Equipment</a></li>
         | 
| 89 | 
            +
                                        </ul>
         | 
| 74 90 | 
             
                                    </li>
         | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 91 | 
            +
             | 
| 92 | 
            +
                                    <li class="nav-item dropdown">
         | 
| 93 | 
            +
                                        <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
         | 
| 94 | 
            +
                                            aria-expanded="false">
         | 
| 95 | 
            +
                                            Explorer
         | 
| 96 | 
            +
                                        </a>
         | 
| 97 | 
            +
                                        <ul class="dropdown-menu">
         | 
| 98 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('explorer.map', zoom=14) }}">Explorer
         | 
| 99 | 
            +
                                                    Tiles (Zoom 14)</a></li>
         | 
| 100 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('explorer.map', zoom=17) }}">Squadratinhos
         | 
| 101 | 
            +
                                                    (Zoom 17)</a></li>
         | 
| 102 | 
            +
                                        </ul>
         | 
| 78 103 | 
             
                                    </li>
         | 
| 104 | 
            +
             | 
| 79 105 | 
             
                                    <li class="nav-item">
         | 
| 80 106 | 
             
                                        <a class="nav-link" aria-current="page" href="{{ url_for('heatmap.index') }}">Heatmap</a>
         | 
| 81 107 | 
             
                                    </li>
         | 
| 82 | 
            -
                                    <li class="nav-item">
         | 
| 83 | 
            -
                                        <a class="nav-link" aria-current="page"
         | 
| 84 | 
            -
                                            href="{{ url_for('eddington.index') }}">Eddington</a>
         | 
| 85 | 
            -
                                    </li>
         | 
| 86 | 
            -
                                    <li class="nav-item">
         | 
| 87 | 
            -
                                        <a class="nav-link" aria-current="page"
         | 
| 88 | 
            -
                                            href="{{ url_for('equipment.index') }}">Equipment</a>
         | 
| 89 | 
            -
                                    </li>
         | 
| 90 108 | 
             
                                    {% endif %}
         | 
| 91 | 
            -
                                    <li class="nav-item">
         | 
| 92 | 
            -
                                        <a class="nav-link" aria-current="page" href="{{ url_for('upload.index') }}">Upload</a>
         | 
| 93 | 
            -
                                    </li>
         | 
| 94 | 
            -
                                    <li class="nav-item">
         | 
| 95 | 
            -
                                        <a class="nav-link" aria-current="page" href="{{ url_for('upload.reload') }}">Refresh</a>
         | 
| 96 | 
            -
                                    </li>
         | 
| 97 | 
            -
                                    <li class="nav-item">
         | 
| 98 | 
            -
                                        <a class="nav-link" aria-current="page" href="{{ url_for('settings.index') }}">Settings</a>
         | 
| 99 | 
            -
                                    </li>
         | 
| 100 109 |  | 
| 110 | 
            +
                                    <li class="nav-item dropdown">
         | 
| 111 | 
            +
                                        <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
         | 
| 112 | 
            +
                                            aria-expanded="false">
         | 
| 113 | 
            +
                                            Admin
         | 
| 114 | 
            +
                                        </a>
         | 
| 115 | 
            +
                                        <ul class="dropdown-menu">
         | 
| 116 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('upload.index') }}">Upload Activities</a>
         | 
| 117 | 
            +
                                            </li>
         | 
| 118 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('upload.reload') }}">Scan New
         | 
| 119 | 
            +
                                                    Activities</a>
         | 
| 120 | 
            +
                                            </li>
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                                            <li>
         | 
| 123 | 
            +
                                                <hr class="dropdown-divider">
         | 
| 124 | 
            +
                                            </li>
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                                            <li><a class="dropdown-item" href="{{ url_for('settings.index') }}">Settings</a></li>
         | 
| 127 | 
            +
                                        </ul>
         | 
| 128 | 
            +
                                    </li>
         | 
| 101 129 |  | 
| 102 130 | 
             
                                    <li class="nav-item dropdown">
         | 
| 103 131 | 
             
                                        <button
         | 
    
        {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/METADATA
    RENAMED
    
    | @@ -1,14 +1,13 @@ | |
| 1 1 | 
             
            Metadata-Version: 2.1
         | 
| 2 2 | 
             
            Name: geo-activity-playground
         | 
| 3 | 
            -
            Version: 0. | 
| 3 | 
            +
            Version: 0.29.0
         | 
| 4 4 | 
             
            Summary: Analysis of geo data activities like rides, runs or hikes.
         | 
| 5 5 | 
             
            License: MIT
         | 
| 6 6 | 
             
            Author: Martin Ueding
         | 
| 7 7 | 
             
            Author-email: mu@martin-ueding.de
         | 
| 8 | 
            -
            Requires-Python: >=3. | 
| 8 | 
            +
            Requires-Python: >=3.10,<3.13
         | 
| 9 9 | 
             
            Classifier: License :: OSI Approved :: MIT License
         | 
| 10 10 | 
             
            Classifier: Programming Language :: Python :: 3
         | 
| 11 | 
            -
            Classifier: Programming Language :: Python :: 3.9
         | 
| 12 11 | 
             
            Classifier: Programming Language :: Python :: 3.10
         | 
| 13 12 | 
             
            Classifier: Programming Language :: Python :: 3.11
         | 
| 14 13 | 
             
            Classifier: Programming Language :: Python :: 3.12
         | 
| @@ -30,7 +29,7 @@ Requires-Dist: python-dateutil (>=2.8.2,<3.0.0) | |
| 30 29 | 
             
            Requires-Dist: requests (>=2.28.1,<3.0.0)
         | 
| 31 30 | 
             
            Requires-Dist: scipy (>=1.8.1,<2.0.0)
         | 
| 32 31 | 
             
            Requires-Dist: shapely (>=2.0.5,<3.0.0)
         | 
| 33 | 
            -
            Requires-Dist: stravalib (>= | 
| 32 | 
            +
            Requires-Dist: stravalib (>=2.0,<3.0)
         | 
| 34 33 | 
             
            Requires-Dist: tcxreader (>=0.4.5,<0.5.0)
         | 
| 35 34 | 
             
            Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
         | 
| 36 35 | 
             
            Requires-Dist: tqdm (>=4.64.0,<5.0.0)
         | 
    
        {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/RECORD
    RENAMED
    
    | @@ -2,22 +2,22 @@ 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=yDjf3aVOC3LvnAwAvQUl_e7S3NXq_vKgccq0hGuFdLI,6609
         | 
| 5 | 
            -
            geo_activity_playground/core/config.py,sha256= | 
| 5 | 
            +
            geo_activity_playground/core/config.py,sha256=DsjZhiq2BZs94rjd7zgt_KTZIjVfxfntiEQZhgq8NeA,4617
         | 
| 6 6 | 
             
            geo_activity_playground/core/coordinates.py,sha256=tDfr9mlXhK6E_MMIJ0vYWVCoH0Lq8uyuaqUgaa8i0jg,966
         | 
| 7 7 | 
             
            geo_activity_playground/core/enrichment.py,sha256=CwZhW-svgPAYbdx3n9kvKlTgcsiCaeuJfSRCC4JxX6g,7411
         | 
| 8 8 | 
             
            geo_activity_playground/core/heart_rate.py,sha256=IwMt58TpjOYqpAxtsj07zP2ttpN_J3GZeiv-qGhYyJc,1598
         | 
| 9 9 | 
             
            geo_activity_playground/core/heatmap.py,sha256=bRLQHzmTEsQbX8XWeg85x_lRGk272UoYRiCnoxZ5da0,4189
         | 
| 10 | 
            -
            geo_activity_playground/core/paths.py,sha256= | 
| 10 | 
            +
            geo_activity_playground/core/paths.py,sha256=BZYuIg1LVHjuWLKB0Iz6Cevlq-XSalpCes_ClFuXa0s,2410
         | 
| 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 | 
            -
            geo_activity_playground/core/tasks.py,sha256= | 
| 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 16 | 
             
            geo_activity_playground/core/tiles.py,sha256=KpzD-h3kNzZ2ieLt6f2xHilSF3lHyfaEXPnrGvlIAz0,3379
         | 
| 17 17 | 
             
            geo_activity_playground/core/time_conversion.py,sha256=9J6aTlqJhWvsknQkoECNL-CIG-8BKs6ZatJJ9XJnTsg,367
         | 
| 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
         | 
| 20 | 
            -
            geo_activity_playground/explorer/tile_visits.py,sha256= | 
| 20 | 
            +
            geo_activity_playground/explorer/tile_visits.py,sha256=VaenegdPQ51vXxvn2oQXj8-3A7t8ihMhkgV8HpAqEuI,14133
         | 
| 21 21 | 
             
            geo_activity_playground/explorer/video.py,sha256=ROAmV9shfJyqTgnXVD41KFORiwnRgVpEWenIq4hMCRM,4389
         | 
| 22 22 | 
             
            geo_activity_playground/importers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 23 23 | 
             
            geo_activity_playground/importers/activity_parsers.py,sha256=m2SpvGlTZ8F3gG6YB24_ZFrlOAbtqbfWi-GIYspeUco,10593
         | 
| @@ -31,15 +31,15 @@ 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=upQzZa5sKApj_Fmu6PziFDboi7SBL5Zsi-tNSSNPlEE,1759
         | 
| 34 | 
            -
            geo_activity_playground/webui/activity/controller.py,sha256= | 
| 34 | 
            +
            geo_activity_playground/webui/activity/controller.py,sha256=PJIZ7pFqpUyWDrintnnlW6Hxj7DbdPswARozws6TE30,18861
         | 
| 35 35 | 
             
            geo_activity_playground/webui/activity/templates/activity/day.html.j2,sha256=r3qKl9uTzOko4R-ZzyYAZt1j61JSevYP4g0Yi06HHPg,2702
         | 
| 36 36 | 
             
            geo_activity_playground/webui/activity/templates/activity/lines.html.j2,sha256=5gB1aDjRgi_RventenRfC10_FtMT4ch_VuWvA9AMlBY,1121
         | 
| 37 37 | 
             
            geo_activity_playground/webui/activity/templates/activity/name.html.j2,sha256=RDLEt6ip8_ngmdLgaC5jg92Dk-F2umGwKkd8cWmvVko,2400
         | 
| 38 | 
            -
            geo_activity_playground/webui/activity/templates/activity/show.html.j2,sha256= | 
| 39 | 
            -
            geo_activity_playground/webui/app.py,sha256= | 
| 38 | 
            +
            geo_activity_playground/webui/activity/templates/activity/show.html.j2,sha256=W77M1S7RQOGY3Vg9LRT5mFnefuMWUKKU1Vd-ZKxUoKg,6552
         | 
| 39 | 
            +
            geo_activity_playground/webui/app.py,sha256=foON49jw8klBJ3GF70DDjgz-_KRDPFvC31_mScaqDXk,4255
         | 
| 40 40 | 
             
            geo_activity_playground/webui/auth/blueprint.py,sha256=Lx-ZvMnfHLC1CMre1xPQI3k_pCtQoZvgRhtmafULzoE,812
         | 
| 41 41 | 
             
            geo_activity_playground/webui/auth/templates/auth/index.html.j2,sha256=ILQ5HvTEYc3OrtOAIFt1VrqWorVD70V9DC342znmP70,579
         | 
| 42 | 
            -
            geo_activity_playground/webui/authenticator.py,sha256= | 
| 42 | 
            +
            geo_activity_playground/webui/authenticator.py,sha256=k278OEVuOfAmTGT4F2X4pqSTwwkK_FA87EIhAeysEqc,1416
         | 
| 43 43 | 
             
            geo_activity_playground/webui/calendar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 44 44 | 
             
            geo_activity_playground/webui/calendar/blueprint.py,sha256=rlnhgU2DWAcdLMRq7m77NzrM_aDyp4s3kuuQHuzjHhg,782
         | 
| 45 45 | 
             
            geo_activity_playground/webui/calendar/controller.py,sha256=QpSAkR2s1sbLSu6P_fNNTccgGglOzEH2PIv1XwKxeVY,2778
         | 
| @@ -49,26 +49,29 @@ geo_activity_playground/webui/eddington/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J | |
| 49 49 | 
             
            geo_activity_playground/webui/eddington/blueprint.py,sha256=evIvueLfDWVTxJ9pRguqmZ9-Pybd2WmBRst_-7vX2QA,551
         | 
| 50 50 | 
             
            geo_activity_playground/webui/eddington/controller.py,sha256=ly7JSkSS79kO4CL_xugB62uRuuWKVqOjbN-pheelv94,2910
         | 
| 51 51 | 
             
            geo_activity_playground/webui/eddington/templates/eddington/index.html.j2,sha256=XHKeUymQMS5x00PLOVlg-nSRCz_jHB2pvD8QunULWJ4,1839
         | 
| 52 | 
            -
            geo_activity_playground/webui/entry_controller.py,sha256= | 
| 52 | 
            +
            geo_activity_playground/webui/entry_controller.py,sha256=kTEToBtR1T4l30cV3HkCK3KO2hVYfb22BSgcWLdLEXQ,2164
         | 
| 53 53 | 
             
            geo_activity_playground/webui/equipment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 54 54 | 
             
            geo_activity_playground/webui/equipment/blueprint.py,sha256=_NIhRJuJNbXpEd_nEPo01AqnUqPgo1vawFn7E3yoeng,636
         | 
| 55 55 | 
             
            geo_activity_playground/webui/equipment/controller.py,sha256=Sx9i2RCK7m4W6FgpDfRMewcH64VBQfUhHJdTSCwMqOU,4079
         | 
| 56 56 | 
             
            geo_activity_playground/webui/equipment/templates/equipment/index.html.j2,sha256=FEfxB4XwVYELAOdjVlSlprjJH_kLmE-pNWEEXdPqc6I,1778
         | 
| 57 57 | 
             
            geo_activity_playground/webui/explorer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 58 58 | 
             
            geo_activity_playground/webui/explorer/blueprint.py,sha256=EKnBs8llqT6Wy1uac18dF2epp3TebF9p3iGlSbj6Vl0,2337
         | 
| 59 | 
            -
            geo_activity_playground/webui/explorer/controller.py,sha256 | 
| 60 | 
            -
            geo_activity_playground/webui/explorer/templates/explorer/index.html.j2,sha256= | 
| 59 | 
            +
            geo_activity_playground/webui/explorer/controller.py,sha256=PAVyewO5ZY8BM1-EwtiqZuU-u63BJYuAKNNEDNutJkw,11669
         | 
| 60 | 
            +
            geo_activity_playground/webui/explorer/templates/explorer/index.html.j2,sha256=u2htecx-XwINRiINHFN6EZDaRXVtiape1OCUZexTBU8,7049
         | 
| 61 61 | 
             
            geo_activity_playground/webui/heatmap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 62 62 | 
             
            geo_activity_playground/webui/heatmap/blueprint.py,sha256=bjQu-HL3QN5UpJ6tHOifhcLGlPr_hIKvaRu78md4JqM,1470
         | 
| 63 | 
            -
            geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256= | 
| 63 | 
            +
            geo_activity_playground/webui/heatmap/heatmap_controller.py,sha256=Q17Ay8hbU5ZlUiz2a9S-ULWrnNGWQHvTTV3kDY5FhNc,7411
         | 
| 64 64 | 
             
            geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2,sha256=YLeu6P4djl8G4qAXR6DhetseqrbOodN7aN4coocknc4,1875
         | 
| 65 | 
            -
            geo_activity_playground/webui/ | 
| 66 | 
            -
            geo_activity_playground/webui/ | 
| 65 | 
            +
            geo_activity_playground/webui/plot_util.py,sha256=pTTQoqOCkLVjkgOit7mbry28kMruZIL8amZozSzEpxQ,283
         | 
| 66 | 
            +
            geo_activity_playground/webui/search/blueprint.py,sha256=b3TCIplY60MWE2_VsKHuoV1LAgNwd_T5ft5t0CKALFI,642
         | 
| 67 | 
            +
            geo_activity_playground/webui/search/templates/search/index.html.j2,sha256=FvNRoDfUlSzXjM_tqZY_fDhuhUDgbPaY73q56gdvF1A,1130
         | 
| 68 | 
            +
            geo_activity_playground/webui/settings/blueprint.py,sha256=u5VGzVoRRA9Am5WqC1VGod4L-k839_9qVlecKO9sRUE,7593
         | 
| 67 69 | 
             
            geo_activity_playground/webui/settings/controller.py,sha256=v14oKvI1QzWn0g41Sm4NA_g9q4SUVZ_bO9SUOZuPAaY,9121
         | 
| 68 70 | 
             
            geo_activity_playground/webui/settings/templates/settings/admin-password.html.j2,sha256=VYwddpObD1RpeTH5Dm4y7VtmT7kwURDCIjxyzJeq08c,495
         | 
| 71 | 
            +
            geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2,sha256=CaFbYkkU1yGTOlAzGq97u3tVgS79RIo7PEmiVjuZiBc,1226
         | 
| 69 72 | 
             
            geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2,sha256=ltaYwFe8S8Mi72ddmIp1vwqlu8MEXXjBGfbpN2WBTC4,1728
         | 
| 70 73 | 
             
            geo_activity_playground/webui/settings/templates/settings/heart-rate.html.j2,sha256=UPT3MegRgSeff36lhCo0l3ZwhqNSIg5gM6h2s32GkCY,4255
         | 
| 71 | 
            -
            geo_activity_playground/webui/settings/templates/settings/index.html.j2,sha256 | 
| 74 | 
            +
            geo_activity_playground/webui/settings/templates/settings/index.html.j2,sha256=-ZSlR6htaeMMOf4ISUSzWPu5BUhYODuAmcWPN0ZoBno,4443
         | 
| 72 75 | 
             
            geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2,sha256=IdUfXon1Pu8zX3NirKb28ypshLHOvZRpz2T4bJrzrak,1067
         | 
| 73 76 | 
             
            geo_activity_playground/webui/settings/templates/settings/metadata-extraction.html.j2,sha256=Ppa8O-zRJznbeCsF4YQj37_HM9nOW8fyTi66jvWvHmA,2285
         | 
| 74 77 | 
             
            geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2,sha256=7BxFvCaVJOEqbImyK5vxCmhh-NGSFaRa9ARhqjZeYJ0,3093
         | 
| @@ -91,12 +94,11 @@ geo_activity_playground/webui/static/mstile-150x150.png,sha256=j1ANUQJ1Xi1DR2sGq | |
| 91 94 | 
             
            geo_activity_playground/webui/static/safari-pinned-tab.svg,sha256=OzoEVGY0igWRXM1NiM3SRKugdICBN7aB_XuxaC3Mu9Q,8371
         | 
| 92 95 | 
             
            geo_activity_playground/webui/static/site.webmanifest,sha256=4vYxdPMpwTdB8EmOvHkkYcjZ8Yrci3pOwwY3o_VwACA,440
         | 
| 93 96 | 
             
            geo_activity_playground/webui/summary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 94 | 
            -
            geo_activity_playground/webui/summary/blueprint.py,sha256= | 
| 95 | 
            -
            geo_activity_playground/webui/summary/controller.py,sha256= | 
| 97 | 
            +
            geo_activity_playground/webui/summary/blueprint.py,sha256=tfA2aPF19yKwkQOb5lPQBySoQYYhTn49Iuh0SYvsGP8,593
         | 
| 98 | 
            +
            geo_activity_playground/webui/summary/controller.py,sha256=cWn5szA1o5Vjht0DyhRwBjmwqJryrLcmm4FUdmVpUoo,9443
         | 
| 96 99 | 
             
            geo_activity_playground/webui/summary/templates/summary/index.html.j2,sha256=rsII1eMY-xNugh8A9SecnEcDZqkEOWYIfiHAGroQYuM,4442
         | 
| 97 | 
            -
            geo_activity_playground/webui/templates/home.html.j2,sha256= | 
| 98 | 
            -
            geo_activity_playground/webui/templates/page.html.j2,sha256= | 
| 99 | 
            -
            geo_activity_playground/webui/templates/search.html.j2,sha256=FvNRoDfUlSzXjM_tqZY_fDhuhUDgbPaY73q56gdvF1A,1130
         | 
| 100 | 
            +
            geo_activity_playground/webui/templates/home.html.j2,sha256=eIPNyLHhUNVTITDbn6nR82-ZJA5Dp4SY41cZTjymZDU,2428
         | 
| 101 | 
            +
            geo_activity_playground/webui/templates/page.html.j2,sha256=HN7s4i4kR3laMFkToktjwmTQiTHdrY3nqKcCVO9nKdA,11088
         | 
| 100 102 | 
             
            geo_activity_playground/webui/tile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 101 103 | 
             
            geo_activity_playground/webui/tile/blueprint.py,sha256=cK0o2Z3BrLycgF9zw0F8s9qF-JaYDbF5Gog-GXDtUZ8,943
         | 
| 102 104 | 
             
            geo_activity_playground/webui/tile/controller.py,sha256=PISh4vKs27b-LxFfTARtr5RAwHFresA1Kw1MDcERSRU,1221
         | 
| @@ -105,8 +107,8 @@ geo_activity_playground/webui/upload/blueprint.py,sha256=xX9scEmleN_dL03jfhWRh5y | |
| 105 107 | 
             
            geo_activity_playground/webui/upload/controller.py,sha256=disRtrlvPiqsIFt9UaAokgtRtXCvosg7bXkAnN_qaxk,4102
         | 
| 106 108 | 
             
            geo_activity_playground/webui/upload/templates/upload/index.html.j2,sha256=I1Ix8tDS3YBdi-HdaNfjkzYXVVCjfUTe5PFTnap1ydc,775
         | 
| 107 109 | 
             
            geo_activity_playground/webui/upload/templates/upload/reload.html.j2,sha256=YZWX5eDeNyqKJdQAywDBcU8DZBm22rRBbZqFjrFrCvQ,556
         | 
| 108 | 
            -
            geo_activity_playground-0. | 
| 109 | 
            -
            geo_activity_playground-0. | 
| 110 | 
            -
            geo_activity_playground-0. | 
| 111 | 
            -
            geo_activity_playground-0. | 
| 112 | 
            -
            geo_activity_playground-0. | 
| 110 | 
            +
            geo_activity_playground-0.29.0.dist-info/LICENSE,sha256=4RpAwKO8bPkfXH2lnpeUW0eLkNWglyG4lbrLDU_MOwY,1070
         | 
| 111 | 
            +
            geo_activity_playground-0.29.0.dist-info/METADATA,sha256=692u7MhLYbWTJR5OSwKqyS8sZDG3oq3TNZQyPIsHVlk,1612
         | 
| 112 | 
            +
            geo_activity_playground-0.29.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
         | 
| 113 | 
            +
            geo_activity_playground-0.29.0.dist-info/entry_points.txt,sha256=pbNlLI6IIZIp7nPYCfAtiSiz2oxJSCl7DODD6SPkLKk,81
         | 
| 114 | 
            +
            geo_activity_playground-0.29.0.dist-info/RECORD,,
         | 
| @@ -1,19 +0,0 @@ | |
| 1 | 
            -
            import logging
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            from ..core.activities import ActivityRepository
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            logger = logging.getLogger(__name__)
         | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
            class SearchController:
         | 
| 9 | 
            -
                def __init__(self, repository: ActivityRepository) -> None:
         | 
| 10 | 
            -
                    self._repository = repository
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                def render_search_results(self, name: str) -> dict:
         | 
| 13 | 
            -
                    logger.info(f"Searching for {name=}")
         | 
| 14 | 
            -
                    activities = []
         | 
| 15 | 
            -
                    for _, row in self._repository.meta.iterrows():
         | 
| 16 | 
            -
                        if name in row["name"]:
         | 
| 17 | 
            -
                            activities.append(row)
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                    return {"activities": activities}
         | 
    
        /geo_activity_playground/webui/{templates/search.html.j2 → search/templates/search/index.html.j2}
    RENAMED
    
    | 
            File without changes
         | 
    
        {geo_activity_playground-0.28.0.dist-info → geo_activity_playground-0.29.0.dist-info}/LICENSE
    RENAMED
    
    | 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         |