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

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

More prototype support for stored queries.

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        query=StoredQuery(qid, queryExpressionText = qet)
94        self.queries[qid]=(query, CSMLQueries.query_one_func)
95       
96        qid='queryTwo'
97        qet=QueryExpressionText('csml:GridSeriesFeature') #TODO, possibly this could differ for different features...
98        query=StoredQuery(qid,queryExpressionText = qet)
99        self.queries[qid]=(query, CSMLQueries.query_two_func)     
100
101class CSMLFeatureSet(IFeatureSet):
102    """ A set of features available via a WFS. Supports querying methods as used by OGG filters """
103    def __init__(self):
104        self.featureinstances={}
105   
106    def getFeatureByGMLid(self, gmlid):
107        return self.featureinstances[gmlid]
108   
109    def _checkforOverlap(self, filterbbox, featurebbox):
110        """ Uses Shapely Polygons to calculate bounding box intersections """
111        log.debug('comparing against %s'%str(filterbbox))       
112        filterpolygon=Polygon(((filterbbox[0],filterbbox[1]),(filterbbox[0],filterbbox[3]),(filterbbox[2],filterbbox[3]),(filterbbox[2],filterbbox[1])))
113        featurepolygon=Polygon(((featurebbox[0],featurebbox[1]),(featurebbox[0],featurebbox[3]),(featurebbox[2],featurebbox[3]),(featurebbox[2],featurebbox[1])))       
114        log.debug(dir(filterpolygon))
115        log.debug('intersect result%s'%featurepolygon.intersects(filterpolygon))
116        return filterpolygon.intersects(featurepolygon)
117   
118    def _checkforPointinBox(self, filterbbox, location):
119        """ Uses Shapely Polygons to calculate bounding box intersections """
120        log.debug('comparing against %s'%str(filterbbox))       
121        filterpolygon=Polygon(((filterbbox[0],filterbbox[1]),(filterbbox[0],filterbbox[3]),(filterbbox[2],filterbbox[3]),(filterbbox[2],filterbbox[1])))
122        featurepoint=Point(float(location[0]), float(location[1]))
123        log.debug(featurepoint.within(filterpolygon))
124        log.debug('intersect result%s'%featurepoint.intersects(filterpolygon))
125        return featurepoint.intersects(filterpolygon)
126   
127    def getFeaturesByBBox(self,bboxtuple, srsname):         
128        log.debug('GET FEATURES BY BBOX')
129        result=[]
130        log.debug('Request BBOX: %s %s'%(bboxtuple,srsname))
131        for featureid,feature in self.featureinstances.iteritems():
132            if hasattr(feature._feature, 'boundedBy'):
133                log.debug('Checking bbox for feature %s: '%feature._feature.id)
134                lowercorner=feature._feature.boundedBy.lowerCorner.CONTENT.split()
135                uppercorner=feature._feature.boundedBy.upperCorner.CONTENT.split()
136                featurebbox=(float(lowercorner[0]), float(lowercorner[1]), float(uppercorner[0]), float(uppercorner[1]))               
137                if self._checkforOverlap(bboxtuple, featurebbox):
138                    result.append(feature) 
139            elif hasattr(feature._feature, 'location'):
140                log.debug('Checking location for feature %s: '%feature._feature.id)
141                log.debug(feature._feature.location)
142                location=feature._feature.location.CONTENT.split()
143                if self._checkforPointinBox(bboxtuple, location):
144                    result.append(feature)                       
145        return result
146
147    def getFeaturesByPropertyBetween(self, propertyname, minvalue, maxvalue):
148        log.debug('GET FEATURES BY PropertyBetween')
149        log.debug('propertyname, min, max: %s, %s, %s'%(propertyname, minvalue, maxvalue))
150        result = []
151        #Identifies times overlapping between filter and feature times
152        if propertyname=='csml:value/csml:PointSeriesCoverage/csml:pointSeriesDomain/csml:TimeSeries/csml:timePositionList':
153            for featureid, feature in self.featureinstances.iteritems():
154                featuretimes=feature._feature.value.pointSeriesDomain.timePositionList.CONTENT.split()
155                featureMinTime=featuretimes[0]
156                featureMaxTime=featuretimes[len(featuretimes)-1]
157                if csml.csmllibs.csmltime.compareTimes(featureMinTime, minvalue ,featureMaxTime) == 1:
158                    result.append(feature)
159                elif csml.csmllibs.csmltime.compareTimes(featureMinTime, maxvalue ,featureMaxTime) == 1:
160                    result.append(feature)   
161        return result
162       
163    def getFeaturesByPropertyEqualTo(self, propertyname, value):
164        log.debug('GET FEATURES BY PropertyEqualTo')
165        result=[]
166        if propertyname == 'csml:parameter':
167            log.debug('filtering on csml:parameter')         
168            for featureid,feature in self.featureinstances.iteritems():
169                if feature._feature.parameter.getStandardName() == value:
170                    result.append(feature)       
171        return result
172   
173class CSMLFeatureInstance(IFeatureInstance):
174    def __init__(self, title, abstract, feature):
175        """ representing a CSML Feature Instance
176        @ivar title: The title of the feature instance
177        @ivar abstract: The abstract of the feature instance
178        @ivar feature: the csml feature instance object
179         """
180        self.title=title
181        self.abstract=abstract
182        self._feature=feature
183        self.featuretype=self._feature.__class__.__name__
184        bb= self._feature.getCSMLBoundingBox().getBox()
185        #convert 0 - 360 to -180, 180 as per common WMS convention
186        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
187            bb[0], bb[2]=-180, 180
188        self.wgs84BBox = bb
189       
190    def toGML(self):
191        """ Create a GML (CSML) representation of the feature """
192       
193        #TODO un-hardcode this:
194        emptyelem=etree.Element('{http://ndg.nerc.ac.uk/csml}GridSeriesFeature')
195        csmlelem=self._feature.toXML(emptyelem)
196        return etree.tostring(csmlelem)
197       
198       
199     
200class CSMLwfsLayer(IwfsLayer):
201    """ representing a WFS FeatureType (termed layer here). Implements IwfsLayer
202    @ivar featuretype: The namespaced name of the feature type, e.g. csml:PointSeriesFeature
203   
204    """
205    def __init__(self, featuretype, wgs84bb):
206        self.type=featuretype
207        #Have to hard code some definitions for CSML feature types to
208        #use in the capabilities document as they don't exist anywhere else.
209        #Hardcoding is okay as this is a CSML specific interface, so will only ever deal
210        #with CSML feature types.
211        #TODO: However, might be better to move to some sort of schema.cfg file?
212        self.wgs84BBox=wgs84bb     
213        self.outputformats=['text/xml: subtype=csml/2.0']   
214        if self.type=='csml:GridSeriesFeature':
215            self.title='GridSeriesFeature as defined in Climate Science Modelling Language.'
216            self.abstract='The CSML GridSeriesFeature is used to represent 4D gridded data such as atmospheric model output.'
217            self.keywords=['Grid', 'Climate', 'CSML']
218        elif self.type=='csml:PointSeriesFeature':
219            self.title='PointSeriesFeature as defined in Climate Science Modelling Language.'
220            self.abstract='The CSML PointSeriesFeature represents a time series of measurements at a single point in space.'
221            self.keywords=['Point', 'Timeseries', 'Climate', 'CSML']
222        #TODO: definitions for all feature types.
223       
224       
225
Note: See TracBrowser for help on using the repository browser.