Skip to content

Commit

Permalink
Merge branch 'release/1.33.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
rajadain committed Mar 10, 2022
2 parents 5b7dccc + 3e779ed commit 79a985a
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 49 deletions.
19 changes: 19 additions & 0 deletions src/mmw/apps/geoprocessing_api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@
type=TYPE_STRING,
)

HUC = Parameter(
'huc',
IN_QUERY,
description='The Hydrologic Unit Code (HUC) of the area of interest. '
'Should be an 8, 10, or 12 digit string of numbers, e.g. '
'"020402031008" will analyze the HUC-12 City of Philadelphia-'
'Schuylkill River.',
type=TYPE_STRING,
)

MULTIPOLYGON = Schema(
title='Area of Interest',
description='A valid single-ringed Multipolygon GeoJSON '
Expand Down Expand Up @@ -236,6 +246,15 @@
'Format "table__id", eg. "huc12__55174" will analyze '
'the HUC-12 City of Philadelphia-Schuylkill River.',
),
'huc': Schema(
title='Hydrologic Unit Code',
type=TYPE_STRING,
example='020402031008',
description='The Hydrologic Unit Code (HUC) of the area of '
'interest. Should be an 8, 10, or 12 digit string of '
'numbers, e.g. "020402031008" will analyze the HUC-12 '
'City of Philadelphia-Schuylkill River.',
),
'layer_overrides': LAYER_OVERRIDES,
},
)
Expand Down
73 changes: 42 additions & 31 deletions src/mmw/apps/geoprocessing_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
multi_mapshed,
nlcd_streams)
from apps.modeling.serializers import AoiSerializer
from apps.modeling.views import _parse_input as _parse_modeling_input

from apps.geoprocessing_api import exceptions, schemas, tasks
from apps.geoprocessing_api.permissions import AuthTokenSerializerAuthentication # noqa
Expand Down Expand Up @@ -246,7 +245,8 @@ def start_rwd(request, format=None):

