rename Level to Section

This commit is contained in:
Laura Klünder 2017-05-07 12:06:13 +02:00
parent d1fe5bce5c
commit c9661e4edb
22 changed files with 217 additions and 157 deletions

View file

@ -7,10 +7,10 @@ from rest_framework.generics import GenericAPIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.routers import SimpleRouter from rest_framework.routers import SimpleRouter
from c3nav.mapdata.api import GeometryTypeViewSet, GeometryViewSet, LevelViewSet, LocationViewSet, SourceViewSet from c3nav.mapdata.api import GeometryTypeViewSet, GeometryViewSet, LocationViewSet, SectionViewSet, SourceViewSet
router = SimpleRouter() router = SimpleRouter()
router.register(r'levels', LevelViewSet) router.register(r'sections', SectionViewSet)
router.register(r'sources', SourceViewSet) router.register(r'sources', SourceViewSet)
router.register(r'geometrytypes', GeometryTypeViewSet, base_name='geometrytype') router.register(r'geometrytypes', GeometryTypeViewSet, base_name='geometrytype')

View file

@ -22,22 +22,9 @@ class MapitemFormMixin(ModelForm):
if creating: if creating:
self.fields['name'].initial = hex(int(time.time()*1000000))[2:] self.fields['name'].initial = hex(int(time.time()*1000000))[2:]
if 'level' in self.fields: if 'section' in self.fields:
# hide level widget and set field_name # hide section widget
self.fields['level'].widget = HiddenInput() self.fields['section'].widget = HiddenInput()
self.fields['level'].to_field_name = 'name'
if not creating:
self.initial['level'] = self.instance.level.name
if 'crop_to_level' in self.fields:
# set field_name
self.fields['crop_to_level'].to_field_name = 'name'
if not creating and self.instance.crop_to_level is not None:
self.initial['crop_to_level'] = self.instance.crop_to_level.name
if 'levels' in self.fields:
# set field_name
self.fields['levels'].to_field_name = 'name'
if 'groups' in self.fields: if 'groups' in self.fields:
# set field_name # set field_name
@ -66,12 +53,6 @@ class MapitemFormMixin(ModelForm):
initial=titles[language].strip(), max_length=50) initial=titles[language].strip(), max_length=50)
self.titles = titles self.titles = titles
def clean_levels(self):
levels = self.cleaned_data.get('levels')
if len(levels) < 2:
raise ValidationError(_('Please select at least two levels.'))
return levels
def clean(self): def clean(self):
if 'geometry' in self.fields: if 'geometry' in self.fields:
if not self.cleaned_data.get('geometry'): if not self.cleaned_data.get('geometry'):

View file

@ -7,7 +7,7 @@ urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='editor/map.html'), name='editor.index'), url(r'^$', TemplateView.as_view(template_name='editor/map.html'), name='editor.index'),
url(r'^mapitemtypes/(?P<level>[^/]+)/$', list_mapitemtypes, name='editor.mapitemtypes'), url(r'^mapitemtypes/(?P<level>[^/]+)/$', list_mapitemtypes, name='editor.mapitemtypes'),
url(r'^mapitems/(?P<mapitem_type>[^/]+)/list/$', list_mapitems, name='editor.mapitems'), url(r'^mapitems/(?P<mapitem_type>[^/]+)/list/$', list_mapitems, name='editor.mapitems'),
url(r'^mapitems/(?P<mapitem_type>[^/]+)/list/(?P<level>[^/]+)/$', list_mapitems, name='editor.mapitems.level'), url(r'^mapitems/(?P<mapitem_type>[^/]+)/list/(?P<sectionl>[0-9]+)/$', list_mapitems, name='editor.mapitems.level'),
url(r'^mapitems/(?P<mapitem_type>[^/]+)/add/$', edit_mapitem, name='editor.mapitems.add'), url(r'^mapitems/(?P<mapitem_type>[^/]+)/add/$', edit_mapitem, name='editor.mapitems.add'),
url(r'^mapitems/(?P<mapitem_type>[^/]+)/edit/(?P<id>[^/]+)/$', edit_mapitem, name='editor.mapitems.edit'), url(r'^mapitems/(?P<mapitem_type>[^/]+)/edit/(?P<id>[^/]+)/$', edit_mapitem, name='editor.mapitems.edit'),
] ]

View file

