From 0b6362c8ab0afaf3cd2983c86e0138bdd9dd68b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Fri, 10 Nov 2023 20:11:50 +0100 Subject: [PATCH] start implementing RangeLocator --- src/c3nav/mapdata/models/update.py | 4 ++ src/c3nav/mapdata/utils/locations.py | 24 ++++--- src/c3nav/mesh/models.py | 5 ++ .../mesh/templates/mesh/node_detail.html | 31 +++++++-- .../templatetags/__init__.py | 0 .../templatetags/mesh_node.py | 6 +- src/c3nav/routing/rangelocator.py | 69 +++++++++++++++++++ src/c3nav/routing/router.py | 7 +- 8 files changed, 125 insertions(+), 21 deletions(-) rename src/c3nav/{control => mesh}/templatetags/__init__.py (100%) rename src/c3nav/{control => mesh}/templatetags/mesh_node.py (89%) create mode 100644 src/c3nav/routing/rangelocator.py diff --git a/src/c3nav/mapdata/models/update.py b/src/c3nav/mapdata/models/update.py index a51e9e47..dfeba68d 100644 --- a/src/c3nav/mapdata/models/update.py +++ b/src/c3nav/mapdata/models/update.py @@ -184,6 +184,10 @@ class MapUpdate(models.Model): from c3nav.routing.locator import Locator Locator.rebuild(new_updates[-1].to_tuple) + logger.info('Rebuilding range locator...') + from c3nav.routing.rangelocator import RangeLocator + RangeLocator.rebuild(new_updates[-1].to_tuple) + for new_update in reversed(new_updates): new_update.processed = True new_update.save() diff --git a/src/c3nav/mapdata/utils/locations.py b/src/c3nav/mapdata/utils/locations.py index 0a79c0d8..475a5519 100644 --- a/src/c3nav/mapdata/utils/locations.py +++ b/src/c3nav/mapdata/utils/locations.py @@ -2,9 +2,10 @@ import math import operator import re from collections import OrderedDict +from dataclasses import dataclass, field from functools import reduce from itertools import chain -from typing import List, Mapping, Optional, Union +from typing import Any, List, Mapping, Optional, Union from django.apps import apps from django.db.models import Prefetch, Q @@ -271,20 +272,23 @@ def get_custom_location_for_request(slug: str, request): AccessPermission.get_for_request(request)) +@dataclass class CustomLocation: can_search = True can_describe = True access_restriction_id = None - def __init__(self, level, x, y, permissions, icon='pin_drop'): - x = round(x, 2) - y = round(y, 2) - self.pk = 'c:%s:%s:%s' % (level.short_label, x, y) - self.permissions = permissions - self.level = level - self.x = x - self.y = y - self.icon = icon + pk: str = field(init=False) + level: Level + x: float | int + y: float | int + permissions: Any = () # todo: correct this + icon: str = "pin_drop" + + def __post_init__(self): + x = round(self.x, 2) + y = round(self.y, 2) + self.pk = 'c:%s:%s:%s' % (self.level.short_label, x, y) @property def serialized_geometry(self): diff --git a/src/c3nav/mesh/models.py b/src/c3nav/mesh/models.py index b6014d56..241fbcec 100644 --- a/src/c3nav/mesh/models.py +++ b/src/c3nav/mesh/models.py @@ -19,6 +19,7 @@ from c3nav.mesh.messages import ChipType, ConfigFirmwareMessage, ConfigHardwareM from c3nav.mesh.messages import MeshMessage as MeshMessage from c3nav.mesh.messages import MeshMessageType from c3nav.mesh.utils import UPLINK_TIMEOUT +from c3nav.routing.rangelocator import RangeLocator FirmwareLookup = namedtuple('FirmwareLookup', ('sha256_hash', 'chip', 'project_name', 'version', 'idf_version')) @@ -283,6 +284,10 @@ class MeshNode(models.Model): return False return dst_node.get_uplink() + def get_locator_beacon(self): + locator = RangeLocator.load() + return locator.beacons.get(self.address, None) + class MeshUplink(models.Model): """ diff --git a/src/c3nav/mesh/templates/mesh/node_detail.html b/src/c3nav/mesh/templates/mesh/node_detail.html index 68adf25b..16a7cad5 100644 --- a/src/c3nav/mesh/templates/mesh/node_detail.html +++ b/src/c3nav/mesh/templates/mesh/node_detail.html @@ -1,5 +1,5 @@ {% extends 'mesh/base.html' %} -{% load i18n %} +{% load i18n mesh_node %} {% block heading %}{% trans 'Mesh Node' %} {{ node }}{% endblock %} @@ -105,13 +105,30 @@

Position configuration

- X={{ node.last_messages.CONFIG_POSITION.parsed.x_pos }}, Y={{ node.last_messages.CONFIG_POSITION.parsed.y_pos }}, Z={{ node.last_messages.CONFIG_POSITION.parsed.z_pos }} -

-

