source: qesdi/wms_ddc_vis/trunk/lib/wms_ddc_vis/controllers/coastwms.py @ 6020

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

Added a title for the coastwms endpoint.

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