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

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@5697
Revision 5697, 12.1 KB checked in by pnorton, 11 years ago (diff)

Extended the options available on the grid and contour slabs in the data reader backend. Also fixed a bug with the slab_option_parser and improved the legend rendering code.

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