@ -4,54 +4,49 @@ from django.http.response import Http404
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from c3nav.access.apply import can_access, filter_queryset_by_access from c3nav.access.apply import can_access, filter_queryset_by_access
from c3nav.mapdata.models import AreaLocation, Level from c3nav.mapdata.models import AreaLocation, Section
from c3nav.mapdata.models.base import FEATURE_TYPES from c3nav.mapdata.models.base import FEATURE_TYPES
def list_mapitemtypes(request, level): def list_mapitemtypes(request, section):
level = get_object_or_404() section = get_object_or_404(Section, pk=section)
def get_item_count(mapitemtype): def get_item_count(mapitemtype):
if hasattr(mapitemtype, 'level'): if hasattr(mapitemtype, 'section'):
return filter_queryset_by_access(request, mapitemtype.objects.filter(level__name=level)).count() return filter_queryset_by_access(request, mapitemtype.objects.filter(section=section)).count()
if hasattr(mapitemtype, 'levels'):
return filter_queryset_by_access(request, mapitemtype.objects.filter(levels__name=level)).count()
return 0 return 0
return render(request, 'editor/mapitemtypes.html', { return render(request, 'editor/mapitemtypes.html', {
'level': level, 'section': section,
'mapitemtypes': [ 'mapitemtypes': [
{ {
'name': name, 'name': name,
'title': mapitemtype._meta.verbose_name_plural, 'title': mapitemtype._meta.verbose_name_plural,
'has_level': hasattr(mapitemtype, 'level') or hasattr(mapitemtype, 'levels'), 'has_section': hasattr(mapitemtype, 'section'),
'count': get_item_count(mapitemtype), 'count': get_item_count(mapitemtype),
} for name, mapitemtype in FEATURE_TYPES.items() } for name, mapitemtype in FEATURE_TYPES.items()
], ],
}) })
def list_mapitems(request, mapitem_type, level=None): def list_mapitems(request, mapitem_type, section=None):
mapitemtype = FEATURE_TYPES.get(mapitem_type) mapitemtype = FEATURE_TYPES.get(mapitem_type)
if mapitemtype is None: if mapitemtype is None:
raise Http404('Unknown mapitemtype.') raise Http404('Unknown mapitemtype.')
has_level = hasattr(mapitemtype, 'level') or hasattr(mapitemtype, 'levels') has_section = hasattr(mapitemtype, 'section')
if has_level and level is None: if has_section and section is None:
raise Http404('Missing level.') raise Http404('Missing section.')
elif not has_level and level is not None: elif not has_section and section is not None:
return redirect('editor.mapitems', mapitem_type=mapitem_type) return redirect('editor.mapitems', mapitem_type=mapitem_type)
queryset = mapitemtype.objects.all().order_by('name') queryset = mapitemtype.objects.all().order_by('name')
if level is not None: if section is not None:
level = get_object_or_404(Level, level) section = get_object_or_404(Section, section)
if hasattr(mapitemtype, 'level'): if hasattr(mapitemtype, 'section'):
queryset = queryset.filter(level=level) queryset = queryset.filter(section=section)
elif hasattr(mapitemtype, 'levels'):
queryset = queryset.filter(levels=level)
queryset = filter_queryset_by_access(request, queryset) queryset = filter_queryset_by_access(request, queryset)
@ -61,12 +56,9 @@ def list_mapitems(request, mapitem_type, level=None):
return render(request, 'editor/mapitems.html', { return render(request, 'editor/mapitems.html', {
'mapitem_type': mapitem_type, 'mapitem_type': mapitem_type,
'title': mapitemtype._meta.verbose_name_plural, 'title': mapitemtype._meta.verbose_name_plural,
'has_level': level is not None, 'has_section': section is not None,
'has_elevator': hasattr(mapitemtype, 'elevator'),
'has_levels': hasattr(mapitemtype, 'levels'),
'has_altitude': hasattr(mapitemtype, 'altitude'), 'has_altitude': hasattr(mapitemtype, 'altitude'),
'has_intermediate': hasattr(mapitemtype, 'intermediate'), 'section': section.id,
'level': level.id,
'items': queryset, 'items': queryset,
}) })

View file

