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

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

Basic support for WebMapContext? documents.

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