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

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

Moved QESDI tree from DCIP repository
 http://proj.badc.rl.ac.uk/svn/dcip/qesdi@3900.

Line 
1import logging
2import Image
3import thread
4import re
5from StringIO import StringIO
6from sets import Set
7from matplotlib.cm import get_cmap
8from pylons import request, response, c
9from routes import url_for
10from routes.util import GenerationException
11from genshi.template import NewTextTemplate
12from cows.pylons.wms_controller import WMSController
13from cows.model.wms import WmsDatasetSummary, Dimension, DataURL
14from cows.model import PossibleValues, WGS84BoundingBox, BoundingBox, Contents
15from cows.pylons import ows_controller
16from cows.exceptions import *
17from cows import bbox_util
18
19from geoplot.layer_drawer_grid import LayerDrawerGrid
20
21from cows.service.imps.csmlbackend.wms_csmllayer import CSMLwmsLayerMapper
22
23from wms_ddc_vis.model.ddc_layer_mapper import DDCLayerMapper
24log = logging.getLogger(__name__)
25
26class CoastwmsController(ows_controller.OWSController):
27    layerMapper = DDCLayerMapper()
28
29    #layers = {}   
30    _pilImageFormats = {
31        'image/png': 'PNG',
32        'image/jpg': 'JPEG',
33        'image/gif': 'GIF',
34        'image/tiff': 'TIFF'
35        }
36   
37    _layerSlabCache = {}
38
39    #-------------------------------------------------------------------------
40    # Attributes required by OWSController
41
42    service = 'WMS'
43    owsOperations = (ows_controller.OWSController.owsOperations +
44        ['GetMap', 'GetContext', 'GetLegend', 'GetFeatureInfo', 'GetInfo'])
45    validVersions = ['1.1.1', '1.3.0']
46
47    #-------------------------------------------------------------------------
48
49    def __before__(self, **kwargs):
50        """
51        This default implementation of __before__() will pass all routes
52        arguments to the layer mapper to retrieve a list of layers for
53        this WMS.
54
55        It will be called automatically by pylons before each action method.
56
57        @todo: The layer mapper needs to come from somewhere.
58
59        """
60        pass
61        #self.updateSequence = "hello"
62#        log.debug("loading layers")
63        #print self.layers
64#        self.layers = self.layerMapper.map(**kwargs)
65   
66        #-------------------------------------------------------------------------
67        # Methods implementing stubs in OWSController
68
69    def _renderCapabilities(self, version, format):
70        if format == 'application/json':
71            t = ows_controller.templateLoader.load('wms_capabilities_json.txt',
72                                                   cls=NewTextTemplate)
73        elif version == '1.1.1':
74            t = ows_controller.templateLoader.load('wms_capabilities_1_1_1.xml')
75        elif version == '1.3.0':
76            t = ows_controller.templateLoader.load('wms_capabilities_1_3_0.xml')
77        else:
78            # We should never get here!  The framework should raise an exception before now.
79            raise RuntimeError("Version %s not supported" % version)
80       
81        return t.generate(c=c).render()
82
83    def _loadCapabilities(self):
84        """
85        @note: Assumes self.layers has already been created by __before__().
86
87        """
88        #!TODO: Add json format to GetCapabilities operation
89
90        ows_controller.addOperation('GetMap', formats=self._pilImageFormats.keys())
91        ows_controller.addOperation('GetContext', formats=['text/xml', 'application/json'])
92        ows_controller.addOperation('GetLegend',
93                                    formats=['image/png'])
94        ows_controller.addOperation('GetInfo')
95       
96        featureInfoFormats = Set()
97
98        log.debug('Loading capabilities contents')
99        c.capabilities.contents = Contents()
100        for layerName, layer in self.layers.items():
101            log.debug('LayerName: %s' % layerName)
102            log.debug('Loading layer %s' % layerName)
103
104            wgs84BBox = WGS84BoundingBox(layer.wgs84BBox[:2],
105                                         layer.wgs84BBox[2:])
106            # Get CRS/BBOX pairs
107            bboxObjs = []
108            for crs in layer.crss:
109                bbox = layer.getBBox(crs)
110                bboxObjs.append(BoundingBox(bbox[:2], bbox[2:], crs=crs))
111            # Get dimensions
112            dims = {}
113            for dimName, dim in layer.dimensions.items():
114                dimParam = self._mapDimToParam(dimName)
115                dims[dimParam] = Dimension(valuesUnit=dim.units,
116                                          unitSymbol=dim.units,
117                                          possibleValues=
118                                            PossibleValues.fromAllowedValues(dim.extent))
119            # Does the layer implement GetFeatureInfo?
120            if layer.featureInfoFormats:
121                queryable = True
122                featureInfoFormats.union_update(layer.featureInfoFormats)
123            else:
124                queryable = False
125               
126            #URL to WCS - uses named route 'wcsroute'
127            #TODO: Allow for a WCS blacklist to opt out of providing dataurls for certain datasets?
128            #TODO: How to make this more configurable - what if WCS is not coupled with WMS?
129            try:
130                version='1.0.0' #wcs version
131                wcsbaseurl=url_for('wcsroute', fileoruri=c.fileoruri,qualified=True)+'?'
132                dataURLs=[DataURL(format='WCS:CoverageDescription', onlineResource='%sService=WCS&Request=DescribeCoverage&Coverage=%s&Version=%s'%(wcsbaseurl, layerName, version))]
133            except GenerationException:
134                log.info("dataURLs not populated: could not generate WCS url with url_for('wcsroute', filedoruri=%s,qualified=True)"%c.fileoruri)
135                dataURLs=[]
136            # Create the cows object
137            ds = WmsDatasetSummary(identifier=layerName,
138                                   titles=[layer.title],
139                                   CRSs=layer.crss,
140                                   wgs84BoundingBoxes=[wgs84BBox],
141                                   boundingBoxes=bboxObjs,
142                                   abstracts=[layer.abstract],
143                                   dimensions=dims,
144                                   queryable=queryable,
145                                   dataURLs=dataURLs)
146
147            # Stuff that should go in the capabilities tree eventually
148            ds.legendSize = layer.legendSize
149            ds.legendFormats = ['image/png']
150
151            c.capabilities.contents.datasetSummaries.append(ds)
152
153        # Add this operation here after we have found all formats
154        ows_controller.addOperation('GetFeatureInfo',
155                                    formats = list(featureInfoFormats))
156
157    def _getLayerParamInfo(self, paramName='layers'):
158        """
159        Retrieve the layers parameter enforcing the rule of only
160        selecting one layer.
161
162        @param paramName: Overrides the query string parameter name to
163            look for.  This is usefull for implementing GetFeatureInfo.
164
165        """
166        layerName = self.getOwsParam(paramName)
167
168        # Select the first layer if several are requested.
169        # This plays nicer with mapClient.
170        if ',' in layerName:
171            #layerName = layerName.split(',')[0]
172            raise InvalidParameterValue(
173                'Multi-layer GetLegend requests are not supported', 'layers')
174        try:
175            layerObj = self.layers[layerName]
176        except KeyError:
177            raise InvalidParameterValue('Layer %s not found' % layerName,
178                                        paramName)
179
180        return layerName, layerObj
181
182    def _getLayerParam(self, paramName='layers'):
183        """
184        Retrieve the layers parameter enforcing the rule of only
185        selecting one layer.
186
187        @param paramName: Overrides the query string parameter name to
188            look for.  This is usefull for implementing GetFeatureInfo.
189
190        """
191        layers = {}
192        layerNames = self.getOwsParam(paramName)
193
194        # Select the first layer if several are requested.
195        # This plays nicer with mapClient.
196        #if ',' in layerName:
197        layerNames = layerNames.split(',')
198        #raise InvalidParameterValue(
199        #    'Multi-layer GetMap requests are not supported', 'layers')
200        for layerName in layerNames:
201            try:
202                layerObj = self.layers[layerName]
203                layers[layerName] = layerObj
204            except KeyError:
205                raise InvalidParameterValue('Layer %s not found' % layerName,
206                                        paramName)
207
208        #return layerName, layerObj
209        return layers
210
211    def _getFormatParam(self):
212        format = self.getOwsParam('format', default='image/png')
213        if format not in self._pilImageFormats:
214            raise InvalidParameterValue(
215                'Format %s not supported' % format, 'format')
216
217        return format
218
219    _escapedDimNames = ['width', 'height', 'version', 'request',
220                        'layers', 'styles', 'crs', 'srs', 'bbox',
221                        'format', 'transparent', 'bgcolor',
222                        'exceptions']
223
224    def _getDimValues(self, layerObj):
225        dimValues = {}
226        for dimName, dim in layerObj.dimensions.items():
227            defaultValue = dim.extent[0]
228            escapedDimName=self._mapDimToParam(dimName)
229            dimValues[escapedDimName] = self.getOwsParam(escapedDimName,
230                                                  default=defaultValue)
231        return dimValues
232
233    def _mapDimToParam(self, dimName):
234        """
235        Dimension names might clash with WMS parameter names, making
236        them inaccessible in WMS requests.  This method maps a
237        dimension name to a parameter name that appears in the
238        capabilities document and WMS requests.
239
240        """
241        if dimName.lower() in self._escapedDimNames:
242            return dimName+'_dim'
243        else:
244            return dimName
245       
246    def _mapParamToDim(self, dimParam):
247        """
248        Maps a dimension parameter name to it's real dimension name.
249
250        @see: _mapDimToParam()
251
252        """
253        try:
254            dimName = re.match(r'(.*)_dim$', dimParam).group(1)
255            if dimName.lower() in self._escapedDimNames:
256                return dimName
257            else:
258                return dimParam
259        except AttributeError:
260            return dimParam
261
262
263    def _retrieveSlab(self, layerObj, srs, dimValues, renderOpts):
264        # Find the slab in the cache first
265        cacheKey = layerObj.getCacheKey(srs, dimValues)
266        slab = self._layerSlabCache.get(cacheKey)
267        if slab is None:
268            slab = layerObj.getSlab(srs, dimValues, renderOpts)
269            if cacheKey is not None:
270                self._layerSlabCache[cacheKey] = slab
271
272        return slab
273
274    #-------------------------------------------------------------------------
275    # OWS Operation methods
276   
277    def GetMap(self):
278
279        # Housekeeping
280        version = self.getOwsParam('version', default=self.validVersions[0])
281        if version not in self.validVersions:
282            raise InvalidParameterValue('Version %s not supported' % version,
283                                        'version')
284        styles = self.getOwsParam('styles', default='')
285        transparent = self.getOwsParam('transparent', default='FALSE')
286        bgcolor = self.getOwsParam('bgcolor', default='0xFFFFFF')
287
288        # Layer handling
289        #layerName, layerObj = self._getLayerParam()
290#        layers = self._getLayerParam()
291#        log.debug('GetMap request for layer(s) %s  #%s ' % (layers, thread.get_ident(),) )
292        layerName = self.getOwsParam('layers')
293       
294        transparent = self.getOwsParam('transparent').lower() == 'true'
295       
296       
297        # Coordinate parameters
298        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
299        width = int(self.getOwsParam('width'))
300        height = int(self.getOwsParam('height'))
301
302        if version == '1.1.1':
303            srs = self.getOwsParam('srs')
304        else:
305            srs = self.getOwsParam('crs')
306
307        #if srs not in layerObj.crss:
308         #   raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
309
310        # Get format
311        format = self.getOwsParam('format')
312        if format not in self._pilImageFormats:
313            raise InvalidParameterValue(
314                'Format %s not supported' % format, 'format')
315
316
317        ldg = LayerDrawerGrid()
318
319        xLimits = (bbox[0], bbox[2])
320        yLimits = (bbox[1], bbox[3])       
321        finalImg = ldg.makeImage(xLimits, yLimits, width, height, resolution=layerName,
322                                 transparent=transparent)     
323     
324       
325        # IE < 7 doesn't display the alpha layer right.  Here we sniff the
326        # user agent and remove the alpha layer if necessary.
327        try:
328            ua = request.headers['User-Agent']
329        except:
330            pass
331        else:
332            if 'MSIE' in ua and 'MSIE 7' not in ua:
333                finalImg = finalImg.convert('RGB')
334
335        buf = StringIO()
336        finalImg.save(buf, self._pilImageFormats[format])
337
338        response.headers['Content-Type'] = format
339        response.write(buf.getvalue())
340
341
342    def GetContext(self):
343        """
344        Return a WebMap Context document for a given set of layers.
345
346        """
347        # Parameters
348        layers = self.getOwsParam('layers', default=None)
349        format = self.getOwsParam('format', default='text/xml')
350
351        # Filter self.layers for selected layers
352        if layers is not None:
353            newLayerMap = {}
354            for layerName in layers.split(','):
355                try:
356                    newLayerMap[layerName] = self.layers[layerName]
357                except KeyError:
358                    raise InvalidParameterValue('Layer %s not found' % layerName,
359                                                'layers')
360                   
361            self.layers = newLayerMap
362
363        # Automatically select the first bbox/crs for the first layer
364        aLayer = self.layers.values()[0]
365        crs = aLayer.crss[0]
366        bb = aLayer.getBBox(crs)
367        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
368
369        # Initialise as if doing GetCapabilities
370        ows_controller.initCapabilities()
371        self._loadCapabilities()
372
373        if format == 'text/xml':
374            response.headers['Content-Type'] = format
375            t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
376            return t.generate(c=c).render()
377        elif format == 'application/json':
378            response.headers['Content-Type'] = format
379            t = ows_controller.templateLoader.load('wms_context_json.txt',
380                                                   cls=NewTextTemplate)
381            return t.generate(c=c).render()
382        else:
383            raise InvalidParameterValue('Format %s not supported' % format)
384
385    def GetFeatureInfo(self):
386        # Housekeeping
387        version = self.getOwsParam('version', default=self.validVersions[0])
388        if version not in self.validVersions:
389            raise InvalidParameterValue('Version %s not supported' % version,
390                                        'version')
391
392        # Coordinate parameters
393        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
394        width = int(self.getOwsParam('width'))
395        height = int(self.getOwsParam('height'))
396     
397        # Get pixel location
398        i = int(self.getOwsParam('i'))
399        j = int(self.getOwsParam('j'))
400
401        # Translate to geo-coordinates
402        x, y = bbox_util.pixelToGeo(i, j, bbox, width, height)
403        #start preparing GetFeatureInfo response. Assumes "HTML" output format
404
405        htmlResponse = "<html><body><p> <b>Feature Information about pixel position: "+self.getOwsParam('i')+","+self.getOwsParam('j')+"/geo position: "+str(x)+","+str(y) +"<b/></p>"
406       
407       
408        layers = self._getLayerParam('query_layers')
409        #Adjusts response for multiple layers
410        if len(layers) > 1:
411            htmlResponse = htmlResponse+" Multiple possible features found as follows:"
412 
413        htmlResponse = htmlResponse+"<ul>"
414       
415        format = self.getOwsParam('info_format', default='text/html')
416        for layerName, layerObj in layers.iteritems():
417            log.debug('Format: %s' % format)
418            log.debug('Title: %s' % layerObj.title)
419            log.debug('FeatureInfoFormats: %s' % layerObj.featureInfoFormats)
420           
421            if format not in layerObj.featureInfoFormats:
422                raise InvalidParameterValue('Layer %s does not support GetFeatureInfo in format %s' %(layerName, format), 'info_format')
423
424        if version == '1.1.1':
425            srs = self.getOwsParam('srs')
426        else:
427            srs = self.getOwsParam('crs')
428
429        if srs not in layerObj.crss:
430            raise InvalidParameterValue('Layer %s does not support SRS %s' %
431                                        (layerName, srs))
432
433        # Dimension handling
434        dimValues = {}
435        for dimName, dim in layerObj.dimensions.items():
436            defaultValue = dim.extent[0]
437            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
438       
439        response.headers['Content-Type'] = format
440        response.write(layerObj.getFeatureInfo(format, srs, (x, y), dimValues))
441
442    def GetLegend(self):
443        """
444        Return an image of the legend.
445
446        """
447        # Parameters
448        layerName, layerObj = self._getLayerParamInfo()
449        format = self._getFormatParam()
450
451        # This hook alows extra arguments to be passed to the layer backend.  It
452        # is required for UKCP.
453        renderOpts = dict(request_params=self._owsParams)
454
455        img = layerObj.getLegendImage(renderOpts=renderOpts)
456
457        buf = StringIO()
458        img.save(buf, self._pilImageFormats[format])
459
460        response.headers['Content-Type'] = format
461        response.write(buf.getvalue())
462
463
464    def GetInfo(self):
465        from pprint import pformat
466        request.headers['Content-Type'] = 'text/ascii'
467        response.write('Some info about this service\n')
468        for layer in model.ukcip02.layers:
469            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
470
471           
Note: See TracBrowser for help on using the repository browser.