diff --git a/autotest/autotest/expected/WMS13GetLegendGraphicStyledTestCase.png b/autotest/autotest/expected/WMS13GetLegendGraphicStyledTestCase.png new file mode 100644 index 000000000..06dab009e Binary files /dev/null and b/autotest/autotest/expected/WMS13GetLegendGraphicStyledTestCase.png differ diff --git a/autotest/autotest/expected/WMS13GetLegendGraphicTestCase.png b/autotest/autotest/expected/WMS13GetLegendGraphicTestCase.png new file mode 100644 index 000000000..81a5e47bd Binary files /dev/null and b/autotest/autotest/expected/WMS13GetLegendGraphicTestCase.png differ diff --git a/autotest/autotest/expected/WMS13GetLegendGraphicTestCaseCOllectionLevel.png b/autotest/autotest/expected/WMS13GetLegendGraphicTestCaseCOllectionLevel.png new file mode 100644 index 000000000..46c1f0ac3 Binary files /dev/null and b/autotest/autotest/expected/WMS13GetLegendGraphicTestCaseCOllectionLevel.png differ diff --git a/autotest/autotest_services/tests/wms/base.py b/autotest/autotest_services/tests/wms/base.py index d6c6a82ea..468a196d9 100644 --- a/autotest/autotest_services/tests/wms/base.py +++ b/autotest/autotest_services/tests/wms/base.py @@ -158,4 +158,26 @@ def testBandStatistics(self): array1 = np.array(exp_band.ReadAsArray()).flatten() array2 = np.array(res_band.ReadAsArray()).flatten() regress_result = linregress(array1,array2) - self.assertGreaterEqual(regress_result.rvalue, 0.9) \ No newline at end of file + self.assertGreaterEqual(regress_result.rvalue, 0.9) + +@tag('wms', 'wms13') +class WMS13GetLegendTestCase(testbase.RasterTestCase): + layers = [] + styles = [] + frmt = "image/png" + + def getFileExtension(self, part=None): + try: + return format_to_extension[self.frmt] + except KeyError: + return testbase.mimetypes.guess_extension(self.frmt, False)[1:] + + def getRequest(self): + params = "service=WMS&request=GetMap&version=1.1.1&" \ + "layers=%s&styles=%s&format=%s" % ( + ",".join(self.layers), ",".join(self.styles), self.frmt + ) + if self.httpHeaders is None: + return (params, "kvp") + else: + return (params, "kvp", self.httpHeaders) \ No newline at end of file diff --git a/autotest/autotest_services/tests/wms/test_v13.py b/autotest/autotest_services/tests/wms/test_v13.py index 54fc28c76..1344dc47f 100644 --- a/autotest/autotest_services/tests/wms/test_v13.py +++ b/autotest/autotest_services/tests/wms/test_v13.py @@ -419,9 +419,37 @@ def getRequest(self): #=============================================================================== # Legend Graphic #=============================================================================== +class WMS13GetLegendGraphicTestCase(wmsbase.WMS13GetLegendTestCase): + """ Test a GetLegendGraphic request for a dataset with an associated style. """ -# currently disabled because of segfaults in MapServer + def getRequest(self): + params = "service=WMS&version=1.3.0&request=GetLegendGraphic&format=image/png&layer=ASA_WSM_1PNDPA20050331_075939_000000552036_00035_16121_0775" + return params, "kvp" + + def getFileExtension(self, file_type): + return "png" + +class WMS13GetLegendGraphicTestCaseCOllectionLevel(wmsbase.WMS13GetLegendTestCase): + """ Test a GetLegendGraphic request on Collection level. """ + + def getRequest(self): + params = "service=WMS&version=1.3.0&request=GetLegendGraphic&format=image/png&layer=MER_FRS_1P_reduced" + return params, "kvp" + def getFileExtension(self, file_type): + return "png" + +class WMS13GetLegendGraphicStyledTestCase(wmsbase.WMS13GetLegendTestCase): + """ Test a GetLegendGraphic request for a dataset with an associated style. """ + + def getRequest(self): + params = "service=WMS&version=1.3.0&request=GetLegendGraphic&format=image/png&layer=ASA_WSM_1PNDPA20050331_075939_000000552036_00035_16121_0775&style=earth" + return params, "kvp" + + def getFileExtension(self, file_type): + return "png" + +# currently disabled because of segfaults in MapServer class WMS13GetLegendGraphicDatasetStyledTestCase(testbase.RasterTestCase): """ Test a GetLegendGraphic request for a dataset with an associated style. """ diff --git a/eoxserver/render/map/config.py b/eoxserver/render/map/config.py index a6e3d07e5..ca51f02cf 100644 --- a/eoxserver/render/map/config.py +++ b/eoxserver/render/map/config.py @@ -29,3 +29,7 @@ DEFAULT_EOXS_MAP_RENDERER = ( "eoxserver.render.mapserver.map_renderer.MapserverMapRenderer" ) + +DEFAULT_EOXS_LEGEND_RENDERER = ( + "eoxserver.render.mapserver.map_renderer.MapserverMapRenderer" +) diff --git a/eoxserver/render/map/objects.py b/eoxserver/render/map/objects.py index f7ccd5519..17de17121 100644 --- a/eoxserver/render/map/objects.py +++ b/eoxserver/render/map/objects.py @@ -391,6 +391,30 @@ def __repr__(self): ) +class Legend(object): + def __init__(self, layer, width, height, format): + self._layer = layer + self._width = width + self._height = height + self._format = format + + @property + def layer(self): + return self._layer + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + @property + def format(self): + return self._format + + class LayerDescription(object): """ Abstract layer description """ diff --git a/eoxserver/render/map/renderer.py b/eoxserver/render/map/renderer.py index 918cd0572..4b34aa3da 100644 --- a/eoxserver/render/map/renderer.py +++ b/eoxserver/render/map/renderer.py @@ -28,10 +28,13 @@ from django.conf import settings from django.utils.module_loading import import_string -from eoxserver.render.map.config import DEFAULT_EOXS_MAP_RENDERER +from eoxserver.render.map.config import ( + DEFAULT_EOXS_MAP_RENDERER, DEFAULT_EOXS_LEGEND_RENDERER +) MAP_RENDERER = None +LEGEND_RENDERER = None def get_map_renderer(): @@ -44,3 +47,16 @@ def get_map_renderer(): MAP_RENDERER = import_string(specifier)() return MAP_RENDERER + + +def get_legend_renderer(): + global LEGEND_RENDERER + if LEGEND_RENDERER is None: + specifier = getattr( + settings, 'EOXS_LEGEND_RENDERER', DEFAULT_EOXS_LEGEND_RENDERER + ) + + LEGEND_RENDERER = import_string(specifier)() + + return LEGEND_RENDERER + diff --git a/eoxserver/render/mapserver/factories.py b/eoxserver/render/mapserver/factories.py index fef1ff5b7..515132356 100644 --- a/eoxserver/render/mapserver/factories.py +++ b/eoxserver/render/mapserver/factories.py @@ -310,7 +310,7 @@ def create(self, map_obj, layer: CoveragesLayer): shape_obj = ms.shapeObj.fromWKT(coverage.footprint.wkt) outlines_layer_obj.addFeature(shape_obj) - class_obj = _create_geometry_class(vector_style) + class_obj = _create_geometry_class(vector_style, name='outlines') outlines_layer_obj.insertClass(class_obj) return coverage_layers @@ -361,17 +361,20 @@ def make_browse_layer_generator(self, map_obj, browses, map_, ) layer_objs = _create_raster_layer_objs( map_obj, browse.extent, browse.spatial_reference, - creation_info.filename, filename_generator, creation_info.env, browse.mode, + creation_info.filename if creation_info else '', + filename_generator, {} ) for layer_obj in layer_objs: - if creation_info.env: - ms.set_env(map_obj, creation_info.env, True) + if creation_info: + layer_obj.data = creation_info.filename + if creation_info.env: + ms.set_env(map_obj, creation_info.env, True) - if creation_info.bands: - layer_obj.setProcessingKey('BANDS', ','.join( - str(band) for band in creation_info.bands - )) + if creation_info.bands: + layer_obj.setProcessingKey('BANDS', ','.join( + str(band) for band in creation_info.bands + )) if reset_info: sr = osr.SpatialReference(map_.crs) @@ -411,16 +414,18 @@ def make_browse_layer_generator(self, map_obj, browses, map_, ) for i, (field, field_range, nodata_value) in browse_iter: if ranges: - if len(ranges) == 1: - range_ = ranges[0] - else: - range_ = ranges[i - 1] - elif field_range != (None, None): - range_ = field_range + browse_range = ranges[0] + elif browse.ranges[0] != (None, None): + browse_range = browse.ranges[0] else: - range_ = _get_range(field) + browse_range = _get_range(field) for layer_obj in layer_objs: + _create_raster_style( + style or "blackwhite", layer_obj, + browse_range[0], browse_range[1], + browse.nodata_values + ) # NOTE: Only works if browsetype nodata is lower than browse_type_min by at least 1 if browse.show_out_of_bounds_data: # final LUT for min,max 200,700 and nodata=0 should look like: @@ -450,6 +455,27 @@ def make_browse_layer_generator(self, map_obj, browses, map_, "%s,%s" % tuple(range_) ) + else: + browse_iter = enumerate( + zip(browse.field_list, browse.ranges), start=1 + ) + for i, (field, field_range) in browse_iter: + if ranges: + if len(ranges) == 1: + range_ = ranges[0] + else: + range_ = ranges[i - 1] + elif field_range != (None, None): + range_ = field_range + else: + range_ = _get_range(field) + + for layer_obj in layer_objs: + layer_obj.setProcessingKey( + "SCALE_%d" % i, + "%s,%s" % tuple(range_) + ) + elif isinstance(browse, Browse): layer_objs = _create_raster_layer_objs( map_obj, browse.extent, browse.spatial_reference, @@ -525,7 +551,7 @@ def create(self, map_obj, layer): shape_obj = ms.shapeObj.fromWKT(browse.footprint.wkt) outlines_layer_obj.addFeature(shape_obj) - class_obj = _create_geometry_class(vector_style) + class_obj = _create_geometry_class(vector_style, name='outlines') outlines_layer_obj.insertClass(class_obj) return filename_generator @@ -655,7 +681,8 @@ def create(self, map_obj, layer): layer_obj.addFeature(shape_obj) class_obj = _create_geometry_class( - layer.style or 'red', fill_opacity=layer.fill + layer.style or 'red', fill_opacity=layer.fill, + name='outlines', ) layer_obj.insertClass(class_obj) @@ -841,8 +868,10 @@ def _create_polygon_layer(map_obj): def _create_geometry_class(color_name, background_color_name=None, - fill_opacity=None): + fill_opacity=None, name=None): cls_obj = ms.classObj() + if name is not None: + cls_obj.name = name outline_style_obj = ms.styleObj() try: @@ -1001,6 +1030,9 @@ def _create_raster_style_ramp(raster_style, layer, minvalue=0, maxvalue=255, (minvalue + next_perc * interval) )) cls.group = name + cls.name = "%s - %s" % ( + (minvalue + prev_perc * interval), + (minvalue + next_perc * interval)) style = ms.styleObj() style.mincolor = ms.colorObj(*prev_color) diff --git a/eoxserver/render/mapserver/map_renderer.py b/eoxserver/render/mapserver/map_renderer.py index 7aed8ae96..009b87f6e 100644 --- a/eoxserver/render/mapserver/map_renderer.py +++ b/eoxserver/render/mapserver/map_renderer.py @@ -29,6 +29,7 @@ import tempfile from typing import List, Tuple, Type from uuid import uuid4 +from contextlib import contextmanager from eoxserver.contrib import mapserver as ms from eoxserver.contrib import vsi @@ -74,20 +75,122 @@ def get_supported_layer_types(self): def get_supported_formats(self): return getFormatRegistry().getSupportedFormatsWMS() - def render_map(self, render_map: Map): + def getOutputFormat(self, layer_format): + frmt = getFormatRegistry().getFormatByMIME(layer_format) + outputformat_obj = ms.outputFormatObj(frmt.driver) + outputformat_obj.mimetype = frmt.mimeType + + if frmt.defaultExt: + if frmt.defaultExt.startswith('.'): + extension = frmt.defaultExt[1:] + else: + extension = frmt.defaultExt + + outputformat_obj.extension = extension + return outputformat_obj + + def render_map(self, render_map): + layers = render_map.layers + format_ = render_map.format + transparent = render_map.transparent + with self._prepare_map( + layers, + format_, + render_map.width, + render_map.height, + (render_map.bbox), + render_map.crs, + transparent + ) as map_obj: + + if render_map.bgcolor: + map_obj.imagecolor.setHex("#" + render_map.bgcolor.lower()) + else: + map_obj.imagecolor.setRGB(0, 0, 0) + + # actually render the map + image_obj = map_obj.draw() + + try: + image_bytes = image_obj.getBytes() + except Exception: + tmp_name = '/vsimem/%s' % uuid4().hex + image_obj.save(tmp_name, map_obj) + with vsi.open(tmp_name) as f: + image_bytes = f.read() + vsi.unlink(tmp_name) + outputformat_obj = self.getOutputFormat(format_) + extension = outputformat_obj.extension + if extension: + if len(render_map.layers) == 1: + filename = '%s.%s' % ( + render_map.layers[0].name, extension + ) + else: + filename = 'map.%s' % extension + else: + filename = None + + return image_bytes, outputformat_obj.mimetype, filename + + def render_legend(self, legend): + layers = [legend.layer] + with self._prepare_map(layers, legend.format) as map_obj: + outputformat_obj = self.getOutputFormat(legend.format) + legend_obj = map_obj.legend + legend_obj.width = legend.width or 1000 + legend_obj.height = legend.height or 1000 + + image_obj = map_obj.drawLegend() + try: + image_bytes = image_obj.getBytes() + except Exception: + tmp_name = '/vsimem/%s' % uuid4().hex + image_obj.save(tmp_name, map_obj) + with vsi.open(tmp_name) as f: + image_bytes = f.read() + vsi.unlink(tmp_name) + + extension = outputformat_obj.extension + if extension: + if len(layers) == 1: + filename = '%s.%s' % ( + layers[0].name, extension + ) + else: + filename = 'legend.%s' % extension + else: + filename = None + return image_bytes, outputformat_obj.mimetype, filename + + @contextmanager + def _prepare_map( + self, + layers, + format_, + width=1000, + height=500, + bbox=(-180, -90, 180, 90), + crs='EPSG:4326', + transparent=False + ): # TODO: get layer creators for each layer type in the map map_obj = ms.mapObj() + map_obj.setExtent(*bbox) + map_obj.setSize(width, height) + map_obj.setProjection(crs) + map_obj.setConfigOption('MS_NONSQUARE', 'yes') + layers_plus_factories = self._get_layers_plus_factories(layers) + layers_plus_factories_plus_data = [ + (layer, factory, factory.create(map_obj, layer)) + for layer, factory in layers_plus_factories + ] - if render_map.bgcolor: - map_obj.imagecolor.setHex("#" + render_map.bgcolor.lower()) - else: - map_obj.imagecolor.setRGB(0, 0, 0) - - frmt = getFormatRegistry().getFormatByMIME(render_map.format) + frmt = getFormatRegistry().getFormatByMIME(format_) if not frmt: raise MapRenderError( - 'No such format %r' % render_map.format, + 'No such format %r' % format_, code='InvalidFormat', locator='format' ) @@ -95,7 +198,7 @@ def render_map(self, render_map: Map): outputformat_obj = ms.outputFormatObj(frmt.driver) outputformat_obj.transparent = ( - ms.MS_ON if render_map.transparent else ms.MS_OFF + ms.MS_ON if transparent else ms.MS_OFF ) outputformat_obj.mimetype = frmt.mimeType @@ -109,18 +212,6 @@ def render_map(self, render_map: Map): map_obj.setOutputFormat(outputformat_obj) - # - map_obj.setExtent(*render_map.bbox) - map_obj.setSize(render_map.width, render_map.height) - map_obj.setProjection(render_map.crs) - map_obj.setConfigOption('MS_NONSQUARE', 'yes') - - layers_plus_factories = self._get_layers_plus_factories(render_map) - layers_plus_factories_plus_data = [ - (layer, factory, factory.create(map_obj, layer)) - for layer, factory in layers_plus_factories - ] - # log the resulting map if logger.isEnabledFor(logging.DEBUG): with tempfile.NamedTemporaryFile() as f: @@ -129,30 +220,7 @@ def render_map(self, render_map: Map): logger.debug(f.read().decode('ascii')) try: - # actually render the map - image_obj = map_obj.draw() - - try: - image_bytes = image_obj.getBytes() - except Exception: - tmp_name = '/vsimem/%s' % uuid4().hex - image_obj.save(tmp_name, map_obj) - with vsi.open(tmp_name) as f: - image_bytes = f.read() - vsi.unlink(tmp_name) - - extension = outputformat_obj.extension - if extension: - if len(render_map.layers) == 1: - filename = '%s.%s' % ( - render_map.layers[0].name, extension - ) - else: - filename = 'map.%s' % extension - else: - filename = None - - return image_bytes, outputformat_obj.mimetype, filename + yield map_obj finally: # disconnect @@ -165,7 +233,8 @@ def _get_layers_plus_factories( ) -> List[Tuple[Layer, BaseMapServerLayerFactory]]: layers_plus_factories = [] type_to_layer_factory = {} - for layer in render_map.layers: + + for layer in render_map: layer_type = type(layer) if layer_type in type_to_layer_factory: factory = type_to_layer_factory[layer_type] diff --git a/eoxserver/services/ows/config.py b/eoxserver/services/ows/config.py index 9acd8bd3e..8365088d2 100644 --- a/eoxserver/services/ows/config.py +++ b/eoxserver/services/ows/config.py @@ -46,6 +46,7 @@ # 'eoxserver.services.ows.wms.v11.handlers.WMS11GetFeatureInfoHandler', 'eoxserver.services.ows.wms.v13.handlers.WMS13GetCapabilitiesHandler', 'eoxserver.services.ows.wms.v13.handlers.WMS13GetMapHandler', + 'eoxserver.services.ows.wms.v13.handlers.WMS13GetLegendGraphicHandler', # 'eoxserver.services.ows.wms.v13.handlers.WMS13GetFeatureInfoHandler', 'eoxserver.services.ows.wps.v10.getcapabilities.WPS10GetCapabilitiesHandler', diff --git a/eoxserver/services/ows/wms/basehandlers.py b/eoxserver/services/ows/wms/basehandlers.py index 6e23c11c5..800458a07 100644 --- a/eoxserver/services/ows/wms/basehandlers.py +++ b/eoxserver/services/ows/wms/basehandlers.py @@ -39,13 +39,14 @@ from django.conf import settings from django.urls import reverse from django.http import HttpResponse +from django.db.models import Q from eoxserver.core.decoders import kvp, typelist, InvalidParameterException from eoxserver.core.config import get_eoxserver_config from eoxserver.render.map.renderer import ( - get_map_renderer, # get_feature_info_renderer + get_map_renderer, get_legend_renderer, # get_feature_info_renderer ) -from eoxserver.render.map.objects import Map +from eoxserver.render.map.objects import Map, Legend from eoxserver.resources.coverages import crss from eoxserver.resources.coverages import models from eoxserver.services.ows.wms.util import ( @@ -100,8 +101,13 @@ def handle(self, request): service_visibility__visibility=True ), models.Coverage.objects.filter( - service_visibility__service='wms', - service_visibility__visibility=True + Q( + service_visibility__service='wms', + service_visibility__visibility=True + ) | Q( # include all Collections, exclude "WMS-invisible" + # later + collections__isnull=False + ) ) ) @@ -328,13 +334,61 @@ def handle(self, request): layers=layers ) - result_bytes, content_type, filename = \ - feature_info_renderer.render_feature_info(map_) + result_bytes, content_type, filename = feature_info_renderer.render_feature_info(map_) response = HttpResponse(result_bytes, content_type=content_type) if filename: - response['Content-Disposition'] = \ - 'inline; filename="%s"' % filename + response['Content-Disposition'] = 'inline; filename="%s"' % filename + + return response + + +class WMSBaseGetLegendGraphicHandler(object): + methods = ['GET'] + service = "WMS" + request = "GetLegendGraphic" + + def handle(self, request): + decoder = self.get_decoder(request) + layer_name = decoder.layer + style = decoder.style + + if not layer_name: + raise InvalidParameterException("No layers specified", "layers") + + renderer = get_legend_renderer() + + layer_mapper = LayerMapper(renderer.get_supported_layer_types()) + + name, suffix = layer_mapper.split_layer_suffix_name(layer_name) + layer = layer_mapper.lookup_layer( + name, + suffix, + style, + filters_expressions=Q(), + sort_by=None, + time=None, + ranges=None, + bands=None, + wavelengths=None, + elevation=None, + zoom=decoder.scale, + limit=1, + variables={}, + ) + + legend = Legend( + layer=layer, + width=decoder.width, height=decoder.height, format=decoder.format, + ) + + result_bytes, content_type, filename = renderer.render_legend(legend) + + response = HttpResponse(result_bytes, content_type=content_type) + if filename: + response['Content-Disposition'] = 'inline; filename="%s"' % ( + filename + ) return response @@ -407,3 +461,12 @@ def calculate_zoom(bbox, width, height, crs): if zoom < 1: zoom = 1 return zoom + + +class WMSBaseGetLegendGraphicDecoder(kvp.Decoder): + layer = kvp.Parameter(num=1) + style = kvp.Parameter(num='?') + width = kvp.Parameter(type=int, num='?') + height = kvp.Parameter(type=int, num='?') + format = kvp.Parameter(num=1) + scale = kvp.Parameter(type=float, default=0, num='?') diff --git a/eoxserver/services/ows/wms/layermapper.py b/eoxserver/services/ows/wms/layermapper.py index aba1b6365..494eefba6 100644 --- a/eoxserver/services/ows/wms/layermapper.py +++ b/eoxserver/services/ows/wms/layermapper.py @@ -13,8 +13,8 @@ # 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 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, @@ -189,7 +189,9 @@ def get_layer_description(self, eo_object, default_raster_styles, default_geomet return LayerDescription( name=eo_object.identifier, - bbox=eo_object.footprint.extent if eo_object.footprint else None, + bbox=( + eo_object.footprint.extent if eo_object.footprint else None + ), dimensions=dimensions, sub_layers=sub_layers ) @@ -200,13 +202,18 @@ def get_layer_description(self, eo_object, default_raster_styles, default_geomet def lookup_layer(self, layer_name, suffix, style, filters_expressions, sort_by, time, ranges, bands, wavelengths, elevation, - zoom, variables): + zoom, variables, limit=None): """ Lookup the layer from the registered objects. """ reader = LayerMapperConfigReader(get_eoxserver_config()) - limit_products = ( + config_limit = ( reader.limit_products if reader.limit_mode == 'hide' else None ) + if config_limit is not None and limit is not None: + limit = min(limit, config_limit) + elif limit is None: + limit = config_limit + min_render_zoom = reader.min_render_zoom full_name = '%s%s%s' % (layer_name, self.suffix_separator, suffix) @@ -267,7 +274,7 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, browses = [] product_browses = self.iter_products_browses( eo_object, filters_expressions, sort_by, None, style, - limit=limit_products + limit=limit ) has_products = False @@ -303,7 +310,7 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, if not has_products: coverages = self.iter_coverages( - eo_object, filters_expressions, sort_by + eo_object, filters_expressions, sort_by, limit ) if suffix == '': @@ -343,9 +350,10 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, name=full_name, style=reader.color, fill=reader.fill_opacity, footprints=[ - product.footprint for product in self.iter_products( + product.footprint + for product in self.iter_products( eo_object, filters_expressions, sort_by, - limit=limit_products + limit=limit ) ] ) @@ -356,7 +364,7 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, footprints=[ product.footprint for product in self.iter_products( eo_object, filters_expressions, sort_by, - limit=limit_products + limit=limit ) ] ) @@ -366,7 +374,7 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, product_browses_mask = self.iter_products_browses_masks( eo_object, filters_expressions, sort_by, post_suffix, - limit=limit_products + limit=limit ) footprints = [] masks = [] @@ -391,7 +399,7 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, product_browses_mask = self.iter_products_browses_masks( eo_object, filters_expressions, sort_by, post_suffix, - limit=limit_products + limit=limit ) for product, browse, mask, mask_type in product_browses_mask: # When bands/wavelengths are specifically requested, make a @@ -442,7 +450,7 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, footprints=[ product.footprint for product in self.iter_products( eo_object, filters_expressions, sort_by, - limit=limit_products + limit=reader.limit_products ) ], @@ -457,7 +465,7 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, product_browses = self.iter_products_browses( eo_object, filters_expressions, sort_by, suffix, - style, limit=limit_products + style, limit=limit ) for product, browse, browse_type in product_browses: @@ -488,8 +496,8 @@ def lookup_layer(self, layer_name, suffix, style, filters_expressions, masks=[ Mask.from_model(mask_model, mask_type) for _, mask_model in self.iter_products_masks( - eo_object, filters_expressions, sort_by, suffix, - limit=limit_products + eo_object, filters_expressions, sort_by, + suffix, limit=limit ) ] ) @@ -523,7 +531,8 @@ def get_mask_type(self, eo_object, name): # iteration methods # - def iter_coverages(self, eo_object, filters_expressions, sort_by=None): + def iter_coverages(self, eo_object, filters_expressions, sort_by=None, + limit=None): if isinstance(eo_object, models.Mosaic): base_filter = dict(mosaics=eo_object) elif isinstance(eo_object, models.Collection): @@ -532,6 +541,7 @@ def iter_coverages(self, eo_object, filters_expressions, sort_by=None): base_filter = dict(parent_product=eo_object) qs = models.Coverage.objects.filter(filters_expressions, **base_filter) + if sort_by: qs = qs.order_by('%s%s' % ( '-' if sort_by[1] == 'DESC' else '', @@ -542,6 +552,9 @@ def iter_coverages(self, eo_object, filters_expressions, sort_by=None): '-begin_time', '-end_time', 'identifier' ) + if limit is not None: + qs = qs[:limit] + return qs def iter_products(self, eo_object, filters_expressions, sort_by=None, @@ -552,8 +565,6 @@ def iter_products(self, eo_object, filters_expressions, sort_by=None, base_filter = dict(pk=eo_object.pk) qs = models.Product.objects.filter(filters_expressions, **base_filter) - if limit is not None: - qs = qs[:limit] if sort_by: qs = qs.order_by('%s%s' % ( @@ -565,6 +576,9 @@ def iter_products(self, eo_object, filters_expressions, sort_by=None, '-begin_time', '-end_time', 'identifier' ) + if limit is not None: + qs = qs[:limit] + return qs def iter_products_browses(self, eo_object, filters_expressions, sort_by, diff --git a/eoxserver/services/ows/wms/v13/encoders.py b/eoxserver/services/ows/wms/v13/encoders.py index e38acc466..893f95b6f 100644 --- a/eoxserver/services/ows/wms/v13/encoders.py +++ b/eoxserver/services/ows/wms/v13/encoders.py @@ -98,6 +98,12 @@ def encode_capabilities(self, config, ows_url, srss, formats, info_formats, ), self.encode_dcptype(ows_url) ), + WMS("GetLegendGraphic", + WMS("Format", + # TODO + ), + self.encode_dcptype(ows_url) + ), # TODO: describe layer? ), WMS("Exception", diff --git a/eoxserver/services/ows/wms/v13/getlegendgraphic.py b/eoxserver/services/ows/wms/v13/getlegendgraphic.py index fca8cda67..4795c73d1 100644 --- a/eoxserver/services/ows/wms/v13/getlegendgraphic.py +++ b/eoxserver/services/ows/wms/v13/getlegendgraphic.py @@ -39,9 +39,6 @@ class WMS13GetLegendGraphicHandler(Component): - implements(ServiceHandlerInterface) - implements(GetServiceHandlerInterface) - renderer = UniqueExtensionPoint(WMSLegendGraphicRendererInterface) service = "WMS" diff --git a/eoxserver/services/ows/wms/v13/handlers.py b/eoxserver/services/ows/wms/v13/handlers.py index 68f188731..6f1e7a890 100644 --- a/eoxserver/services/ows/wms/v13/handlers.py +++ b/eoxserver/services/ows/wms/v13/handlers.py @@ -13,8 +13,8 @@ # 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 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, @@ -30,7 +30,8 @@ from eoxserver.services.ows.wms.util import parse_bbox from eoxserver.services.ows.wms.exceptions import InvalidCRS from eoxserver.services.ows.wms.basehandlers import ( - WMSBaseGetCapabilitiesHandler, WMSBaseGetMapHandler, WMSBaseGetMapDecoder + WMSBaseGetCapabilitiesHandler, WMSBaseGetMapHandler, WMSBaseGetMapDecoder, + WMSBaseGetLegendGraphicDecoder, WMSBaseGetLegendGraphicHandler ) from eoxserver.services.ows.wms.v13.encoders import WMS13Encoder @@ -73,3 +74,15 @@ def bbox(self): crs = kvp.Parameter(num=1) srs = property(lambda self: self.crs) + + +class WMS13GetLegendGraphicHandler(WMSBaseGetLegendGraphicHandler): + service = ("WMS", None) + versions = ("1.3.0", "1.3") + + def get_decoder(self, request): + return WMS13GetLegendGraphicDecoder(request.GET) + + +class WMS13GetLegendGraphicDecoder(WMSBaseGetLegendGraphicDecoder): + pass