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

RevLine 
[4274]1"""
2implementation of ILayerMapper, IwfsLayer, IDimension, ILayerSlab interfaces, as defined in wfs_iface.py & wxs_iface.py
3
4"""
5
[4352]6from cows.service.imps.csmlbackend.csmlcommon import CSMLLayerMapper, CSMLConnector
[4292]7from cows.service.wfs_iface import *
[4398]8from cows.model.storedquery import *
[4274]9import csml
[4289]10from xml.etree import ElementTree as etree
[4359]11from shapely.geometry import Polygon, Point
[4352]12from cows.bbox_util import union
[4274]13
14import logging
15log = logging.getLogger(__name__)
16
17class CSMLwfsLayerMapper(CSMLLayerMapper):
18    """
19    Map keyword arguments to a collection of layers.
[4278]20    Supports the retrieval of sets of layers according to arbitrary
[4274]21    keyword/value pairs.
22    Implements  ILayerMapper
23   
[4278]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   
[4274]28    """
29    def __init__(self):
30        super(CSMLwfsLayerMapper, self).__init__()
[4278]31        self.featureinstancecache={}
[4398]32        self.queryDescriptions=CSMLStoredQueries()
[4274]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
[4278]46            return self.layermapcache[fileoruri], self.featureinstancecache[fileoruri]
[4274]47         
[4351]48        ds = self.connector.getCsmlDoc(fileoruri)
[4278]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:
[4292]53        featureset=CSMLFeatureSet() #holds feature instances               
[4278]54        layermap={} #feature types       
[4352]55        aggregatedBBoxes={}#for aggregations of bounding boxes.
56       
[4284]57
[4274]58        for feature in csml.csmllibs.csmlextra.listify(ds.featureCollection.featureMembers):
[4351]59            title, abstract=self.getInfo(feature)
[4292]60            featureset.featureinstances[feature.id]=CSMLFeatureInstance(title, abstract, feature)             
61        for id, instance in featureset.featureinstances.iteritems():
[4278]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.
[4352]66                aggregatedBBoxes[ftype]= instance.wgs84BBox               
[4278]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.
[4289]70#                log.debug('Checking bbox for feature id: %s and title: %s'%(instance._feature.id, instance.title))
[4352]71                currentbbox=aggregatedBBoxes[ftype]
72                newbbox=union(currentbbox, instance.wgs84BBox)
73                aggregatedBBoxes[ftype]= newbbox
74                layermap[ftype]=CSMLwfsLayer(ftype, newbbox)
[4289]75           
[4278]76
[4274]77        if len(layermap) > 0:
[4278]78            #cache results
[4274]79            self.layermapcache[fileoruri]=layermap
[4292]80            self.featureinstancecache[fileoruri]=featureset
81            return layermap, featureset
[4274]82        else:
83            raise ValueError
84
[4398]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
[4292]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):
[4303]105        return self.featureinstances[gmlid]
[4292]106   
[4312]107    def _checkforOverlap(self, filterbbox, featurebbox):
[4359]108        """ Uses Shapely Polygons to calculate bounding box intersections """
109        log.debug('comparing against %s'%str(filterbbox))       
[4312]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])))       
[4359]112        log.debug(dir(filterpolygon))
113        log.debug('intersect result%s'%featurepolygon.intersects(filterpolygon))
[4312]114        return filterpolygon.intersects(featurepolygon)
115   
[4359]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   
[4312]125    def getFeaturesByBBox(self,bboxtuple, srsname):         
[4311]126        log.debug('GET FEATURES BY BBOX')
[4312]127        result=[]
[4359]128        log.debug('Request BBOX: %s %s'%(bboxtuple,srsname))
[4312]129        for featureid,feature in self.featureinstances.iteritems():
[4359]130            if hasattr(feature._feature, 'boundedBy'):
131                log.debug('Checking bbox for feature %s: '%feature._feature.id)
[4312]132                lowercorner=feature._feature.boundedBy.lowerCorner.CONTENT.split()
133                uppercorner=feature._feature.boundedBy.upperCorner.CONTENT.split()
[4359]134                featurebbox=(float(lowercorner[0]), float(lowercorner[1]), float(uppercorner[0]), float(uppercorner[1]))               
[4312]135                if self._checkforOverlap(bboxtuple, featurebbox):
[4359]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)                       
[4312]143        return result
[4311]144
[4394]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       
[4354]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
[4303]170   
[4284]171class CSMLFeatureInstance(IFeatureInstance):
[4274]172    def __init__(self, title, abstract, feature):
[4278]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         """
[4274]178        self.title=title
179        self.abstract=abstract
180        self._feature=feature
[4278]181        self.featuretype=self._feature.__class__.__name__
[4274]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
[4278]187       
[4289]188    def toGML(self):
189        """ Create a GML (CSML) representation of the feature """
190       
191        #TODO un-hardcode this:
[4291]192        emptyelem=etree.Element('{http://ndg.nerc.ac.uk/csml}GridSeriesFeature')
193        csmlelem=self._feature.toXML(emptyelem)
194        return etree.tostring(csmlelem)
[4289]195       
196       
[4278]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.
[4284]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']   
[4278]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.'
[4284]215            self.keywords=['Grid', 'Climate', 'CSML']
[4278]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.'
[4284]219            self.keywords=['Point', 'Timeseries', 'Climate', 'CSML']
[4278]220        #TODO: definitions for all feature types.
221       
222       
223
Note: See TracBrowser for help on using the repository browser.