Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dbf8eb8
Separated VerticalPerspective into itself plus a Geostationary coord …
trexfeathers Sep 12, 2019
44c2fe1
Added docstring to Geostationary coord system.
trexfeathers Sep 13, 2019
a384b1a
Separated test_VerticalPerspective into itself plus a test_Geostation…
trexfeathers Sep 13, 2019
5a4313d
Functionality to pick up Vertical Perspective and Geostationary from …
trexfeathers Sep 16, 2019
0bd0d2a
Added Vertical Perspective and Geostationary tests to test_Saver.
trexfeathers Sep 16, 2019
c7c555f
Added Vertical Perspective and Geostationary to _create_cf_grid_mapping.
trexfeathers Sep 16, 2019
7e725f4
Created tests for coordinate system build in pyke rules for Vertical …
trexfeathers Sep 18, 2019
7d60b78
Renamed sweep_axis to sweep_angle_axis for clarity
trexfeathers Sep 18, 2019
7860bae
Licence header years and code style
trexfeathers Sep 18, 2019
3b23a73
Added Geostationary correction to convert coordinates before plotting
trexfeathers Sep 19, 2019
1826547
Refactored Geostationary conversion into function, wrote tests for it
trexfeathers Sep 19, 2019
154dc83
Removed unnecessary float tests.
trexfeathers Sep 20, 2019
7ef0537
Fixed mock import.
trexfeathers Sep 20, 2019
54e5d65
Adjusted expected result file now that iris is correctly handling Ver…
trexfeathers Sep 20, 2019
3daad66
Stickler unused variables
trexfeathers Sep 20, 2019
9eb0e63
What's New
trexfeathers Sep 20, 2019
17436b8
Grammar fixes.
trexfeathers Sep 27, 2019
bbd7d14
Split What's New into new feature and bugfix
trexfeathers Sep 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* :class:`iris.coord_systems.VerticalPerspective` coordinate system now uses
the `CF Vertical perspective definition <http://cfconventions
.org/cf-conventions/cf-conventions.html#vertical-perspective>`_; had been
erroneously using Geostationary.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* New coordinate system: :class:`iris.coord_systems.Geostationary`, based on
the `CF Geostationary projection definition <http://cfconventions
.org/cf-conventions/cf-conventions.html#_geostationary_projection>`_.
* :class:`iris.coord_systems.VerticalPerspective` can now be saved to and
loaded from NetCDF files.
178 changes: 129 additions & 49 deletions lib/iris/coord_systems.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2010 - 2018, Met Office
# (C) British Crown Copyright 2010 - 2019, Met Office
#
# This file is part of Iris.
#
Expand Down Expand Up @@ -76,6 +76,15 @@ def xml_element(self, doc, attrs=None):

return coord_system_xml_element

@staticmethod
def _ellipsoid_to_globe(ellipsoid, globe_default):
if ellipsoid is not None:
globe = ellipsoid.as_cartopy_globe()
else:
globe = globe_default

return globe

@abstractmethod
def as_cartopy_crs(self):
"""
Expand Down Expand Up @@ -338,9 +347,8 @@ def xml_element(self, doc):
return CoordSystem.xml_element(self, doc, self._pretty_attrs())

def _ccrs_kwargs(self):
globe = None
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, None)

# Cartopy v0.12 provided the new arg north_pole_grid_longitude
cartopy_kwargs = {'pole_longitude': self.grid_north_pole_longitude,
'pole_latitude': self.grid_north_pole_latitude,
Expand Down Expand Up @@ -441,10 +449,7 @@ def __repr__(self):
self.ellipsoid)

def as_cartopy_crs(self):
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = None
globe = self._ellipsoid_to_globe(self.ellipsoid, None)

return ccrs.TransverseMercator(
central_longitude=self.longitude_of_central_meridian,
Expand Down Expand Up @@ -534,10 +539,7 @@ def __repr__(self):
self.ellipsoid)

def as_cartopy_crs(self):
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = ccrs.Globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

warnings.warn('Discarding false_easting and false_northing that are '
'not used by Cartopy.')
Expand All @@ -553,7 +555,7 @@ def as_cartopy_projection(self):

class VerticalPerspective(CoordSystem):
"""
An geostationary satellite image map projection.
A vertical/near-side perspective satellite image map projection.

"""

Expand All @@ -563,7 +565,7 @@ def __init__(self, latitude_of_projection_origin,
longitude_of_projection_origin, perspective_point_height,
false_easting=0, false_northing=0, ellipsoid=None):
"""
Constructs an Vertical Perspective Geostationary coord system.
Constructs a Vertical Perspective coord system.

Args:

