source: cows/trunk/cows/service/imps/csml_geoplot_backend/csml_geoplot_wms_layer.py @ 5632

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows/trunk/cows/service/imps/csml_geoplot_backend/csml_geoplot_wms_layer.py@5632
Revision 5632, 11.1 KB checked in by domlowe, 11 years ago (diff)

Adding CSML Geoplot backend to COWS trunk from Qesdi branch

Line 
1'''
2Created on 9 Jun 2009
3
4@author: pnorton
5'''
6
7import logging
8
9import os
10import csml
11import cdms2 as cdms
12
13from cows.service.imps.csmlbackend.config import config
14from cows.service.wms_iface import IwmsLayer
15
16log = logging.getLogger(__name__)
17
18from cows.service.imps.csml_geoplot_backend.csml_geoplot_wms_layer_slab import CSMLGeoplotWmsLayerSlab
19from cows.service.imps.csml_geoplot_backend.csml_geoplot_wms_layer_slab_contour import CSMLGeoplotWmsLayerSlabContour
20from cows.service.imps.csml_geoplot_backend.csml_geoplot_render_options_parser import CSMLGeoplotRenderOptionsParser
21
22import geoplot.colour_bar
23
24from cows.model.wms import Style, LegendURL, FormattedURL, MetadataURL
25from cows.xml.iso19115_subset import OnlineResource
26
27from routes import url_for
28
29class STYLES:
30    CONTOUR = 'contour'
31    GRID = 'grid'
32    DEFAULT = GRID
33
34class CSMLGeoplotWmsLayer(IwmsLayer):
35   
36   
37    def __init__(self, title, abstract, dimensions, units, crss, feature,
38                 name=None):
39        self.featureInfoFormats=None #NotImplemented
40        self.title=title
41        self.abstract=abstract
42        self.dimensions=dimensions
43        self.units=units
44        self.crss=crss
45        self._feature=feature
46        self.legendSize=(630,80)
47        self._minval=0
48        self._maxval=10.0 #dummy values
49        self.name = name
50       
51        self.styles = self._buildStyles()
52        self.metadataURLs = self._buildMetadataURL()
53         
54        try: 
55            bb = self._feature.getCSMLBoundingBox().getBox()
56        except:
57            #default to global
58            bb=[-180,-90,180,90]
59           
60        #convert 0 - 360 to -180, 180 as per common WMS convention
61        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
62            bb[0], bb[2]=-180, 180
63           
64        self.wgs84BBox = bb
65        self.featureInfoFormats = ['text/html']
66       
67        try:
68            self.wgs84BBox = self.getBBox('EPSG:4326')
69        except:
70            raise ValueError("Layer must provide a bounding box in EPSG:4326 "
71                             "coordinates for compatibility with WMS-1.3.0")
72           
73        self.featureinfofilecache={} #used for caching netcdf file in getFeatureInfo
74   
75    def getBBox(self, crs):
76        """
77        @return: A 4-typle of the bounding box in the given coordinate
78            reference system.
79        """
80        #bb= self._feature.getCSMLBoundingBox().getBox()
81        #convert 0 - 360 to -180, 180 as per common WMS convention
82        #if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
83        #    bb[0], bb[2]=-180, 180
84        #self.wgs84BBox = bb
85        return self.wgs84BBox
86        #raise NotImplementedError
87       
88    def getSlab(self, crs, style, dimValues, transparent, bgcolor, 
89                    additionalParams={}):
90        """
91        Creates a slab of the layer in a particular CRS and set of
92        dimensions.
93
94        @param crs: The coordinate reference system.
95        @param dimValues: A mapping of dimension names to dimension values
96            as specified in the IDimension.extent
97        @param renderOpts: A generic mapping object for passing rendering
98            options
99        @return: An object implementing ILayerSlab
100        #create netcdf for whole lat/lon for given dimValues, use to init slab
101        """
102        log.debug("additionalParams = %s" % (additionalParams,))
103        log.debug("bgcolor = %s" % (bgcolor,))
104        log.debug("transparent = %s" % (transparent,))
105        log.debug("dimValues = %s" % (dimValues,))
106       
107        #unicode conversion
108        for dimval in dimValues:
109            if dimval != 'time':
110                dimValues[dimval]=float(dimValues[dimval])
111            else:
112                #remove any trailing Zs from time string
113                if dimValues[dimval] [-1:] in ['Z', 'z']:
114                    dimValues[dimval]=dimValues[dimval][:-1]
115       
116       
117        netcdf = self._getNetcdfVar(dimValues)
118
119        bbox=self.getBBox(crs)
120       
121        slabClass = self._getSlabClass(style)
122       
123        slab = slabClass(netcdf, self.title, crs, dimValues, transparent, bbox, additionalParams)
124       
125        self._minval=slab.minval #needed for legend rendering.
126        self._maxval=slab.maxval
127       
128        return slab       
129 
130    def _getNetcdfVar(self, dimValues):
131        "Opens up the csml and retrieves the variable described by the dimensions"
132       
133        if type(self._feature) == csml.parser.GridSeriesFeature:
134           
135            randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
136           
137            result= self._feature.subsetToGridSeries(config['tmpdir'], 
138                                        ncname=randomname, **dimValues)
139           
140            #for now have to read netcdf back from
141            #disk (limitiation of CSML api)
142            netcdf=cdms.open(result[1])
143           
144            #and then delete the temporary file
145            os.system('rm %s'%result[1])
146           
147        else:
148            raise NotImplementedError
149       
150        return netcdf
151   
152    def _getSlabClass(self, style):
153        "Gets the slab class for a style value"
154       
155        if style == None or style == "":
156            style = STYLES.DEFAULT
157           
158        if style == STYLES.CONTOUR:
159            slabClass = CSMLGeoplotWmsLayerSlabContour
160        elif style == STYLES.GRID:
161            slabClass = CSMLGeoplotWmsLayerSlab
162        else:
163            raise Exception("Unknown style %s" % (style,))
164       
165        return slabClass
166                       
167       
168    def getCacheKey(self, crs, style, dimValues, transparent, bgcolor, 
169                    additionalParams={}): 
170        """
171        Create a unique key for use in caching a slab.
172
173        The intention here is that most of the work should be done when
174        instantiating an ILayerSlab object.  These can be cached by the
175        server for future use.  The server will first call getCacheKey()
176        for the slab creation arguments and if the key is in it's cache
177        it will use a pre-generated ILayerSlab object.
178
179        """
180
181        dimList = list(dimValues.items())
182        dimList.sort()
183
184        #set the default style if none provided
185        if style == None or style == "":
186            s = STYLES.DEFAULT
187        else:
188            s = style
189           
190        return '%s:%s:%s:%s:%s:%s:%s' % (self._feature.id, crs, s, dimList,
191                                      transparent, bgcolor, additionalParams)
192
193    def getFeatureInfo(self, format, crs, point, dimValues):
194        """
195        Return a response string descibing the feature at a given
196        point in a given CRS.
197
198        Currently only "html" is supported as output format
199
200        @param format: One of self.featureInfoFormats.  Defines which
201            format the response will be in.
202        @param crs: One of self.crss
203        @param point: a tuple (x, y) in the supplied crs of the point
204            being selected.
205        @param dimValues: A mapping of dimension names to dimension values.
206        @return: A string containing the response.
207
208        """
209       
210        #cached netcdf is indexed by a tuple of feature id and dimvalues - i.e. typically a particular time and Z value for that feature.
211        #look in dictionary for cached copy, and if so use that as the file object.
212        dictindex=str((self._feature.id, dimValues))
213        if dictindex in self.featureinfofilecache:
214            log.debug('calling cache')
215            f=self.featureinfofilecache[dictindex]
216        else: #else, use the csml api to subset the feature afresh
217            log.debug('not calling cache')
218            randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
219            result= self._feature.subsetToGridSeries(config['tmpdir'], ncname=randomname, **dimValues)
220            #for now have to read netcdf back from disk (limitation of CSML api)
221            f=cdms.open(result[1])
222            #append to cache:
223            self.featureinfofilecache[dictindex]=f
224            #and then delete the temporary file
225            os.system('rm %s'%result[1])
226       
227        netcdf = f(self.title)  #netcdf here is a cdms transient variable
228       
229       
230        #Now grab the netCDF object for the point specified.
231        #The reason for the 'cob' option is so that if the grid the data
232        #is defined on does not have a grid point at the point specified,
233        #we should  still get the nearest location
234       
235        t_point = netcdf(latitude=(point[1], point[1], 'cob'), longitude=(point[0], point[0], 'cob'))
236        #now get the value recorded at this location
237        value = t_point.getValue().tolist()
238        log.debug(value)
239        log.debug(t_point.fill_value())
240        #and the fill_value too
241        fill_value = t_point.fill_value()
242        #value is actually embedded in a multi dimensional list,
243        #so we need to extract the actual value from the list
244        while type(value) is list:
245                value = value[0]
246
247        #now check if the value is actually the fill_value rather than
248        #a value recorded at the point specified
249        log.debug('%s %s' % (value, fill_value))
250        if (2*fill_value) == value:
251                value = "No value found at position: "+str(point[1])+", "+str(point[0])
252        else:
253                value = "Value found at position: "+str(point[1])+", "+str(point[0])+" is: "+str(value)
254        # finally return the value
255        return value
256
257    def getLegendImage(self, width=None, height=None, orientation='horizontal', renderOpts={}):
258        """
259        Create an image of the colourbar for this layer.
260        @param orientation: Either 'vertical' or 'horizontal'
261        @return: A PIL image with labels
262
263        """
264        if width == None:
265            width = self.legendSize[0]
266           
267        if height == None:
268            height = self.legendSize[1]
269       
270        parser = CSMLGeoplotRenderOptionsParser(renderOpts, self._minval, self._maxval)
271       
272        cmapRange = (parser.getOption('cmap_min'), parser.getOption('cmap_max'))
273
274        im = geoplot.colour_bar.getColourBarImage(width, height, 
275                                             label='Units of measure: %s' % str(self.units), 
276                                             cmap=parser.getOption('cmap'), 
277                                             cmapRange=cmapRange, 
278                                             orientation=orientation)
279       
280        return im
281   
282    def _buildStyles(self):
283        onlineRes = OnlineResource(url_for(qualified=True, action='index') + "?request=GetLegend&layers=%s" % self.name)
284       
285        legendURL = LegendURL(630, 80, format='img/png', onlineResource=onlineRes )
286       
287        gridStyle = Style(STYLES.GRID, 'Grid Boxes', legendURLs=[legendURL] )
288        contourStyle = Style(STYLES.CONTOUR, 'Contour Lines', legendURLs=[legendURL] )
289       
290        return [gridStyle, contourStyle]
291   
292    def _buildMetadataURL(self):
293        onlineRes = OnlineResource(url_for(qualified=True, action='index') +\
294                                    "?request=GetDisplayOptions&layers=%s" % self.name)
295       
296        displayMetadata = MetadataURL(metadataType='display_options', format='application/json', onlineResource=onlineRes)
297       
298        return [displayMetadata] 
Note: See TracBrowser for help on using the repository browser.