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

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

typename parameter enabled in WFS

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