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

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

Fixes to enable Point and GridSeries? subsetting in WFS.

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