source: cows/branches/migrate-py26-pylons10/cows/pylons/wfs_controller.py @ 7342

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows/branches/migrate-py26-pylons10/cows/pylons/wfs_controller.py@7342
Revision 7342, 14.1 KB checked in by spascoe, 9 years ago (diff)

New branch for migration to Python-2.6 and Pylons-1.0.

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