Skip to content

Commit

Permalink
Merge pull request #1978 from rhattersley/clip-latitude
Browse files Browse the repository at this point in the history
Clip guessed latitude bounds to [-90, 90].
  • Loading branch information
bjlittle committed Apr 20, 2016
2 parents a207512 + 21b50a4 commit e2a4552
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 9 deletions.
14 changes: 11 additions & 3 deletions lib/iris/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ class Future(threading.local):
"""Run-time configuration controller."""

def __init__(self, cell_datetime_objects=False, netcdf_promote=False,
strict_grib_load=False, netcdf_no_unlimited=False):
strict_grib_load=False, netcdf_no_unlimited=False,
clip_latitudes=False):
"""
A container for run-time options controls.
Expand Down Expand Up @@ -178,17 +179,24 @@ def __init__(self, cell_datetime_objects=False, netcdf_promote=False,
unlimited. The current default is that the leading dimension is
unlimited unless otherwise specified.
The option `clip_latitudes` controls whether the
:meth:`iris.coords.Coord.guess_bounds()` method limits the
guessed bounds to [-90, 90] for latitudes.
"""
self.__dict__['cell_datetime_objects'] = cell_datetime_objects
self.__dict__['netcdf_promote'] = netcdf_promote
self.__dict__['strict_grib_load'] = strict_grib_load
self.__dict__['netcdf_no_unlimited'] = netcdf_no_unlimited
self.__dict__['clip_latitudes'] = clip_latitudes

def __repr__(self):
msg = ('Future(cell_datetime_objects={}, netcdf_promote={}, '
'strict_grib_load={}, netcdf_no_unlimited={})')
'strict_grib_load={}, netcdf_no_unlimited={}, '
'clip_latitudes={})')
return msg.format(self.cell_datetime_objects, self.netcdf_promote,
self.strict_grib_load, self.netcdf_no_unlimited)
self.strict_grib_load, self.netcdf_no_unlimited,
self.clip_latitudes)

def __setattr__(self, name, value):
if name not in self.__dict__:
Expand Down
35 changes: 31 additions & 4 deletions lib/iris/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -1011,8 +1011,9 @@ def _guess_bounds(self, bound_position=0.5):
Kwargs:
* bound_position - The desired position of the bounds relative to the
position of the points.
* bound_position:
The desired position of the bounds relative to the position
of the points.
Returns:
A numpy array of shape (len(self.points), 2).
Expand All @@ -1021,6 +1022,15 @@ def _guess_bounds(self, bound_position=0.5):
This method only works for coordinates with ``coord.ndim == 1``.
.. note::
If `iris.FUTURE.clip_latitudes` is True, then this method
will clip the coordinate bounds to the range [-90, 90] when:
- it is a `latitude` or `grid_latitude` coordinate,
- the units are degrees,
- all the points are in the range [-90, 90].
"""
# XXX Consider moving into DimCoord
# ensure we have monotonic points
Expand Down Expand Up @@ -1056,6 +1066,13 @@ def _guess_bounds(self, bound_position=0.5):

bounds = np.array([min_bounds, max_bounds]).transpose()

if (iris.FUTURE.clip_latitudes and
self.name() in ('latitude', 'grid_latitude') and
self.units == 'degree'):
points = self.points
if (points >= -90).all() and (points <= 90).all():
np.clip(bounds, -90, 90, out=bounds)

return bounds

def guess_bounds(self, bound_position=0.5):
Expand All @@ -1073,8 +1090,9 @@ def guess_bounds(self, bound_position=0.5):
Kwargs:
* bound_position - The desired position of the bounds relative to the
position of the points.
* bound_position:
The desired position of the bounds relative to the position
of the points.
.. note::
Expand All @@ -1087,6 +1105,15 @@ def guess_bounds(self, bound_position=0.5):
produce unexpected results : In such cases you should assign
suitable values directly to the bounds property, instead.
.. note::
If `iris.FUTURE.clip_latitudes` is True, then this method
will clip the coordinate bounds to the range [-90, 90] when:
- it is a `latitude` or `grid_latitude` coordinate,
- the units are degrees,
- all the points are in the range [-90, 90].
"""
self.bounds = self._guess_bounds(bound_position)

