start implementing RangeLocator
This commit is contained in:
parent
e3afa4b2ea
commit
0b6362c8ab
8 changed files with 125 additions and 21 deletions
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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 }}
|
||||
<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 %}
|
||||
|
|
|
@ -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)
|
69
src/c3nav/routing/rangelocator.py
Normal file
69
src/c3nav/routing/rangelocator.py
Normal 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
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue