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

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

Several bugs that were making the Capabilities document invalid.

The DDP test WMS server is now viewable through Cadcorp's Map Browser.

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', 'GetInfo']
46    validVersions = ['1.1.1']
47
48    #-------------------------------------------------------------------------
49
50    def __before__(self, **kwargs):
51        """
52        This default implementation of __before__() will pass all routes
53        arguments to the layer mapper to retrieve a list of layers for
54        this WMS.
55
56        It will be called automatically by pylons before each action method.
57
58        @todo: The layer mapper needs to come from somewhere.
59
60        """
61        self.layers = self.layerMapper.map(**kwargs)
62
63    #-------------------------------------------------------------------------
64    # Methods implementing stubs in OWSController
65
66    def _renderCapabilities(self, version, format):
67        t = ows_controller.templateLoader.load('wms_capabilities_1_1_1.xml')
68        return t.generate(c=c).render()
69
70    def _loadCapabilities(self):
71        """
72        @note: Assumes self.layers has already been created by __before__().
73
74        """
75        ows_controller.addOperation('GetMap', formats=self._pilImageFormats.keys())
76        ows_controller.addOperation('GetInfo')
77        log.debug('Loading capabilities contents')
78        c.capabilities.contents = Contents()
79        for layerName, layer in self.layers.items():
80            log.debug('Loading layer %s' % layerName)
81
82            # Get CRS/BBOX pairs
83            bboxObjs = []
84            for crs in layer.crss:
85                bbox = layer.getBBox(crs)
86                bboxObjs.append(BoundingBox(bbox[:2], bbox[2:], crs=crs))
87            # Get dimensions
88            dims = {}
89            for dimName, dim in layer.dimensions.items():
90                dims[dimName] = Dimension(valuesUnit=dim.units,
91                                          unitSymbol=dim.units,
92                                          possibleValues=
93                                            PossibleValues.fromAllowedValues(dim.extent))
94            # Create the ows_common object
95            ds = WmsDatasetSummary(identifier=layerName,
96                                   titles=[layer.title],
97                                   CRSs=layer.crss,
98                                   boundingBoxes=bboxObjs,
99                                   abstracts=[layer.abstract],
100                                   dimensions=dims)
101
102            c.capabilities.contents.datasetSummaries.append(ds)
103
104    #-------------------------------------------------------------------------
105    # OWS Operation methods
106   
107    def GetMap(self):
108
109        # Housekeeping
110        version = self.getOwsParam('version', default=self.validVersions[0])
111        if version not in self.validVersions:
112            raise InvalidParameterValue('Version %s not supported' % version,
113                                        'version')
114        styles = self.getOwsParam('styles', default='')
115        transparent = self.getOwsParam('transparent', default='FALSE')
116        bgcolor = self.getOwsParam('bgcolor', default='0xFFFFFF')
117
118        # Layer handling
119        layerName = self.getOwsParam('layers')
120        if ',' in layerName:
121            raise InvalidParameterValue(
122                'Multi-layer GetMap requests are not supported', 'layers')
123        try:
124            layerObj = self.layers[layerName]
125        except KeyError:
126            raise InvalidParameterValue('Layer %s not found' % layerName, 'layers')
127
128       
129        # Coordinate parameters
130        bbox = tuple(float(x) for x in self.getOwsParam('bbox').split(','))
131        width = int(self.getOwsParam('width'))
132        height = int(self.getOwsParam('height'))
133        srs = self.getOwsParam('srs')
134        if srs not in layerObj.crss:
135            raise InvalidParameterValue('Layer %s does not support SRS %s' % (layerName, srs))
136
137        # Get format
138        format = self.getOwsParam('format')
139        if format not in self._pilImageFormats:
140            raise InvalidParameterValue(
141                'Format %s not supported' % format, 'format')
142
143        # Dimension handling
144        dimValues = {}
145        for dimName, dim in layerObj.dimensions.items():
146            defaultValue = dim.extent[0]
147            dimValues[dimName] = self.getOwsParam(dimName, default=defaultValue)
148           
149        #---------------------------------------------------------------------
150        # The real work
151        #!TODO: Minimum and maximum values
152       
153        # Find the slab in the cache first
154        cacheKey = layerObj.getCacheKey(srs, dimValues)
155        try:
156            slab = self._layerSlabCache[cacheKey]
157        except KeyError:
158            slab = layerObj.getSlab(srs, dimValues, dict(minValue=0, maxValue=100))
159            self._layerSlabCache[cacheKey] = slab
160
161        # We must request a bbox within the layer's bbox.
162        lbbox = layerObj.getBBox(srs)
163        ibbox = bbox_util.intersection(bbox, lbbox)
164
165        log.debug('bbox = %s' % (bbox,))
166        log.debug('lbbox = %s' % (lbbox,))
167        log.debug('ibbox = %s' % (ibbox,))
168
169        # If bbox is not within layerObj.bbox then we need to calculate the
170        # pixel offset of the inner bbox, request the right width/height
171        # and paste the image into a blank background
172        if bbox == ibbox:
173            img = slab.getImage(bbox, width, height)
174            log.debug('slab image.size = %s' % (img.size,))
175        else:
176            sx, sy = bbox_util.relativeSize(ibbox, bbox)
177            log.debug('scaling: %s,%s' % (sx, sy))
178            img1 = slab.getImage(ibbox, int(width*sx), int(height*sy))
179            log.debug('inner image.size = %s' % (img1.size,))
180
181            img = Image.new('RGBA', (width, height))
182            pxOrigin = bbox_util.geoToPixel(ibbox[0], ibbox[3], bbox, width, height)
183            log.debug('pxOrigin = %s' % (pxOrigin,))
184            img.paste(img1, pxOrigin)
185           
186
187        # IE < 7 doesn't display the alpha layer right.  Here we sniff the
188        # user agent and remove the alpha layer if necessary.
189        try:
190            ua = request.headers['User-Agent']
191        except:
192            pass
193        else:
194            if 'MSIE' in ua and 'MSIE 7' not in ua:
195                img = img.convert('RGB')
196
197        buf = StringIO()
198        img.save(buf, self._pilImageFormats[format])
199
200        response.headers['Content-Type'] = format
201        response.write(buf.getvalue())
202
203        return request
204
205    def GetInfo(self):
206        from pprint import pformat
207        request.headers['Content-Type'] = 'text/ascii'
208        response.write('Some info about this service\n')
209        for layer in model.ukcip02.layers:
210            response.write('Layer %s: %s\n' % (layer, pformat(g.ukcip02_layers[layer].__dict__)))
211
212           
Note: See TracBrowser for help on using the repository browser.