implement multi-point ramps including in-ramp marker support
This commit is contained in:
parent
53b30df5a2
commit
5eb7cd363c
1 changed files with 47 additions and 35 deletions
|
@ -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:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue