diff --git a/.cirrus.yml b/.cirrus.yml
index 92b8d788e6..c9c1d71859 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -38,7 +38,7 @@ env:
# Conda packages to be installed.
CONDA_CACHE_PACKAGES: "nox pip"
# Git commit hash for iris test data.
- IRIS_TEST_DATA_VERSION: "2.5"
+ IRIS_TEST_DATA_VERSION: "2.7"
# Base directory for the iris-test-data.
IRIS_TEST_DATA_DIR: ${HOME}/iris-test-data
diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst
index 27ed876a20..38bf5cf835 100644
--- a/docs/src/whatsnew/dev.rst
+++ b/docs/src/whatsnew/dev.rst
@@ -31,7 +31,8 @@ This document explains the changes made to Iris for this release
✨ Features
===========
-#. N/A
+#. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to
+ :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`)
🐛 Bugs Fixed
diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py
index 2f875bb159..311ed35f44 100644
--- a/lib/iris/coord_systems.py
+++ b/lib/iris/coord_systems.py
@@ -1083,6 +1083,8 @@ def __init__(
longitude_of_projection_origin=None,
ellipsoid=None,
standard_parallel=None,
+ false_easting=None,
+ false_northing=None,
):
"""
Constructs a Mercator coord system.
@@ -1098,6 +1100,12 @@ def __init__(
* standard_parallel:
The latitude where the scale is 1. Defaults to 0.0 .
+ * false_easting:
+ X offset from the planar origin in metres. Defaults to 0.0.
+
+ * false_northing:
+ Y offset from the planar origin in metres. Defaults to 0.0.
+
"""
#: True longitude of planar origin in degrees.
self.longitude_of_projection_origin = _arg_default(
@@ -1110,12 +1118,20 @@ def __init__(
#: The latitude where the scale is 1.
self.standard_parallel = _arg_default(standard_parallel, 0)
+ #: X offset from the planar origin in metres.
+ self.false_easting = _arg_default(false_easting, 0)
+
+ #: Y offset from the planar origin in metres.
+ self.false_northing = _arg_default(false_northing, 0)
+
def __repr__(self):
res = (
"Mercator(longitude_of_projection_origin="
"{self.longitude_of_projection_origin!r}, "
"ellipsoid={self.ellipsoid!r}, "
- "standard_parallel={self.standard_parallel!r})"
+ "standard_parallel={self.standard_parallel!r}, "
+ "false_easting={self.false_easting!r}, "
+ "false_northing={self.false_northing!r})"
)
return res.format(self=self)
@@ -1126,6 +1142,8 @@ def as_cartopy_crs(self):
central_longitude=self.longitude_of_projection_origin,
globe=globe,
latitude_true_scale=self.standard_parallel,
+ false_easting=self.false_easting,
+ false_northing=self.false_northing,
)
def as_cartopy_projection(self):
diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py
index a5b507d583..198daeceea 100644
--- a/lib/iris/fileformats/_nc_load_rules/helpers.py
+++ b/lib/iris/fileformats/_nc_load_rules/helpers.py
@@ -440,10 +440,13 @@ def build_mercator_coordinate_system(engine, cf_grid_var):
longitude_of_projection_origin = getattr(
cf_grid_var, CF_ATTR_GRID_LON_OF_PROJ_ORIGIN, None
)
+ standard_parallel = getattr(
+ cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None
+ )
+ false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None)
+ false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None)
# Iris currently only supports Mercator projections with specific
- # values for false_easting, false_northing,
- # scale_factor_at_projection_origin and standard_parallel. These are
- # checked elsewhere.
+ # scale_factor_at_projection_origin. This is checked elsewhere.
ellipsoid = None
if (
@@ -454,7 +457,11 @@ def build_mercator_coordinate_system(engine, cf_grid_var):
ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening)
cs = iris.coord_systems.Mercator(
- longitude_of_projection_origin, ellipsoid=ellipsoid
+ longitude_of_projection_origin,
+ ellipsoid=ellipsoid,
+ standard_parallel=standard_parallel,
+ false_easting=false_easting,
+ false_northing=false_northing,
)
return cs
@@ -1244,27 +1251,10 @@ def has_supported_mercator_parameters(engine, cf_name):
is_valid = True
cf_grid_var = engine.cf_var.cf_group[cf_name]
- false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None)
- false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None)
scale_factor_at_projection_origin = getattr(
cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None
)
- standard_parallel = getattr(
- cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None
- )
- if false_easting is not None and false_easting != 0:
- warnings.warn(
- "False eastings other than 0.0 not yet supported "
- "for Mercator projections"
- )
- is_valid = False
- if false_northing is not None and false_northing != 0:
- warnings.warn(
- "False northings other than 0.0 not yet supported "
- "for Mercator projections"
- )
- is_valid = False
if (
scale_factor_at_projection_origin is not None
and scale_factor_at_projection_origin != 1
@@ -1274,12 +1264,6 @@ def has_supported_mercator_parameters(engine, cf_name):
"Mercator projections"
)
is_valid = False
- if standard_parallel is not None and standard_parallel != 0:
- warnings.warn(
- "Standard parallels other than 0.0 not yet "
- "supported for Mercator projections"
- )
- is_valid = False
return is_valid
diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py
index 100ab29daa..f79367125e 100644
--- a/lib/iris/fileformats/netcdf.py
+++ b/lib/iris/fileformats/netcdf.py
@@ -2553,10 +2553,8 @@ def add_ellipsoid(ellipsoid):
cf_var_grid.longitude_of_projection_origin = (
cs.longitude_of_projection_origin
)
- # The Mercator class has implicit defaults for certain
- # parameters
- cf_var_grid.false_easting = 0.0
- cf_var_grid.false_northing = 0.0
+ cf_var_grid.false_easting = cs.false_easting
+ cf_var_grid.false_northing = cs.false_northing
cf_var_grid.scale_factor_at_projection_origin = 1.0
# lcc
diff --git a/lib/iris/tests/results/coord_systems/Mercator.xml b/lib/iris/tests/results/coord_systems/Mercator.xml
index e8036ef824..db3ccffec7 100644
--- a/lib/iris/tests/results/coord_systems/Mercator.xml
+++ b/lib/iris/tests/results/coord_systems/Mercator.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/lib/iris/tests/results/netcdf/netcdf_merc.cml b/lib/iris/tests/results/netcdf/netcdf_merc.cml
index 02fc4e7c34..5e17400158 100644
--- a/lib/iris/tests/results/netcdf/netcdf_merc.cml
+++ b/lib/iris/tests/results/netcdf/netcdf_merc.cml
@@ -53,15 +53,15 @@
45.5158, 45.9993]]" shape="(192, 192)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="lon"/>
-
-
+
-
-
+
diff --git a/lib/iris/tests/results/netcdf/netcdf_merc_false.cml b/lib/iris/tests/results/netcdf/netcdf_merc_false.cml
new file mode 100644
index 0000000000..d916f5f753
--- /dev/null
+++ b/lib/iris/tests/results/netcdf/netcdf_merc_false.cml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py
index 2c22c6d088..8cdbe27257 100644
--- a/lib/iris/tests/test_netcdf.py
+++ b/lib/iris/tests/test_netcdf.py
@@ -218,6 +218,16 @@ def test_load_merc_grid(self):
)
self.assertCML(cube, ("netcdf", "netcdf_merc.cml"))
+ def test_load_merc_false_en_grid(self):
+ # Test loading a single CF-netCDF file with a Mercator grid_mapping that
+ # includes false easting and northing
+ cube = iris.load_cube(
+ tests.get_data_path(
+ ("NetCDF", "mercator", "false_east_north_merc.nc")
+ )
+ )
+ self.assertCML(cube, ("netcdf", "netcdf_merc_false.cml"))
+
def test_load_stereographic_grid(self):
# Test loading a single CF-netCDF file with a stereographic
# grid_mapping.
diff --git a/lib/iris/tests/unit/coord_systems/test_Mercator.py b/lib/iris/tests/unit/coord_systems/test_Mercator.py
index 33efaef9da..8a37a8fcc5 100644
--- a/lib/iris/tests/unit/coord_systems/test_Mercator.py
+++ b/lib/iris/tests/unit/coord_systems/test_Mercator.py
@@ -29,7 +29,8 @@ def test_repr(self):
"Mercator(longitude_of_projection_origin=90.0, "
"ellipsoid=GeogCS(semi_major_axis=6377563.396, "
"semi_minor_axis=6356256.909), "
- "standard_parallel=0.0)"
+ "standard_parallel=0.0, "
+ "false_easting=0.0, false_northing=0.0)"
)
self.assertEqual(expected, repr(self.tm))
@@ -38,16 +39,23 @@ class Test_init_defaults(tests.IrisTest):
def test_set_optional_args(self):
# Check that setting the optional (non-ellipse) args works.
crs = Mercator(
- longitude_of_projection_origin=27, standard_parallel=157.4
+ longitude_of_projection_origin=27,
+ standard_parallel=157.4,
+ false_easting=13,
+ false_northing=12,
)
self.assertEqualAndKind(crs.longitude_of_projection_origin, 27.0)
self.assertEqualAndKind(crs.standard_parallel, 157.4)
+ self.assertEqualAndKind(crs.false_easting, 13.0)
+ self.assertEqualAndKind(crs.false_northing, 12.0)
def _check_crs_defaults(self, crs):
# Check for property defaults when no kwargs options were set.
# NOTE: except ellipsoid, which is done elsewhere.
self.assertEqualAndKind(crs.longitude_of_projection_origin, 0.0)
self.assertEqualAndKind(crs.standard_parallel, 0.0)
+ self.assertEqualAndKind(crs.false_easting, 0.0)
+ self.assertEqualAndKind(crs.false_northing, 0.0)
def test_no_optional_args(self):
# Check expected defaults with no optional args.
@@ -57,7 +65,10 @@ def test_no_optional_args(self):
def test_optional_args_None(self):
# Check expected defaults with optional args=None.
crs = Mercator(
- longitude_of_projection_origin=None, standard_parallel=None
+ longitude_of_projection_origin=None,
+ standard_parallel=None,
+ false_easting=None,
+ false_northing=None,
)
self._check_crs_defaults(crs)
@@ -77,6 +88,8 @@ def test_extra_kwargs(self):
# converted to a cartopy CRS.
longitude_of_projection_origin = 90.0
true_scale_lat = 14.0
+ false_easting = 13
+ false_northing = 12
ellipsoid = GeogCS(
semi_major_axis=6377563.396, semi_minor_axis=6356256.909
)
@@ -85,6 +98,8 @@ def test_extra_kwargs(self):
longitude_of_projection_origin,
ellipsoid=ellipsoid,
standard_parallel=true_scale_lat,
+ false_easting=false_easting,
+ false_northing=false_northing,
)
expected = ccrs.Mercator(
@@ -95,6 +110,8 @@ def test_extra_kwargs(self):
ellipse=None,
),
latitude_true_scale=true_scale_lat,
+ false_easting=false_easting,
+ false_northing=false_northing,
)
res = merc_cs.as_cartopy_crs()
@@ -113,6 +130,8 @@ def test_simple(self):
def test_extra_kwargs(self):
longitude_of_projection_origin = 90.0
true_scale_lat = 14.0
+ false_easting = 13
+ false_northing = 12
ellipsoid = GeogCS(
semi_major_axis=6377563.396, semi_minor_axis=6356256.909
)
@@ -121,6 +140,8 @@ def test_extra_kwargs(self):
longitude_of_projection_origin,
ellipsoid=ellipsoid,
standard_parallel=true_scale_lat,
+ false_easting=false_easting,
+ false_northing=false_northing,
)
expected = ccrs.Mercator(
@@ -131,6 +152,8 @@ def test_extra_kwargs(self):
ellipse=None,
),
latitude_true_scale=true_scale_lat,
+ false_easting=false_easting,
+ false_northing=false_northing,
)
res = merc_cs.as_cartopy_projection()
diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py
index dfe2895f29..1b9857c0be 100644
--- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py
+++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py
@@ -28,7 +28,7 @@ def _engine(cf_grid_var, cf_name):
class TestHasSupportedMercatorParameters(tests.IrisTest):
- def test_valid(self):
+ def test_valid_base(self):
cf_name = "mercator"
cf_grid_var = mock.Mock(
spec=[],
@@ -45,85 +45,50 @@ def test_valid(self):
self.assertTrue(is_valid)
- def test_invalid_scale_factor(self):
- # Iris does not yet support scale factors other than one for
- # Mercator projections
+ def test_valid_false_easting_northing(self):
cf_name = "mercator"
cf_grid_var = mock.Mock(
spec=[],
- longitude_of_projection_origin=0,
- false_easting=0,
- false_northing=0,
- scale_factor_at_projection_origin=0.9,
+ longitude_of_projection_origin=-90,
+ false_easting=15,
+ false_northing=10,
+ scale_factor_at_projection_origin=1,
semi_major_axis=6377563.396,
semi_minor_axis=6356256.909,
)
engine = _engine(cf_grid_var, cf_name)
- with warnings.catch_warnings(record=True) as warns:
- warnings.simplefilter("always")
- is_valid = has_supported_mercator_parameters(engine, cf_name)
+ is_valid = has_supported_mercator_parameters(engine, cf_name)
- self.assertFalse(is_valid)
- self.assertEqual(len(warns), 1)
- self.assertRegex(str(warns[0]), "Scale factor")
+ self.assertTrue(is_valid)
- def test_invalid_standard_parallel(self):
- # Iris does not yet support standard parallels other than zero for
- # Mercator projections
+ def test_valid_standard_parallel(self):
cf_name = "mercator"
cf_grid_var = mock.Mock(
spec=[],
- longitude_of_projection_origin=0,
+ longitude_of_projection_origin=-90,
false_easting=0,
false_northing=0,
- standard_parallel=30,
- semi_major_axis=6377563.396,
- semi_minor_axis=6356256.909,
- )
- engine = _engine(cf_grid_var, cf_name)
-
- with warnings.catch_warnings(record=True) as warns:
- warnings.simplefilter("always")
- is_valid = has_supported_mercator_parameters(engine, cf_name)
-
- self.assertFalse(is_valid)
- self.assertEqual(len(warns), 1)
- self.assertRegex(str(warns[0]), "Standard parallel")
-
- def test_invalid_false_easting(self):
- # Iris does not yet support false eastings other than zero for
- # Mercator projections
- cf_name = "mercator"
- cf_grid_var = mock.Mock(
- spec=[],
- longitude_of_projection_origin=0,
- false_easting=100,
- false_northing=0,
- scale_factor_at_projection_origin=1,
+ standard_parallel=15,
semi_major_axis=6377563.396,
semi_minor_axis=6356256.909,
)
engine = _engine(cf_grid_var, cf_name)
- with warnings.catch_warnings(record=True) as warns:
- warnings.simplefilter("always")
- is_valid = has_supported_mercator_parameters(engine, cf_name)
+ is_valid = has_supported_mercator_parameters(engine, cf_name)
- self.assertFalse(is_valid)
- self.assertEqual(len(warns), 1)
- self.assertRegex(str(warns[0]), "False easting")
+ self.assertTrue(is_valid)
- def test_invalid_false_northing(self):
- # Iris does not yet support false northings other than zero for
+ def test_invalid_scale_factor(self):
+ # Iris does not yet support scale factors other than one for
# Mercator projections
cf_name = "mercator"
cf_grid_var = mock.Mock(
spec=[],
longitude_of_projection_origin=0,
false_easting=0,
- false_northing=100,
- scale_factor_at_projection_origin=1,
+ false_northing=0,
+ scale_factor_at_projection_origin=0.9,
semi_major_axis=6377563.396,
semi_minor_axis=6356256.909,
)
@@ -135,7 +100,7 @@ def test_invalid_false_northing(self):
self.assertFalse(is_valid)
self.assertEqual(len(warns), 1)
- self.assertRegex(str(warns[0]), "False northing")
+ self.assertRegex(str(warns[0]), "Scale factor")
if __name__ == "__main__":