source: qesdi/wms_ddc_vis/trunk/lib/wms_ddc_vis/model/ddc_wms_layer.py @ 5403

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/qesdi/wms_ddc_vis/trunk/lib/wms_ddc_vis/model/ddc_wms_layer.py@5403
Revision 5403, 9.6 KB checked in by pnorton, 12 years ago (diff)

Moved QESDI tree from DCIP repository
 http://proj.badc.rl.ac.uk/svn/dcip/qesdi@3900.

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.csmlbackend.wms_csmllayer import CSMLwmsLayerSlab
19from wms_ddc_vis.model.ddc_wms_layer_slab import DDCWmsLayerSlab
20
21class DDCWmsLayer(IwmsLayer):
22   
23   
24    def __init__(self, title, abstract, dimensions, units, crss, feature):
25        self.featureInfoFormats=None #NotImplemented
26        self.title=title
27        self.abstract=abstract
28        self.dimensions=dimensions
29        self.units=units
30        self.crss=crss
31        self._feature=feature
32        self.legendSize=(630,80)
33        self._minval=0
34        self._maxval=10.0 #dummy values
35        try: 
36            bb = self._feature.getCSMLBoundingBox().getBox()
37        except:
38            #default to global
39            bb=[-180,-90,180,90]
40        #convert 0 - 360 to -180, 180 as per common WMS convention
41        if abs(bb[2]-bb[0]) >= 359 and abs(bb[2]-bb[0]) < 361:
42            bb[0], bb[2]=-180, 180
43        self.wgs84BBox = bb
44        self.featureInfoFormats = ['text/html']
45        try:
46            self.wgs84BBox = self.getBBox('EPSG:4326')
47        except:
48            raise ValueError("Layer must provide a bounding box in EPSG:4326 "
49                             "coordinates for compatibility with WMS-1.3.0")
50        self.featureinfofilecache={} #used for caching netcdf file in getFeatureInfo
51       
52    def getBBox(self, crs):
53        """
54        @return: A 4-typle of the bounding box in the given coordinate
55            reference system.
56        """
57        #bb= self._feature.getCSMLBoundingBox().getBox()
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        #self.wgs84BBox = bb
62        return self.wgs84BBox
63        #raise NotImplementedError
64       
65    def getSlab(self, crs, dimValues=None, renderOpts={}):
66        """
67        Creates a slab of the layer in a particular CRS and set of
68        dimensions.
69
70        @param crs: The coordinate reference system.
71        @param dimValues: A mapping of dimension names to dimension values
72            as specified in the IDimension.extent
73        @param renderOpts: A generic mapping object for passing rendering
74            options
75        @return: An object implementing ILayerSlab
76        #create netcdf for whole lat/lon for given dimValues, use to init slab
77        """
78
79        log.debug('getSlab(%s, %s) renderOpts = %s' % (crs, dimValues, renderOpts))
80       
81        #unicode conversion
82        for dimval in dimValues:
83            if dimval != 'time':
84                dimValues[dimval]=float(dimValues[dimval])
85            else:
86                #remove any trailing Zs from time string
87                if dimValues[dimval] [-1:] in ['Z', 'z']:
88                    dimValues[dimval]=dimValues[dimval][:-1]
89       
90       
91             
92        if type(self._feature) == csml.parser.GridSeriesFeature:
93           
94            randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
95            result= self._feature.subsetToGridSeries(config['tmpdir'], ncname=randomname, **dimValues)
96           
97            #for now have to read netcdf back from
98            #disk (limitiation of CSML api)
99            netcdf=cdms.open(result[1])
100           
101            #and then delete the temporary file
102            os.system('rm %s'%result[1])
103           
104            bbox=self.getBBox(crs)
105           
106            slab = DDCWmsLayerSlab(netcdf, self, crs, dimValues, renderOpts, bbox)
107           
108            self._minval=slab.minval #needed for legend rendering.
109            self._maxval=slab.maxval
110           
111            return slab
112        else:
113            raise NotImplementedError
114       
115    def getCacheKey(self, crs, dimValues=None, renderOpts={}):
116        """
117        Create a unique key for use in caching a slab.
118
119        The intention here is that most of the work should be done when
120        instantiating an ILayerSlab object.  These can be cached by the
121        server for future use.  The server will first call getCacheKey()
122        for the slab creation arguments and if the key is in it's cache
123        it will use a pre-generated ILayerSlab object.
124
125        """
126        dimList = list(dimValues.items())
127        dimList.sort()
128        return '%s:%s:%s' % (self._feature.id, crs, dimList)
129
130    def getFeatureInfo(self, format, crs, point, dimValues):
131        """
132        Return a response string descibing the feature at a given
133        point in a given CRS.
134
135        Currently only "html" is supported as output format
136
137        @param format: One of self.featureInfoFormats.  Defines which
138            format the response will be in.
139        @param crs: One of self.crss
140        @param point: a tuple (x, y) in the supplied crs of the point
141            being selected.
142        @param dimValues: A mapping of dimension names to dimension values.
143        @return: A string containing the response.
144
145        """
146       
147        #cached netcdf is indexed by a tuple of feature id and dimvalues - i.e. typically a particular time and Z value for that feature.
148        #look in dictionary for cached copy, and if so use that as the file object.
149        dictindex=str((self._feature.id, dimValues))
150        if dictindex in self.featureinfofilecache:
151            log.debug('calling cache')
152            f=self.featureinfofilecache[dictindex]
153        else: #else, use the csml api to subset the feature afresh
154            log.debug('not calling cache')
155            randomname= csml.csmllibs.csmlextra.getRandomID() + '.nc'
156            result= self._feature.subsetToGridSeries(config['tmpdir'], ncname=randomname, **dimValues)
157            #for now have to read netcdf back from disk (limitation of CSML api)
158            f=cdms.open(result[1])
159            #append to cache:
160            self.featureinfofilecache[dictindex]=f
161            #and then delete the temporary file
162            os.system('rm %s'%result[1])
163       
164        netcdf = f(self.title)  #netcdf here is a cdms transient variable
165       
166       
167        #Now grab the netCDF object for the point specified.
168        #The reason for the 'cob' option is so that if the grid the data
169        #is defined on does not have a grid point at the point specified,
170        #we should  still get the nearest location
171       
172        t_point = netcdf(latitude=(point[1], point[1], 'cob'), longitude=(point[0], point[0], 'cob'))
173        #now get the value recorded at this location
174        value = t_point.getValue().tolist()
175        log.debug(value)
176        log.debug(t_point.fill_value())
177        #and the fill_value too
178        fill_value = t_point.fill_value()
179        #value is actually embedded in a multi dimensional list,
180        #so we need to extract the actual value from the list
181        while type(value) is list:
182                value = value[0]
183
184        #now check if the value is actually the fill_value rather than
185        #a value recorded at the point specified
186        log.debug('%s %s' % (value, fill_value))
187        if (2*fill_value) == value:
188                value = "No value found at position: "+str(point[1])+", "+str(point[0])
189        else:
190                value = "Value found at position: "+str(point[1])+", "+str(point[0])+" is: "+str(value)
191        # finally return the value
192        return value
193
194    def getLegendImage(self, orientation='horizontal', renderOpts={}):
195        """
196        Create an image of the colourbar for this layer.
197        @param orientation: Either 'vertical' or 'horizontal'
198        @return: A PIL image with labels
199
200        """
201        width = self.legendSize[0]
202        height = self.legendSize[1]
203       
204        # if width and height specified in the GetLegend request parameters
205        # then use them instead of the default values
206        if 'width' in renderOpts:
207                width = renderOpts['width']
208        if 'height' in renderOpts:
209                height = renderOpts['height']
210               
211        cmap = cm.get_cmap(config['colourmap'])
212        renderer=RGBARenderer(self._minval, self._maxval)
213       
214        if orientation =='vertical':
215            legendImage= renderer.renderColourbar(630, 30, cmap, isVertical=True)
216        else:
217            legendImage= renderer.renderColourbar(630, 30, cmap, isVertical=False)
218               
219        imageWithLabels=Image.new('RGBA', (630, 80), "white")
220        imageWithLabels.paste(legendImage, (0,0))
221       
222        #add minvalue label
223        minvalueImg=self._simpletxt2image(str(self._minval), (49,25))
224        imageWithLabels.paste(minvalueImg,(0,40))
225       
226        #add midvalue  label
227        midvalue=self._minval+(self._maxval-self._minval)/2
228       
229        #add maxvalue label
230        midvalueImg=self._simpletxt2image(str(midvalue),(49,25))
231        imageWithLabels.paste(midvalueImg,(280,40))
232       
233        #add maxvalue label
234        maxvalueImg=self._simpletxt2image(str(self._maxval), (49,25))
235        imageWithLabels.paste(maxvalueImg,(575,40))
236       
237        #add units:
238        unitsImg=self._simpletxt2image('Units of measure: %s'%str(self.units), (200,25))
239        imageWithLabels.paste(unitsImg,(240,60))
240             
241#        return imageWithLabels                         
242        return imageWithLabels.resize((width, height)) 
243   
244    def _simpletxt2image(self, text, size):
245        image = Image.new('RGBA',size,"white")
246        fontfullpath = config['legendfont']
247        ifo = ImageFont.truetype(fontfullpath,16)
248        draw = ImageDraw.Draw(image)
249        draw.text((0, 0), text, font=ifo,fill=(100, 123, 165))
250        return image
Note: See TracBrowser for help on using the repository browser.