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

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@3696
Revision 3696, 13.1 KB checked in by spascoe, 12 years ago (diff)

Implementation of GetFeatureInfo?. Not tested yet but the interface
described in wms_iface.py should remain constant.

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                dims[dimName] = Dimension(valuesUnit=dim.units,
107                                          unitSymbol=dim.units,
108                                          possibleValues=
109                                            PossibleValues.fromAllowedValues(dim.extent))
110            # Does the layer implement GetFeatureInfo?
111            if layer.featureInfoFormats:
112                queryable = True
113                featureInfoFormats.union_update(layer.featureInfoFormats)
114            else:
115                queryable = False
116               
117            # Create the ows_common object
118            ds = WmsDatasetSummary(identifier=layerName,
119                                   titles=[layer.title],
120                                   CRSs=layer.crss,
121                                   wgs84BoundingBoxes=[wgs84BBox],
122                                   boundingBoxes=bboxObjs,
123                                   abstracts=[layer.abstract],
124                                   dimensions=dims,
125                                   queryable=queryable)
126
127            # Stuff that should go in the capabilities tree eventually
128            ds.legendSize = layer.legendSize
129            ds.legendFormats = ['image/png']
130
131            c.capabilities.contents.datasetSummaries.append(ds)
132
133        # Add this operation here after we have found all formats
134        ows_controller.addOperation('GetFeatureInfo',
135                                    formats = list(featureInfoFormats))
136
137
138    def _getLayerParam(self, paramName='layers'):
139        """
140        Retrieve the layers parameter enforcing the rule of only
141        selecting one layer.
142
143        @param paramName: Overrides the query string parameter name to
144            look for.  This is usefull for implementing GetFeatureInfo.
145
146        """
147        layerName = self.getOwsParam(paramName)
148
149        # Select the first layer if several are requested.
150        # This plays nicer with mapClient.
151        if ',' in layerName:
152            layerName = layerName.split(',')[0]
153            #raise InvalidParameterValue(
154            #    'Multi-layer GetMap requests are not supported', 'layers')
155        try:
156            layerObj = self.layers[layerName]
157        except KeyError:
158            raise InvalidParameterValue('Layer %s not found' % layerName,
159                                        paramName)
160
161        return layerName, layerObj
162
163    def _getFormatParam(self):
164        format = self.getOwsParam('format', default='image/png')
165        if format not in self._pilImageFormats:
166            raise InvalidParameterValue(
167                'Format %s not supported' % format, 'format')
168
169        return format
170
171    #-------------------------------------------------------------------------
172    # OWS Operation methods
173   
174    def GetMap(self):
175
176        # Housekeeping
177        version = self.getOwsParam('version', default=self.validVersions[0])
178        if version not in self.validVersions:
179            raise InvalidParameterValue('Version %s not supported' % version,
180                                        'version')
181        styles = self.getOwsParam('styles', default='')
182        transparent = self.getOwsParam('transparent', default='FALSE')
183        bgcolor = self.getOwsParam('bgcolor', default='0xFFFFFF')
184
185        # Layer handling
186        layerName, layerObj = self._getLayerParam()
187       
188        # Coordinate parameters
189        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
190        width = int(self.getOwsParam('width'))
191        height = int(self.getOwsParam('height'))
192
193        if version == '1.1.1':
194            srs = self.getOwsParam('srs')
195        else:
196            srs = self.getOwsParam('crs')
197
198        if srs not in layerObj.crss:
199            raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
200
201        # Get format
202        format = self.getOwsParam('format')
203        if format not in self._pilImageFormats:
204            raise InvalidParameterValue(
205                'Format %s not supported' % format, 'format')
206
207        # Dimension handling
208        dimValues = {}
209        for dimName, dim in layerObj.dimensions.items():
210            defaultValue = dim.extent[0]
211            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
212           
213        #---------------------------------------------------------------------
214        # The real work
215        #!TODO: Minimum and maximum values
216       
217        # Find the slab in the cache first
218        cacheKey = layerObj.getCacheKey(srs, dimValues)
219
220        slab = self._layerSlabCache.get(cacheKey)
221        if slab is None:
222            slab = layerObj.getSlab(srs, dimValues, dict(minValue=0, maxValue=100))
223            if cacheKey is not None:
224                self._layerSlabCache[cacheKey] = slab
225
226        # We must request a bbox within the layer's bbox.
227        lbbox = layerObj.getBBox(srs)
228        ibbox = bbox_util.intersection(bbox, lbbox)
229
230        log.debug('bbox = %s' % (bbox,))
231        log.debug('lbbox = %s' % (lbbox,))
232        log.debug('ibbox = %s' % (ibbox,))
233
234        # If bbox is not within layerObj.bbox then we need to calculate the
235        # pixel offset of the inner bbox, request the right width/height
236        # and paste the image into a blank background
237        if bbox == ibbox:
238            img = slab.getImage(bbox, width, height)
239            log.debug('slab image.size = %s' % (img.size,))
240        else:
241            sx, sy = bbox_util.relativeSize(ibbox, bbox)
242            log.debug('scaling: %s,%s' % (sx, sy))
243            img1 = slab.getImage(ibbox, int(width*sx), int(height*sy))
244            log.debug('inner image.size = %s' % (img1.size,))
245
246            img = Image.new('RGBA', (width, height))
247            pxOrigin = bbox_util.geoToPixel(ibbox[0], ibbox[3], bbox, width, height)
248            log.debug('pxOrigin = %s' % (pxOrigin,))
249            img.paste(img1, pxOrigin)
250           
251
252        # IE < 7 doesn't display the alpha layer right.  Here we sniff the
253        # user agent and remove the alpha layer if necessary.
254        try:
255            ua = request.headers['User-Agent']
256        except:
257            pass
258        else:
259            if 'MSIE' in ua and 'MSIE 7' not in ua:
260                img = img.convert('RGB')
261
262        buf = StringIO()
263        img.save(buf, self._pilImageFormats[format])
264
265        response.headers['Content-Type'] = format
266        response.write(buf.getvalue())
267
268        return request
269
270    def GetContext(self):
271        """
272        Return a WebMap Context document for a given set of layers.
273
274        """
275        # Parameters
276        layers = self.getOwsParam('layers', default=None)
277
278        # Filter self.layers for selected layers
279        if layers is not None:
280            newLayerMap = {}
281            for layerName in layers.split(','):
282                try:
283                    newLayerMap[layerName] = self.layers[layerName]
284                except KeyError:
285                    raise InvalidParameterValue('Layer %s not found' % layerName,
286                                                'layers')
287                   
288            self.layers = newLayerMap
289
290        # Automatically select the first bbox/crs for the first layer
291        aLayer = self.layers.values()[0]
292        crs = aLayer.crss[0]
293        bb = aLayer.getBBox(crs)
294        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
295
296        # Initialise as if doing GetCapabilities
297        ows_controller.initCapabilities()
298        self._loadCapabilities()
299
300        response.headers['Content-Type'] = 'text/xml'
301        t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
302        return t.generate(c=c).render()
303
304    def GetFeatureInfo(self):
305        layerName, layerObj = self._getLayerParam('query_layers')
306        format = self.getOwsParam('info_format')
307        if format not in layerObj.featureInfoFormats:
308            raise InvalidParameterValue(
309                'Layer %s does not support GetFeatureInfo in format %s' %
310                (layerName, format), 'info_format')
311
312        # Coordinate parameters
313        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
314        width = int(self.getOwsParam('width'))
315        height = int(self.getOwsParam('height'))
316
317        if version == '1.1.1':
318            srs = self.getOwsParam('srs')
319        else:
320            srs = self.getOwsParam('crs')
321
322        if srs not in layerObj.crss:
323            raise InvalidParameterValue('Layer %s does not support SRS %s' %
324                                        (layerName, srs))
325
326        # Dimension handling
327        dimValues = {}
328        for dimName, dim in layerObj.dimensions.items():
329            defaultValue = dim.extent[0]
330            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
331        # Get pixel location
332        i = self.getOwsParam('i')
333        j = self.getOwsParam('j')
334
335        # Translate to geo-coordinates
336        x, y = bbox_util.pixelToGeo(i, j, bbox, width, height)
337
338        # Call the layer
339        response.headers['Content-Type'] = format
340        response.write(layerObj.getFeatureInfo(format, srs, (x, y), dimValues))
341
342    def GetLegend(self):
343        """
344        Return an image of the legend.
345
346        """
347        # Parameters
348        layerName, layerObj = self._getLayerParam()
349        format = self._getFormatParam()
350
351        img = layerObj.getLegendImage()
352
353        buf = StringIO()
354        img.save(buf, self._pilImageFormats[format])
355
356        response.headers['Content-Type'] = format
357        response.write(buf.getvalue())
358
359
360    def GetInfo(self):
361        from pprint import pformat
362        request.headers['Content-Type'] = 'text/ascii'
363        response.write('Some info about this service\n')
364        for layer in model.ukcip02.layers:
365            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
366
367           
Note: See TracBrowser for help on using the repository browser.