source: qesdi/geoplot/trunk/lib/geoplot/grid_builder_base.py @ 5826

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/qesdi/geoplot/trunk/lib/geoplot/grid_builder_base.py@5826
Revision 5826, 12.4 KB checked in by pnorton, 10 years ago (diff)

Improved geoplot's behaviour when dealing with variables with axis in the order of lon/lat instead of lat/lon.

Line 
1"""
2grid_builder_base.py
3====================
4
5Holds the GridBuilderBase class. A GridBuilder is an object that knows how to
6extract a lat-lon Grid object from a given cdmsVariable. This is an abstract
7baseclass and can't be used directly.
8
9"""
10
11#python modules
12import logging
13
14#third party modules
15
16#internal modules
17from geoplot.grid import Grid
18
19import numpy as N
20import numpy.ma as MA
21
22#set the log
23log = logging.getLogger(__name__)
24
25class GridBuilderBase(object):
26    """
27    Impliments the common GridBuilder functionality.
28
29    This is an abstract class. Any methods that iherit from this class need to
30    impliment the _resizeVar, _buildGridBounds, _buildGridMidpoints and the
31    _buildGridValues methods.
32    """
33
34    def __init__(self, cdmsVar):
35        """
36        constructs the grid builder object
37       
38        @param cdmsVar: the cdms variable that conains the grid data.
39        @type cdmsVar:a cdms.variable object 
40        """
41       
42        self.cdmsVar = cdmsVar
43        self._checkVariable()
44       
45#        for axis in self.cdmsVar.getAxisList():
46#            _logAxis(axis)
47
48    def buildGrid(self, xLimits=None, yLimits=None):
49        """
50        builds a grid object from the data in the cdmsVar ascociated with the
51        grid builder object.
52       
53        If xLimits or yLimits values are given then the resulting grid will
54        contain the portion of the data in the cdms variable that falls within
55        the limits given.
56       
57        @keyword xLimits: (optional) longitude limits of the resulting grid
58        @type xLimits: a tuple of (MinLongitude, MaxLongitude)
59        @keyword yLimits: (optional) latitude limits of the resulting grid
60        @type yLimits: a tuple of (MinLatitude, MaxLatitude)
61        @return: a grid built using the cdms variable data
62        @rtype: geoplot.Grid
63        """
64
65#        log.debug("building grid with cdms variable id = %s" % (self.cdmsVar.id,))
66#        log.debug("self.cdmsVar.getAxisIds() = %s" % (self.cdmsVar.getAxisIds(),))
67#        log.debug("self.cdmsVar.shape = %s" % (self.cdmsVar.shape,))
68
69        xmid, ymid = self._buildGridMidpoints(self.cdmsVar)
70       
71#        log.debug("y midpoints min =[" + str(ymid.min()) + \
72#                "] max =[" + str(ymid.max()) + "]")
73#        log.debug("x midpoints min =[" + str(xmid.min()) + \
74#                "] max =[" + str(xmid.max()) + "]")
75#       
76        reducedVar = self._getResizedVar(xLimits, yLimits)
77       
78           
79        (gridBoundsX, gridBoundsY) = self._buildGridBounds(reducedVar)
80        (gridMidpointX, gridMidpointY) = self._buildGridMidpoints(reducedVar)
81
82        gridValues = self._buildGridValues(reducedVar)
83               
84#        log.debug("After resize:")
85#        log.debug("y midpoints min =[" + str(gridMidpointY.min()) + \
86#                "] max =[" + str(gridMidpointY.max()) + "]")
87#        log.debug("x midpoints min =[" + str(gridMidpointX.min()) + \
88#                "] max =[" + str(gridMidpointX.max()) + "]")
89#       
90#        log.debug("Diff: y [" + str(gridMidpointY.min() - ymid.min()) + \
91#                  "][" + str(gridMidpointY.max() - ymid.max()) )
92#        log.debug("Diff: x [" + str(gridMidpointX.min() - xmid.min()) + \
93#                  "][" + str(gridMidpointX.max() - xmid.max()) +"]")
94#       
95#        log.debug("self.cdmsVar.shape = %s" % (self.cdmsVar.shape,))
96#        log.debug("gridValues.shape = %s" % (gridValues.shape,))
97#        log.debug("gridBoundsX.shape = %s" % (gridBoundsX.shape,))
98
99        return Grid(gridBoundsX, gridBoundsY, gridMidpointX, gridMidpointY, gridValues)
100
101    def _getResizedVar(self, xLimits, yLimits):
102        """
103        Replaces any None's in the limits with the max/min midpoint values then
104        returns the reduced variable from self.resizeVar().
105       
106        If both of these limits are None then the original self.cdmsVar will be
107        returned. If any part of these limits is None then it will be replaced
108        with the maximum or minimum midpoint value from the self.cdms variable.
109       
110        After filling any Nones in the limits this method calls the
111        self._resizeVar method to do the resizing.
112       
113        @param xLimits: (optional) longitude limits of the resulting grid
114        @type xLimits: a tuple of (MinLongitude, MaxLongitude)
115        @param yLimits: (optional) latitude limits of the resulting grid
116        @type yLimits: a tuple of (MinLatitude, MaxLatitude)
117        @return: A variable containing the subset of data
118        @rtype: cdms.variable
119        """
120       
121        if xLimits == None: xLimits = (None, None)
122        if yLimits == None: yLimits = (None, None)
123       
124        if xLimits == (None, None) and yLimits == (None, None):
125            reducedVar = self.cdmsVar
126        else:
127            if None in xLimits or None in yLimits:
128                xLimits, yLimits = self._replaceNoneInLimitsWithMaxMin(xLimits, yLimits)
129           
130#            log.debug("limits:" + str(xLimits) + ", " + str(yLimits))
131            reducedVar = self._resizeVar(xLimits, yLimits)
132           
133        return reducedVar
134       
135    def _resizeVar(self, xLimits, yLimits):
136        """
137        Returns a cdms variable that contains the subset of self.cdmsVar that is between
138        the limits.
139       
140        @param xLimits: (optional) longitude limits of the resulting grid
141        @type xLimits: a tuple of (MinLongitude, MaxLongitude)
142        @param yLimits: (optional) latitude limits of the resulting grid
143        @type yLimits: a tuple of (MinLatitude, MaxLatitude)
144        @return: A variable containing the subset of data
145        @rtype: cdms.variable
146        """
147        raise NotImplementedError
148
149    def _buildGridBounds(self, cdmsVar):
150        """
151        Builds two 2d Numpy array of the x and y positions of the grid
152        boundaries.
153       
154        Returns one array for the x bounds and one for the y bound. These
155        arrays contain the lower boundary for a given grid box.
156       
157        e.g. xBounds[x,y] will give the lower x boundary for the gridbox x,y.
158       
159        @param cdmsVar: the variable to extract the boundry data from
160        @type cdmsVar: cdms.variable
161        @return: the position data as (xPositions, yPositions)
162        @rtype: 2 Numpy Arrays
163       
164        """
165        raise NotImplementedError
166
167    def _buildGridMidpoints(self, cdmsVar):
168        """
169        Builds two 2d Numpy arrays for the x and y midpoints of a given grid
170        box, this position corresponds to the location of the grid box value.
171       
172        Returns one array for the midpoint x position and another for the y
173        postion.
174       
175        e.g. xMidpoints[x,y] will give the midpoint of the gridbox x,y and the
176        position of the ascociated measurment (value[x,y]).
177       
178        @param cdmsVar: the variable to extract the midpoint data from
179        @type cdmsVar: cdms.variable
180        @return: the midpoint data  as (xMidpoints, yMidpoints)
181        @rtype: 2 Numpy Arrays
182        """
183        raise NotImplementedError
184   
185    @staticmethod
186    def _getBoundsFromAxis(axis):
187        """
188        returns the bounding grid for the values in an axis, These bounds will
189        be retrieved form the cdms variable if present or generated.
190
191        The bounds array is
192
193        @param axis: the axis to get bounds for
194        @type axis: cdms.axis.Axis
195        @return: the axis bounds
196        @rtype: numpy.aarray
197        """
198        if axis.getBounds() == None:
199            axisBounds = GridBuilderBase._createBoundsFormList(axis.getValue())
200        else:
201            axisBounds = GridBuilderBase._mergeBounds(axis.getBounds())
202
203        return axisBounds
204   
205    @staticmethod
206    def _createBoundsFormList(values):
207        """
208        Creates a list of boundries from a given list of vlaues.
209
210        These bounding values are created by halfing the distance between the first
211        two items in the list and then adding this value to every other item in the list.
212        The first bounding value is the first item in the list minus this shift.
213
214        @params values: a list of values to create bounds from
215        @type values: a list of int or float
216        """
217        bounds = []
218        shift = (values[1] - values[0])/2
219        bounds.append(values[0] - shift)
220        for item in values:
221            bounds.append(item + shift)
222
223        return N.array(bounds)
224
225    @staticmethod
226    def _mergeBounds(bounds):
227        """
228        Folds a bounds array of shape (x, 2) into a 1D array of shape (x + 1,).
229   
230        We assume that grid boxes are contiguous.  I.e. the
231        right-hand edge of grid box (x, y) is the same as the left-hand
232        edge of grid box (x + 1, y) and similarly in y.
233   
234        @param lonBounds: The longitude bounds array
235        @param latBounds: the latitude bounds array
236        """
237   
238        # Get grid dimensions
239        n = bounds.shape[0]
240   
241        # Take the lower bounds as the mesh point
242        # except for the last index where the upper bounds is taken
243        merged = N.resize(bounds[:, 0], (n + 1,))
244        merged[-1] = bounds[-1, 1]
245   
246        return merged
247   
248   
249   
250   
251    @staticmethod
252    def _fillMissingLimitsFromArray(limits, array):
253       
254        newLimits = [limits[0], limits[1]]
255
256        if newLimits[0] == None:
257            newLimits[0] = array.min()
258           
259        if newLimits[1] == None:
260            newLimits[1] = array.max()
261
262        return tuple(newLimits)
263
264    def _replaceNoneInLimitsWithMaxMin(self, xLimits, yLimits):
265       
266        (newXLimits, newYLimits) = (xLimits, yLimits)
267        xBounds, yBounds = self._buildGridMidpoints(self.cdmsVar)
268        #log.debug("bounds" + str(yBounds))
269       
270        if None in xLimits:
271            axis = self.cdmsVar.getAxisList()[1]
272            newXLimits = GridBuilderBase._fillMissingLimitsFromArray(xLimits, xBounds)
273           
274        if None in yLimits:
275            axis = self.cdmsVar.getAxisList()[0]
276            newYLimits = GridBuilderBase._fillMissingLimitsFromArray(yLimits, yBounds)
277       
278        return (newXLimits, newYLimits)
279
280    def _checkVariable(self):
281        """
282        checks the cdms variable to make sure that it is suitable for plotting
283        """
284
285        self._checkVariableAxis()
286
287    def _checkVariableAxis(self):
288        """
289        Checks the axis on a given variable, if axes that arn't expected are
290        found then a warning is written to the log.
291        """
292
293        if len(self.cdmsVar.getAxisList()) > 2:
294            log.warning('cdms variable contains ' \
295                        +str(len(self.cdmsVar.getAxisList())) + ' axes.')
296
297        if self.cdmsVar.getTime() != None:
298            log.warning('cdms variable contains a time axes.')
299            #firstTime = self.cdmsVar.getTime().getValue()[0]
300            #self.cdmsVar = self.cdmsVar(time = firstTime, squeeze = 1)
301            #log.warning('using first time variable of ' + str(firstTime))
302
303        if self.cdmsVar.getLevel() != None:
304            log.warning('cdms variable contains a level axes.')
305            #firstLevel = self.cdmsVar.getLevel().getValue()[0]
306            #self.cdmsVar = self.cdmsVar(level = float(firstLevel), squeeze = 1)
307            #log.warning('using first level variable of ' + str(firstLevel))
308
309    def _buildGridValues(self,cdmsVar):
310        """
311        Builds a numpy array of values for each of the grid boxes.
312       
313        e.g. values[y,x] is the value for grid box y, x.
314        """
315        data = cdmsVar.getValue()
316        missing = cdmsVar.getMissing()
317       
318        # this data is extracted such that if the axis are in order x/y
319        # data[xIndex, yIndex] = value
320        # but if they are in y/x it is:
321        # data[yIndex, xIndex] = value
322        # this function needs to return the value in terms of data[y,x] for the
323        # imshow call to work.
324
325        if not self.__class__._areAxisInOrderYX(cdmsVar):
326            data = data.swapaxes(0,1)
327                 
328        if missing == None:
329            missing = 1e20
330               
331        return MA.masked_values(data, missing)
332       
333def _logAxis(axis):
334    """
335    A function that writes the details of a particular axis to log.debug.
336    """
337
338    if axis.isLongitude() :
339        msg = 'Longitude Axis'
340    elif axis.isLatitude():
341        msg = 'Latitude Axis'
342    elif axis.isLevel():
343        msg = 'Level Axis'
344    elif axis.isTime():
345        msg = 'Time Axis'
346    else:
347        msg = 'Unknown axis'
348
349    log.debug(msg + ' ' + str(axis.id) + '(' + str(len(axis.getValue())) +') :' +\
350                str(axis.getValue()[0]) + ' - ' + str(axis.getValue()[-1]))
Note: See TracBrowser for help on using the repository browser.