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

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

more merging

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