@ -4,18 +4,18 @@ import mimetypes
from collections import OrderedDict from collections import OrderedDict
from django.http import Http404, HttpResponse, HttpResponseNotModified from django.http import Http404, HttpResponse, HttpResponseNotModified
from django.shortcuts import get_object_or_404
from rest_framework.decorators import detail_route, list_route from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from c3nav.access.apply import filter_arealocations_by_access, filter_queryset_by_access from c3nav.access.apply import filter_arealocations_by_access, filter_queryset_by_access
from c3nav.mapdata.lastupdate import get_last_mapdata_update from c3nav.mapdata.lastupdate import get_last_mapdata_update
from c3nav.mapdata.models import AreaLocation, Level, LocationGroup, Source from c3nav.mapdata.models import AreaLocation, LocationGroup, Source
from c3nav.mapdata.models.geometry.space import Stair
from c3nav.mapdata.models.geometry.base import GEOMETRY_FEATURE_TYPES from c3nav.mapdata.models.geometry.base import GEOMETRY_FEATURE_TYPES
from c3nav.mapdata.models.geometry.space import Stair
from c3nav.mapdata.models.section import Section
from c3nav.mapdata.search import get_location from c3nav.mapdata.search import get_location
from c3nav.mapdata.serializers.main import LevelSerializer, SourceSerializer from c3nav.mapdata.serializers.main import SectionSerializer, SourceSerializer
from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin, cache_mapdata_api_response, get_bssid_areas_cached from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin, cache_mapdata_api_response, get_bssid_areas_cached
@ -37,7 +37,6 @@ class GeometryTypeViewSet(ViewSet):
class GeometryViewSet(ViewSet): class GeometryViewSet(ViewSet):
""" """
List all geometries. List all geometries.
You can filter by adding a level GET parameter.
""" """
def list(self, request): def list(self, request):
types = set(request.GET.getlist('type')) types = set(request.GET.getlist('type'))
@ -47,45 +46,25 @@ class GeometryViewSet(ViewSet):
else: else:
types = [t for t in valid_types if t in types] types = [t for t in valid_types if t in types]
level = None
if 'level' in request.GET:
level = get_object_or_404(Level, id=request.GET['level'])
cache_key = '__'.join(( cache_key = '__'.join((
','.join([str(i) for i in types]), ','.join([str(i) for i in types]),
str(level.id) if level is not None else '',
)) ))
return self._list(request, types=types, level=level, add_cache_key=cache_key) return self._list(request, types=types, add_cache_key=cache_key)
@staticmethod @staticmethod
def compare_by_location_type(x: AreaLocation, y: AreaLocation): def compare_by_location_type(x: AreaLocation, y: AreaLocation):
return AreaLocation.LOCATION_TYPES.index(x.location_type) - AreaLocation.LOCATION_TYPES.index(y.location_type) return AreaLocation.LOCATION_TYPES.index(x.location_type) - AreaLocation.LOCATION_TYPES.index(y.location_type)
@cache_mapdata_api_response() @cache_mapdata_api_response()
def _list(self, request, types, level): def _list(self, request, types):
results = [] results = []
for t in types: for t in types:
mapitemtype = GEOMETRY_FEATURE_TYPES[t] mapitemtype = GEOMETRY_FEATURE_TYPES[t]
queryset = mapitemtype.objects.all() queryset = mapitemtype.objects.all()
if level:
if hasattr(mapitemtype, 'level'):
queryset = queryset.filter(level=level)
elif hasattr(mapitemtype, 'levels'):
queryset = queryset.filter(levels=level)
else:
queryset = queryset.none()
queryset = filter_queryset_by_access(request, queryset) queryset = filter_queryset_by_access(request, queryset)
queryset = queryset.order_by('id') queryset = queryset.order_by('id')
for field_name in ('level', 'crop_to_level', 'elevator'):
if hasattr(mapitemtype, field_name):
queryset = queryset.select_related(field_name)
for field_name in ('levels', ):
if hasattr(mapitemtype, field_name):
queryset.prefetch_related(field_name)
if issubclass(mapitemtype, AreaLocation): if issubclass(mapitemtype, AreaLocation):
queryset = sorted(queryset, key=AreaLocation.get_sort_key) queryset = sorted(queryset, key=AreaLocation.get_sort_key)
@ -97,12 +76,12 @@ class GeometryViewSet(ViewSet):
return Response(results) return Response(results)
class LevelViewSet(CachedReadOnlyViewSetMixin, ReadOnlyModelViewSet): class SectionViewSet(CachedReadOnlyViewSetMixin, ReadOnlyModelViewSet):
""" """
List and retrieve levels. List and retrieve sections.
""" """
queryset = Level.objects.all() queryset = Section.objects.all()
serializer_class = LevelSerializer serializer_class = SectionSerializer
lookup_field = 'id' lookup_field = 'id'

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-05-07 09:37
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0058_auto_20170506_1549'),
]
operations = [
migrations.RenameModel(
old_name='Level',
new_name='Section',
),
]

