Skip to content

Commit

Permalink
Allow some 'key=None' args in Geostationary creation. (#3628)
Browse files Browse the repository at this point in the history
LGTM

* Allow some 'key=None' args in Geostationary creation.

* Integration test loading netcdf geostationary without offset properties.
  • Loading branch information
pp-mo authored and trexfeathers committed Jan 10, 2020
1 parent 64cedf1 commit c9506e6
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* :class:`iris.coord_systems.Geostationary` can now accept creation arguments of
`false_easting=None` or `false_northing=None`, equivalent to values of 0.
Previously these kwargs could be omitted, but could not be set to `None`.
This also enables loading netcdf data on a Geostationary grid, where either of these
keys is not present as a grid-mapping variable property : Previously, loading any
such data caused an exception.
8 changes: 6 additions & 2 deletions lib/iris/coord_systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,8 +714,8 @@ def __init__(
longitude_of_projection_origin,
perspective_point_height,
sweep_angle_axis,
false_easting=0,
false_northing=0,
false_easting=None,
false_northing=None,
ellipsoid=None,
):

Expand Down Expand Up @@ -768,9 +768,13 @@ def __init__(
self.perspective_point_height = float(perspective_point_height)

#: X offset from planar origin in metres.
if false_easting is None:
false_easting = 0
self.false_easting = float(false_easting)

#: Y offset from planar origin in metres.
if false_northing is None:
false_northing = 0
self.false_northing = float(false_northing)

#: The axis along which the satellite instrument sweeps - 'x' or 'y'.
Expand Down
76 changes: 76 additions & 0 deletions lib/iris/tests/integration/test_netcdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
from contextlib import contextmanager
from itertools import repeat
import os.path
from os.path import join as path_join
import shutil
from subprocess import check_call
import tempfile
from unittest import mock
import warnings
Expand Down Expand Up @@ -592,5 +594,79 @@ def test_standard_name_roundtrip(self):
self.assertEqual(detection_limit_cube.standard_name, standard_name)


class TestLoadMinimalGeostationary(tests.IrisTest):
"""
Check we can load data with a geostationary grid-mapping, even when the
'false-easting' and 'false_northing' properties are missing.
"""

_geostationary_problem_cdl = """
netcdf geostationary_problem_case {
dimensions:
y = 2 ;
x = 3 ;
variables:
short radiance(y, x) ;
radiance:standard_name = "toa_outgoing_radiance_per_unit_wavelength" ;
radiance:units = "W m-2 sr-1 um-1" ;
radiance:coordinates = "y x" ;
radiance:grid_mapping = "imager_grid_mapping" ;
short y(y) ;
y:units = "rad" ;
y:axis = "Y" ;
y:long_name = "fixed grid projection y-coordinate" ;
y:standard_name = "projection_y_coordinate" ;
short x(x) ;
x:units = "rad" ;
x:axis = "X" ;
x:long_name = "fixed grid projection x-coordinate" ;
x:standard_name = "projection_x_coordinate" ;
int imager_grid_mapping ;
imager_grid_mapping:grid_mapping_name = "geostationary" ;
imager_grid_mapping:perspective_point_height = 35786023. ;
imager_grid_mapping:semi_major_axis = 6378137. ;
imager_grid_mapping:semi_minor_axis = 6356752.31414 ;
imager_grid_mapping:latitude_of_projection_origin = 0. ;
imager_grid_mapping:longitude_of_projection_origin = -75. ;
imager_grid_mapping:sweep_angle_axis = "x" ;
data:
// coord values, just so these can be dim-coords
y = 0, 1 ;
x = 0, 1, 2 ;
}
"""

@classmethod
def setUpClass(cls):
# Create a temp directory for transient test files.
cls.temp_dir = tempfile.mkdtemp()
cls.path_test_cdl = path_join(cls.temp_dir, "geos_problem.cdl")
cls.path_test_nc = path_join(cls.temp_dir, "geos_problem.nc")
# Create a reference file from the CDL text.
with open(cls.path_test_cdl, "w") as f_out:
f_out.write(cls._geostationary_problem_cdl)
# Call 'ncgen' to make an actual netCDF file from the CDL.
command = "ncgen -o {} {}".format(cls.path_test_nc, cls.path_test_cdl)
check_call(command, shell=True)

@classmethod
def tearDownClass(cls):
# Destroy the temp directory.
shutil.rmtree(cls.temp_dir)

def test_geostationary_no_false_offsets(self):
# Check we can load the test data and coordinate system properties are correct.
cube = iris.load_cube(self.path_test_nc)
# Check the coordinate system properties has the correct default properties.
cs = cube.coord_system()
self.assertIsInstance(cs, iris.coord_systems.Geostationary)
self.assertEqual(cs.false_easting, 0.0)
self.assertEqual(cs.false_northing, 0.0)


if __name__ == "__main__":
tests.main()
Original file line number Diff line number Diff line change
Expand Up @@ -22,47 +22,57 @@


class TestBuildGeostationaryCoordinateSystem(tests.IrisTest):
def _test(self, inverse_flattening=False):
def _test(self, inverse_flattening=False, replace_props=None, remove_props=None):
"""
Generic test that can check vertical perspective validity with or
without inverse flattening.
"""
cf_grid_var_kwargs = {
'spec': [],
# Make a dictionary of the non-ellipsoid properties to be added to both a test
# coord-system, and a test grid-mapping cf_var.
non_ellipsoid_kwargs = {
'latitude_of_projection_origin': 0.0,
'longitude_of_projection_origin': 2.0,
'perspective_point_height': 2000000.0,
'sweep_angle_axis': 'x',
'false_easting': 100.0,
'false_northing': 200.0,
'semi_major_axis': 6377563.396}
'false_northing': 200.0}

# Make specified adjustments to the non-ellipsoid properties.
if remove_props:
for key in remove_props:
non_ellipsoid_kwargs.pop(key, None)
if replace_props:
for key, value in replace_props.items():
non_ellipsoid_kwargs[key] = value

# Make a dictionary of ellipsoid properties, to be added to both a test
# ellipsoid and the grid-mapping cf_var.
ellipsoid_kwargs = {'semi_major_axis': 6377563.396}
if inverse_flattening:
ellipsoid_kwargs['inverse_flattening'] = 299.3249646
else:
ellipsoid_kwargs['semi_minor_axis'] = 6356256.909
cf_grid_var_kwargs.update(ellipsoid_kwargs)

cf_grid_var = mock.Mock(**cf_grid_var_kwargs)
ellipsoid = iris.coord_systems.GeogCS(**ellipsoid_kwargs)

cf_grid_var_kwargs = non_ellipsoid_kwargs.copy()
cf_grid_var_kwargs.update(ellipsoid_kwargs)
cf_grid_var = mock.Mock(spec=[], **cf_grid_var_kwargs)
cs = build_geostationary_coordinate_system(None, cf_grid_var)
expected = Geostationary(
latitude_of_projection_origin=cf_grid_var.
latitude_of_projection_origin,
longitude_of_projection_origin=cf_grid_var.
longitude_of_projection_origin,
perspective_point_height=cf_grid_var.perspective_point_height,
sweep_angle_axis=cf_grid_var.sweep_angle_axis,
false_easting=cf_grid_var.false_easting,
false_northing=cf_grid_var.false_northing,
ellipsoid=ellipsoid)

ellipsoid = iris.coord_systems.GeogCS(**ellipsoid_kwargs)
expected = Geostationary(ellipsoid=ellipsoid, **non_ellipsoid_kwargs)
self.assertEqual(cs, expected)

def test_valid(self):
self._test(inverse_flattening=False)

def test_inverse_flattening(self):
self._test(inverse_flattening=True)

def test_false_offsets_missing(self):
self._test(remove_props=['false_easting', 'false_northing'])

def test_false_offsets_none(self):
self._test(replace_props={'false_easting':None, 'false_northing':None})


if __name__ == "__main__":
tests.main()

0 comments on commit c9506e6

Please sign in to comment.