Expand Down
77 changes: 76 additions & 1 deletion lib/iris/tests/unit/coords/test_Coord.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2013 - 2015, Met Office
# (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of Iris.
#
Expand Down Expand Up @@ -27,6 +27,7 @@

import numpy as np

import iris
from iris.coords import DimCoord, AuxCoord, Coord
from iris.tests import mock

Expand Down Expand Up @@ -158,6 +159,80 @@ def test_circular_decreasing_alt_range(self):
self.assertArrayEqual(target, self.coord.bounds)


class Test_guess_bounds__default_latitude_clipping(tests.IrisTest):
def test_all_inside(self):
lat = DimCoord([-10, 0, 20], units='degree', standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-15, -5], [-5, 10], [10, 30]])

def test_points_inside_bounds_outside(self):
lat = DimCoord([-80, 0, 70], units='degree', standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]])

def test_points_to_edges_bounds_outside(self):
lat = DimCoord([-90, 0, 90], units='degree', standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-135, -45], [-45, 45], [45, 135]])

def test_points_outside(self):
lat = DimCoord([-100, 0, 120], units='degree',
standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-150, -50], [-50, 60], [60, 180]])


class Test_guess_bounds__enabled_latitude_clipping(tests.IrisTest):
def setUp(self):
iris.FUTURE.clip_latitudes = True

def tearDown(self):
iris.FUTURE.clip_latitudes = False

def test_all_inside(self):
lat = DimCoord([-10, 0, 20], units='degree', standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-15, -5], [-5, 10], [10, 30]])

def test_points_inside_bounds_outside(self):
lat = DimCoord([-80, 0, 70], units='degree', standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-90, -40], [-40, 35], [35, 90]])

def test_points_inside_bounds_outside_grid_latitude(self):
lat = DimCoord([-80, 0, 70], units='degree',
standard_name='grid_latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-90, -40], [-40, 35], [35, 90]])

def test_points_to_edges_bounds_outside(self):
lat = DimCoord([-90, 0, 90], units='degree', standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-90, -45], [-45, 45], [45, 90]])

def test_points_outside(self):
lat = DimCoord([-100, 0, 120], units='degree',
standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-150, -50], [-50, 60], [60, 180]])

def test_points_inside_bounds_outside_wrong_unit(self):
lat = DimCoord([-80, 0, 70], units='feet', standard_name='latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]])

def test_points_inside_bounds_outside_wrong_name(self):
lat = DimCoord([-80, 0, 70], units='degree', standard_name='longitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]])

def test_points_inside_bounds_outside_wrong_name_2(self):
lat = DimCoord([-80, 0, 70], units='degree',
long_name='other_latitude')
lat.guess_bounds()
self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]])


class Test_cell(tests.IrisTest):
def _mock_coord(self):
coord = mock.Mock(spec=Coord, ndim=1,
Expand Down
8 changes: 7 additions & 1 deletion lib/iris/tests/unit/test_Future.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2013 - 2015, Met Office
# (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of Iris.
#
Expand Down Expand Up @@ -45,6 +45,12 @@ def test_valid_strict_grib_load(self):
future.strict_grib_load = new_value
self.assertEqual(future.strict_grib_load, new_value)

def test_valid_clip_latitudes(self):
future = Future()
new_value = not future.clip_latitudes
future.clip_latitudes = new_value
self.assertEqual(future.clip_latitudes, new_value)

def test_invalid_attribute(self):
future = Future()
with self.assertRaises(AttributeError):
Expand Down

0 comments on commit e2a4552

Please sign in to comment.