diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index c5920a81505..6df3dd542fa 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -205,6 +205,7 @@ Other azimuth_range_to_lat_lon find_bounding_indices find_intersections + find_local_extrema get_layer get_layer_heights get_perturbation diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 10486e8058a..077999a0b52 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -800,12 +800,11 @@ def find_local_extrema(var, nsize, extrema): var_extrema: `xarray.DataArray` The values of the local extrema with other values as NaNs - See Also - -------- - plot_local_extrema - """ from scipy.ndimage import maximum_filter, minimum_filter + if extrema not in ['max', 'min']: + raise ValueError('Invalid input for "extrema". Valid options are "max" or "min".') + if extrema == 'max': extreme_val = maximum_filter(var.values, nsize, mode='nearest') elif extrema == 'min': diff --git a/src/metpy/plots/_util.py b/src/metpy/plots/_util.py index 8bbf772cbcd..7bc014eda63 100644 --- a/src/metpy/plots/_util.py +++ b/src/metpy/plots/_util.py @@ -290,7 +290,7 @@ def normalize(x): def plot_local_extrema(ax, extreme_vals, symbol, plot_val=True, **kwargs): """Plot the local extreme (max/min) values of an array. - + The behavior of the plotting will have the symbol horizontal/vertical alignment be center/bottom and any value plotted will be center/top. The text size of plotted values is 0.65 of the symbol size. @@ -309,7 +309,7 @@ def plot_local_extrema(ax, extreme_vals, symbol, plot_val=True, **kwargs): Returns ------- Plots local extrema on the plot axes - + Other Parameters ---------------- kwargs : `matplotlib.pyplot.Text` properties. @@ -330,14 +330,13 @@ def plot_local_extrema(ax, extreme_vals, symbol, plot_val=True, **kwargs): """ defaultkwargs = {'size': 20, 'color': 'black', 'fontweight': 'bold', - 'horizontalalignment': 'center', 'verticalalignment': 'center', - 'transform': None} + 'horizontalalignment': 'center', 'verticalalignment': 'center'} kwargs = {**defaultkwargs, **kwargs} if plot_val: kwargs.pop('verticalalignment') size = kwargs.pop('size') textsize = size * .65 - + stack_vals = extreme_vals.stack(x=[extreme_vals.metpy.x.name, extreme_vals.metpy.y.name]) for extrema in stack_vals[stack_vals.notnull()]: x = extrema[stack_vals.metpy.x.name].values @@ -345,8 +344,8 @@ def plot_local_extrema(ax, extreme_vals, symbol, plot_val=True, **kwargs): if plot_val: ax.text(x, y, symbol, clip_on=True, clip_box=ax.bbox, size=size, verticalalignment='bottom', **kwargs) - ax.text(x, y, f'{extrema.values:.0f}', clip_on=True, clip_box=ax.bbox, size=textsize, - verticalalignment='top', **kwargs) + ax.text(x, y, f'{extrema.values:.0f}', clip_on=True, clip_box=ax.bbox, + size=textsize, verticalalignment='top', **kwargs) else: ax.text(x, y, symbol, clip_on=True, clip_box=ax.bbox, size=size, **kwargs) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 9d5f0bf1adb..7bfab0cf155 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -21,8 +21,7 @@ from metpy.calc.tools import (_delete_masked_points, _get_bound_pressure_height, _greater_or_close, _less_or_close, _next_non_masked_element, _remove_nans, azimuth_range_to_lat_lon, BASE_DEGREE_MULTIPLIER, - DIR_STRS, nominal_lat_lon_grid_deltas, - parse_grid_arguments, UND) + DIR_STRS, nominal_lat_lon_grid_deltas, parse_grid_arguments, UND) from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_array_equal, get_test_data) from metpy.units import units @@ -481,42 +480,44 @@ def test_get_layer_heights_agl_bottom_no_interp(): def local_extrema_data(): """Test data for local extrema finding.""" data = xr.DataArray( - np.array([[101628.24 , 101483.67 , 101366.06 , 101287.55 , 101233.45 ], - [101637.19 , 101515.555, 101387.164, 101280.32 , 101210.15 ], - [101581.78 , 101465.234, 101342. , 101233.22 , 101180.25 ], - [101404.31 , 101318.4 , 101233.18 , 101166.445, 101159.93 ], - [101280.586, 101238.445, 101195.234, 101183.34 , 101212.8 ]]), - name='mslp', - dims=('lat', 'lon'), - coords={'lat': xr.DataArray(np.array([45., 43., 41., 39., 37.]), - dims=('lat',), attrs={'units': 'degrees_north'}), - 'lon': xr.DataArray(np.array([265., 267., 269., 271., 273.]), - dims=('lon',), attrs={'units': 'degrees_east'}) - }, - attrs={'units': 'Pa'} + np.array([[101628.24, 101483.67, 101366.06, 101287.55, 101233.45], + [101637.19, 101515.555, 101387.164, 101280.32, 101210.15], + [101581.78, 101465.234, 101342., 101233.22, 101180.25], + [101404.31, 101318.4, 101233.18, 101166.445, 101159.93], + [101280.586, 101238.445, 101195.234, 101183.34, 101212.8]]), + name='mslp', + dims=('lat', 'lon'), + coords={'lat': xr.DataArray(np.array([45., 43., 41., 39., 37.]), + dims=('lat',), attrs={'units': 'degrees_north'}), + 'lon': xr.DataArray(np.array([265., 267., 269., 271., 273.]), + dims=('lon',), attrs={'units': 'degrees_east'}) + }, + attrs={'units': 'Pa'} ) return data def test_find_local_extrema(local_extrema_data): """Test find_local_extrema function for maximum.""" - mslp_data = local_extrema_data - local_max = find_local_extrema(mslp_data, 3, 'max') - local_min = find_local_extrema(mslp_data, 3, 'min') + local_max = find_local_extrema(local_extrema_data, 3, 'max') + local_min = find_local_extrema(local_extrema_data, 3, 'min') max_truth = np.array([[np.nan, np.nan, np.nan, np.nan, np.nan], - [101637.19, np.nan, np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan, np.nan, 101212.8]]) + [101637.19, np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan, 101212.8]]) min_truth = np.array([[np.nan, np.nan, np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan, np.nan, 101159.93], - [np.nan, np.nan, np.nan, np.nan, np.nan]]) + [np.nan, np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan, 101159.93], + [np.nan, np.nan, np.nan, np.nan, np.nan]]) assert_array_almost_equal(local_max.data, max_truth) assert_array_almost_equal(local_min.data, min_truth) + with pytest.raises(ValueError): + find_local_extrema(local_extrema_data, 3, 'large') + def test_lat_lon_grid_deltas_1d(): """Test for lat_lon_grid_deltas for variable grid.""" diff --git a/tests/plots/baseline/test_plot_extrema.png b/tests/plots/baseline/test_plot_extrema.png index 93ee45bcdad..0ef47eefbb4 100644 Binary files a/tests/plots/baseline/test_plot_extrema.png and b/tests/plots/baseline/test_plot_extrema.png differ diff --git a/tests/plots/test_util.py b/tests/plots/test_util.py index cf2273c3a86..7ab3a5b3e21 100644 --- a/tests/plots/test_util.py +++ b/tests/plots/test_util.py @@ -5,8 +5,6 @@ from datetime import datetime -import cartopy.crs as ccrs -import cartopy.feature as cfeature import matplotlib import matplotlib.pyplot as plt import numpy as np @@ -166,34 +164,18 @@ def test_plot_extrema(): data = xr.open_dataset(get_test_data('GFS_test.nc', as_file_obj=False)) mslp = data.Pressure_reduced_to_MSL_msl.squeeze() - relmax2D = find_local_extrema(mslp, 10, 'max').metpy.convert_units('hPa') - relmin2D = find_local_extrema(mslp, 15, 'min').metpy.convert_units('hPa') + relmax2d = find_local_extrema(mslp, 10, 'max').metpy.convert_units('hPa') + relmin2d = find_local_extrema(mslp, 15, 'min').metpy.convert_units('hPa') - fig = plt.figure(1, figsize=(17., 11.)) - ax = plt.subplot(111, projection=ccrs.LambertConformal(central_latitude=35, - central_longitude=-100)) - - # Set extent and plot map lines - ax.set_extent([-124., -70, 20., 60.], ccrs.PlateCarree()) - ax.add_feature(cfeature.COASTLINE.with_scale('50m'), - edgecolor='grey', linewidth=0.75) - ax.add_feature(cfeature.STATES.with_scale('50m'), - edgecolor='grey', linewidth=0.5) - - # Plot thickness with multiple colors - clevs = (np.arange(0, 5400, 60), - np.array([5400]), - np.arange(5460, 7000, 60)) + fig = plt.figure(figsize=(8., 8.)) + ax = fig.add_subplot(1, 1, 1) # Plot MSLP clevmslp = np.arange(800., 1120., 4) - cs2 = ax.contour(mslp.lon, mslp.lat, mslp.metpy.convert_units('hPa'), - clevmslp, colors='k', linewidths=1.25, - linestyles='solid', transform=ccrs.PlateCarree()) - plt.clabel(cs2) + ax.contour(mslp.lon, mslp.lat, mslp.metpy.convert_units('hPa'), + clevmslp, colors='k', linewidths=1.25, linestyles='solid') - plot_local_extrema(ax, relmax2D, 'H', plot_val=False, color='tab:red', - transform=ccrs.PlateCarree()) - plot_local_extrema(ax, relmin2D, 'L', color='tab:blue', transform=ccrs.PlateCarree()) + plot_local_extrema(ax, relmax2d, 'H', plot_val=False, color='tab:red') + plot_local_extrema(ax, relmin2d, 'L', color='tab:blue') - return fig \ No newline at end of file + return fig