Skip to content

Commit

Permalink
Geoprocessing API: Analyze request body as GeoJSON
Browse files Browse the repository at this point in the history
* Previously the analyze requests took a json body either like:
      { "area_of_interest": { "type": "MultiPolygon" ... more geojson }}
   or like:
      { "wkaoi": "mywkaoi__id" }

* Now the input should be either GeoJson in the request body:
      { "type": "MultiPolygon", ... more geojson }
   or take a query param with the wkaoi:
      `api/analyze/?wkaoi=mywkaoi__id`
  • Loading branch information
Alice Rottersman committed Aug 29, 2017
1 parent f2362b9 commit dbe7976
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 37 deletions.
32 changes: 26 additions & 6 deletions src/mmw/apps/geoprocessing_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@

from celery import chain

import json

from rest_framework.response import Response
from rest_framework import decorators
from rest_framework.permissions import AllowAny
from rest_framework.exceptions import ParseError

from django.utils.timezone import now
from django.core.urlresolvers import reverse

from apps.core.models import Job
from apps.core.tasks import save_job_error, save_job_result
from apps.modeling import geoprocessing
from apps.modeling.views import parse_input
from apps.modeling.views import parse_area_of_interest, parse_wkaoi
from apps.geoprocessing_api import tasks


Expand Down Expand Up @@ -56,7 +59,8 @@ def start_rwd(request, format=None):
def start_analyze_land(request, format=None):
user = request.user if request.user.is_authenticated() else None

area_of_interest, wkaoi = parse_input(request.data['analyze_input'])
wkaoi_str = request.query_params.get('wkaoi', None)
area_of_interest, wkaoi = parse_input(request.data, wkaoi_str)

geop_input = {'polygon': [area_of_interest]}

Expand All @@ -71,7 +75,8 @@ def start_analyze_land(request, format=None):
def start_analyze_soil(request, format=None):
user = request.user if request.user.is_authenticated() else None

area_of_interest, wkaoi = parse_input(request.data['analyze_input'])
wkaoi_str = request.query_params.get('wkaoi', None)
area_of_interest, wkaoi = parse_input(request.data, wkaoi_str)

geop_input = {'polygon': [area_of_interest]}

Expand All @@ -85,7 +90,9 @@ def start_analyze_soil(request, format=None):
@decorators.permission_classes((AllowAny, ))
def start_analyze_animals(request, format=None):
user = request.user if request.user.is_authenticated() else None
area_of_interest, __ = parse_input(request.data['analyze_input'])

wkaoi_str = request.query_params.get('wkaoi', None)
area_of_interest, __ = parse_input(request.data, wkaoi_str)

