source: cows/trunk/cows/pylons/wfs_controller.py @ 5399

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows/trunk/cows/pylons/wfs_controller.py@5399
Revision 5399, 13.2 KB checked in by domlowe, 12 years ago (diff)

Changing PIL imports in cows

Line 
1# BSD Licence
2# Copyright (c) 2009, Science & Technology Facilities Council (STFC)
3# All rights reserved.
4#
5# See the LICENSE file in the source distribution of this software for
6# the full license text.
7
8"""
9WFS controller for OGC Web Services (OWS).
10
11@author: Dominic Lowe
12"""
13
14import re
15from cStringIO import StringIO
16from sets import Set
17from pylons import request, response, c
18import ConfigParser
19import xml.etree.ElementTree as etree
20import logging
21log = logging.getLogger(__name__)
22
23
24from cows.model.wfs import WfsFeatureSummary
25from cows.model.filterencoding import FEQueryProcessor
26from cows.model import PossibleValues, WGS84BoundingBox, BoundingBox, Contents
27from cows.pylons import ows_controller
28from cows.exceptions import *
29from cows import bbox_util
30from cows.service.imps.csmlbackend.config import config
31
32class WFSController(ows_controller.OWSController):
33    """
34    Subclass this controller in a pylons application and set the layerMapper
35    class attribute to implement a WFS. Each layer can be mapped to a Feature for the WFS.
36
37    @cvar layerMapper: an cows.service.wxs_iface.ILayerMapper object.
38   
39
40    """
41    layerMapper = None
42    _layerSlabCache = {}
43
44    #-------------------------------------------------------------------------
45    # Attributes required by OWSController
46
47    service = 'WFS'
48    owsOperations = (ows_controller.OWSController.owsOperations + ['DescribeFeatureType', 'GetFeature', 'DescribeStoredQueries','ListStoredQueries', 'GetPropertyValue'])
49    validVersions = ['1.1.0', '2.0.0']
50   
51
52    #-------------------------------------------------------------------------
53
54    def __before__(self, **kwargs):
55        """
56        This default implementation of __before__() will pass all routes
57        arguments to the layer mapper to retrieve a list of layers for
58        this WFS.
59
60        It will be called automatically by pylons before each action method.
61
62
63        """
64        log.debug('loading layers')
65        self.layers, self.featureset = self.layerMapper.map(**kwargs)
66        log.debug('Feature instances %s'%self.featureset.featureinstances)
67   
68        #-------------------------------------------------------------------------
69        # Methods implementing stubs in OWSController
70
71    def _renderCapabilities(self, version, format):
72        """
73        Renders capabilities document.
74        """
75        if version == '1.1.0':
76            t = ows_controller.templateLoader.load('wfs_capabilities_1_1_0.xml')
77        elif version == '2.0.0':
78            t = ows_controller.templateLoader.load('wfs_capabilities_2_0_0.xml')
79        else:
80            # We should never get here!  The framework should raise an exception before now.
81            raise RuntimeError("Version %s not supported" % version)
82       
83        return t.generate(c=c).render()
84
85    def _loadCapabilities(self):
86        """
87        @note: Assumes self.layers has already been created by __before__().
88        Builds capabilities document.
89
90        """
91        log.info('Loading WFS Capabilites')
92       
93        ows_controller.addOperation('GetFeature')
94        ows_controller.addOperation('DescribeFeature')
95        ows_controller.addOperation('DescribeStoredQueries')
96        ows_controller.addOperation('ListStoredQueries')
97        ows_controller.addOperation('GetPropertyValue')
98       
99       
100        featureInfoFormats = Set()
101
102        log.debug('Loading capabilities contents')
103        c.capabilities.contents = Contents()
104       
105       
106        ftlist={}
107        #       
108       
109        for layerName, layer in self.layers.items():
110            log.info('Loading layer %s' % layerName)
111#            log.info('feature type %s'%layer._feature)
112
113            wgs84BBox = WGS84BoundingBox(layer.wgs84BBox[:2],
114                                         layer.wgs84BBox[2:])
115           
116            ds = WfsFeatureSummary(keywords=layer.keywords, 
117                                   outputformats=layer.outputformats, 
118                                   identifier=layerName,
119                                   titles=[layer.title],
120                                   abstracts=[layer.abstract],                                   
121                                   wgs84BoundingBoxes=[wgs84BBox])
122
123            c.capabilities.contents.datasetSummaries.append(ds)
124
125    def _getSchema(self, typename):
126        namespace = typename.split(':')[0]
127        schemalocation = conf
128       
129    def _parsetypename(self, typename):
130        """ parse feature type name into schema and name and return schema"""       
131        if typename not in self.layers.keys():
132            raise InvalidParameterValue('Invalid typename parameter: %s. Typename must consist of namespace and featuretype separated with a colon, as displayed in the GetCapabilities response.'%typename, 'typename')
133   
134        namespace, ft = typename.split(':')
135        wfsconfiglocation=config['wfsconfig']
136        wfscfg = ConfigParser.ConfigParser()
137        wfscfg.read(wfsconfiglocation)     
138        xmlschema=open(wfscfg.get('application_schemas', namespace)).read()     
139        log.debug('location of application schema %s' %(xmlschema))
140        return xmlschema
141   
142    def _runQuery(self, queryxml = None, storedqueryid=None, typename=None, maxfeatures=None,**kwargs):
143        """ this is used by both the GetFeature and GetPropertyValue methods to
144        run a wfs:query over a featurecollection and return a subset of the collection.
145        The query may be defined as an xml <wfs:query> or may be referenced by a StoredQuery_id
146        """
147        additionalobjects=[] #not implemented for filter encoding, just for storedqueries (e.g. subsetting)
148        if queryxml:
149            qp=FEQueryProcessor()
150            resultset=qp.evaluate(self.featureset, queryxml)
151            log.debug('Final resultset from query processor %s'%resultset)
152        elif storedqueryid:
153            storedquery, func=self.layerMapper.queryDescriptions.queries[storedqueryid]
154            storedqueryresult=func(self.featureset, **kwargs)
155            if len(storedqueryresult) == 2:
156                resultset=storedqueryresult[0]
157                additionalobjects=storedqueryresult[1]
158            else:
159                resultset=storedqueryresult
160            log.debug('Final resultset from stored query %s'%resultset)
161        else:           
162            #If neither query or storedquery_id are provided then return entire featureset, filtered on 'typename' and/or 'maxfeatures' if supplied.
163            resultset=self.featureset.getFeatureList()
164            if typename:
165                templist = resultset[:]
166                for feature in templist:   #remove any features of the wrong feature type
167                    if feature.featuretype != typename:
168                        resultset.remove(feature)
169                resultset=resultset
170                if maxfeatures: #reduce the response to the requested number of feature instances
171                    maxf=int(maxfeatures)
172                    if len(resultset) >maxf:
173                        resultset=resultset[:maxf]
174        return resultset, additionalobjects
175
176    def _applyXPath(self, xpath, features):
177        """ applies an xpath expression to a set of features and returns
178        a set of wfs:members containing property values expressed in the xpath"""
179        resultset=[]       
180        #if xpath is looking for an attribute, handle it differently - this should be unnecessary when migrated to element tree 1.3
181        #i.e. if of the form /somepath/path[@attribute]
182        attstart=xpath.find('[')
183        if attstart == -1: #no attribute           
184            for feature in features:
185                featureXML=feature._feature.elem #the underlying XML
186                valuecomponent=featureXML.find(xpath)
187                if valuecomponent is not None:
188                    valstr=etree.tostring(valuecomponent)           
189                    resultset.append(valstr)
190        else:
191            #there is an attribute:
192            attributeName=xpath.split('[')[1][1:-1]
193            xpath=xpath.split('[')[0]
194            for feature in features:
195                featureXML=feature._feature.elem #the underlying XML
196                log.debug('xpath path %s'%xpath)
197                log.debug('attribute %s'%attributeName)
198                if len(xpath) ==0:
199                    valuecomponent=featureXML
200                else:
201                    valuecomponent=featureXML.find(xpath)
202                log.debug('valuecomponent %s'%valuecomponent)
203                if valuecomponent is not None:
204                    attribute=valuecomponent.get(attributeName)         
205                    resultset.append(attribute)           
206        log.debug(resultset)
207        return resultset     
208   
209   
210    def DescribeFeatureType(self):
211        """ DescribeFeatureType """
212        version = self.getOwsParam('version', default=self.validVersions[0])
213        if version not in self.validVersions:
214            raise InvalidParameterValue('Version %s not supported' % version,
215                                        'version')
216        typename=self.getOwsParam('typename')
217        ftschema =self._parsetypename(typename)
218        log.debug(self.layers.items())       
219               
220        outputformat=self.getOwsParam('outputformat', default='text/xml')
221       
222        #temporarily returns entire schema
223        #TODO: return single featuretype definition
224        msg  = ftschema
225        response.headers['content-type'] = 'text/xml'
226        return msg
227           
228 
229    def GetPropertyValue(self):
230        """ GetPropertyValue request - similar to get feature but only returns chosen property
231        values """
232        valueReference = self.getOwsParam('valueReference')
233        queryxml=self.getOwsParam('query',default=None)
234        storedqueryid=self.getOwsParam('storedquery_id', default=None)
235        typename=self.getOwsParam('typename', default=None)
236        #get any other parameters from self._owsParams and pass them to the stored query
237        otherparams={}
238        for key in self._owsParams.keys():
239            if key not in ['query', 'request', 'service', 'version', 'storedquery_id', 'typename', 'valuereference']:
240                otherparams[key]=self._owsParams[key]     
241        featureresultset, additionalobjects =self._runQuery(queryxml, storedqueryid, typename, **otherparams)       
242       
243        #Now need to take account of valueReferencexpath, and distill the
244        #resultset down to just the requested properties.
245        c.resultset=self._applyXPath(valueReference, featureresultset)
246       
247        response.headers['content-type'] = 'text/xml'
248        #TODO, new template for values
249        t = ows_controller.templateLoader.load('wfs_valuecollection.xml')
250        return t.generate(c=c).render() 
251 
252    def GetFeature(self):
253        """ GetFeature request
254        """
255        log.info('GET FEATURE Request made: %s'%request)
256        version = self.getOwsParam('version', default=self.validVersions[0])
257        if version not in self.validVersions:
258            raise InvalidParameterValue('Version %s not supported' % version,
259                                        'version')
260
261        #The GetFeature request may either use the 'query' filter encoding or a
262        #storedquery, but not both:     
263        #Parse the query to analyse the filters it contains
264        queryxml=self.getOwsParam('query',default=None)
265        storedqueryid=self.getOwsParam('storedquery_id', default=None)
266        typename=self.getOwsParam('typename', default=None)
267        maxfeatures=self.getOwsParam('maxfeatures', default=None)
268       
269        #retrieve any other parameters and pass them off to the stored query (they are ignored in the case of the queryxml option
270        otherparams={}
271        for key in self._owsParams.keys():
272            if key not in ['query', 'request', 'service', 'version', 'storedquery_id', 'typename', 'maxfeatures']:
273                otherparams[key]=self._owsParams[key]       
274        c.resultset, c.additionalobjects=self._runQuery(queryxml, storedqueryid, typename, maxfeatures, **otherparams)
275               
276        #Group resultset together in a wfs feature collection (use template)
277        response.headers['content-type'] = 'text/xml'
278        t = ows_controller.templateLoader.load('wfs_featurecollection.xml')
279        return t.generate(c=c).render()           
280
281   
282    def DescribeStoredQueries(self):
283        """ DescribeStoredQueries method. Takes zero or more stored query ids as args"""
284        allqueries=self.layerMapper.queryDescriptions.queries       
285        storedqueryid=self.getOwsParam('storedqueryid', default=None)
286        if storedqueryid is None:
287            c.storedqueries = self.layerMapper.queryDescriptions.queries
288        else:
289            c.storedqueries={}
290            for queryid in storedqueryid.split(','):
291                c.storedqueries[queryid]=self.layerMapper.queryDescriptions.queries[queryid]       
292        response.headers['Content-Type'] = 'text/xml'
293        t = ows_controller.templateLoader.load('wfs_describestoredqueries.xml')
294        return t.generate(c=c).render() 
295         
296    def ListStoredQueries(self):
297        """ DescribeStoredQueries method. Takes zero or more stored query ids as args"""
298        c.storedqueries = self.layerMapper.queryDescriptions.queries
299        t = ows_controller.templateLoader.load('wfs_liststoredqueries.xml')
300        response.headers['Content-Type'] = 'text/xml'
301        return t.generate(c=c).render() 
Note: See TracBrowser for help on using the repository browser.