From 1f3eacac4ef2b9cec8e19d53349ba2fc57261c51 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Fri, 4 Feb 2022 18:41:39 -0700 Subject: [PATCH 01/14] Add North Atlantic and North Pacific comparison grids To make this easier, I've done a bunch of clean up of the code for defining projections and comparison grid descriptors. This should also make it easier to add new projection comparison grids in the future. --- mpas_analysis/shared/climatology/__init__.py | 3 +- .../climatology/comparison_descriptors.py | 228 ++++++++++++------ 2 files changed, 151 insertions(+), 80 deletions(-) diff --git a/mpas_analysis/shared/climatology/__init__.py b/mpas_analysis/shared/climatology/__init__.py index 0f9c73d17..81c254f10 100644 --- a/mpas_analysis/shared/climatology/__init__.py +++ b/mpas_analysis/shared/climatology/__init__.py @@ -17,4 +17,5 @@ import RemapObservedClimatologySubtask from mpas_analysis.shared.climatology.comparison_descriptors import \ get_comparison_descriptor, get_antarctic_stereographic_projection, \ - get_arctic_stereographic_projection + get_arctic_stereographic_projection, get_north_atlantic_projection, \ + get_north_pacific_projection diff --git a/mpas_analysis/shared/climatology/comparison_descriptors.py b/mpas_analysis/shared/climatology/comparison_descriptors.py index 01341da9a..e3428ea70 100644 --- a/mpas_analysis/shared/climatology/comparison_descriptors.py +++ b/mpas_analysis/shared/climatology/comparison_descriptors.py @@ -26,40 +26,84 @@ from pyremap import LatLonGridDescriptor, ProjectionGridDescriptor -def get_comparison_descriptor(config, comparisonGridName): # {{{ +known_comparison_grids = ['latlon', 'antarctic', 'arctic', 'north_atlantic', + 'north_pacific'] + + +def get_comparison_descriptor(config, comparison_grid_name): # {{{ """ - Get the comparison grid descriptor from the comparisonGridName. + Get the comparison grid descriptor from the comparison_grid_name. Parameters ---------- - config : MpasAnalysisConfigParser object + config : mpas_analysis.configuration.MpasAnalysisConfigParser Contains configuration options - comparisonGridName : {'latlon', 'antarctic', 'arctic'} + comparison_grid_name : {'latlon', 'antarctic', 'arctic', 'north_atlantic', + 'north_pacific'} The name of the comparison grid to use for remapping. Raises ------ ValueError - If comparisonGridName does not describe a known comparions grid + If comparison_grid_name does not describe a known comparison grid """ # Authors # ------- # Xylar Asay-Davis, Milena Veneziani - if comparisonGridName == 'latlon': - comparisonDescriptor = \ + if comparison_grid_name not in known_comparison_grids: + raise ValueError( + f'Unknown comparison grid type {comparison_grid_name}') + + if comparison_grid_name == 'latlon': + comparison_descriptor = \ _get_lat_lon_comparison_descriptor(config) - elif comparisonGridName == 'antarctic': - comparisonDescriptor = \ - _get_antarctic_stereographic_comparison_descriptor(config) - elif comparisonGridName == 'arctic': - comparisonDescriptor = \ - _get_arctic_stereographic_comparison_descriptor(config) else: - raise ValueError('Unknown comaprison grid type {}'.format( - comparisonGridName)) - return comparisonDescriptor # }}} + comparison_descriptor = \ + _get_projection_comparison_descriptor(config, comparison_grid_name) + + return comparison_descriptor # }}} + + +def get_projection(comparison_grid_name): # {{{ + """ + Get the projection from the comparison_grid_name. + + Parameters + ---------- + comparison_grid_name : {'antarctic', 'arctic', 'north_atlantic', + 'north_pacific'} + The name of the comparison grid to use for remapping. + + Raises + ------ + ValueError + If comparison_grid_name does not describe a known comparison grid + """ + # Authors + # ------- + # Xylar Asay-Davis + + if comparison_grid_name not in known_comparison_grids: + raise ValueError( + f'Unknown comparison grid type {comparison_grid_name}') + + if comparison_grid_name == 'latlon': + raise ValueError('latlon is not a projection grid.') + elif comparison_grid_name == 'antarctic': + projection = get_antarctic_stereographic_projection() + elif comparison_grid_name == 'arctic': + projection = get_arctic_stereographic_projection() + elif comparison_grid_name == 'north_atlantic': + projection = get_north_atlantic_projection() + elif comparison_grid_name == 'north_atlantic': + projection = get_north_atlantic_projection() + else: + raise ValueError(f'We missed one of the known comparison grids: ' + f'{comparison_grid_name}') + + return projection # }}} def get_antarctic_stereographic_projection(): # {{{ @@ -68,7 +112,7 @@ def get_antarctic_stereographic_projection(): # {{{ Returns ------- - projection : ``pyproj.Proj`` object + projection : pyproj.Proj The projection """ # Authors @@ -87,7 +131,7 @@ def get_arctic_stereographic_projection(): # {{{ Returns ------- - projection : ``pyproj.Proj`` object + projection : pyproj.Proj The projection """ # Authors @@ -100,119 +144,145 @@ def get_arctic_stereographic_projection(): # {{{ return projection # }}} -def _get_lat_lon_comparison_descriptor(config): # {{{ +def get_north_atlantic_projection(): # {{{ """ - Get a descriptor of the lat/lon comparison grid, used for remapping and - determining the grid name - - Parameters - ---------- - config : instance of ``MpasAnalysisConfigParser`` - Contains configuration options + Get a projection for a North Atlantic Lambert Conformal Conic (LCC) + comparison grid Returns ------- - descriptor : ``LatLonGridDescriptor`` object - A descriptor of the lat/lon grid + projection : pyproj.Proj + The projection """ # Authors # ------- # Xylar Asay-Davis + # Yohei Takano - climSection = 'climatology' + projection = pyproj.Proj('+proj=lcc +lon_0=-45 +lat_1=20 +lat_2=75 ' + '+x_0=0.0 +y_0=0.0 +ellps=WGS84') - comparisonLatRes = config.getWithDefault(climSection, - 'comparisonLatResolution', - constants.dLatitude) - comparisonLonRes = config.getWithDefault(climSection, - 'comparisonLatResolution', - constants.dLongitude) + return projection # }}} - nLat = int((constants.latmax - constants.latmin) / comparisonLatRes) + 1 - nLon = int((constants.lonmax - constants.lonmin) / comparisonLonRes) + 1 - lat = numpy.linspace(constants.latmin, constants.latmax, nLat) - lon = numpy.linspace(constants.lonmin, constants.lonmax, nLon) - descriptor = LatLonGridDescriptor.create(lat, lon, units='degrees') +def get_north_pacific_projection(): # {{{ + """ + Get a projection for a North Pacific Lambert Conformal Conic (LCC) + comparison grid - return descriptor # }}} + Returns + ------- + projection : pyproj.Proj + The projection + """ + # Authors + # ------- + # Xylar Asay-Davis + # Yohei Takano + + projection = pyproj.Proj('+proj=lcc +lon_0=180 +lat_1=20 +lat_2=60 ' + '+x_0=0.0 +y_0=0.0 +ellps=WGS84') + + return projection # }}} -def _get_antarctic_stereographic_comparison_descriptor(config): # {{{ +def _get_lat_lon_comparison_descriptor(config): # {{{ """ - Get a descriptor of an Antarctic stereographic comparison grid, used for - remapping and determining the grid name + Get a descriptor of the lat/lon comparison grid, used for remapping and + determining the grid name Parameters ---------- - config : instance of ``MpasAnalysisConfigParser`` + config : mpas_analysis.configuration.MpasAnalysisConfigParser Contains configuration options Returns ------- - descriptor : ``ProjectionGridDescriptor`` object - A descriptor of the Antarctic comparison grid + descriptor : LatLonGridDescriptor + A descriptor of the lat/lon grid """ # Authors # ------- # Xylar Asay-Davis - climSection = 'climatology' - - comparisonStereoWidth = config.getfloat(climSection, - 'comparisonAntarcticStereoWidth') - comparisonStereoResolution = config.getfloat( - climSection, 'comparisonAntarcticStereoResolution') + section = 'climatology' - projection = get_antarctic_stereographic_projection() + lat_res = config.getWithDefault(section, 'comparisonLatResolution', + constants.dLatitude) + lon_res = config.getWithDefault(section, 'comparisonLatResolution', + constants.dLongitude) - xMax = 0.5 * comparisonStereoWidth * 1e3 - nx = int(comparisonStereoWidth / comparisonStereoResolution) + 1 - x = numpy.linspace(-xMax, xMax, nx) + nlat = int((constants.latmax - constants.latmin) / lat_res) + 1 + nlon = int((constants.lonmax - constants.lonmin) / lon_res) + 1 + lat = numpy.linspace(constants.latmin, constants.latmax, nlat) + lon = numpy.linspace(constants.lonmin, constants.lonmax, nlon) - meshName = '{}x{}km_{}km_Antarctic_stereo'.format( - comparisonStereoWidth, comparisonStereoWidth, - comparisonStereoResolution) - descriptor = ProjectionGridDescriptor.create(projection, x, x, meshName) + descriptor = LatLonGridDescriptor.create(lat, lon, units='degrees') return descriptor # }}} -def _get_arctic_stereographic_comparison_descriptor(config): # {{{ +def _get_projection_comparison_descriptor(config, comparison_grid_name): # {{{ """ - Get a descriptor of an Arctic stereographic comparison grid, used for + Get a descriptor of any comparison grid base on a projection, used for remapping and determining the grid name Parameters ---------- - config : instance of ``MpasAnalysisConfigParser`` + config : mpas_analysis.configuration.MpasAnalysisConfigParser Contains configuration options + comparison_grid_name : str + One of the projections + Returns ------- - descriptor : ``ProjectionGridDescriptor`` object + descriptor : pyremap.ProjectionGridDescriptor A descriptor of the Arctic comparison grid """ # Authors # ------- - # Milena Veneziani + # Xylar Asay-Davis + + section = 'climatology' + + option_suffixes = {'antarctic': 'AntarcticStereo', + 'arctic': 'ArcticStereo', + 'north_atlantic': 'NorthAtlantic', + 'north_pacific': 'NorthPacific'} - climSection = 'climatology' + grid_suffixes = {'antarctic': 'Antarctic_stereo', + 'arctic': 'Arctic_stereo', + 'north_atlantic': 'North_Atlantic', + 'north_pacific': 'North_Pacific'} - comparisonStereoWidth = config.getfloat(climSection, - 'comparisonArcticStereoWidth') - comparisonStereoResolution = config.getfloat( - climSection, 'comparisonArcticStereoResolution') + if comparison_grid_name not in option_suffixes: + raise ValueError(f'{comparison_grid_name} is not one of the supported ' + f'projection grids') + + projection = get_projection(comparison_grid_name) + + option_suffix = option_suffixes[comparison_grid_name] + grid_suffix = grid_suffixes[comparison_grid_name] + width = config.getfloat( + section, f'comparison{option_suffix}Width') + option = f'comparison{option_suffix}Height' + if config.has_option(section, option): + height = config.getfloat(section, option) + else: + height = width + res = config.getfloat( + section, f'comparison{option_suffix}Resolution') - projection = get_arctic_stereographic_projection() + xmax = 0.5 * width * 1e3 + nx = int(width / res) + 1 + x = numpy.linspace(-xmax, xmax, nx) - xMax = 0.5 * comparisonStereoWidth * 1e3 - nx = int(comparisonStereoWidth / comparisonStereoResolution) + 1 - x = numpy.linspace(-xMax, xMax, nx) + ymax = 0.5 * height * 1e3 + ny = int(height / res) + 1 + y = numpy.linspace(-ymax, ymax, ny) - meshName = '{}x{}km_{}km_Arctic_stereo'.format( - comparisonStereoWidth, comparisonStereoWidth, - comparisonStereoResolution) - descriptor = ProjectionGridDescriptor.create(projection, x, x, meshName) + mesh_name = f'{width}x{height}km_{res}km_{grid_suffix}' + descriptor = ProjectionGridDescriptor.create(projection, x, y, mesh_name) return descriptor # }}} From 0264a6f29d7aa9996ce36fcc3320471830898a73 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Fri, 4 Feb 2022 19:07:28 -0700 Subject: [PATCH 02/14] Update docstrings and checks with new comparison grids --- mpas_analysis/ocean/plot_climatology_map_subtask.py | 4 ++-- mpas_analysis/shared/climatology/climatology.py | 9 ++++----- .../shared/climatology/comparison_descriptors.py | 2 +- .../climatology/remap_mpas_climatology_subtask.py | 12 ++++++------ .../remap_observed_climatology_subtask.py | 8 ++++---- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/mpas_analysis/ocean/plot_climatology_map_subtask.py b/mpas_analysis/ocean/plot_climatology_map_subtask.py index d22564ca8..1d3ddcd6b 100644 --- a/mpas_analysis/ocean/plot_climatology_map_subtask.py +++ b/mpas_analysis/ocean/plot_climatology_map_subtask.py @@ -48,7 +48,7 @@ class PlotClimatologyMapSubtask(AnalysisTask): # {{{ A season (key in ``shared.constants.monthDictionary``) to be plotted. - comparisonGridName : {'latlon', 'antarctic', 'arctic'} + comparisonGridName : str The name of the comparison grid to plot. remapMpasClimatologySubtask : ``RemapMpasClimatologySubtask`` @@ -136,7 +136,7 @@ def __init__(self, parentTask, season, comparisonGridName, A season (key in ``shared.constants.monthDictionary``) to be plotted. - comparisonGridName : {'latlon', 'antarctic', 'arctic'} + comparisonGridName : str The name of the comparison grid to plot. remapMpasClimatologySubtask : ``RemapMpasClimatologySubtask`` diff --git a/mpas_analysis/shared/climatology/climatology.py b/mpas_analysis/shared/climatology/climatology.py index d3d25aa24..2eef6c6ea 100644 --- a/mpas_analysis/shared/climatology/climatology.py +++ b/mpas_analysis/shared/climatology/climatology.py @@ -34,7 +34,7 @@ from mpas_analysis.shared.io import write_netcdf from mpas_analysis.shared.climatology.comparison_descriptors import \ - get_comparison_descriptor + get_comparison_descriptor, known_comparison_grids def get_remapper(config, sourceDescriptor, comparisonDescriptor, @@ -554,9 +554,8 @@ def get_remapped_mpas_climatology_file_name(config, season, componentName, comparisonGridName : str The name of the comparison grid to use for remapping. If it is one - of the default comparison grid names ``{'latlon', 'antarctic', - 'arctic'}``, the full grid name is looked up via - get_comparison_descriptor + of the known comparison grid names, the full grid name is looked up via + :py:fun:`mpas_analysis.shared.climatology.get_comparison_descriptor()` op : {'avg', 'min', 'max'} operator for monthly stats @@ -580,7 +579,7 @@ def get_remapped_mpas_climatology_file_name(config, season, componentName, climatologyOpDirectory = get_climatology_op_directory(config, op) - if comparisonGridName in ['latlon', 'antarctic', 'arctic']: + if comparisonGridName in known_comparison_grids: comparisonDescriptor = get_comparison_descriptor(config, comparisonGridName) comparisonFullMeshName = comparisonDescriptor.meshName diff --git a/mpas_analysis/shared/climatology/comparison_descriptors.py b/mpas_analysis/shared/climatology/comparison_descriptors.py index e3428ea70..7247671b4 100644 --- a/mpas_analysis/shared/climatology/comparison_descriptors.py +++ b/mpas_analysis/shared/climatology/comparison_descriptors.py @@ -108,7 +108,7 @@ def get_projection(comparison_grid_name): # {{{ def get_antarctic_stereographic_projection(): # {{{ """ - Get a projection for an Antarctic steregraphic comparison grid + Get a projection for an Antarctic stereographic comparison grid Returns ------- diff --git a/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py b/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py index d2236ea37..a793abdf2 100644 --- a/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py +++ b/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py @@ -113,7 +113,7 @@ def __init__(self, mpasClimatologyTask, parentTask, climatologyName, to be computed or ['none'] (not ``None``) if only monthly climatologies are needed. - comparisonGridNames : list of {'latlon', 'antarctic', 'arctic'}, + comparisonGridNames : list of str optional The name(s) of the comparison grid to use for remapping. If none is supplied, `add_comparison_descriptor()` must be called to add @@ -268,8 +268,8 @@ def run_task(self): # {{{ def add_comparison_grid_descriptor(self, comparisonGridName, comparisonDescriptor): # {{{ ''' - Add a custom grid descriptor (something other than 'latlon' or - 'antarctic' or 'arctic'). + Add a custom grid descriptor (something other than 'latlon', + 'antarctic', 'arctic', 'north_atlantic', or 'north_pacific'). Parameters ---------- @@ -323,7 +323,7 @@ def get_remapped_file_name(self, season, comparisonGridName): # {{{ season : str One of the seasons in ``constants.monthDictionary`` - comparisonGridName : {'latlon', 'antarctic', 'arctic'} + comparisonGridName : str The name of the comparison grid to use for remapping. Returns @@ -377,7 +377,7 @@ def customize_remapped_climatology(self, climatology, comparisonGridNames, climatology : ``xarray.Dataset``` The MPAS climatology data set that has been remapped - comparisonGridNames : {'latlon', 'antarctic', 'arctic'} + comparisonGridNames : str The name of the comparison grid to use for remapping. season : str @@ -571,7 +571,7 @@ def _remap(self, inFileName, outFileName, remapper, comparisonGridName, A remapper that can be used to remap files or data sets to a comparison grid. - comparisonGridNames : {'latlon', 'antarctic', 'arctic'} + comparisonGridNames : str The name of the comparison grid to use for remapping. season : str diff --git a/mpas_analysis/shared/climatology/remap_observed_climatology_subtask.py b/mpas_analysis/shared/climatology/remap_observed_climatology_subtask.py index ad5d8319e..abe28f820 100644 --- a/mpas_analysis/shared/climatology/remap_observed_climatology_subtask.py +++ b/mpas_analysis/shared/climatology/remap_observed_climatology_subtask.py @@ -47,7 +47,7 @@ class RemapObservedClimatologySubtask(AnalysisTask): # {{{ The prefix in front of output files and mapping files, typically the name of the field being remapped - comparisonGridNames : list of {'latlon', 'antarctic', 'arctic'} + comparisonGridNames : list of str The name(s) of the comparison grid to use for remapping. """ # Authors @@ -78,7 +78,7 @@ def __init__(self, parentTask, seasons, fileName, outFilePrefix, The prefix in front of output files and mapping files, typically the name of the field being remapped - comparisonGridNames : list of {'latlon', 'antarctic', 'arctic'}, + comparisonGridNames : list of str optional The name(s) of the comparison grid to use for remapping. @@ -254,7 +254,7 @@ def get_file_name(self, stage, season=None, comparisonGridName=None): season : str, optional One of the seasons in ``constants.monthDictionary`` - comparisonGridName : {'latlon', 'antarctic', 'arctic'}, optional + comparisonGridName : str, optional The name of the comparison grid to use for remapping. Returns @@ -337,7 +337,7 @@ def _setup_remappers(self, fileName): # {{{ for comparisonGridName in self.comparisonGridNames: comparisonDescriptor = get_comparison_descriptor( - config, comparisonGridName=comparisonGridName) + config, comparison_grid_name=comparisonGridName) self.remappers[comparisonGridName] = get_remapper( config=config, From e94abbb78a78610c1828cbe6e4f53091f39a4b4a Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 5 Feb 2022 10:57:34 -0700 Subject: [PATCH 03/14] Clean up docstrings in climatology map plotting --- mpas_analysis/shared/plot/climatology_map.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mpas_analysis/shared/plot/climatology_map.py b/mpas_analysis/shared/plot/climatology_map.py index de88e25d3..1a049efd6 100644 --- a/mpas_analysis/shared/plot/climatology_map.py +++ b/mpas_analysis/shared/plot/climatology_map.py @@ -62,14 +62,14 @@ def plot_polar_comparison( Parameters ---------- - config : instance of ConfigParser + config : mpas_analysis.configuration.MpasAnalysisConfigParser the configuration, containing a [plot] section with options that control plotting - Lons, Lats : float arrays + Lons, Lats : numpy.ndarray longitude and latitude arrays - modelArray, refArray : float arrays + modelArray, refArray : numpy.ndarray model and observational or control run data sets diffArray : float array @@ -84,7 +84,7 @@ def plot_polar_comparison( title : str, optional the subtitle of the plot - plotProjection : 'npstere' or 'spstere', optional + plotProjection : {'npstere', 'spstere'}, optional projection for the plot (north or south pole) modelTitle : str, optional @@ -273,14 +273,14 @@ def plot_global_comparison( Parameters ---------- - config : instance of ConfigParser + config : mpas_analysis.configuration.MpasAnalysisConfigParser the configuration, containing a [plot] section with options that control plotting - Lons, Lats : float arrays + Lons, Lats : numpy.ndarray longitude and latitude arrays - modelArray, refArray : float arrays + modelArray, refArray : numpy.ndarray model and observational or control run data sets diffArray : float array @@ -459,21 +459,21 @@ def plot_polar_projection_comparison( hemisphere='north', maxTitleLength=55): """ - Plots a data set as a longitude/latitude map. + Plots a data set as a projection map. Parameters ---------- - config : instance of ConfigParser + config : mpas_analysis.configuration.MpasAnalysisConfigParser the configuration, containing a [plot] section with options that control plotting - x, y : numpy ndarrays + x, y : numpy.ndarrays 1D x and y arrays defining the projection grid - landMask : numpy ndarrays + landMask : numpy.ndarrays model and observational or control run data sets - modelArray, refArray : numpy ndarrays + modelArray, refArray : numpy.ndarrays model and observational or control run data sets diffArray : float array From d70166655933c1ee860a3265810bb39fe959fd14 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 5 Feb 2022 12:16:43 -0700 Subject: [PATCH 04/14] Move projection to its own module, add cartopy --- mpas_analysis/shared/climatology/__init__.py | 4 +- .../climatology/comparison_descriptors.py | 141 ++---------------- mpas_analysis/shared/projection/__init__.py | 126 ++++++++++++++++ 3 files changed, 136 insertions(+), 135 deletions(-) create mode 100644 mpas_analysis/shared/projection/__init__.py diff --git a/mpas_analysis/shared/climatology/__init__.py b/mpas_analysis/shared/climatology/__init__.py index 81c254f10..37c313086 100644 --- a/mpas_analysis/shared/climatology/__init__.py +++ b/mpas_analysis/shared/climatology/__init__.py @@ -16,6 +16,4 @@ from mpas_analysis.shared.climatology.remap_observed_climatology_subtask \ import RemapObservedClimatologySubtask from mpas_analysis.shared.climatology.comparison_descriptors import \ - get_comparison_descriptor, get_antarctic_stereographic_projection, \ - get_arctic_stereographic_projection, get_north_atlantic_projection, \ - get_north_pacific_projection + get_comparison_descriptor diff --git a/mpas_analysis/shared/climatology/comparison_descriptors.py b/mpas_analysis/shared/climatology/comparison_descriptors.py index 7247671b4..6ab30d4b2 100644 --- a/mpas_analysis/shared/climatology/comparison_descriptors.py +++ b/mpas_analysis/shared/climatology/comparison_descriptors.py @@ -19,18 +19,15 @@ unicode_literals import numpy -import pyproj from mpas_analysis.shared.constants import constants +from mpas_analysis.shared.projection import known_comparison_grids, \ + get_pyproj_projection from pyremap import LatLonGridDescriptor, ProjectionGridDescriptor -known_comparison_grids = ['latlon', 'antarctic', 'arctic', 'north_atlantic', - 'north_pacific'] - - -def get_comparison_descriptor(config, comparison_grid_name): # {{{ +def get_comparison_descriptor(config, comparison_grid_name): """ Get the comparison grid descriptor from the comparison_grid_name. @@ -63,130 +60,10 @@ def get_comparison_descriptor(config, comparison_grid_name): # {{{ comparison_descriptor = \ _get_projection_comparison_descriptor(config, comparison_grid_name) - return comparison_descriptor # }}} - - -def get_projection(comparison_grid_name): # {{{ - """ - Get the projection from the comparison_grid_name. - - Parameters - ---------- - comparison_grid_name : {'antarctic', 'arctic', 'north_atlantic', - 'north_pacific'} - The name of the comparison grid to use for remapping. - - Raises - ------ - ValueError - If comparison_grid_name does not describe a known comparison grid - """ - # Authors - # ------- - # Xylar Asay-Davis - - if comparison_grid_name not in known_comparison_grids: - raise ValueError( - f'Unknown comparison grid type {comparison_grid_name}') - - if comparison_grid_name == 'latlon': - raise ValueError('latlon is not a projection grid.') - elif comparison_grid_name == 'antarctic': - projection = get_antarctic_stereographic_projection() - elif comparison_grid_name == 'arctic': - projection = get_arctic_stereographic_projection() - elif comparison_grid_name == 'north_atlantic': - projection = get_north_atlantic_projection() - elif comparison_grid_name == 'north_atlantic': - projection = get_north_atlantic_projection() - else: - raise ValueError(f'We missed one of the known comparison grids: ' - f'{comparison_grid_name}') - - return projection # }}} - - -def get_antarctic_stereographic_projection(): # {{{ - """ - Get a projection for an Antarctic stereographic comparison grid - - Returns - ------- - projection : pyproj.Proj - The projection - """ - # Authors - # ------- - # Xylar Asay-Davis - - projection = pyproj.Proj('+proj=stere +lat_ts=-71.0 +lat_0=-90 +lon_0=0.0 ' - '+k_0=1.0 +x_0=0.0 +y_0=0.0 +ellps=WGS84') - - return projection # }}} - - -def get_arctic_stereographic_projection(): # {{{ - """ - Get a projection for an Arctic stereographic comparison grid - - Returns - ------- - projection : pyproj.Proj - The projection - """ - # Authors - # ------- - # Milena Veneziani - - projection = pyproj.Proj('+proj=stere +lat_ts=75.0 +lat_0=90 +lon_0=0.0 ' - '+k_0=1.0 +x_0=0.0 +y_0=0.0 +ellps=WGS84') - - return projection # }}} - - -def get_north_atlantic_projection(): # {{{ - """ - Get a projection for a North Atlantic Lambert Conformal Conic (LCC) - comparison grid - - Returns - ------- - projection : pyproj.Proj - The projection - """ - # Authors - # ------- - # Xylar Asay-Davis - # Yohei Takano - - projection = pyproj.Proj('+proj=lcc +lon_0=-45 +lat_1=20 +lat_2=75 ' - '+x_0=0.0 +y_0=0.0 +ellps=WGS84') - - return projection # }}} - - -def get_north_pacific_projection(): # {{{ - """ - Get a projection for a North Pacific Lambert Conformal Conic (LCC) - comparison grid - - Returns - ------- - projection : pyproj.Proj - The projection - """ - # Authors - # ------- - # Xylar Asay-Davis - # Yohei Takano - - projection = pyproj.Proj('+proj=lcc +lon_0=180 +lat_1=20 +lat_2=60 ' - '+x_0=0.0 +y_0=0.0 +ellps=WGS84') - - return projection # }}} + return comparison_descriptor -def _get_lat_lon_comparison_descriptor(config): # {{{ +def _get_lat_lon_comparison_descriptor(config): """ Get a descriptor of the lat/lon comparison grid, used for remapping and determining the grid name @@ -219,10 +96,10 @@ def _get_lat_lon_comparison_descriptor(config): # {{{ descriptor = LatLonGridDescriptor.create(lat, lon, units='degrees') - return descriptor # }}} + return descriptor -def _get_projection_comparison_descriptor(config, comparison_grid_name): # {{{ +def _get_projection_comparison_descriptor(config, comparison_grid_name): """ Get a descriptor of any comparison grid base on a projection, used for remapping and determining the grid name @@ -260,7 +137,7 @@ def _get_projection_comparison_descriptor(config, comparison_grid_name): # {{{ raise ValueError(f'{comparison_grid_name} is not one of the supported ' f'projection grids') - projection = get_projection(comparison_grid_name) + projection = get_pyproj_projection(comparison_grid_name) option_suffix = option_suffixes[comparison_grid_name] grid_suffix = grid_suffixes[comparison_grid_name] @@ -285,4 +162,4 @@ def _get_projection_comparison_descriptor(config, comparison_grid_name): # {{{ mesh_name = f'{width}x{height}km_{res}km_{grid_suffix}' descriptor = ProjectionGridDescriptor.create(projection, x, y, mesh_name) - return descriptor # }}} + return descriptor diff --git a/mpas_analysis/shared/projection/__init__.py b/mpas_analysis/shared/projection/__init__.py new file mode 100644 index 000000000..0a2bf4f43 --- /dev/null +++ b/mpas_analysis/shared/projection/__init__.py @@ -0,0 +1,126 @@ +# This software is open source software available under the BSD-3 license. +# +# Copyright (c) 2020 Triad National Security, LLC. All rights reserved. +# Copyright (c) 2020 Lawrence Livermore National Security, LLC. All rights +# reserved. +# Copyright (c) 2020 UT-Battelle, LLC. All rights reserved. +# +# Additional copyright and license information can be found in the LICENSE file +# distributed with this code, or at +# https://raw.githubusercontent.com/MPAS-Dev/MPAS-Analysis/master/LICENSE + +import pyproj +import cartopy + + +known_comparison_grids = ['latlon', 'antarctic', 'arctic', 'north_atlantic', + 'north_pacific'] + + +def get_pyproj_projection(comparison_grid_name): + """ + Get the projection from the comparison_grid_name. + + Parameters + ---------- + comparison_grid_name : {'antarctic', 'arctic', 'north_atlantic', + 'north_pacific'} + The name of the projection comparison grid to use for remapping + + Returns + ------- + projection : pyproj.Proj + The projection + + Raises + ------ + ValueError + If comparison_grid_name does not describe a known comparison grid + """ + # Authors + # ------- + # Xylar Asay-Davis + # Milena Veneziani + # Yohei Takano + + if comparison_grid_name not in known_comparison_grids: + raise ValueError( + f'Unknown comparison grid type {comparison_grid_name}') + + if comparison_grid_name == 'latlon': + raise ValueError('latlon is not a projection grid.') + elif comparison_grid_name == 'antarctic': + projection = pyproj.Proj( + '+proj=stere +lat_ts=-71.0 +lat_0=-90 +lon_0=0.0 +k_0=1.0 ' + '+x_0=0.0 +y_0=0.0 +ellps=WGS84') + elif comparison_grid_name == 'arctic': + projection = pyproj.Proj( + '+proj=stere +lat_ts=75.0 +lat_0=90 +lon_0=0.0 +k_0=1.0 ' + '+x_0=0.0 +y_0=0.0 +ellps=WGS84') + elif comparison_grid_name == 'north_atlantic': + projection = pyproj.Proj('+proj=lcc +lon_0=-45 +lat_0=45 +lat_1=39 ' + '+lat_2=51 +x_0=0.0 +y_0=0.0 +ellps=WGS84') + elif comparison_grid_name == 'north_pacific': + projection = pyproj.Proj('+proj=lcc +lon_0=180 +lat_0=40 +lat_1=34 ' + '+lat_2=46 +x_0=0.0 +y_0=0.0 +ellps=WGS84') + else: + raise ValueError(f'We missed one of the known comparison grids: ' + f'{comparison_grid_name}') + + return projection + + +def get_cartopy_projection(comparison_grid_name): + """ + Get the projection from the comparison_grid_name. + + Parameters + ---------- + comparison_grid_name : {'antarctic', 'arctic', 'north_atlantic', + 'north_pacific'} + The name of the projection comparison grid to use for remapping + + Returns + ------- + projection : cartopy.crs.Projection + The projection + + Raises + ------ + ValueError + If comparison_grid_name does not describe a known comparison grid + """ + # Authors + # ------- + # Xylar Asay-Davis + # Milena Veneziani + # Yohei Takano + + if comparison_grid_name not in known_comparison_grids: + raise ValueError( + f'Unknown comparison grid type {comparison_grid_name}') + + if comparison_grid_name == 'latlon': + raise ValueError('latlon is not a projection grid.') + + elif comparison_grid_name == 'antarctic': + projection = cartopy.crs.Stereographic( + central_latitude=-90., central_longitude=0.0, + true_scale_latitude=-71.0) + elif comparison_grid_name == 'arctic': + projection = cartopy.crs.Stereographic( + central_latitude=90., central_longitude=0.0, + true_scale_latitude=75.0) + elif comparison_grid_name == 'north_atlantic': + projection = cartopy.crs.LambertConformal( + central_latitude=45., central_longitude=-45., + standard_parallels=(39., 51.)) + elif comparison_grid_name == 'north_pacific': + projection = cartopy.crs.LambertConformal( + central_latitude=40., central_longitude=180., + standard_parallels=(34., 46.)) + else: + raise ValueError(f'We missed one of the known comparison grids: ' + f'{comparison_grid_name}') + + return projection From 9db94d519b0f8b9b4aee2767fbe5a4e30ba924ab Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 5 Feb 2022 12:25:00 -0700 Subject: [PATCH 05/14] Generalize projection plots to more projections What were previously only polar plots should now handle other projections (North Atlantic and North Pacific) --- mpas_analysis/default.cfg | 68 +++++++++++++++++- mpas_analysis/shared/plot/__init__.py | 2 +- mpas_analysis/shared/plot/climatology_map.py | 73 +++++++------------- 3 files changed, 93 insertions(+), 50 deletions(-) diff --git a/mpas_analysis/default.cfg b/mpas_analysis/default.cfg index 6abd4e481..b81528b02 100644 --- a/mpas_analysis/default.cfg +++ b/mpas_analysis/default.cfg @@ -235,6 +235,16 @@ comparisonAntarcticStereoResolution = 10. comparisonArcticStereoWidth = 6000. comparisonArcticStereoResolution = 10. +# The comparison North Atlantic grid size and resolution in km +comparisonNorthAtlanticWidth = 8500. +comparisonNorthAtlanticHeight = 5500. +comparisonNorthAtlanticResolution = 20. + +# The comparison North Pacific c grid size and resolution in km +comparisonNorthPacificWidth = 15000. +comparisonNorthPacificHeight = 5000. +comparisonNorthPacificResolution = 20. + # interpolation order for model and observation results. Likely values are # 'bilinear', 'neareststod' (nearest neighbor) or 'conserve' mpasInterpolationMethod = bilinear @@ -349,12 +359,66 @@ dpi = 200 # Write out PDFs in addition to PNGs? pdf = False -[polarProjection] -## options related to polar-projection plots +[plot_arctic] +## options related to Arctic projection plots + +# figure sizes for different numbers of panels and layouts +onePanelFigSize = (8, 7.5) +threePanelVertFigSize = (8, 22) +threePanelHorizFigSize = (22, 7.5) + +# whether to use the cartopy coastline (as opposed to the model coastline) +useCartopyCoastline = True + +# latitude and longitude grid lines +latLines = np.arange(-80., 81., 10.) +lonLines = np.arange(-180., 181., 30.) + +[plot_antarctic] +## options related to Antarctic projection plots + +# figure sizes for different numbers of panels and layouts +onePanelFigSize = (8, 7.5) +threePanelVertFigSize = (8, 22) +threePanelHorizFigSize = (22, 7.5) + +# whether to use the cartopy coastline (as opposed to the model coastline) +useCartopyCoastline = True + +# latitude and longitude grid lines +latLines = np.arange(-80., 81., 10.) +lonLines = np.arange(-180., 181., 30.) + +[plot_north_atlantic] +## options related to North Atlantic projection plots + +# figure sizes for different numbers of panels and layouts +onePanelFigSize = (8, 7.5) +threePanelVertFigSize = (8, 16) +threePanelHorizFigSize = (22, 7.5) # whether to use the cartopy coastline (as opposed to the model coastline) useCartopyCoastline = True +# latitude and longitude grid lines +latLines = np.arange(-80., 81., 10.) +lonLines = np.arange(-180., 181., 30.) + +[plot_north_pacific] +## options related to North Pacific projection plots + +# figure sizes for different numbers of panels and layouts +onePanelFigSize = (8, 7.5) +threePanelVertFigSize = (8, 10) +threePanelHorizFigSize = (22, 7.5) + +# whether to use the cartopy coastline (as opposed to the model coastline) +useCartopyCoastline = True + +# latitude and longitude grid lines +latLines = np.arange(-80., 81., 10.) +lonLines = np.arange(-180., 181., 30.) + [html] ## options related to generating a webpage to display the analysis diff --git a/mpas_analysis/shared/plot/__init__.py b/mpas_analysis/shared/plot/__init__.py index c097e14cb..cc9f65e08 100644 --- a/mpas_analysis/shared/plot/__init__.py +++ b/mpas_analysis/shared/plot/__init__.py @@ -2,7 +2,7 @@ timeseries_analysis_plot_polar from mpas_analysis.shared.plot.climatology_map import plot_polar_comparison, \ - plot_global_comparison, plot_polar_projection_comparison + plot_global_comparison, plot_projection_comparison from mpas_analysis.shared.plot.vertical_section import \ plot_vertical_section_comparison, plot_vertical_section diff --git a/mpas_analysis/shared/plot/climatology_map.py b/mpas_analysis/shared/plot/climatology_map.py index 1a049efd6..62935f7cc 100644 --- a/mpas_analysis/shared/plot/climatology_map.py +++ b/mpas_analysis/shared/plot/climatology_map.py @@ -32,6 +32,7 @@ from mpas_analysis.shared.plot.colormap import setup_colormap from mpas_analysis.shared.plot.title import limit_title from mpas_analysis.shared.plot.save import savefig +from mpas_analysis.shared.projection import get_cartopy_projection def plot_polar_comparison( @@ -433,7 +434,7 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, plt.close() -def plot_polar_projection_comparison( +def plot_projection_comparison( config, x, y, @@ -443,6 +444,7 @@ def plot_polar_projection_comparison( diffArray, fileout, colorMapSectionName, + projectionName, title=None, modelTitle='Model', refTitle='Observations', @@ -451,12 +453,7 @@ def plot_polar_projection_comparison( titleFontSize=None, cartopyGridFontSize=None, defaultFontSize=None, - figsize=None, - dpi=None, - lineWidth=0.5, - lineColor='black', vertical=False, - hemisphere='north', maxTitleLength=55): """ Plots a data set as a projection map. @@ -468,7 +465,7 @@ def plot_polar_projection_comparison( control plotting x, y : numpy.ndarrays - 1D x and y arrays defining the projection grid + 1D x and y arrays of the corners of grid cells on the projection grid landMask : numpy.ndarrays model and observational or control run data sets @@ -509,26 +506,14 @@ def plot_polar_projection_comparison( defaultFontSize : int, optional the size of text other than the title - figsize : tuple of float, optional - the size of the figure in inches. If ``None``, the figure size is - ``(8, 22)`` if ``vertical == True`` and ``(22, 8)`` otherwise. - - dpi : int, optional - the number of dots per inch of the figure, taken from section ``plot`` - option ``dpi`` in the config file by default - - lineWidth : int, optional - the line width of contour lines (if specified) - - lineColor : str, optional - the color of contour lines (if specified) - vertical : bool, optional whether the subplots should be stacked vertically rather than horizontally - hemisphere : {'north', 'south'}, optional - the hemisphere to plot + projectionName : str, optional + the name of projection that the data is on, one of the projections + available via + :py:func:`mpas_analysis.shared.projection.get_cartopy_projection()`. maxTitleLength : int, optional the maximum number of characters in the title, beyond which it is @@ -548,11 +533,11 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, gl = ax.gridlines(crs=cartopy.crs.PlateCarree(), color='k', linestyle=':', zorder=5, draw_labels=True) - gl.xlocator = mticker.FixedLocator(np.arange(-180., 181., 30.)) - gl.ylocator = mticker.FixedLocator(np.arange(-80., 81., 10.)) + gl.xlocator = mticker.FixedLocator(lonLines) + gl.ylocator = mticker.FixedLocator(latLines) gl.n_steps = 100 gl.right_labels = False - gl.left_labels = False + gl.left_labels = left_labels gl.xformatter = cartopy.mpl.gridliner.LONGITUDE_FORMATTER gl.yformatter = cartopy.mpl.gridliner.LATITUDE_FORMATTER gl.xlabel_style['size'] = cartopyGridFontSize @@ -598,25 +583,28 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, if cartopyGridFontSize is None: cartopyGridFontSize = config.getint('plot', 'cartopyGridFontSize') - useCartopyCoastline = config.getboolean('polarProjection', - 'useCartopyCoastline') # set up figure - if dpi is None: - dpi = config.getint('plot', 'dpi') + dpi = config.getint('plot', 'dpi') + + section = f'plot_{projectionName}' + useCartopyCoastline = config.getboolean(section, 'useCartopyCoastline') if refArray is None: - if figsize is None: - figsize = (8, 7.5) + figsize = config.getExpression(section, 'onePanelFigSize') subplots = [111] elif vertical: - if figsize is None: - figsize = (8, 22) + figsize = config.getExpression(section, 'threePanelVertFigSize') subplots = [311, 312, 313] else: - if figsize is None: - figsize = (22, 7.5) + figsize = config.getExpression(section, 'threePanelHorizFigSize') subplots = [131, 132, 133] + latLines = config.getExpression(section, 'latLines', usenumpyfunc=True) + lonLines = config.getExpression(section, 'lonLines', usenumpyfunc=True) + + # put latitude labels on the left unless we're in a polar projection + left_labels = projectionName not in ['arctic', 'antarctic'] + dictModelRef = setup_colormap(config, colorMapSectionName, suffix='Result') dictDiff = setup_colormap(config, colorMapSectionName, suffix='Difference') @@ -642,17 +630,8 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, xCenter = 0.5 * (x[1:] + x[0:-1]) yCenter = 0.5 * (y[1:] + y[0:-1]) - if hemisphere == 'north': - projection = cartopy.crs.Stereographic( - central_latitude=90., central_longitude=0.0, - true_scale_latitude=75.0) - elif hemisphere == 'south': - projection = cartopy.crs.Stereographic( - central_latitude=-90., central_longitude=0.0, - true_scale_latitude=-71.0) - else: - raise ValueError('Unexpected hemisphere {}'.format( - hemisphere)) + projection = get_cartopy_projection(projectionName) + extent = [x[0], x[-1], y[0], y[-1]] ax = plt.subplot(subplots[0], projection=projection) From 09d3a06de55f969cca7f049e5ecef2ed4b9681f9 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 5 Feb 2022 12:36:03 -0700 Subject: [PATCH 06/14] Plot projection grids in ocean climatology map subtask --- .../ocean/plot_climatology_map_subtask.py | 65 +++++++------------ 1 file changed, 22 insertions(+), 43 deletions(-) diff --git a/mpas_analysis/ocean/plot_climatology_map_subtask.py b/mpas_analysis/ocean/plot_climatology_map_subtask.py index 1d3ddcd6b..5840bfac8 100644 --- a/mpas_analysis/ocean/plot_climatology_map_subtask.py +++ b/mpas_analysis/ocean/plot_climatology_map_subtask.py @@ -21,12 +21,11 @@ import xarray as xr import numpy as np -from pyremap.descriptor import interp_extrap_corner from mpas_analysis.shared import AnalysisTask from mpas_analysis.shared.plot import plot_global_comparison, \ - plot_polar_projection_comparison + plot_projection_comparison from mpas_analysis.shared.html import write_image_xml @@ -38,7 +37,7 @@ from mpas_analysis.ocean.utility import nans_to_numpy_mask -class PlotClimatologyMapSubtask(AnalysisTask): # {{{ +class PlotClimatologyMapSubtask(AnalysisTask): """ An analysis task for plotting 2D model fields against observations. @@ -79,12 +78,6 @@ class PlotClimatologyMapSubtask(AnalysisTask): # {{{ mpasFieldName : str The name of the variable in the MPAS timeSeriesStatsMonthly output - obsFieldName : str - The name of the variable to use from the observations file - - observationTitleLabel : str - the title of the observations subplot - diffTitleLabel : str, optional the title of the difference subplot @@ -122,7 +115,7 @@ class PlotClimatologyMapSubtask(AnalysisTask): # {{{ def __init__(self, parentTask, season, comparisonGridName, remapMpasClimatologySubtask, remapObsClimatologySubtask=None, secondRemapMpasClimatologySubtask=None, controlConfig=None, - depth=None, removeMean=False, subtaskName=None): # {{{ + depth=None, removeMean=False, subtaskName=None): ''' Construct one analysis subtask for each plot (i.e. each season and comparison grid) and a subtask for computing climatologies. @@ -165,7 +158,7 @@ def __init__(self, parentTask, season, comparisonGridName, useful for data sets where the desire is to compare the spatial pattern but the mean offset is not meaningful (e.g. SSH) - subtaskName : str, optinal + subtaskName : str, optional The name of the subtask. If not specified, it is ``plot_`` with a suffix indicating the depth being sliced (if any) @@ -210,14 +203,12 @@ def __init__(self, parentTask, season, comparisonGridName, self.run_after(remapObsClimatologySubtask) if secondRemapMpasClimatologySubtask is not None: self.run_after(secondRemapMpasClimatologySubtask) - # }}} def set_plot_info(self, outFileLabel, fieldNameInTitle, mpasFieldName, refFieldName, refTitleLabel, unitsLabel, imageCaption, galleryGroup, groupSubtitle, groupLink, galleryName, diffTitleLabel='Model - Observations', configSectionName=None): - # {{{ """ Store attributes related to plots, plot file names and HTML output. @@ -305,9 +296,8 @@ def set_plot_info(self, outFileLabel, fieldNameInTitle, mpasFieldName, self.fieldNameInTitle = '{} at z={} m'.format(fieldNameInTitle, depth) self.thumbnailDescription = '{} z={} m'.format(season, depth) - # }}} - def setup_and_check(self): # {{{ + def setup_and_check(self): """ Perform steps to set up the analysis and check for errors in the setup. """ @@ -345,9 +335,8 @@ def setup_and_check(self): # {{{ self.xmlFileNames.append('{}/{}.xml'.format(self.plotsDirectory, self.filePrefix)) - # }}} - def run_task(self): # {{{ + def run_task(self): """ Plots a comparison of E3SM/MPAS output to SST/TEMP, SSS/SALT or MLD observations or a control run @@ -453,14 +442,11 @@ def run_task(self): # {{{ if self.comparisonGridName == 'latlon': self._plot_latlon(remappedModelClimatology, remappedRefClimatology) - elif self.comparisonGridName == 'antarctic' or \ - self.comparisonGridName == 'arctic': - self._plot_polar(remappedModelClimatology, - remappedRefClimatology) - # }}} + else: + self._plot_projection(remappedModelClimatology, + remappedRefClimatology) def _plot_latlon(self, remappedModelClimatology, remappedRefClimatology): - # {{{ """ plotting a global lat-lon data set """ season = self.season @@ -532,10 +518,8 @@ def _plot_latlon(self, remappedModelClimatology, remappedRefClimatology): imageDescription=caption, imageCaption=caption) - # }}} - - def _plot_polar(self, remappedModelClimatology, - remappedRefClimatology): # {{{ + def _plot_projection(self, remappedModelClimatology, + remappedRefClimatology): """ plotting an Arctic or Antarctic data set """ season = self.season @@ -567,18 +551,18 @@ def _plot_polar(self, remappedModelClimatology, x = comparisonDescriptor.xCorner y = comparisonDescriptor.yCorner + aspectRatio = (x[-1] - x[0])/(y[-1] - y[0]) + + # if the plots are even a bit wider than they are tall, make them + # vertical + vertical = aspectRatio > 1.2 + filePrefix = self.filePrefix outFileName = '{}/{}.png'.format(self.plotsDirectory, filePrefix) title = '{} ({}, years {:04d}-{:04d})'.format( self.fieldNameInTitle, season, self.startYear, self.endYear) - if self.comparisonGridName == 'antarctic': - hemisphere = 'south' - else: - # arctic - hemisphere = 'north' - if config.has_option(configSectionName, 'titleFontSize'): titleFontSize = config.getint(configSectionName, 'titleFontSize') else: @@ -596,7 +580,7 @@ def _plot_polar(self, remappedModelClimatology, else: cartopyGridFontSize = None - plot_polar_projection_comparison( + plot_projection_comparison( config, x, y, @@ -606,17 +590,18 @@ def _plot_polar(self, remappedModelClimatology, bias, fileout=outFileName, colorMapSectionName=configSectionName, + projectionName=comparisonGridName, title=title, modelTitle='{}'.format(mainRunName), refTitle=self.refTitleLabel, diffTitle=self.diffTitleLabel, cbarlabel=self.unitsLabel, - hemisphere=hemisphere, titleFontSize=titleFontSize, cartopyGridFontSize=cartopyGridFontSize, - defaultFontSize=defaultFontSize) + defaultFontSize=defaultFontSize, + vertical=vertical) - upperGridName = comparisonGridName[0].upper() + comparisonGridName[1:] + upperGridName = comparisonGridName.replace('_', ' ').capitalize() caption = '{} {}'.format(season, self.imageCaption) write_image_xml( config, @@ -631,9 +616,3 @@ def _plot_polar(self, remappedModelClimatology, thumbnailDescription=self.thumbnailDescription, imageDescription=caption, imageCaption=caption) - - # }}} - # }}} - - -# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python From e052d860234911e26a528f2c7a770e5f76ebb0ad Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 5 Feb 2022 13:03:07 -0700 Subject: [PATCH 07/14] Get Antarctic projection with new approach --- mpas_analysis/ocean/climatology_map_antarctic_melt.py | 5 +++-- mpas_analysis/ocean/climatology_map_schmidtko.py | 6 +++--- mpas_analysis/ocean/remap_sose_climatology.py | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mpas_analysis/ocean/climatology_map_antarctic_melt.py b/mpas_analysis/ocean/climatology_map_antarctic_melt.py index aea56852d..dfa6d2e75 100644 --- a/mpas_analysis/ocean/climatology_map_antarctic_melt.py +++ b/mpas_analysis/ocean/climatology_map_antarctic_melt.py @@ -25,7 +25,8 @@ from mpas_analysis.shared.io import write_netcdf from mpas_analysis.shared.climatology import RemapMpasClimatologySubtask, \ - RemapObservedClimatologySubtask, get_antarctic_stereographic_projection + RemapObservedClimatologySubtask +from mpas_analysis.shared.projection import get_pyproj_projection from mpas_analysis.shared.climatology.climatology import \ get_masked_mpas_climatology_file_name @@ -294,7 +295,7 @@ def get_observation_descriptor(self, fileName): # {{{ # create a descriptor of the observation grid using the x/y polar # stereographic coordinates - projection = get_antarctic_stereographic_projection() + projection = get_pyproj_projection(comparison_grid_name='antarctic') obsDescriptor = ProjectionGridDescriptor.read( projection, fileName=fileName, xVarName='x', yVarName='y') return obsDescriptor # }}} diff --git a/mpas_analysis/ocean/climatology_map_schmidtko.py b/mpas_analysis/ocean/climatology_map_schmidtko.py index 136c2730e..a091f8a15 100644 --- a/mpas_analysis/ocean/climatology_map_schmidtko.py +++ b/mpas_analysis/ocean/climatology_map_schmidtko.py @@ -28,8 +28,8 @@ from mpas_analysis.shared.io.utility import build_obs_path -from mpas_analysis.shared.climatology import RemapObservedClimatologySubtask, \ - get_antarctic_stereographic_projection +from mpas_analysis.shared.climatology import RemapObservedClimatologySubtask +from mpas_analysis.shared.projection import get_pyproj_projection class ClimatologyMapSchmidtko(AnalysisTask): # {{{ @@ -262,7 +262,7 @@ def get_observation_descriptor(self, fileName): # {{{ # create a descriptor of the observation grid using the x/y polar # stereographic coordinates - projection = get_antarctic_stereographic_projection() + projection = get_pyproj_projection(comparison_grid_name='antarctic') obsDescriptor = ProjectionGridDescriptor.read( projection, fileName=fileName, xVarName='x', yVarName='y') return obsDescriptor # }}} diff --git a/mpas_analysis/ocean/remap_sose_climatology.py b/mpas_analysis/ocean/remap_sose_climatology.py index 18171bc8d..97974192b 100644 --- a/mpas_analysis/ocean/remap_sose_climatology.py +++ b/mpas_analysis/ocean/remap_sose_climatology.py @@ -20,8 +20,8 @@ from pyremap import ProjectionGridDescriptor -from mpas_analysis.shared.climatology import RemapObservedClimatologySubtask, \ - get_antarctic_stereographic_projection +from mpas_analysis.shared.climatology import RemapObservedClimatologySubtask +from mpas_analysis.shared.projection import get_pyproj_projection class RemapSoseClimatology(RemapObservedClimatologySubtask): @@ -113,7 +113,7 @@ def get_observation_descriptor(self, fileName): # {{{ # create a descriptor of the observation grid using the x/y polar # stereographic coordinates - projection = get_antarctic_stereographic_projection() + projection = get_pyproj_projection(comparison_grid_name='antarctic') obsDescriptor = ProjectionGridDescriptor.read( projection, fileName=fileName, xVarName='x', yVarName='y') return obsDescriptor # }}} From 557edb7247c906748506abe544b6d548641bb5c2 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 5 Feb 2022 14:57:00 -0700 Subject: [PATCH 08/14] Fix tests --- mpas_analysis/test/test_climatology.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mpas_analysis/test/test_climatology.py b/mpas_analysis/test/test_climatology.py index d760b61bc..bcfa4d9f3 100644 --- a/mpas_analysis/test/test_climatology.py +++ b/mpas_analysis/test/test_climatology.py @@ -88,7 +88,7 @@ def setup_mpas_remapper(self, config): mpasMeshFileName = '{}/mpasMesh.nc'.format(self.datadir) comparisonDescriptor = \ - get_comparison_descriptor(config, comparisonGridName='latlon') + get_comparison_descriptor(config, comparison_grid_name='latlon') mpasDescriptor = MpasMeshDescriptor( mpasMeshFileName, meshName=config.get('input', 'mpasMeshName')) @@ -105,7 +105,7 @@ def setup_obs_remapper(self, config, fieldName): gridFileName = '{}/obsGrid.nc'.format(self.datadir) comparisonDescriptor = \ - get_comparison_descriptor(config, comparisonGridName='latlon') + get_comparison_descriptor(config, comparison_grid_name='latlon') obsDescriptor = LatLonGridDescriptor.read(fileName=gridFileName, latVarName='lat', From 2458e17608f7af3c81588f424ba797afd3eed01e Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 5 Feb 2022 23:43:58 -0700 Subject: [PATCH 09/14] Fixes to documentaiton and docstring --- docs/api.rst | 12 +++++++++++- mpas_analysis/shared/climatology/climatology.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 7c0e18d60..4b7a32b02 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -184,7 +184,6 @@ Climatology :toctree: generated/ get_comparison_descriptor - get_antarctic_stereographic_projection get_remapper compute_monthly_climatology compute_climatology @@ -275,6 +274,17 @@ Plotting add_inset +Projection +---------- +.. currentmodule:: mpas_analysis.shared.projection + +.. autosummary:: + :toctree: generated/ + + get_pyproj_projection + get_cartopy_projection + + Regions ------- .. currentmodule:: mpas_analysis.shared.regions diff --git a/mpas_analysis/shared/climatology/climatology.py b/mpas_analysis/shared/climatology/climatology.py index 2eef6c6ea..8770542d6 100644 --- a/mpas_analysis/shared/climatology/climatology.py +++ b/mpas_analysis/shared/climatology/climatology.py @@ -555,7 +555,7 @@ def get_remapped_mpas_climatology_file_name(config, season, componentName, comparisonGridName : str The name of the comparison grid to use for remapping. If it is one of the known comparison grid names, the full grid name is looked up via - :py:fun:`mpas_analysis.shared.climatology.get_comparison_descriptor()` + :py:func:`mpas_analysis.shared.climatology.get_comparison_descriptor()` op : {'avg', 'min', 'max'} operator for monthly stats From da64728db103363d2dedeb6abfc8c428159e9abc Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sun, 6 Feb 2022 09:26:15 -0600 Subject: [PATCH 10/14] Add SST on new projections to test suite --- suite/template.cfg | 9 +++++++++ suite/wc_defaults.cfg | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/suite/template.cfg b/suite/template.cfg index a5e4f79c6..5b13144a5 100644 --- a/suite/template.cfg +++ b/suite/template.cfg @@ -129,3 +129,12 @@ endYear = {{ end_year }} # NOTE: this is a temporary option that will be removed once the online # MOC takes into account the bolus velocity when GM is on. usePostprocessingScript = True + +[climatologyMapSST] +## options related to plotting horizontally remapped climatologies of +## sea surface temperature (SST) against control model results and +## observations + +# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis +comparisonGrids = ['latlon', 'antarctic', 'arctic', 'north_atlantic', + 'north_pacific'] diff --git a/suite/wc_defaults.cfg b/suite/wc_defaults.cfg index 068cd26b9..0d7028261 100644 --- a/suite/wc_defaults.cfg +++ b/suite/wc_defaults.cfg @@ -8,4 +8,4 @@ generate = ['all', 'no_landIceCavities', 'no_BGC', 'no_icebergs', 'no_min', 'no_climatologyMapAntarcticMelt', 'no_regionalTSDiagrams', 'no_timeSeriesAntarcticMelt', 'no_timeSeriesOceanRegions', 'no_climatologyMapSose', 'no_woceTransects', 'no_soseTransects', - 'no_geojsonTransects', 'no_oceanRegionalProfiles'] \ No newline at end of file + 'no_geojsonTransects', 'no_oceanRegionalProfiles'] From 54cb954a1351898eae01e3f57cd114cba77305cb Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sun, 6 Feb 2022 15:00:53 -0700 Subject: [PATCH 11/14] Fix contour plots on projeciton grids --- mpas_analysis/shared/plot/climatology_map.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mpas_analysis/shared/plot/climatology_map.py b/mpas_analysis/shared/plot/climatology_map.py index 62935f7cc..07a009e9e 100644 --- a/mpas_analysis/shared/plot/climatology_map.py +++ b/mpas_analysis/shared/plot/climatology_map.py @@ -561,8 +561,10 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, if contours is not None: matplotlib.rcParams['contour.negative_linestyle'] = 'solid' - ax.contour(x, y, array, levels=contours, colors=lineColor, - linewidths=lineWidth) + x_center = 0.5*(x[0:-1] + x[1:]) + y_center = 0.5*(y[0:-1] + y[1:]) + ax.contour(x_center, y_center, array, levels=contours, + colors=lineColor, linewidths=lineWidth) # create an axes on the right side of ax. The width of cax will be 5% # of ax and the padding between cax and ax will be fixed at 0.05 inch. From 8dd6d906600601de7fae2b903d15d846aeb72bec Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Mon, 7 Feb 2022 16:14:17 -0700 Subject: [PATCH 12/14] Compute global stats on obs only where not land --- mpas_analysis/shared/plot/climatology_map.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mpas_analysis/shared/plot/climatology_map.py b/mpas_analysis/shared/plot/climatology_map.py index 07a009e9e..287b99001 100644 --- a/mpas_analysis/shared/plot/climatology_map.py +++ b/mpas_analysis/shared/plot/climatology_map.py @@ -67,7 +67,7 @@ def plot_polar_comparison( the configuration, containing a [plot] section with options that control plotting - Lons, Lats : numpy.ndarray + lon, lat : float arrays longitude and latitude arrays modelArray, refArray : numpy.ndarray @@ -663,6 +663,13 @@ def _add_stats(modelArray, refArray, diffArray, Lats, axes): ax=axes[0], loc='upper') if refArray is not None: + if isinstance(modelArray, np.ma.MaskedArray): + # make sure we're using the MPAS land mask for all 3 sets of stats + mask = modelArray.mask + if isinstance(refArray, np.ma.MaskedArray): + # mask invalid where either model or ref array is invalid + mask = np.logical_or(mask, refArray.mask) + refArray = np.ma.array(refArray, mask=mask) modelAnom = modelArray - modelMean modelVar = np.average(modelAnom ** 2, weights=weights) refMean = np.average(refArray, weights=weights) From 45f4be1c861ea4447b9b1be0bc26723fd0d7f872 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Mon, 7 Feb 2022 17:16:15 -0700 Subject: [PATCH 13/14] Update pyremap version --- ci/recipe/meta.yaml | 2 +- dev-spec.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/recipe/meta.yaml b/ci/recipe/meta.yaml index 9dedce6ab..297cef9f4 100644 --- a/ci/recipe/meta.yaml +++ b/ci/recipe/meta.yaml @@ -40,7 +40,7 @@ requirements: - pillow - progressbar2 - pyproj - - pyremap >=0.0.13,<0.1.0 + - pyremap >=0.0.14,<0.1.0 - python-dateutil - requests - scipy diff --git a/dev-spec.txt b/dev-spec.txt index ab788cd35..ac43d1594 100644 --- a/dev-spec.txt +++ b/dev-spec.txt @@ -21,7 +21,7 @@ pandas pillow progressbar2 pyproj -pyremap>=0.0.13,<0.1.0 +pyremap>=0.0.14,<0.1.0 python-dateutil requests scipy From 8b6ac6f0122d61ea8c6a937b2d126da9f1a9e7d6 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Tue, 8 Feb 2022 13:52:02 -0700 Subject: [PATCH 14/14] Update to 1.6.1 --- ci/recipe/meta.yaml | 2 +- docs/versions.rst | 3 +++ mpas_analysis/__init__.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ci/recipe/meta.yaml b/ci/recipe/meta.yaml index 297cef9f4..9320bbed4 100644 --- a/ci/recipe/meta.yaml +++ b/ci/recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "MPAS-Analysis" %} -{% set version = "1.6.0" %} +{% set version = "1.6.1" %} package: name: {{ name|lower }} diff --git a/docs/versions.rst b/docs/versions.rst index a7a43eab3..59e5fb66f 100644 --- a/docs/versions.rst +++ b/docs/versions.rst @@ -14,6 +14,7 @@ Documentation On GitHub `v1.4.0`_ `1.4.0`_ `v1.5.0`_ `1.5.0`_ `v1.6.0`_ `1.6.0`_ +`v1.6.1`_ `1.6.1`_ ================ =============== .. _`stable`: ../stable/index.html @@ -26,6 +27,7 @@ Documentation On GitHub .. _`v1.4.0`: ../1.4.0/index.html .. _`v1.5.0`: ../1.5.0/index.html .. _`v1.6.0`: ../1.6.0/index.html +.. _`v1.6.1`: ../1.6.1/index.html .. _`master`: https://github.com/MPAS-Dev/MPAS-Analysis/tree/master .. _`develop`: https://github.com/MPAS-Dev/MPAS-Analysis/tree/develop .. _`1.2.6`: https://github.com/MPAS-Dev/MPAS-Analysis/tree/1.2.6 @@ -36,3 +38,4 @@ Documentation On GitHub .. _`1.4.0`: https://github.com/MPAS-Dev/MPAS-Analysis/tree/1.4.0 .. _`1.5.0`: https://github.com/MPAS-Dev/MPAS-Analysis/tree/1.5.0 .. _`1.6.0`: https://github.com/MPAS-Dev/MPAS-Analysis/tree/1.6.0 +.. _`1.6.1`: https://github.com/MPAS-Dev/MPAS-Analysis/tree/1.6.1 diff --git a/mpas_analysis/__init__.py b/mpas_analysis/__init__.py index 1a006aab5..9c3b51e56 100644 --- a/mpas_analysis/__init__.py +++ b/mpas_analysis/__init__.py @@ -3,5 +3,5 @@ import matplotlib as mpl mpl.use('Agg') -__version_info__ = (1, 6, 0) +__version_info__ = (1, 6, 1) __version__ = '.'.join(str(vi) for vi in __version_info__)