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

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@3688
Revision 3688, 11.0 KB checked in by spascoe, 11 years ago (diff)

A required element was being missed. This has required a change to the WMS API.

Line 
1"""
2WMS controller for OGC Web Services (OWS).
3
4@author: Stephen Pascoe
5"""
6
7from cStringIO import StringIO
8
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 + ['GetMap', 'GetContext',
46                                                                  'GetLegend',
47                                                                  'GetInfo']
48    validVersions = ['1.1.1', '1.3.0']
49
50    #-------------------------------------------------------------------------
51
52    def __before__(self, **kwargs):
53        """
54        This default implementation of __before__() will pass all routes
55        arguments to the layer mapper to retrieve a list of layers for
56        this WMS.
57
58        It will be called automatically by pylons before each action method.
59
60        @todo: The layer mapper needs to come from somewhere.
61
62        """
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.1.1':
70            t = ows_controller.templateLoader.load('wms_capabilities_1_1_1.xml')
71        elif version == '1.3.0':
72            t = ows_controller.templateLoader.load('wms_capabilities_1_3_0.xml')
73        else:
74            # We should never get here!  The framework should raise an exception before now.
75            raise RuntimeError("Version %s not supported" % version)
76       
77        return t.generate(c=c).render()
78
79    def _loadCapabilities(self):
80        """
81        @note: Assumes self.layers has already been created by __before__().
82
83        """
84        ows_controller.addOperation('GetMap', formats=self._pilImageFormats.keys())
85        ows_controller.addOperation('GetContext')
86        ows_controller.addOperation('GetLegend',
87                                    formats=['image/png'])
88        ows_controller.addOperation('GetInfo')
89        log.debug('Loading capabilities contents')
90        c.capabilities.contents = Contents()
91        for layerName, layer in self.layers.items():
92            log.debug('Loading layer %s' % layerName)
93
94            wgs84BBox = WGS84BoundingBox(layer.wgs84BBox[:2],
95                                         layer.wgs84BBox[2:])
96            # Get CRS/BBOX pairs
97            bboxObjs = []
98            for crs in layer.crss:
99                bbox = layer.getBBox(crs)
100                bboxObjs.append(BoundingBox(bbox[:2], bbox[2:], crs=crs))
101            # Get dimensions
102            dims = {}
103            for dimName, dim in layer.dimensions.items():
104                dims[dimName] = Dimension(valuesUnit=dim.units,
105                                          unitSymbol=dim.units,
106                                          possibleValues=
107                                            PossibleValues.fromAllowedValues(dim.extent))
108            # Create the ows_common object
109            ds = WmsDatasetSummary(identifier=layerName,
110                                   titles=[layer.title],
111                                   CRSs=layer.crss,
112                                   wgs84BoundingBoxes=[wgs84BBox],
113                                   boundingBoxes=bboxObjs,
114                                   abstracts=[layer.abstract],
115                                   dimensions=dims)
116
117            # Stuff that should go in the capabilities tree eventually
118            ds.legendSize = layer.legendSize
119            ds.legendFormats = ['image/png']
120
121            c.capabilities.contents.datasetSummaries.append(ds)
122
123
124
125    def _getLayerParam(self):
126        """
127        Retrieve the layers parameter enforcing the rule of only
128        selecting one layer.
129
130        """
131        layerName = self.getOwsParam('layers')
132
133        # Select the first layer if several are requested.
134        # This plays nicer with mapClient.
135        if ',' in layerName:
136            layerName = layerName.split(',')[0]
137            #raise InvalidParameterValue(
138            #    'Multi-layer GetMap requests are not supported', 'layers')
139        try:
140            layerObj = self.layers[layerName]
141        except KeyError:
142            raise InvalidParameterValue('Layer %s not found' % layerName, 'layers')
143
144        return layerName, layerObj
145
146    def _getFormatParam(self):
147        format = self.getOwsParam('format', default='image/png')
148        if format not in self._pilImageFormats:
149            raise InvalidParameterValue(
150                'Format %s not supported' % format, 'format')
151
152        return format
153   
154    #-------------------------------------------------------------------------
155    # OWS Operation methods
156   
157    def GetMap(self):
158
159        # Housekeeping
160        version = self.getOwsParam('version', default=self.validVersions[0])
161        if version not in self.validVersions:
162            raise InvalidParameterValue('Version %s not supported' % version,
163                                        'version')
164        styles = self.getOwsParam('styles', default='')
165        transparent = self.getOwsParam('transparent', default='FALSE')
166        bgcolor = self.getOwsParam('bgcolor', default='0xFFFFFF')
167
168        # Layer handling
169        layerName, layerObj = self._getLayerParam()
170       
171        # Coordinate parameters
172        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
173        width = int(self.getOwsParam('width'))
174        height = int(self.getOwsParam('height'))
175
176        if version == '1.1.1':
177            srs = self.getOwsParam('srs')
178        else:
179            srs = self.getOwsParam('crs')
180
181        if srs not in layerObj.crss:
182            raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
183
184        # Get format
185        format = self.getOwsParam('format')
186        if format not in self._pilImageFormats:
187            raise InvalidParameterValue(
188                'Format %s not supported' % format, 'format')
189
190        # Dimension handling
191        dimValues = {}
192        for dimName, dim in layerObj.dimensions.items():
193            defaultValue = dim.extent[0]
194            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
195           
196        #---------------------------------------------------------------------
197        # The real work
198        #!TODO: Minimum and maximum values
199       
200        # Find the slab in the cache first
201        cacheKey = layerObj.getCacheKey(srs, dimValues)
202
203        slab = self._layerSlabCache.get(cacheKey)
204        if slab is None:
205            slab = layerObj.getSlab(srs, dimValues, dict(minValue=0, maxValue=100))
206            if cacheKey is not None:
207                self._layerSlabCache[cacheKey] = slab
208
209        # We must request a bbox within the layer's bbox.
210        lbbox = layerObj.getBBox(srs)
211        ibbox = bbox_util.intersection(bbox, lbbox)
212
213        log.debug('bbox = %s' % (bbox,))
214        log.debug('lbbox = %s' % (lbbox,))
215        log.debug('ibbox = %s' % (ibbox,))
216
217        # If bbox is not within layerObj.bbox then we need to calculate the
218        # pixel offset of the inner bbox, request the right width/height
219        # and paste the image into a blank background
220        if bbox == ibbox:
221            img = slab.getImage(bbox, width, height)
222            log.debug('slab image.size = %s' % (img.size,))
223        else:
224            sx, sy = bbox_util.relativeSize(ibbox, bbox)
225            log.debug('scaling: %s,%s' % (sx, sy))
226            img1 = slab.getImage(ibbox, int(width*sx), int(height*sy))
227            log.debug('inner image.size = %s' % (img1.size,))
228
229            img = Image.new('RGBA', (width, height))
230            pxOrigin = bbox_util.geoToPixel(ibbox[0], ibbox[3], bbox, width, height)
231            log.debug('pxOrigin = %s' % (pxOrigin,))
232            img.paste(img1, pxOrigin)
233           
234
235        # IE < 7 doesn't display the alpha layer right.  Here we sniff the
236        # user agent and remove the alpha layer if necessary.
237        try:
238            ua = request.headers['User-Agent']
239        except:
240            pass
241        else:
242            if 'MSIE' in ua and 'MSIE 7' not in ua:
243                img = img.convert('RGB')
244
245        buf = StringIO()
246        img.save(buf, self._pilImageFormats[format])
247
248        response.headers['Content-Type'] = format
249        response.write(buf.getvalue())
250
251        return request
252
253    def GetContext(self):
254        """
255        Return a WebMap Context document for a given set of layers.
256
257        """
258        # Parameters
259        layers = self.getOwsParam('layers', default=None)
260
261        # Filter self.layers for selected layers
262        if layers is not None:
263            newLayerMap = {}
264            for layerName in layers.split(','):
265                try:
266                    newLayerMap[layerName] = self.layers[layerName]
267                except KeyError:
268                    raise InvalidParameterValue('Layer %s not found' % layerName,
269                                                'layers')
270                   
271            self.layers = newLayerMap
272
273        # Automatically select the first bbox/crs for the first layer
274        aLayer = self.layers.values()[0]
275        crs = aLayer.crss[0]
276        bb = aLayer.getBBox(crs)
277        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
278
279        # Initialise as if doing GetCapabilities
280        ows_controller.initCapabilities()
281        self._loadCapabilities()
282
283        response.headers['Content-Type'] = 'text/xml'
284        t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
285        return t.generate(c=c).render()
286
287    def GetLegend(self):
288        """
289        Return an image of the legend.
290
291        """
292        # Parameters
293        layerName, layerObj = self._getLayerParam()
294        format = self._getFormatParam()
295
296        img = layerObj.getLegendImage()
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
305    def GetInfo(self):
306        from pprint import pformat
307        request.headers['Content-Type'] = 'text/ascii'
308        response.write('Some info about this service\n')
309        for layer in model.ukcip02.layers:
310            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
311
312           
Note: See TracBrowser for help on using the repository browser.