team-3/src/c3nav/mapdata/models/level.py

266 lines
9.4 KiB
Python
Raw Normal View History

2016-08-29 18:49:24 +02:00
from django.db import models
2016-11-26 13:48:44 +01:00
from django.utils.functional import cached_property
2016-08-29 18:49:24 +02:00
from django.utils.translation import ugettext_lazy as _
from shapely.geometry import CAP_STYLE, JOIN_STYLE
2016-11-26 13:48:44 +01:00
from shapely.ops import cascaded_union
2016-08-29 18:49:24 +02:00
from c3nav.mapdata.models.base import MapItem
2016-12-08 12:36:09 +01:00
from c3nav.mapdata.utils.geometry import assert_multilinestring
2016-08-29 18:49:24 +02:00
class Level(MapItem):
2016-08-29 18:49:24 +02:00
"""
A map level (-1, 0, 1, 2)
"""
name = models.SlugField(_('level name'), unique=True, max_length=50,
2016-08-29 18:49:24 +02:00
help_text=_('Usually just an integer (e.g. -1, 0, 1, 2)'))
2016-12-18 00:40:10 +01:00
altitude = models.DecimalField(_('level altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
2016-12-04 01:49:49 +01:00
intermediate = models.BooleanField(_('intermediate level'))
2016-08-29 18:49:24 +02:00
class Meta:
verbose_name = _('Level')
verbose_name_plural = _('Levels')
default_related_name = 'levels'
2016-12-05 12:14:07 +01:00
ordering = ['altitude']
2016-09-24 14:09:52 +02:00
2016-11-26 13:48:44 +01:00
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
2016-12-04 01:49:49 +01:00
@cached_property
def public_geometries(self):
return LevelGeometries.by_level(self, only_public=True)
2016-12-04 01:49:49 +01:00
@cached_property
def geometries(self):
return LevelGeometries.by_level(self, only_public=False)
2016-11-26 13:48:44 +01:00
2016-09-24 15:36:14 +02:00
def tofilename(self):
return 'levels/%s.json' % self.name
2016-12-04 01:49:49 +01:00
def lower(self):
return Level.objects.filter(altitude__lt=self.altitude).order_by('altitude')
def higher(self):
return Level.objects.filter(altitude__gt=self.altitude).order_by('altitude')
@classmethod
def fromfile(cls, data, file_path):
kwargs = super().fromfile(data, file_path)
if 'altitude' not in data:
2016-09-24 14:09:52 +02:00
raise ValueError('missing altitude.')
if not isinstance(data['altitude'], (int, float)):
2016-09-24 14:09:52 +02:00
raise ValueError('altitude has to be int or float.')
kwargs['altitude'] = data['altitude']
2016-12-04 11:19:53 +01:00
if 'intermediate' not in data:
raise ValueError('missing intermediate.')
if not isinstance(data['intermediate'], bool):
raise ValueError('intermediate has to be boolean.')
kwargs['intermediate'] = data['intermediate']
return kwargs
2016-08-30 13:58:45 +02:00
def tofile(self):
result = super().tofile()
result['altitude'] = float(self.altitude)
2016-12-04 11:19:53 +01:00
result['intermediate'] = self.intermediate
return result
2016-11-26 13:48:44 +01:00
2016-12-01 12:25:02 +01:00
def __str__(self):
return self.name
2016-11-26 13:48:44 +01:00
class LevelGeometries():
2016-12-04 01:49:49 +01:00
by_level_name = {}
@classmethod
def by_level(cls, level, only_public=True):
return cls.by_level_name.setdefault((level.name, only_public), cls(level, only_public=only_public))
2016-12-04 01:49:49 +01:00
def __init__(self, level, only_public=True):
2016-11-26 13:48:44 +01:00
self.level = level
self.only_public = only_public
from c3nav.mapdata.permissions import get_public_packages
self.public_packages = get_public_packages()
def query(self, name):
queryset = getattr(self.level, name)
if not self.only_public:
return queryset.all()
return queryset.filter(package__in=self.public_packages)
2016-11-26 13:48:44 +01:00
2016-12-04 01:49:49 +01:00
@cached_property
def raw_rooms(self):
return cascaded_union([room.geometry for room in self.query('rooms')])
2016-12-04 01:49:49 +01:00
2016-11-26 13:48:44 +01:00
@cached_property
def buildings(self):
result = cascaded_union([building.geometry for building in self.query('buildings')])
2016-12-04 01:49:49 +01:00
if self.level.intermediate:
result = cascaded_union([result, self.raw_rooms])
return result
2016-11-26 13:48:44 +01:00
@cached_property
def rooms(self):
2016-12-04 01:49:49 +01:00
return self.raw_rooms.intersection(self.buildings)
@cached_property
def outsides(self):
return cascaded_union([outside.geometry for outside in self.query('outsides')]).difference(self.buildings)
@cached_property
def mapped(self):
return cascaded_union([self.buildings, self.outsides])
@cached_property
def lineobstacles(self):
lineobstacles = []
for obstacle in self.query('lineobstacles'):
lineobstacles.append(obstacle.geometry.buffer(obstacle.width/2,
join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat))
return cascaded_union(lineobstacles)
@cached_property
2016-12-09 02:50:21 +01:00
def uncropped_obstacles(self):
obstacles = [obstacle.geometry for obstacle in self.query('obstacles').filter(crop_to_level__isnull=True)]
2016-12-09 02:50:21 +01:00
return cascaded_union(obstacles).intersection(self.mapped)
@cached_property
def cropped_obstacles(self):
levels_by_name = {}
obstacles_by_crop_to_level = {}
for obstacle in self.query('obstacles').filter(crop_to_level__isnull=False):
2016-12-09 02:50:21 +01:00
level_name = obstacle.crop_to_level.name
levels_by_name.setdefault(level_name, obstacle.crop_to_level)
obstacles_by_crop_to_level.setdefault(level_name, []).append(obstacle.geometry)
all_obstacles = []
for level_name, obstacles in obstacles_by_crop_to_level.items():
2016-12-09 02:50:21 +01:00
obstacles = cascaded_union(obstacles).intersection(levels_by_name[level_name].geometries.mapped)
all_obstacles.append(obstacles)
all_obstacles.extend(self.lineobstacles)
return cascaded_union(all_obstacles).intersection(self.mapped)
2016-12-09 02:50:21 +01:00
@cached_property
def obstacles(self):
return cascaded_union([self.uncropped_obstacles, self.cropped_obstacles])
@cached_property
def raw_doors(self):
return cascaded_union([door.geometry for door in self.query('doors').all()]).intersection(self.mapped)
@cached_property
def raw_escalators(self):
return cascaded_union([escalator.geometry for escalator in self.query('escalators').all()])
@cached_property
def escalators(self):
return self.raw_escalators.intersection(self.accessible)
2016-11-28 20:56:52 +01:00
@cached_property
def elevatorlevels(self):
return cascaded_union([elevatorlevel.geometry for elevatorlevel in self.query('elevatorlevels').all()])
2016-11-28 20:56:52 +01:00
@cached_property
def areas(self):
2016-11-29 00:47:37 +01:00
return cascaded_union([self.rooms, self.outsides, self.elevatorlevels])
@cached_property
def holes(self):
return cascaded_union([holes.geometry for holes in self.query('holes').all()]).intersection(self.areas)
2016-11-29 00:47:37 +01:00
@cached_property
def accessible(self):
return self.areas.difference(self.holes).difference(self.obstacles)
2016-12-18 14:36:06 +01:00
@cached_property
def accessible_without_oneways(self):
return self.accessible.difference(self.oneways_buffered)
2016-11-29 00:47:37 +01:00
@cached_property
def buildings_with_holes(self):
return self.buildings.difference(self.holes)
2016-11-28 20:56:52 +01:00
2016-12-04 21:45:17 +01:00
@cached_property
def outsides_with_holes(self):
return self.outsides.difference(self.holes)
@cached_property
def areas_and_doors(self):
2016-11-28 20:56:52 +01:00
return cascaded_union([self.areas, self.raw_doors])
@cached_property
def walls(self):
2016-11-29 00:47:37 +01:00
return self.buildings.difference(self.areas_and_doors)
@cached_property
def walls_shadow(self):
2016-11-29 00:47:37 +01:00
return self.walls.buffer(0.2, join_style=JOIN_STYLE.mitre).intersection(self.buildings_with_holes)
@cached_property
def doors(self):
2016-11-28 20:56:52 +01:00
return self.raw_doors.difference(self.areas)
def get_levelconnectors(self, to_level=None):
queryset = self.query('levelconnectors').prefetch_related('levels')
if to_level is not None:
queryset = queryset.filter(levels=to_level)
return cascaded_union([levelconnector.geometry for levelconnector in queryset])
@cached_property
def levelconnectors(self):
return cascaded_union([levelconnector.geometry for levelconnector in self.query('levelconnectors')])
2016-12-04 18:26:30 +01:00
@cached_property
def intermediate_shadows(self):
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.level.altitude)
2016-12-04 18:26:30 +01:00
connectors = cascaded_union([levelconnector.geometry for levelconnector in qs])
2016-12-04 20:44:42 +01:00
shadows = self.buildings.difference(connectors.buffer(0.4, join_style=JOIN_STYLE.mitre))
shadows = shadows.buffer(0.3)
return shadows
2016-12-04 18:14:36 +01:00
2016-12-04 18:26:30 +01:00
@cached_property
2016-12-04 18:14:36 +01:00
def hole_shadows(self):
holes = self.holes.buffer(0.1, join_style=JOIN_STYLE.mitre)
shadows = holes.difference(self.holes.buffer(-0.3, join_style=JOIN_STYLE.mitre))
2016-12-04 18:26:30 +01:00
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.level.altitude)
2016-12-04 18:14:36 +01:00
connectors = cascaded_union([levelconnector.geometry for levelconnector in qs])
2016-12-04 18:26:30 +01:00
shadows = shadows.difference(connectors.buffer(1.0, join_style=JOIN_STYLE.mitre))
2016-12-04 18:14:36 +01:00
return shadows
2016-12-08 12:36:09 +01:00
@cached_property
def stairs(self):
return cascaded_union([stair.geometry for stair in self.query('stairs')]).intersection(self.accessible)
2016-12-08 12:36:09 +01:00
@cached_property
def escalatorslopes(self):
return cascaded_union([s.geometry for s in self.query('escalatorslopes')]).intersection(self.accessible)
2016-12-18 14:36:06 +01:00
@cached_property
def oneways_raw(self):
return cascaded_union([oneway.geometry for oneway in self.query('oneways')])
@cached_property
def oneways(self):
return self.oneways_raw.intersection(self.accessible)
@cached_property
def oneways_buffered(self):
return self.oneways_raw.buffer(0.05, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.square)
2016-12-08 12:36:09 +01:00
@cached_property
def stair_areas(self):
left = []
2016-12-08 12:36:09 +01:00
for stair in assert_multilinestring(self.stairs):
left.append(stair.parallel_offset(0.15, 'right', join_style=JOIN_STYLE.mitre))
return cascaded_union(left).buffer(0.20, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)