@swagger_auto_schema(method='post',
manual_parameters=[schemas.NLCD_YEAR,
schemas.WKAOI],
schemas.WKAOI,
schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -416,7 +416,7 @@ def start_analyze_land(request, nlcd_year, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

geop_input = {'polygon': [area_of_interest]}

Expand All @@ -436,7 +436,7 @@ def start_analyze_land(request, nlcd_year, format=None):


@swagger_auto_schema(method='post',
manual_parameters=[schemas.WKAOI],
manual_parameters=[schemas.WKAOI, schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -518,7 +518,7 @@ def start_analyze_soil(request, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

geop_input = {'polygon': [area_of_interest]}

Expand Down Expand Up @@ -647,7 +647,7 @@ def start_analyze_streams(request, datasource, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

if datasource not in settings.STREAM_TABLES:
raise ValidationError(f'Invalid stream datasource: {datasource}.'
Expand All @@ -666,7 +666,7 @@ def start_analyze_streams(request, datasource, format=None):


@swagger_auto_schema(method='post',
manual_parameters=[schemas.WKAOI],
manual_parameters=[schemas.WKAOI, schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -737,15 +737,15 @@ def start_analyze_animals(request, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

return start_celery_job([
tasks.analyze_animals.s(area_of_interest)
], area_of_interest, user)


@swagger_auto_schema(method='post',
manual_parameters=[schemas.WKAOI],
manual_parameters=[schemas.WKAOI, schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -797,15 +797,15 @@ def start_analyze_pointsource(request, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

return start_celery_job([
tasks.analyze_pointsource.s(area_of_interest)
], area_of_interest, user)


@swagger_auto_schema(method='post',
manual_parameters=[schemas.WKAOI],
manual_parameters=[schemas.WKAOI, schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -884,15 +884,15 @@ def start_analyze_catchment_water_quality(request, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

return start_celery_job([
tasks.analyze_catchment_water_quality.s(area_of_interest)
], area_of_interest, user)


@swagger_auto_schema(method='post',
manual_parameters=[schemas.WKAOI],
manual_parameters=[schemas.WKAOI, schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -946,7 +946,7 @@ def start_analyze_climate(request, format=None):
"""
user = request.user if request.user.is_authenticated else None

area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)
shape = [{'id': wkaoi or geoprocessing.NOCACHE, 'shape': area_of_interest}]

return start_celery_job([
Expand All @@ -956,7 +956,7 @@ def start_analyze_climate(request, format=None):


@swagger_auto_schema(method='post',
manual_parameters=[schemas.WKAOI],
manual_parameters=[schemas.WKAOI, schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -1012,7 +1012,7 @@ def start_analyze_terrain(request, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

geop_input = {'polygon': [area_of_interest]}

Expand All @@ -1023,7 +1023,7 @@ def start_analyze_terrain(request, format=None):


@swagger_auto_schema(method='post',
manual_parameters=[schemas.WKAOI],
manual_parameters=[schemas.WKAOI, schemas.HUC],
request_body=schemas.MULTIPOLYGON,
responses={200: schemas.JOB_STARTED_RESPONSE})
@decorators.api_view(['POST'])
Expand Down Expand Up @@ -1148,7 +1148,7 @@ def start_analyze_protected_lands(request, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

geop_input = {'polygon': [area_of_interest]}

Expand Down Expand Up @@ -1316,7 +1316,7 @@ def start_analyze_drb_2100_land(request, key=None, format=None):
</details>
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_input(request)
area_of_interest, wkaoi = _parse_analyze_input(request)

errs = []
if not key:
Expand All @@ -1326,8 +1326,8 @@ def start_analyze_drb_2100_land(request, key=None, format=None):
'", "'.join(settings.DREXEL_FAST_ZONAL_API['keys'])))

# A little redundant since GeoJSON -> GEOSGeometry is already done once
# within _parse_input, but it is not returned from there and changing
# that API could break many things.
# within _parse_analyze_input, but it is not returned from there and
# changing that API could break many things.
geom = GEOSGeometry(area_of_interest, srid=4326)

# In the front-end, we use DRB_SIMPLE_PERIMETER. This is sent from the
Expand Down Expand Up @@ -1367,7 +1367,7 @@ def start_modeling_worksheet(request, format=None):
to get the actual Excel files.
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, _ = _parse_input(request)
area_of_interest, _ = _parse_analyze_input(request)

return start_celery_job([
tasks.collect_worksheet.s(area_of_interest),
Expand All @@ -1394,15 +1394,16 @@ def start_modeling_gwlfe_prepare(request, format=None):
By default, NLCD 2019 and NHD High Resolution Streams are used to prepare
the input payload. This can be changed using the `layer_overrides` option.
Only one of `area_of_interest` or `wkaoi` should be provided. If both are
given, the `area_of_interest` will be used.
Only one of `area_of_interest`, `wkaoi`, or `huc` should be provided.
If multiple are given, the `area_of_interest` will be used first, then
`wkaoi`, then `huc`.
The `result` should be used with the gwlf-e/run endpoint, by sending at as
the `input`. Alternatively, the `job` UUID can be used as well by sending
it as the `job_uuid`.
"""
user = request.user if request.user.is_authenticated else None
area_of_interest, wkaoi = _parse_modeling_input(request.data)
area_of_interest, wkaoi = _parse_modeling_input(request)

layer_overrides = request.data.get('layer_overrides', {})

Expand Down Expand Up @@ -1519,10 +1520,20 @@ def start_celery_job(task_list, job_input, user=None, link_error=True):
)


def _parse_input(request):
wkaoi = request.query_params.get('wkaoi', None)
serializer = AoiSerializer(data={'area_of_interest': request.data,
'wkaoi': wkaoi})
def _parse_analyze_input(request):
return _parse_aoi(data={'area_of_interest': request.data,
'wkaoi': request.query_params.get('wkaoi'),
'huc': request.query_params.get('huc')})


def _parse_modeling_input(request):
return _parse_aoi(request.data)


def _parse_aoi(data):
serializer = AoiSerializer(data=data)
serializer.is_valid(raise_exception=True)
return (serializer.validated_data.get('area_of_interest'),
wkaoi)
area_of_interest = serializer.validated_data.get('area_of_interest')
wkaoi = serializer.validated_data.get('wkaoi')

return area_of_interest, wkaoi
29 changes: 29 additions & 0 deletions src/mmw/apps/modeling/calcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from copy import deepcopy
from datetime import datetime, timedelta

from rest_framework.exceptions import NotFound, ValidationError

from django.conf import settings
from django.db import connection

Expand Down Expand Up @@ -189,6 +191,33 @@ def get_weather_simulation_for_project(project, category):
}, errs


def wkaoi_from_huc(huc):
huc_len = len(huc)
if huc_len not in [8, 10, 12]:
raise ValidationError(f'Specified HUC {huc} is {huc_len} digits. '
'Must be 8, 10, or 12.')

if huc_len == 8:
huc_col = 'huc08'
else:
huc_col = f'huc{huc_len}'

sql = f'''
SELECT id
FROM boundary_{huc_col}
WHERE {huc_col} = %s
'''

with connection.cursor() as cursor:
cursor.execute(sql, [huc])
row = cursor.fetchone()
if not row:
raise NotFound(f'No shape found for HUC {huc}')

wkaoi = f'huc{huc_len}__{row[0]}'
return wkaoi


def split_into_huc12s(code, id):
layer = _get_boundary_layer_by_code(code)
if not layer or 'table_name' not in layer:
Expand Down
21 changes: 15 additions & 6 deletions src/mmw/apps/modeling/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from apps.export.serializers import HydroShareResourceSerializer
from apps.modeling.models import Project, Scenario, WeatherType
from apps.modeling.validation import validate_aoi
from apps.modeling.calcs import get_layer_shape
from apps.modeling.calcs import get_layer_shape, wkaoi_from_huc
from apps.user.serializers import UserSerializer
from apps.core.models import Job

Expand Down Expand Up @@ -207,22 +207,28 @@ def to_internal_value(self, data):
to its validated, geojson string representation, and 'wkaoi' as None.
If the input has a 'wkaoi' key, its shape is pulled from the
appropriate database, and returned as 'area_of_interest' with the
value of 'wkaoi' returned unchanged.
value of 'wkaoi' returned unchanged. If the input has a 'huc' key, then
we use it to look up the 'wkaoi'.
Args:
data: Only one of the keys is necessary
{
'area_of_interest': { <geojson dict or string> }
'wkaoi': '{table}__{id}',
'huc': '<huc8, huc10, or huc12 id>',
}
"""
wkaoi = data.get('wkaoi', None)
huc = data.get('huc', None)
aoi = data.get('area_of_interest', None)

if (not aoi and not wkaoi):
raise ValidationError(detail='Must supply either ' +
if (not aoi and not wkaoi and not huc):
raise ValidationError(detail='Must supply exactly one of: ' +
'the area of interest (GeoJSON), ' +
'or a WKAoI ID.')
'a WKAoI ID, or a HUC.')

if (huc and not wkaoi):
wkaoi = wkaoi_from_huc(huc)

if (wkaoi and not aoi):
try:
Expand All @@ -234,9 +240,12 @@ def to_internal_value(self, data):
if (not aoi):
raise ValidationError(detail=f'Invalid wkaoi: {wkaoi}')

huc = aoi['properties'].get('huc')

aoi_field = MultiPolygonGeoJsonField().to_internal_value(aoi)

return {
'area_of_interest': aoi_field,
'wkaoi': wkaoi
'wkaoi': wkaoi,
'huc': huc,
}
20 changes: 10 additions & 10 deletions src/mmw/js/src/modeling/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ module.exports = {
},
// In sync with apps.modeling.models.WeatherType.simulations
Simulations: [
// {
// group: 'Recent Weather',
// items: [
// {
// name: 'NASA_NLDAS_2000_2019',
// label: 'NASA NLDAS 2000-2019',
// },
// ],
// in_drb: true,
// },
{
group: 'Recent Weather',
items: [
{
name: 'NASA_NLDAS_2000_2019',
label: 'NASA NLDAS 2000-2019',
},
],
in_drb: true,
},
{
group: 'Future Weather Simulations',
items: [
Expand Down
Loading

0 comments on commit 79a985a

Please sign in to comment.