source: cowsserver/trunk/lib/wms_ddc_vis/controllers/coastwms.py @ 6071

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cowsserver/trunk/lib/wms_ddc_vis/controllers/coastwms.py@6071
Revision 6071, 16.9 KB checked in by spascoe, 10 years ago (diff)

Changed references to wms_ddc_vis to cowsserver. NOTE: filename changes are TODO therefore this will not run.

RevLine 
[5403]1import logging
[5752]2
[5535]3
[5403]4from StringIO import StringIO
5from sets import Set
[5879]6import genshi.template 
[5403]7from cows.pylons import ows_controller
[6041]8from pylons import config
[5734]9from cows.service.imps.data_reader_geoplot_backend.rendering_option import RenderingOption
10from cows.service.imps.data_reader_geoplot_backend.slab_options_parser import SlabOptionsParser
11from cows.service.imps.data_reader_geoplot_backend.slab_options_json_generator import SlabJSONGenerator
[5879]12from cows.model import WGS84BoundingBox, BoundingBox, Contents
13from cows.exceptions import InvalidParameterValue
[5734]14
[5879]15from routes import url_for
16from cows.model.wms import WmsDatasetSummary
[6071]17from cowsserver.lib.wms_request_log_utils import buildLogString, wms_request_logger
[5734]18
[5647]19import time
[5978]20import numpy
21from paste.deploy.converters import asbool
[5403]22
[5978]23import pylons
24
[5479]25from geoplot.layer_drawer_coastline import LayerDrawerCoastlines
[5978]26import matplotlib.colors
[5403]27
[6071]28from cowsserver.lib.image_import import Image
29from cowsserver.lib.base import request, response, c
[5879]30
31from cows.model.wms import MetadataURL
[5535]32from cows.xml.iso19115_subset import OnlineResource
33
[6071]34from cowsserver.lib.modified_beaker_cache_decorator import beaker_cache
[5773]35
[5403]36log = logging.getLogger(__name__)
37
38class CoastwmsController(ows_controller.OWSController):
39
[5978]40    coastlineRenderingOptions = [
41        RenderingOption('coastline_colour', "Coastline Colour" ,str , 'black'),
[5762]42        RenderingOption('coastline_width', "Coastline Width" ,float , 0.5 ),
[5734]43        RenderingOption('resolution', "Coastline Resolution" ,str , "auto", ["coarse","low", "intermediate", "high", "auto"]  ),           
44    ]
[5978]45   
46    riverRenderingOptions = [
47        RenderingOption('resolution', "River Resolution" ,str , "auto", ["coarse","low", "intermediate", "high", "auto"]  ),                                                             
48        RenderingOption('river_colour', "River Colour" ,str , 'blue'), 
49    ]
50   
51    landMassRenderingOptions = [
52        RenderingOption('resolution', "Coastline Resolution" ,str , "auto", ["coarse","low", "intermediate", "high", "auto"]  ),                               
53        RenderingOption('land_colour', "Land Colour" ,str , 'green'),                               
54    ]
[5734]55       
56
[5403]57    #layers = {}   
58    _pilImageFormats = {
59        'image/png': 'PNG',
60        'image/jpg': 'JPEG',
61        'image/gif': 'GIF',
62        'image/tiff': 'TIFF'
63        }
64   
65    _layerSlabCache = {}
66
67    #-------------------------------------------------------------------------
68    # Attributes required by OWSController
69
70    service = 'WMS'
71    owsOperations = (ows_controller.OWSController.owsOperations +
[5535]72        ['GetMap', 'GetContext', 'GetLegend', 'GetFeatureInfo', 'GetInfo', 'GetDisplayOptions'])
73   
[5403]74    validVersions = ['1.1.1', '1.3.0']
75
76
[5734]77    def __before__(self, **kwargs):
78        wms_request_logger.info( buildLogString(request) )
[5403]79
80    def _renderCapabilities(self, version, format):
81        if format == 'application/json':
82            t = ows_controller.templateLoader.load('wms_capabilities_json.txt',
[5879]83                                                   cls=genshi.template.NewTextTemplate)
[5403]84        elif version == '1.1.1':
85            t = ows_controller.templateLoader.load('wms_capabilities_1_1_1.xml')
86        elif version == '1.3.0':
87            t = ows_controller.templateLoader.load('wms_capabilities_1_3_0.xml')
88        else:
89            # We should never get here!  The framework should raise an exception before now.
90            raise RuntimeError("Version %s not supported" % version)
91       
92        return t.generate(c=c).render()
93
94    def _loadCapabilities(self):
95        """
96        @note: Assumes self.layers has already been created by __before__().
97
98        """
99        #!TODO: Add json format to GetCapabilities operation
100
101        ows_controller.addOperation('GetMap', formats=self._pilImageFormats.keys())
102        ows_controller.addOperation('GetContext', formats=['text/xml', 'application/json'])
[5479]103
[5403]104       
105        featureInfoFormats = Set()
106
107        log.debug('Loading capabilities contents')
108        c.capabilities.contents = Contents()
[5535]109       
[5978]110        layers = ( ('coastline', 'coastline', 'Coast Outline'), 
111                   ('rivers', 'rivers', 'Rivers'),
112                   ('landmass', 'landmass', 'Land Mass'),)
[5535]113       
114       
115        for layerName, title,  abstract in layers:
[5403]116            log.debug('LayerName: %s' % layerName)
117            log.debug('Loading layer %s' % layerName)
118
[5535]119            wgs84BBox = WGS84BoundingBox((-180,-90), (180,90))
120
[5403]121            # Get CRS/BBOX pairs
122            bboxObjs = []
[5535]123            for crs in ('EPSG:4326', 'CRS:84', 'WGS84'):
124                bbox = [-180,-90,180,90]
[5403]125                bboxObjs.append(BoundingBox(bbox[:2], bbox[2:], crs=crs))
126               
[5535]127
128               
[5403]129            #URL to WCS - uses named route 'wcsroute'
130            #TODO: Allow for a WCS blacklist to opt out of providing dataurls for certain datasets?
131            #TODO: How to make this more configurable - what if WCS is not coupled with WMS?
[5535]132#            try:
133#                version='1.0.0' #wcs version
134#                wcsbaseurl=url_for('wcsroute', fileoruri=c.fileoruri,qualified=True)+'?'
135#                dataURLs=[DataURL(format='WCS:CoverageDescription', onlineResource='%sService=WCS&Request=DescribeCoverage&Coverage=%s&Version=%s'%(wcsbaseurl, layerName, version))]
136#            except GenerationException:
137#                log.info("dataURLs not populated: could not generate WCS url with url_for('wcsroute', filedoruri=%s,qualified=True)"%c.fileoruri)
138#                dataURLs=[]
139           
[6041]140            onlineRes = OnlineResource(self._getIndexActionURL() +\
[5535]141                                    "?request=GetDisplayOptions&layers=%s" % layerName)
142            metadataURL = MetadataURL(metadataType='display_options', format='application/json', onlineResource=onlineRes)
143       
[5403]144            # Create the cows object
145            ds = WmsDatasetSummary(identifier=layerName,
[5535]146                                   titles=[title],
147                                   CRSs=('EPSG:4326', 'CRS:84', 'WGS84'),
[5403]148                                   wgs84BoundingBoxes=[wgs84BBox],
149                                   boundingBoxes=bboxObjs,
[5535]150                                   abstracts=[abstract],
151                                   dimensions={},
152                                   queryable=False,
153                                   dataURLs=[],
154                                   styles=[],
155                                   metadataURLs=[metadataURL])
[5403]156
157            # Stuff that should go in the capabilities tree eventually
[5535]158            ds.legendSize = (630,80)
[5403]159            ds.legendFormats = ['image/png']
160
161            c.capabilities.contents.datasetSummaries.append(ds)
[6020]162       
163        c.capabilities.serviceIdentification.titles=['coastline']
164       
[5403]165        # Add this operation here after we have found all formats
166        ows_controller.addOperation('GetFeatureInfo',
167                                    formats = list(featureInfoFormats))
168
169
170    _escapedDimNames = ['width', 'height', 'version', 'request',
171                        'layers', 'styles', 'crs', 'srs', 'bbox',
172                        'format', 'transparent', 'bgcolor',
173                        'exceptions']
174
175
176    def _mapDimToParam(self, dimName):
177        """
178        Dimension names might clash with WMS parameter names, making
179        them inaccessible in WMS requests.  This method maps a
180        dimension name to a parameter name that appears in the
181        capabilities document and WMS requests.
182
183        """
184        if dimName.lower() in self._escapedDimNames:
185            return dimName+'_dim'
186        else:
187            return dimName
188       
[5978]189    #@beaker_cache()
[5647]190    def GetMap(self):       
191        st = time.time()
[5403]192
[5978]193        # Layer handling
194        layerName = self.getOwsParam('layers')
[5647]195       
[5978]196        if layerName == 'rivers':
197            renderingOpts = CoastwmsController.riverRenderingOptions
198        elif layerName == 'landmass':
199            renderingOpts = CoastwmsController.landMassRenderingOptions
200        else:
201            renderingOpts = CoastwmsController.coastlineRenderingOptions
202           
203        parser = SlabOptionsParser(renderingOpts, request.params)
[5734]204
205       
[5403]206        # Housekeeping
207        version = self.getOwsParam('version', default=self.validVersions[0])
208        if version not in self.validVersions:
209            raise InvalidParameterValue('Version %s not supported' % version,
210                                        'version')
211        styles = self.getOwsParam('styles', default='')
212        bgcolor = self.getOwsParam('bgcolor', default='0xFFFFFF')
[5978]213        transparent = self.getOwsParam('transparent', default='FALSE').lower() == 'true'
[5403]214       
215        # Coordinate parameters
216        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
217        width = int(self.getOwsParam('width'))
218        height = int(self.getOwsParam('height'))
219
220        if version == '1.1.1':
221            srs = self.getOwsParam('srs')
222        else:
223            srs = self.getOwsParam('crs')
224
[5734]225        # Get format
[5403]226        format = self.getOwsParam('format')
227        if format not in self._pilImageFormats:
228            raise InvalidParameterValue(
229                'Format %s not supported' % format, 'format')
230
[5978]231
[5734]232        longResolution = parser.getOption('resolution')
[5978]233        resMap = {'coarse':'c','low':'l','intermediate':'i','high':'h', 'full':'f', 'auto':None}
[5734]234        resolution = resMap[longResolution]
[5978]235       
[5762]236        #make the colour compatable with matplotlib
237        if bgcolor.find('0x') == 0:
238            bgcolor = '#' + bgcolor[2:]
[5978]239       
240        log.debug("bgcolor = %s" % (bgcolor,))
241       
242        ldgArgs = {'transparent':transparent,
243                   'resolution':resolution,
244                   'bgcolour':bgcolor}
245       
246        convertColour = None
247       
248       
249        # if transparency is true then matplotlib will make the image all one colour
250        # and use the alpha channel to draw the lines, to aid in caching we can
251        # request the image in black and then convert the colour afterwards (so
252        # only the black coloured image is cached.)
253       
254        if transparent == True:
255           
256            if layerName  == 'rivers':
257                convertColour = self._colourToRGB(parser.getOption('river_colour'), 'blue')
258                ldgArgs['riverColour'] = 'black'
259                ldgArgs['coastlineColour'] = None
260               
261            elif layerName == 'landmass':
262                convertColour = self._colourToRGB(parser.getOption('land_colour'), 'green')
263                ldgArgs['landColour'] = 'black'
264                ldgArgs['coastlineColour'] = None
265            else:
266                convertColour = self._colourToRGB(parser.getOption('coastline_colour'), 'black')
267                ldgArgs['coastlineColour'] = 'black'
268                ldgArgs['coastlineWidth']  = parser.getOption('coastline_width')
269        else:
270           
271            if layerName == 'rivers':
272                ldgArgs['riverColour'] = self._colourToRGB(parser.getOption('river_colour'), 'blue')
273                ldgArgs['coastlineColour'] = None
274               
275            elif layerName == 'landmass':
276                ldgArgs['landColour'] = self._colourToRGB(parser.getOption('land_colour'), 'green')
277                ldgArgs['coastlineColour'] = None
278            else:
279                ldgArgs['coastlineColour'] = self._colourToRGB(parser.getOption('coastline_colour'), 'black')
280                ldgArgs['coastlineWidth']  = parser.getOption('coastline_width')           
281           
282           
283           
284           
[5403]285        xLimits = (bbox[0], bbox[2])
[5978]286        yLimits = (bbox[1], bbox[3])
287       
288        finalImg = self._getLDCImage(ldgArgs, xLimits, yLimits, width, height)
289       
290        if convertColour != None:
291           
292            log.debug("convertColour = %s" % (convertColour,))
293            st = time.time()
294            imgArr = numpy.asarray(finalImg)
[5647]295
[5978]296            arr = numpy.zeros(imgArr.shape, dtype=numpy.uint8)
297            arr[:,:,0] = int(round(convertColour[0] * 255.0))
298            arr[:,:,1] = int(round(convertColour[1] * 255.0))
299            arr[:,:,2] = int(round(convertColour[2] * 255.0))
300            arr[:,:,3] =  imgArr[:,:,3]
301           
302            finalImg = Image.fromarray(arr, 'RGBA')
303            log.debug("converted in %ss" % (time.time() - st,))
[5403]304       
305        # IE < 7 doesn't display the alpha layer right.  Here we sniff the
306        # user agent and remove the alpha layer if necessary.
307        try:
308            ua = request.headers['User-Agent']
[5773]309            #log.debug("ua = %s" % (ua,))
[5403]310        except:
311            pass
312        else:
[5742]313            if 'MSIE 6.0' in ua:
[5403]314                finalImg = finalImg.convert('RGB')
315
316        buf = StringIO()
317        finalImg.save(buf, self._pilImageFormats[format])
318
319        response.headers['Content-Type'] = format
320        response.write(buf.getvalue())
[5647]321       
[5773]322        #log.debug("got coastline in %s" % (time.time() - st,))
[5403]323
324    def GetContext(self):
325        """
326        Return a WebMap Context document for a given set of layers.
327
328        """
329        # Parameters
330        layers = self.getOwsParam('layers', default=None)
331        format = self.getOwsParam('format', default='text/xml')
332
333        # Filter self.layers for selected layers
334        if layers is not None:
335            newLayerMap = {}
336            for layerName in layers.split(','):
337                try:
338                    newLayerMap[layerName] = self.layers[layerName]
339                except KeyError:
340                    raise InvalidParameterValue('Layer %s not found' % layerName,
341                                                'layers')
342                   
343            self.layers = newLayerMap
344
345        # Automatically select the first bbox/crs for the first layer
346        aLayer = self.layers.values()[0]
347        crs = aLayer.crss[0]
348        bb = aLayer.getBBox(crs)
349        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
350
351        # Initialise as if doing GetCapabilities
352        ows_controller.initCapabilities()
353        self._loadCapabilities()
354
355        if format == 'text/xml':
[5479]356           
[5403]357            response.headers['Content-Type'] = format
358            t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
359            return t.generate(c=c).render()
[5479]360       
[5403]361        elif format == 'application/json':
[5479]362           
[5403]363            response.headers['Content-Type'] = format
364            t = ows_controller.templateLoader.load('wms_context_json.txt',
[5879]365                                                   cls=genshi.template.NewTextTemplate)
[5403]366            return t.generate(c=c).render()
[5479]367       
[5403]368        else:
369            raise InvalidParameterValue('Format %s not supported' % format)
370
[5535]371    def GetDisplayOptions(self):
372       
[5978]373        layer = self.getOwsParam('layers', default=None)
[5535]374       
[5978]375       
376        log.debug("layer = %s" % (layer,))
377        if layer == 'rivers':
378            generator = SlabJSONGenerator({'':CoastwmsController.riverRenderingOptions})
379        elif layer == 'landmass':
380            generator = SlabJSONGenerator({'':CoastwmsController.landMassRenderingOptions})
381        else:
382            generator = SlabJSONGenerator({'':CoastwmsController.coastlineRenderingOptions})
383           
[5535]384        request.headers['Content-Type'] = 'application/json'
[5734]385        response.write( generator.generateJSON() )
[5535]386
[5978]387    def _colourToRGB(self, colour, default=None):
388       
389        if colour == None:
390            return default
391       
392        try:
393            if colour.find('0x') == 0:
394                colour = '#' + colour[2:]
395               
396            convertColour = matplotlib.colors.colorConverter.to_rgb(colour)
397        except:
398            log.warning("Error converting colour %s to rgb value." % (colour,))
399            return default
400        else:
401            return convertColour
402
403    def _getLDCImage(self, ldgArgs, xLimits, yLimits, width, height, expire='never'):
404       
405        dic = ldgArgs.copy()
406        dic['xLimits'] = xLimits
407        dic['yLimits'] = yLimits
408        dic['width'] = width
409        dic['height'] = height
410       
411        cache_key = repr(dic)
412        log.debug("cache_key = %s" % (cache_key,))
413
414        def create_func():
415            log.debug("generating, cache_key = %s" % (cache_key,))
416           
417            log.debug("ldgArgs = %s" % (ldgArgs,))
418            ldg = LayerDrawerCoastlines(**ldgArgs)
419            return ldg.makeImage(xLimits, yLimits, width, height)
420   
421        enabled = pylons.config.get("cache_enabled", "True")
[6020]422
[5978]423        if not asbool(enabled):
424            log.debug("Caching disabled, skipping cache lookup")
425            return create_func()
426       
427        my_cache = pylons.cache.get_cache('LayerDrawerCoastlineCache')
428       
429        if expire == "never":
430            cache_expire = None
431        else:
432            cache_expire = expire
433       
434        img = my_cache.get_value(cache_key, createfunc=create_func, 
435                                      expiretime=cache_expire,)
436     
[6041]437        return img
438
439    def _getIndexActionURL(self):
440        """
441        Uses the pylons config to build a url for the index action of this contoller.
442        """
443               
444        indexURL = url_for(qualified=True, action='index')
445        return indexURL   
Note: See TracBrowser for help on using the repository browser.