source: TI05-delivery/ows_framework/branches/ows_framework-refactor/ows_common/ows_common/pylons/wms_controller.py @ 3748

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/branches/ows_framework-refactor/ows_common/ows_common/pylons/wms_controller.py@3748
Revision 3748, 14.6 KB checked in by spascoe, 12 years ago (diff)

Renaming dimension parameter names to avoid clashes

Line 
1"""
2WMS controller for OGC Web Services (OWS).
3
4@author: Stephen Pascoe
5"""
6
7from cStringIO import StringIO
8from sets import Set
9from matplotlib.cm import get_cmap
10from pylons import request, response, c
11
12import logging
13log = logging.getLogger(__name__)
14
15import Image
16
17from ows_common.model.wms import WmsDatasetSummary, Dimension
18from ows_common.model import PossibleValues, WGS84BoundingBox, BoundingBox, Contents
19from ows_common.pylons import ows_controller
20from ows_common.exceptions import *
21from ows_common import bbox_util
22
23class WMSController(ows_controller.OWSController):
24    """
25    Subclass this controller in a pylons application and set the layerMapper
26    class attribute to implement a WMS.
27
28    @cvar layerMapper: an ows_common.service.wms_iface.ILayerMapper object.
29
30    """
31    layerMapper = None
32   
33    _pilImageFormats = {
34        'image/png': 'PNG',
35        'image/jpg': 'JPEG',
36        'image/gif': 'GIF',
37        'image/tiff': 'TIFF'
38        }
39    _layerSlabCache = {}
40
41    #-------------------------------------------------------------------------
42    # Attributes required by OWSController
43
44    service = 'WMS'
45    owsOperations = (ows_controller.OWSController.owsOperations +
46        ['GetMap', 'GetContext', 'GetLegend', 'GetFeatureInfo', 'GetInfo'])
47    validVersions = ['1.1.1', '1.3.0']
48
49    #-------------------------------------------------------------------------
50
51    def __before__(self, **kwargs):
52        """
53        This default implementation of __before__() will pass all routes
54        arguments to the layer mapper to retrieve a list of layers for
55        this WMS.
56
57        It will be called automatically by pylons before each action method.
58
59        @todo: The layer mapper needs to come from somewhere.
60
61        """
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.1.1':
69            t = ows_controller.templateLoader.load('wms_capabilities_1_1_1.xml')
70        elif version == '1.3.0':
71            t = ows_controller.templateLoader.load('wms_capabilities_1_3_0.xml')
72        else:
73            # We should never get here!  The framework should raise an exception before now.
74            raise RuntimeError("Version %s not supported" % version)
75       
76        return t.generate(c=c).render()
77
78    def _loadCapabilities(self):
79        """
80        @note: Assumes self.layers has already been created by __before__().
81
82        """
83        ows_controller.addOperation('GetMap', formats=self._pilImageFormats.keys())
84        ows_controller.addOperation('GetContext')
85        ows_controller.addOperation('GetLegend',
86                                    formats=['image/png'])
87        ows_controller.addOperation('GetInfo')
88       
89        featureInfoFormats = Set()
90
91        log.debug('Loading capabilities contents')
92        c.capabilities.contents = Contents()
93        for layerName, layer in self.layers.items():
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 ows_common 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='layers'):
140        """
141        Retrieve the layers parameter enforcing the rule of only
142        selecting one layer.
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 GetMap 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    def _getFormatParam(self):
165        format = self.getOwsParam('format', default='image/png')
166        if format not in self._pilImageFormats:
167            raise InvalidParameterValue(
168                'Format %s not supported' % format, 'format')
169
170        return format
171
172    _escapedDimNames = ['width', 'height', 'version', 'request',
173                        'layers', 'styles', 'crs', 'srs', 'bbox',
174                        'format', 'transparent', 'bgcolor',
175                        'exceptions']
176    def _mapDimToParam(self, dimName):
177        """
178        Dimension names might clash with WMS parameter names, making
179        them inaccessible in WMS requests.  This method maps a
180        dimension name to a parameter name that appears in the
181        capabilities document and WMS requests.
182
183        """
184        if dimName.lower() in self._escapedDimNames:
185            return dimName+'_dim'
186        else:
187            return dimName
188       
189    def _mapParamToDim(self, dimParam):
190        """
191        Maps a dimension parameter name to it's real dimension name.
192
193        @see: _mapDimToParam()
194
195        """
196        try:
197            dimName = re.match(r'(.*)_dim$', dimParam).group(1)
198            if dimName.lower() in self._escapedDimNames:
199                return dimName
200            else:
201                return dimParam
202        except AttributeError:
203            return dimParam
204
205
206    #-------------------------------------------------------------------------
207    # OWS Operation methods
208   
209    def GetMap(self):
210
211        # Housekeeping
212        version = self.getOwsParam('version', default=self.validVersions[0])
213        if version not in self.validVersions:
214            raise InvalidParameterValue('Version %s not supported' % version,
215                                        'version')
216        styles = self.getOwsParam('styles', default='')
217        transparent = self.getOwsParam('transparent', default='FALSE')
218        bgcolor = self.getOwsParam('bgcolor', default='0xFFFFFF')
219
220        # Layer handling
221        layerName, layerObj = self._getLayerParam()
222       
223        # Coordinate parameters
224        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
225        width = int(self.getOwsParam('width'))
226        height = int(self.getOwsParam('height'))
227
228        if version == '1.1.1':
229            srs = self.getOwsParam('srs')
230        else:
231            srs = self.getOwsParam('crs')
232
233        if srs not in layerObj.crss:
234            raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
235
236        # Get format
237        format = self.getOwsParam('format')
238        if format not in self._pilImageFormats:
239            raise InvalidParameterValue(
240                'Format %s not supported' % format, 'format')
241
242        # Dimension handling
243        dimValues = {}
244        for dimName, dim in layerObj.dimensions.items():
245            defaultValue = dim.extent[0]
246            dimParam = self._mapDimToParam(dimName)
247            dimValues[dimName] = self.getOwsParam(dimParam, default=defaultValue)
248           
249        #---------------------------------------------------------------------
250        # The real work
251        #!TODO: Minimum and maximum values
252       
253        # Find the slab in the cache first
254        cacheKey = layerObj.getCacheKey(srs, dimValues)
255
256        slab = self._layerSlabCache.get(cacheKey)
257        if slab is None:
258            slab = layerObj.getSlab(srs, dimValues, dict(minValue=0, maxValue=100))
259            if cacheKey is not None:
260                self._layerSlabCache[cacheKey] = slab
261
262        # We must request a bbox within the layer's bbox.
263        lbbox = layerObj.getBBox(srs)
264        ibbox = bbox_util.intersection(bbox, lbbox)
265
266        log.debug('bbox = %s' % (bbox,))
267        log.debug('lbbox = %s' % (lbbox,))
268        log.debug('ibbox = %s' % (ibbox,))
269
270        # If bbox is not within layerObj.bbox then we need to calculate the
271        # pixel offset of the inner bbox, request the right width/height
272        # and paste the image into a blank background
273        if bbox == ibbox:
274            img = slab.getImage(bbox, width, height)
275            log.debug('slab image.size = %s' % (img.size,))
276        else:
277            sx, sy = bbox_util.relativeSize(ibbox, bbox)
278            log.debug('scaling: %s,%s' % (sx, sy))
279            img1 = slab.getImage(ibbox, int(width*sx), int(height*sy))
280            log.debug('inner image.size = %s' % (img1.size,))
281
282            img = Image.new('RGBA', (width, height))
283            pxOrigin = bbox_util.geoToPixel(ibbox[0], ibbox[3], bbox, width, height)
284            log.debug('pxOrigin = %s' % (pxOrigin,))
285            img.paste(img1, pxOrigin)
286           
287
288        # IE < 7 doesn't display the alpha layer right.  Here we sniff the
289        # user agent and remove the alpha layer if necessary.
290        try:
291            ua = request.headers['User-Agent']
292        except:
293            pass
294        else:
295            if 'MSIE' in ua and 'MSIE 7' not in ua:
296                img = img.convert('RGB')
297
298        buf = StringIO()
299        img.save(buf, self._pilImageFormats[format])
300
301        response.headers['Content-Type'] = format
302        response.write(buf.getvalue())
303
304        return request
305
306    def GetContext(self):
307        """
308        Return a WebMap Context document for a given set of layers.
309
310        """
311        # Parameters
312        layers = self.getOwsParam('layers', default=None)
313
314        # Filter self.layers for selected layers
315        if layers is not None:
316            newLayerMap = {}
317            for layerName in layers.split(','):
318                try:
319                    newLayerMap[layerName] = self.layers[layerName]
320                except KeyError:
321                    raise InvalidParameterValue('Layer %s not found' % layerName,
322                                                'layers')
323                   
324            self.layers = newLayerMap
325
326        # Automatically select the first bbox/crs for the first layer
327        aLayer = self.layers.values()[0]
328        crs = aLayer.crss[0]
329        bb = aLayer.getBBox(crs)
330        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
331
332        # Initialise as if doing GetCapabilities
333        ows_controller.initCapabilities()
334        self._loadCapabilities()
335
336        response.headers['Content-Type'] = 'text/xml'
337        t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
338        return t.generate(c=c).render()
339
340    def GetFeatureInfo(self):
341        # Housekeeping
342        version = self.getOwsParam('version', default=self.validVersions[0])
343        if version not in self.validVersions:
344            raise InvalidParameterValue('Version %s not supported' % version,
345                                        'version')
346        layerName, layerObj = self._getLayerParam('query_layers')
347        format = self.getOwsParam('info_format')
348        if format not in layerObj.featureInfoFormats:
349            raise InvalidParameterValue(
350                'Layer %s does not support GetFeatureInfo in format %s' %
351                (layerName, format), 'info_format')
352
353        # Coordinate parameters
354        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
355        width = int(self.getOwsParam('width'))
356        height = int(self.getOwsParam('height'))
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            dimParam = self._mapDimToParam(dimName)
372            dimValues[dimName] = self.getOwsParam(dimParam, default=defaultValue)
373        # Get pixel location
374        i = int(self.getOwsParam('i'))
375        j = int(self.getOwsParam('j'))
376
377        # Translate to geo-coordinates
378        x, y = bbox_util.pixelToGeo(i, j, bbox, width, height)
379
380        # Call the layer
381        response.headers['Content-Type'] = format
382        response.write(layerObj.getFeatureInfo(format, srs, (x, y), dimValues))
383
384    def GetLegend(self):
385        """
386        Return an image of the legend.
387
388        """
389        # Parameters
390        layerName, layerObj = self._getLayerParam()
391        format = self._getFormatParam()
392
393        img = layerObj.getLegendImage()
394
395        buf = StringIO()
396        img.save(buf, self._pilImageFormats[format])
397
398        response.headers['Content-Type'] = format
399        response.write(buf.getvalue())
400
401
402    def GetInfo(self):
403        from pprint import pformat
404        request.headers['Content-Type'] = 'text/ascii'
405        response.write('Some info about this service\n')
406        for layer in model.ukcip02.layers:
407            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
408
409           
Note: See TracBrowser for help on using the repository browser.