diff --git a/population-density/population_file_extraction.py b/population-density/population_file_extraction.py index 5053da75934e816f2712c3be86cfc18e7b6f483e..5682b47a53ed789094df97e44f383f70a73e4088 100644 --- a/population-density/population_file_extraction.py +++ b/population-density/population_file_extraction.py @@ -7,7 +7,7 @@ Author: Martin Schultz, FZ Juelich (04 May 2016) """ import numpy as np import os -from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_POPULATION_DATA +from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_POPULATION_DATA, PLOT_DATA import matplotlib.pyplot as plt @@ -58,7 +58,7 @@ def read_proxydata(filename, dummy=DEBUG and USE_DUMMY_POPULATION_DATA): logdata = data.copy() logdata[logdata <= 1.e-4] = 1.e-4 - if DEBUG: + if DEBUG and PLOT_DATA: plt.contourf(lonvec, latvec, np.log10(logdata)) plt.savefig('../plots/global_population_density.png') plt.close() diff --git a/stable_night_lights/nightlights_file_extraction.py b/stable_night_lights/nightlights_file_extraction.py index 65490e9e6dc0437f62efebac36d85cba72242002..6f65af80d207f12494d31da27cc4870d85ba6e0a 100644 --- a/stable_night_lights/nightlights_file_extraction.py +++ b/stable_night_lights/nightlights_file_extraction.py @@ -8,7 +8,7 @@ Author: Lukas Leufen, FZ Juelich (21th May 2019) """ import numpy as np import os -from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_STABLE_NIGHT_LIGHTS_DATA +from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_STABLE_NIGHT_LIGHTS_DATA, PLOT_DATA from django.contrib.gis.gdal import GDALRaster import matplotlib.pyplot as plt # import gdal @@ -52,7 +52,7 @@ def read_proxydata(filename, dummy=DEBUG and USE_DUMMY_STABLE_NIGHT_LIGHTS_DATA) boundingbox = [lon0, latvec.min(), lonvec.max(), lat0] # plot data - if DEBUG: + if DEBUG and PLOT_DATA: plt.contourf(lonvec, latvec, data) plt.savefig('../plots/global_nighttime_lights.png') plt.close() diff --git a/stable_night_lights/serializers.py b/stable_night_lights/serializers.py index 19a81b3affa6ee245d7e77ccc53341a3c6abe5c6..54d16e819d2dd827bced2f933bbd492441b9b9af 100644 --- a/stable_night_lights/serializers.py +++ b/stable_night_lights/serializers.py @@ -10,6 +10,7 @@ from rest_framework.serializers import BaseSerializer def get_provenance(obj): """construct provenance information on stable night lights dataset""" + # TODO: complete provenancen information prov = OrderedDict([ ('dataset_name', 'night time lights'), ('dataset_description', 'Year 2013 Nighttime lights brightness values from NOAA DMSP. Resolution? Morde ' @@ -39,6 +40,7 @@ class AggSerializer(BaseSerializer): vlength = len(val) except TypeError: vlength = 1 + # TODO: check units if vlength > 1: properties = OrderedDict([ ('agg_function', agg_function), diff --git a/toar_location_services/settings.py b/toar_location_services/settings.py index 7e2caab3fcedfc699529ebc30b2766192e67df13..7afb2ab71381361af789acce6e880834d0c02c24 100644 --- a/toar_location_services/settings.py +++ b/toar_location_services/settings.py @@ -43,7 +43,8 @@ INSTALLED_APPS = [ 'major-roads', 'population-density', 'topography-tandem-x', - 'stable_night_lights' + 'stable_night_lights', + 'wheat_production' ] MIDDLEWARE = [ @@ -145,4 +146,5 @@ USE_LOCAL_OVERPASS_DATA = False USE_DUMMY_POPULATION_DATA = True USE_DUMMY_TOPOGRAPHY_TANDEM_DATA = False USE_DUMMY_STABLE_NIGHT_LIGHTS_DATA = False - +USE_DUMMY_WHEAT_DATA = False +PLOT_DATA = False diff --git a/toar_location_services/urls.py b/toar_location_services/urls.py index 8fb7bc0f1e2da53c5d0e4538e45b54cd895afc98..0fd8d49bdd7897755f20e3c8f07e7d75fc98499b 100644 --- a/toar_location_services/urls.py +++ b/toar_location_services/urls.py @@ -8,5 +8,6 @@ urlpatterns = [ url(r'major-roads/', include('major-roads.urls'), name='major-roads'), url(r'population-density/', include('population-density.urls'), name='population-density-density'), url(r'topography-tandem-x/', include('topography-tandem-x.urls'), name='topography-tandem-x'), - url(r'stable_night_lights/', include('stable_night_lights.urls'), name='stable_night_lights') + url(r'stable_night_lights/', include('stable_night_lights.urls'), name='stable_night_lights'), + url(r'wheat_production/', include('wheat_production.urls'), name='wheat_production') ] diff --git a/toar_location_services/views.py b/toar_location_services/views.py index 075480761b458fa2bc8100b0d70ad7568431734b..a90aa29405f319a04c1be88af373a297009aded9 100644 --- a/toar_location_services/views.py +++ b/toar_location_services/views.py @@ -3,7 +3,6 @@ from rest_framework.views import APIView from rest_framework.response import Response - class LocationServicesRootView(APIView): """Local services REST services""" @@ -16,6 +15,7 @@ class LocationServicesRootView(APIView): ('population-density', request.build_absolute_uri()+'population-density/'), ('topography-tandem-x', request.build_absolute_uri()+'topography-tandem-x/'), ('stable_night_lights', request.build_absolute_uri()+'stable_night_lights/'), + ('wheat_production', request.build_absolute_uri()+'wheat_production/'), ]), ] # add admin view if staff user diff --git a/topography-tandem-x/topography_file_extraction.py b/topography-tandem-x/topography_file_extraction.py index 9454e127d6aa4d15db27f95ea8d13d6b447ed2a0..38afb60bdcc71ab22ef1631138788716204ee884 100644 --- a/topography-tandem-x/topography_file_extraction.py +++ b/topography-tandem-x/topography_file_extraction.py @@ -8,7 +8,7 @@ Author: Lukas Leufen, FZ Juelich (04 December 2018) """ import numpy as np import os -from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_TOPOGRAPHY_TANDEM_DATA +from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_TOPOGRAPHY_TANDEM_DATA, PLOT_DATA from django.contrib.gis.gdal import GDALRaster import matplotlib.pyplot as plt @@ -53,7 +53,7 @@ def read_proxydata(filename, dummy=DEBUG and USE_DUMMY_TOPOGRAPHY_TANDEM_DATA): # set metadata boundingbox = [lon0, latvec.min(), lonvec.max(), lat0] - if DEBUG: + if DEBUG and PLOT_DATA: plt.contourf(lonvec, latvec, data) plt.savefig('../plots/topography.png') plt.close() diff --git a/wheat_production/__init__.py b/wheat_production/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/wheat_production/admin.py b/wheat_production/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/wheat_production/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/wheat_production/apps.py b/wheat_production/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..aaa8c337714c876d7da653a051134d10faaeb78e --- /dev/null +++ b/wheat_production/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class WheatProductionConfig(AppConfig): + name = 'wheat_production' diff --git a/wheat_production/migrations/__init__.py b/wheat_production/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/wheat_production/models.py b/wheat_production/models.py new file mode 100644 index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91 --- /dev/null +++ b/wheat_production/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/wheat_production/serializers.py b/wheat_production/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..2ddacd511ee7215e83c93b6aaf067c51f97f919d --- /dev/null +++ b/wheat_production/serializers.py @@ -0,0 +1,82 @@ +"""serializer for wheat production data +""" + +from collections import OrderedDict +import datetime as dt +from rest_framework.serializers import BaseSerializer + + +# helper functions + +def get_provenance(obj): + """construct provenance information on wheat production dataset""" + # TODO: Fill right provenance information + prov = OrderedDict([ + ('dataset_name', '?'), + ('dataset_description', """Wheat production values for the globe at 5 arc minute resolution. The data are in + units of production (irrigated + non-irrigated) in thousand tonnes. \nThe data were downloaded from the GAEZ + data portal ( http://gaez.fao.org/Main.html# ) and then output to ascii text format by:\nDr Katrina Sharps\n + Centre for Ecology & Hydrology, Environment Centre Wales, UK\nTel. + 44 (0)1248 374518 (direct)\n + Tel. + 44 (0)1248 374500 (reception)\nE-mail: katshar@ceh.ac.uk"""), + ('data_source', """Dr Katrina Sharps\nCentre for Ecology & Hydrology, Environment Centre Wales, UK\n + Tel. + 44 (0)1248 374518 (direct)\nTel. + 44 (0)1248 374500 (reception)\nE-mail: katshar@ceh.ac.uk"""), + ('datacenter_url', 'http://gaez.fao.org/Main.html'), + ('download_date', '?'), + ('timestamp', dt.datetime.now().isoformat()) + ]) + return prov + + +# serializer classes +class AggSerializer(BaseSerializer): + """ see http://www.django-rest-framework.org/api-guide/serializers/#baseserializer """ + + def to_representation(self, obj): + """takes dictionary-like obj and returns geojson compliant structure""" + agg_function = obj['agg_function'] + val = obj[agg_function] + + # build GeoJSON response with 'provenance' extension + # ToDo (probably in views): change content-type to application/vnd.geo+json + # ToDo: enable support for different output formats + # format properties depending on 'by_direction' (vector or not) + try: + vlength = len(val) + except TypeError: + vlength = 1 + # TODO: check units below + if vlength > 1: + properties = OrderedDict([ + ('agg_function', agg_function), + ('many', True), + (agg_function, OrderedDict([ + (d, v) for d, v in zip(obj['direction'], val) + ])), + ('units', 'kt'), + ('radius', obj['radius']), + ]) + if obj['direction'] is None: + properties.pop('direction') + else: + properties = OrderedDict([ + ('agg_function', agg_function), + ('many', False), + (agg_function, val), + ('units', 'kt'), + ('radius', obj['radius']), + ('direction', obj['direction']), + ]) + if obj['direction'] is None: + properties.pop('direction') + + response = OrderedDict([ + ('type', 'Feature'), + ('geometry', OrderedDict([ + ('type', 'Point'), + ('coordinates', [obj['lon'], obj['lat']]), + ])), + ('properties', properties), + ('provenance', get_provenance(obj)), + ]) + + return response diff --git a/wheat_production/tests.py b/wheat_production/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..1e0e74f059a55bd8bbf617f1ccb2fd0bcc3050ee --- /dev/null +++ b/wheat_production/tests.py @@ -0,0 +1,10 @@ +from django.test import TestCase + +# Create your tests here. + +from wheat_file_extraction import read_proxydata + + +FILENAME = "wheat.txt" + +read_proxydata(FILENAME, dummy=False) diff --git a/wheat_production/urls.py b/wheat_production/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..8b0dbc25b9c680646c3b023897bc7c349228820d --- /dev/null +++ b/wheat_production/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import url +from .views import WheatView + +urlpatterns = [ + url(r'^$', WheatView.as_view()), + ] diff --git a/wheat_production/views.py b/wheat_production/views.py new file mode 100644 index 0000000000000000000000000000000000000000..7134e695b5d2bd3b7b802ecf9141b4f79cf12147 --- /dev/null +++ b/wheat_production/views.py @@ -0,0 +1,94 @@ +import datetime as dt +import numpy as np +from collections import OrderedDict +from rest_framework.views import APIView +from rest_framework.response import Response + +from toar_location_services.settings import DEBUG +from .wheat_file_extraction import read_proxydata +from .serializers import AggSerializer +from utils.views_commons import get_query_params, get_agg_function +from utils.geoutils import Directions +from utils.extraction_tools import extract_value, extract_value_stats + +FILENAME = "wheat.txt" + +lonvec, latvec, data, datainfo = read_proxydata(FILENAME) + +if DEBUG: + print("File %s successfully loaded" % FILENAME, datainfo) + + +class WheatView(APIView): + + def _extract(self, lat, lon, radius, agg, direction, by_direction): + """perform actual extraction of desired quantity""" + print('**by_direction:', by_direction, '** radius, agg = ', radius, agg) + if agg is not None and radius is not None and radius > 0.: + agg_function = get_agg_function(agg) + min_angle = None + max_angle = None + if by_direction: + result = np.zeros((16,)) + direction = Directions.LABELS + for i, d in enumerate(Directions.LABELS): + min_angle, max_angle = Directions.edges(d) + # ToDo: once we serve the data via rasdaman the calls with directions should use + # a polygon query for efficiency reasons. + result[i] = extract_value_stats(lonvec, latvec, data, lon, lat, default_value=-999., + out_of_bounds_value=0., min_valid=0., max_valid=2.e6, + radius=radius, min_angle=min_angle, max_angle=max_angle, + agg=agg_function) + else: + if direction is not None: + min_angle, max_angle = Directions.edges(direction) + # ToDo: once we serve the data via rasdaman the calls with directions should use + # a polygon query for efficiency reasons. + result = extract_value_stats(lonvec, latvec, data, lon, lat, default_value=-999., + out_of_bounds_value=0., min_valid=0., max_valid=2.e6, + radius=radius, min_angle=min_angle, max_angle=max_angle, + agg=agg_function) + else: + agg = 'value' + result = extract_value(lonvec, latvec, data, lon, lat, default_value=-999., + out_of_bounds_value=0., min_valid=0., max_valid=2.e6) + # return data, also return agg and direction as they may have been overwritten + return result, agg, direction + + def get(self, request, format=None): + """process GET requests for wheat_production app + + returns a Geo-JSON response with information about the wheat production at or + around a point location. + + required arguments: + lat: latitude in degrees_north + lng: longitude in degrees_east (can be either -180 to 180 or 0 to 360) + + optional arguments: + radius: search radius in m. See settings.py for default and max allowed values. + Without 'agg', the radius defaults to None and the wheat production at the + point location is returned. + agg: method of aggregation for data around point location. See settings.py for + default method. Only evaluated if radius > 0. Allowed methods are mean, + min, max, median, and NN-percentile (see views_commons.py) + direction: return data aggregation in one direction (wind sector) only. + Direction must be given as wind sector (e.g. 'N', 'NNE', 'NE', etc.). + by_direction: if True, data are returned as vector with one value aggregated + over each of 16 wind directions. + """ + lat, lon, radius, agg, direction, by_direction = get_query_params(request.query_params, + ['lat', 'lon', 'radius', 'agg', 'direction', 'by_direction']) + result, agg, direction = self._extract(lat, lon, radius, agg, direction, by_direction) + + rawdata = OrderedDict([ + ("lat", lat), + ("lon", lon), + ("radius", radius), + ("direction", direction), + ("agg_function", agg), + (agg, result), + ]) + response = AggSerializer(rawdata).data + return Response(response) + diff --git a/wheat_production/wheat_file_extraction.py b/wheat_production/wheat_file_extraction.py new file mode 100644 index 0000000000000000000000000000000000000000..c855e51796a4c7b9383cb42a319ea87ee4132cd3 --- /dev/null +++ b/wheat_production/wheat_file_extraction.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +""" +Import wheat production from .txt-file + +Author: Lukas Leufen, FZ Juelich (24th May 2019) +""" + +import numpy as np +import os +from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_WHEAT_DATA, PLOT_DATA +import matplotlib.pyplot as plt + + +def read_proxydata(filename, dummy=DEBUG and USE_DUMMY_WHEAT_DATA): + """Read the ascii file and return the data array together with + some dataset properties for use in the extraction routines. + + filename: name of data file (wheat.txt) + dummy: if true a small set of dummy data are returned to speed up development of other services + """ + + if dummy: + return create_dummy_data() + + if DEBUG: + print("DATA_DIR = ", DATA_DIR, "...") + print("Opening ", os.path.join(DATA_DIR, filename), "...") + + with open(os.path.join(DATA_DIR, filename), "r") as dataset: + tok, cols = dataset.readline().split() + tok, rows = dataset.readline().split() + cols = int(cols) + rows = int(rows) + tok, lon0 = dataset.readline().split() + tok, lat0 = dataset.readline().split() + tok, dlon = dataset.readline().split() + lon0 = float(lon0) + lat0 = float(lat0) + dlon = float(dlon) + dlat = dlon + tok, missval = dataset.readline().split() + + # construct data array and lonvec, latvec + data = np.zeros((rows, cols), dtype='f4') + lonvec = np.linspace(lon0, lon0 + cols * dlon, cols) + latvec = np.linspace(lat0, lat0 + rows * dlat, rows) + + # data are flipped, therefore reverse latitudes + # trick from http://stackoverflow.com/questions/6771428/most-efficient-way-to-reverse-a-numpy-array + latvec = np.fliplr(np.atleast_2d((latvec)))[0] + + # read actual data + for i, line in enumerate(dataset): + row = np.array([float(x) for x in line.split()], dtype='f4') + data[i, :] = row + + # correct missing values + data[data == float(missval)] = np.nan + + logdata = data.copy() + logdata[logdata <= 1.e-4] = 1.e-4 + + if DEBUG and PLOT_DATA: + plt.contourf(lonvec, latvec, np.log10(logdata)) + plt.savefig('../plots/global_wheat_production.png') + plt.close() + + # set metadata + boundingbox = [lon0, lat0, lonvec.max(), latvec.max()] + + datainfo = {'size': (rows, cols), 'resolution': (np.abs(dlon), np.abs(dlat)), + 'boundingbox': boundingbox} + return lonvec, latvec, data, datainfo + + +def create_dummy_data(): + """generate some small dummy data set for testing of other services + This avoids loading of a large file + Eventually this should become obsolete when we have rasdaman running...""" + lonvec = np.array([4., 5., 6.]) + latvec = np.array([52., 53., 54.]) + data = np.array([[1., 10., 11.], [0., 2.5, 3.], [6., 7., 8.]]) + datainfo = {'size': (3, 3), 'resolution': (1., 1.), + 'boundingbox': [4., 52., 6., 54.]} + msg = "#DEBUG: Using dummy data for wheat production" + if DEBUG: + print(msg) + else: + raise UserWarning(msg) + return lonvec, latvec, data, datainfo