diff --git a/eoxserver/core/decoders/base.py b/eoxserver/core/decoders/base.py index 8a6592b3c..481366ec8 100644 --- a/eoxserver/core/decoders/base.py +++ b/eoxserver/core/decoders/base.py @@ -59,8 +59,8 @@ def __get__(self, decoder, decoder_class=None): """ results = self.select(decoder, decoder_class) - count = len(results) - + count = len(results) + locator = self.locator multiple = self.num not in SINGLE_VALUES @@ -79,7 +79,6 @@ def __get__(self, decoder, decoder_class=None): # parse the value/values, or return the defaults if multiple: - if count == 0 and self.num == ANY and self.default is not None: return self.default diff --git a/eoxserver/core/decoders/kvp.py b/eoxserver/core/decoders/kvp.py index e8dfa6aa7..a3faa2a23 100644 --- a/eoxserver/core/decoders/kvp.py +++ b/eoxserver/core/decoders/kvp.py @@ -53,6 +53,25 @@ def locator(self): return self._locator or self.key +class MultiParameter(Parameter): + """ Class for selecting different KVP parameters at once. + """ + + def __init__(self, selector, num=1, default=None, locator=None): + super(MultiParameter, self).__init__( + "", lambda s: s, num, default, locator + ) + self.key = selector + + def select(self, decoder, decoder_class=None): + result = [] + for key, values in decoder._query_dict.items(): + if self.key(key): + result.append((key, values)) + + return result + + class DecoderMetaclass(type): """ Metaclass for KVP Decoders to allow easy parameter declaration. """ @@ -70,13 +89,13 @@ class Decoder(object): """ Base class for KVP decoders. """ __metaclass__ = DecoderMetaclass - + def __init__(self, params): query_dict = {} if isinstance(params, QueryDict): for key, values in params.lists(): query_dict[key.lower()] = values - + elif isinstance(params, basestring): tmp = parse_qs(params) for key, values in tmp.items(): @@ -90,6 +109,6 @@ def __init__(self, params): raise ValueError( "Decoder input '%s' not supported." % type(params).__name__ ) - + self.kvp = params self._query_dict = query_dict diff --git a/eoxserver/core/util/perftools.py b/eoxserver/core/util/perftools.py new file mode 100644 index 000000000..f22d33208 --- /dev/null +++ b/eoxserver/core/util/perftools.py @@ -0,0 +1,68 @@ +#------------------------------------------------------------------------------- +# $Id$ +# +# Project: EOxServer +# Authors: Fabian Schindler +# +#------------------------------------------------------------------------------- +# Copyright (C) 2014 EOX IT Services GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies of this Software or works derived from this Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +#------------------------------------------------------------------------------- + +import time +import logging + + +_logger = logging.getLogger(__name__) + + +class DurationMeasurement(object): + def __init__(self, name, logger, level): + self.name = name + self.logger = logger + self.level = level + self.start = None + self.end = None + + def __enter__(self): + self.start = time.time() + return self + + def __exit__(self, *args, **kwargs): + self.end = time.time() + msg = "'%s' took %f seconds." % (self.name, self.duration) + self.logger.log(self.level, msg) + + @property + def duration(self): + if self.start is not None and self.end is not None: + return self.end - self.start + return None + + +def log_duration(name, logger=None, level=logging.DEBUG): + """ Convenience function to log the duration of a specific event. + :param name: The name of the event. + :param logger: The logger to use. + :param level: The log level to log the final message to. + """ + + logger = logger or _logger + return DurationMeasurement(name, logger, level) diff --git a/eoxserver/resources/coverages/admin.py b/eoxserver/resources/coverages/admin.py index 0134a01c7..0aef49387 100644 --- a/eoxserver/resources/coverages/admin.py +++ b/eoxserver/resources/coverages/admin.py @@ -191,7 +191,7 @@ class NilValueInline(AbstractInline): class BandInline(AbstractInline): form = BandInlineForm # TODO: not working as expected... model = models.Band - extra = 1 + extra = 0 def get_queryset(self): queryset = super(BandInline, self).get_queryset() diff --git a/eoxserver/resources/coverages/models.py b/eoxserver/resources/coverages/models.py index 35f28b936..88d1723a3 100644 --- a/eoxserver/resources/coverages/models.py +++ b/eoxserver/resources/coverages/models.py @@ -38,7 +38,8 @@ from eoxserver.contrib import gdal, osr from eoxserver.backends import models as backends from eoxserver.resources.coverages.util import ( - detect_circular_reference, collect_eo_metadata, is_same_grid + detect_circular_reference, collect_eo_metadata, is_same_grid, + parse_raw_value ) @@ -334,50 +335,7 @@ def __unicode__(self): def value(self): """ Get the parsed python value from the saved value string. """ - dt = self.nil_value_set.data_type - is_float = False - is_complex = False - - if dt in gdal.GDT_INTEGRAL_TYPES : - value = int(self.raw_value) - - elif dt in gdal.GDT_FLOAT_TYPES : - value = float(self.raw_value) - is_float = True - - elif dt in gdal.GDT_INTEGRAL_COMPLEX_TYPES : - value = complex(self.raw_value) - is_complex = True - - elif dt in gdal.GDT_FLOAT_COMPLEX_TYPES : - value = complex(self.raw_value) - is_complex = True - is_float = True - - else: - value = None - - # range check makes sense for integral values only - if not is_float : - - limits = gdal.GDT_NUMERIC_LIMITS.get(dt) - - if limits and value is not None: - def within(v, low, high): - return (v >= low and v <= high) - - error = ValueError( - "Stored value is out of the limits for the data type" - ) - if not is_complex and not within(value, *limits) : - raise error - elif is_complex: - if (not within(value.real, limits[0].real, limits[1].real) - or not within(value.real, limits[0].real, limits[1].real)): - raise error - - return value - + return parse_raw_value(self.raw_value, self.nil_value_set.data_type) def clean(self): """ Check that the value can be parsed. @@ -439,9 +397,11 @@ class Band(models.Model): data_type = models.PositiveIntegerField() color_interpretation = models.PositiveIntegerField(null=True, blank=True) + raw_value_min = models.CharField(max_length=512, null=True, blank=True, help_text="The string representation of the minimum value.") + raw_value_max = models.CharField(max_length=512, null=True, blank=True, help_text="The string representation of the maximum value.") + range_type = models.ForeignKey(RangeType, related_name="bands", null=False, blank=False) nil_value_set = models.ForeignKey(NilValueSet, null=True, blank=True) - def clean(self): nil_value_set = self.nil_value_set @@ -451,6 +411,12 @@ def clean(self): "its nil value set." ) + min_ = parse_raw_value(self.raw_value_min, self.data_type) + max_ = parse_raw_value(self.raw_value_min, self.data_type) + + if min_ is not None and max_ is not None and min_ > max_: + raise ValidationError("Minimum value larger than maximum value") + class Meta: ordering = ('index',) unique_together = (('index', 'range_type'), ('identifier', 'range_type')) @@ -461,7 +427,15 @@ def __unicode__(self): @property def allowed_values(self): - return gdal.GDT_NUMERIC_LIMITS[self.data_type] + dt = self.data_type + min_ = parse_raw_value(self.raw_value_min, dt) + max_ = parse_raw_value(self.raw_value_max, dt) + limits = gdal.GDT_NUMERIC_LIMITS[dt] + + return ( + min_ if min_ is not None else limits[0], + max_ if max_ is not None else limits[1], + ) @property def significant_figures(self): diff --git a/eoxserver/resources/coverages/rangetype.py b/eoxserver/resources/coverages/rangetype.py index 9217051db..ecd69ea13 100644 --- a/eoxserver/resources/coverages/rangetype.py +++ b/eoxserver/resources/coverages/rangetype.py @@ -312,7 +312,7 @@ def getRangeType( name ) : # get range-type record rt = RangeType.objects.get(name=name) - band = [] + bands = [] # loop over band records (ordering set in model) for b in rt.bands.all() : @@ -326,23 +326,29 @@ def getRangeType( name ) : # append created nil-value dictionary nil_values.append( { 'reason': n.reason, 'value': n.raw_value } ) + + band = { + 'name' : b.name, + 'data_type' : gdal.GDT_TO_NAME.get(b.data_type,'Invalid'), + 'identifier' : b.identifier, + 'description' : b.description, + 'definition' : b.definition, + 'uom' : b.uom, + 'nil_values' : nil_values, + 'color_interpretation' : + gdal.GCI_TO_NAME.get(b.color_interpretation,'Invalid'), + } + + if b.raw_value_min is not None: + band["value_min"] = b.raw_value_min + if b.raw_value_max is not None: + band["value_max"] = b.raw_value_max + # append created band dictionary - band.append( - { - 'name' : b.name, - 'data_type' : gdal.GDT_TO_NAME.get(b.data_type,'Invalid'), - 'identifier' : b.identifier, - 'description' : b.description, - 'definition' : b.definition, - 'uom' : b.uom, - 'nil_values' : nil_values, - 'color_interpretation' : - gdal.GCI_TO_NAME.get(b.color_interpretation,'Invalid'), - } - ) + bands.append(band) # return JSON serializable dictionary - return { 'name': rt.name, 'bands': band } + return { 'name': rt.name, 'bands': bands } except RangeType.DoesNotExist : @@ -382,20 +388,22 @@ def setRangeType( rtype ) : cint = gdal.NAME_TO_GCI[cint.lower()] # prepare nil-value set - - nvset = NilValueSet.objects.create( - name = "__%s_%2.2d__"%(rtype['name'],idx), - data_type = dtype ) - - for nval in band['nil_values'] : - - nv = NilValue.objects.create( - reason = nval['reason'], - raw_value = str(nval['value']), - nil_value_set = nvset ) + if band['nil_values']: + nvset = NilValueSet.objects.create( + name = "__%s_%2.2d__"%(rtype['name'],idx), + data_type = dtype ) + + for nval in band['nil_values'] : + + nv = NilValue.objects.create( + reason = nval['reason'], + raw_value = str(nval['value']), + nil_value_set = nvset ) - # cheking value - tmp = nv.value + # cheking value + tmp = nv.value + else: + nvset = None bn = Band.objects.create( index = idx, @@ -407,5 +415,7 @@ def setRangeType( rtype ) : uom = band['uom'], color_interpretation = cint, range_type = rt, - nil_value_set = nvset + nil_value_set = nvset, + raw_value_min = band.get("value_min"), + raw_value_max = band.get("value_max") ) diff --git a/eoxserver/resources/coverages/util.py b/eoxserver/resources/coverages/util.py index 6ffce6ae4..8303c03ef 100644 --- a/eoxserver/resources/coverages/util.py +++ b/eoxserver/resources/coverages/util.py @@ -12,8 +12,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -37,6 +37,7 @@ from django.utils.timezone import is_naive, make_aware, get_current_timezone from django.utils.dateparse import parse_datetime +from eoxserver.contrib import gdal def pk_equals(first, second): return first.pk == second.pk @@ -154,3 +155,52 @@ def is_same_grid(coverages, epsilon=1e-10): return False return True + + +def parse_raw_value(raw_value, dt): + """ Parse a raw value from a string according to a given data type. + """ + if raw_value is None: # allow null values + return None + + is_float = False + is_complex = False + + if dt in gdal.GDT_INTEGRAL_TYPES: + value = int(raw_value) + + elif dt in gdal.GDT_FLOAT_TYPES: + value = float(raw_value) + is_float = True + + elif dt in gdal.GDT_INTEGRAL_COMPLEX_TYPES: + value = complex(raw_value) + is_complex = True + + elif dt in gdal.GDT_FLOAT_COMPLEX_TYPES: + value = complex(raw_value) + is_complex = True + is_float = True + + else: + value = None + + # range check makes sense for integral values only + if not is_float: + limits = gdal.GDT_NUMERIC_LIMITS.get(dt) + + if limits and value is not None: + def within(v, low, high): + return (v >= low and v <= high) + + error = ValueError( + "Stored value is out of the limits for the data type" + ) + if not is_complex and not within(value, *limits): + raise error + elif is_complex: + if (not within(value.real, limits[0].real, limits[1].real) + or not within(value.real, limits[0].real, limits[1].real)): + raise error + + return value diff --git a/eoxserver/services/mapserver/connectors/multifile_connector.py b/eoxserver/services/mapserver/connectors/multifile_connector.py index 2bf93d3a3..c8b4ce293 100644 --- a/eoxserver/services/mapserver/connectors/multifile_connector.py +++ b/eoxserver/services/mapserver/connectors/multifile_connector.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -52,7 +52,7 @@ def supports(self, data_items): ) ) - def connect(self, coverage, data_items, layer): + def connect(self, coverage, data_items, layer, options): # TODO: implement vrt_doc = vrt.VRT() @@ -83,5 +83,5 @@ def connect(self, coverage, data_items, layer): layer.data = path - def disconnect(self, coverage, data_items, layer): + def disconnect(self, coverage, data_items, layer, options): vsi.remove(layer.data) diff --git a/eoxserver/services/mapserver/connectors/polygonmask_connector.py b/eoxserver/services/mapserver/connectors/polygonmask_connector.py index 32f304014..fbda6cd88 100644 --- a/eoxserver/services/mapserver/connectors/polygonmask_connector.py +++ b/eoxserver/services/mapserver/connectors/polygonmask_connector.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -26,35 +26,31 @@ # THE SOFTWARE. #------------------------------------------------------------------------------- - -from os.path import join -from uuid import uuid4 - from eoxserver.core import Component, implements -from eoxserver.contrib import vsi, vrt, ogr +from eoxserver.contrib import ogr from eoxserver.contrib import mapserver as ms from eoxserver.backends.access import connect from eoxserver.services.mapserver.interfaces import ConnectorInterface class PolygonMaskConnector(Component): - """ Connects polygon mask files to MapServer polygon layers. For some + """ Connects polygon mask files to MapServer polygon layers. For some purposes this can also be done via "reverse" polygons, where the actual polygons are subtracted from the coverages footprint. """ implements(ConnectorInterface) - + def supports(self, data_items): num = len(data_items) return ( - len(data_items) >= 1 + len(data_items) >= 1 and len(filter( lambda d: d.semantic.startswith("polygonmask"), data_items )) == num ) - def connect(self, coverage, data_items, layer): + def connect(self, coverage, data_items, layer, options): mask_item = data_items[0] try: @@ -100,9 +96,8 @@ def connect(self, coverage, data_items, layer): # TODO: more than one mask_item? layer.setProjection("EPSG:4326") - layer.setMetaData("ows_srs", "EPSG:4326") + layer.setMetaData("ows_srs", "EPSG:4326") layer.setMetaData("wms_srs", "EPSG:4326") - - def disconnect(self, coverage, data_items, layer): + def disconnect(self, coverage, data_items, layer, options): pass diff --git a/eoxserver/services/mapserver/connectors/simple_connector.py b/eoxserver/services/mapserver/connectors/simple_connector.py index 6a20140fb..df3420809 100644 --- a/eoxserver/services/mapserver/connectors/simple_connector.py +++ b/eoxserver/services/mapserver/connectors/simple_connector.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -31,7 +31,7 @@ from eoxserver.core import Component, implements from eoxserver.backends.access import connect -from eoxserver.contrib import vsi, vrt, mapserver, gdal +from eoxserver.contrib import vsi, gdal from eoxserver.services.mapserver.interfaces import ConnectorInterface from eoxserver.processing.gdal.vrt import create_simple_vrt from eoxserver.processing.gdal import reftools @@ -43,12 +43,12 @@ class SimpleConnector(Component): """ Connector for single file layers. """ implements(ConnectorInterface) - + def supports(self, data_items): filtered = filter(lambda d: d.semantic.startswith("bands"), data_items) - return len(filtered) == 1 + return len(filtered) == 1 - def connect(self, coverage, data_items, layer): + def connect(self, coverage, data_items, layer, options): filtered = filter(lambda d: d.semantic.startswith("bands"), data_items) data = connect(filtered[0]) @@ -68,16 +68,16 @@ def connect(self, coverage, data_items, layer): vrt_ds = create_simple_vrt(ds, vrt_path) size_x = ds.RasterXSize size_y = ds.RasterYSize - + dx = abs(e[0] - e[2]) / size_x - dy = abs(e[1] - e[3]) / size_y - + dy = abs(e[1] - e[3]) / size_y + vrt_ds.SetGeoTransform([e[0], dx, 0, e[3], 0, -dy]) vrt_ds = None - + layer.data = vrt_path - def disconnect(self, coverage, data_items, layer): + def disconnect(self, coverage, data_items, layer, options): if layer.metadata.get("eoxs_wrap_dateline") == "true": vsi.remove(layer.data) diff --git a/eoxserver/services/mapserver/connectors/tileindex_connector.py b/eoxserver/services/mapserver/connectors/tileindex_connector.py index d83906738..2479ad1a7 100644 --- a/eoxserver/services/mapserver/connectors/tileindex_connector.py +++ b/eoxserver/services/mapserver/connectors/tileindex_connector.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -35,7 +35,7 @@ class TileIndexConnector(Component): - """ Connects a tile index with the given layer. The tileitem is fixed to + """ Connects a tile index with the given layer. The tileitem is fixed to "location". """ @@ -46,9 +46,9 @@ def supports(self, data_items): len(data_items) == 1 and data_items[0].semantic == "tileindex" ) - def connect(self, coverage, data_items, layer): + def connect(self, coverage, data_items, layer, options): layer.tileindex = os.path.abspath(connect(data_items[0])) layer.tileitem = "location" - def disconnect(self, coverage, data_items, layer): + def disconnect(self, coverage, data_items, layer, options): pass diff --git a/eoxserver/services/mapserver/interfaces.py b/eoxserver/services/mapserver/interfaces.py index 95051d6ee..0271f7a03 100644 --- a/eoxserver/services/mapserver/interfaces.py +++ b/eoxserver/services/mapserver/interfaces.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -28,51 +28,51 @@ class ConnectorInterface(object): - """ Interface for connectors between `mapscript.layerObj` and associated + """ Interface for connectors between `mapscript.layerObj` and associated data. """ def supports(self, data_items): - """ Returns `True` if the given `data_items` are supported and + """ Returns `True` if the given `data_items` are supported and `False` if not. """ - - def connect(self, coverage, data_items, layer): - """ Connect a layer (a `mapscript.layerObj`) with the given data + + def connect(self, coverage, data_items, layer, options): + """ Connect a layer (a `mapscript.layerObj`) with the given data items and coverage (a list of two-tuples: location and semantic). """ - def disconnect(self, coverage, data_items, layer): + def disconnect(self, coverage, data_items, layer, options): """ Performs all necessary cleanup operations. """ class LayerFactoryInterface(object): - """ Interface for factories that create `mapscript.layerObj` objects for + """ Interface for factories that create `mapscript.layerObj` objects for coverages. """ @property def suffixes(self): - """ The suffixes associated with layers this factory produces. This is + """ The suffixes associated with layers this factory produces. This is used for "specialized" layers such as "bands" or "outlines" layers. For factories that don't use this feature, it can be left out. """ @property def requires_connection(self): - """ Return whether or layers generated by this factory require to be + """ Return whether or layers generated by this factory require to be connected via a layer connector. """ def generate(self, eo_object, group_layer, options): - """ Returns an iterable of `mapscript.layerObj` objects preconfigured - for the given EO object. This is easily done via the `yield` + """ Returns an iterable of `mapscript.layerObj` objects preconfigured + for the given EO object. This is easily done via the `yield` statement. """ def generate_group(self, name): - """ Returns a 'group layer' to be referenced by all other layers + """ Returns a 'group layer' to be referenced by all other layers generated by this factory. """ diff --git a/eoxserver/services/mapserver/wcs/coverage_renderer.py b/eoxserver/services/mapserver/wcs/coverage_renderer.py index 7d5b3f070..cb399c9de 100644 --- a/eoxserver/services/mapserver/wcs/coverage_renderer.py +++ b/eoxserver/services/mapserver/wcs/coverage_renderer.py @@ -159,7 +159,7 @@ def render(self, params): ) try: - connector.connect(coverage, data_items, layer) + connector.connect(coverage, data_items, layer, {}) # create request object and dispatch it against the map request = ms.create_request( self.translate_params(params, range_type) @@ -169,7 +169,7 @@ def render(self, params): finally: # perform any required layer related cleanup - connector.disconnect(coverage, data_items, layer) + connector.disconnect(coverage, data_items, layer, {}) result_set = result_set_from_raw_data(raw_result) diff --git a/eoxserver/services/mapserver/wms/util.py b/eoxserver/services/mapserver/wms/util.py index fd0a37a64..5ed31d68f 100644 --- a/eoxserver/services/mapserver/wms/util.py +++ b/eoxserver/services/mapserver/wms/util.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -30,7 +30,6 @@ import logging from itertools import chain -from django.db.models import Q from django.utils.datastructures import SortedDict from eoxserver.core import Component, ExtensionPoint @@ -42,7 +41,7 @@ ) from eoxserver.services.result import result_set_from_raw_data, get_content_type from eoxserver.services.exceptions import RenderException -from eoxserver.services.ows.wms.exceptions import InvalidCRS, InvalidFormat +from eoxserver.services.ows.wms.exceptions import InvalidFormat logger = logging.getLogger(__name__) @@ -55,7 +54,6 @@ class MapServerWMSBaseComponent(Component): layer_factories = ExtensionPoint(LayerFactoryInterface) style_applicators = ExtensionPoint(StyleApplicatorInterface) - def render(self, layer_groups, request_values, **options): map_ = ms.Map() map_.setMetaData("ows_enable_request", "*") @@ -73,7 +71,7 @@ def render(self, layer_groups, request_values, **options): self.check_parameters(map_, request_values) session = self.setup_map(layer_groups, map_, options) - + with session: request = ms.create_request(request_values) raw_result = map_.dispatch(request) @@ -81,7 +79,6 @@ def render(self, layer_groups, request_values, **options): result = result_set_from_raw_data(raw_result) return result, get_content_type(result) - def check_parameters(self, map_, request_values): for key, value in request_values: if key.lower() == "format": @@ -89,7 +86,7 @@ def check_parameters(self, map_, request_values): raise InvalidFormat(value) break else: - raise RenderException("Missing 'format' parameter") + raise RenderException("Missing 'format' parameter") @property def suffixes(self): @@ -97,29 +94,26 @@ def suffixes(self): chain(*[factory.suffixes for factory in self.layer_factories]) ) - def get_connector(self, data_items): for connector in self.connectors: if connector.supports(data_items): return connector return None - def get_layer_factory(self, suffix): result = None for factory in self.layer_factories: if suffix in factory.suffixes: if result: - pass # TODO + pass # TODO #raise Exception("Found") result = factory return result return result - def setup_map(self, layer_selection, map_, options): group_layers = SortedDict() - session = ConnectorSession() + session = ConnectorSession(options) # set up group layers before any "real" layers for collections, _, name, suffix in tuple(layer_selection.walk()): @@ -132,7 +126,7 @@ def setup_map(self, layer_selection, map_, options): # raise or pass? continue - # get the groups name, which is the name of the collection + the + # get the groups name, which is the name of the collection + the # suffix group_name = collections[-1].identifier + (suffix or "") @@ -174,18 +168,17 @@ def setup_map(self, layer_selection, map_, options): for layer, data_items in layers_and_data_items: connector = self.get_connector(data_items) - + if group_name: layer.setMetaData("wms_layer_group", "/" + group_name) session.add(connector, coverage, data_items, layer) - coverage_layers = [layer for _, layer, _ in session.coverage_layers] for layer in chain(group_layers.values(), coverage_layers): old_layer = map_.getLayerByName(layer.name) if old_layer: - # remove the old layer and reinsert the new one, to + # remove the old layer and reinsert the new one, to # raise the layer to the top. # TODO: find a more efficient way to do this map_.removeLayer(old_layer.index) @@ -199,7 +192,6 @@ def setup_map(self, layer_selection, map_, options): return session - def get_empty_layers(self, name): layer = ms.layerObj() layer.name = name @@ -208,11 +200,12 @@ def get_empty_layers(self, name): class ConnectorSession(object): - """ Helper class to be used in `with` statements. Allows connecting and + """ Helper class to be used in `with` statements. Allows connecting and disconnecting all added layers with the given data items. """ - def __init__(self): + def __init__(self, options=None): self.item_list = [] + self.options = options or {} def add(self, connector, coverage, data_items, layer): self.item_list.append( @@ -222,13 +215,12 @@ def add(self, connector, coverage, data_items, layer): def __enter__(self): for connector, coverage, layer, data_items in self.item_list: if connector: - connector.connect(coverage, data_items, layer) + connector.connect(coverage, data_items, layer, self.options) def __exit__(self, *args, **kwargs): for connector, coverage, layer, data_items in self.item_list: if connector: - connector.disconnect(coverage, data_items, layer) - + connector.disconnect(coverage, data_items, layer, self.options) @property def coverage_layers(self): diff --git a/eoxserver/services/ows/wms/v10/getmap.py b/eoxserver/services/ows/wms/v10/getmap.py index 577e383a4..dc142cddc 100644 --- a/eoxserver/services/ows/wms/v10/getmap.py +++ b/eoxserver/services/ows/wms/v10/getmap.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -26,13 +26,10 @@ # THE SOFTWARE. #------------------------------------------------------------------------------- - -from itertools import chain - from eoxserver.core import Component, implements, UniqueExtensionPoint from eoxserver.core.decoders import kvp, typelist, InvalidParameterException -from eoxserver.resources.coverages import models, crss -from eoxserver.services.subset import Subsets, Trim, Slice +from eoxserver.resources.coverages import crss +from eoxserver.services.subset import Subsets, Trim from eoxserver.services.ows.interfaces import ( ServiceHandlerInterface, GetServiceHandlerInterface ) @@ -48,7 +45,7 @@ class WMS10GetMapHandler(Component): implements(ServiceHandlerInterface) implements(GetServiceHandlerInterface) - renderer = UniqueExtensionPoint(WMSMapRendererInterface) + renderer = UniqueExtensionPoint(WMSMapRendererInterface) service = ("WMS", None) versions = ("1.0", "1.0.0") @@ -77,11 +74,12 @@ def handle(self, request): Trim("x", minx, maxx), Trim("y", miny, maxy), ), crs=srs) - + root_group = lookup_layers(layers, subsets) - + result, _ = self.renderer.render( - root_group, request.GET.items() + root_group, request.GET.items(), subsets=subsets, + width=int(decoder.width), height=int(decoder.height) ) return to_http_response(result) diff --git a/eoxserver/services/ows/wms/v11/getmap.py b/eoxserver/services/ows/wms/v11/getmap.py index ccef111d4..130bec44a 100644 --- a/eoxserver/services/ows/wms/v11/getmap.py +++ b/eoxserver/services/ows/wms/v11/getmap.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -29,8 +29,8 @@ from eoxserver.core import Component, implements, UniqueExtensionPoint from eoxserver.core.decoders import kvp, typelist, InvalidParameterException -from eoxserver.resources.coverages import models, crss -from eoxserver.services.subset import Subsets, Trim, Slice +from eoxserver.resources.coverages import crss +from eoxserver.services.subset import Subsets, Trim from eoxserver.services.ows.interfaces import ( ServiceHandlerInterface, GetServiceHandlerInterface ) @@ -46,7 +46,7 @@ class WMS11GetMapHandler(Component): implements(ServiceHandlerInterface) implements(GetServiceHandlerInterface) - renderer = UniqueExtensionPoint(WMSMapRendererInterface) + renderer = UniqueExtensionPoint(WMSMapRendererInterface) service = "WMS" versions = ("1.1", "1.1.0", "1.1.1") @@ -78,13 +78,18 @@ def handle(self, request): ), crs=srs) if time: subsets.append(time) - + renderer = self.renderer root_group = lookup_layers(layers, subsets, renderer.suffixes) result, _ = renderer.render( - root_group, request.GET.items(), - time=decoder.time, bands=decoder.dim_bands + root_group, request.GET.items(), + width=int(decoder.width), height=int(decoder.height), + time=decoder.time, bands=decoder.dim_bands, subsets=subsets, + elevation=decoder.elevation, + dimensions=dict( + (key[4:], values) for key, values in decoder.dimensions + ) ) return to_http_response(result) @@ -99,3 +104,5 @@ class WMS11GetMapDecoder(kvp.Decoder): height = kvp.Parameter(num=1) format = kvp.Parameter(num=1) dim_bands = kvp.Parameter(type=typelist(int_or_str, ","), num="?") + elevation = kvp.Parameter(type=float, num="?") + dimensions = kvp.MultiParameter(lambda s: s.startswith("dim_"), locator="dimension", num="*") diff --git a/eoxserver/services/ows/wms/v13/getmap.py b/eoxserver/services/ows/wms/v13/getmap.py index 4b0d0c0f2..9a54dec92 100644 --- a/eoxserver/services/ows/wms/v13/getmap.py +++ b/eoxserver/services/ows/wms/v13/getmap.py @@ -10,8 +10,8 @@ # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all @@ -29,7 +29,7 @@ from eoxserver.core import Component, implements, UniqueExtensionPoint from eoxserver.core.decoders import kvp, typelist, InvalidParameterException -from eoxserver.resources.coverages import models, crss +from eoxserver.resources.coverages import crss from eoxserver.services.subset import Subsets, Trim from eoxserver.services.ows.interfaces import ( ServiceHandlerInterface, GetServiceHandlerInterface @@ -78,15 +78,20 @@ def handle(self, request): Trim("x", minx, maxx), Trim("y", miny, maxy), ), crs=crs) - if time: + if time: subsets.append(time) - + renderer = self.renderer root_group = lookup_layers(layers, subsets, renderer.suffixes) result, _ = renderer.render( - root_group, request.GET.items(), - time=decoder.time, bands=decoder.dim_bands + root_group, request.GET.items(), + width=int(decoder.width), height=int(decoder.height), + time=decoder.time, bands=decoder.dim_bands, subsets=subsets, + elevation=decoder.elevation, + dimensions=dict( + (key[4:], values) for key, values in decoder.dimensions + ) ) return to_http_response(result) @@ -102,3 +107,5 @@ class WMS13GetMapDecoder(kvp.Decoder): height = kvp.Parameter(num=1) format = kvp.Parameter(num=1) dim_bands = kvp.Parameter(type=typelist(int_or_str, ","), num="?") + elevation = kvp.Parameter(type=float, num="?") + dimensions = kvp.MultiParameter(lambda s: s.startswith("dim_"), locator="dimension", num="*")