source: cows/trunk/cows/service/imps/data_reader_geoplot_backend/data_reader_geoplot_wms_layer.py @ 6123

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

Added the interval geoplot renderer, also improved the slab option parser and moved the responsibility for generating the colour bars to the slabs.

Line 
1'''
2Created on 9 Jun 2009
3
4@author: pnorton
5'''
6
7import logging
8
9import time
10import os
11import numpy
12
13from cows.service.imps.csmlbackend.config import config
14from cows.service.wms_iface import IwmsLayer
15
16
17log = logging.getLogger(__name__)
18
19import geoplot.colour_bar
20from geoplot.grid_builder_lat_lon import GridBuilderLatLon
21
22from cows.model.wms import Style, LegendURL, FormattedURL, MetadataURL
23from cows.xml.iso19115_subset import OnlineResource
24
25from routes import url_for
26
27from cows.service.imps.data_reader_geoplot_backend.geoplot_slabs.geoplot_slab_contour import GeoplotSlabContour
28from cows.service.imps.data_reader_geoplot_backend.geoplot_slabs.geoplot_slab_grid import GeoplotSlabGrid
29from cows.service.imps.data_reader_geoplot_backend.geoplot_slabs.geoplot_slab_interval import GeoplotSlabInterval
30from cows.service.imps.data_reader_geoplot_backend.slab_options_parser import SlabOptionsParser
31
32
33class DRGeoplotWmsLayer(IwmsLayer):
34
35    slab_classes = [GeoplotSlabGrid, GeoplotSlabContour, GeoplotSlabInterval]
36    default_slab_class = GeoplotSlabGrid
37   
38    EnableDisplayOptions = False
39    EnableXMLAxisConfig = False
40   
41    def __init__(self, name, title, abstract, dimensions, units, crss, 
42                 boundingBox, dataReader):
43        self.featureInfoFormats=None #NotImplemented
44        self.title=title
45        self.abstract=abstract
46        self.dimensions=dimensions
47        self.units=units
48        self.crss=crss
49        self.legendSize=(630,120)
50       
51        # dummy values, will need to be set when the data is opened
52        self._minval = None
53        self._maxval = None 
54       
55        self.name = name
56        self.dataReader = dataReader
57       
58        self.styles = self._buildStyles()
59        self.metadataURLs = self._buildMetadataURL()
60       
61        bb = boundingBox
62           
63        #convert 0 - 360 to -180, 180 as per common WMS convention
64        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
65            bb[0], bb[2]=-180, 180
66           
67        self.wgs84BBox = bb
68        self.featureInfoFormats = ['text/html']
69       
70        try:
71            self.wgs84BBox = self.getBBox('EPSG:4326')
72        except:
73            raise ValueError("Layer must provide a bounding box in EPSG:4326 "
74                             "coordinates for compatibility with WMS-1.3.0")
75           
76        self.featureinfofilecache={} #used for caching netcdf file in getFeatureInfo
77   
78    def getBBox(self, crs):
79        """
80        @return: A 4-typle of the bounding box in the given coordinate
81            reference system.
82        """
83        #bb= self._feature.getCSMLBoundingBox().getBox()
84        #convert 0 - 360 to -180, 180 as per common WMS convention
85        #if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
86        #    bb[0], bb[2]=-180, 180
87        #self.wgs84BBox = bb
88        return self.wgs84BBox
89        #raise NotImplementedError
90       
91    def getSlab(self, crs, style, dimValues, transparent, bgcolor, 
92                    additionalParams={}):
93        """
94        Creates a slab of the layer in a particular CRS and set of
95        dimensions.
96
97        @param crs: The coordinate reference system.
98        @param dimValues: A mapping of dimension names to dimension values
99            as specified in the IDimension.extent
100        @param renderOpts: A generic mapping object for passing rendering
101            options
102        @return: An object implementing ILayerSlab
103        #create netcdf for whole lat/lon for given dimValues, use to init slab
104        """
105        log.debug("additionalParams = %s" % (additionalParams,))
106        log.debug("bgcolor = %s" % (bgcolor,))
107        log.debug("transparent = %s" % (transparent,))
108        log.debug("dimValues = %s" % (dimValues,))
109       
110        #make the colour compatable with matplotlib
111        if bgcolor.find('0x') == 0:
112            bgcolor = '#' + bgcolor[2:]
113       
114        st = time.time()
115        netcdfVar = self.dataReader.getNetcdfVar(self.name, dimValues)
116        log.debug("got netcdf in %ss" % (time.time() - st,))
117       
118        slabClass = self._getSlabClass(style)
119       
120        bbox=self.getBBox(crs)
121       
122        slab = slabClass(netcdfVar, self.title, crs, dimValues, transparent, bgcolor, bbox, additionalParams)
123               
124        return slab
125 
126    def getCacheKey(self, crs, style, dimValues, transparent, bgcolor, 
127                    additionalParams={}): 
128        """
129        Create a unique key for use in caching a slab.
130
131        The intention here is that most of the work should be done when
132        instantiating an ILayerSlab object.  These can be cached by the
133        server for future use.  The server will first call getCacheKey()
134        for the slab creation arguments and if the key is in it's cache
135        it will use a pre-generated ILayerSlab object.
136
137        """
138
139        dimList = list(dimValues.items())
140        dimList.sort()
141
142        #set the default style if none provided
143        s = self._getActualStyle(style)
144           
145        return '%s:%s:%s:%s:%s:%s:%s' % (self.name, crs, s, dimList,
146                                      transparent, bgcolor, additionalParams)
147
148    def _getActualStyle(self, style=None):
149        actualStyle = None
150       
151        if style == 'default' or style == '' or style is None:
152            actualStyle = DRGeoplotWmsLayer.default_slab_class.style
153        else:
154            actualStyle = style
155       
156        if actualStyle not in [x.style for x in DRGeoplotWmsLayer.slab_classes]:
157            Exception("No slab class found for style = %s"  % (style,))
158             
159        return actualStyle
160   
161    def _getSlabClass(self, style):
162        slabClass = None
163       
164        s = self._getActualStyle(style)
165       
166        for klass in DRGeoplotWmsLayer.slab_classes:
167            if klass.style == s:
168                slabClass = klass
169                break
170       
171        if slabClass == None:
172            Exception("No slab class found for style = %s"  % (style,))
173       
174        return slabClass
175
176    def getFeatureInfo(self, format, crs, point, dimValues):
177        """
178        Return a response string descibing the feature at a given
179        point in a given CRS.
180
181        Currently only "html" is supported as output format
182
183        @param format: One of self.featureInfoFormats.  Defines which
184            format the response will be in.
185        @param crs: One of self.crss
186        @param point: a tuple (x, y) in the supplied crs of the point
187            being selected.
188        @param dimValues: A mapping of dimension names to dimension values.
189        @return: A string containing the response.
190
191        """
192       
193#        #cached netcdf is indexed by a tuple of feature id and dimvalues - i.e. typically a particular time and Z value for that feature.
194#        #look in dictionary for cached copy, and if so use that as the file object.
195#        dictindex=str((self._feature.id, dimValues))
196#       
197#        if dictindex in self.featureinfofilecache:
198#            log.debug('calling cache')
199#            f=self.featureinfofilecache[dictindex]
200#        else: #else, use the csml api to subset the feature afresh
201#            log.debug('not calling cache')
202#            randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
203#            result= self._feature.subsetToGridSeries(config['tmpdir'], ncname=randomname, **dimValues)
204#            #for now have to read netcdf back from disk (limitation of CSML api)
205#            f=cdms.open(result[1])
206#            #append to cache:
207#            self.featureinfofilecache[dictindex]=f
208#            #and then delete the temporary file
209#            os.system('rm %s'%result[1])
210#       
211#        netcdf = f(self.title)  #netcdf here is a cdms transient variable
212       
213        netcdf = self.dataReader.getNetcdfVar(self.name, dimValues)
214       
215        #Now grab the netCDF object for the point specified.
216        #The reason for the 'cob' option is so that if the grid the data
217        #is defined on does not have a grid point at the point specified,
218        #we should  still get the nearest location
219       
220        t_point = netcdf(latitude=(point[1], point[1], 'cob'), longitude=(point[0], point[0], 'cob'))
221        #now get the value recorded at this location
222        value = t_point.getValue().tolist()
223        log.debug(value)
224        log.debug(t_point.fill_value())
225        #and the fill_value too
226        fill_value = t_point.fill_value()
227        #value is actually embedded in a multi dimensional list,
228        #so we need to extract the actual value from the list
229        while type(value) is list:
230                value = value[0]
231
232        #now check if the value is actually the fill_value rather than
233        #a value recorded at the point specified
234        log.debug('%s %s' % (value, fill_value))
235        if (2*fill_value) == value:
236                value = "No value found at position: "+str(point[1])+", "+str(point[0])
237        else:
238                value = "Value found at position: "+str(point[1])+", "+str(point[0])+" is: "+str(value)
239               
240        # finally return the value
241        return value
242
243    def getLegendImage(self, dimValues, width=None, height=None, 
244                       orientation='horizontal', 
245                       renderOpts={}, 
246                       style=None
247                       ):
248        """
249        Create an image of the colourbar for this layer.
250        @param orientation: Either 'vertical' or 'horizontal'
251        @return: A PIL image with labels
252
253        """
254        if width == None:
255            width = self.legendSize[0]
256           
257        if height == None:
258            height = self.legendSize[1]
259               
260        variable = self.dataReader.getNetcdfVar(self.name, dimValues)
261       
262        klass = self._getSlabClass(style)
263       
264        return klass.makeColourBar(width , height, orientation, self.units, renderOpts, variable)
265   
266       
267        parser = SlabOptionsParser(klass.renderingOptions, renderOpts)
268       
269        log.debug("klass.style = %s" % (klass.style,))
270       
271        minval = parser.getOption('cmap_min')
272        if minval == None:
273            minval = variable.min()
274           
275        maxval = parser.getOption('cmap_max')
276        if maxval == None:
277            maxval = variable.max()
278           
279            # can't have a colourbar with an infinite maximum, take the highest
280            # non-inf value.
281            if maxval == numpy.inf:
282                maxval = numpy.ma.masked_equal(variable, numpy.inf).max()
283       
284        log.debug("parser.getOption('intervals') = %s" % (parser.getOption('intervals'),))
285        log.debug("parser.getOption('intervalNames') = %s" % (parser.getOption('intervalNames'),))
286       
287        im = geoplot.colour_bar.getColourBarImage(width, height, 
288                                             label='Units of measure: %s' % str(self.units),
289                                             cmap=parser.getOption('cmap'), 
290                                             colourBarMin=minval,
291                                             colourBarMax=maxval,
292                                             colourBarScale=parser.getOption('cmap_scale'),
293                                             numIntervals=parser.getOption('num_intervals'), 
294                                             orientation=orientation,
295                                             intervals=parser.getOption('intervals'),
296                                             intervalNames=parser.getOption('intervalNames'),
297                                             colourBarStyle=parser.getOption('cbar_style'),
298                                             )
299       
300        return im
301   
302    def _buildStyles(self):
303        onlineRes = OnlineResource(self._getIndexActionURL() + "?request=GetLegend&layers=%s" % self.name)
304       
305        legendURL = LegendURL(630, 80, format='img/png', onlineResource=onlineRes )
306       
307        styles = []
308        for klass in DRGeoplotWmsLayer.slab_classes:
309           
310            styleName = klass.style
311           
312            title = getattr(klass, 'title', None)
313           
314            if title is None:
315                title = styleName
316           
317            s = Style(styleName, title, legendURLs=[legendURL] )
318           
319            styles.append(s)
320       
321        return styles
322   
323    def getAxisConfigFile(self):
324        xmlFile = None
325       
326        if hasattr(self.dataReader, 'getConfigAxisXMLFile'):
327           
328            xmlFile =  self.dataReader.getConfigAxisXMLFile()
329       
330        return xmlFile
331   
332    def _buildMetadataURL(self):
333       
334        metadataURLs = []
335       
336        if DRGeoplotWmsLayer.EnableDisplayOptions == True:
337            onlineRes = OnlineResource(self._getIndexActionURL() +\
338                       "?request=GetDisplayOptions&layers=%s" % self.name)
339           
340            metadataURLs.append( MetadataURL(metadataType='display_options', 
341                                          format='application/json',
342                                          onlineResource=onlineRes) )
343           
344        if DRGeoplotWmsLayer.EnableXMLAxisConfig:
345           
346            xmlFile =  self.getAxisConfigFile()
347           
348            if xmlFile != None:
349               
350                onlineRes = OnlineResource(self._getIndexActionURL() +\
351                                "?request=GetAxisConfig&layers=%s" % self.name)
352           
353                metadataURLs.append( MetadataURL(metadataType='axis_config', 
354                                                 format='text/xml',
355                                                 onlineResource=onlineRes) )     
356       
357        return metadataURLs
358   
359    def _getIndexActionURL(self):
360        """
361        Uses the pylons config to build a url for the index action of this contoller.
362        """
363               
364        indexURL = url_for(qualified=True, action='index')
365        return indexURL
Note: See TracBrowser for help on using the repository browser.