Expand Down Expand Up @@ -592,34 +594,28 @@ def __init__(self, latitude_of_projection_origin,
#: True latitude of planar origin in degrees.
self.latitude_of_projection_origin = float(
latitude_of_projection_origin)
if self.latitude_of_projection_origin != 0.0:
raise ValueError('Non-zero latitude of projection currently not'
' supported by Cartopy.')

#: True longitude of planar origin in degrees.
self.longitude_of_projection_origin = float(
longitude_of_projection_origin)

#: Altitude of satellite in metres.
# test if perspective_point_height may be cast to float for proj.4
test_pph = float(perspective_point_height)
self.perspective_point_height = perspective_point_height
self.perspective_point_height = float(perspective_point_height)

#: X offset from planar origin in metres.
test_fe = float(false_easting)
self.false_easting = false_easting
self.false_easting = float(false_easting)

#: Y offset from planar origin in metres.
test_fn = float(false_northing)
self.false_northing = false_northing
self.false_northing = float(false_northing)

#: Ellipsoid definition.
self.ellipsoid = ellipsoid

def __repr__(self):
return "Vertical Perspective(latitude_of_projection_origin={!r}, "\
"longitude_of_projection_origin={!r}, "\
"perspective_point_height = {!r}, "\
"perspective_point_height={!r}, "\
"false_easting={!r}, false_northing={!r}, "\
"ellipsoid={!r})".format(self.latitude_of_projection_origin,
self.longitude_of_projection_origin,
Expand All @@ -629,12 +625,10 @@ def __repr__(self):
self.ellipsoid)

def as_cartopy_crs(self):
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = ccrs.Globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

return ccrs.Geostationary(
return ccrs.NearsidePerspective(
central_latitude=self.latitude_of_projection_origin,
central_longitude=self.longitude_of_projection_origin,
satellite_height=self.perspective_point_height,
false_easting=self.false_easting,
Expand All @@ -645,6 +639,104 @@ def as_cartopy_projection(self):
return self.as_cartopy_crs()


class Geostationary(CoordSystem):
"""
A geostationary satellite image map projection.

"""

grid_mapping_name = 'geostationary'

def __init__(self, latitude_of_projection_origin,
longitude_of_projection_origin,
perspective_point_height, sweep_angle_axis, false_easting=0,
false_northing=0, ellipsoid=None):

"""
Constructs a Geostationary coord system.

Args:

* latitude_of_projection_origin (float):
True latitude of planar origin in degrees.

* longitude_of_projection_origin (float):
True longitude of planar origin in degrees.

* perspective_point_height (float):
Altitude of satellite in metres above the surface of the ellipsoid.

* sweep_angle_axis (string):
The axis along which the satellite instrument sweeps - 'x' or 'y'.

Kwargs:

* false_easting (float):
X offset from planar origin in metres. Defaults to 0.

* false_northing (float):
Y offset from planar origin in metres. Defaults to 0.

* ellipsoid (iris.coord_systems.GeogCS):
:class:`GeogCS` defining the ellipsoid.

"""
#: True latitude of planar origin in degrees.
self.latitude_of_projection_origin = float(
latitude_of_projection_origin)
if self.latitude_of_projection_origin != 0.0:
raise ValueError('Non-zero latitude of projection currently not'
' supported by Cartopy.')

#: True longitude of planar origin in degrees.
self.longitude_of_projection_origin = float(
longitude_of_projection_origin)

#: Altitude of satellite in metres.
# test if perspective_point_height may be cast to float for proj.4
self.perspective_point_height = float(perspective_point_height)

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

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

#: The axis along which the satellite instrument sweeps - 'x' or 'y'.
self.sweep_angle_axis = sweep_angle_axis
if self.sweep_angle_axis not in ('x', 'y'):
raise ValueError('Invalid sweep_angle_axis - must be "x" or "y"')

#: Ellipsoid definition.
self.ellipsoid = ellipsoid

def __repr__(self):
return "Geostationary(latitude_of_projection_origin={!r}, " \
"longitude_of_projection_origin={!r}, " \
"perspective_point_height={!r}, false_easting={!r}, " \
"false_northing={!r}, sweep_angle_axis={!r}, " \
"ellipsoid={!r}".format(self.latitude_of_projection_origin,
self.longitude_of_projection_origin,
self.perspective_point_height,
self.false_easting,
self.false_northing,
self.sweep_angle_axis, self.ellipsoid)

def as_cartopy_crs(self):
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

return ccrs.Geostationary(
central_longitude=self.longitude_of_projection_origin,
satellite_height=self.perspective_point_height,
false_easting=self.false_easting,
false_northing=self.false_northing,
globe=globe,
sweep_axis=self.sweep_angle_axis)

def as_cartopy_projection(self):
return self.as_cartopy_crs()


class Stereographic(CoordSystem):
"""
A stereographic map projection.
Expand Down Expand Up @@ -712,10 +804,8 @@ def __repr__(self):
self.ellipsoid)

def as_cartopy_crs(self):
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = ccrs.Globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

return ccrs.Stereographic(
self.central_lat, self.central_lon,
self.false_easting, self.false_northing,
Expand Down Expand Up @@ -807,10 +897,7 @@ def as_cartopy_crs(self):
else:
cutoff = None

if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = ccrs.Globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

# Cartopy v0.12 deprecated the use of secant_latitudes.
if cartopy.__version__ < '0.12':
Expand Down Expand Up @@ -866,10 +953,7 @@ def __repr__(self):
return res.format(self=self)

def as_cartopy_crs(self):
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = ccrs.Globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

return ccrs.Mercator(
central_longitude=self.longitude_of_projection_origin,
Expand Down Expand Up @@ -935,10 +1019,8 @@ def __repr__(self):
self.ellipsoid)

def as_cartopy_crs(self):
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = ccrs.Globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

return ccrs.LambertAzimuthalEqualArea(
central_longitude=self.longitude_of_projection_origin,
central_latitude=self.latitude_of_projection_origin,
Expand Down Expand Up @@ -1015,10 +1097,8 @@ def __repr__(self):
self.ellipsoid)

def as_cartopy_crs(self):
if self.ellipsoid is not None:
globe = self.ellipsoid.as_cartopy_globe()
else:
globe = ccrs.Globe()
globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe())

return ccrs.AlbersEqualArea(
central_longitude=self.longitude_of_central_meridian,
central_latitude=self.latitude_of_projection_origin,
Expand Down
Loading