2017-11-25 14:36:18 +01:00
|
|
|
from decimal import Decimal
|
2017-06-11 00:00:25 +02:00
|
|
|
from itertools import chain
|
2017-11-09 17:32:39 +01:00
|
|
|
from operator import attrgetter
|
2017-06-11 00:00:25 +02:00
|
|
|
|
2017-11-25 14:36:18 +01:00
|
|
|
from django.core.validators import MinValueValidator
|
2016-08-29 18:49:24 +02:00
|
|
|
from django.db import models
|
2017-11-02 13:35:58 +01:00
|
|
|
from django.urls import reverse
|
2017-08-07 14:55:08 +02: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-11-26 13:48:44 +01:00
|
|
|
from shapely.ops import cascaded_union
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2017-05-10 18:03:57 +02:00
|
|
|
from c3nav.mapdata.models.locations import SpecificLocation
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2016-10-12 15:25:00 +02:00
|
|
|
|
2017-06-21 12:47:28 +02:00
|
|
|
class Level(SpecificLocation, models.Model):
|
2016-08-29 18:49:24 +02:00
|
|
|
"""
|
2017-06-11 14:43:14 +02:00
|
|
|
A map level
|
2016-08-29 18:49:24 +02:00
|
|
|
"""
|
2017-08-05 16:49:10 +02:00
|
|
|
base_altitude = models.DecimalField(_('base altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
|
2017-11-25 14:36:18 +01:00
|
|
|
default_height = models.DecimalField(_('default space height'), max_digits=6, decimal_places=2, default=3.0,
|
|
|
|
validators=[MinValueValidator(Decimal('0'))])
|
|
|
|
door_height = models.DecimalField(_('door height'), max_digits=6, decimal_places=2, default=2.0,
|
|
|
|
validators=[MinValueValidator(Decimal('0'))])
|
2017-06-11 14:43:14 +02:00
|
|
|
on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE,
|
|
|
|
related_name='levels_on_top', verbose_name=_('on top of'))
|
2017-10-31 16:08:20 +01:00
|
|
|
short_label = models.SlugField(max_length=20, verbose_name=_('short label'), unique=True)
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2016-10-12 15:25:00 +02:00
|
|
|
class Meta:
|
2017-06-11 14:43:14 +02:00
|
|
|
verbose_name = _('Level')
|
|
|
|
verbose_name_plural = _('Levels')
|
|
|
|
default_related_name = 'levels'
|
2017-08-05 16:49:10 +02:00
|
|
|
ordering = ['base_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
|
|
|
|
2017-06-12 23:33:59 +02:00
|
|
|
def lower(self, level_model=None):
|
2017-06-11 15:37:25 +02:00
|
|
|
if self.on_top_of_id is not None:
|
2017-06-10 14:58:13 +02:00
|
|
|
raise TypeError
|
2017-06-16 18:38:41 +02:00
|
|
|
if level_model is None:
|
|
|
|
level_model = Level
|
2017-08-05 16:49:10 +02:00
|
|
|
return level_model.objects.filter(base_altitude__lt=self.base_altitude,
|
|
|
|
on_top_of__isnull=True).order_by('-base_altitude')
|
2016-12-04 01:49:49 +01:00
|
|
|
|
2017-06-12 23:33:59 +02:00
|
|
|
def higher(self, level_model=None):
|
2017-06-11 15:37:25 +02:00
|
|
|
if self.on_top_of_id is not None:
|
2017-06-10 14:58:13 +02:00
|
|
|
raise TypeError
|
2017-06-16 18:38:41 +02:00
|
|
|
if level_model is None:
|
|
|
|
level_model = Level
|
2017-08-05 16:49:10 +02:00
|
|
|
return level_model.objects.filter(base_altitude__gt=self.base_altitude,
|
|
|
|
on_top_of__isnull=True).order_by('base_altitude')
|
2016-12-04 01:49:49 +01:00
|
|
|
|
2017-06-11 00:00:25 +02:00
|
|
|
@property
|
2017-06-11 14:43:14 +02:00
|
|
|
def sublevels(self):
|
2017-06-11 00:00:25 +02:00
|
|
|
if self.on_top_of is not None:
|
|
|
|
raise TypeError
|
2017-06-11 14:43:14 +02:00
|
|
|
return chain((self, ), self.levels_on_top.all())
|
2017-06-11 00:00:25 +02:00
|
|
|
|
|
|
|
@property
|
2017-06-11 14:43:14 +02:00
|
|
|
def sublevel_title(self):
|
2017-06-11 00:00:25 +02:00
|
|
|
return '-' if self.on_top_of_id is None else self.title
|
|
|
|
|
|
|
|
@property
|
2017-06-11 14:43:14 +02:00
|
|
|
def primary_level(self):
|
2017-06-11 00:00:25 +02:00
|
|
|
return self if self.on_top_of_id is None else self.on_top_of
|
|
|
|
|
2017-06-10 23:04:28 +02:00
|
|
|
@property
|
2017-06-11 14:43:14 +02:00
|
|
|
def primary_level_pk(self):
|
2017-06-10 23:04:28 +02:00
|
|
|
return self.pk if self.on_top_of_id is None else self.on_top_of_id
|
|
|
|
|
2017-06-11 14:43:14 +02:00
|
|
|
def _serialize(self, level=True, **kwargs):
|
2017-05-11 19:36:49 +02:00
|
|
|
result = super()._serialize(**kwargs)
|
2017-10-25 12:29:08 +02:00
|
|
|
result['short_label'] = self.short_label
|
2017-08-07 15:30:50 +02:00
|
|
|
result['on_top_of'] = self.on_top_of_id
|
2017-08-05 16:49:10 +02:00
|
|
|
result['base_altitude'] = float(str(self.base_altitude))
|
2017-08-21 16:05:31 +02:00
|
|
|
result['default_height'] = float(str(self.default_height))
|
2017-11-14 22:24:29 +01:00
|
|
|
result['door_height'] = float(str(self.door_height))
|
2017-05-11 19:36:49 +02:00
|
|
|
return result
|
2016-12-01 12:25:02 +01:00
|
|
|
|
2018-09-19 19:08:47 +02:00
|
|
|
def details_display(self, editor_url=True, **kwargs):
|
|
|
|
result = super().details_display(**kwargs)
|
2017-11-30 01:10:49 +01:00
|
|
|
result['display'].insert(3, (_('short label'), self.short_label))
|
2017-11-02 13:35:58 +01:00
|
|
|
result['display'].extend([
|
2017-11-30 01:10:49 +01:00
|
|
|
(_('outside only'), self.base_altitude),
|
|
|
|
(_('default height'), self.default_height),
|
2017-11-02 13:35:58 +01:00
|
|
|
])
|
2018-09-19 19:08:47 +02:00
|
|
|
if editor_url:
|
|
|
|
result['editor_url'] = reverse('editor.levels.detail', kwargs={'pk': self.pk})
|
2017-11-02 13:35:58 +01:00
|
|
|
return result
|
|
|
|
|
2017-08-07 14:55:08 +02:00
|
|
|
@cached_property
|
|
|
|
def min_altitude(self):
|
|
|
|
return min(self.altitudeareas.all(), key=attrgetter('altitude'), default=self.base_altitude).altitude
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def bounds(self):
|
|
|
|
return cascaded_union(tuple(item.geometry.buffer(0)
|
|
|
|
for item in chain(self.altitudeareas.all(), self.buildings.all()))).bounds
|