Skip to content

Commit c9506e6

Browse files
pp-motrexfeathers
authored andcommitted
Allow some 'key=None' args in Geostationary creation. (#3628)
LGTM * Allow some 'key=None' args in Geostationary creation. * Integration test loading netcdf geostationary without offset properties.
1 parent 64cedf1 commit c9506e6

File tree

4 files changed

+118
-22
lines changed

4 files changed

+118
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
* :class:`iris.coord_systems.Geostationary` can now accept creation arguments of
2+
`false_easting=None` or `false_northing=None`, equivalent to values of 0.
3+
Previously these kwargs could be omitted, but could not be set to `None`.
4+
This also enables loading netcdf data on a Geostationary grid, where either of these
5+
keys is not present as a grid-mapping variable property : Previously, loading any
6+
such data caused an exception.

lib/iris/coord_systems.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,8 @@ def __init__(
714714
longitude_of_projection_origin,
715715
perspective_point_height,
716716
sweep_angle_axis,
717-
false_easting=0,
718-
false_northing=0,
717+
false_easting=None,
718+
false_northing=None,
719719
ellipsoid=None,
720720
):
721721

@@ -768,9 +768,13 @@ def __init__(
768768
self.perspective_point_height = float(perspective_point_height)
769769

770770
#: X offset from planar origin in metres.
771+
if false_easting is None:
772+
false_easting = 0
771773
self.false_easting = float(false_easting)
772774

773775
#: Y offset from planar origin in metres.
776+
if false_northing is None:
777+
false_northing = 0
774778
self.false_northing = float(false_northing)
775779

776780
#: The axis along which the satellite instrument sweeps - 'x' or 'y'.

lib/iris/tests/integration/test_netcdf.py

+76
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from contextlib import contextmanager
1313
from itertools import repeat
1414
import os.path
15+
from os.path import join as path_join
1516
import shutil
17+
from subprocess import check_call
1618
import tempfile
1719
from unittest import mock
1820
import warnings
@@ -592,5 +594,79 @@ def test_standard_name_roundtrip(self):
592594
self.assertEqual(detection_limit_cube.standard_name, standard_name)
593595

594596

597+
class TestLoadMinimalGeostationary(tests.IrisTest):
598+
"""
599+
Check we can load data with a geostationary grid-mapping, even when the
600+
'false-easting' and 'false_northing' properties are missing.
601+
602+
"""
603+
604+
_geostationary_problem_cdl = """
605+
netcdf geostationary_problem_case {
606+
dimensions:
607+
y = 2 ;
608+
x = 3 ;
609+
variables:
610+
short radiance(y, x) ;
611+
radiance:standard_name = "toa_outgoing_radiance_per_unit_wavelength" ;
612+
radiance:units = "W m-2 sr-1 um-1" ;
613+
radiance:coordinates = "y x" ;
614+
radiance:grid_mapping = "imager_grid_mapping" ;
615+
short y(y) ;
616+
y:units = "rad" ;
617+
y:axis = "Y" ;
618+
y:long_name = "fixed grid projection y-coordinate" ;
619+
y:standard_name = "projection_y_coordinate" ;
620+
short x(x) ;
621+
x:units = "rad" ;
622+
x:axis = "X" ;
623+
x:long_name = "fixed grid projection x-coordinate" ;
624+
x:standard_name = "projection_x_coordinate" ;
625+
int imager_grid_mapping ;
626+
imager_grid_mapping:grid_mapping_name = "geostationary" ;
627+
imager_grid_mapping:perspective_point_height = 35786023. ;
628+
imager_grid_mapping:semi_major_axis = 6378137. ;
629+
imager_grid_mapping:semi_minor_axis = 6356752.31414 ;
630+
imager_grid_mapping:latitude_of_projection_origin = 0. ;
631+
imager_grid_mapping:longitude_of_projection_origin = -75. ;
632+
imager_grid_mapping:sweep_angle_axis = "x" ;
633+
634+
data:
635+
636+
// coord values, just so these can be dim-coords
637+
y = 0, 1 ;
638+
x = 0, 1, 2 ;
639+
640+
}
641+
"""
642+
643+
@classmethod
644+
def setUpClass(cls):
645+
# Create a temp directory for transient test files.
646+
cls.temp_dir = tempfile.mkdtemp()
647+
cls.path_test_cdl = path_join(cls.temp_dir, "geos_problem.cdl")
648+
cls.path_test_nc = path_join(cls.temp_dir, "geos_problem.nc")
649+
# Create a reference file from the CDL text.
650+
with open(cls.path_test_cdl, "w") as f_out:
651+
f_out.write(cls._geostationary_problem_cdl)
652+
# Call 'ncgen' to make an actual netCDF file from the CDL.
653+
command = "ncgen -o {} {}".format(cls.path_test_nc, cls.path_test_cdl)
654+
check_call(command, shell=True)
655+
656+
@classmethod
657+
def tearDownClass(cls):
658+
# Destroy the temp directory.
659+
shutil.rmtree(cls.temp_dir)
660+
661+
def test_geostationary_no_false_offsets(self):
662+
# Check we can load the test data and coordinate system properties are correct.
663+
cube = iris.load_cube(self.path_test_nc)
664+
# Check the coordinate system properties has the correct default properties.
665+
cs = cube.coord_system()
666+
self.assertIsInstance(cs, iris.coord_systems.Geostationary)
667+
self.assertEqual(cs.false_easting, 0.0)
668+
self.assertEqual(cs.false_northing, 0.0)
669+
670+
595671
if __name__ == "__main__":
596672
tests.main()

lib/iris/tests/unit/fileformats/pyke_rules/compiled_krb/fc_rules_cf_fc/test_build_geostationary_coordinate_system.py

+30-20
Original file line numberDiff line numberDiff line change
@@ -22,47 +22,57 @@
2222

2323

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

40+
# Make specified adjustments to the non-ellipsoid properties.
41+
if remove_props:
42+
for key in remove_props:
43+
non_ellipsoid_kwargs.pop(key, None)
44+
if replace_props:
45+
for key, value in replace_props.items():
46+
non_ellipsoid_kwargs[key] = value
47+
48+
# Make a dictionary of ellipsoid properties, to be added to both a test
49+
# ellipsoid and the grid-mapping cf_var.
4050
ellipsoid_kwargs = {'semi_major_axis': 6377563.396}
4151
if inverse_flattening:
4252
ellipsoid_kwargs['inverse_flattening'] = 299.3249646
4353
else:
4454
ellipsoid_kwargs['semi_minor_axis'] = 6356256.909
45-
cf_grid_var_kwargs.update(ellipsoid_kwargs)
46-
47-
cf_grid_var = mock.Mock(**cf_grid_var_kwargs)
48-
ellipsoid = iris.coord_systems.GeogCS(**ellipsoid_kwargs)
4955

56+
cf_grid_var_kwargs = non_ellipsoid_kwargs.copy()
57+
cf_grid_var_kwargs.update(ellipsoid_kwargs)
58+
cf_grid_var = mock.Mock(spec=[], **cf_grid_var_kwargs)
5059
cs = build_geostationary_coordinate_system(None, cf_grid_var)
51-
expected = Geostationary(
52-
latitude_of_projection_origin=cf_grid_var.
53-
latitude_of_projection_origin,
54-
longitude_of_projection_origin=cf_grid_var.
55-
longitude_of_projection_origin,
56-
perspective_point_height=cf_grid_var.perspective_point_height,
57-
sweep_angle_axis=cf_grid_var.sweep_angle_axis,
58-
false_easting=cf_grid_var.false_easting,
59-
false_northing=cf_grid_var.false_northing,
60-
ellipsoid=ellipsoid)
61-
60+
ellipsoid = iris.coord_systems.GeogCS(**ellipsoid_kwargs)
61+
expected = Geostationary(ellipsoid=ellipsoid, **non_ellipsoid_kwargs)
6262
self.assertEqual(cs, expected)
6363

6464
def test_valid(self):
6565
self._test(inverse_flattening=False)
6666

6767
def test_inverse_flattening(self):
6868
self._test(inverse_flattening=True)
69+
70+
def test_false_offsets_missing(self):
71+
self._test(remove_props=['false_easting', 'false_northing'])
72+
73+
def test_false_offsets_none(self):
74+
self._test(replace_props={'false_easting':None, 'false_northing':None})
75+
76+
77+
if __name__ == "__main__":
78+
tests.main()

0 commit comments

Comments
 (0)