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

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

More progress on 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#           
222#            slab = layerObj.getCvg(srs, dimValues)
223#            slab = layerObj.getCvg(srs, dimValues)
224#            if cacheKey is not None:
225#                self._layerSlabCache[cacheKey] = slab
226#               
227#        return slab
228   
229    #-------------------------------------------------------------------------
230    # OWS Operation methods
231   
232    def GetCoverage(self):
233        # Housekeeping
234        version = self.getOwsParam('version', default=self.validVersions[0])
235        if version not in self.validVersions:
236            raise InvalidParameterValue('Version %s not supported' % version,
237                                        'version')
238        # Layer handling
239        layerName, layerObj = self._getLayerParam()
240       
241        # Coordinate parameters
242        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
243
244        srs = self.getOwsParam('crs')
245
246        #if srs not in layerObj.crss:
247         #   raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
248
249        # Get format
250        format = self.getOwsParam('format')
251       
252        if srs not in layerObj.crss:
253            raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
254
255        dimValues = self._getDimValues(layerObj)
256           
257        #now need to revert modified dim values (e.g. height_dim) back to dim values the layerMapper understands (e.g. height)
258        restoredDimValues={}
259        for dim in dimValues:
260            restoredDim=self._mapParamToDim(dim)
261            restoredDimValues[restoredDim]=dimValues[dim]
262               
263        #-------------------------------------------------------
264        #!TODO: Minimum and maximum values
265#        filepath = self._retrieveSubset(layerObj, srs, bbox, restoredDimValues)
266        filepath = layerObj.getCvg(bbox)
267        fileToReturn=open(filepath, 'r')
268        mType='application/cf-netcdf'
269        response.headers['Content-Type']=mType
270        response.headers['Content-Disposition'] = paste.httpheaders.CONTENT_DISPOSITION(attachment=True, filename='filename.nc')
271        return response.write(fileToReturn.read())
272           
273#
274    def GetContext(self):
275        """
276        Return a WebMap Context document for a given set of layers.
277
278        """
279        # Parameters
280        layers = self.getOwsParam('layers', default=None)
281
282        # Filter self.layers for selected layers
283        if layers is not None:
284            newLayerMap = {}
285            for layerName in layers.split(','):
286                try:
287                    newLayerMap[layerName] = self.layers[layerName]
288                except KeyError:
289                    raise InvalidParameterValue('Layer %s not found' % layerName,
290                                                'layers')
291                   
292            self.layers = newLayerMap
293
294        # Automatically select the first bbox/crs for the first layer
295        aLayer = self.layers.values()[0]
296        crs = aLayer.crss[0]
297        bb = aLayer.getBBox(crs)
298        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
299
300        # Initialise as if doing GetCapabilities
301        ows_controller.initCapabilities()
302        self._loadCapabilities()
303
304        response.headers['Content-Type'] = 'text/xml'
305        t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
306        return t.generate(c=c).render()
307
308    def GetFeatureInfo(self):
309        # Housekeeping
310        version = self.getOwsParam('version', default=self.validVersions[0])
311        if version not in self.validVersions:
312            raise InvalidParameterValue('Version %s not supported' % version,
313                                        'version')
314
315        # Coordinate parameters
316        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
317        width = int(self.getOwsParam('width'))
318        height = int(self.getOwsParam('height'))
319         
320        # Get pixel location
321        i = int(self.getOwsParam('i'))
322        j = int(self.getOwsParam('j'))
323
324        # Translate to geo-coordinates
325        x, y = bbox_util.pixelToGeo(i, j, bbox, width, height)
326        #start preparing GetFeatureInfo response. Assumes "HTML" output format
327
328        htmlResponse = "<html><body><p> <b>Feature Information about pixel position: "+self.getOwsParam('i')+","+self.getOwsParam('j')+"/geo position: "+str(x)+","+str(y) +"<b/></p>"
329       
330       
331        layers = self._getLayerParam('query_layers')
332        #Adjusts response for multiple layers
333        if len(layers) > 1:
334            htmlResponse = htmlResponse+" Multiple possible features found as follows:"
335 
336        htmlResponse = htmlResponse+"<ul>"
337       
338        format = self.getOwsParam('info_format', default='text/html')
339        for layerName, layerObj in layers.iteritems():
340            log.debug('Format: %s' % format)
341            log.debug('Title: %s' % layerObj.title)
342            log.debug('FeatureInfoFormats: %s' % layerObj.featureInfoFormats)
343        if format not in layerObj.featureInfoFormats:
344            raise InvalidParameterValue('Layer %s does not support GetFeatureInfo in format %s' %(layerName, format), 'info_format')
345
346        if version == '1.1.1':
347                srs = self.getOwsParam('srs')
348        else:
349            srs = self.getOwsParam('crs')
350
351        if srs not in layerObj.crss:
352            raise InvalidParameterValue('Layer %s does not support SRS %s' %
353                                        (layerName, srs))
354
355        # Dimension handling
356        dimValues = {}
357        for dimName, dim in layerObj.dimensions.items():
358            defaultValue = dim.extent[0]
359            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
360       
361        response.headers['Content-Type'] = format
362        response.write(layerObj.getFeatureInfo(format, srs, (x, y), dimValues))
363
364    def GetLegend(self):
365        """
366        Return an image of the legend.
367
368        """
369        # Parameters
370        layerName, layerObj = self._getLayerParamInfo()
371        format = self._getFormatParam()
372
373        img = layerObj.getLegendImage()
374
375        buf = StringIO()
376        img.save(buf, self._pilImageFormats[format])
377
378        response.headers['Content-Type'] = format
379        response.write(buf.getvalue())
380
381
382    def GetInfo(self):
383        from pprint import pformat
384        request.headers['Content-Type'] = 'text/ascii'
385        response.write('Some info about this service\n')
386        for layer in model.ukcip02.layers:
387            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
388
389           
Note: See TracBrowser for help on using the repository browser.