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

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

Fixes to enable Point and GridSeries? subsetting in WFS.

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                    resultset.append(attribute)           
189        log.debug(resultset)
190        return resultset     
191   
192   
193    def DescribeFeatureType(self):
194        """ DescribeFeatureType """
195        version = self.getOwsParam('version', default=self.validVersions[0])
196        if version not in self.validVersions:
197            raise InvalidParameterValue('Version %s not supported' % version,
198                                        'version')
199        typename=self.getOwsParam('typename')
200        ftschema =self._parsetypename(typename)
201        log.debug(self.layers.items())       
202               
203        outputformat=self.getOwsParam('outputformat', default='text/xml')
204       
205        #temporarily returns entire schema
206        #TODO: return single featuretype definition
207        msg  = ftschema
208        response.headers['content-type'] = 'text/xml'
209        return msg
210           
211 
212    def GetPropertyValue(self):
213        """ GetPropertyValue request - similar to get feature but only returns chosen property
214        values """
215        valueReference = self.getOwsParam('valueReference')
216        queryxml=self.getOwsParam('query',default=None)
217        storedqueryid=self.getOwsParam('storedquery_id', default=None)
218        #get any other parameters from self._owsParams and pass them to the stored query
219        otherparams={}
220        for key in self._owsParams.keys():
221            if key not in ['query', 'request', 'service', 'version', 'storedquery_id', 'valuereference']:
222                otherparams[key]=self._owsParams[key]     
223        featureresultset, additionalobjects =self._runQuery(queryxml, storedqueryid, **otherparams)       
224       
225        #Now need to take account of valueReferencexpath, and distill the
226        #resultset down to just the requested properties.
227        c.resultset=self._applyXPath(valueReference, featureresultset)
228       
229        response.headers['content-type'] = 'text/xml'
230        #TODO, new template for values
231        t = ows_controller.templateLoader.load('wfs_valuecollection.xml')
232        return t.generate(c=c).render() 
233 
234    def GetFeature(self):
235        """ GetFeature request
236        """
237        version = self.getOwsParam('version', default=self.validVersions[0])
238        if version not in self.validVersions:
239            raise InvalidParameterValue('Version %s not supported' % version,
240                                        'version')
241
242        #The GetFeature request may either use the 'query' filter encoding or a
243        #storedquery, but not both:     
244        #Parse the query to analyse the filters it contains
245        queryxml=self.getOwsParam('query',default=None)
246        storedqueryid=self.getOwsParam('storedquery_id', default=None)
247       
248        #retrieve any other parameters and pass them off to the stored query (they are ignored in the case of the queryxml option
249        otherparams={}
250        for key in self._owsParams.keys():
251            if key not in ['query', 'request', 'service', 'version', 'storedquery_id']:
252                otherparams[key]=self._owsParams[key]       
253        c.resultset, c.additionalobjects=self._runQuery(queryxml, storedqueryid, **otherparams)
254               
255        #Group resultset together in a wfs feature collection (use template)
256        response.headers['content-type'] = 'text/xml'
257        t = ows_controller.templateLoader.load('wfs_featurecollection.xml')
258        return t.generate(c=c).render()           
259
260   
261    def DescribeStoredQueries(self):
262        """ DescribeStoredQueries method. Takes zero or more stored query ids as args"""
263        allqueries=self.layerMapper.queryDescriptions.queries       
264        storedqueryid=self.getOwsParam('storedqueryid', default=None)
265        if storedqueryid is None:
266            c.storedqueries = self.layerMapper.queryDescriptions.queries
267        else:
268            c.storedqueries={}
269            for queryid in storedqueryid.split(','):
270                c.storedqueries[queryid]=self.layerMapper.queryDescriptions.queries[queryid]       
271        response.headers['Content-Type'] = 'text/xml'
272        t = ows_controller.templateLoader.load('wfs_describestoredqueries.xml')
273        return t.generate(c=c).render() 
274         
275    def ListStoredQueries(self):
276        """ DescribeStoredQueries method. Takes zero or more stored query ids as args"""
277        c.storedqueries = self.layerMapper.queryDescriptions.queries
278        t = ows_controller.templateLoader.load('wfs_liststoredqueries.xml')
279        response.headers['Content-Type'] = 'text/xml'
280        return t.generate(c=c).render() 
Note: See TracBrowser for help on using the repository browser.