View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-05-07 09:52
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0059_auto_20170507_0937'),
]
operations = [
migrations.RenameField(
model_name='arealocation',
old_name='level',
new_name='section',
),
migrations.RenameField(
model_name='building',
old_name='level',
new_name='section',
),
migrations.RenameField(
model_name='door',
old_name='level',
new_name='section',
),
migrations.RenameField(
model_name='hole',
old_name='level',
new_name='section',
),
migrations.RenameField(
model_name='space',
old_name='level',
new_name='section',
),
]

View file

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-05-07 09:53
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0060_auto_20170507_0952'),
]
operations = [
migrations.AlterField(
model_name='arealocation',
name='section',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arealocations', to='mapdata.Section', verbose_name='section'),
),
migrations.AlterField(
model_name='building',
name='section',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='buildings', to='mapdata.Section', verbose_name='section'),
),
migrations.AlterField(
model_name='door',
name='section',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='doors', to='mapdata.Section', verbose_name='section'),
),
migrations.AlterField(
model_name='hole',
name='section',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='holes', to='mapdata.Section', verbose_name='section'),
),
migrations.AlterField(
model_name='section',
name='altitude',
field=models.DecimalField(decimal_places=2, max_digits=6, unique=True, verbose_name='section altitude'),
),
migrations.AlterField(
model_name='section',
name='name',
field=models.SlugField(unique=True, verbose_name='section name'),
),
migrations.AlterField(
model_name='space',
name='section',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='areas', to='mapdata.Section', verbose_name='section'),
),
]

View file

@ -1,3 +1,3 @@
from .level import Level # noqa from .section import Section # noqa
from .source import Source # noqa from .source import Source # noqa
from .locations import AreaLocation, LocationGroup # noqa from .locations import AreaLocation, LocationGroup # noqa

View file

@ -1,4 +1,5 @@
from collections import OrderedDict from collections import OrderedDict
from django.db import models from django.db import models
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.utils.translation import get_language from django.utils.translation import get_language
@ -38,5 +39,3 @@ class Feature(models.Model, metaclass=FeatureBase):
class Meta: class Meta:
abstract = True abstract = True

View file

@ -1,3 +0,0 @@

View file

@ -1,5 +1,6 @@
from collections import OrderedDict from collections import OrderedDict
from shapely.geometry import mapping, Point
from shapely.geometry import Point, mapping
from c3nav.mapdata.fields import GeometryField from c3nav.mapdata.fields import GeometryField
from c3nav.mapdata.models.base import Feature, FeatureBase from c3nav.mapdata.models.base import Feature, FeatureBase

View file

@ -1,4 +1,5 @@
from collections import OrderedDict from collections import OrderedDict
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -7,7 +8,7 @@ from c3nav.mapdata.models.geometry.base import GeometryFeature, GeometryFeatureB
LEVEL_FEATURE_TYPES = OrderedDict() LEVEL_FEATURE_TYPES = OrderedDict()
class LevelFeatureBase(GeometryFeatureBase): class SectionFeatureBase(GeometryFeatureBase):
def __new__(mcs, name, bases, attrs): def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs) cls = super().__new__(mcs, name, bases, attrs)
if not cls._meta.abstract: if not cls._meta.abstract:
@ -15,22 +16,22 @@ class LevelFeatureBase(GeometryFeatureBase):
return cls return cls
class LevelFeature(GeometryFeature, metaclass=LevelFeatureBase): class SectionFeature(GeometryFeature, metaclass=SectionFeatureBase):
""" """
a map feature that has a geometry and belongs to a level a map feature that has a geometry and belongs to a section
""" """
level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level')) section = models.ForeignKey('mapdata.Section', on_delete=models.CASCADE, verbose_name=_('section'))
class Meta: class Meta:
abstract = True abstract = True
def get_geojson_properties(self): def get_geojson_properties(self):
result = super().get_geojson_properties() result = super().get_geojson_properties()
result['level'] = self.level.id result['section'] = self.section.id
return result return result
class Building(LevelFeature): class Building(SectionFeature):
""" """
The outline of a building on a specific level The outline of a building on a specific level
""" """
@ -42,7 +43,7 @@ class Building(LevelFeature):
default_related_name = 'buildings' default_related_name = 'buildings'
class Space(LevelFeature): class Space(SectionFeature):
""" """
An accessible space. Shouldn't overlap. An accessible space. Shouldn't overlap.
""" """
@ -77,7 +78,7 @@ class Space(LevelFeature):
return result return result
class Door(LevelFeature): class Door(SectionFeature):
""" """
A connection between two rooms A connection between two rooms
""" """
@ -89,7 +90,7 @@ class Door(LevelFeature):
default_related_name = 'doors' default_related_name = 'doors'
class Hole(LevelFeature): class Hole(SectionFeature):
""" """
A hole in the ground of a room, e.g. for stairs. A hole in the ground of a room, e.g. for stairs.
""" """

