diff --git a/app/api/common.py b/app/api/common.py new file mode 100644 index 00000000..344a15cd --- /dev/null +++ b/app/api/common.py @@ -0,0 +1,31 @@ +from django.core.exceptions import ObjectDoesNotExist +from rest_framework import exceptions + +from app import models + + +def get_and_check_project(request, project_pk, perms=('view_project',)): + ''' + Retrieves a project and raises an exeption if the current user + has no access to it. + ''' + try: + project = models.Project.objects.get(pk=project_pk, deleting=False) + for perm in perms: + if not request.user.has_perm(perm, project): raise ObjectDoesNotExist() + except ObjectDoesNotExist: + raise exceptions.NotFound() + return project + + +def get_tiles_json(name, tiles, bounds): + return { + 'tilejson': '2.1.0', + 'name': name, + 'version': '1.0.0', + 'scheme': 'tms', + 'tiles': tiles, + 'minzoom': 0, + 'maxzoom': 22, + 'bounds': bounds + } \ No newline at end of file diff --git a/app/api/projects.py b/app/api/projects.py index c614f90e..cc0ea713 100644 --- a/app/api/projects.py +++ b/app/api/projects.py @@ -1,8 +1,13 @@ -from django.contrib.auth.models import User +from django.contrib.gis.db.models.functions import Envelope from rest_framework import serializers, viewsets +from rest_framework.generics import get_object_or_404 +from rest_framework.response import Response +from rest_framework.views import APIView +from django.contrib.gis.db.models import Extent from app import models from .tasks import TaskIDsSerializer +from .common import get_and_check_project, get_tiles_json class ProjectSerializer(serializers.ModelSerializer): @@ -29,4 +34,25 @@ class ProjectViewSet(viewsets.ModelViewSet): filter_fields = ('id', 'name', 'description', 'created_at') serializer_class = ProjectSerializer queryset = models.Project.objects.filter(deleting=False) - ordering_fields = '__all__' \ No newline at end of file + ordering_fields = '__all__' + + +class ProjectTilesJson(APIView): + queryset = models.Project.objects.filter(deleting=False) + + def get(self, request, pk=None): + """ + Returns a tiles.json file for consumption by a client + """ + project = get_and_check_project(request, pk) + task_ids = [task.id for task in project.tasks()] + extent = [0, 0, 0, 0] # TODO! world extent + + if len(task_ids) > 0: + # Extent of all orthophotos of all tasks for this project + extent = project.task_set.only('geom').annotate(geom=Envelope('orthophoto')).aggregate(Extent('geom'))['geom__extent'] + + json = get_tiles_json(project.name, [ + '/api/projects/{}/tasks/{}/tiles/{{z}}/{{x}}/{{y}}.png'.format(project.id, task_id) for task_id in task_ids + ], extent) + return Response(json) diff --git a/app/api/tasks.py b/app/api/tasks.py index e6606b15..efc08bcc 100644 --- a/app/api/tasks.py +++ b/app/api/tasks.py @@ -8,6 +8,7 @@ from rest_framework import status, serializers, viewsets, filters, exceptions, p from rest_framework.response import Response from rest_framework.decorators import detail_route from rest_framework.views import APIView +from .common import get_and_check_project, get_tiles_json from app import models, scheduler, pending_actions from nodeodm.models import ProcessingNode @@ -31,20 +32,6 @@ class TaskSerializer(serializers.ModelSerializer): exclude = ('processing_lock', 'console_output', 'orthophoto', ) -def get_and_check_project(request, project_pk, perms=('view_project',)): - ''' - Retrieves a project and raises an exeption if the current user - has no access to it. - ''' - try: - project = models.Project.objects.get(pk=project_pk) - for perm in perms: - if not request.user.has_perm(perm, project): raise ObjectDoesNotExist() - except ObjectDoesNotExist: - raise exceptions.NotFound() - return project - - class TaskViewSet(viewsets.ViewSet): """ A task represents a set of images and other input to be sent to a processing node. @@ -189,18 +176,9 @@ class TaskTilesJson(TaskNestedView): Returns a tiles.json file for consumption by a client """ task = self.get_and_check_task(request, pk, project_pk) - json = { - 'tilejson': '2.1.0', - 'name': task.name, - 'version': '1.0.0', - 'scheme': 'tms', - 'tiles': [ + json = get_tiles_json(task.name, [ '/api/projects/{}/tasks/{}/tiles/{{z}}/{{x}}/{{y}}.png'.format(task.project.id, task.id) - ], - 'minzoom': 0, - 'maxzoom': 22, - 'bounds': task.orthophoto.extent - } + ], task.orthophoto.extent) return Response(json) diff --git a/app/api/urls.py b/app/api/urls.py index 72294600..c9df57e2 100644 --- a/app/api/urls.py +++ b/app/api/urls.py @@ -1,5 +1,5 @@ from django.conf.urls import url, include -from .projects import ProjectViewSet +from .projects import ProjectViewSet, ProjectTilesJson from .tasks import TaskViewSet, TaskTiles, TaskTilesJson, TaskAssets from .processingnodes import ProcessingNodeViewSet from rest_framework_nested import routers @@ -15,6 +15,8 @@ urlpatterns = [ url(r'^', include(router.urls)), url(r'^', include(tasks_router.urls)), + url(r'projects/(?P[^/.]+)/tiles\.json$', ProjectTilesJson.as_view()), + url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)\.png$', TaskTiles.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/tiles\.json$', TaskTilesJson.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/download/(?P[^/.]+)/$', TaskAssets.as_view()), diff --git a/app/models.py b/app/models.py index e8c31689..e6e34f9d 100644 --- a/app/models.py +++ b/app/models.py @@ -61,7 +61,7 @@ class Project(models.Model): def __str__(self): return self.name - def tasks(self, pk=None): + def tasks(self): return self.task_set.only('id') class Meta: diff --git a/app/tests/test_api.py b/app/tests/test_api.py index 29a0a286..6abdbce1 100644 --- a/app/tests/test_api.py +++ b/app/tests/test_api.py @@ -197,6 +197,7 @@ class TestApi(BootTestCase): # - tiles API urls (permissions, 404s) # - assets download # - project deletion + # - project tiles API urls def test_processingnodes(self): client = APIClient()