- - {% trans 'Change' %} - + On Device: + X={{ node.last_messages.CONFIG_POSITION.parsed.x_pos | cm_to_m }} + Y={{ node.last_messages.CONFIG_POSITION.parsed.y_pos | cm_to_m }} + Z={{ node.last_messages.CONFIG_POSITION.parsed.z_pos | cm_to_m }}

+ {% with locator_beacon=node.get_locator_beacon %} + {% if locator_beacon %} +

+ In Map: + X={{ locator_beacon.x | cm_to_m }} + Y={{ locator_beacon.y | cm_to_m }} + Z={{ locator_beacon.z | cm_to_m }} +

+ {% else %} +

+ (not known in map) +

+

+ + {% trans 'Change' %} + +

+ {% endif %} + {% endwith %} {% endblock %} diff --git a/src/c3nav/control/templatetags/__init__.py b/src/c3nav/mesh/templatetags/__init__.py similarity index 100% rename from src/c3nav/control/templatetags/__init__.py rename to src/c3nav/mesh/templatetags/__init__.py diff --git a/src/c3nav/control/templatetags/mesh_node.py b/src/c3nav/mesh/templatetags/mesh_node.py similarity index 89% rename from src/c3nav/control/templatetags/mesh_node.py rename to src/c3nav/mesh/templatetags/mesh_node.py index baa835a1..d38f11b3 100644 --- a/src/c3nav/control/templatetags/mesh_node.py +++ b/src/c3nav/mesh/templatetags/mesh_node.py @@ -27,6 +27,6 @@ def mesh_node(context, node: str | MeshNode): ) -@register.filter() -def m_to_cm(value): - return "%.2fm" % (int(value)/100) +@register.filter(name="cm_to_m") +def cm_to_m(value): + return "%.2f" % (int(value)/100) diff --git a/src/c3nav/routing/rangelocator.py b/src/c3nav/routing/rangelocator.py new file mode 100644 index 00000000..e66fb447 --- /dev/null +++ b/src/c3nav/routing/rangelocator.py @@ -0,0 +1,69 @@ +import os +import pickle +import threading +from dataclasses import dataclass +from typing import Self + +from django.conf import settings + +from c3nav.mapdata.models import MapUpdate +from c3nav.mapdata.models.geometry.space import RangingBeacon +from c3nav.routing.router import Router + + +@dataclass +class RangeLocatorBeacon: + bssid: str + x: int + y: int + z: int + + +@dataclass +class RangeLocator: + filename = os.path.join(settings.CACHE_ROOT, 'rangelocator') + + beacons: dict[str, RangeLocatorBeacon] + + @classmethod + def rebuild(cls, update): + router = Router.load() + + # get beacons and calculate absoluze z coordinate + beacons = {} + for beacon in RangingBeacon.objects.all(): + beacons[beacon.bssid] = RangeLocatorBeacon( + bssid=beacon.bssid, + x=int(beacon.geometry.x * 100), + y=int(beacon.geometry.y * 100), + z=int(router.altitude_for_point(beacon.space_id, beacon.geometry) * 100), + ) + + locator = cls(beacons=beacons) + pickle.dump(locator, open(cls.build_filename(update), 'wb')) + return locator + + @classmethod + def build_filename(cls, update): + return os.path.join(settings.CACHE_ROOT, 'rangelocator_%s.pickle' % MapUpdate.build_cache_key(*update)) + + @classmethod + def load_nocache(cls, update): + return pickle.load(open(cls.build_filename(update), 'rb')) + + cached = None + cache_update = None + cache_lock = threading.Lock() + + @classmethod + def load(cls) -> Self: + from c3nav.mapdata.models import MapUpdate + update = MapUpdate.last_processed_update() + if cls.cache_update != update: + with cls.cache_lock: + cls.cache_update = update + cls.cached = cls.load_nocache(update) + return cls.cached + + def locate(self, scan, permissions=None): + return None diff --git a/src/c3nav/routing/router.py b/src/c3nav/routing/router.py index b2f1bc3f..11b544d2 100644 --- a/src/c3nav/routing/router.py +++ b/src/c3nav/routing/router.py @@ -78,7 +78,9 @@ class Router: for space in level.spaces.all(): # create space geometries accessible_geom = space.geometry.difference(unary_union( - tuple(unwrap_geom(column.geometry) for column in space.columns.all() if column.access_restriction_id is None) + + tuple(unwrap_geom(column.geometry) + for column in space.columns.all() + if column.access_restriction_id is None) + tuple(unwrap_geom(hole.geometry) for hole in space.holes.all()) + ((buildings_geom, ) if space.outside else ()) )) @@ -362,6 +364,9 @@ class Router: return None return min(spaces, key=operator.itemgetter(1))[0] + def altitude_for_point(self, space: int, point: Point) -> float: + return self.spaces[space].altitudearea_for_point(point).get_altitude(point) + def describe_custom_location(self, location): restrictions = self.get_restrictions(location.permissions) space = self.space_for_point(level=location.level.pk, point=location, restrictions=restrictions)