View file

@ -1,12 +1,12 @@
from collections import OrderedDict from collections import OrderedDict
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from shapely.geometry import JOIN_STYLE, CAP_STYLE, mapping from shapely.geometry import CAP_STYLE, JOIN_STYLE, mapping
from c3nav.mapdata.models.geometry.base import GeometryFeature, GeometryFeatureBase from c3nav.mapdata.models.geometry.base import GeometryFeature, GeometryFeatureBase
from c3nav.mapdata.utils.json import format_geojson from c3nav.mapdata.utils.json import format_geojson
SPACE_FEATURE_TYPES = OrderedDict() SPACE_FEATURE_TYPES = OrderedDict()

View file

@ -9,9 +9,9 @@ from django.utils.translation import ungettext_lazy
from c3nav.mapdata.fields import JSONField, validate_bssid_lines from c3nav.mapdata.fields import JSONField, validate_bssid_lines
from c3nav.mapdata.lastupdate import get_last_mapdata_update from c3nav.mapdata.lastupdate import get_last_mapdata_update
from c3nav.mapdata.models import Level
from c3nav.mapdata.models.base import Feature from c3nav.mapdata.models.base import Feature
from c3nav.mapdata.models.geometry.level import LevelFeature from c3nav.mapdata.models.geometry.section import SectionFeature
from c3nav.mapdata.models.section import Section
class Location: class Location:
@ -85,7 +85,7 @@ class LocationGroup(LocationModelMixin, Feature):
level_ids.add(area.id) level_ids.add(area.id)
in_levels.append(area) in_levels.append(area)
in_levels = sorted(in_levels, key=lambda area: area.level.altitude) in_levels = sorted(in_levels, key=lambda area: area.section.altitude)
return in_levels return in_levels
@property @property
@ -102,7 +102,7 @@ class LocationGroup(LocationModelMixin, Feature):
return result return result
class AreaLocation(LocationModelMixin, LevelFeature): class AreaLocation(LocationModelMixin, SectionFeature):
LOCATION_TYPES = ( LOCATION_TYPES = (
('level', _('Level')), ('level', _('Level')),
('area', _('General Area')), ('area', _('General Area')),
@ -162,7 +162,7 @@ class AreaLocation(LocationModelMixin, LevelFeature):
in_areas = [] in_areas = []
area_location_i = self.get_sort_key(self) area_location_i = self.get_sort_key(self)
for location_type in reversed(self.LOCATION_TYPES_ORDER[:area_location_i]): for location_type in reversed(self.LOCATION_TYPES_ORDER[:area_location_i]):
for arealocation in AreaLocation.objects.filter(location_type=location_type, level=self.level): for arealocation in AreaLocation.objects.filter(location_type=location_type, section=self.section):
intersection_area = arealocation.geometry.intersection(self.geometry).area intersection_area = arealocation.geometry.intersection(self.geometry).area
if intersection_area and intersection_area / my_area > 0.99: if intersection_area and intersection_area / my_area > 0.99:
in_areas.append(arealocation) in_areas.append(arealocation)
@ -196,15 +196,15 @@ class AreaLocation(LocationModelMixin, LevelFeature):
class PointLocation(Location): class PointLocation(Location):
def __init__(self, level: Level, x: int, y: int, request): def __init__(self, section: Section, x: int, y: int, request):
self.level = level self.section = section
self.x = x self.x = x
self.y = y self.y = y
self.request = request self.request = request
@cached_property @cached_property
def location_id(self): def location_id(self):
return 'c:%s:%d:%d' % (self.level.name, self.x*100, self.y*100) return 'c:%d:%d:%d' % (self.section.id, self.x * 100, self.y * 100)
@cached_property @cached_property
def xy(self): def xy(self):
@ -214,7 +214,7 @@ class PointLocation(Location):
def description(self): def description(self):
from c3nav.routing.graph import Graph from c3nav.routing.graph import Graph
graph = Graph.load() graph = Graph.load()
point = graph.get_nearest_point(self.level, self.x, self.y) point = graph.get_nearest_point(self.section, self.x, self.y)
if point is None or (':nonpublic' in point.arealocations and not self.request.c3nav_full_access and if point is None or (':nonpublic' in point.arealocations and not self.request.c3nav_full_access and
not len(set(self.request.c3nav_access_list) & set(point.arealocations))): not len(set(self.request.c3nav_access_list) & set(point.arealocations))):
@ -239,14 +239,14 @@ class PointLocation(Location):
@property @property
def subtitle(self) -> str: def subtitle(self) -> str:
add_subtitle = self.description[1] add_subtitle = self.description[1]
subtitle = '%s:%d:%d' % (self.level.name, self.x*100, self.y*100) subtitle = '%s:%d:%d' % (self.section.name, self.x * 100, self.y * 100)
if add_subtitle: if add_subtitle:
subtitle += ' - '+add_subtitle subtitle += ' - '+add_subtitle
return subtitle return subtitle
def to_location_json(self): def to_location_json(self):
result = super().to_location_json() result = super().to_location_json()
result['level'] = self.level.name result['section'] = self.section.id
result['x'] = self.x result['x'] = self.x
result['y'] = self.y result['y'] = self.y
return result return result

View file

@ -8,18 +8,17 @@ from c3nav.mapdata.models.base import Feature
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon
class Level(Feature): class Section(Feature):
""" """
A map level (-1, 0, 1, 2) A map section like a level
""" """
name = models.SlugField(_('level name'), unique=True, max_length=50, name = models.SlugField(_('section name'), unique=True, max_length=50)
help_text=_('Usually just an integer (e.g. -1, 0, 1, 2)')) altitude = models.DecimalField(_('section altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
altitude = models.DecimalField(_('level altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
class Meta: class Meta:
verbose_name = _('Level') verbose_name = _('Section')
verbose_name_plural = _('Levels') verbose_name_plural = _('Sections')
default_related_name = 'levels' default_related_name = 'sections'
ordering = ['altitude'] ordering = ['altitude']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -27,35 +26,35 @@ class Level(Feature):
@cached_property @cached_property
def public_geometries(self): def public_geometries(self):
return LevelGeometries.by_level(self, only_public=True) return SectionGeometries.by_section(self, only_public=True)
@cached_property @cached_property
def geometries(self): def geometries(self):
return LevelGeometries.by_level(self, only_public=False) return SectionGeometries.by_section(self, only_public=False)
def lower(self): def lower(self):
return Level.objects.filter(altitude__lt=self.altitude).order_by('altitude') return Section.objects.filter(altitude__lt=self.altitude).order_by('altitude')
def higher(self): def higher(self):
return Level.objects.filter(altitude__gt=self.altitude).order_by('altitude') return Section.objects.filter(altitude__gt=self.altitude).order_by('altitude')
def __str__(self): def __str__(self):
return self.name return self.name
class LevelGeometries(): class SectionGeometries():
by_level_name = {} by_section_id = {}
@classmethod @classmethod
def by_level(cls, level, only_public=True): def by_section(cls, section, only_public=True):
return cls.by_level_name.setdefault((level.name, only_public), cls(level, only_public=only_public)) return cls.by_section_id.setdefault((section.id, only_public), cls(section, only_public=only_public))
def __init__(self, level, only_public=True): def __init__(self, section, only_public=True):
self.level = level self.section = section
self.only_public = only_public self.only_public = only_public
def query(self, name): def query(self, name):
queryset = getattr(self.level, name) queryset = getattr(self.section, name)
if not self.only_public: if not self.only_public:
return queryset.all() return queryset.all()
return queryset.filter(public=True) return queryset.filter(public=True)
@ -67,7 +66,7 @@ class LevelGeometries():
@cached_property @cached_property
def buildings(self): def buildings(self):
result = cascaded_union([building.geometry for building in self.query('buildings')]) result = cascaded_union([building.geometry for building in self.query('buildings')])
if self.level.intermediate: if self.section.intermediate:
result = cascaded_union([result, self.raw_rooms]) result = cascaded_union([result, self.raw_rooms])
return result return result
@ -181,7 +180,7 @@ class LevelGeometries():
@cached_property @cached_property
def intermediate_shadows(self): def intermediate_shadows(self):
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.level.altitude) qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.section.altitude)
connectors = cascaded_union([levelconnector.geometry for levelconnector in qs]) connectors = cascaded_union([levelconnector.geometry for levelconnector in qs])
shadows = self.buildings.difference(connectors.buffer(0.4, join_style=JOIN_STYLE.mitre)) shadows = self.buildings.difference(connectors.buffer(0.4, join_style=JOIN_STYLE.mitre))
shadows = shadows.buffer(0.3) shadows = shadows.buffer(0.3)
@ -192,7 +191,7 @@ class LevelGeometries():
holes = self.holes.buffer(0.1, join_style=JOIN_STYLE.mitre) holes = self.holes.buffer(0.1, join_style=JOIN_STYLE.mitre)
shadows = holes.difference(self.holes.buffer(-0.3, join_style=JOIN_STYLE.mitre)) shadows = holes.difference(self.holes.buffer(-0.3, join_style=JOIN_STYLE.mitre))
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.level.altitude) qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.section.altitude)
connectors = cascaded_union([levelconnector.geometry for levelconnector in qs]) connectors = cascaded_union([levelconnector.geometry for levelconnector in qs])
shadows = shadows.difference(connectors.buffer(1.0, join_style=JOIN_STYLE.mitre)) shadows = shadows.difference(connectors.buffer(1.0, join_style=JOIN_STYLE.mitre))

View file

@ -1,11 +1,11 @@
from c3nav.mapdata.models import Level from c3nav.mapdata.models.section import Section
from c3nav.mapdata.render.renderer import LevelRenderer # noqa from c3nav.mapdata.render.renderer import LevelRenderer # noqa
def render_all_levels(show_accessibles=False): def render_all_levels(show_accessibles=False):
renderers = [] renderers = []
for level in Level.objects.all(): for level in Section.objects.all():
renderers.append(LevelRenderer(level, only_public=False)) renderers.append(LevelRenderer(level, only_public=False))
renderers.append(LevelRenderer(level, only_public=True)) renderers.append(LevelRenderer(level, only_public=True))

View file

@ -5,17 +5,17 @@ from django.db.models import Q
from c3nav.access.apply import filter_arealocations_by_access, filter_queryset_by_access from c3nav.access.apply import filter_arealocations_by_access, filter_queryset_by_access
from c3nav.mapdata.models import AreaLocation, LocationGroup from c3nav.mapdata.models import AreaLocation, LocationGroup
from c3nav.mapdata.models.locations import PointLocation from c3nav.mapdata.models.locations import PointLocation
from c3nav.mapdata.utils.cache import get_levels_cached from c3nav.mapdata.utils.cache import get_sections_cached
def get_location(request, location_id): def get_location(request, location_id):
match = re.match('^c:(?P<level>[a-z0-9-_]+):(?P<x>[0-9]+):(?P<y>[0-9]+)$', location_id) match = re.match('^c:(?P<section>[0-9]+):(?P<x>[0-9]+):(?P<y>[0-9]+)$', location_id)
if match: if match:
levels = get_levels_cached() levels = get_sections_cached()
level = levels.get(match.group('level')) section = levels.get(int(match.group('section')))
if level is None: if section is None:
return None return None
return PointLocation(level=level, x=int(match.group('x'))/100, y=int(match.group('y'))/100, request=request) return PointLocation(section=section, x=int(match.group('x')) / 100, y=int(match.group('y')) / 100, request=request)
if location_id.startswith('g:'): if location_id.startswith('g:'):
queryset = LocationGroup.objects.filter(Q(slug=location_id[2:], can_search=True)) queryset = LocationGroup.objects.filter(Q(slug=location_id[2:], can_search=True))

View file

@ -1,11 +1,12 @@
from rest_framework import serializers from rest_framework import serializers
from c3nav.mapdata.models import Level, Source from c3nav.mapdata.models.section import Section
from c3nav.mapdata.models.source import Source
class LevelSerializer(serializers.ModelSerializer): class SectionSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Level model = Section
fields = ('id', 'name', 'altitude') fields = ('id', 'name', 'altitude')

View file

@ -81,10 +81,10 @@ class CachedReadOnlyViewSetMixin():
return super().retrieve(request, *args, **kwargs) return super().retrieve(request, *args, **kwargs)
@cache_result('c3nav__mapdata__levels') @cache_result('c3nav__mapdata__sections')
def get_levels_cached(): def get_sections_cached():
from c3nav.mapdata.models import Level from c3nav.mapdata.models.section import Section
return OrderedDict((level.name, level) for level in Level.objects.all()) return OrderedDict((section.id, section) for section in Section.objects.all())
@cache_result('c3nav__mapdata__bssids') @cache_result('c3nav__mapdata__bssids')

View file

@ -8,8 +8,8 @@ from django.conf import settings
from scipy.sparse.csgraph._shortest_path import shortest_path from scipy.sparse.csgraph._shortest_path import shortest_path
from scipy.sparse.csgraph._tools import csgraph_from_dense from scipy.sparse.csgraph._tools import csgraph_from_dense
from c3nav.mapdata.models import Level
from c3nav.mapdata.models.locations import AreaLocation, Location, LocationGroup, PointLocation from c3nav.mapdata.models.locations import AreaLocation, Location, LocationGroup, PointLocation
from c3nav.mapdata.models.section import Section
from c3nav.routing.connection import GraphConnection from c3nav.routing.connection import GraphConnection
from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable
from c3nav.routing.level import GraphLevel from c3nav.routing.level import GraphLevel
@ -27,7 +27,7 @@ class Graph:
def __init__(self, mtime=None): def __init__(self, mtime=None):
self.mtime = mtime self.mtime = mtime
self.levels = OrderedDict() self.levels = OrderedDict()
for level in Level.objects.all(): for level in Section.objects.all():
self.levels[level.name] = GraphLevel(self, level) self.levels[level.name] = GraphLevel(self, level)
self.points = [] self.points = []
@ -245,7 +245,7 @@ class Graph:
def get_location_points(self, location: Location, mode): def get_location_points(self, location: Location, mode):
if isinstance(location, PointLocation): if isinstance(location, PointLocation):
points = self.levels[location.level.name].connected_points(np.array((location.x, location.y)), mode) points = self.levels[location.section.name].connected_points(np.array((location.x, location.y)), mode)
if not points: if not points:
return (), None, None return (), None, None
points, distances, ctypes = zip(*((point, distance, ctype) for point, (distance, ctype) in points.items())) points, distances, ctypes = zip(*((point, distance, ctype) for point, (distance, ctype) in points.items()))

View file

@ -10,9 +10,9 @@ from django.utils import timezone
from c3nav.access.apply import get_visible_areas from c3nav.access.apply import get_visible_areas
from c3nav.mapdata.inclusion import get_includables_avoidables, parse_include_avoid from c3nav.mapdata.inclusion import get_includables_avoidables, parse_include_avoid
from c3nav.mapdata.lastupdate import get_last_mapdata_update from c3nav.mapdata.lastupdate import get_last_mapdata_update
from c3nav.mapdata.models import Level from c3nav.mapdata.models.section import Section
from c3nav.mapdata.search import get_location, search_location from c3nav.mapdata.search import get_location, search_location
from c3nav.mapdata.utils.cache import get_levels_cached from c3nav.mapdata.utils.cache import get_sections_cached
from c3nav.mapdata.utils.misc import get_dimensions, get_render_path from c3nav.mapdata.utils.misc import get_dimensions, get_render_path
from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable
from c3nav.routing.graph import Graph from c3nav.routing.graph import Graph
@ -92,18 +92,18 @@ def main(request, location=None, origin=None, destination=None):
} }
width, height = get_dimensions() width, height = get_dimensions()
levels = tuple(name for name, level in get_levels_cached().items() if not level.intermediate) sections = tuple(section for id_, section in get_sections_cached().items())
ctx.update({ ctx.update({
'width': width, 'width': width,
'height': height, 'height': height,
'svg_width': int(width * 6), 'svg_width': int(width * 6),
'svg_height': int(height * 6), 'svg_height': int(height * 6),
'levels': levels, 'sections': sections,
}) })
map_level = request.GET.get('map-level') map_level = request.GET.get('map-level')
if map_level in levels: if map_level in sections:
ctx.update({ ctx.update({
'map_level': map_level 'map_level': map_level
}) })
@ -238,7 +238,7 @@ def main(request, location=None, origin=None, destination=None):
def map_image(request, area, level): def map_image(request, area, level):
level = get_object_or_404(Level, name=level, intermediate=False) level = get_object_or_404(Section, name=level, intermediate=False)
if area == ':base': if area == ':base':
img = get_render_path('png', level.name, 'full', True) img = get_render_path('png', level.name, 'full', True)
elif area == ':full': elif area == ':full':