return start_celery_job([
tasks.analyze_animals.s(area_of_interest)
Expand All @@ -96,7 +103,9 @@ def start_analyze_animals(request, format=None):
@decorators.permission_classes((AllowAny, ))
def start_analyze_pointsource(request, format=None):
user = request.user if request.user.is_authenticated() else None
area_of_interest, __ = parse_input(request.data['analyze_input'])

wkaoi_str = request.query_params.get('wkaoi', None)
area_of_interest, __ = parse_input(request.data, wkaoi_str)

return start_celery_job([
tasks.analyze_pointsource.s(area_of_interest)
Expand All @@ -107,7 +116,9 @@ def start_analyze_pointsource(request, format=None):
@decorators.permission_classes((AllowAny, ))
def start_analyze_catchment_water_quality(request, format=None):
user = request.user if request.user.is_authenticated() else None
area_of_interest, __ = parse_input(request.data['analyze_input'])

wkaoi_str = request.query_params.get('wkaoi', None)
area_of_interest, __ = parse_input(request.data, wkaoi_str)

return start_celery_job([
tasks.analyze_catchment_water_quality.s(area_of_interest)
Expand Down Expand Up @@ -155,3 +166,12 @@ def start_celery_job(task_list, job_input, user=None):
},
headers={'Location': reverse('get_job', args=[task_chain.id])}
)

def parse_input(aoi_json=None, wkaoi_str=None):
if (aoi_json):
return parse_area_of_interest(aoi_json), None

if (wkaoi_str):
return parse_wkaoi(wkaoi_str)

raise ParseError(detail='Must supply either a GeoJSON request body, or a wkaoi url parameter.')
46 changes: 26 additions & 20 deletions src/mmw/apps/modeling/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from rest_framework.response import Response
from rest_framework import decorators, status
from rest_framework.exceptions import ParseError
from rest_framework.permissions import (AllowAny,
IsAuthenticated,
IsAuthenticatedOrReadOnly)
Expand Down Expand Up @@ -539,6 +540,27 @@ def get_layer_shape(table_code, id):
else:
return None

def parse_area_of_interest(area_of_interest, geojson=True):
# Area of Interest is expected to be GeoJSON in 4326
try:
shape = geoprocessing.to_one_ring_multipolygon(area_of_interest)
except:
raise ParseError(detail='Area of interest must be valid GeoJSON')

return json.dumps(shape) if geojson else shape


def parse_wkaoi(wkaoi, geojson=True):
# WKAoI is expected to be in the format '{table}__{id}'
table, id = wkaoi.split('__')
shape = get_layer_shape(table, id)

if shape:
result = shape if geojson else json.loads(shape)
return result, wkaoi
else:
raise ParseError(detail='Invalid wkaoi: {}'.format(wkaoi))


def parse_input(model_input, geojson=True):
"""
Expand All @@ -553,34 +575,18 @@ def parse_input(model_input, geojson=True):
as a dict instead of a JSON string.
"""
if not model_input:
return Response('model_input cannot be empty',
status=status.HTTP_400_BAD_REQUEST)
raise ParseError(detail='model_input cannot be empty')

if isinstance(model_input, basestring):
# Input is string, assumed JSON. Convert to dict before proceeding
model_input = json.loads(model_input)

if 'area_of_interest' in model_input:
# Area of Interest is expected to be GeoJSON in 4326
shape = geoprocessing.to_one_ring_multipolygon(
model_input['area_of_interest']
)

result = json.dumps(shape) if geojson else shape
result = parse_area_of_interest(model_input['area_of_interest'], geojson)
return result, None

if 'wkaoi' in model_input:
# WKAoI is expected to be in the format '{table}__{id}'
table, id = model_input['wkaoi'].split('__')
shape = get_layer_shape(table, id)

if shape:
result = shape if geojson else json.loads(shape)
return result, model_input['wkaoi']
else:
return Response('Invalid wkaoi: {}'.format(model_input['wkaoi']),
status=status.HTTP_400_BAD_REQUEST)
return parse_wkaoi(model_input['wkaoi'], geojson)

# If neither 'area_of_interest' nor 'wkaoi' were found, report
return Response('model_input must have either area_of_interest or wkaoi',
status=status.HTTP_400_BAD_REQUEST)
raise ParseError(detail='model_input must have either area_of_interest or wkaoi')
15 changes: 7 additions & 8 deletions src/mmw/js/src/analyze/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,13 @@ var AnalyzeTaskModel = coreModels.TaskModel.extend({
result = self.get('result');

if (aoi && !result && self.fetchAnalysisPromise === undefined) {
var analyzeInput = utils.isWKAoIValid(wkaoi) ?
JSON.stringify({ 'wkaoi': wkaoi }) :
JSON.stringify({ 'area_of_interest': aoi }),
promises = self.start({
postData: {
'analyze_input': analyzeInput
}
});
var isWkaoi = utils.isWKAoIValid(wkaoi),
taskHelper = {
contentType: 'application/json',
queryParams: isWkaoi ? { wkaoi: wkaoi } : null,
postData: isWkaoi ? null : JSON.stringify(aoi)
},
promises = self.start(taskHelper);

self.fetchAnalysisPromise = $.when(promises.startPromise,
promises.pollingPromise);
Expand Down
9 changes: 6 additions & 3 deletions src/mmw/js/src/core/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,11 +427,12 @@ var TaskModel = Backbone.Model.extend({
timeout: 45000,
},

url: function() {
url: function(queryParams) {
var encodedQueryParams = queryParams ? '?' + $.param(queryParams) : '';
if (this.get('job')) {
return '/' + this.get('taskType') + '/jobs/' + this.get('job') + '/';
} else {
return '/' + this.get('taskType') + '/' + this.get('taskName') + '/';
return '/' + this.get('taskType') + '/' + this.get('taskName') + '/' + encodedQueryParams;
}
},

Expand Down Expand Up @@ -463,8 +464,10 @@ var TaskModel = Backbone.Model.extend({
}
var self = this,
startDefer = self.fetch({
url: self.url(taskHelper.queryParams),
method: 'POST',
data: taskHelper.postData
data: taskHelper.postData,
contentType: taskHelper.contentType
}),
pollingDefer = $.Deferred();

Expand Down

0 comments on commit dbe7976

Please sign in to comment.