replace Space.level with secondary sections on top of primary sections
This commit is contained in:
parent
9502e247d1
commit
9910b3ed83
7 changed files with 122 additions and 47 deletions
|
@ -16,17 +16,10 @@ class EditorViewSet(ViewSet):
|
||||||
/geometries/ returns a list of geojson features, you have to specify ?section=<id> or ?space=<id>
|
/geometries/ returns a list of geojson features, you have to specify ?section=<id> or ?space=<id>
|
||||||
/geometrystyles/ returns styling information for all geometry types
|
/geometrystyles/ returns styling information for all geometry types
|
||||||
"""
|
"""
|
||||||
@list_route(methods=['get'])
|
def _get_section_geometries(self, section: Section):
|
||||||
def geometries(self, request, *args, **kwargs):
|
|
||||||
section = request.GET.get('section')
|
|
||||||
space = request.GET.get('space')
|
|
||||||
if section is not None:
|
|
||||||
if space is not None:
|
|
||||||
raise ValidationError('Only section or space can be specified.')
|
|
||||||
section = get_object_or_404(Section, pk=section)
|
|
||||||
buildings = section.buildings.all()
|
buildings = section.buildings.all()
|
||||||
buildings_geom = cascaded_union([building.geometry for building in buildings])
|
buildings_geom = cascaded_union([building.geometry for building in buildings])
|
||||||
spaces = {space.id: space for space in section.spaces.all().prefetch_related('groups', 'holes', 'columns')}
|
spaces = {space.id: space for space in section.spaces.all()}
|
||||||
holes_geom = []
|
holes_geom = []
|
||||||
for space in spaces.values():
|
for space in spaces.values():
|
||||||
if space.outside:
|
if space.outside:
|
||||||
|
@ -42,27 +35,59 @@ class EditorViewSet(ViewSet):
|
||||||
|
|
||||||
for building in buildings:
|
for building in buildings:
|
||||||
building.original_geometry = building.geometry
|
building.original_geometry = building.geometry
|
||||||
for obj in chain(buildings, (s for s in spaces.values() if s.level == 'normal')):
|
for obj in chain(buildings, spaces.values()):
|
||||||
obj.geometry = obj.geometry.difference(holes_geom)
|
obj.geometry = obj.geometry.difference(holes_geom)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
def add_spaces(level):
|
|
||||||
results.extend(space for space in spaces.values() if space.level == level)
|
|
||||||
areas = Area.objects.filter(space__section=section, space__level=level).prefetch_related('groups')
|
|
||||||
areas = [area for area in areas if area.get_color()]
|
|
||||||
for area in areas:
|
|
||||||
area.geometry = area.geometry.intersection(spaces[area.space_id].geometry)
|
|
||||||
results.extend((area for area in areas if not area.geometry.is_empty))
|
|
||||||
|
|
||||||
add_spaces('lower')
|
|
||||||
|
|
||||||
results.extend(buildings)
|
results.extend(buildings)
|
||||||
for door in section.doors.all():
|
for door in section.doors.all():
|
||||||
results.append(door)
|
results.append(door)
|
||||||
|
|
||||||
add_spaces('normal')
|
results.extend(spaces.values())
|
||||||
add_spaces('upper')
|
areas = Area.objects.filter(space__in=spaces.values()).prefetch_related('groups')
|
||||||
|
areas = [area for area in areas if area.get_color()]
|
||||||
|
for area in areas:
|
||||||
|
area.geometry = area.geometry.intersection(spaces[area.space_id].geometry)
|
||||||
|
results.extend((area for area in areas if not area.geometry.is_empty))
|
||||||
|
return results
|
||||||
|
|
||||||
|
@list_route(methods=['get'])
|
||||||
|
def geometries(self, request, *args, **kwargs):
|
||||||
|
section = request.GET.get('section')
|
||||||
|
space = request.GET.get('space')
|
||||||
|
if section is not None:
|
||||||
|
if space is not None:
|
||||||
|
raise ValidationError('Only section or space can be specified.')
|
||||||
|
section = get_object_or_404(Section, pk=section)
|
||||||
|
|
||||||
|
sections_under = ()
|
||||||
|
sections_on_top = ()
|
||||||
|
|
||||||
|
lower_section = section.lower().first()
|
||||||
|
primary_sections = (section, ) + ((lower_section, ) if lower_section else ())
|
||||||
|
secondary_sections = Section.objects.filter(on_top_of__in=primary_sections).values_list('pk', 'on_top_of')
|
||||||
|
if lower_section:
|
||||||
|
sections_under = tuple(pk for pk, on_top_of in secondary_sections if on_top_of == lower_section.pk)
|
||||||
|
|
||||||
|
if True:
|
||||||
|
sections_on_top = tuple(pk for pk, on_top_of in secondary_sections if on_top_of == section.pk)
|
||||||
|
|
||||||
|
sections = chain([section.pk], sections_under, sections_on_top)
|
||||||
|
sections = Section.objects.filter(pk__in=sections).prefetch_related('buildings', 'spaces', 'doors',
|
||||||
|
'spaces__groups', 'spaces__holes',
|
||||||
|
'spaces__columns')
|
||||||
|
sections = {s.pk: s for s in sections}
|
||||||
|
|
||||||
|
section = sections[section.pk]
|
||||||
|
sections_under = [sections[pk] for pk in sections_under]
|
||||||
|
sections_on_top = [sections[pk] for pk in sections_on_top]
|
||||||
|
|
||||||
|
results = chain(
|
||||||
|
*(self._get_section_geometries(s) for s in sections_under),
|
||||||
|
self._get_section_geometries(section),
|
||||||
|
*(self._get_section_geometries(s) for s in sections_on_top)
|
||||||
|
)
|
||||||
|
|
||||||
return Response([obj.to_geojson() for obj in results])
|
return Response([obj.to_geojson() for obj in results])
|
||||||
elif space is not None:
|
elif space is not None:
|
||||||
space = get_object_or_404(Space.objects.select_related('section'), pk=space)
|
space = get_object_or_404(Space.objects.select_related('section'), pk=space)
|
||||||
|
|
|
@ -89,7 +89,7 @@ class MapitemFormMixin(ModelForm):
|
||||||
|
|
||||||
|
|
||||||
def create_editor_form(editor_model):
|
def create_editor_form(editor_model):
|
||||||
possible_fields = ['slug', 'name', 'altitude', 'level', 'category', 'width', 'groups', 'color', 'public',
|
possible_fields = ['slug', 'name', 'altitude', 'category', 'width', 'groups', 'color', 'public',
|
||||||
'can_search', 'can_describe', 'outside', 'stuffed', 'geometry',
|
'can_search', 'can_describe', 'outside', 'stuffed', 'geometry',
|
||||||
'left', 'top', 'right', 'bottom']
|
'left', 'top', 'right', 'bottom']
|
||||||
field_names = [field.name for field in editor_model._meta.get_fields()]
|
field_names = [field.name for field in editor_model._meta.get_fields()]
|
||||||
|
|
|
@ -39,7 +39,7 @@ def child_model(model_name, kwargs=None, parent=None):
|
||||||
@sidebar_view
|
@sidebar_view
|
||||||
def main_index(request):
|
def main_index(request):
|
||||||
return render(request, 'editor/index.html', {
|
return render(request, 'editor/index.html', {
|
||||||
'sections': Section.objects.all(),
|
'sections': Section.objects.filter(on_top_of__isnull=True),
|
||||||
'child_models': [
|
'child_models': [
|
||||||
child_model('LocationGroup'),
|
child_model('LocationGroup'),
|
||||||
child_model('Source'),
|
child_model('Source'),
|
||||||
|
@ -52,7 +52,7 @@ def section_detail(request, pk):
|
||||||
section = get_object_or_404(Section, pk=pk)
|
section = get_object_or_404(Section, pk=pk)
|
||||||
|
|
||||||
return render(request, 'editor/section.html', {
|
return render(request, 'editor/section.html', {
|
||||||
'sections': Section.objects.all(),
|
'sections': Section.objects.filter(on_top_of__isnull=True),
|
||||||
'section': section,
|
'section': section,
|
||||||
'section_url': 'editor.sections.detail',
|
'section_url': 'editor.sections.detail',
|
||||||
'section_as_pk': True,
|
'section_as_pk': True,
|
||||||
|
|
|
@ -38,6 +38,17 @@ class MapdataViewSet(ReadOnlyModelViewSet):
|
||||||
except Space.DoesNotExist:
|
except Space.DoesNotExist:
|
||||||
raise NotFound(detail=_('space not found.'))
|
raise NotFound(detail=_('space not found.'))
|
||||||
qs = qs.filter(space=space)
|
qs = qs.filter(space=space)
|
||||||
|
if qs.model == Section and 'on_top_of' in request.GET:
|
||||||
|
if request.GET['on_top_of'] == 'null':
|
||||||
|
qs = qs.filter(on_top_of__isnull=False)
|
||||||
|
else:
|
||||||
|
if not request.GET['on_top_of'].isdigit():
|
||||||
|
raise ValidationError(detail={'detail': _('%s is not null or an integer.') % 'on_top_of'})
|
||||||
|
try:
|
||||||
|
section = Section.objects.get(pk=request.GET['on_top_of'])
|
||||||
|
except Section.DoesNotExist:
|
||||||
|
raise NotFound(detail=_('section not found.'))
|
||||||
|
qs = qs.filter(on_top_of=section)
|
||||||
return Response([obj.serialize(geometry=geometry) for obj in qs.order_by('id')])
|
return Response([obj.serialize(geometry=geometry) for obj in qs.order_by('id')])
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
@ -51,6 +62,7 @@ class MapdataViewSet(ReadOnlyModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
class SectionViewSet(MapdataViewSet):
|
class SectionViewSet(MapdataViewSet):
|
||||||
|
""" Add ?on_top_of=null or ?on_top_of=<id> to filter by on_top_of. """
|
||||||
queryset = Section.objects.all()
|
queryset = Section.objects.all()
|
||||||
|
|
||||||
@list_route(methods=['get'])
|
@list_route(methods=['get'])
|
||||||
|
|
40
src/c3nav/mapdata/migrations/0010_on_top_of.py
Normal file
40
src/c3nav/mapdata/migrations/0010_on_top_of.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-06-10 11:15
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def move_upper_spaces_to_section_on_top(apps, schema_editor):
|
||||||
|
Section = apps.get_model('mapdata', 'Section')
|
||||||
|
Space = apps.get_model('mapdata', 'Space')
|
||||||
|
Space.objects.filter(level='lower').delete()
|
||||||
|
for section in Section.objects.all():
|
||||||
|
if Space.objects.filter(level='upper', section=section).exists():
|
||||||
|
section_on_top_of = section.sections_on_top.create(altitude=section.altitude+Decimal('0.01'), public=section.public,
|
||||||
|
can_search=False, can_describe=False)
|
||||||
|
Space.objects.filter(level='upper', section=section).update(section=section_on_top_of, outside=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mapdata', '0009_column'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='section',
|
||||||
|
name='on_top_of',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='sections_on_top', to='mapdata.Section', verbose_name='on top of'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(move_upper_spaces_to_section_on_top),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='space',
|
||||||
|
name='level',
|
||||||
|
),
|
||||||
|
]
|
|
@ -16,7 +16,6 @@ class SectionGeometryMixin(GeometryMixin):
|
||||||
|
|
||||||
def get_geojson_properties(self) -> dict:
|
def get_geojson_properties(self) -> dict:
|
||||||
result = super().get_geojson_properties()
|
result = super().get_geojson_properties()
|
||||||
result['layer'] = getattr(self, 'level', 'base')
|
|
||||||
if hasattr(self, 'get_color'):
|
if hasattr(self, 'get_color'):
|
||||||
color = self.get_color()
|
color = self.get_color()
|
||||||
if color:
|
if color:
|
||||||
|
@ -44,13 +43,8 @@ class Building(SectionGeometryMixin, models.Model):
|
||||||
|
|
||||||
class Space(SpecificLocation, SectionGeometryMixin, models.Model):
|
class Space(SpecificLocation, SectionGeometryMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
An accessible space. Shouldn't overlap with spaces on same secion and level.
|
An accessible space. Shouldn't overlap with spaces on same section.
|
||||||
"""
|
"""
|
||||||
LEVELS = (
|
|
||||||
('normal', _('normal')),
|
|
||||||
('upper', _('upper')),
|
|
||||||
('lower', _('lower')),
|
|
||||||
)
|
|
||||||
CATEGORIES = (
|
CATEGORIES = (
|
||||||
('normal', _('normal')),
|
('normal', _('normal')),
|
||||||
('stairs', _('stairs')),
|
('stairs', _('stairs')),
|
||||||
|
@ -58,7 +52,6 @@ class Space(SpecificLocation, SectionGeometryMixin, models.Model):
|
||||||
('elevator', _('elevator')),
|
('elevator', _('elevator')),
|
||||||
)
|
)
|
||||||
geometry = GeometryField('polygon')
|
geometry = GeometryField('polygon')
|
||||||
level = models.CharField(verbose_name=_('level'), choices=LEVELS, default='normal', max_length=16)
|
|
||||||
category = models.CharField(verbose_name=_('category'), choices=CATEGORIES, default='normal', max_length=16)
|
category = models.CharField(verbose_name=_('category'), choices=CATEGORIES, default='normal', max_length=16)
|
||||||
outside = models.BooleanField(default=False, verbose_name=_('is outside of building'))
|
outside = models.BooleanField(default=False, verbose_name=_('is outside of building'))
|
||||||
|
|
||||||
|
@ -71,7 +64,6 @@ class Space(SpecificLocation, SectionGeometryMixin, models.Model):
|
||||||
result = super()._serialize(**kwargs)
|
result = super()._serialize(**kwargs)
|
||||||
if space:
|
if space:
|
||||||
result['category'] = self.category
|
result['category'] = self.category
|
||||||
result['level'] = self.level
|
|
||||||
result['public'] = self.public
|
result['public'] = self.public
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ class Section(SpecificLocation, EditorFormMixin, models.Model):
|
||||||
A map section like a level
|
A map section like a level
|
||||||
"""
|
"""
|
||||||
altitude = models.DecimalField(_('section altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
|
altitude = models.DecimalField(_('section altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
|
||||||
|
on_top_of = models.ForeignKey('mapdata.Section', null=True, on_delete=models.CASCADE,
|
||||||
|
related_name='sections_on_top', verbose_name=_('on top of'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Section')
|
verbose_name = _('Section')
|
||||||
|
@ -25,10 +27,14 @@ class Section(SpecificLocation, EditorFormMixin, models.Model):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def lower(self):
|
def lower(self):
|
||||||
return Section.objects.filter(altitude__lt=self.altitude).order_by('altitude')
|
if self.on_top_of is not None:
|
||||||
|
raise TypeError
|
||||||
|
return Section.objects.filter(altitude__lt=self.altitude, on_top_of__isnull=True).order_by('-altitude')
|
||||||
|
|
||||||
def higher(self):
|
def higher(self):
|
||||||
return Section.objects.filter(altitude__gt=self.altitude).order_by('altitude')
|
if self.on_top_of is not None:
|
||||||
|
raise TypeError
|
||||||
|
return Section.objects.filter(altitude__gt=self.altitude, on_top_of__isnull=True).order_by('altitude')
|
||||||
|
|
||||||
def _serialize(self, section=True, **kwargs):
|
def _serialize(self, section=True, **kwargs):
|
||||||
result = super()._serialize(**kwargs)
|
result = super()._serialize(**kwargs)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue