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

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

Fixed Stored Query definitions so multiple parameters show up.

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.parser
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       
92        #simple example query
93        qid='queryOne'       
94        qet=QueryExpressionText('xyzml:SomeFeatureType') 
95        pex1=ParameterExpression('arg1', 'xsd:string')
96        pex2=ParameterExpression('arg2', 'xsd:string')
97        query=StoredQuery(qid, title='test query', abstract = 'my test query', queryExpressionText = qet, parameter=[pex1, pex2])
98        self.queries[qid]=(query, CSMLQueries.query_one_func)
99     
100         
101        #mandatory get feature by id
102        qid='urn-x:wfs:StoredQueryId:ISO:GetFeatureById'
103        qet=QueryExpressionText('csml:AbstractFeature')
104        pex=ParameterExpression('id', 'xsd:anyURI')
105        query=StoredQuery(qid, title='GetFeatureById', abstract = 'Get any feature by id', queryExpressionText = qet, parameter=[pex])
106        self.queries[qid]=(query, CSMLQueries.query_getFeatureById)
107
108        #query to select features by phenomenon
109        qid='urn-x:wfs:StoredQueryId:badc.nerc.ac.uk:phenomenonQuery'
110        qet=QueryExpressionText('csml:AbstractFeature')#TODO, this could definitely differ for different features...
111        pex=ParameterExpression('phenomenon', 'xsd:string')
112        query=StoredQuery(qid, title='SelectFeaturesByPhenomenon', abstract = 'Select features based on their phenomenon type, e.g. "temperature"', queryExpressionText = qet, parameter=[pex])
113        self.queries[qid]=(query, CSMLQueries.query_getFeatureByPhenomenon)
114       
115        #query to extract a PointFeature from a PointSeriesFeature
116        qid='urn-x:wfs:StoredQueryId:badc.nerc.ac.uk:extractPointFromPointSeries'
117        qet=QueryExpressionText('csml:PointFeature')
118        pex1=ParameterExpression('featureid', 'xsd:anyURI')
119        pex2=ParameterExpression('timeinstance', 'gml:TimePositionUnion')
120        query=StoredQuery(qid, title='ExtractPointFromPointSeries', abstract = 'Extract a csml:PointFeature for a single time instance from a csml:PointSeriesFeature', queryExpressionText = qet, parameter=[pex1, pex2])
121        self.queries[qid]=(query, CSMLQueries.query_extractPointFromPointSeries)
122
123        #query to extract a GridSeriesFeature from a GridSeriesFeature
124        qid='urn-x:wfs:StoredQueryId:badc.nerc.ac.uk:extractGridSeriesFromGridSeries'
125        qet=QueryExpressionText('csml:GridSeriesFeature')
126        pex1=ParameterExpression('featureid', 'xsd:anyURI')
127        pex2=ParameterExpression('mintime', 'gml:TimePositionUnion')
128        pex3=ParameterExpression('maxtime', 'gml:TimePositionUnion')
129        pex4=ParameterExpression('bbox', 'xsd:string')
130        paramlist=[pex1,pex2,pex3, pex4]
131        query=StoredQuery(qid, title='ExtractGridSeriesFromGridSeries', abstract = 'Extract a csml:GridSeries from a csml:PointSeriesFeature', queryExpressionText = qet, parameter=paramlist)
132        self.queries[qid]=(query, CSMLQueries.query_extractGridSeriesFromGridSeries)
133
134
135
136class CSMLFeatureSet(IFeatureSet):
137    """ A set of features available via a WFS. Supports querying methods as used by OGG filters """
138    def __init__(self):
139        self.featureinstances={}
140
141    def getFeatureList(self):
142        results=[]
143        for featureid,feature in self.featureinstances.iteritems():
144            results.append(feature)
145        return results
146
147    def getFeatureByGMLid(self, gmlid):
148        return self.featureinstances[gmlid]
149   
150    def _checkforOverlap(self, filterbbox, featurebbox):
151        """ Uses Shapely Polygons to calculate bounding box intersections """
152        log.debug('comparing against %s'%str(filterbbox))       
153        filterpolygon=Polygon(((filterbbox[0],filterbbox[1]),(filterbbox[0],filterbbox[3]),(filterbbox[2],filterbbox[3]),(filterbbox[2],filterbbox[1])))
154        featurepolygon=Polygon(((featurebbox[0],featurebbox[1]),(featurebbox[0],featurebbox[3]),(featurebbox[2],featurebbox[3]),(featurebbox[2],featurebbox[1])))       
155        log.debug(dir(filterpolygon))
156        log.debug('intersect result%s'%featurepolygon.intersects(filterpolygon))
157        return filterpolygon.intersects(featurepolygon)
158   
159    def _checkforPointinBox(self, filterbbox, location):
160        """ Uses Shapely Polygons to calculate bounding box intersections """
161        log.debug('comparing against %s'%str(filterbbox))       
162        filterpolygon=Polygon(((filterbbox[0],filterbbox[1]),(filterbbox[0],filterbbox[3]),(filterbbox[2],filterbbox[3]),(filterbbox[2],filterbbox[1])))
163        featurepoint=Point(float(location[0]), float(location[1]))
164        log.debug(featurepoint.within(filterpolygon))
165        log.debug('intersect result%s'%featurepoint.intersects(filterpolygon))
166        return featurepoint.intersects(filterpolygon)
167   
168    def getFeaturesByBBox(self,bboxtuple, srsname):         
169        log.debug('GET FEATURES BY BBOX')
170        result=[]
171        log.debug('Request BBOX: %s %s'%(bboxtuple,srsname))
172        for featureid,feature in self.featureinstances.iteritems():
173            if hasattr(feature._feature, 'boundedBy'):
174                log.debug('Checking bbox for feature %s: '%feature._feature.id)
175                lowercorner=feature._feature.boundedBy.lowerCorner.CONTENT.split()
176                uppercorner=feature._feature.boundedBy.upperCorner.CONTENT.split()
177                featurebbox=(float(lowercorner[0]), float(lowercorner[1]), float(uppercorner[0]), float(uppercorner[1]))               
178                if self._checkforOverlap(bboxtuple, featurebbox):
179                    result.append(feature) 
180            elif hasattr(feature._feature, 'location'):
181                log.debug('Checking location for feature %s: '%feature._feature.id)
182                log.debug(feature._feature.location)
183                location=feature._feature.location.CONTENT.split()
184                if self._checkforPointinBox(bboxtuple, location):
185                    result.append(feature)                       
186        return result
187
188    def getFeaturesByPropertyBetween(self, propertyname, minvalue, maxvalue):
189        log.debug('GET FEATURES BY PropertyBetween')
190        log.debug('propertyname, min, max: %s, %s, %s'%(propertyname, minvalue, maxvalue))
191        result = []
192        #Identifies times overlapping between filter and feature times
193        if propertyname=='csml:value/csml:PointSeriesCoverage/csml:pointSeriesDomain/csml:TimeSeries/csml:timePositionList':
194            for featureid, feature in self.featureinstances.iteritems():
195                featuretimes=feature._feature.value.pointSeriesDomain.timePositionList.CONTENT.split()
196                featureMinTime=featuretimes[0]
197                featureMaxTime=featuretimes[len(featuretimes)-1]
198                if csml.csmllibs.csmltime.compareTimes(featureMinTime, minvalue ,featureMaxTime) == 1:
199                    result.append(feature)
200                elif csml.csmllibs.csmltime.compareTimes(featureMinTime, maxvalue ,featureMaxTime) == 1:
201                    result.append(feature)   
202        return result
203       
204    def getFeaturesByPropertyEqualTo(self, propertyname, value):
205        log.debug('GET FEATURES BY PropertyEqualTo, value=%s'%value)
206        result=[]
207        value=value #value may be unicode
208        if propertyname == 'csml:parameter':
209            log.debug('filtering on csml:parameter')         
210            for featureid,feature in self.featureinstances.iteritems():
211                if feature._feature.parameter.getStandardName() == value:
212                    result.append(feature)     
213                elif feature._feature.parameter.getNonStandardName() == value:                   
214                    result.append(feature) 
215            return result
216   
217class CSMLFeatureInstance(IFeatureInstance):
218    def __init__(self, title, abstract, feature):
219        """ representing a CSML Feature Instance
220        @ivar title: The title of the feature instance
221        @ivar abstract: The abstract of the feature instance
222        @ivar feature: the csml feature instance object
223         """
224        self.title=title
225        self.abstract=abstract
226        self._feature=feature
227        self.featuretype=self._feature.__class__.__name__
228        try:
229            bb= self._feature.getCSMLBoundingBox().getBox()
230            #convert 0 - 360 to -180, 180 as per common WMS convention
231            if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
232                bb[0], bb[2]=-180, 180
233            self.wgs84BBox = bb
234        except AttributeError:
235            log.debug('there is a problem getting the bounding box for feature id %s'%self._feature.id)
236            self.wgs84BBox=()
237    def toGML(self):
238        """ Create a GML (CSML) representation of the feature """
239        qualifiedFeatureType='{http://ndg.nerc.ac.uk/csml}' + self.featuretype
240        emptyelem=etree.Element(qualifiedFeatureType)
241        csmlelem=self._feature.toXML(emptyelem)
242        return etree.tostring(csmlelem)
243       
244       
245     
246class CSMLwfsLayer(IwfsLayer):
247    """ representing a WFS FeatureType (termed layer here). Implements IwfsLayer
248    @ivar featuretype: The namespaced name of the feature type, e.g. csml:PointSeriesFeature
249   
250    """
251    def __init__(self, featuretype, wgs84bb):
252        self.type=featuretype
253        #Have to hard code some definitions for CSML feature types to
254        #use in the capabilities document as they don't exist anywhere else.
255        #Hardcoding is okay as this is a CSML specific interface, so will only ever deal
256        #with CSML feature types.
257        #TODO: However, might be better to move to some sort of schema.cfg file?
258        self.wgs84BBox=wgs84bb     
259        self.outputformats=['text/xml: subtype=csml/2.0']   
260        if self.type=='csml:GridSeriesFeature':
261            self.title='GridSeriesFeature as defined in Climate Science Modelling Language.'
262            self.abstract='The CSML GridSeriesFeature is used to represent 4D gridded data such as atmospheric model output.'
263            self.keywords=['Grid', 'Climate', 'CSML']
264        elif self.type=='csml:PointSeriesFeature':
265            self.title='PointSeriesFeature as defined in Climate Science Modelling Language.'
266            self.abstract='The CSML PointSeriesFeature represents a time series of measurements at a single point in space.'
267            self.keywords=['Point', 'Timeseries', 'Climate', 'CSML']
268        #TODO: definitions for all feature types.
269       
270       
271
272
Note: See TracBrowser for help on using the repository browser.