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

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

GetLegend? support

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