source: cows/branches/wcsmerge/cows/pylons/wcs_controller.py @ 4574

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows/branches/wcsmerge/cows/pylons/wcs_controller.py@4574
Revision 4574, 14.5 KB checked in by domlowe, 11 years ago (diff)

First stage in merging - still in flux.

Line 
1"""
2WMS controller for OGC Web Services (OWS).
3
4@author: Stephen Pascoe
5"""
6
7import re
8import math
9from cStringIO import StringIO
10from sets import Set
11from matplotlib.cm import get_cmap
12from pylons import request, response, c
13import paste
14
15import logging
16log = logging.getLogger(__name__)
17
18import Image
19from genshi.template import TextTemplate
20
21from cows.model.wms import WmsDatasetSummary, Dimension
22from cows.model import PossibleValues, WGS84BoundingBox, BoundingBox, Contents
23from cows.pylons import ows_controller
24from cows.exceptions import *
25from cows import bbox_util
26
27class WCSController(ows_controller.OWSController):
28    """
29    Subclass this controller in a pylons application and set the layerMapper
30    class attribute to implement a WCS.
31
32    @cvar layerMapper: an cows.service.wcs_iface.ILayerMapper object.
33
34    """
35    layerMapper = None
36    _layerSlabCache = {}
37
38    #-------------------------------------------------------------------------
39    # Attributes required by OWSController
40
41    service = 'WCS'
42    owsOperations = (ows_controller.OWSController.owsOperations +
43        ['GetCoverage', 'DescribeCoverage'])
44    validVersions = ['1.0.0']
45
46    #-------------------------------------------------------------------------
47
48    def __before__(self, **kwargs):
49        """
50        This default implementation of __before__() will pass all routes
51        arguments to the layer mapper to retrieve a list of coverages for
52        this WCS.
53
54        It will be called automatically by pylons before each action method.
55
56        @todo: The layer mapper needs to come from somewhere.
57
58        """
59            #self.updateSequence = "hello"
60        log.debug("loading layers")
61            #print self.layers
62        self.layers = self.layerMapper.map(**kwargs)
63
64    #-------------------------------------------------------------------------
65    # Methods implementing stubs in OWSController
66
67    def _renderCapabilities(self, version, format):
68        if version == '1.0.0':
69            t = ows_controller.templateLoader.load('wcs_capabilities_1_0_0.xml')
70        else:
71            # We should never get here!  The framework should raise an exception before now.
72            raise RuntimeError("Version %s not supported" % version)
73       
74        return t.generate(c=c).render()
75
76    def _loadCapabilities(self):
77        """
78        @note: Assumes self.layers has already been created by __before__().
79
80        """
81        ows_controller.addOperation('GetMap', formats=self._pilImageFormats.keys())
82        ows_controller.addOperation('GetContext')
83        ows_controller.addOperation('GetLegend',
84                                    formats=['image/png'])
85        ows_controller.addOperation('GetInfo')
86        ows_controller.addOperation('GetCoverage')
87       
88        featureInfoFormats = Set()
89
90        log.debug('Loading capabilities contents')
91        c.capabilities.contents = Contents()
92        for layerName, layer in self.layers.items():
93            log.debug('LayerName: %s' % layerName)
94            log.debug('Loading layer %s' % layerName)
95
96            wgs84BBox = WGS84BoundingBox(layer.wgs84BBox[:2],
97                                         layer.wgs84BBox[2:])
98            # Get CRS/BBOX pairs
99            bboxObjs = []
100            for crs in layer.crss:
101                bbox = layer.getBBox(crs)
102                bboxObjs.append(BoundingBox(bbox[:2], bbox[2:], crs=crs))
103            # Get dimensions
104            dims = {}
105            for dimName, dim in layer.dimensions.items():
106                dimParam = self._mapDimToParam(dimName)
107                dims[dimParam] = Dimension(valuesUnit=dim.units,
108                                          unitSymbol=dim.units,
109                                          possibleValues=
110                                            PossibleValues.fromAllowedValues(dim.extent))
111            # Does the layer implement GetFeatureInfo?
112            if layer.featureInfoFormats:
113                queryable = True
114                featureInfoFormats.union_update(layer.featureInfoFormats)
115            else:
116                queryable = False
117               
118            # Create the cows object
119            ds = WmsDatasetSummary(identifier=layerName,
120                                   titles=[layer.title],
121                                   CRSs=layer.crss,
122                                   wgs84BoundingBoxes=[wgs84BBox],
123                                   boundingBoxes=bboxObjs,
124                                   abstracts=[layer.abstract],
125                                   dimensions=dims,
126                                   queryable=queryable)
127
128            # Stuff that should go in the capabilities tree eventually
129            ds.legendSize = layer.legendSize
130            ds.legendFormats = ['image/png']
131
132            c.capabilities.contents.datasetSummaries.append(ds)
133
134        # Add this operation here after we have found all formats
135        ows_controller.addOperation('GetFeatureInfo',
136                                    formats = list(featureInfoFormats))
137
138   
139    def _getLayerParam(self, paramName='coverage'):
140        """
141        Retrieve the layers parameter enforcing the rule of only
142        selecting one coverage
143
144        @param paramName: Overrides the query string parameter name to
145            look for.  This is usefull for implementing GetFeatureInfo.
146
147        """
148        layerName = self.getOwsParam(paramName)
149
150        # Select the first layer if several are requested.
151        # This plays nicer with mapClient.
152        if ',' in layerName:
153            #layerName = layerName.split(',')[0]
154            raise InvalidParameterValue(
155                'Multi-layer GetLegend requests are not supported', 'layers')
156        try:
157            layerObj = self.layers[layerName]
158        except KeyError:
159            raise InvalidParameterValue('Layer %s not found' % layerName,
160                                        paramName)
161
162        return layerName, layerObj
163   
164   
165    def _getFormatParam(self):
166        format = self.getOwsParam('format', default='image/png')
167        if format not in self._pilImageFormats:
168            raise InvalidParameterValue(
169                'Format %s not supported' % format, 'format')
170
171        return format
172
173    _escapedDimNames = ['width', 'height', 'version', 'request',
174                        'layers', 'styles', 'crs', 'srs', 'bbox',
175                        'format', 'transparent', 'bgcolor',
176                        'exceptions']
177
178    def _getDimValues(self, layerObj):
179        dimValues = {}
180        for dimName, dim in layerObj.dimensions.items():
181            defaultValue = dim.extent[0]
182            escapedDimName=self._mapDimToParam(dimName)
183            dimValues[escapedDimName] = self.getOwsParam(escapedDimName,
184                                                  default=defaultValue)
185        return dimValues
186
187    def _mapDimToParam(self, dimName):
188        """
189        Dimension names might clash with WMS parameter names, making
190        them inaccessible in WMS requests.  This method maps a
191        dimension name to a parameter name that appears in the
192        capabilities document and WMS requests.
193
194        """
195        if dimName.lower() in self._escapedDimNames:
196            return dimName+'_dim'
197        else:
198            return dimName
199       
200    def _mapParamToDim(self, dimParam):
201        """
202        Maps a dimension parameter name to it's real dimension name.
203
204        @see: _mapDimToParam()
205
206        """
207        try:
208            dimName = re.match(r'(.*)_dim$', dimParam).group(1)
209            if dimName.lower() in self._escapedDimNames:
210                return dimName
211            else:
212                return dimParam
213        except AttributeError:
214            return dimParam
215
216
217#    def _retrieveSlab(self, layerObj, srs, dimValues, renderOpts):
218#        # Find the slab in the cache first
219#        cacheKey = layerObj.getCacheKey(srs, dimValues)
220#       
221#        slab = self._layerSlabCache.get(cacheKey)
222#        if slab is None:
223#            slab = layerObj.getSlab(srs, dimValues, renderOpts)
224#            if cacheKey is not None:
225#                self._layerSlabCache[cacheKey] = slab
226#
227#        return slab
228   
229    def _retrieveSubset(self, layerObj, srs, dimValues):
230        # Find the slab in the cache first
231        cacheKey = layerObj.getCacheKey(srs, dimValues)
232       
233        slab = self._layerSlabCache.get(cacheKey)
234        if slab is None:
235            slab = layerObj.getSubset(srs, dimValues)
236            if cacheKey is not None:
237                self._layerSlabCache[cacheKey] = slab
238               
239        return slab
240   
241    #-------------------------------------------------------------------------
242    # OWS Operation methods
243   
244    def GetCoverage(self):
245        # Housekeeping
246        version = self.getOwsParam('version', default=self.validVersions[0])
247        if version not in self.validVersions:
248            raise InvalidParameterValue('Version %s not supported' % version,
249                                        'version')
250        # Layer handling
251        layerName, layerObj = self._getLayerParam()
252       
253        # Coordinate parameters
254        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
255
256        srs = self.getOwsParam('crs')
257
258        #if srs not in layerObj.crss:
259         #   raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
260
261        # Get format
262        format = self.getOwsParam('format')
263       
264        if srs not in layerObj.crss:
265            raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
266
267        dimValues = self._getDimValues(layerObj)
268           
269        #now need to revert modified dim values (e.g. height_dim) back to dim values the layerMapper understands (e.g. height)
270        restoredDimValues={}
271        for dim in dimValues:
272            restoredDim=self._mapParamToDim(dim)
273            restoredDimValues[restoredDim]=dimValues[dim]
274               
275        #-------------------------------------------------------
276        # The real work
277        #!TODO: Minimum and maximum values
278        filepath = self._retrieveSubset(layerObj, srs, restoredDimValues)
279        fileToReturn=open(filepath, 'r')
280        mType='application/cf-netcdf'
281        response.headers['Content-Type']=mType
282        response.headers['Content-Disposition'] = paste.httpheaders.CONTENT_DISPOSITION(attachment=True, filename='filename.nc')
283        return response.write(fileToReturn.read())
284           
285#
286    def GetContext(self):
287        """
288        Return a WebMap Context document for a given set of layers.
289
290        """
291        # Parameters
292        layers = self.getOwsParam('layers', default=None)
293
294        # Filter self.layers for selected layers
295        if layers is not None:
296            newLayerMap = {}
297            for layerName in layers.split(','):
298                try:
299                    newLayerMap[layerName] = self.layers[layerName]
300                except KeyError:
301                    raise InvalidParameterValue('Layer %s not found' % layerName,
302                                                'layers')
303                   
304            self.layers = newLayerMap
305
306        # Automatically select the first bbox/crs for the first layer
307        aLayer = self.layers.values()[0]
308        crs = aLayer.crss[0]
309        bb = aLayer.getBBox(crs)
310        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
311
312        # Initialise as if doing GetCapabilities
313        ows_controller.initCapabilities()
314        self._loadCapabilities()
315
316        response.headers['Content-Type'] = 'text/xml'
317        t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
318        return t.generate(c=c).render()
319
320    def GetFeatureInfo(self):
321        # Housekeeping
322        version = self.getOwsParam('version', default=self.validVersions[0])
323        if version not in self.validVersions:
324            raise InvalidParameterValue('Version %s not supported' % version,
325                                        'version')
326
327        # Coordinate parameters
328        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
329        width = int(self.getOwsParam('width'))
330        height = int(self.getOwsParam('height'))
331         
332        # Get pixel location
333        i = int(self.getOwsParam('i'))
334        j = int(self.getOwsParam('j'))
335
336        # Translate to geo-coordinates
337        x, y = bbox_util.pixelToGeo(i, j, bbox, width, height)
338        #start preparing GetFeatureInfo response. Assumes "HTML" output format
339
340        htmlResponse = "<html><body><p> <b>Feature Information about pixel position: "+self.getOwsParam('i')+","+self.getOwsParam('j')+"/geo position: "+str(x)+","+str(y) +"<b/></p>"
341       
342       
343        layers = self._getLayerParam('query_layers')
344        #Adjusts response for multiple layers
345        if len(layers) > 1:
346            htmlResponse = htmlResponse+" Multiple possible features found as follows:"
347 
348        htmlResponse = htmlResponse+"<ul>"
349       
350        format = self.getOwsParam('info_format', default='text/html')
351        for layerName, layerObj in layers.iteritems():
352            log.debug('Format: %s' % format)
353            log.debug('Title: %s' % layerObj.title)
354            log.debug('FeatureInfoFormats: %s' % layerObj.featureInfoFormats)
355        if format not in layerObj.featureInfoFormats:
356            raise InvalidParameterValue('Layer %s does not support GetFeatureInfo in format %s' %(layerName, format), 'info_format')
357
358        if version == '1.1.1':
359                srs = self.getOwsParam('srs')
360        else:
361            srs = self.getOwsParam('crs')
362
363        if srs not in layerObj.crss:
364            raise InvalidParameterValue('Layer %s does not support SRS %s' %
365                                        (layerName, srs))
366
367        # Dimension handling
368        dimValues = {}
369        for dimName, dim in layerObj.dimensions.items():
370            defaultValue = dim.extent[0]
371            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
372       
373        response.headers['Content-Type'] = format
374        response.write(layerObj.getFeatureInfo(format, srs, (x, y), dimValues))
375
376    def GetLegend(self):
377        """
378        Return an image of the legend.
379
380        """
381        # Parameters
382        layerName, layerObj = self._getLayerParamInfo()
383        format = self._getFormatParam()
384
385        img = layerObj.getLegendImage()
386
387        buf = StringIO()
388        img.save(buf, self._pilImageFormats[format])
389
390        response.headers['Content-Type'] = format
391        response.write(buf.getvalue())
392
393
394    def GetInfo(self):
395        from pprint import pformat
396        request.headers['Content-Type'] = 'text/ascii'
397        response.write('Some info about this service\n')
398        for layer in model.ukcip02.layers:
399            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
400
401           
Note: See TracBrowser for help on using the repository browser.