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

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

Bug fix for GetFeatureInfo?. The basic mechanism appears to work now.

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        # Housekeeping
306        version = self.getOwsParam('version', default=self.validVersions[0])
307        if version not in self.validVersions:
308            raise InvalidParameterValue('Version %s not supported' % version,
309                                        'version')
310        layerName, layerObj = self._getLayerParam('query_layers')
311        format = self.getOwsParam('info_format')
312        if format not in layerObj.featureInfoFormats:
313            raise InvalidParameterValue(
314                'Layer %s does not support GetFeatureInfo in format %s' %
315                (layerName, format), 'info_format')
316
317        # Coordinate parameters
318        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
319        width = int(self.getOwsParam('width'))
320        height = int(self.getOwsParam('height'))
321
322        if version == '1.1.1':
323            srs = self.getOwsParam('srs')
324        else:
325            srs = self.getOwsParam('crs')
326
327        if srs not in layerObj.crss:
328            raise InvalidParameterValue('Layer %s does not support SRS %s' %
329                                        (layerName, srs))
330
331        # Dimension handling
332        dimValues = {}
333        for dimName, dim in layerObj.dimensions.items():
334            defaultValue = dim.extent[0]
335            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
336        # Get pixel location
337        i = int(self.getOwsParam('i'))
338        j = int(self.getOwsParam('j'))
339
340        # Translate to geo-coordinates
341        x, y = bbox_util.pixelToGeo(i, j, bbox, width, height)
342
343        # Call the layer
344        response.headers['Content-Type'] = format
345        response.write(layerObj.getFeatureInfo(format, srs, (x, y), dimValues))
346
347    def GetLegend(self):
348        """
349        Return an image of the legend.
350
351        """
352        # Parameters
353        layerName, layerObj = self._getLayerParam()
354        format = self._getFormatParam()
355
356        img = layerObj.getLegendImage()
357
358        buf = StringIO()
359        img.save(buf, self._pilImageFormats[format])
360
361        response.headers['Content-Type'] = format
362        response.write(buf.getvalue())
363
364
365    def GetInfo(self):
366        from pprint import pformat
367        request.headers['Content-Type'] = 'text/ascii'
368        response.write('Some info about this service\n')
369        for layer in model.ukcip02.layers:
370            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
371
372           
Note: See TracBrowser for help on using the repository browser.