team-3/src/c3nav/editor/api.py

251 lines
11 KiB
Python
Raw Normal View History

from itertools import chain
2017-07-27 16:07:00 +02:00
from django.db.models import Prefetch, Q
2017-07-06 13:07:05 +02:00
from rest_framework.decorators import detail_route, list_route
from rest_framework.exceptions import ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
2017-07-06 13:07:05 +02:00
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from shapely.ops import cascaded_union
2017-06-12 23:33:59 +02:00
from c3nav.editor.models import ChangeSet
2017-10-27 17:33:50 +02:00
from c3nav.editor.views.base import etag_func
from c3nav.mapdata.api import api_etag
2017-10-27 17:33:50 +02:00
from c3nav.mapdata.models import Area, Door, MapUpdate, Source
from c3nav.mapdata.models.geometry.space import POI
class EditorViewSet(ViewSet):
"""
Editor API
2017-06-11 14:43:14 +02:00
/geometries/ returns a list of geojson features, you have to specify ?level=<id> or ?space=<id>
/geometrystyles/ returns styling information for all geometry types
2017-07-14 00:00:53 +02:00
/bounds/ returns the maximum bounds of the map
"""
2017-06-27 17:10:22 +02:00
@staticmethod
def _get_level_geometries(level):
2017-06-11 14:43:14 +02:00
buildings = level.buildings.all()
buildings_geom = cascaded_union([building.geometry for building in buildings])
spaces = {space.pk: space for space in level.spaces.all()}
holes_geom = []
for space in spaces.values():
if space.outside:
space.geometry = space.geometry.difference(buildings_geom)
columns_geom = cascaded_union([column.geometry for column in space.columns.all()])
space.geometry = space.geometry.difference(columns_geom)
space_holes_geom = cascaded_union([hole.geometry for hole in space.holes.all()])
holes_geom.append(space_holes_geom.intersection(space.geometry))
space.geometry = space.geometry.difference(space_holes_geom)
holes_geom = cascaded_union(holes_geom)
for building in buildings:
building.original_geometry = building.geometry
for obj in buildings:
obj.geometry = obj.geometry.difference(holes_geom)
results = []
results.extend(buildings)
2017-06-11 14:43:14 +02:00
for door in level.doors.all():
results.append(door)
results.extend(spaces.values())
return results
2017-06-27 17:10:22 +02:00
@staticmethod
def _get_levels_pk(request, level):
# noinspection PyPep8Naming
Level = request.changeset.wrap_model('Level')
2017-06-11 14:43:14 +02:00
levels_under = ()
levels_on_top = ()
2017-06-12 23:33:59 +02:00
lower_level = level.lower(Level).first()
2017-06-11 14:43:14 +02:00
primary_levels = (level,) + ((lower_level,) if lower_level else ())
secondary_levels = Level.objects.filter(on_top_of__in=primary_levels).values_list('pk', 'on_top_of')
if lower_level:
levels_under = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == lower_level.pk)
if True:
2017-06-11 14:43:14 +02:00
levels_on_top = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == level.pk)
levels = chain([level.pk], levels_under, levels_on_top)
return levels, levels_on_top, levels_under
2017-06-27 17:10:22 +02:00
# noinspection PyPep8Naming
@list_route(methods=['get'])
@api_etag(etag_func)
def geometries(self, request, *args, **kwargs):
2017-06-12 23:33:59 +02:00
request.changeset = ChangeSet.get_for_request(request)
Level = request.changeset.wrap_model('Level')
Space = request.changeset.wrap_model('Space')
2017-06-12 23:33:59 +02:00
2017-06-11 14:43:14 +02:00
level = request.GET.get('level')
space = request.GET.get('space')
2017-06-11 14:43:14 +02:00
if level is not None:
if space is not None:
2017-06-11 14:43:14 +02:00
raise ValidationError('Only level or space can be specified.')
level = get_object_or_404(Level.objects.filter(Level.q_for_request(request)), pk=level)
2017-06-12 23:33:59 +02:00
levels, levels_on_top, levels_under = self._get_levels_pk(request, level)
# don't prefetch groups for now as changesets do not yet work with m2m-prefetches
levels = Level.objects.filter(pk__in=levels).filter(Level.q_for_request(request))
2017-08-06 16:52:08 +02:00
graphnodes = request.changeset.wrap_model('GraphNode').objects.all()
levels = levels.prefetch_related(
Prefetch('spaces', request.changeset.wrap_model('Space').objects.filter(Space.q_for_request(request))),
2017-07-13 23:49:00 +02:00
Prefetch('doors', request.changeset.wrap_model('Door').objects.filter(Door.q_for_request(request))),
'buildings', 'spaces__holes', 'spaces__groups', 'spaces__columns', 'spaces__altitudemarkers',
2017-07-26 15:06:14 +02:00
Prefetch('spaces__graphnodes', graphnodes)
)
2017-06-11 14:43:14 +02:00
levels = {s.pk: s for s in levels}
2017-06-11 14:43:14 +02:00
level = levels[level.pk]
levels_under = [levels[pk] for pk in levels_under]
levels_on_top = [levels[pk] for pk in levels_on_top]
2017-07-27 19:21:07 +02:00
# todo: permissions
2017-07-27 16:07:00 +02:00
graphnodes = tuple(chain(*(space.graphnodes.all()
for space in chain(*(level.spaces.all() for level in levels.values())))))
graphedges = request.changeset.wrap_model('GraphEdge').objects.all()
graphedges = graphedges.filter(Q(from_node__in=graphnodes) | Q(to_node__in=graphnodes))
graphedges = graphedges.select_related('from_node', 'to_node', 'waytype')
# graphedges = [edge for edge in graphedges if edge.from_node.space_id != edge.to_node.space_id]
2017-07-27 16:07:00 +02:00
results = chain(
2017-07-27 15:12:30 +02:00
*(self._get_level_geometries(l) for l in levels_under),
2017-06-11 14:43:14 +02:00
self._get_level_geometries(level),
2017-07-27 16:07:00 +02:00
*(self._get_level_geometries(l) for l in levels_on_top),
*(space.altitudemarkers.all() for space in level.spaces.all()),
2017-07-27 16:07:00 +02:00
graphedges,
graphnodes,
)
return Response([obj.to_geojson(instance=obj) for obj in results])
elif space is not None:
space_q_for_request = Space.q_for_request(request)
qs = Space.objects.filter(space_q_for_request)
space = get_object_or_404(qs.select_related('level', 'level__on_top_of'), pk=space)
2017-06-11 14:43:14 +02:00
level = space.level
2017-07-13 23:49:00 +02:00
doors = [door for door in level.doors.filter(Door.q_for_request(request)).all()
if door.geometry.intersects(space.geometry)]
doors_space_geom = cascaded_union([door.geometry for door in doors]+[space.geometry])
2017-06-12 23:33:59 +02:00
levels, levels_on_top, levels_under = self._get_levels_pk(request, level.primary_level)
if level.on_top_of_id is not None:
2017-11-13 21:52:38 +01:00
levels = chain([level.pk], levels_on_top)
2017-07-27 16:07:00 +02:00
other_spaces = Space.objects.filter(space_q_for_request, level__pk__in=levels).prefetch_related('groups')
space = next(s for s in other_spaces if s.pk == space.pk)
other_spaces = [s for s in other_spaces
if s.geometry.intersects(doors_space_geom) and s.pk != space.pk]
2017-08-06 16:52:08 +02:00
all_other_spaces = other_spaces
2017-07-27 16:07:00 +02:00
if level.on_top_of_id is None:
other_spaces_lower = [s for s in other_spaces if s.level_id in levels_under]
other_spaces_upper = [s for s in other_spaces if s.level_id in levels_on_top]
else:
other_spaces_lower = [s for s in other_spaces if s.level_id == level.on_top_of_id]
other_spaces_upper = []
other_spaces = [s for s in other_spaces if s.level_id == level.pk]
2017-05-27 16:19:49 +02:00
space.bounds = True
2017-06-11 14:43:14 +02:00
buildings = level.buildings.all()
buildings_geom = cascaded_union([building.geometry for building in buildings])
for other_space in other_spaces:
if other_space.outside:
other_space.geometry = other_space.geometry.difference(buildings_geom)
for other_space in chain(other_spaces, other_spaces_lower, other_spaces_upper):
other_space.opacity = 0.4
other_space.color = '#ffffff'
for building in buildings:
building.opacity = 0.5
2017-07-27 19:21:07 +02:00
# todo: permissions
2017-07-27 16:07:00 +02:00
graphnodes = request.changeset.wrap_model('GraphNode').objects.all()
2017-08-06 16:52:08 +02:00
graphnodes = graphnodes.filter((Q(space__in=all_other_spaces)) | Q(space__pk=space.pk))
space_graphnodes = tuple(node for node in graphnodes if node.space == space)
2017-07-27 16:07:00 +02:00
graphedges = request.changeset.wrap_model('GraphEdge').objects.all()
2017-08-06 16:52:08 +02:00
graphedges = graphedges.filter(Q(from_node__in=space_graphnodes) | Q(to_node__in=space_graphnodes))
2017-07-27 16:07:00 +02:00
graphedges = graphedges.select_related('from_node', 'to_node', 'waytype')
results = chain(
buildings,
other_spaces_lower,
doors,
other_spaces,
[space],
space.areas.filter(Area.q_for_request(request)).prefetch_related('groups'),
2017-06-08 15:19:12 +02:00
space.holes.all(),
space.stairs.all(),
2017-11-17 20:31:29 +01:00
space.ramps.all(),
space.obstacles.all(),
space.lineobstacles.all(),
2017-06-09 15:22:30 +02:00
space.columns.all(),
2017-08-05 11:56:29 +02:00
space.altitudemarkers.all(),
space.pois.filter(POI.q_for_request(request)).prefetch_related('groups'),
other_spaces_upper,
2017-07-27 16:07:00 +02:00
graphedges,
graphnodes
)
return Response([obj.to_geojson(instance=obj) for obj in results])
else:
2017-06-11 14:43:14 +02:00
raise ValidationError('No level or space specified.')
@list_route(methods=['get'])
@api_etag(MapUpdate.current_cache_key)
def geometrystyles(self, request, *args, **kwargs):
return Response({
2017-09-21 16:22:20 +02:00
'building': '#aaaaaa',
2017-09-21 16:16:44 +02:00
'space': '#eeeeee',
'hole': 'rgba(255, 0, 0, 0.3)',
'door': '#ffffff',
2017-05-27 19:01:38 +02:00
'area': 'rgba(85, 170, 255, 0.2)',
'stair': '#a000a0',
2017-11-17 20:31:29 +01:00
'ramp': 'rgba(160, 0, 160, 0.2)',
'obstacle': '#999999',
'lineobstacle': '#999999',
2017-06-09 15:22:30 +02:00
'column': '#888888',
2017-07-08 16:29:12 +02:00
'poi': '#4488cc',
2017-05-22 00:12:49 +02:00
'shadow': '#000000',
2017-08-06 16:53:42 +02:00
'graphnode': '#009900',
2017-07-27 16:07:00 +02:00
'graphedge': '#00CC00',
2017-11-18 17:38:13 +01:00
'altitudemarker': '#0000FF',
})
@list_route(methods=['get'])
@api_etag(etag_func)
def bounds(self, request, *args, **kwargs):
return Response({
'bounds': Source.max_bounds(),
})
2017-07-06 13:07:05 +02:00
class ChangeSetViewSet(ReadOnlyModelViewSet):
"""
List change sets
/current/ returns the current changeset.
"""
queryset = ChangeSet.objects.all()
def get_queryset(self):
2017-07-06 13:09:33 +02:00
return ChangeSet.qs_for_request(self.request).select_related('last_update', 'last_state_update', 'last_change')
2017-07-06 13:07:05 +02:00
def list(self, request, *args, **kwargs):
return Response([obj.serialize() for obj in self.get_queryset().order_by('id')])
def retrieve(self, request, *args, **kwargs):
return Response(self.get_object().serialize())
@list_route(methods=['get'])
def current(self, request, *args, **kwargs):
changeset = ChangeSet.get_for_request(request)
return Response(changeset.serialize())
@detail_route(methods=['get'])
def changes(self, request, *args, **kwargs):
changeset = self.get_object()
changeset.fill_changes_cache()
2017-07-06 15:06:01 +02:00
return Response([obj.serialize() for obj in changeset.iter_changed_objects()])