diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py index 2a197689..9ff2d295 100644 --- a/src/c3nav/editor/forms.py +++ b/src/c3nav/editor/forms.py @@ -100,6 +100,6 @@ def create_editor_form(mapitemtype): def create_editor_forms(): - from c3nav.mapdata.models.base import MAPITEM_TYPES - for mapitemtype in MAPITEM_TYPES.values(): + from c3nav.mapdata.models.base import FEATURE_TYPES + for mapitemtype in FEATURE_TYPES.values(): create_editor_form(mapitemtype) diff --git a/src/c3nav/editor/views.py b/src/c3nav/editor/views.py index 3735fe8d..3a8cc2d7 100644 --- a/src/c3nav/editor/views.py +++ b/src/c3nav/editor/views.py @@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404, redirect, render from c3nav.access.apply import can_access, filter_queryset_by_access from c3nav.mapdata.models import AreaLocation -from c3nav.mapdata.models.base import MAPITEM_TYPES +from c3nav.mapdata.models.base import FEATURE_TYPES def list_mapitemtypes(request, level): @@ -26,13 +26,13 @@ def list_mapitemtypes(request, level): 'title': mapitemtype._meta.verbose_name_plural, 'has_level': hasattr(mapitemtype, 'level') or hasattr(mapitemtype, 'levels'), 'count': get_item_count(mapitemtype), - } for name, mapitemtype in MAPITEM_TYPES.items() + } for name, mapitemtype in FEATURE_TYPES.items() ], }) def list_mapitems(request, mapitem_type, level=None): - mapitemtype = MAPITEM_TYPES.get(mapitem_type) + mapitemtype = FEATURE_TYPES.get(mapitem_type) if mapitemtype is None: raise Http404('Unknown mapitemtype.') @@ -68,7 +68,7 @@ def list_mapitems(request, mapitem_type, level=None): def edit_mapitem(request, mapitem_type, name=None): - mapitemtype = MAPITEM_TYPES.get(mapitem_type) + mapitemtype = FEATURE_TYPES.get(mapitem_type) if mapitemtype is None: raise Http404() diff --git a/src/c3nav/mapdata/api.py b/src/c3nav/mapdata/api.py index 9203ca5f..483af4a6 100644 --- a/src/c3nav/mapdata/api.py +++ b/src/c3nav/mapdata/api.py @@ -10,7 +10,7 @@ from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet 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.models import GEOMETRY_MAPITEM_TYPES, AreaLocation, Level, LocationGroup, Source +from c3nav.mapdata.models import GEOMETRY_FEATURE_TYPES, AreaLocation, Level, LocationGroup, Source from c3nav.mapdata.models.geometry import Stair from c3nav.mapdata.search import get_location from c3nav.mapdata.serializers.main import LevelSerializer, SourceSerializer @@ -29,7 +29,7 @@ class GeometryTypeViewSet(ViewSet): ('name', name), ('title', str(mapitemtype._meta.verbose_name)), ('title_plural', str(mapitemtype._meta.verbose_name_plural)), - )) for name, mapitemtype in GEOMETRY_MAPITEM_TYPES.items() + )) for name, mapitemtype in GEOMETRY_FEATURE_TYPES.items() ]) @@ -40,7 +40,7 @@ class GeometryViewSet(ViewSet): """ def list(self, request): types = set(request.GET.getlist('type')) - valid_types = list(GEOMETRY_MAPITEM_TYPES.keys()) + valid_types = list(GEOMETRY_FEATURE_TYPES.keys()) if not types: types = valid_types else: @@ -68,7 +68,7 @@ class GeometryViewSet(ViewSet): def _list(self, request, types, level): results = [] for t in types: - mapitemtype = GEOMETRY_MAPITEM_TYPES[t] + mapitemtype = GEOMETRY_FEATURE_TYPES[t] queryset = mapitemtype.objects.all() if level: if hasattr(mapitemtype, 'level'): diff --git a/src/c3nav/mapdata/models/__init__.py b/src/c3nav/mapdata/models/__init__.py index 211d869e..c17ddeb6 100644 --- a/src/c3nav/mapdata/models/__init__.py +++ b/src/c3nav/mapdata/models/__init__.py @@ -1,5 +1,6 @@ from .level import Level # noqa from .source import Source # noqa from .collections import Elevator # noqa -from .geometry import GeometryMapItemWithLevel, GEOMETRY_MAPITEM_TYPES # noqa +from .geometry import LevelFeature # noqa +from c3nav.mapdata.models.base import GEOMETRY_FEATURE_TYPES from .locations import AreaLocation, LocationGroup # noqa diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py index 001d53d1..a907dd1a 100644 --- a/src/c3nav/mapdata/models/base.py +++ b/src/c3nav/mapdata/models/base.py @@ -4,21 +4,34 @@ from django.db import models from django.db.models.base import ModelBase from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language +from shapely.geometry import mapping, Point +from c3nav.mapdata.fields import GeometryField from c3nav.mapdata.lastupdate import set_last_mapdata_update +from c3nav.mapdata.utils.json import format_geojson -MAPITEM_TYPES = OrderedDict() +FEATURE_TYPES = OrderedDict() +GEOMETRY_FEATURE_TYPES = OrderedDict() +LEVEL_FEATURE_TYPES = OrderedDict() +AREA_FEATURE_TYPES = OrderedDict() -class MapItemMeta(ModelBase): +class FeatureBase(ModelBase): def __new__(mcs, name, bases, attrs): cls = super().__new__(mcs, name, bases, attrs) if not cls._meta.abstract and name != 'Source': - MAPITEM_TYPES[name.lower()] = cls + FEATURE_TYPES[name.lower()] = cls + if hasattr(cls, 'geometry'): + GEOMETRY_FEATURE_TYPES[name.lower()] = cls + if hasattr(cls, 'level'): + LEVEL_FEATURE_TYPES[name.lower()] = cls + if hasattr(cls, 'area'): + AREA_FEATURE_TYPES[name.lower()] = cls + return cls -class MapItem(models.Model, metaclass=MapItemMeta): +class Feature(models.Model, metaclass=FeatureBase): name = models.SlugField(_('Name'), unique=True, max_length=50) EditorForm = None @@ -42,3 +55,34 @@ class MapItem(models.Model, metaclass=MapItemMeta): class Meta: abstract = True + + +class GeometryFeature(Feature): + """ + A map feature with a geometry + """ + geometry = GeometryField() + + geomtype = None + + class Meta: + abstract = True + + def get_geojson_properties(self): + return OrderedDict(( + ('type', self.__class__.__name__.lower()), + ('name', self.name), + )) + + def to_geojson(self): + return OrderedDict(( + ('type', 'Feature'), + ('properties', self.get_geojson_properties()), + ('geometry', format_geojson(mapping(self.geometry), round=False)), + )) + + def get_shadow_geojson(self): + return None + + def contains(self, x, y): + return self.geometry.contains(Point(x, y)) diff --git a/src/c3nav/mapdata/models/collections.py b/src/c3nav/mapdata/models/collections.py index 5e2dd0ff..52c768bb 100644 --- a/src/c3nav/mapdata/models/collections.py +++ b/src/c3nav/mapdata/models/collections.py @@ -1,9 +1,9 @@ from django.utils.translation import ugettext_lazy as _ -from c3nav.mapdata.models.base import MapItem +from c3nav.mapdata.models.base import Feature -class Elevator(MapItem): +class Elevator(Feature): """ An elevator. """ diff --git a/src/c3nav/mapdata/models/geometry.py b/src/c3nav/mapdata/models/geometry.py index af86b32b..0064a277 100644 --- a/src/c3nav/mapdata/models/geometry.py +++ b/src/c3nav/mapdata/models/geometry.py @@ -1,61 +1,18 @@ from collections import OrderedDict - from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from shapely.geometry import CAP_STYLE, JOIN_STYLE, Point +from shapely.geometry import CAP_STYLE, JOIN_STYLE from shapely.geometry.geo import mapping -from c3nav.mapdata.fields import GeometryField from c3nav.mapdata.models import Elevator -from c3nav.mapdata.models.base import MapItem, MapItemMeta +from c3nav.mapdata.models.base import GeometryFeature from c3nav.mapdata.utils.json import format_geojson -GEOMETRY_MAPITEM_TYPES = OrderedDict() - -class GeometryMapItemMeta(MapItemMeta): - def __new__(mcs, name, bases, attrs): - cls = super().__new__(mcs, name, bases, attrs) - if not cls._meta.abstract: - GEOMETRY_MAPITEM_TYPES[name.lower()] = cls - return cls - - -class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta): +class LevelFeature(GeometryFeature): """ - A map feature - """ - geometry = GeometryField() - - geomtype = None - - class Meta: - abstract = True - - def get_geojson_properties(self): - return OrderedDict(( - ('type', self.__class__.__name__.lower()), - ('name', self.name), - )) - - def to_geojson(self): - return OrderedDict(( - ('type', 'Feature'), - ('properties', self.get_geojson_properties()), - ('geometry', format_geojson(mapping(self.geometry), round=False)), - )) - - def get_shadow_geojson(self): - return None - - def contains(self, x, y): - return self.geometry.contains(Point(x, y)) - - -class GeometryMapItemWithLevel(GeometryMapItem): - """ - A map feature + a map feature that has a geometry and belongs to a level """ level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level')) @@ -68,9 +25,9 @@ class GeometryMapItemWithLevel(GeometryMapItem): return result -class GeometryMapItemWithArea(GeometryMapItem): +class AreaFeature(GeometryFeature): """ - A map feature + a map feature that has a geometry and belongs to an area """ area = models.ForeignKey('mapdata.Area', on_delete=models.CASCADE, verbose_name=_('area')) @@ -83,7 +40,7 @@ class GeometryMapItemWithArea(GeometryMapItem): return result -class Building(GeometryMapItemWithLevel): +class Building(LevelFeature): """ The outline of a building on a specific level """ @@ -95,7 +52,7 @@ class Building(GeometryMapItemWithLevel): default_related_name = 'buildings' -class Area(GeometryMapItemWithLevel): +class Area(LevelFeature): """ An accessible area. Shouldn't overlap. """ @@ -128,7 +85,7 @@ class Area(GeometryMapItemWithLevel): return result -class StuffedArea(GeometryMapItemWithArea): +class StuffedArea(AreaFeature): """ A slow area with many tables or similar. Avoid it from routing by slowing it a bit down """ @@ -140,7 +97,7 @@ class StuffedArea(GeometryMapItemWithArea): default_related_name = 'stuffedareas' -class Escalator(GeometryMapItemWithArea): +class Escalator(AreaFeature): """ An escalator area """ @@ -163,7 +120,7 @@ class Escalator(GeometryMapItemWithArea): return result -class Stair(GeometryMapItemWithArea): +class Stair(AreaFeature): """ A stair """ @@ -197,7 +154,7 @@ class Stair(GeometryMapItemWithArea): )) -class Obstacle(GeometryMapItemWithArea): +class Obstacle(AreaFeature): """ An obstacle """ @@ -218,7 +175,7 @@ class Obstacle(GeometryMapItemWithArea): return result -class LineObstacle(GeometryMapItemWithArea): +class LineObstacle(AreaFeature): """ An obstacle that is a line with a specific width """ @@ -248,7 +205,7 @@ class LineObstacle(GeometryMapItemWithArea): return result -class Door(GeometryMapItemWithLevel): +class Door(LevelFeature): """ A connection between two rooms """ @@ -260,7 +217,7 @@ class Door(GeometryMapItemWithLevel): default_related_name = 'doors' -class Hole(GeometryMapItemWithLevel): +class Hole(LevelFeature): """ A hole in the ground of a room, e.g. for stairs. """ @@ -272,7 +229,7 @@ class Hole(GeometryMapItemWithLevel): default_related_name = 'holes' -class ElevatorLevel(GeometryMapItemWithLevel): +class ElevatorLevel(LevelFeature): """ An elevator Level """ diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index 2d7138d2..2a131148 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -4,11 +4,11 @@ from django.utils.translation import ugettext_lazy as _ from shapely.geometry import CAP_STYLE, JOIN_STYLE from shapely.ops import cascaded_union -from c3nav.mapdata.models.base import MapItem +from c3nav.mapdata.models.base import Feature from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon -class Level(MapItem): +class Level(Feature): """ A map level (-1, 0, 1, 2…) """ diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index 5c611f05..fa1fadc1 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -10,8 +10,8 @@ from django.utils.translation import ungettext_lazy from c3nav.mapdata.fields import JSONField, validate_bssid_lines from c3nav.mapdata.lastupdate import get_last_mapdata_update from c3nav.mapdata.models import Level -from c3nav.mapdata.models.base import MapItem -from c3nav.mapdata.models.geometry import GeometryMapItemWithLevel +from c3nav.mapdata.models.base import Feature +from c3nav.mapdata.models.geometry import LevelFeature class Location: @@ -43,7 +43,7 @@ class LocationModelMixin(Location): return self._meta.verbose_name -class LocationGroup(LocationModelMixin, MapItem): +class LocationGroup(LocationModelMixin, Feature): titles = JSONField() can_search = models.BooleanField(default=True, verbose_name=_('can be searched')) can_describe = models.BooleanField(default=True, verbose_name=_('can be used to describe a position')) @@ -99,7 +99,7 @@ class LocationGroup(LocationModelMixin, MapItem): return result -class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel): +class AreaLocation(LocationModelMixin, LevelFeature): LOCATION_TYPES = ( ('level', _('Level')), ('area', _('General Area')), diff --git a/src/c3nav/mapdata/models/source.py b/src/c3nav/mapdata/models/source.py index 776ced00..887e01e9 100644 --- a/src/c3nav/mapdata/models/source.py +++ b/src/c3nav/mapdata/models/source.py @@ -1,10 +1,10 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from c3nav.mapdata.models.base import MapItem +from c3nav.mapdata.models.base import Feature -class Source(MapItem): +class Source(Feature): """ A map source, images of levels that can be useful as backgrounds for the map editor """