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

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

prototype support for attributes in getpropertyvalue method - need to check with ogc.

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