implement multi-point ramps including in-ramp marker support

This commit is contained in:
Laura Klünder 2024-08-18 13:14:51 +02:00
parent 53b30df5a2
commit 5eb7cd363c

View file

@ -1,5 +1,5 @@
import logging import logging
from collections import deque from collections import deque, namedtuple
from decimal import Decimal from decimal import Decimal
from itertools import chain, combinations from itertools import chain, combinations
from operator import attrgetter, itemgetter from operator import attrgetter, itemgetter
@ -183,6 +183,9 @@ class AltitudeAreaPoint(BaseSchema):
altitude: float altitude: float
RampConnectedTo = namedtuple('RampConnectedTo', ('area', 'intersections'))
class AltitudeArea(LevelGeometryMixin, models.Model): class AltitudeArea(LevelGeometryMixin, models.Model):
""" """
An altitude area An altitude area
@ -235,10 +238,11 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
@classmethod @classmethod
def recalculate(cls): def recalculate(cls):
# collect location areas # collect location areas
all_areas = [] all_areas: list[AltitudeArea] = [] # all non-ramp altitude areas of the entire map
all_ramps = [] all_ramps: list[AltitudeArea] = [] # all ramp altitude areas of the entire map
space_areas = {} space_areas: dict[int, list[AltitudeArea]] = {} # all non-ramp altitude areas present in the given space
spaces = {} space_ramps: dict[int, list[AltitudeArea]] = {} # all ramp altitude areas present in the given space
spaces: dict[int, list[Space]] = {} # all spaces by space id
levels = Level.objects.prefetch_related('buildings', 'doors', 'spaces', 'spaces__columns', levels = Level.objects.prefetch_related('buildings', 'doors', 'spaces', 'spaces__columns',
'spaces__obstacles', 'spaces__lineobstacles', 'spaces__holes', 'spaces__obstacles', 'spaces__lineobstacles', 'spaces__holes',
'spaces__stairs', 'spaces__ramps', 'spaces__stairs', 'spaces__ramps',
@ -246,9 +250,9 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
logger = logging.getLogger('c3nav') logger = logging.getLogger('c3nav')
for level in levels: for level in levels:
areas = [] areas = [] # all altitude areas on this level that aren't ramps
ramps = [] ramps = [] # all altitude areas on this level that are ramps
stairs = [] stairs = [] # all stairs on this level
# collect all accessible areas on this level # collect all accessible areas on this level
buildings_geom = unary_union(tuple(unwrap_geom(building.geometry) for building in level.buildings.all())) buildings_geom = unary_union(tuple(unwrap_geom(building.geometry) for building in level.buildings.all()))
@ -271,7 +275,9 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
ramp = AltitudeArea(geometry=geometry, level=level) ramp = AltitudeArea(geometry=geometry, level=level)
ramp.geometry_prep = prepared.prep(geometry) ramp.geometry_prep = prepared.prep(geometry)
ramp.space = space.pk ramp.space = space.pk
ramp.markers = []
ramps.append(ramp) ramps.append(ramp)
space_ramps.setdefault(space.pk, []).append(ramp)
areas = tuple(orient(polygon) for polygon in assert_multipolygon( areas = tuple(orient(polygon) for polygon in assert_multipolygon(
unary_union(areas+list(unwrap_geom(door.geometry) for door in level.doors.all())) unary_union(areas+list(unwrap_geom(door.geometry) for door in level.doors.all()))
@ -315,11 +321,16 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
area.altitude = altitudemarker.altitude area.altitude = altitudemarker.altitude
break break
else: else:
logger.error( for ramp in space_ramps[space.pk]:
_('AltitudeMarker #%(marker_id)d in Space #%(space_id)d on Level %(level_label)s ' if ramp.geometry_prep.contains(unwrap_geom(altitudemarker.geometry)):
'is not placed in an accessible area') % {'marker_id': altitudemarker.pk, ramp.markers.append(altitudemarker)
'space_id': space.pk, break
'level_label': level.short_label}) else:
logger.error(
_('AltitudeMarker #%(marker_id)d in Space #%(space_id)d on Level %(level_label)s '
'is not placed in an accessible area') % {'marker_id': altitudemarker.pk,
'space_id': space.pk,
'level_label': level.short_label})
# determine altitude area connections # determine altitude area connections
for area in areas: for area in areas:
@ -335,15 +346,18 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
buffered = ramp.geometry.buffer(0.001) buffered = ramp.geometry.buffer(0.001)
for area in areas: for area in areas:
if area.geometry_prep.intersects(buffered): if area.geometry_prep.intersects(buffered):
intersection = area.geometry.intersection(buffered) intersections = []
ramp.connected_to.append((area, intersection)) for area_polygon in assert_multipolygon(area.geometry):
if len(ramp.connected_to) != 2: for ring in chain([area_polygon.exterior], area_polygon.interiors):
if len(ramp.connected_to) == 0: if ring.intersects(buffered):
logger.warning('A ramp in space #%d has no connections!' % ramp.space) intersections.append(ring.intersection(buffered))
elif len(ramp.connected_to) == 1: ramp.connected_to.append(RampConnectedTo(area, intersections))
logger.warning('A ramp in space #%d has only one connection!' % ramp.space) num_altitudes = len(ramp.connected_to) + len(ramp.markers)
else: if num_altitudes != 2:
logger.warning('A ramp in space #%d has more than two connections!' % ramp.space) if num_altitudes == 0:
logger.warning('A ramp in space #%d has no altitudes!' % ramp.space)
elif num_altitudes == 1:
logger.warning('A ramp in space #%d has only one altitude!' % ramp.space)
# add areas to global areas # add areas to global areas
all_areas.extend(areas) all_areas.extend(areas)
@ -476,20 +490,17 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
continue continue
if len(ramp.connected_to) == 1: if len(ramp.connected_to) == 1:
ramp.altitude = ramp.connected_to[0][0].altitude ramp.altitude = ramp.connected_to[0].area.altitude
continue continue
# todo: implement multiple points points = []
for connected_to in ramp.connected_to:
if len(ramp.connected_to) > 2: for intersection in connected_to.intersections:
ramp.connected_to = sorted(ramp.connected_to, key=lambda item: item[1].area)[-2:] points.extend([AltitudeAreaPoint(coordinates=coords, altitude=float(connected_to.area.altitude))
for coords in intersection.coords])
ramp.points = [ points.extend([AltitudeAreaPoint(coordinates=marker.geometry.coords, altitude=float(marker.altitude))
AltitudeAreaPoint(coordinates=ramp.connected_to[0][1].centroid.coords, for marker in ramp.markers])
altitude=float(ramp.connected_to[0][0].altitude)), ramp.points = points
AltitudeAreaPoint(coordinates=ramp.connected_to[1][1].centroid.coords,
altitude=float(ramp.connected_to[1][0].altitude)),
]
ramp.tmpid = len(areas) ramp.tmpid = len(areas)
areas.append(ramp) areas.append(ramp)
@ -589,7 +600,8 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
area = max(touches, key=lambda item: ( area = max(touches, key=lambda item: (
item.value > min_touches, item.value > min_touches,
item.obj.points is not None, item.obj.points is not None,
item.obj.altitude or max(p.altitude for p in item.obj.points), (max(p.altitude for p in item.obj.points)
if item.obj.altitude is None else item.obj.altitude),
item.value item.value
)).obj )).obj
else: else: