start implementing RangeLocator

This commit is contained in:
Laura Klünder 2023-11-10 20:11:50 +01:00
parent e3afa4b2ea
commit 0b6362c8ab
8 changed files with 125 additions and 21 deletions

View file

@ -184,6 +184,10 @@ class MapUpdate(models.Model):
from c3nav.routing.locator import Locator from c3nav.routing.locator import Locator
Locator.rebuild(new_updates[-1].to_tuple) 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): for new_update in reversed(new_updates):
new_update.processed = True new_update.processed = True
new_update.save() new_update.save()

View file

@ -2,9 +2,10 @@ import math
import operator import operator
import re import re
from collections import OrderedDict from collections import OrderedDict
from dataclasses import dataclass, field
from functools import reduce from functools import reduce
from itertools import chain 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.apps import apps
from django.db.models import Prefetch, Q 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)) AccessPermission.get_for_request(request))
@dataclass
class CustomLocation: class CustomLocation:
can_search = True can_search = True
can_describe = True can_describe = True
access_restriction_id = None access_restriction_id = None
def __init__(self, level, x, y, permissions, icon='pin_drop'): pk: str = field(init=False)
x = round(x, 2) level: Level
y = round(y, 2) x: float | int
self.pk = 'c:%s:%s:%s' % (level.short_label, x, y) y: float | int
self.permissions = permissions permissions: Any = () # todo: correct this
self.level = level icon: str = "pin_drop"
self.x = x
self.y = y def __post_init__(self):
self.icon = icon x = round(self.x, 2)
y = round(self.y, 2)
self.pk = 'c:%s:%s:%s' % (self.level.short_label, x, y)
@property @property
def serialized_geometry(self): def serialized_geometry(self):

View file

@ -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 MeshMessage as MeshMessage
from c3nav.mesh.messages import MeshMessageType from c3nav.mesh.messages import MeshMessageType
from c3nav.mesh.utils import UPLINK_TIMEOUT from c3nav.mesh.utils import UPLINK_TIMEOUT
from c3nav.routing.rangelocator import RangeLocator
FirmwareLookup = namedtuple('FirmwareLookup', ('sha256_hash', 'chip', 'project_name', 'version', 'idf_version')) FirmwareLookup = namedtuple('FirmwareLookup', ('sha256_hash', 'chip', 'project_name', 'version', 'idf_version'))
@ -283,6 +284,10 @@ class MeshNode(models.Model):
return False return False
return dst_node.get_uplink() return dst_node.get_uplink()
def get_locator_beacon(self):
locator = RangeLocator.load()
return locator.beacons.get(self.address, None)
class MeshUplink(models.Model): class MeshUplink(models.Model):
""" """

View file

@ -1,5 +1,5 @@
{% extends 'mesh/base.html' %} {% extends 'mesh/base.html' %}
{% load i18n %} {% load i18n mesh_node %}
{% block heading %}{% trans 'Mesh Node' %} {{ node }}{% endblock %} {% block heading %}{% trans 'Mesh Node' %} {{ node }}{% endblock %}
@ -105,13 +105,30 @@
<h4>Position configuration</h4> <h4>Position configuration</h4>
<p> <p>
<strong>X=</strong>{{ node.last_messages.CONFIG_POSITION.parsed.x_pos }}, <strong>Y=</strong>{{ node.last_messages.CONFIG_POSITION.parsed.y_pos }}, <strong>Z=</strong>{{ node.last_messages.CONFIG_POSITION.parsed.z_pos }} <strong>On Device:</strong>
</p> X={{ node.last_messages.CONFIG_POSITION.parsed.x_pos | cm_to_m }}
<p> Y={{ node.last_messages.CONFIG_POSITION.parsed.y_pos | cm_to_m }}
<a class="button" href="{% url "mesh.send" recipient=node.address msg_type="CONFIG_POSITION" %}"> Z={{ node.last_messages.CONFIG_POSITION.parsed.z_pos | cm_to_m }}
{% trans 'Change' %}
</a>
</p> </p>
{% with locator_beacon=node.get_locator_beacon %}
{% if locator_beacon %}
<p>
<strong>In Map:</strong>
X={{ locator_beacon.x | cm_to_m }}
Y={{ locator_beacon.y | cm_to_m }}
Z={{ locator_beacon.z | cm_to_m }}
</p>
{% else %}
<p>
<em>(not known in map)</em>
</p>
<p>
<a class="button" href="{% url "mesh.send" recipient=node.address msg_type="CONFIG_POSITION" %}">
{% trans 'Change' %}
</a>
</p>
{% endif %}
{% endwith %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -27,6 +27,6 @@ def mesh_node(context, node: str | MeshNode):
) )
@register.filter() @register.filter(name="cm_to_m")
def m_to_cm(value): def cm_to_m(value):
return "%.2fm" % (int(value)/100) return "%.2f" % (int(value)/100)

View file

@ -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

View file

@ -78,7 +78,9 @@ class Router:
for space in level.spaces.all(): for space in level.spaces.all():
# create space geometries # create space geometries
accessible_geom = space.geometry.difference(unary_union( 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()) + tuple(unwrap_geom(hole.geometry) for hole in space.holes.all()) +
((buildings_geom, ) if space.outside else ()) ((buildings_geom, ) if space.outside else ())
)) ))
@ -362,6 +364,9 @@ class Router:
return None return None
return min(spaces, key=operator.itemgetter(1))[0] 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): def describe_custom_location(self, location):
restrictions = self.get_restrictions(location.permissions) restrictions = self.get_restrictions(location.permissions)
space = self.space_for_point(level=location.level.pk, point=location, restrictions=restrictions) space = self.space_for_point(level=location.level.pk, point=location, restrictions=restrictions)