diff --git a/README.md b/README.md index 281679ab22e9e11ad18cad8fd21a965483887127..bb776a15ee4ba9192f72873367cec05852b547b6 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ be harvested, e.g. for inclusion into the TOAR database: **Valid `SERVICE_NAME`s are:** -`major-roads`: returns distance to major roads using the OpenStreetMap Overpass +`major_roads`: returns distance to major roads using the OpenStreetMap Overpass interface -`population-density`: returns aggregate statistics of population density from +`population_density`: returns aggregate statistics of population density from the GPW3.1 1 km dataset -`topography-tandem-x`: returns aggregate statistics of topography from TanDEM-X +`topography_tandem_x`: returns aggregate statistics of topography from TanDEM-X 90m dataset (provided by German Aerospace Center DLR) **Arguments:** @@ -39,17 +39,15 @@ _Note:_ You can also write `long` or `longitude` instead of `lon`, or all services. For example, major-raods will always return the nearest distance to a road. `NN-percentile` can also be written as `NN%-ile`. -`direction`=N|NEN|NE|ENE|E|ESE|SE|SES|S|SWS|SW|WSW|W|WNW|NW|NWN: restrict - search and/or aggregation to specified wind sector. - -`by_direction`=True: return result for each wind sector. The result will be an - array (or dict??) instead of a single value. +`direction`=N|NEN|NE|ENE|E|ESE|SE|SES|S|SWS|SW|WSW|W|WNW|NW|NWN|all|true: restrict + search and/or aggregation to specified wind sector. If set to all or true, return + result for each wind sector Individual services may have additional optional URL parameters. ## 2. Individual services -### 2.1 major-roads +### 2.1 major_roads This services performs a query to the Overpass Web API of Open Street Map and returns the distance @@ -60,7 +58,7 @@ and highway name if available. **OPTIONS:** -The `radius`, `direction`, and `by_direction` arguments work as described above. +The `radius` and `direction` arguments work as described above. The `agg` option is ignored. `highway_types`=string: specify the Open Street Map highway types to be included @@ -68,12 +66,12 @@ in the search. Default is to ask for _'motorway,trunk,primary,secondary,tertiary'_. Highway types must be specified as comma-separated string. -The *major-roads* service also has a `map` variant where +The *major_roads* service also has a `map` variant where OpenStreetmap tiles are displayed with an overlay of the search point and the roads surrounding it. The url for this is -[base-url]/major-roads/map/[?Options]. +[base-url]/major_roads/map/[?Options]. -### 2.2 population-density +### 2.2 population_density This services queries a local copy of the Global Population of the World, version 3.1 dataset (see [SEDAC](https://doi.org/10.7927/H4XK8CG2)) and @@ -85,12 +83,12 @@ the `lat` and `lon` arguments or aggregated over a given `radius`. All arguments can be used as described above. There are no service-specific arguments. -### 2.3 topography-tandem-x +### 2.3 topography_tandem_x -This services queries a local copy of TANDEM-X radar topography data from the DLR, Germany. +This services queries a local copy of TANDEM-X radar topography data from the [DLR, Germany](ftpes://tandemx-90m.dlr.de). The dataset has 90 m resolution. The current local copy only covers the region of Harz, Germany. -The service returns the topgraphic altitude at the point location specified by +The service returns the topographic altitude at the point location specified by the `lat` and `lon` arguments or aggregated over a given `radius`. **OPTIONS:** @@ -98,18 +96,6 @@ the `lat` and `lon` arguments or aggregated over a given `radius`. All arguments can be used as described above. There are no service-specific arguments. -### 2.3 topography-tandem-x - -This service works on a local file with topography data previously downloaded from -[DLR ftp-server](ftpes://tandemx-90m.dlr.de). It returns the height of topography at -the point location specified by the `lat` and `lon` arguments or aggregrated over -given `radius`. - -**OPTIONS:** - -All arguments can be used as described above. There are no service-specific -arguments. - ## 3. Installation ### ubuntu diff --git a/climatic_zones/apps.py b/climatic_zones/apps.py index a9fa3e119d1d9c05c5d92fb4ec82268c196dd494..6eec2469900a264872fe298f2e0c8a6bcf3a45a7 100644 --- a/climatic_zones/apps.py +++ b/climatic_zones/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class ClimaticZonesConfig(AppConfig): +class ClimaticZonesConfig(AppConfig): # pragma: no cover name = 'climatic_zones' diff --git a/climatic_zones/views.py b/climatic_zones/views.py index dcee06403e7178537af0c58866f5d70d2c70d39f..72096c844010f47fa66b1e26ce0d3d370a67fe3e 100644 --- a/climatic_zones/views.py +++ b/climatic_zones/views.py @@ -31,4 +31,5 @@ class ClimateView(APIView, CommonViews): CommonViews.__init__(self, **opts) self.keep_only_given_agg_allowed(dict(maxclass=most_common_value, frequency=relative_frequency), - frequency=dict(bins=13)) + frequency=dict(bins=13, + missing_value=self.default_value)) diff --git a/htap_regions_tier1/apps.py b/htap_regions_tier1/apps.py index 8dd55a16b6e32c56e37e5fa250b0da2a42dc04dc..8409d408982fe72fc9fa08cc053ca17c6f8c8653 100644 --- a/htap_regions_tier1/apps.py +++ b/htap_regions_tier1/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class HtapRegionsConfig(AppConfig): +class HtapRegionsConfig(AppConfig): # pragma: no cover name = 'htap_regions_tier1' diff --git a/htap_regions_tier1/views.py b/htap_regions_tier1/views.py index 27cb0ab6c68c5cf4b59e6cb0db2ee17f99ff2947..897b0014eb90c5b912f9b281b1e84c2205c26926 100755 --- a/htap_regions_tier1/views.py +++ b/htap_regions_tier1/views.py @@ -34,4 +34,5 @@ class HTAPView(APIView, CommonViews): self.keep_only_given_agg_allowed(dict(maxclass=most_common_value, frequency=relative_frequency), frequency=dict(bins=len(self.bins), - start_bin=self.min_valid)) + start_bin=self.min_valid, + missing_value=self.default_value)) diff --git a/htap_regions_tier2/apps.py b/htap_regions_tier2/apps.py index d90ec102dcc566593b2e0f29f2ae9b9ecabb7058..7a4760b8d1baf8cf0309bb90058455e5d72d0846 100644 --- a/htap_regions_tier2/apps.py +++ b/htap_regions_tier2/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class HtapRegionsConfig(AppConfig): +class HtapRegionsConfig(AppConfig): # pragma: no cover name = 'htap_regions_tier2' diff --git a/major-roads/apps.py b/major-roads/apps.py deleted file mode 100644 index 8d6f0c32820f09fb15dc97c5b1a412bc1da93e01..0000000000000000000000000000000000000000 --- a/major-roads/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class LocalConfig(AppConfig): - name = 'major-roads' diff --git a/major-roads/amir_code/README.md b/major_roads/amir_code/README.md similarity index 100% rename from major-roads/amir_code/README.md rename to major_roads/amir_code/README.md diff --git a/major-roads/amir_code/overpassQL.py b/major_roads/amir_code/overpassQL.py similarity index 100% rename from major-roads/amir_code/overpassQL.py rename to major_roads/amir_code/overpassQL.py diff --git a/major-roads/amir_code/processor.py b/major_roads/amir_code/processor.py similarity index 100% rename from major-roads/amir_code/processor.py rename to major_roads/amir_code/processor.py diff --git a/major-roads/amir_code/views.py b/major_roads/amir_code/views.py similarity index 97% rename from major-roads/amir_code/views.py rename to major_roads/amir_code/views.py index 67a367fa1dc6833c15aa021da31b6af5b59027e0..33baa62e1dc03b809433dc13d6a7d78c6caebc67 100644 --- a/major-roads/amir_code/views.py +++ b/major_roads/amir_code/views.py @@ -23,7 +23,7 @@ class Road(APIView): example : https://www.openstreetmap.org/way/493995603 USE EXAMPLE: - /major-roads/roads-radius/?lat=50.9&lng=6.9&radius=700&direction=ESE + /major_roads/roads-radius/?lat=50.9&lng=6.9&radius=700&direction=ESE PARAMETERS: lat diff --git a/major_roads/apps.py b/major_roads/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..0514d857e22b549fa6f73b6dab383c2c9cb15992 --- /dev/null +++ b/major_roads/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class LocalConfig(AppConfig): # pragma: no cover + name = 'major_roads' diff --git a/major-roads/overpass_api.py b/major_roads/overpass_api.py similarity index 100% rename from major-roads/overpass_api.py rename to major_roads/overpass_api.py diff --git a/major-roads/pygeometry_nearestOn4_tests.py b/major_roads/pygeometry_nearestOn4_tests.py similarity index 100% rename from major-roads/pygeometry_nearestOn4_tests.py rename to major_roads/pygeometry_nearestOn4_tests.py diff --git a/major-roads/serializers.py b/major_roads/serializers.py similarity index 100% rename from major-roads/serializers.py rename to major_roads/serializers.py diff --git a/major-roads/static/js/osm_map.js b/major_roads/static/js/osm_map.js similarity index 100% rename from major-roads/static/js/osm_map.js rename to major_roads/static/js/osm_map.js diff --git a/major-roads/templates/map_view.html b/major_roads/templates/map_view.html similarity index 100% rename from major-roads/templates/map_view.html rename to major_roads/templates/map_view.html diff --git a/major-roads/tests.py b/major_roads/tests.py similarity index 100% rename from major-roads/tests.py rename to major_roads/tests.py diff --git a/major-roads/urls.py b/major_roads/urls.py similarity index 100% rename from major-roads/urls.py rename to major_roads/urls.py diff --git a/major-roads/vector_test.py b/major_roads/vector_test.py similarity index 100% rename from major-roads/vector_test.py rename to major_roads/vector_test.py diff --git a/major-roads/views.py b/major_roads/views.py similarity index 94% rename from major-roads/views.py rename to major_roads/views.py index d6794fd9ef5e9e48988d0dbcb1c7ecc843a46972..f4ebceed2b23a03d3591ddedc3f26672dccec8e1 100644 --- a/major-roads/views.py +++ b/major_roads/views.py @@ -14,10 +14,10 @@ from .serializers import NearestSerializer, NearestDirectionSerializer, NearestB class MajorRoadsView(APIView, CommonViews): def __init__(self): - CommonViews.__init__(self, service_type="major-roads") + CommonViews.__init__(self, service_type="major_roads") def get(self, request, format=None): - """process GET requests for major-roads app + """process GET requests for major_roads app returns a Geo-JSON response with information about the nearest major road @@ -33,7 +33,7 @@ class MajorRoadsView(APIView, CommonViews): by_direction: if true, then results are returned as array (or dict??) for all wind directions. - optional arguments specific to major-roads service: + optional arguments specific to major_roads service: highway_types: string with road types according to Overpass syntax. Default is motorway,trunk,primary,secondary. For additional options see https://wiki.openstreetmap.org/wiki/Key:highway @@ -78,13 +78,13 @@ class MajorRoadsView(APIView, CommonViews): class MajorRoadsMapView(APIView, CommonViews): def __init__(self): - CommonViews.__init__(self, service_type="major-roads") + CommonViews.__init__(self, service_type="major_roads") renderer_classes = [TemplateHTMLRenderer] template_name = 'map_view.html' def get(self, request, format=None): - """process GET requests for major-roads app + """process GET requests for major_roads app Works similar to MajorRoadsView but display a template HTML view with a map """ lat, lon, radius, direction, by_direction, highway_types = self.get_query_params( diff --git a/nox_emissions/apps.py b/nox_emissions/apps.py index d2ead1f0e40e312b2b75c0cbc517bbf7d1ea167f..88f3ce90702f67e259bdcb22f041be6bb57747d0 100644 --- a/nox_emissions/apps.py +++ b/nox_emissions/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class NoxEmissionConfig(AppConfig): +class NoxEmissionConfig(AppConfig): # pragma: no cover name = 'nox_emissions' diff --git a/omi_no2/apps.py b/omi_no2/apps.py index 6779f6eb93c1f13d07fb7e135d58311d18edfa03..fd07c8b0958483f8948686ec811d719239ba94b6 100644 --- a/omi_no2/apps.py +++ b/omi_no2/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class OmiNo2Config(AppConfig): +class OmiNo2Config(AppConfig): # pragma: no cover name = 'omi_no2' diff --git a/population-density/apps.py b/population-density/apps.py deleted file mode 100644 index 58582de03d9e916a668ad756a9d966df2a10a1df..0000000000000000000000000000000000000000 --- a/population-density/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class PopulationConfig(AppConfig): - name = 'population-density' diff --git a/population-density/__init__.py b/population_density/__init__.py similarity index 100% rename from population-density/__init__.py rename to population_density/__init__.py diff --git a/population-density/admin.py b/population_density/admin.py similarity index 100% rename from population-density/admin.py rename to population_density/admin.py diff --git a/population_density/apps.py b/population_density/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..f2906a1ff7e5c4380882bcdab1a09b732fc32953 --- /dev/null +++ b/population_density/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PopulationConfig(AppConfig): # pragma: no cover + name = 'population_density' diff --git a/population-density/migrations/__init__.py b/population_density/migrations/__init__.py similarity index 100% rename from population-density/migrations/__init__.py rename to population_density/migrations/__init__.py diff --git a/population-density/population_file_extraction.py b/population_density/population_file_extraction.py similarity index 100% rename from population-density/population_file_extraction.py rename to population_density/population_file_extraction.py diff --git a/population-density/serializers.py b/population_density/serializers.py similarity index 100% rename from population-density/serializers.py rename to population_density/serializers.py diff --git a/population-density/urls.py b/population_density/urls.py similarity index 100% rename from population-density/urls.py rename to population_density/urls.py diff --git a/population-density/views.py b/population_density/views.py similarity index 100% rename from population-density/views.py rename to population_density/views.py diff --git a/rice_production/apps.py b/rice_production/apps.py index a37fc3b5b2d12c854bd13a94a0992f6eb2dfbd0a..4b90c30e8f453e1ca766616f94a1c3fbaf8a0fa4 100644 --- a/rice_production/apps.py +++ b/rice_production/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class RiceProductionConfig(AppConfig): +class RiceProductionConfig(AppConfig): # pragma: no cover name = 'rice_production' diff --git a/stable_night_lights/apps.py b/stable_night_lights/apps.py index 6cf8d9c7dd27218faeb871686c676ab1aee94b31..c9c0c72077d416a3cc312d7aa27975bf4f83e4f4 100644 --- a/stable_night_lights/apps.py +++ b/stable_night_lights/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class StableNightLightsConfig(AppConfig): +class StableNightLightsConfig(AppConfig): # pragma: no cover name = 'stable_night_lights' diff --git a/toar_location_services/settings.py b/toar_location_services/settings.py index 2934a79fb99f967650d8ef685ef759eedf30e35c..8cdd5b18a2c7a04dd6318d7bc1f54486d83f9b47 100644 --- a/toar_location_services/settings.py +++ b/toar_location_services/settings.py @@ -41,9 +41,9 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'django.contrib.gis', 'rest_framework', - 'major-roads', - 'population-density', - 'topography-tandem-x', + 'major_roads', + 'population_density', + 'topography_tandem_x', 'stable_night_lights', 'wheat_production', 'rice_production', diff --git a/toar_location_services/urls.py b/toar_location_services/urls.py index 2d9f40bc265727ab6e8ef4cf11ea46544cb740f3..5383997dcc238cfc38f54c61f90de2c0e56d235e 100644 --- a/toar_location_services/urls.py +++ b/toar_location_services/urls.py @@ -6,9 +6,9 @@ from toar_location_services.settings import DEBUG urlpatterns = [ url(r'^$', LocationServicesRootView.as_view()), url('admin/', admin.site.urls), - 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'major_roads/', include('major_roads.urls'), name='major_roads'), + url(r'population_density/', include('population_density.urls'), name='population_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'wheat_production/', include('wheat_production.urls'), name='wheat_production'), url(r'rice_production/', include('rice_production.urls'), name='rice_production'), diff --git a/toar_location_services/views.py b/toar_location_services/views.py index c0caba9dd754a2f41f76747c08a9a1b7c83e5e3f..d3961505e378fb290876415351c441f6f6834829 100644 --- a/toar_location_services/views.py +++ b/toar_location_services/views.py @@ -11,9 +11,9 @@ class LocationServicesRootView(APIView): data = [ 'User interface APIs:', OrderedDict([ - ('major-roads', request.build_absolute_uri()+'major-roads/'), - ('population-density', request.build_absolute_uri()+'population-density/'), - ('topography-tandem-x', request.build_absolute_uri()+'topography-tandem-x/'), + ('major_roads', request.build_absolute_uri()+'major_roads/'), + ('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/'), ('rice_production', request.build_absolute_uri()+'rice_production/'), diff --git a/topography-tandem-x/apps.py b/topography-tandem-x/apps.py deleted file mode 100644 index a29302faf62bcc3bdf11fd63294db630fbac6dde..0000000000000000000000000000000000000000 --- a/topography-tandem-x/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class TopographyTandemXConfig(AppConfig): - name = 'topography-tandem-x' diff --git a/topography-tandem-x/__init__.py b/topography_tandem_x/__init__.py similarity index 100% rename from topography-tandem-x/__init__.py rename to topography_tandem_x/__init__.py diff --git a/topography-tandem-x/admin.py b/topography_tandem_x/admin.py similarity index 100% rename from topography-tandem-x/admin.py rename to topography_tandem_x/admin.py diff --git a/topography_tandem_x/apps.py b/topography_tandem_x/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..73ee436a827315f1edb75691d2691a408c41b891 --- /dev/null +++ b/topography_tandem_x/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TopographyTandemXConfig(AppConfig): # pragma: no cover + name = 'topography_tandem_x' diff --git a/topography-tandem-x/migrations/__init__.py b/topography_tandem_x/migrations/__init__.py similarity index 100% rename from topography-tandem-x/migrations/__init__.py rename to topography_tandem_x/migrations/__init__.py diff --git a/topography-tandem-x/serializers.py b/topography_tandem_x/serializers.py similarity index 100% rename from topography-tandem-x/serializers.py rename to topography_tandem_x/serializers.py diff --git a/topography-tandem-x/topography_file_extraction.py b/topography_tandem_x/topography_file_extraction.py similarity index 97% rename from topography-tandem-x/topography_file_extraction.py rename to topography_tandem_x/topography_file_extraction.py index 50525ef46ca7df898bb0f52fecae72b0bf457940..c5eb675da433f03511a1af1ddc26df54398403ca 100644 --- a/topography-tandem-x/topography_file_extraction.py +++ b/topography_tandem_x/topography_file_extraction.py @@ -21,7 +21,7 @@ def read_proxydata(filename, dummy=DEBUG and USE_DUMMY_TOPOGRAPHY_TANDEM_DATA): if dummy or TEST: return create_dummy_data() - # TOPOGRAPHY_DATA_DIR = os.path.join(DATA_DIR, 'topography-tandem-x') + # TOPOGRAPHY_DATA_DIR = os.path.join(DATA_DIR, 'topography_tandem_x') TOPOGRAPHY_FILE = os.path.join(DATA_DIR, filename) if DEBUG: diff --git a/topography-tandem-x/urls.py b/topography_tandem_x/urls.py similarity index 100% rename from topography-tandem-x/urls.py rename to topography_tandem_x/urls.py diff --git a/topography-tandem-x/views.py b/topography_tandem_x/views.py similarity index 100% rename from topography-tandem-x/views.py rename to topography_tandem_x/views.py diff --git a/utils/agg_serializer.py b/utils/agg_serializer.py index 88ad88e9df7231ae91511e6cadb7a32b59ab9300..aa797bc17048db78f9c4bffb009e7c11fe1037f6 100644 --- a/utils/agg_serializer.py +++ b/utils/agg_serializer.py @@ -14,133 +14,55 @@ class GeneralAggSerializer(BaseSerializer): # 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) + + # check if single value is returned try: vlength = len(val) except TypeError: vlength = 1 + # either multiple bins or direction is enabled if vlength > 1 or obj['direction'] is not None: + # return values are bin values if obj['direction'] is None: f = OrderedDict([(d, v) for d, v in zip(range(vlength), val)]) else: + # check if there are bins for each direction property try: vlength_sub = len(val[0]) except TypeError: vlength_sub = 0 + # create nested dicts with direction in outer and bins in inner dict if vlength_sub > 1: - f = OrderedDict([(d, OrderedDict([(b, v) for b, v in zip(obj['bins'], v_sub)])) for d, v_sub in zip(obj['direction'], val) ]) + f = OrderedDict([(d, OrderedDict([(b, v) for b, v in zip(obj['bins'], v_sub)])) for d, v_sub in zip(obj['direction'], val)]) + # only single value for each direction else: if not isinstance(val, list): val = [val] f = OrderedDict([(d, v) for d, v in zip(obj['direction'], val)]) - properties = OrderedDict([ - ('agg_function', agg_function), - ('many', True), - (agg_function, f), - ('units', obj['unit']), - ('radius', obj['radius']), - ]) + many = True + # only single value is returned else: try: vlength_sub = len(val[0]) except (IndexError, TypeError): vlength_sub = 1 - if vlength_sub > 1: - val = OrderedDict([(d, v) for d, v in zip(obj['bins'], val[0])]) - properties = OrderedDict([ - ('agg_function', agg_function), - ('many', False), - (agg_function, val), - ('units', obj['unit']), - ('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', obj['get_provenance'](obj)), - ]) - - return response - - def to_representation_old(self, obj): # pragma: no cover - """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 - if vlength > 1 or obj['direction'] is not None: - if obj['direction'] is None: - properties = OrderedDict([ - ('agg_function', agg_function), - ('many', True), - (agg_function, OrderedDict([ - (d, v) for d, v in zip(range(vlength), val) - ])), - ('units', obj['unit']), - ('radius', obj['radius']), - ]) + if vlength_sub > 1: # leave this for now, but I think this case never happens?! + f = OrderedDict([(d, v) for d, v in zip(obj['bins'], val[0])]) + many = True else: - try: - vlength_sub = len(val[0]) - except TypeError: - vlength_sub = 0 - if vlength_sub > 1: - properties = OrderedDict([ - ('agg_function', agg_function), - ('many', True), - (agg_function, OrderedDict([ - (d, OrderedDict([ - (b, v) for b, v in zip(obj['bins'], v_sub)]) - ) - for d, v_sub in zip(obj['direction'], val) - ])), - ('units', obj['unit']), - ('radius', obj['radius']), - ]) + f = val + many = False - else: - properties = OrderedDict([ - ('agg_function', agg_function), - ('many', True), - (agg_function, OrderedDict([ - (d, v) for d, v in zip(obj['direction'], val) - ])), - ('units', obj['unit']), - ('radius', obj['radius']), - ]) - else: - try: - vlength_sub = len(val[0]) - except (IndexError, TypeError): - vlength_sub = 1 - if vlength_sub > 1: - val = OrderedDict([(d, v) for d, v in zip(obj['bins'], val[0])]) - properties = OrderedDict([ - ('agg_function', agg_function), - ('many', False), - (agg_function, val), - ('units', obj['unit']), - ('radius', obj['radius']), - ('direction', obj['direction']), - ]) - if obj['direction'] is None: - properties.pop('direction') + # create properties + properties = OrderedDict([ + ('agg_function', agg_function), + ('many', many), + (agg_function, f), + ('units', obj['unit']), + ('radius', obj['radius']), + ]) + # create response response = OrderedDict([ ('type', 'Feature'), ('geometry', OrderedDict([ diff --git a/utils/extraction_tools.py b/utils/extraction_tools.py index a9496001bf3408c42290cdb0baa3a105ba22315e..447b73b42a7942399c57954fcdf002ed72497e48 100755 --- a/utils/extraction_tools.py +++ b/utils/extraction_tools.py @@ -6,6 +6,7 @@ get_station_info() - query REST service to obtain names and coordinates of all s import numpy as np from utils.geoutils import standardize_lon180 +from toar_location_services.settings import DEBUG def is_inside(lon, lat, boundingbox): @@ -104,7 +105,8 @@ def extract_circle(lonvec, latvec, data, ilon, ilat, nlon, nlat, if max_angle is None: max_angle = 360. max_angle = np.deg2rad(standardize_lon180(max_angle)) - print('#### min_angle, max_angle = ', np.rad2deg(min_angle), np.rad2deg(max_angle)) + if DEBUG: + print('#### min_angle, max_angle = ', np.rad2deg(min_angle), np.rad2deg(max_angle)) res = np.zeros((2 * nlat + 1, 2 * nlon + 1)) res[:] = np.nan diff --git a/utils/statistics.py b/utils/statistics.py index e075427cf682427bf7db787945657c29c4c335a8..87dc33796b2773b774de793a87b64f04f01009bf 100755 --- a/utils/statistics.py +++ b/utils/statistics.py @@ -6,12 +6,16 @@ def most_common_value(values): return np.bincount(values.astype(int)).argmax() -def relative_frequency(values, decimals=6, bins=0, start_bin=0): - """Get the relative frequency of each possible class""" +def relative_frequency(values, decimals=6, bins=0, start_bin=0, missing_value=-999.): + """Get the relative frequency of each possible class. There is 1 additional class for missing values.""" + values = values.astype(float) + values[np.where(values == missing_value)] = np.nan if bins > 0: - f = np.round(np.bincount(values.astype(int), minlength=int(bins)+start_bin) / len(values), decimals)[start_bin:] + f = np.round(np.bincount(values[~np.isnan(values)].astype(int), minlength=int(bins)+start_bin) / len(values), decimals)[start_bin:] else: - f = np.round(np.bincount(values.astype(int)) / len(values), decimals) + f = np.round(np.bincount(values[~np.isnan(values)].astype(int)) / len(values), decimals) + # collect missing data + f = np.append(f, np.round(np.sum(np.isnan(values))/len(values), decimals)) return f diff --git a/utils/tests.py b/utils/tests.py index ac1e31718dccc6d5e5dab2fdb245c36ccb7d010b..ae5a5d9fefee2d378bf2e45fe3dca88e2a7c29cf 100644 --- a/utils/tests.py +++ b/utils/tests.py @@ -37,32 +37,38 @@ class TestStatistics(TestCase): f = statistics.relative_frequency # use default settings - expected = np.array([0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667]) + expected = np.array([0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667, 0.]) self.assertIsNone(np.testing.assert_array_equal(f(self.data), expected)) # round on 1 decimal - expected = np.array([0.1, 0.4, 0.3, 0., 0.1, 0., 0.1, 0., 0., 0.1]) + expected = np.array([0.1, 0.4, 0.3, 0., 0.1, 0., 0.1, 0., 0., 0.1, 0.]) self.assertIsNone(np.testing.assert_array_equal(f(self.data, decimals=1), expected)) # return 12 bins - expected = np.array([0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667, 0., 0.]) + expected = np.array([0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667, 0., 0., 0.]) self.assertIsNone(np.testing.assert_array_equal(f(self.data, bins=12), expected)) # first bin in array is 1, start with bin 0 data_var = self.data + 1 - expected = np.array([0., 0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667]) + expected = np.array([0., 0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667, 0.]) self.assertIsNone(np.testing.assert_array_equal(f(data_var), expected)) # first bin in array is 1, start with bin 0, return 12 bins data_var = self.data + 1 - expected = np.array([0., 0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667, 0.]) + expected = np.array([0., 0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667, 0., 0.]) self.assertIsNone(np.testing.assert_array_equal(f(data_var, bins=12), expected)) # first bin in array is 1, start with bin 1 data_var = self.data + 1 - expected = np.array([0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667]) + expected = np.array([0.066667, 0.4, 0.266667, 0., 0.133333, 0., 0.066667, 0., 0., 0.066667, 0.]) self.assertIsNone(np.testing.assert_array_equal(f(data_var, start_bin=1, bins=10), expected)) + # use default settings + data_var = self.data + data_var[3] = -999. + expected = np.array([0.066667, 0.4, 0.266667, 0., 0.066667, 0., 0.066667, 0., 0., 0.066667, 0.066667]) + self.assertIsNone(np.testing.assert_array_equal(f(data_var), expected)) + def test_relative_frequency_categorical(self): f = statistics.relative_frequency_categorical @@ -640,33 +646,44 @@ class TestCommonViews(TestCase): self.assertListEqual(f('abvd'), ['a', 'b', 'v', 'd']) def test_extract(self): + # continuous data f = self.view_specific._extract + # default expected = (4.0, 'value', 'N', None) self.assertEqual(f(lon=40, lat=10, radius=None, agg='mean', direction='N'), expected) + # out of bounds expected = (-888, 'value', 'N', None) self.assertEqual(f(lon=6, lat=10, radius=None, agg='mean', direction='N'), expected) + # return value because r=0 expected = (4.0, 'value', 'N', None) self.assertEqual(f(lon=40, lat=10, radius=0, agg='mean', direction='N'), expected) + # get mean=0 expected = ([0.0], 'mean', None, None) self.assertEqual(f(lon=43, lat=13, radius=150000, agg='mean', direction=None), expected) + # get mean=4/9 expected = ([4/9.], 'mean', None, None) self.assertEqual(f(lon=41, lat=11, radius=200000, agg='mean', direction=None), expected) + # get mean for each direction expected = ([4., 4., 2., 4., 2., 4., 2., 4., 4., 4., 6., 4., 6., 4., 4., 4.], 'mean', ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'], None) self.assertEqual(f(lon=40, lat=9, radius=200000, agg='mean', direction=['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']), expected) + # categorical data f = self.view_categorical._extract + # return maxclass expected = ([4.], 'maxclass', None, None) self.assertEqual(f(lon=40, lat=9, radius=200000, agg='maxclass', direction=None), expected) - expected = (np.array([1./3, 0., 0., 0., 4./9, 0., 0., 0., 2./9, 0., 0.]), 'frequency', None, - list(range(0, 11))) + # relative frequency of each bin + expected = (np.array([1./3, 0., 0., 0., 4./9, 0., 0., 0., 2./9, 0., 0., 0.]), 'frequency', None, + list(range(0, 11)) + ['nan']) response = f(lon=40, lat=9, radius=200000, agg='frequency', direction=None) self.assertEqual(response[1:], expected[1:]) self.assertIsNone(np.testing.assert_array_almost_equal(response[0], expected[0])) + # no bin length set, therefore return only up to max found bin class f = self.view_categorical_nobins._extract - expected = (np.array([1./3, 0., 0., 0., 4./9, 0., 0., 0., 2./9]), 'frequency', None, + expected = (np.array([1./3, 0., 0., 0., 4./9, 0., 0., 0., 2./9, 0.]), 'frequency', None, list(range(0, 11))) response = f(lon=40, lat=9, radius=200000, agg='frequency', direction=None) self.assertEqual(response[1:], expected[1:]) @@ -786,7 +803,7 @@ class TestGeneralAggSerializer(TestCase): (4, 2), ]) properties = OrderedDict([ ('agg_function', 'mean'), - ('many', False), + ('many', True), ('mean', values), ('units', 'TestUnit'), ('radius', 400), ]) diff --git a/utils/views_commons.py b/utils/views_commons.py index fffab26d014de3251bec523168ac3479138f1ee2..3bf1ba83135c39545403f9ec49538e5782861975 100644 --- a/utils/views_commons.py +++ b/utils/views_commons.py @@ -267,7 +267,7 @@ class CommonViews: val = self.get_longitude_param(params) tmp['lon'] = val elif k == 'radius': - radius_none_by_default = not ('agg' in param_keys or self.service_type == "major-roads") + radius_none_by_default = not ('agg' in param_keys or self.service_type == "major_roads") val = self.get_radius_param(params, radius_none_by_default) tmp['radius'] = val elif k == 'agg': @@ -328,6 +328,8 @@ class CommonViews: bins = list(range(self.min_valid, self.max_valid+1)) else: bins = self.bins + if len(result_tmp) > len(bins): + bins.append('nan') result_list.append(result_tmp) diff --git a/wheat_production/apps.py b/wheat_production/apps.py index aaa8c337714c876d7da653a051134d10faaeb78e..4f020eba356ed931d9fed84850514a499dbfb5c5 100644 --- a/wheat_production/apps.py +++ b/wheat_production/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class WheatProductionConfig(AppConfig): +class WheatProductionConfig(AppConfig): # pragma: no cover name = 'wheat_production'