source: cows/trunk/cows/service/imps/csmlbackend/wfs_csmllayer.py @ 4417

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows/trunk/cows/service/imps/csmlbackend/wfs_csmllayer.py@4417
Revision 4417, 12.3 KB checked in by domlowe, 12 years ago (diff)

Implemented first cut of GetPropertyValue? method for wfs

Line 
1"""
2implementation of ILayerMapper, IwfsLayer, IDimension, ILayerSlab interfaces, as defined in wfs_iface.py & wxs_iface.py
3
4"""
5import cows.service.imps.csmlbackend.wfs_csmlstoredqueries as CSMLQueries
6from cows.service.imps.csmlbackend.csmlcommon import CSMLLayerMapper, CSMLConnector
7from cows.service.wfs_iface import *
8from cows.model.storedquery import *
9import csml
10from xml.etree import ElementTree as etree
11from shapely.geometry import Polygon, Point
12from cows.bbox_util import union
13
14import logging
15log = logging.getLogger(__name__)
16
17class CSMLwfsLayerMapper(CSMLLayerMapper):
18    """
19    Map keyword arguments to a collection of layers.
20    Supports the retrieval of sets of layers according to arbitrary
21    keyword/value pairs.
22    Implements  ILayerMapper
23   
24    WFS differs from WMS/WCS in that the 'layers' are feature types, not instances.
25    So the CSMLwfsLayerMapper map method returns both a map of feature types and a map
26    of feature instances as both are needed for the GetFeature method to work.
27   
28    """
29    def __init__(self):
30        super(CSMLwfsLayerMapper, self).__init__()
31        self.featureinstancecache={}
32        self.queryDescriptions=CSMLStoredQueries()
33   
34
35    def map(self, **kwargs):
36        """
37        Given csml.parser.Dataset object list the names of
38        all layers available.
39       
40        @return: A mapping of layer names to ILayer implementations.
41        @raise ValueError: If no layers are available for these keywords.
42        """
43        fileoruri=kwargs['fileoruri']
44        if fileoruri in self.layermapcache.keys():
45            #we've accessed this layer map before, get it from the cache dictionary
46            return self.layermapcache[fileoruri], self.featureinstancecache[fileoruri]
47         
48        ds = self.connector.getCsmlDoc(fileoruri)
49       
50        #The WFS differs from WMS & WCS in that the contents layer is a list of
51        #feature *types* not *instances*. However a record of instances is also
52        #needed to fulfil GetFeature requests:
53        featureset=CSMLFeatureSet() #holds feature instances               
54        layermap={} #feature types       
55        aggregatedBBoxes={}#for aggregations of bounding boxes.
56       
57
58        for feature in csml.csmllibs.csmlextra.listify(ds.featureCollection.featureMembers):
59            title, abstract=self.getInfo(feature)
60            featureset.featureinstances[feature.id]=CSMLFeatureInstance(title, abstract, feature)             
61        for id, instance in featureset.featureinstances.iteritems():
62            ftype='csml:' + instance.featuretype
63            if ftype not in layermap.keys():
64                layermap[ftype]=CSMLwfsLayer(ftype, instance.wgs84BBox)
65                #instantiate an aggregator to compare future bounding boxes with.
66                aggregatedBBoxes[ftype]= instance.wgs84BBox               
67            else:
68                #the featuretype has already been stored in the dictionary.
69                #but, the bounding box may need changing to accommodate this new feature instance.
70#                log.debug('Checking bbox for feature id: %s and title: %s'%(instance._feature.id, instance.title))
71                currentbbox=aggregatedBBoxes[ftype]
72                newbbox=union(currentbbox, instance.wgs84BBox)
73                aggregatedBBoxes[ftype]= newbbox
74                layermap[ftype]=CSMLwfsLayer(ftype, newbbox)
75           
76
77        if len(layermap) > 0:
78            #cache results
79            self.layermapcache[fileoruri]=layermap
80            self.featureinstancecache[fileoruri]=featureset
81            return layermap, featureset
82        else:
83            raise ValueError
84
85class CSMLStoredQueries(object):
86    """ Holds definitions of supported WFS stored queries and mappings to functional implementations"""
87    def __init__(self):
88        #self.queries is a dictionary of form: {id:(StoredQuery, func)}
89        #where func is the name of the implementation of the stored query (typically) in wfs_csmlstoredqueries     
90        self.queries={}
91        qid='queryOne'       
92        qet=QueryExpressionText('csml:PointSeriesFeature') #TODO, possibly this could differ for different features...
93        pex=ParameterExpression('nameblah1', 'typeblah1')
94        query=StoredQuery(qid, title='blah1', abstract = 'my blah1 query', queryExpressionText = qet, parameter=pex)
95        self.queries[qid]=(query, CSMLQueries.query_one_func)
96       
97        qid='queryTwo'
98        qet=QueryExpressionText('csml:GridSeriesFeature') #TODO, possibly this could differ for different features...
99        pex=ParameterExpression('nameblah2', 'typeblah2')
100        query=StoredQuery(qid, title='blah2', abstract = 'my blah2 query', queryExpressionText = qet, parameter=pex)
101        self.queries[qid]=(query, CSMLQueries.query_two_func)     
102
103        #query to select features by phenomenon
104        qid='phenomenonQuery'
105        qet=QueryExpressionText('csml:GridSeriesFeature')#TODO, this could definitely differ for different features...
106        pex=ParameterExpression('phenomenon', 'string')
107        query=StoredQuery(qid, title='SelectFeaturesByPhenomenon', abstract = 'Select features based on their phenomenon type, e.g. "temperature"', queryExpressionText = qet, parameter=pex)
108        self.queries[qid]=(query, CSMLQueries.query_getFeatureByPhenomenon)
109
110
111
112class CSMLFeatureSet(IFeatureSet):
113    """ A set of features available via a WFS. Supports querying methods as used by OGG filters """
114    def __init__(self):
115        self.featureinstances={}
116
117    def getFeatureList(self):
118        results=[]
119        for featureid,feature in self.featureinstances.iteritems():
120            results.append(feature)
121        return results
122
123    def getFeatureByGMLid(self, gmlid):
124        return self.featureinstances[gmlid]
125   
126    def _checkforOverlap(self, filterbbox, featurebbox):
127        """ Uses Shapely Polygons to calculate bounding box intersections """
128        log.debug('comparing against %s'%str(filterbbox))       
129        filterpolygon=Polygon(((filterbbox[0],filterbbox[1]),(filterbbox[0],filterbbox[3]),(filterbbox[2],filterbbox[3]),(filterbbox[2],filterbbox[1])))
130        featurepolygon=Polygon(((featurebbox[0],featurebbox[1]),(featurebbox[0],featurebbox[3]),(featurebbox[2],featurebbox[3]),(featurebbox[2],featurebbox[1])))       
131        log.debug(dir(filterpolygon))
132        log.debug('intersect result%s'%featurepolygon.intersects(filterpolygon))
133        return filterpolygon.intersects(featurepolygon)
134   
135    def _checkforPointinBox(self, filterbbox, location):
136        """ Uses Shapely Polygons to calculate bounding box intersections """
137        log.debug('comparing against %s'%str(filterbbox))       
138        filterpolygon=Polygon(((filterbbox[0],filterbbox[1]),(filterbbox[0],filterbbox[3]),(filterbbox[2],filterbbox[3]),(filterbbox[2],filterbbox[1])))
139        featurepoint=Point(float(location[0]), float(location[1]))
140        log.debug(featurepoint.within(filterpolygon))
141        log.debug('intersect result%s'%featurepoint.intersects(filterpolygon))
142        return featurepoint.intersects(filterpolygon)
143   
144    def getFeaturesByBBox(self,bboxtuple, srsname):         
145        log.debug('GET FEATURES BY BBOX')
146        result=[]
147        log.debug('Request BBOX: %s %s'%(bboxtuple,srsname))
148        for featureid,feature in self.featureinstances.iteritems():
149            if hasattr(feature._feature, 'boundedBy'):
150                log.debug('Checking bbox for feature %s: '%feature._feature.id)
151                lowercorner=feature._feature.boundedBy.lowerCorner.CONTENT.split()
152                uppercorner=feature._feature.boundedBy.upperCorner.CONTENT.split()
153                featurebbox=(float(lowercorner[0]), float(lowercorner[1]), float(uppercorner[0]), float(uppercorner[1]))               
154                if self._checkforOverlap(bboxtuple, featurebbox):
155                    result.append(feature) 
156            elif hasattr(feature._feature, 'location'):
157                log.debug('Checking location for feature %s: '%feature._feature.id)
158                log.debug(feature._feature.location)
159                location=feature._feature.location.CONTENT.split()
160                if self._checkforPointinBox(bboxtuple, location):
161                    result.append(feature)                       
162        return result
163
164    def getFeaturesByPropertyBetween(self, propertyname, minvalue, maxvalue):
165        log.debug('GET FEATURES BY PropertyBetween')
166        log.debug('propertyname, min, max: %s, %s, %s'%(propertyname, minvalue, maxvalue))
167        result = []
168        #Identifies times overlapping between filter and feature times
169        if propertyname=='csml:value/csml:PointSeriesCoverage/csml:pointSeriesDomain/csml:TimeSeries/csml:timePositionList':
170            for featureid, feature in self.featureinstances.iteritems():
171                featuretimes=feature._feature.value.pointSeriesDomain.timePositionList.CONTENT.split()
172                featureMinTime=featuretimes[0]
173                featureMaxTime=featuretimes[len(featuretimes)-1]
174                if csml.csmllibs.csmltime.compareTimes(featureMinTime, minvalue ,featureMaxTime) == 1:
175                    result.append(feature)
176                elif csml.csmllibs.csmltime.compareTimes(featureMinTime, maxvalue ,featureMaxTime) == 1:
177                    result.append(feature)   
178        return result
179       
180    def getFeaturesByPropertyEqualTo(self, propertyname, value):
181        log.debug('GET FEATURES BY PropertyEqualTo, value=%s'%value)
182        result=[]
183        value=value #value may be unicode
184        if propertyname == 'csml:parameter':
185            log.debug('filtering on csml:parameter')         
186            for featureid,feature in self.featureinstances.iteritems():
187                if feature._feature.parameter.getStandardName() == value:
188                    result.append(feature)     
189                elif feature._feature.parameter.getNonStandardName() == value:                   
190                    result.append(feature) 
191            return result
192   
193class CSMLFeatureInstance(IFeatureInstance):
194    def __init__(self, title, abstract, feature):
195        """ representing a CSML Feature Instance
196        @ivar title: The title of the feature instance
197        @ivar abstract: The abstract of the feature instance
198        @ivar feature: the csml feature instance object
199         """
200        self.title=title
201        self.abstract=abstract
202        self._feature=feature
203        self.featuretype=self._feature.__class__.__name__
204        bb= self._feature.getCSMLBoundingBox().getBox()
205        #convert 0 - 360 to -180, 180 as per common WMS convention
206        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
207            bb[0], bb[2]=-180, 180
208        self.wgs84BBox = bb
209       
210    def toGML(self):
211        """ Create a GML (CSML) representation of the feature """
212       
213        #TODO un-hardcode this:
214        emptyelem=etree.Element('{http://ndg.nerc.ac.uk/csml}GridSeriesFeature')
215        csmlelem=self._feature.toXML(emptyelem)
216        return etree.tostring(csmlelem)
217       
218       
219     
220class CSMLwfsLayer(IwfsLayer):
221    """ representing a WFS FeatureType (termed layer here). Implements IwfsLayer
222    @ivar featuretype: The namespaced name of the feature type, e.g. csml:PointSeriesFeature
223   
224    """
225    def __init__(self, featuretype, wgs84bb):
226        self.type=featuretype
227        #Have to hard code some definitions for CSML feature types to
228        #use in the capabilities document as they don't exist anywhere else.
229        #Hardcoding is okay as this is a CSML specific interface, so will only ever deal
230        #with CSML feature types.
231        #TODO: However, might be better to move to some sort of schema.cfg file?
232        self.wgs84BBox=wgs84bb     
233        self.outputformats=['text/xml: subtype=csml/2.0']   
234        if self.type=='csml:GridSeriesFeature':
235            self.title='GridSeriesFeature as defined in Climate Science Modelling Language.'
236            self.abstract='The CSML GridSeriesFeature is used to represent 4D gridded data such as atmospheric model output.'
237            self.keywords=['Grid', 'Climate', 'CSML']
238        elif self.type=='csml:PointSeriesFeature':
239            self.title='PointSeriesFeature as defined in Climate Science Modelling Language.'
240            self.abstract='The CSML PointSeriesFeature represents a time series of measurements at a single point in space.'
241            self.keywords=['Point', 'Timeseries', 'Climate', 'CSML']
242        #TODO: definitions for all feature types.
243       
244       
245
246
Note: See TracBrowser for help on using the repository browser.