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

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

Added the EnableDisplayOptions? to the CsmlGeoplotWmsLayer? class that controls if the display options metadata URL is added to the response, by default it is not.

Also improved the handling of requests outside the boundaries of the data in the csml geoplot slab.

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