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
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()

View file

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

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 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):
"""

View file

@ -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 @@
<h4>Position configuration</h4>
<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 }}
</p>
<p>
<a class="button" href="{% url "mesh.send" recipient=node.address msg_type="CONFIG_POSITION" %}">
{% trans 'Change' %}
</a>
<strong>On Device:</strong>
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 }}
</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>
{% endblock %}

View file

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

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():
# 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)