diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2023-03-22 20:02:18 +0100 | 
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2023-03-25 13:16:45 +0100 | 
| commit | 7e2ed3206897632c59ce62e7e19852d04b4c256e (patch) | |
| tree | b3cae9a099e358b13c3e4854a7c70d54817777f7 | |
| parent | 9aa6266d0e29523aecc6e449e40fd64d0574485b (diff) | |
| download | fietsboek-7e2ed3206897632c59ce62e7e19852d04b4c256e.tar.gz fietsboek-7e2ed3206897632c59ce62e7e19852d04b4c256e.tar.bz2 fietsboek-7e2ed3206897632c59ce62e7e19852d04b4c256e.zip  | |
show heatmap on profile
So far, fietsboek does not generate them, but if you happen (by
accident) to have hittekaart output a heatmap to the right location, the
profile page will now show it.
| -rw-r--r-- | fietsboek/data.py | 37 | ||||
| -rw-r--r-- | fietsboek/routes.py | 5 | ||||
| -rw-r--r-- | fietsboek/templates/layout.jinja2 | 6 | ||||
| -rw-r--r-- | fietsboek/templates/profile.jinja2 | 40 | ||||
| -rw-r--r-- | fietsboek/views/profile.py | 73 | 
5 files changed, 160 insertions, 1 deletions
diff --git a/fietsboek/data.py b/fietsboek/data.py index 9f49c49..9be47da 100644 --- a/fietsboek/data.py +++ b/fietsboek/data.py @@ -54,6 +54,9 @@ class DataManager:      def _track_data_dir(self, track_id):          return self.data_dir / "tracks" / str(track_id) +    def _user_data_dir(self, user_id): +        return self.data_dir / "users" / str(user_id) +      def maintenance_mode(self) -> Optional[str]:          """Checks whether the maintenance mode is enabled. @@ -99,6 +102,18 @@ class DataManager:              raise FileNotFoundError(f"The path {path} is not a directory") from None          return TrackDataDir(track_id, path) +    def open_user(self, user_id: int) -> "UserDataDir": +        """Opens a user's data directory. + +        :raises FileNotFoundError: If the user directory does not exist. +        :param user_id: ID of the user. +        :return: The manager that can be used to manage this user's data. +        """ +        path = self._user_data_dir(user_id) +        if not path.is_dir(): +            raise FileNotFoundError(f"The path {path} is not a directory") from None +        return UserDataDir(user_id, path) +  class TrackDataDir:      """Manager for a single track's data. @@ -360,3 +375,25 @@ class TrackDataDir:              self.journal.append(("delete_image", path, path.read_bytes()))          path.unlink() + + +class UserDataDir: +    """Manager for a single user's data.""" + +    def __init__(self, user_id: int, path: Path): +        self.user_id = user_id +        self.path = path + +    def heatmap_path(self) -> Path: +        """Returns the path for the heatmap tile file. + +        :return: The path of the heatmap SQLite databse. +        """ +        return self.path / "heatmap.sqlite" + +    def tilehunt_path(self) -> Path: +        """Returns the path for the tilehunt tile file. + +        :return: The path of the tilehunt SQLite database. +        """ +        return self.path / "tilehunt.sqlite" diff --git a/fietsboek/routes.py b/fietsboek/routes.py index 5e05269..8f109d9 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -60,5 +60,10 @@ def includeme(config):      config.add_route("json-friends", "/me/friends.json")      config.add_route("profile", "/user/{user_id}", factory="fietsboek.models.User.factory") +    config.add_route( +        "user-tile", +        "/user/{user_id}/tile/{map}/{z:\\d+}/{x:\\d+}/{y:\\d+}", +        factory="fietsboek.models.User.factory", +    )      config.add_route("tile-proxy", "/tile/{provider}/{z:\\d+}/{x:\\d+}/{y:\\d+}") diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index 8f322f6..19fb700 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -17,6 +17,8 @@      <!-- Custom styles for this scaffold -->      <link href="{{request.static_url('fietsboek:static/fonts.css')}}" rel="stylesheet">      <link href="{{request.static_url('fietsboek:static/theme.css')}}" rel="stylesheet"> +    <!-- Pre-load leaflet CSS --> +    <link href="{{request.static_url('fietsboek:static/GM_Utils/leaflet/leaflet.css')}}" rel="stylesheet">      <script>  const FRIENDS_URL = {{ request.route_url('json-friends') | tojson }}; @@ -111,10 +113,14 @@ const Legende = false;      ================================================== -->      <!-- Placed at the end of the document so the pages load faster -->      <script src="{{request.static_url('fietsboek:static/bootstrap.bundle.min.js')}}"></script> +    <!-- Pre-load leaflet Javascript. This lets us use Leaflet on any page, without relying on GPXViewer to load it --> +    <script src="{{request.static_url('fietsboek:static/GM_Utils/leaflet/leaflet.js')}}">      <!-- Our patch to the GPX viewer, load before the actual GPX viewer -->      <script src="{{request.static_url('fietsboek:static/osm-monkeypatch.js')}}"></script>      <!-- Jürgen Berkemeier's GPX viewer -->      <script src="{{request.static_url('fietsboek:static/GM_Utils/GPX2GM.js')}}"></script>      <script src="{{request.static_url('fietsboek:static/fietsboek.js')}}"></script> +    {% block latescripts %} +    {% endblock %}    </body>  </html> diff --git a/fietsboek/templates/profile.jinja2 b/fietsboek/templates/profile.jinja2 index da4d732..1363216 100644 --- a/fietsboek/templates/profile.jinja2 +++ b/fietsboek/templates/profile.jinja2 @@ -3,6 +3,11 @@  {% block content %}  <div class="container">    <h1>{{ user.name }}</h1> + +  {% if heatmap_url or tilehunt_url %} +  <div id="userMap" style="height: 600px; width: 100%;"></div> +  {% endif %} +    <table class="table table-hover table-sm">      <th scope="row">{{ _("page.details.length") }}</th>        <td id="detailsLength">{{ (total_length / 1000) | round(2) | format_decimal }} km</td> @@ -34,3 +39,38 @@    </table>  </div>  {% endblock %} + +{% block latescripts %} +<script> +  var map = L.map('userMap').setView([52.520008, 13.404954], 2); + +  baseLayers = {}; +  overlayLayers = {}; + +  {% if heatmap_url %} +  overlayLayers[{{ _("page.profile.heatmap") | tojson }}] = L.tileLayer({{ heatmap_url | tojson }}, { +    maxZoom: 19, +  }); +  {% endif %} +  {% if tilehunt_url %} +  overlayLayers[{{ _("page.profile.tilehunt") | tojson }}] = L.tileLayer({{ tilehunt_url | tojson }}, { +    maxZoom: 19, +  }); +  {% endif %} + +  for (let layer of TILE_LAYERS) { +      if (layer.type === "base") { +          baseLayers[layer.name] = L.tileLayer(layer.url, { +              maxZoom: layer.zoom, +              attribution: layer.attribution, +          }); +      } else if (layer.type === "overlay") { +          overlayLayers[layer.name] = L.tileLayer(layer.url, { +              attribution: layer.attribution, +          }); +      } +  } + +  L.control.layers(baseLayers, overlayLayers).addTo(map); +</script> +{% endblock %} diff --git a/fietsboek/views/profile.py b/fietsboek/views/profile.py index 488ffb0..94133f0 100644 --- a/fietsboek/views/profile.py +++ b/fietsboek/views/profile.py @@ -1,9 +1,13 @@  import datetime +import sqlite3 +from pyramid.httpexceptions import HTTPNotFound  from pyramid.request import Request +from pyramid.response import Response  from pyramid.view import view_config  from .. import models, util +from ..data import UserDataDir  from ..models.track import TrackType, TrackWithMetadata @@ -47,6 +51,38 @@ def profile(request: Request) -> dict:      total_moving_time = round_to_seconds(total_moving_time)      total_stopped_time = round_to_seconds(total_stopped_time) +    heatmap_url = None +    tilehunt_url = None +    try: +        user_dir: UserDataDir = request.data_manager.open_user(request.context.id) +    except FileNotFoundError: +        pass +    else: +        if user_dir.heatmap_path().is_file(): +            heatmap_url = request.route_url( +                "user-tile", +                user_id=request.context.id, +                map="heatmap", +                z="ZZZ", +                x="XXX", +                y="YYY", +            ) +            heatmap_url = ( +                heatmap_url.replace("ZZZ", "{z}").replace("XXX", "{x}").replace("YYY", "{y}") +            ) +        if user_dir.tilehunt_path().is_file(): +            tilehunt_url = request.route_url( +                "user-tile", +                user_id=request.context.id, +                map="tilehunt", +                z="ZZZ", +                x="XXX", +                y="YYY", +            ) +            tilehunt_url = ( +                tilehunt_url.replace("ZZZ", "{z}").replace("XXX", "{x}").replace("YYY", "{y}") +            ) +      return {          "user": request.context,          "total_length": total_length, @@ -57,7 +93,42 @@ def profile(request: Request) -> dict:          "max_speed": max_speed,          "avg_speed": avg_speed,          "mps_to_kph": util.mps_to_kph, +        "heatmap_url": heatmap_url, +        "tilehunt_url": tilehunt_url,      } -__all__ = ["profile"] +@view_config(route_name="user-tile", request_method="GET") +def user_tile(request: Request) -> Response: +    """Returns a single tile from the user's own overlay maps. + +    :param request: The pyramid request. +    :return: The response, with the tile content (or an error). +    """ +    try: +        user_dir: UserDataDir = request.data_manager.open_user(request.context.id) +    except FileNotFoundError: +        return HTTPNotFound() + +    paths = { +        "heatmap": user_dir.heatmap_path(), +        "tilehunt": user_dir.tilehunt_path(), +    } +    path = paths.get(request.matchdict["map"]) +    if path is None: +        return HTTPNotFound() + +    connection = sqlite3.connect(path) +    cursor = connection.cursor() +    result = cursor.execute( +        "SELECT data FROM tiles WHERE zoom = ? AND x = ? AND y = ?;", +        (int(request.matchdict["z"]), int(request.matchdict["x"]), int(request.matchdict["y"])), +    ) +    result = result.fetchone() +    if result is None: +        return HTTPNotFound() + +    return Response(result[0], content_type="image/png") + + +__all__ = ["profile", "user_tile"]  | 
