Skip to content
Snippets Groups Projects
Commit 514d79a3 authored by lukas leufen's avatar lukas leufen
Browse files

Merge branch 'lukas_issue008_feat_new-app-climatic-zones' into 'develop'

Lukas issue008 feat new app climatic zones

See merge request toar/toar-location-services!14
parents 543c16e1 5092355a
No related branches found
No related tags found
1 merge request!14Lukas issue008 feat new app climatic zones
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class ClimaticZonesConfig(AppConfig):
name = 'climatic_zones'
#!/usr/bin/python
"""
Load stable night lights data from file or use dummy data.
..todo: implementation of RASDAMAN access
Author: Lukas Leufen, FZ Juelich (4th June 2019)
"""
import numpy as np
import os
from toar_location_services.settings import DATA_DIR, DEBUG, USE_DUMMY_CLIMATIC_ZONES_DATA, PLOT_DATA
from django.contrib.gis.gdal import GDALRaster
import matplotlib.pyplot as plt
# import gdal
# from gdalconst import GA_ReadOnly
def read_proxydata(filename, dummy=DEBUG and USE_DUMMY_CLIMATIC_ZONES_DATA, plot=False):
"""
PLACEHOLDER
expand this routine, if you want to use real and not dummy data
"""
if dummy:
return create_dummy_data()
CLIMATIC_ZONES_FILE = os.path.join(DATA_DIR, filename)
if DEBUG:
print("DATA_DIR = ", DATA_DIR, "...")
print("Opening ", CLIMATIC_ZONES_FILE, "...")
with open(CLIMATIC_ZONES_FILE, "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='f2')
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([int(x) for x in line.split()], dtype='f2')
data[i, :] = row
# correct missing values
data[data == float(missval)] = np.nan
if (DEBUG and PLOT_DATA) or plot:
fig, ax = plt.subplots()
cmap = plt.cm.get_cmap('tab20')
cax = ax.contourf(lonvec, latvec, data, levels=np.arange(-.5, 13).tolist(), cmap=cmap)
cbar = fig.colorbar(cax, ticks=np.arange(0, 13).tolist())
cbar.ax.set_yticklabels(["Water", "Warm Temperate Moist", "Warm Temperate Dry", "Cool Temperate Moist",
"Cool Temperate Dry", "Polar Moist", "Polar Dry", "Boreal Moist", "Boreal Dry",
"Tropical Montane", "Tropical Wet", "Tropical Moist", "Tropical Dry"])
plt.savefig('../plots/global_climatic_zone.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([[100., 100., 105.], [200., 250., 300.], [600., 700., 800.]])
datainfo = {'size': (3, 3), 'resolution': (1., 1.),
'boundingbox': [4., 52., 6., 54.]}
msg = "#DEBUG: Using dummy data for stable night lights!"
if DEBUG:
print(msg)
else:
raise UserWarning(msg)
return lonvec, latvec, data, datainfo
from django.db import models
# Create your models here.
"""serializer for climate zones
"""
from collections import OrderedDict
import datetime as dt
from rest_framework.serializers import BaseSerializer
# helper functions
def get_provenance(obj):
"""construct provenance information on climatic zones dataset"""
# TODO: Complete provenance information
prov = OrderedDict([
('dataset_name', '?'),
('dataset_description', """RClimatic zones in ~9 km resolution were obtained from the eusoils project.\n
The data were output to ascii text format by:\n
Dr Katrina Sharps\n
Centre for Ecology & Hydrology, Environment Centre Wales, UK\n
Tel. + 44 (0)1248 374518 (direct)\n
Tel. + 44 (0)1248 374500 (reception)\n
E-mail: katshar@ceh.ac.uk\n\nThe climate zones are as follows:\n
1 Warm Temperate Moist\n
2 Warm Temperate Dry\n
3 Cool Temperate Moist\n
4 Cool Temperate Dry\n
5 Polar Moist\n
6 Polar Dry\n
7 Boreal Moist\n
8 Boreal Dry\n
9 Tropical Montane\n
10 Tropical Wet\n
11 Tropical Moist\n
12 Tropical Dry"""),
('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://eusoils.jrc.ec.europa.eu/projects/RenewableEnergy/'),
('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', '?'),
('radius', obj['radius']),
])
if obj['direction'] is None:
properties.pop('direction')
else:
properties = OrderedDict([
('agg_function', agg_function),
('many', False),
(agg_function, val),
('units', '?'),
('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
from django.test import TestCase
# Create your tests here.
from climatic_zones_file_extraction import read_proxydata
from toar_location_services import settings
FILENAME = "climate_ascii.txt"
settings.PLOT_DATA = True
read_proxydata(FILENAME, dummy=False, plot=True)
from django.conf.urls import url
from .views import ClimateView
urlpatterns = [
url(r'^$', ClimateView.as_view()),
]
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 .climatic_zones_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 = "climate_ascii.txt"
lonvec, latvec, data, datainfo = read_proxydata(FILENAME)
if DEBUG:
print("File %s successfully loaded" % FILENAME, datainfo)
class ClimateView(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 rice_production app
returns a Geo-JSON response with information about the rice 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 rice 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)
......@@ -45,7 +45,8 @@ INSTALLED_APPS = [
'topography-tandem-x',
'stable_night_lights',
'wheat_production',
'rice_production'
'rice_production',
'climatic_zones'
]
MIDDLEWARE = [
......@@ -149,4 +150,5 @@ USE_DUMMY_TOPOGRAPHY_TANDEM_DATA = False
USE_DUMMY_STABLE_NIGHT_LIGHTS_DATA = False
USE_DUMMY_WHEAT_DATA = False
USE_DUMMY_RICE_DATA = False
USE_DUMMY_CLIMATIC_ZONES_DATA = False
PLOT_DATA = False
......@@ -10,5 +10,6 @@ urlpatterns = [
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'wheat_production/', include('wheat_production.urls'), name='wheat_production'),
url(r'rice_production/', include('rice_production.urls'), name='rice_production')
url(r'rice_production/', include('rice_production.urls'), name='rice_production'),
url(r'climatic_zones/', include('climatic_zones.urls'), name='climatic_zones')
]
......@@ -16,7 +16,8 @@ class LocationServicesRootView(APIView):
('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/'),
('rice_production', request.build_absolute_uri()+'rice_production/')
('rice_production', request.build_absolute_uri()+'rice_production/'),
('climatic_zones', request.build_absolute_uri()+'climatic_zones/')
]),
]
# add admin view if staff user
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment