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 _
|
2016-12-08 12:56:49 +01:00
|
|
|
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
|
|
|
|
2017-05-08 16:40:22 +02:00
|
|
|
from c3nav.mapdata.models.base import EditorFormMixin
|
2017-05-10 18:03:57 +02:00
|
|
|
from c3nav.mapdata.models.locations import SpecificLocation
|
2016-12-19 18:06:50 +01:00
|
|
|
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2016-10-12 15:25:00 +02:00
|
|
|
|
2017-05-10 18:03:57 +02:00
|
|
|
class Section(SpecificLocation, EditorFormMixin, models.Model):
|
2016-08-29 18:49:24 +02:00
|
|
|
"""
|
2017-05-07 12:06:13 +02:00
|
|
|
A map section like a level
|
2016-08-29 18:49:24 +02:00
|
|
|
"""
|
2017-05-07 12:06:13 +02:00
|
|
|
name = models.SlugField(_('section name'), unique=True, max_length=50)
|
|
|
|
altitude = models.DecimalField(_('section altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2016-10-12 15:25:00 +02:00
|
|
|
class Meta:
|
2017-05-07 12:06:13 +02:00
|
|
|
verbose_name = _('Section')
|
|
|
|
verbose_name_plural = _('Sections')
|
|
|
|
default_related_name = 'sections'
|
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
|
|
|
|
2016-12-13 19:16:51 +01:00
|
|
|
@cached_property
|
|
|
|
def public_geometries(self):
|
2017-05-07 12:06:13 +02:00
|
|
|
return SectionGeometries.by_section(self, only_public=True)
|
2016-12-13 19:16:51 +01:00
|
|
|
|
2016-12-04 01:49:49 +01:00
|
|
|
@cached_property
|
|
|
|
def geometries(self):
|
2017-05-07 12:06:13 +02:00
|
|
|
return SectionGeometries.by_section(self, only_public=False)
|
2016-11-26 13:48:44 +01:00
|
|
|
|
2016-12-04 01:49:49 +01:00
|
|
|
def lower(self):
|
2017-05-07 12:06:13 +02:00
|
|
|
return Section.objects.filter(altitude__lt=self.altitude).order_by('altitude')
|
2016-12-04 01:49:49 +01:00
|
|
|
|
|
|
|
def higher(self):
|
2017-05-07 12:06:13 +02:00
|
|
|
return Section.objects.filter(altitude__gt=self.altitude).order_by('altitude')
|
2016-12-04 01:49:49 +01:00
|
|
|
|
2017-05-11 19:36:49 +02:00
|
|
|
def _serialize(self, section=True, **kwargs):
|
|
|
|
result = super()._serialize(**kwargs)
|
|
|
|
result['name'] = self.name
|
|
|
|
result['altitude'] = float(str(self.altitude))
|
|
|
|
return result
|
2016-12-01 12:25:02 +01:00
|
|
|
|
2016-11-26 13:48:44 +01:00
|
|
|
|
2017-05-07 12:06:13 +02:00
|
|
|
class SectionGeometries():
|
|
|
|
by_section_id = {}
|
2016-12-04 01:49:49 +01:00
|
|
|
|
|
|
|
@classmethod
|
2017-05-07 12:06:13 +02:00
|
|
|
def by_section(cls, section, only_public=True):
|
|
|
|
return cls.by_section_id.setdefault((section.id, only_public), cls(section, only_public=only_public))
|
2016-12-04 01:49:49 +01:00
|
|
|
|
2017-05-07 12:06:13 +02:00
|
|
|
def __init__(self, section, only_public=True):
|
|
|
|
self.section = section
|
2016-12-13 19:16:51 +01:00
|
|
|
self.only_public = only_public
|
|
|
|
|
|
|
|
def query(self, name):
|
2017-05-07 12:06:13 +02:00
|
|
|
queryset = getattr(self.section, name)
|
2016-12-13 19:16:51 +01:00
|
|
|
if not self.only_public:
|
|
|
|
return queryset.all()
|
2017-05-01 18:10:46 +02:00
|
|
|
return queryset.filter(public=True)
|
2016-11-26 13:48:44 +01:00
|
|
|
|
2016-12-04 01:49:49 +01:00
|
|
|
@cached_property
|
|
|
|
def raw_rooms(self):
|
2016-12-13 19:16:51 +01:00
|
|
|
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):
|
2016-12-13 19:16:51 +01:00
|
|
|
result = cascaded_union([building.geometry for building in self.query('buildings')])
|
2017-05-07 12:06:13 +02:00
|
|
|
if self.section.intermediate:
|
2016-12-04 01:49:49 +01:00
|
|
|
result = cascaded_union([result, self.raw_rooms])
|
|
|
|
return result
|
2016-11-26 13:48:44 +01:00
|
|
|
|
|
|
|
@cached_property
|
2016-11-28 19:15:36 +01:00
|
|
|
def rooms(self):
|
2016-12-04 01:49:49 +01:00
|
|
|
return self.raw_rooms.intersection(self.buildings)
|
2016-11-28 19:15:36 +01:00
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def outsides(self):
|
2016-12-13 19:16:51 +01:00
|
|
|
return cascaded_union([outside.geometry for outside in self.query('outsides')]).difference(self.buildings)
|
2016-11-26 18:19:55 +01:00
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def mapped(self):
|
2016-11-28 19:15:36 +01:00
|
|
|
return cascaded_union([self.buildings, self.outsides])
|
2016-11-26 18:19:55 +01:00
|
|
|
|
2016-12-08 22:23:42 +01:00
|
|
|
@cached_property
|
|
|
|
def lineobstacles(self):
|
|
|
|
lineobstacles = []
|
2016-12-13 19:16:51 +01:00
|
|
|
for obstacle in self.query('lineobstacles'):
|
2016-12-08 22:23:42 +01:00
|
|
|
lineobstacles.append(obstacle.geometry.buffer(obstacle.width/2,
|
|
|
|
join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat))
|
|
|
|
return cascaded_union(lineobstacles)
|
|
|
|
|
2016-11-26 18:19:55 +01:00
|
|
|
@cached_property
|
2016-12-09 02:50:21 +01:00
|
|
|
def uncropped_obstacles(self):
|
2016-12-13 19:16:51 +01:00
|
|
|
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):
|
2016-12-04 11:58:46 +01:00
|
|
|
levels_by_name = {}
|
|
|
|
obstacles_by_crop_to_level = {}
|
2016-12-13 19:16:51 +01:00
|
|
|
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
|
2016-12-04 11:58:46 +01:00
|
|
|
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)
|
2016-12-04 11:58:46 +01:00
|
|
|
all_obstacles.append(obstacles)
|
2016-12-19 18:06:50 +01:00
|
|
|
all_obstacles.extend(assert_multipolygon(self.lineobstacles))
|
2016-12-04 11:58:46 +01:00
|
|
|
|
|
|
|
return cascaded_union(all_obstacles).intersection(self.mapped)
|
2016-11-26 18:19:55 +01:00
|
|
|
|
2016-12-09 02:50:21 +01:00
|
|
|
@cached_property
|
|
|
|
def obstacles(self):
|
|
|
|
return cascaded_union([self.uncropped_obstacles, self.cropped_obstacles])
|
|
|
|
|
2016-11-26 18:19:55 +01:00
|
|
|
@cached_property
|
|
|
|
def raw_doors(self):
|
2016-12-13 19:16:51 +01:00
|
|
|
return cascaded_union([door.geometry for door in self.query('doors').all()]).intersection(self.mapped)
|
2016-11-26 18:19:55 +01:00
|
|
|
|
2016-12-18 02:28:08 +01:00
|
|
|
@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):
|
2016-12-13 19:16:51 +01:00
|
|
|
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):
|
2016-12-13 19:16:51 +01:00
|
|
|
return cascaded_union([holes.geometry for holes in self.query('holes').all()]).intersection(self.areas)
|
2016-11-29 00:47:37 +01:00
|
|
|
|
2016-12-02 23:32:19 +01:00
|
|
|
@cached_property
|
|
|
|
def accessible(self):
|
2016-12-22 19:43:01 +01:00
|
|
|
return self.areas.difference(cascaded_union([self.holes, self.obstacles]))
|
2016-12-02 23:32:19 +01:00
|
|
|
|
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)
|
|
|
|
|
2016-11-26 18:19:55 +01:00
|
|
|
@cached_property
|
|
|
|
def areas_and_doors(self):
|
2016-11-28 20:56:52 +01:00
|
|
|
return cascaded_union([self.areas, self.raw_doors])
|
2016-11-26 18:19:55 +01:00
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def walls(self):
|
2016-11-29 00:47:37 +01:00
|
|
|
return self.buildings.difference(self.areas_and_doors)
|
2016-11-26 18:19:55 +01:00
|
|
|
|
|
|
|
@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)
|
2016-11-26 18:19:55 +01:00
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def doors(self):
|
2016-11-28 20:56:52 +01:00
|
|
|
return self.raw_doors.difference(self.areas)
|
2016-12-04 02:33:50 +01:00
|
|
|
|
|
|
|
def get_levelconnectors(self, to_level=None):
|
2016-12-13 19:16:51 +01:00
|
|
|
queryset = self.query('levelconnectors').prefetch_related('levels')
|
2016-12-04 02:33:50 +01:00
|
|
|
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):
|
2016-12-13 19:16:51 +01:00
|
|
|
return cascaded_union([levelconnector.geometry for levelconnector in self.query('levelconnectors')])
|
2016-12-04 02:33:50 +01:00
|
|
|
|
2016-12-04 18:26:30 +01:00
|
|
|
@cached_property
|
|
|
|
def intermediate_shadows(self):
|
2017-05-07 12:06:13 +02:00
|
|
|
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.section.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)
|
2016-12-04 17:55:55 +01:00
|
|
|
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
|
|
|
|
2017-05-07 12:06:13 +02:00
|
|
|
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.section.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
|
|
|
|
2016-12-04 21:58:53 +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):
|
2016-12-13 19:16:51 +01:00
|
|
|
return cascaded_union([stair.geometry for stair in self.query('stairs')]).intersection(self.accessible)
|
2016-12-08 12:36:09 +01:00
|
|
|
|
|
|
|
@cached_property
|
2016-12-08 12:56:49 +01:00
|
|
|
def stair_areas(self):
|
|
|
|
left = []
|
2016-12-08 12:36:09 +01:00
|
|
|
for stair in assert_multilinestring(self.stairs):
|
2016-12-08 18:21:12 +01:00
|
|
|
left.append(stair.parallel_offset(0.15, 'right', join_style=JOIN_STYLE.mitre))
|
2016-12-08 12:56:49 +01:00
|
|
|
return cascaded_union(left).buffer(0.20, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
2016-12-27 00:52:22 +01:00
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def stuffedareas(self):
|
|
|
|
return cascaded_union([stuffedarea.geometry for stuffedarea in self.query('stuffedareas')])
|