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

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

Adding stub code for WFS 2.0 stored query implementation

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