diff --git a/.travis.yml b/.travis.yml index e8a8be40ab5..04fc15d98a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,10 +62,12 @@ matrix: env: - TASK="coverage" - VERSIONS="git+git://github.com/hgrecco/pint@master#egg=pint git+git://github.com/pydata/xarray@master#egg=xarray" + - EXTRA_PACKAGES="$EXTRA_PACKAGES setuptools_scm[toml]" - python: "3.8-dev" env: - TASK="docs" - VERSIONS="git+git://github.com/hgrecco/pint@master#egg=pint git+git://github.com/pydata/xarray@master#egg=xarray" + - EXTRA_PACKAGES="$EXTRA_PACKAGES setuptools_scm[toml]" allow_failures: - python: "3.8-dev" - python: nightly diff --git a/docs/index.rst b/docs/index.rst index 1bfc13a5775..032b85436e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -143,4 +143,4 @@ Related Projects .. _netCDF: https://www.unidata.ucar.edu/software/netcdf/ .. _siphon: https://unidata.github.io/siphon/ .. _Unidata Python Gallery: https://unidata.github.io/python-training/gallery/gallery-home -__ https://www.unidata.ucar.edu/software/thredds/current/tds/ +__ https://www.unidata.ucar.edu/software/tds/current/ diff --git a/docs/references.rst b/docs/references.rst index ca184da3fa8..5c766848c89 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -78,8 +78,8 @@ References doi:`10.1175/1520-0426(1987)004%3C0239:DOTPCO%3E2.0.CO;2 `_. -.. [FCMR192003] Federal Coordinator for Meteorological Services and Supporting Research: Report on - Wind Chill Temperature and Extreme Heat Indices: Evaluation and Improvement +.. [FCMR192003] Federal Coordinator for Meteorological Services and Supporting Research: Report + on Wind Chill Temperature and Extreme Heat Indices: Evaluation and Improvement Projects. Washington, DC: Office of the Federal Coordinator for Meteorological Services and Supporting Research, 2003. `FCM-R19-2003 <_static/FCM-R19-2003-WindchillReport.pdf>`_, 75 pp. @@ -92,7 +92,8 @@ References .. [Garratt1994] Garratt, J.R., 1994: *The Atmospheric Boundary Layer*. Cambridge University Press, 316 pp. -.. [Holton2004] Holton, J. R., 2004: *An Introduction to Dynamic Meteorology*. 4th ed. Academic Press, 535 pp. +.. [Holton2004] Holton, J. R., 2004: *An Introduction to Dynamic Meteorology*. 4th ed. + Academic Press, 535 pp. .. [Koch1983] Koch, S. E., M. DesJardins, and P. J. Kocin, 1983: An interactive Barnes objective map analysis scheme for use with satellite and conventional data. @@ -122,8 +123,8 @@ References .. [Rochette2006] Rochette, Scott M., and Patrick S. Market. "A primer on the ageostrophic wind." Natl. Weather Dig. 30 (2006): 17-28. -.. [Rothfusz1990] Rothfusz, L.P.: *The Heat Index "Equation"*. Fort Worth, TX: Scientific Services - Division, NWS Southern Region Headquarters, 1990. +.. [Rothfusz1990] Rothfusz, L.P.: *The Heat Index "Equation"*. Fort Worth, TX: Scientific + Services Division, NWS Southern Region Headquarters, 1990. `SR90-23 <_static/rothfusz-1990-heat-index-equation.pdf>`_, 2 pp. .. [Salby1996] Salby, M. L., 1996: *Fundamentals of Atmospheric Physics*. diff --git a/examples/meteogram_metpy.py b/examples/meteogram_metpy.py index 844209136f7..5fdddfb5840 100644 --- a/examples/meteogram_metpy.py +++ b/examples/meteogram_metpy.py @@ -80,10 +80,10 @@ def plot_winds(self, ws, wd, wsmax, plot_range=None): ax7.set_ylabel('Wind\nDirection\n(degrees)', multialignment='center') ax7.set_ylim(0, 360) ax7.set_yticks(np.arange(45, 405, 90), ['NE', 'SE', 'SW', 'NW']) - lns = ln1 + ln2 + ln3 - labs = [l.get_label() for l in lns] + lines = ln1 + ln2 + ln3 + labs = [line.get_label() for line in lines] ax7.xaxis.set_major_formatter(mpl.dates.DateFormatter('%d/%H UTC')) - ax7.legend(lns, labs, loc='upper center', + ax7.legend(lines, labs, loc='upper center', bbox_to_anchor=(0.5, 1.2), ncol=3, prop={'size': 12}) def plot_thermo(self, t, td, plot_range=None): @@ -111,11 +111,11 @@ def plot_thermo(self, t, td, plot_range=None): ax_twin = self.ax2.twinx() ax_twin.set_ylim(plot_range[0], plot_range[1], plot_range[2]) - lns = ln4 + ln5 - labs = [l.get_label() for l in lns] + lines = ln4 + ln5 + labs = [line.get_label() for line in lines] ax_twin.xaxis.set_major_formatter(mpl.dates.DateFormatter('%d/%H UTC')) - self.ax2.legend(lns, labs, loc='upper center', + self.ax2.legend(lines, labs, loc='upper center', bbox_to_anchor=(0.5, 1.2), ncol=2, prop={'size': 12}) def plot_rh(self, rh, plot_range=None): diff --git a/src/metpy/__init__.py b/src/metpy/__init__.py index 9b01fb469a7..ee1199205ec 100644 --- a/src/metpy/__init__.py +++ b/src/metpy/__init__.py @@ -32,6 +32,6 @@ os.environ['PINT_ARRAY_PROTOCOL_FALLBACK'] = '0' from ._version import get_version # noqa: E402 -from .xarray import * # noqa: F401, F403 +from .xarray import * # noqa: F401, F403, E402 __version__ = get_version() del get_version diff --git a/src/metpy/calc/basic.py b/src/metpy/calc/basic.py index 5d24e816377..7aa5a4d9f45 100644 --- a/src/metpy/calc/basic.py +++ b/src/metpy/calc/basic.py @@ -136,8 +136,7 @@ def wind_components(speed, wind_direction): -------- >>> from metpy.units import units >>> metpy.calc.wind_components(10. * units('m/s'), 225. * units.deg) - (, - ) + (, ) """ wind_direction = _check_radians(wind_direction, max_radians=4 * np.pi) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index f7007ee1209..3574ea33398 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -124,7 +124,7 @@ def potential_temperature(pressure, temperature): -------- >>> from metpy.units import units >>> metpy.calc.potential_temperature(800. * units.mbar, 273. * units.kelvin) - + """ return temperature / exner_function(pressure) diff --git a/src/metpy/plots/_mpl.py b/src/metpy/plots/_mpl.py index a65b246bcc7..084055111aa 100644 --- a/src/metpy/plots/_mpl.py +++ b/src/metpy/plots/_mpl.py @@ -100,6 +100,12 @@ def scattertext(self, x, y, texts, loc=(0, 0), **kw): # Add it to the axes and update range self.add_artist(text_obj) + + # Matplotlib at least up to 3.2.2 does not properly clip text with paths, so + # work-around by setting to the bounding box of the Axes + # TODO: Remove when fixed in our minimum supported version of matplotlib + text_obj.clipbox = self.bbox + self.update_datalim(text_obj.get_datalim(self.transData)) self.autoscale_view() return text_obj @@ -219,8 +225,9 @@ def draw(self, renderer): if renderer.flipy(): y = canvash - y - # Can simplify next two lines once support for matplotlib<3.1 is dropped - check_line = getattr(self, '_preprocess_math', self.is_math_text) + # Can simplify next three lines once support for matplotlib<3.1 is dropped + is_math_text = getattr(self, 'is_math_text', False) + check_line = getattr(self, '_preprocess_math', is_math_text) clean_line, ismath = check_line(line) if self.get_path_effects(): diff --git a/src/metpy/plots/cartopy_utils.py b/src/metpy/plots/cartopy_utils.py index 0140946f990..dd7c5891927 100644 --- a/src/metpy/plots/cartopy_utils.py +++ b/src/metpy/plots/cartopy_utils.py @@ -3,28 +3,39 @@ # SPDX-License-Identifier: BSD-3-Clause """Cartopy specific mapping utilities.""" +import cartopy.crs as ccrs import cartopy.feature as cfeature from ..cbook import get_test_data -class MetPyMapFeature(cfeature.NaturalEarthFeature): - """A simple interface to US County shapefiles.""" +class MetPyMapFeature(cfeature.Feature): + """A simple interface to MetPy-included shapefiles.""" def __init__(self, name, scale, **kwargs): - """Create USCountiesFeature instance.""" - super().__init__('', name, scale, **kwargs) + """Create MetPyMapFeature instance.""" + super().__init__(ccrs.PlateCarree(), **kwargs) + self.name = name + + if isinstance(scale, str): + scale = cfeature.Scaler(scale) + self.scaler = scale def geometries(self): """Return an iterator of (shapely) geometries for this feature.""" import cartopy.io.shapereader as shapereader # Ensure that the associated files are in the cache - fname = '{}_{}'.format(self.name, self.scale) + fname = '{}_{}'.format(self.name, self.scaler.scale) for extension in ['.dbf', '.shx']: get_test_data(fname + extension) path = get_test_data(fname + '.shp', as_file_obj=False) return iter(tuple(shapereader.Reader(path).geometries())) + def intersecting_geometries(self, extent): + """Return geometries that intersect the extent.""" + self.scaler.scale_from_extent(extent) + return super().intersecting_geometries(extent) + def with_scale(self, new_scale): """ Return a copy of the feature with a new scale. diff --git a/src/metpy/plots/skewt.py b/src/metpy/plots/skewt.py index d8619de03a2..9d35cd161ae 100644 --- a/src/metpy/plots/skewt.py +++ b/src/metpy/plots/skewt.py @@ -8,6 +8,7 @@ """ from contextlib import ExitStack +import warnings import matplotlib from matplotlib.axes import Axes @@ -126,7 +127,11 @@ class SkewXAxis(maxis.XAxis): """ def _get_tick(self, major): - return SkewXTick(self.axes, None, '', major=major) + # Warning stuff can go away when we only support Matplotlib >=3.3 + with warnings.catch_warnings(): + warnings.simplefilter('ignore', getattr( + matplotlib, 'MatplotlibDeprecationWarning', DeprecationWarning)) + return SkewXTick(self.axes, None, label=None, major=major) # Needed to properly handle tight bbox def _get_tick_bboxes(self, ticks, renderer): diff --git a/src/metpy/plots/wx_symbols.py b/src/metpy/plots/wx_symbols.py index 8808c6182ff..39134409be0 100644 --- a/src/metpy/plots/wx_symbols.py +++ b/src/metpy/plots/wx_symbols.py @@ -106,13 +106,13 @@ def __init__(self, num, font_start, font_jumps=None, char_jumps=None): font_point += 1 @staticmethod - def _safe_pop(l): + def _safe_pop(lst): """Safely pop from a list. Returns None if list empty. """ - return l.pop(0) if l else None + return lst.pop(0) if lst else None def __call__(self, code): """Return the Unicode code point corresponding to `code`.""" diff --git a/src/metpy/testing.py b/src/metpy/testing.py index dc80b740972..cdd8d8a7eaa 100644 --- a/src/metpy/testing.py +++ b/src/metpy/testing.py @@ -199,17 +199,6 @@ def set_agg_backend(): plt.switch_backend(prev_backend) -@pytest.fixture(autouse=True) -def patch_round(monkeypatch): - """Fixture to patch builtin round using numpy's. - - This works around the fact that built-in round changed between Python 2 and 3. This - is probably not needed once we're testing on matplotlib 2.0, which has been updated - to use numpy's throughout. - """ - monkeypatch.setitem(__builtins__, 'round', np.round) - - def check_and_silence_warning(warn_type): """Decorate a function to swallow some warning type, making sure they are present. diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index 69ec2511256..284b8697114 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -808,6 +808,7 @@ def test_most_unstable_parcel(): assert_almost_equal(ret[2], 19.0 * units.degC, 6) +@pytest.mark.filterwarnings('ignore:invalid value:RuntimeWarning') def test_isentropic_pressure(): """Test calculation of isentropic pressure function.""" lev = [100000., 95000., 90000., 85000.] * units.Pa @@ -819,8 +820,7 @@ def test_isentropic_pressure(): tmp[:, :, -1] = np.nan tmpk = tmp * units.kelvin isentlev = [296.] * units.kelvin - with pytest.warns(RuntimeWarning, match='invalid value'): - isentprs = isentropic_interpolation(isentlev, lev, tmpk) + isentprs = isentropic_interpolation(isentlev, lev, tmpk) trueprs = np.ones((1, 5, 5)) * (1000. * units.hPa) trueprs[:, :, -1] = np.nan assert isentprs[0].shape == (1, 5, 5) diff --git a/tests/plots/baseline/test_arrow_projection.png b/tests/plots/baseline/test_arrow_projection.png index e99617251cd..e338acc350c 100644 Binary files a/tests/plots/baseline/test_arrow_projection.png and b/tests/plots/baseline/test_arrow_projection.png differ diff --git a/tests/plots/baseline/test_barb_projection.png b/tests/plots/baseline/test_barb_projection.png index 0b18e53cd81..765921a3266 100644 Binary files a/tests/plots/baseline/test_barb_projection.png and b/tests/plots/baseline/test_barb_projection.png differ diff --git a/tests/plots/baseline/test_colorfill.png b/tests/plots/baseline/test_colorfill.png index 21b19663067..f4b382a8c41 100644 Binary files a/tests/plots/baseline/test_colorfill.png and b/tests/plots/baseline/test_colorfill.png differ diff --git a/tests/plots/baseline/test_colorfill_horiz_colorbar.png b/tests/plots/baseline/test_colorfill_horiz_colorbar.png index 8d3630a6a11..7958a2cc28f 100644 Binary files a/tests/plots/baseline/test_colorfill_horiz_colorbar.png and b/tests/plots/baseline/test_colorfill_horiz_colorbar.png differ diff --git a/tests/plots/baseline/test_colorfill_no_colorbar.png b/tests/plots/baseline/test_colorfill_no_colorbar.png index b70baf7de5d..26da7345d10 100644 Binary files a/tests/plots/baseline/test_colorfill_no_colorbar.png and b/tests/plots/baseline/test_colorfill_no_colorbar.png differ diff --git a/tests/plots/baseline/test_declarative_contour_convert_units.png b/tests/plots/baseline/test_declarative_contour_convert_units.png index a2afdd08056..6e8783aa43f 100644 Binary files a/tests/plots/baseline/test_declarative_contour_convert_units.png and b/tests/plots/baseline/test_declarative_contour_convert_units.png differ diff --git a/tests/plots/baseline/test_declarative_contour_options.png b/tests/plots/baseline/test_declarative_contour_options.png index db82cc68264..1a234544f5c 100644 Binary files a/tests/plots/baseline/test_declarative_contour_options.png and b/tests/plots/baseline/test_declarative_contour_options.png differ diff --git a/tests/plots/test_cartopy_utils.py b/tests/plots/test_cartopy_utils.py index 57259b202b1..6d2edd97be4 100644 --- a/tests/plots/test_cartopy_utils.py +++ b/tests/plots/test_cartopy_utils.py @@ -10,7 +10,7 @@ from metpy.plots import USCOUNTIES, USSTATES # Fixtures to make sure we have the right backend and consistent round -from metpy.testing import patch_round, set_agg_backend # noqa: F401, I202 +from metpy.testing import set_agg_backend # noqa: F401, I202 MPL_VERSION = matplotlib.__version__[:3] diff --git a/tests/plots/test_declarative.py b/tests/plots/test_declarative.py index 04639f1d50b..5169c149282 100644 --- a/tests/plots/test_declarative.py +++ b/tests/plots/test_declarative.py @@ -10,6 +10,7 @@ import cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib +import numpy as np import pandas as pd import pytest from traitlets import TraitError @@ -77,8 +78,22 @@ def test_declarative_contour(): return pc.figure +@pytest.fixture +def fix_is_closed_polygon(monkeypatch): + """Fix matplotlib.contour._is_closed_polygons for tests. + + Needed because for Matplotlib<3.3, the internal matplotlib.contour._is_closed_polygon + uses strict floating point equality. This causes the test below to yield different + results for macOS vs. Linux/Windows. + + """ + monkeypatch.setattr(matplotlib.contour, '_is_closed_polygon', + lambda X: np.allclose(X[0], X[-1], rtol=1e-10, atol=1e-13), + raising=False) + + @pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.035) -def test_declarative_contour_options(): +def test_declarative_contour_options(fix_is_closed_polygon): """Test making a contour plot.""" data = xr.open_dataset(get_test_data('narr_example.nc', as_file_obj=False)) @@ -107,7 +122,7 @@ def test_declarative_contour_options(): @pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.035) -def test_declarative_contour_convert_units(): +def test_declarative_contour_convert_units(fix_is_closed_polygon): """Test making a contour plot.""" data = xr.open_dataset(get_test_data('narr_example.nc', as_file_obj=False)) @@ -419,7 +434,7 @@ def test_declarative_barb_earth_relative(): return pc.figure -@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.346) +@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.347) def test_declarative_barb_gfs(): """Test making a contour plot.""" data = xr.open_dataset(get_test_data('GFS_test.nc', as_file_obj=False)) @@ -542,7 +557,7 @@ def test_declarative_sfc_obs_changes(): return pc.figure -@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0) +@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.00586) def test_declarative_colored_barbs(): """Test making a surface plot with a colored barb (gh-1274).""" data = pd.read_csv(get_test_data('SFC_obs.csv', as_file_obj=False), @@ -575,7 +590,8 @@ def test_declarative_colored_barbs(): @pytest.mark.mpl_image_compare(remove_text=True, - tolerance={'3.1': 9.771, '2.1': 9.771}.get(MPL_VERSION, 0.)) + tolerance={'3.1': 9.771, + '2.1': 9.771}.get(MPL_VERSION, 0.00651)) def test_declarative_sfc_obs_full(): """Test making a full surface observation plot.""" data = pd.read_csv(get_test_data('SFC_obs.csv', as_file_obj=False), diff --git a/tests/plots/test_skewt.py b/tests/plots/test_skewt.py index 65980e460c6..21036fbc3d3 100644 --- a/tests/plots/test_skewt.py +++ b/tests/plots/test_skewt.py @@ -11,7 +11,7 @@ from metpy.plots import Hodograph, SkewT # Fixtures to make sure we have the right backend and consistent round -from metpy.testing import patch_round, set_agg_backend # noqa: F401, I202 +from metpy.testing import set_agg_backend # noqa: F401, I202 from metpy.units import units diff --git a/tests/plots/test_station_plot.py b/tests/plots/test_station_plot.py index 72c6883ca55..bddce65ad6d 100644 --- a/tests/plots/test_station_plot.py +++ b/tests/plots/test_station_plot.py @@ -13,7 +13,7 @@ from metpy.plots import (current_weather, high_clouds, nws_layout, simple_layout, sky_cover, StationPlot, StationPlotLayout) # Fixtures to make sure we have the right backend and consistent round -from metpy.testing import patch_round, set_agg_backend # noqa: F401, I202 +from metpy.testing import set_agg_backend # noqa: F401, I202 from metpy.units import units @@ -279,7 +279,7 @@ def wind_plot(): return u, v, x, y -@pytest.mark.mpl_image_compare(tolerance=0.00323, remove_text=True) +@pytest.mark.mpl_image_compare(tolerance=0.00434, remove_text=True) def test_barb_projection(wind_plot): """Test that barbs are properly projected (#598).""" u, v, x, y = wind_plot @@ -287,14 +287,14 @@ def test_barb_projection(wind_plot): # Plot and check barbs (they should align with grid lines) fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection=ccrs.LambertConformal()) - ax.gridlines(xlocs=[-135, -120, -105, -90, -75, -60, -45]) + ax.gridlines(xlocs=[-120, -105, -90, -75, -60], ylocs=np.arange(24, 55, 6)) sp = StationPlot(ax, x, y, transform=ccrs.PlateCarree()) sp.plot_barb(u, v) return fig -@pytest.mark.mpl_image_compare(tolerance=0.00205, remove_text=True) +@pytest.mark.mpl_image_compare(tolerance=0.00382, remove_text=True) def test_arrow_projection(wind_plot): """Test that arrows are properly projected.""" u, v, x, y = wind_plot @@ -302,7 +302,7 @@ def test_arrow_projection(wind_plot): # Plot and check barbs (they should align with grid lines) fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection=ccrs.LambertConformal()) - ax.gridlines(xlocs=[-135, -120, -105, -90, -75, -60, -45]) + ax.gridlines(xlocs=[-120, -105, -90, -75, -60], ylocs=np.arange(24, 55, 6)) sp = StationPlot(ax, x, y, transform=ccrs.PlateCarree()) sp.plot_arrow(u, v) sp.plot_arrow(u, v) # plot_arrow used twice to hit removal if statement