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

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

getFeaturesByBBox implemented using shapely polygons for intersection.

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, BboxAggregator
7from cows.service.wfs_iface import *
8import csml
9from xml.etree import ElementTree as etree
10from shapely.geometry import Polygon
11
12import logging
13log = logging.getLogger(__name__)
14
15class CSMLwfsLayerMapper(CSMLLayerMapper):
16    """
17    Map keyword arguments to a collection of layers.
18    Supports the retrieval of sets of layers according to arbitrary
19    keyword/value pairs.
20    Implements  ILayerMapper
21   
22    WFS differs from WMS/WCS in that the 'layers' are feature types, not instances.
23    So the CSMLwfsLayerMapper map method returns both a map of feature types and a map
24    of feature instances as both are needed for the GetFeature method to work.
25   
26    """
27    def __init__(self):
28        super(CSMLwfsLayerMapper, self).__init__()
29        self.featureinstancecache={}
30   
31
32    def map(self, **kwargs):
33        """
34        Given csml.parser.Dataset object list the names of
35        all layers available.
36       
37        @return: A mapping of layer names to ILayer implementations.
38        @raise ValueError: If no layers are available for these keywords.
39        """
40        fileoruri=kwargs['fileoruri']
41        if fileoruri in self.layermapcache.keys():
42            #we've accessed this layer map before, get it from the cache dictionary
43            return self.layermapcache[fileoruri], self.featureinstancecache[fileoruri]
44         
45        ds = self.connector.get_csml_doc(fileoruri)
46       
47        #The WFS differs from WMS & WCS in that the contents layer is a list of
48        #feature *types* not *instances*. However a record of instances is also
49        #needed to fulfil GetFeature requests:
50        featureset=CSMLFeatureSet() #holds feature instances               
51        layermap={} #feature types       
52        bboxAggregators={}#for aggregations of bounding boxes.
53
54        for feature in csml.csmllibs.csmlextra.listify(ds.featureCollection.featureMembers):
55            title, abstract=self._getInfo(feature)
56            featureset.featureinstances[feature.id]=CSMLFeatureInstance(title, abstract, feature)             
57        for id, instance in featureset.featureinstances.iteritems():
58            ftype='csml:' + instance.featuretype
59            if ftype not in layermap.keys():
60                layermap[ftype]=CSMLwfsLayer(ftype, instance.wgs84BBox)
61                #instantiate an aggregator to compare future bounding boxes with.
62                bboxAggregators[ftype]= BboxAggregator(instance.wgs84BBox)               
63            else:
64                #the featuretype has already been stored in the dictionary.
65                #but, the bounding box may need changing to accommodate this new feature instance.
66#                log.debug('Checking bbox for feature id: %s and title: %s'%(instance._feature.id, instance.title))
67                aggregator=bboxAggregators[ftype]
68                aggregator.aggregate(instance.wgs84BBox)
69                layermap[ftype]=CSMLwfsLayer(ftype, aggregator.bbox)
70           
71
72        if len(layermap) > 0:
73            #cache results
74            self.layermapcache[fileoruri]=layermap
75            self.featureinstancecache[fileoruri]=featureset
76            return layermap, featureset
77        else:
78            raise ValueError
79
80class CSMLFeatureSet(IFeatureSet):
81    """ A set of features available via a WFS. Supports querying methods as used by OGG filters """
82    def __init__(self):
83        self.featureinstances={}
84   
85    def getFeatureByGMLid(self, gmlid):
86        return self.featureinstances[gmlid]
87   
88    def _checkforOverlap(self, filterbbox, featurebbox):
89        """ Uses Shapely Polygons to calculate bounding box intersections """       
90        filterpolygon=Polygon(((filterbbox[0],filterbbox[1]),(filterbbox[0],filterbbox[3]),(filterbbox[2],filterbbox[3]),(filterbbox[2],filterbbox[1])))
91        featurepolygon=Polygon(((featurebbox[0],featurebbox[1]),(featurebbox[0],featurebbox[3]),(featurebbox[2],featurebbox[3]),(featurebbox[2],featurebbox[1])))       
92        return filterpolygon.intersects(featurepolygon)
93   
94    def getFeaturesByBBox(self,bboxtuple, srsname):         
95        log.debug('GET FEATURES BY BBOX')
96        result=[]
97        for featureid,feature in self.featureinstances.iteritems():
98            try:
99                lowercorner=feature._feature.boundedBy.lowerCorner.CONTENT.split()
100                uppercorner=feature._feature.boundedBy.upperCorner.CONTENT.split()
101                featurebbox=(float(lowercorner[0]), float(lowercorner[1]), float(uppercorner[0]), float(uppercorner[1]))
102                if self._checkforOverlap(bboxtuple, featurebbox):
103                    result.append(feature)
104            except AttributeError: #sometimes the features don't have the bounding box metadata
105                pass
106        return result
107
108   
109    def getFeaturesByTemporalRange(self, range):
110        #TODO: getFeaturesByTemporalRange
111        return []
112   
113   
114class CSMLFeatureInstance(IFeatureInstance):
115    def __init__(self, title, abstract, feature):
116        """ representing a CSML Feature Instance
117        @ivar title: The title of the feature instance
118        @ivar abstract: The abstract of the feature instance
119        @ivar feature: the csml feature instance object
120         """
121        self.title=title
122        self.abstract=abstract
123        self._feature=feature
124        self.featuretype=self._feature.__class__.__name__
125        bb= self._feature.getCSMLBoundingBox().getBox()
126        #convert 0 - 360 to -180, 180 as per common WMS convention
127        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
128            bb[0], bb[2]=-180, 180
129        self.wgs84BBox = bb
130       
131    def toGML(self):
132        """ Create a GML (CSML) representation of the feature """
133       
134        #TODO un-hardcode this:
135        emptyelem=etree.Element('{http://ndg.nerc.ac.uk/csml}GridSeriesFeature')
136        csmlelem=self._feature.toXML(emptyelem)
137        return etree.tostring(csmlelem)
138       
139       
140     
141class CSMLwfsLayer(IwfsLayer):
142    """ representing a WFS FeatureType (termed layer here). Implements IwfsLayer
143    @ivar featuretype: The namespaced name of the feature type, e.g. csml:PointSeriesFeature
144   
145    """
146    def __init__(self, featuretype, wgs84bb):
147        self.type=featuretype
148        #Have to hard code some definitions for CSML feature types to
149        #use in the capabilities document as they don't exist anywhere else.
150        #Hardcoding is okay as this is a CSML specific interface, so will only ever deal
151        #with CSML feature types.
152        #TODO: However, might be better to move to some sort of schema.cfg file?
153        self.wgs84BBox=wgs84bb     
154        self.outputformats=['text/xml: subtype=csml/2.0']   
155        if self.type=='csml:GridSeriesFeature':
156            self.title='GridSeriesFeature as defined in Climate Science Modelling Language.'
157            self.abstract='The CSML GridSeriesFeature is used to represent 4D gridded data such as atmospheric model output.'
158            self.keywords=['Grid', 'Climate', 'CSML']
159        elif self.type=='csml:PointSeriesFeature':
160            self.title='PointSeriesFeature as defined in Climate Science Modelling Language.'
161            self.abstract='The CSML PointSeriesFeature represents a time series of measurements at a single point in space.'
162            self.keywords=['Point', 'Timeseries', 'Climate', 'CSML']
163        #TODO: definitions for all feature types.
164       
165       
166
Note: See TracBrowser for help on using the repository browser.