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

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@3786
Revision 3786, 15.6 KB checked in by spascoe, 12 years ago (diff)
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    #layers = {}   
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.updateSequence = "hello"
63        print "loading layers"
64        #print self.layers
65        self.layers = self.layerMapper.map()
66
67    #-------------------------------------------------------------------------
68    # Methods implementing stubs in OWSController
69
70    def _renderCapabilities(self, version, format):
71        if version == '1.1.1':
72            t = ows_controller.templateLoader.load('wms_capabilities_1_1_1.xml')
73        elif version == '1.3.0':
74            t = ows_controller.templateLoader.load('wms_capabilities_1_3_0.xml')
75        else:
76            # We should never get here!  The framework should raise an exception before now.
77            raise RuntimeError("Version %s not supported" % version)
78       
79        return t.generate(c=c).render()
80
81    def _loadCapabilities(self):
82        """
83        @note: Assumes self.layers has already been created by __before__().
84
85        """
86        ows_controller.addOperation('GetMap', formats=self._pilImageFormats.keys())
87        ows_controller.addOperation('GetContext')
88        ows_controller.addOperation('GetLegend',
89                                    formats=['image/png'])
90        ows_controller.addOperation('GetInfo')
91       
92        featureInfoFormats = Set()
93
94        log.debug('Loading capabilities contents')
95        c.capabilities.contents = Contents()
96        for layerName, layer in self.layers.items():
97            print layerName
98            log.debug('Loading layer %s' % layerName)
99
100            wgs84BBox = WGS84BoundingBox(layer.wgs84BBox[:2],
101                                         layer.wgs84BBox[2:])
102            # Get CRS/BBOX pairs
103            bboxObjs = []
104            for crs in layer.crss:
105                bbox = layer.getBBox(crs)
106                bboxObjs.append(BoundingBox(bbox[:2], bbox[2:], crs=crs))
107            # Get dimensions
108            dims = {}
109            for dimName, dim in layer.dimensions.items():
110                dims[dimName] = Dimension(valuesUnit=dim.units,
111                                          unitSymbol=dim.units,
112                                          possibleValues=
113                                            PossibleValues.fromAllowedValues(dim.extent))
114            # Does the layer implement GetFeatureInfo?
115            if layer.featureInfoFormats:
116                queryable = True
117                featureInfoFormats.union_update(layer.featureInfoFormats)
118            else:
119                queryable = False
120               
121            # Create the ows_common object
122            ds = WmsDatasetSummary(identifier=layerName,
123                                   titles=[layer.title],
124                                   CRSs=layer.crss,
125                                   wgs84BoundingBoxes=[wgs84BBox],
126                                   boundingBoxes=bboxObjs,
127                                   abstracts=[layer.abstract],
128                                   dimensions=dims,
129                                   queryable=queryable)
130
131            # Stuff that should go in the capabilities tree eventually
132            ds.legendSize = layer.legendSize
133            ds.legendFormats = ['image/png']
134
135            c.capabilities.contents.datasetSummaries.append(ds)
136
137        # Add this operation here after we have found all formats
138        ows_controller.addOperation('GetFeatureInfo',
139                                    formats = list(featureInfoFormats))
140
141    def _getLayerParamInfo(self, paramName='layers'):
142        """
143        Retrieve the layers parameter enforcing the rule of only
144        selecting one layer.
145
146        @param paramName: Overrides the query string parameter name to
147            look for.  This is usefull for implementing GetFeatureInfo.
148
149        """
150        layerName = self.getOwsParam(paramName)
151
152        # Select the first layer if several are requested.
153        # This plays nicer with mapClient.
154        if ',' in layerName:
155            #layerName = layerName.split(',')[0]
156            raise InvalidParameterValue(
157                'Multi-layer GetLegend requests are not supported', 'layers')
158        try:
159            layerObj = self.layers[layerName]
160        except KeyError:
161            raise InvalidParameterValue('Layer %s not found' % layerName,
162                                        paramName)
163
164        return layerName, layerObj
165
166    def _getLayerParam(self, paramName='layers'):
167        """
168        Retrieve the layers parameter enforcing the rule of only
169        selecting one layer.
170
171        @param paramName: Overrides the query string parameter name to
172            look for.  This is usefull for implementing GetFeatureInfo.
173
174        """
175        #print self.getOwsParam(paramName)
176        layers = {}
177        layerNames = self.getOwsParam(paramName)
178
179        # Select the first layer if several are requested.
180        # This plays nicer with mapClient.
181        #if ',' in layerName:
182        layerNames = layerNames.split(',')
183            #raise InvalidParameterValue(
184            #    'Multi-layer GetMap requests are not supported', 'layers')
185        for layerName in layerNames:
186            try:
187                layerObj = self.layers[layerName]
188                layers[layerName] = layerObj
189            except KeyError:
190                raise InvalidParameterValue('Layer %s not found' % layerName,
191                                        paramName)
192
193        #return layerName, layerObj
194        return layers
195
196    def _getFormatParam(self):
197        format = self.getOwsParam('format', default='image/png')
198        if format not in self._pilImageFormats:
199            raise InvalidParameterValue(
200                'Format %s not supported' % format, 'format')
201
202        return format
203
204    #-------------------------------------------------------------------------
205    # OWS Operation methods
206   
207    def GetMap(self):
208
209        # Housekeeping
210        version = self.getOwsParam('version', default=self.validVersions[0])
211        if version not in self.validVersions:
212            raise InvalidParameterValue('Version %s not supported' % version,
213                                        'version')
214        styles = self.getOwsParam('styles', default='')
215        transparent = self.getOwsParam('transparent', default='FALSE')
216        bgcolor = self.getOwsParam('bgcolor', default='0xFFFFFF')
217
218        # Layer handling
219        #layerName, layerObj = self._getLayerParam()
220        layers = self._getLayerParam()
221        # Coordinate parameters
222        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
223        width = int(self.getOwsParam('width'))
224        height = int(self.getOwsParam('height'))
225
226        if version == '1.1.1':
227            srs = self.getOwsParam('srs')
228        else:
229            srs = self.getOwsParam('crs')
230
231        #if srs not in layerObj.crss:
232         #   raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
233
234        # Get format
235        format = self.getOwsParam('format')
236        if format not in self._pilImageFormats:
237            raise InvalidParameterValue(
238                'Format %s not supported' % format, 'format')
239
240        # Dimension handling
241        finalImg = Image.new('RGBA', (width, height), (0,0,0,0))
242        # Multiple Layers handling.. 
243        for layerName, layerObj in layers.iteritems():
244            if srs not in layerObj.crss:
245                raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
246           
247            dimValues = {}
248            for dimName, dim in layerObj.dimensions.items():
249                defaultValue = dim.extent[0]
250                dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
251           
252        #---------------------------------------------------------------------
253        # The real work
254        #!TODO: Minimum and maximum values
255       
256        # Find the slab in the cache first
257            cacheKey = layerObj.getCacheKey(srs, dimValues)
258
259            slab = self._layerSlabCache.get(cacheKey)
260            if slab is None:
261                slab = layerObj.getSlab(srs, dimValues, dict(minValue=0, maxValue=100))
262                if cacheKey is not None:
263                                self._layerSlabCache[cacheKey] = slab
264
265        # We must request a bbox within the layer's bbox.
266            lbbox = layerObj.getBBox(srs)
267            ibbox = bbox_util.intersection(bbox, lbbox)
268
269            log.debug('bbox = %s' % (bbox,))
270            log.debug('lbbox = %s' % (lbbox,))
271            log.debug('ibbox = %s' % (ibbox,))
272
273        # If bbox is not within layerObj.bbox then we need to calculate the
274        # pixel offset of the inner bbox, request the right width/height
275        # and paste the image into a blank background
276            if bbox == ibbox:
277                img = slab.getImage(bbox, width, height)
278                log.debug('slab image.size = %s' % (img.size,))
279                       
280            else:
281                sx, sy = bbox_util.relativeSize(ibbox, bbox)
282                log.debug('scaling: %s,%s' % (sx, sy))
283                img1 = slab.getImage(ibbox, int(width*sx), int(height*sy))
284                log.debug('inner image.size = %s' % (img1.size,))
285
286                img = Image.new('RGBA', (width, height))
287                pxOrigin = bbox_util.geoToPixel(ibbox[0], ibbox[3], bbox, width, height)
288                log.debug('pxOrigin = %s' % (pxOrigin,))
289                img.paste(img1, pxOrigin)
290               
291            finalImg = Image.composite(finalImg, img, finalImg) 
292           
293         
294         
295       
296        # IE < 7 doesn't display the alpha layer right.  Here we sniff the
297        # user agent and remove the alpha layer if necessary.
298        try:
299            ua = request.headers['User-Agent']
300        except:
301            pass
302        else:
303            if 'MSIE' in ua and 'MSIE 7' not in ua:
304                finalImg = finalImg.convert('RGB')
305
306        buf = StringIO()
307        finalImg.save(buf, self._pilImageFormats[format])
308
309        response.headers['Content-Type'] = format
310        response.write(buf.getvalue())
311
312        return request
313
314    def GetContext(self):
315        """
316        Return a WebMap Context document for a given set of layers.
317
318        """
319        # Parameters
320        layers = self.getOwsParam('layers', default=None)
321
322        # Filter self.layers for selected layers
323        if layers is not None:
324            newLayerMap = {}
325            for layerName in layers.split(','):
326                try:
327                    newLayerMap[layerName] = self.layers[layerName]
328                except KeyError:
329                    raise InvalidParameterValue('Layer %s not found' % layerName,
330                                                'layers')
331                   
332            self.layers = newLayerMap
333
334        # Automatically select the first bbox/crs for the first layer
335        aLayer = self.layers.values()[0]
336        crs = aLayer.crss[0]
337        bb = aLayer.getBBox(crs)
338        c.bbox = BoundingBox(bb[:2], bb[2:], crs)
339
340        # Initialise as if doing GetCapabilities
341        ows_controller.initCapabilities()
342        self._loadCapabilities()
343
344        response.headers['Content-Type'] = 'text/xml'
345        t = ows_controller.templateLoader.load('wms_context_1_1_1.xml')
346        return t.generate(c=c).render()
347
348    def GetFeatureInfo(self):
349        # Housekeeping
350        version = self.getOwsParam('version', default=self.validVersions[0])
351        if version not in self.validVersions:
352            raise InvalidParameterValue('Version %s not supported' % version,
353                                        'version')
354
355        # Coordinate parameters
356        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
357        width = int(self.getOwsParam('width'))
358        height = int(self.getOwsParam('height'))
359         
360        # Get pixel location
361        i = int(self.getOwsParam('i'))
362        j = int(self.getOwsParam('j'))
363
364        # Translate to geo-coordinates
365        x, y = bbox_util.pixelToGeo(i, j, bbox, width, height)
366        #start preparing GetFeatureInfo response. Assumes "HTML" output format
367
368        htmlResponse = "<html><body><p> <b>Feature Information about pixel position: "+self.getOwsParam('i')+","+self.getOwsParam('j')+"/geo position: "+str(x)+","+str(y) +"<b/></p>"
369       
370       
371        layers = self._getLayerParam('query_layers')
372        #Adjusts response for multiple layers
373        if len(layers) > 1:
374            htmlResponse = htmlResponse+" Multiple possible features found as follows:"
375 
376        htmlResponse = htmlResponse+"<ul>"
377       
378        format = self.getOwsParam('info_format')
379        for layerName, layerObj in layers.iteritems():
380            if format not in layerObj.featureInfoFormats:
381                raise InvalidParameterValue('Layer %s does not support GetFeatureInfo in format %s' %(layerName, format), 'info_format')
382
383       
384
385            if version == '1.1.1':
386                srs = self.getOwsParam('srs')
387            else:
388                srs = self.getOwsParam('crs')
389
390            if srs not in layerObj.crss:
391                raise InvalidParameterValue('Layer %s does not support SRS %s' %
392                                                (layerName, srs))
393
394        # Dimension handling
395            dimValues = {}
396            for dimName, dim in layerObj.dimensions.items():
397                defaultValue = dim.extent[0]
398                dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
399       
400            htmlResponse = htmlResponse+"<li> Layer Name: "+layerName+" <br />"
401        # Call the layer
402            htmlResponse =htmlResponse+layerObj.getFeatureInfo(format, srs, (x, y), dimValues)+"</li>"
403       
404        htmlResponse = htmlResponse+"</ul></body></html>"
405       
406        response.headers['Content-Type'] = format
407        response.write(htmlResponse)
408
409    def GetLegend(self):
410        """
411        Return an image of the legend.
412
413        """
414        # Parameters
415        layerName, layerObj = self._getLayerParamInfo()
416        format = self._getFormatParam()
417
418        img = layerObj.getLegendImage()
419
420        buf = StringIO()
421        img.save(buf, self._pilImageFormats[format])
422
423        response.headers['Content-Type'] = format
424        response.write(buf.getvalue())
425
426
427    def GetInfo(self):
428        from pprint import pformat
429        request.headers['Content-Type'] = 'text/ascii'
430        response.write('Some info about this service\n')
431        for layer in model.ukcip02.layers:
432            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
433
434           
Note: See TracBrowser for help on using the repository browser.