source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/lib/render.py @ 2989

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/lib/render.py@2989
Revision 2989, 6.1 KB checked in by spascoe, 13 years ago (diff)

Implemented colourbar.

Line 
1# Copyright (C) 2007 STFC & NERC (Science and Technology Facilities Council).
2# This software may be distributed under the terms of the
3# Q Public License, version 1.0 or later.
4# http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
5"""
6Code to render CDMS variables to PIL images
7
8@author: Stephen Pascoe
9
10"""
11
12import ImageColor, Image
13from ows_server.lib.grid_util import *
14from matplotlib import cm, colors
15from matplotlib.figure import Figure
16from matplotlib.backends.backend_agg import FigureCanvasAgg
17from matplotlib.colorbar import ColorbarBase
18
19
20import MA
21
22# Configure logging to log general infomation
23import logging
24logger = logging.getLogger('ows_server.lib.render')
25logger.setLevel(logging.INFO)
26
27class BBox(object):
28    """Class to encapsulate a WMS BBOX and do some basic arrithmatic.
29
30    Openlayers will ask for BBOXs out of the CRS:84 bounds.  Methods in this
31    class help handle this.
32    """
33
34    def __init__(self, bbox):
35        # sanity check
36        (x1,y1,x2,y2) = bbox
37
38        self.bbox = bbox
39
40    def _getCRS84(self):
41        """
42        @return: the BBOX tuple truncated to CRS:84
43        """
44
45        (x1,y1,x2,y2) = self.bbox
46         
47        x1 = max(-180., min(180., x1))
48        x2 = max(-180., min(180., x2))
49        y1 = max(-90., min(90., y1))
50        y2 = max(-90., min(90., y2))
51
52        return (x1,y1,x2,y2)
53    crs84 = property(_getCRS84)
54
55    def getOffset(self):
56        """
57        @return: the offset of the bottom left coordinate of the CRS:84 bounding box from the raw bbox
58        """
59        (x1,y1,x2,y2) = self.bbox
60        (x3,y3,x4,y4) = self.crs84
61
62        return (x3-x1, y3-y1)
63       
64    def getCrs84OffsetInImage(self, width, height):
65        """
66        @param width: width of image in pixels
67        @param height: height of image in pixels
68        @return: the (x,y) offset of the CRS:84 bbox in the full bbox for an image of given dimensions.
69            Note this the offset from the top-left of the image.
70        """
71        (x1,y1,x2,y2) = self.bbox
72        (x3,y3,x4,y4) = self.crs84
73        bwidth = x2 - x1
74        bheight = y2 - y1
75
76        # The upper left corner relative to full bbox
77        ulx, uly = x3-x1, y2-y4
78
79        # Scale to image size
80        return (int(round(ulx / bwidth * width)), int(round(uly / bheight * height)))
81
82    def getCrs84ImageSize(self, width, height):
83        """
84        @param width: width of the image covering the full bbox
85        @param height: height of the image covering the full bbox
86        @return: (width, height) in pixels
87        """
88        (x1,y1,x2,y2) = self.bbox
89        (x3,y3,x4,y4) = self.crs84
90
91        xscale = width / (x2 - x1)
92        yscale = height / (y2 - y1)
93
94        return (int(round((x4-x3) * xscale)), int(round((y4-y3) * yscale)))
95
96    def _getCrs84Width(self):
97        (x1,y1,x2,y2) = self.crs84
98        return x2-x1
99    crs84Width = property(_getCrs84Width)
100
101    def _getCrs84Height(self):
102        (x1,y1,x2,y2) = self.crs84
103        return y2-y1
104    crs84Height = property(_getCrs84Height)
105
106
107def render_variable(var, bbox, width, height, varmin, varmax, cmap=None, ):
108    """
109    Creates RGBA PIL images with a selectable matplotlib colour scale.
110
111    @todo: This should be class really, but it came out of my brain as a function.
112    @todo: Transparent missing_value mask not working.
113
114    """
115
116    if cmap is None:
117        cmap = cm.get_cmap()
118
119
120    # Check var is a single lat/lon slice
121    for axis in var.getAxisList():
122        if not (axis.isLatitude() or axis.isLongitude()):
123            raise ValueError('Variable must be a single lat/lon slice')
124
125    # If var isn't defined on a uniform grid regrid
126    if not is_uniform_grid(var):
127        # debug
128        logger.info('Regridding')
129        var = var_to_uniform_grid(var)
130
131    # Get grid spacing
132    lat0, lat1 = var.getLatitude()[:2]; dlat = lat1 - lat0
133    lon0, lon1 = var.getLongitude()[:2]; dlon = lon1 - lon0
134
135    # debug
136    logger.info('grid spacing: %s, %s' % (dlat, dlon))
137
138    def render(var, width, height):
139        # Normalise variable to varmin, varmax
140        norm = colors.normalize(varmin, varmax)
141        a = norm(var)
142       
143        # Render the normalised variable by converting it into a byte array then to a PIL image
144        img_buf = (cmap(a) * 255).astype('b')
145        (y, x, c) = img_buf.shape
146        img = Image.frombuffer("RGBA", (x, y), img_buf.tostring(), "raw", "RGBA", 0, 1)
147
148        # Ensure lat & lon increase from the bottom left
149        if dlat > 0:
150            img = img.transpose(Image.FLIP_TOP_BOTTOM)
151        if dlon < 0:
152            img = img.transpose(Image.FLIT_LEFT_RIGHT)
153
154        return img.resize((width, height))
155
156
157    # Use BBox object for some of the bbox arithmatic
158    bbox_obj = BBox(bbox)
159
160    # If a non-CRS:84 bbox is requested we must retrieve the valid
161    # sub-bbox and paste it into an image of the correct size.
162    if tuple(bbox) != bbox_obj.crs84:
163        logger.info('Padding image outside CRS:84 BBOX')
164        logger.info('Bbox: %s' % (bbox,))
165        logger.info('CRS84-BBox: %s' % (bbox_obj.crs84,))
166        logger.info('Aspect ratio: %s, %s' % (width, height))
167        img = Image.new('RGBA', (width, height))
168        (ox, oy) = bbox_obj.getCrs84OffsetInImage(width, height)
169        logger.info('Offset in image: %s, %s' % (ox, oy))
170        nwidth, nheight = bbox_obj.getCrs84ImageSize(width, height)
171       
172        logger.info('Inner image size: %s, %s' % (nwidth, nheight))
173        # If the image is too small just send a blank image
174        if bbox_obj.crs84Width > abs(dlon) and bbox_obj.crs84Height > abs(dlat):
175            img1 = render(var, width, nheight)
176            img.paste(img1, (0, oy))
177    else:
178        img = render(var, width, height)
179
180    return img
181
182
183def render_colourbar(width, varmin, varmax, cmap=None):
184    dpi = 75
185
186    figsize = (width / dpi, 0.5)
187    fig = Figure(figsize=figsize, dpi=dpi)
188
189    ax = fig.add_axes((0,0.4, 1, 0.6))
190    cb = ColorbarBase(ax, norm=colors.Normalize(varmin, varmax),
191                      orientation='horizontal')
192
193    canvas = FigureCanvasAgg(fig)
194    canvas.draw()
195
196    imstr = canvas.get_renderer().tostring_rgb()
197    wh = canvas.get_width_height()
198    img = Image.fromstring('RGB', wh, imstr)
199
200    return img
Note: See TracBrowser for help on using the repository browser.