From 8487dfb53efc725a446835f645d006e2e55a7535 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Tue, 27 Aug 2024 10:50:43 -0500 Subject: [PATCH 1/4] Allow overriding area repr map section HTML --- pyresample/_formatting_html.py | 41 ++++++++++++++-------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/pyresample/_formatting_html.py b/pyresample/_formatting_html.py index 462f34a1..b44c30f6 100644 --- a/pyresample/_formatting_html.py +++ b/pyresample/_formatting_html.py @@ -161,28 +161,6 @@ def collapsible_section(name: str, inline_details: Optional[str] = "", details: ) -def map_section(area: Union['geom.AreaDefinition', 'geom.SwathDefinition']) -> str: # noqa F821 - """Create html for map section. - - Args: - area : AreaDefinition or SwathDefinition. - - Returns: - Html with collapsible section with a cartopy plot. - - """ - map_icon = _icon("icon-globe") - - if cartopy: - coll = collapsible_section("Map", details=plot_area_def(area, fmt="svg"), collapsed=True, icon=map_icon) - else: - coll = collapsible_section("Map", - details="Note: If cartopy is installed a display of the area can be seen here", - collapsed=True, icon=map_icon) - - return f"{coll}" - - def proj_area_attrs_section(area: 'geom.AreaDefinition') -> str: # noqa F821 """Create html for attribute section based on an area Area. @@ -308,7 +286,9 @@ def swath_area_attrs_section(area: 'geom.SwathDefinition') -> str: # noqa F821 def area_repr(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], include_header: bool = True, - include_static_files: bool = True): + include_static_files: bool = True, + map_content: str | None = None, + ): """Return html repr of an AreaDefinition. Args: @@ -318,6 +298,8 @@ def area_repr(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], display in the overview of area definitions for the Satpy documentation this should be set to false. include_static_files : Load and include css and html needed for representation. + map_content : Optionally override the map section contents. Can be any string + that is valid HTML between a "
" tag. Returns: Html. @@ -347,7 +329,18 @@ def area_repr(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], html += "
" if isinstance(area, geom.AreaDefinition): html += proj_area_attrs_section(area) - html += map_section(area) + map_icon = _icon("icon-globe") + if map_content is None: + if cartopy: + map_content = plot_area_def(area, fmt="svg") + else: + map_content = "Note: If cartopy is installed a display of the area can be seen here" + coll = collapsible_section("Map", + details=map_content, + collapsed=True, + icon=map_icon) + + html += str(coll) elif isinstance(area, geom.SwathDefinition): html += swath_area_attrs_section(area) From 2e7fb76ce531fff9cbc61c94a4a020ac4d72fd38 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Tue, 27 Aug 2024 10:58:31 -0500 Subject: [PATCH 2/4] Allow customizing features used in area plotting --- pyresample/_formatting_html.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pyresample/_formatting_html.py b/pyresample/_formatting_html.py index b44c30f6..5ef54300 100644 --- a/pyresample/_formatting_html.py +++ b/pyresample/_formatting_html.py @@ -17,6 +17,7 @@ from __future__ import annotations import uuid +from collections.abc import Iterable from functools import lru_cache from html import escape from importlib.resources import read_binary @@ -66,7 +67,9 @@ def _icon(icon_name): def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # noqa F821 - fmt: Optional[Literal["svg", "png", None]] = None) -> Union[str, None]: + fmt: Optional[Literal["svg", "png", None]] = None, + features: Iterable[str] = ("ocean", "land", "coastline", "borders"), + ) -> Union[str, None]: """Plot area. Args: @@ -74,6 +77,10 @@ def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # fmt : Output format of the plot. The output is the string representation of the respective format xml for svg and base64 for png. Either svg or png. If None (default) plot is just shown. + features: Series of string names of cartopy features to add to the plot. + Can be lowercase or uppercase names of the features, for example, + "land", "coastline", "borders", or any other feature available from + ``cartopy.feature``. Returns: svg or png image as string. @@ -98,11 +105,12 @@ def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # ax.add_geometries([poly], crs=cartopy.crs.CRS(area.crs), facecolor="none", edgecolor="red") bounds = poly.buffer(5).bounds ax.set_extent([bounds[0], bounds[2], bounds[1], bounds[3]], crs=cartopy.crs.CRS(area.crs)) + else: + raise NotImplementedError("Only AreaDefinition and SwathDefinition objects can be plotted") - ax.add_feature(cartopy.feature.OCEAN) - ax.add_feature(cartopy.feature.LAND) - ax.add_feature(cartopy.feature.COASTLINE) - ax.add_feature(cartopy.feature.BORDERS) + for feat_name in features: + feat_obj = getattr(cartopy.feature, feat_name.upper()) + ax.add_feature(feat_obj) plt.tight_layout(pad=0) @@ -111,14 +119,12 @@ def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # plt.savefig(svg_str, format="svg", bbox_inches="tight") plt.close() return svg_str.getvalue() - elif fmt == "png": png_str = BytesIO() plt.savefig(png_str, format="png", bbox_inches="tight") img_str = f"" plt.close() return img_str - else: plt.show() return None From 979e6b2778c23e930708a77f58b11eb2c8179ab8 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Tue, 27 Aug 2024 11:13:33 -0500 Subject: [PATCH 3/4] Add more area plotting tests --- pyresample/_formatting_html.py | 8 +++++-- pyresample/geometry.py | 4 ++-- pyresample/test/test_formatting.py | 38 +++++++++++++++++------------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/pyresample/_formatting_html.py b/pyresample/_formatting_html.py index 5ef54300..b4800aad 100644 --- a/pyresample/_formatting_html.py +++ b/pyresample/_formatting_html.py @@ -68,7 +68,7 @@ def _icon(icon_name): def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # noqa F821 fmt: Optional[Literal["svg", "png", None]] = None, - features: Iterable[str] = ("ocean", "land", "coastline", "borders"), + features: Optional[Iterable[str]] = None, ) -> Union[str, None]: """Plot area. @@ -80,7 +80,8 @@ def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # features: Series of string names of cartopy features to add to the plot. Can be lowercase or uppercase names of the features, for example, "land", "coastline", "borders", or any other feature available from - ``cartopy.feature``. + ``cartopy.feature``. If None (default), then land, coastline, borders, + and ocean are used. Returns: svg or png image as string. @@ -108,6 +109,9 @@ def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # else: raise NotImplementedError("Only AreaDefinition and SwathDefinition objects can be plotted") + if features is None: + features = ("ocean", "land", "coastline", "borders") + for feat_name in features: feat_obj = getattr(cartopy.feature, feat_name.upper()) ax.add_feature(feat_obj) diff --git a/pyresample/geometry.py b/pyresample/geometry.py index 9463fb6e..55a5cc12 100644 --- a/pyresample/geometry.py +++ b/pyresample/geometry.py @@ -2121,8 +2121,8 @@ def update_hash(self, existing_hash: Optional[_Hash] = None) -> _Hash: if existing_hash is None: existing_hash = hashlib.sha1() # nosec: B324 existing_hash.update(self.crs_wkt.encode('utf-8')) - existing_hash.update(np.array(self.shape)) # type: ignore[arg-type] - existing_hash.update(np.array(self.area_extent)) # type: ignore[arg-type] + existing_hash.update(np.array(self.shape)) + existing_hash.update(np.array(self.area_extent)) return existing_hash @daskify_2in_2out diff --git a/pyresample/test/test_formatting.py b/pyresample/test/test_formatting.py index 21d0defc..de7cb587 100644 --- a/pyresample/test/test_formatting.py +++ b/pyresample/test/test_formatting.py @@ -19,6 +19,8 @@ import unittest.mock as mock from unittest.mock import ANY +import pytest + import pyresample from pyresample._formatting_html import ( area_repr, @@ -29,25 +31,21 @@ from .test_geometry.test_swath import _gen_swath_def_numpy, _gen_swath_def_xarray_dask -def test_plot_area_def_w_area_def(area_def_stere_source): # noqa F811 - """Test AreaDefinition plotting as svg/png.""" - area = area_def_stere_source - - with mock.patch('matplotlib.pyplot.savefig') as mock_savefig: - plot_area_def(area, fmt="svg") - mock_savefig.asser_called_with(ANY, format="svg", bbox_inches="tight") - mock_savefig.reset_mock() - plot_area_def(area, fmt="png") - mock_savefig.assert_called_with(ANY, format="png", bbox_inches="tight") - - -def test_plot_area_def_w_area_def_show(area_def_stere_source): # noqa F811 +@pytest.mark.parametrize("format", ["svg", "png", None]) +@pytest.mark.parametrize("features", [None, ("coastline",)]) +def test_plot_area_def_w_area_def(area_def_stere_source, format, features): # noqa F811 """Test AreaDefinition plotting as svg/png.""" area = area_def_stere_source - with mock.patch('matplotlib.pyplot.show') as mock_show_plot: - plot_area_def(area) - mock_show_plot.assert_called_once() + with mock.patch('matplotlib.pyplot.savefig') as mock_savefig, \ + mock.patch('matplotlib.pyplot.show') as mock_show_plot: + plot_area_def(area, fmt=format) + if format is None: + mock_show_plot.assert_called_once() + mock_savefig.assert_not_called() + else: + mock_show_plot.assert_not_called() + mock_savefig.asser_called_with(ANY, format=format, bbox_inches="tight") def test_plot_area_def_w_swath_def(create_test_swath): @@ -74,6 +72,14 @@ def test_area_def_cartopy_installed(area_def_stere_source): # noqa F811 assert "Note: If cartopy is installed a display of the area can be seen here" not in area._repr_html_() +def test_area_repr_custom_map(area_def_stere_source): # noqa F811 + """Test custom map section of area repr.""" + area = area_def_stere_source + res = area_repr(area, include_header=False, include_static_files=False, + map_content="TEST") + assert "TEST" in res + + def test_area_repr_w_static_files(area_def_stere_source): # noqa F811 """Test area representation with static files (css/icons) included.""" area_def = area_def_stere_source From 8002a7a3482b99afad01e74e392aedf24edf9d05 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Tue, 27 Aug 2024 11:15:06 -0500 Subject: [PATCH 4/4] Add more area plotting tests --- pyresample/_formatting_html.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyresample/_formatting_html.py b/pyresample/_formatting_html.py index b4800aad..d015f298 100644 --- a/pyresample/_formatting_html.py +++ b/pyresample/_formatting_html.py @@ -79,12 +79,14 @@ def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # If None (default) plot is just shown. features: Series of string names of cartopy features to add to the plot. Can be lowercase or uppercase names of the features, for example, - "land", "coastline", "borders", or any other feature available from - ``cartopy.feature``. If None (default), then land, coastline, borders, - and ocean are used. + "land", "coastline", "borders", "ocean", or any other feature + available from ``cartopy.feature``. If None (default), then land, + coastline, and borders are used. Returns: - svg or png image as string. + svg or png image as string or ``None`` when no format is provided + in which case the plot is shown interactively. + """ import base64 from io import BytesIO, StringIO @@ -110,7 +112,7 @@ def plot_area_def(area: Union['geom.AreaDefinition', 'geom.SwathDefinition'], # raise NotImplementedError("Only AreaDefinition and SwathDefinition objects can be plotted") if features is None: - features = ("ocean", "land", "coastline", "borders") + features = ("land", "coastline", "borders") for feat_name in features: feat_obj = getattr(cartopy.feature, feat_name.upper())