From efd077369cd3d03f5d4a645665149bdc1b5cdc1f Mon Sep 17 00:00:00 2001 From: ghiggi Date: Thu, 23 Nov 2023 17:42:28 +0100 Subject: [PATCH] Deprecate get_edge_bbox_in_projection_coordinates --- pyresample/boundary.py | 208 +++++++++++++++++--- pyresample/geometry.py | 35 ++-- pyresample/slicer.py | 17 +- pyresample/test/test_boundary.py | 40 ++-- pyresample/test/test_geometry/test_dummy.py | 163 --------------- pyresample/test/test_geometry/test_swath.py | 2 +- pyresample/visualization/__init__.py | 24 ++- pyresample/visualization/geometries.py | 22 +-- 8 files changed, 254 insertions(+), 257 deletions(-) delete mode 100644 pyresample/test/test_geometry/test_dummy.py diff --git a/pyresample/boundary.py b/pyresample/boundary.py index 569d0df0..f6867ed8 100644 --- a/pyresample/boundary.py +++ b/pyresample/boundary.py @@ -27,33 +27,6 @@ logger = logging.getLogger(__name__) -class Boundary(object): - """Boundary objects.""" - - def __init__(self, lons=None, lats=None, frequency=1): - self._contour_poly = None - if lons is not None: - self.lons = lons[::frequency] - if lats is not None: - self.lats = lats[::frequency] - - def contour(self): - """Get lon/lats of the contour.""" - return self.lons, self.lats - - @property - def contour_poly(self): - """Get the Spherical polygon corresponding to the Boundary.""" - if self._contour_poly is None: - self._contour_poly = SphPolygon( - np.deg2rad(np.vstack(self.contour()).T)) - return self._contour_poly - - def draw(self, mapper, options, **more_options): - """Draw the current boundary on the *mapper*.""" - self.contour_poly.draw(mapper, options, **more_options) - - def _is_corner_is_clockwise(lon1, lat1, corner_lon, corner_lat, lon2, lat2): """Determine if coordinates follow a clockwise path. @@ -99,7 +72,26 @@ def _check_sides_list(sides): # - Numpy array elements of at least length 2 -class AreaBoundary(Boundary): +# Potentially shared method +# --> _x, _y ? +# --> _sides_* +# --> sides_*, # BoundarySide + +# sides_* = boundary.sides_* (as property) ? Depending on _side_* +# sides_lon, sides_lat = boundary.sides +# --> sides_lon, sides_lat of BoundarySide? +# --> iter to behave as list ? + +# (x/lons), (y/lats), +# --> contour, +# --> vertices, + +# set_clockwise +# set_counter_clockwise, +# _to_shapely_polygon + + +class GeographicBoundary(): """Area boundary objects. The inputs must be a (lon_coords, lat_coords) tuple for each of the 4 sides. @@ -109,7 +101,7 @@ def __init__(self, lon_sides, lat_sides, wished_order=None): _check_sides_list(lon_sides) _check_sides_list(lat_sides) - # Old interface + # Old interface for compatibility to AreaBoundary self._contour_poly = None self.sides_lons = lon_sides self.sides_lats = lat_sides @@ -264,7 +256,161 @@ def draw(self, mapper, options, **more_options): self.contour_poly.draw(mapper, options, **more_options) -class AreaDefBoundary(AreaBoundary): +class ProjectionBoundary(): + """Projection Boundary object. + + The inputs must be the x and y sides of the projection. + It expects the projection coordinates to be planar (i.e. metric, radians). + """ + + def __init__(self, sides_x, sides_y, wished_order=None, crs=None): + + self.crs = crs # TODO needed to plot + + # New interface + self.sides_x = sides_x + self.sides_y = sides_y + # TODO: self.sides (BoundarySide(s)) + + # Check if it is clockwise/counterclockwise + self.is_clockwise = self._is_projection_boundary_clockwise() + self.is_counterclockwise = not self.is_clockwise + + # Define wished order + if self.is_clockwise: + self._actual_order = "clockwise" + else: + self._actual_order = "counterclockwise" + + if wished_order is None: + self._wished_order = self._actual_order + else: + if wished_order not in ["clockwise", "counterclockwise"]: + raise ValueError("Valid order is 'clockwise' or 'counterclockwise'") + self._wished_order = wished_order + + def _is_projection_boundary_clockwise(self): + """Determine if the boundary is clockwise-defined in projection coordinates.""" + from shapely.geometry import Polygon + + x = np.concatenate([xs[:-1] for xs in self.sides_x]) + y = np.concatenate([ys[:-1] for ys in self.sides_y]) + x = np.hstack((x, x[0])) + y = np.hstack((y, y[0])) + polygon = Polygon(zip(x, y)) + return not polygon.exterior.is_ccw + + def set_clockwise(self): + """Set clockwise order for vertices retrieval.""" + self._wished_order = "clockwise" + return self + + def set_counterclockwise(self): + """Set counterclockwise order for vertices retrieval.""" + self._wished_order = "counterclockwise" + return self + + @property + def sides(self): + """Return the boundary sides as a tuple of (sides_x, sides_y) arrays.""" + return self.sides_x, self.sides_y + + @property + def x(self): + """Retrieve boundary x vertices.""" + xs = np.concatenate([xs[:-1] for xs in self.sides_x]) + if self._wished_order == self._actual_order: + return xs + else: + return xs[::-1] + + @property + def y(self): + """Retrieve boundary y vertices.""" + ys = np.concatenate([ys[:-1] for ys in self.sides_y]) + if self._wished_order == self._actual_order: + return ys + else: + return ys[::-1] + + @property + def vertices(self): + """Return boundary vertices 2D array [x, y].""" + vertices = np.vstack((self.x, self.y)).T + vertices = vertices.astype(np.float64, copy=False) + return vertices + + def contour(self, closed=False): + """Return the (x, y) tuple of the boundary object. + + If excludes the last element of each side because it's included in the next side. + If closed=False (the default), the last vertex is not equal to the first vertex + If closed=True, the last vertex is set to be equal to the first + closed=True is required for shapely Polygon creation. + """ + x = self.x + y = self.y + if closed: + x = np.hstack((x, x[0])) + y = np.hstack((y, y[0])) + return x, y + + def _to_shapely_polygon(self): + from shapely.geometry import Polygon + self = self.set_counterclockwise() + x, y = self.contour(closed=True) + return Polygon(zip(x, y)) + + def polygon(self, shapely=True): + """Return the boundary polygon.""" + if shapely: + return self._to_shapely_polygon() + else: + raise NotImplementedError("Only shapely polygon available.") + + def plot(self, ax=None, subplot_kw=None, crs=None, **kwargs): + """Plot the the boundary.""" + from pyresample.visualization.geometries import plot_geometries + + if self.crs is None and crs is None: + raise ValueError("Projection 'crs' is required to display projection boundary.") + if crs is None: + crs = self.crs + + geom = self.polygon(shapely=True) + p = plot_geometries(geometries=[geom], crs=crs, + ax=ax, subplot_kw=subplot_kw, **kwargs) + return p + + +class Boundary(object): + """Boundary objects.""" + + def __init__(self, lons=None, lats=None, frequency=1): + self._contour_poly = None + if lons is not None: + self.lons = lons[::frequency] + if lats is not None: + self.lats = lats[::frequency] + + def contour(self): + """Get lon/lats of the contour.""" + return self.lons, self.lats + + @property + def contour_poly(self): + """Get the Spherical polygon corresponding to the Boundary.""" + if self._contour_poly is None: + self._contour_poly = SphPolygon( + np.deg2rad(np.vstack(self.contour()).T)) + return self._contour_poly + + def draw(self, mapper, options, **more_options): + """Draw the current boundary on the *mapper*.""" + self.contour_poly.draw(mapper, options, **more_options) + + +class AreaDefBoundary(GeographicBoundary): """Boundaries for area definitions (pyresample).""" def __init__(self, area, frequency=1): @@ -272,7 +418,7 @@ def __init__(self, area, frequency=1): warnings.warn("'AreaDefBoundary' will be removed in the future. " + "Use the Swath/AreaDefinition 'boundary' method instead!.", PendingDeprecationWarning, stacklevel=2) - AreaBoundary.__init__(self, lon_sides=lon_sides, lat_sides=lat_sides) + GeographicBoundary.__init__(self, lon_sides=lon_sides, lat_sides=lat_sides) if frequency != 1: self.decimate(frequency) diff --git a/pyresample/geometry.py b/pyresample/geometry.py index b16bda25..948b1090 100644 --- a/pyresample/geometry.py +++ b/pyresample/geometry.py @@ -416,11 +416,7 @@ def _get_boundary_sides(self, coordinates="geographic", vertices_per_side: Optio if self.is_geostationary: return self._get_geostationary_boundary_sides(vertices_per_side=vertices_per_side, coordinates=coordinates) - # ELSE: - # NOT IMPLEMENTED --> Would change behaviour of get_edge_bbox_in_projection_coordinates - # Currently return the x,y coordinates of the full image border - - # if self.is_polar_projection + # if self.is_polar_projection # BUG # self.is_robinson # raise NotImplementedError("Likely a polar projection.") if coordinates == "geographic": @@ -547,20 +543,30 @@ def boundary(self, *, vertices_per_side=None, force_clockwise=False, frequency=N operations assume that coordinates are clockwise. Default is False. """ - from pyresample.boundary import AreaBoundary + from pyresample.boundary import GeographicBoundary, ProjectionBoundary + if frequency is not None: warnings.warn("The `frequency` argument is pending deprecation, use `vertices_per_side` instead", PendingDeprecationWarning, stacklevel=2) vertices_per_side = vertices_per_side or frequency - lon_sides, lat_sides = self._get_boundary_sides(coordinates=coordinates, - vertices_per_side=vertices_per_side) - # TODO: this could be changed but it would breaks backward compatibility - # TODO: Implement code to return projection boundary ! + x_sides, y_sides = self._get_boundary_sides(coordinates=coordinates, + vertices_per_side=vertices_per_side) + + # TODO: I would suggest to deprecate force_clockwise + # --> And use order/wished_order argument (None take as it is) if force_clockwise: wished_order = "clockwise" else: wished_order = None - return AreaBoundary(lon_sides, lat_sides, wished_order=wished_order) + + if coordinates == "geographic" or self.crs.is_geographic: + return GeographicBoundary(lon_sides=x_sides, + lat_sides=y_sides, + wished_order=wished_order) + else: + return ProjectionBoundary(sides_x=x_sides, + sides_y=y_sides, + wished_order=wished_order) def get_cartesian_coords(self, nprocs=None, data_slice=None, cache=False): """Retrieve cartesian coordinates of geometry definition. @@ -1686,9 +1692,12 @@ def get_edge_bbox_in_projection_coordinates(self, vertices_per_side: Optional[in if frequency is not None: warnings.warn("The `frequency` argument is pending deprecation, use `vertices_per_side` instead", PendingDeprecationWarning, stacklevel=2) + warnings.warn("The `get_edge_bbox_in_projection_coordinates` method is pending deprecation." + "Use `area.boundary(coordinates='projection').contour()` instead.", + PendingDeprecationWarning, stacklevel=2) vertices_per_side = vertices_per_side or frequency - x_sides, y_sides = self._get_boundary_sides(coordinates="projection", vertices_per_side=vertices_per_side) - return np.hstack(x_sides), np.hstack(y_sides) + x, y = self.boundary(coordinates="projection", vertices_per_side=vertices_per_side).contour(closed=True) + return x, y @property def area_extent(self): diff --git a/pyresample/slicer.py b/pyresample/slicer.py index eaff08f4..5649cc4a 100644 --- a/pyresample/slicer.py +++ b/pyresample/slicer.py @@ -27,11 +27,7 @@ from pyproj.enums import TransformDirection from pyresample import AreaDefinition, SwathDefinition -from pyresample.geometry import ( - IncompatibleAreas, - InvalidArea, - get_geostationary_bounding_box_in_proj_coords, -) +from pyresample.geometry import IncompatibleAreas, InvalidArea try: import dask.array as da @@ -99,7 +95,7 @@ class SwathSlicer(Slicer): def get_polygon_to_contain(self): """Get the shapely Polygon corresponding to *area_to_contain* in lon/lat coordinates.""" from shapely.geometry import Polygon - x, y = self.area_to_contain.get_edge_bbox_in_projection_coordinates(10) + x, y = self.area_to_contain.boundary(coordinates="projection", vertices_per_side=10).contour(closed=True) poly = Polygon(zip(*self._transformer.transform(x, y))) return poly @@ -148,9 +144,10 @@ class AreaSlicer(Slicer): def get_polygon_to_contain(self): """Get the shapely Polygon corresponding to *area_to_contain* in projection coordinates of *area_to_crop*.""" from shapely.geometry import Polygon - x, y = self.area_to_contain.get_edge_bbox_in_projection_coordinates(vertices_per_side=10) + x, y = self.area_to_contain.boundary(coordinates="projection", vertices_per_side=10).contour(closed=True) if self.area_to_crop.is_geostationary: - x_geos, y_geos = get_geostationary_bounding_box_in_proj_coords(self.area_to_crop, 360) + geo_boundary = self.area_to_crop.boundary(coordinates="projection", vertices_per_side=360) + x_geos, y_geos = geo_boundary.contour(closed=True) x_geos, y_geos = self._transformer.transform(x_geos, y_geos, direction=TransformDirection.INVERSE) geos_poly = Polygon(zip(x_geos, y_geos)) poly = Polygon(zip(x, y)) @@ -175,8 +172,8 @@ def get_slices_from_polygon(self, poly_to_contain): bounds = buffered_poly.bounds except ValueError as err: raise InvalidArea("Invalid area") from err - from shapely.geometry import Polygon - poly_to_crop = Polygon(zip(*self.area_to_crop.get_edge_bbox_in_projection_coordinates(vertices_per_side=10))) + + poly_to_crop = self.area_to_crop.boundary(coordinates="projection", vertices_per_side=10).polygon(shapely=True) if not poly_to_crop.intersects(buffered_poly): raise IncompatibleAreas("Areas not overlapping.") bounds = self._sanitize_polygon_bounds(bounds) diff --git a/pyresample/test/test_boundary.py b/pyresample/test/test_boundary.py index 6ebe6af8..8ee21d8f 100644 --- a/pyresample/test/test_boundary.py +++ b/pyresample/test/test_boundary.py @@ -22,14 +22,14 @@ import numpy as np import pytest -from pyresample.boundary import AreaBoundary +from pyresample.boundary import GeographicBoundary -class TestAreaBoundary(unittest.TestCase): - """Test 'AreaBoundary' class.""" +class TestGeographicBoundary(unittest.TestCase): + """Test 'GeographicBoundary' class.""" def test_creation_from_lonlat_sides(self): - """Test AreaBoundary creation from sides.""" + """Test GeographicBoundary creation from sides.""" lon_sides = [np.array([1.0, 1.5, 2.0]), np.array([2.0, 3.0]), np.array([3.0, 3.5, 4.0]), @@ -39,8 +39,8 @@ def test_creation_from_lonlat_sides(self): np.array([8.0, 8.5, 9.0]), np.array([9.0, 6.0])] - # Define AreaBoundary - boundary = AreaBoundary.from_lonlat_sides(lon_sides, lat_sides) + # Define GeographicBoundary + boundary = GeographicBoundary.from_lonlat_sides(lon_sides, lat_sides) # Assert sides coincides for b_lon, src_lon in zip(boundary.sides_lons, lon_sides): @@ -50,7 +50,7 @@ def test_creation_from_lonlat_sides(self): assert np.allclose(b_lat, src_lat) def test_creation(self): - """Test AreaBoundary creation.""" + """Test GeographicBoundary creation.""" lon_sides = [np.array([1.0, 1.5, 2.0]), np.array([2.0, 3.0]), np.array([3.0, 3.5, 4.0]), @@ -60,8 +60,8 @@ def test_creation(self): np.array([8.0, 8.5, 9.0]), np.array([9.0, 6.0])] - # Define AreaBoundary - boundary = AreaBoundary(lon_sides, lat_sides) + # Define GeographicBoundary + boundary = GeographicBoundary(lon_sides, lat_sides) # Assert sides coincides for b_lon, src_lon in zip(boundary.sides_lons, lon_sides): @@ -71,7 +71,7 @@ def test_creation(self): assert np.allclose(b_lat, src_lat) def test_number_sides_required(self): - """Test AreaBoundary requires 4 sides .""" + """Test GeographicBoundary requires 4 sides .""" lon_sides = [np.array([1.0, 1.5, 2.0]), np.array([2.0, 3.0]), np.array([4.0, 1.0])] @@ -79,10 +79,10 @@ def test_number_sides_required(self): np.array([7.0, 8.0]), np.array([9.0, 6.0])] with pytest.raises(ValueError): - AreaBoundary(lon_sides, lat_sides) + GeographicBoundary(lon_sides, lat_sides) def test_vertices_property(self): - """Test AreaBoundary vertices property.""" + """Test GeographicBoundary vertices property.""" lon_sides = [np.array([1.0, 1.5, 2.0]), np.array([2.0, 3.0]), np.array([3.0, 3.5, 4.0]), @@ -91,8 +91,8 @@ def test_vertices_property(self): np.array([7.0, 8.0]), np.array([8.0, 8.5, 9.0]), np.array([9.0, 6.0])] - # Define AreaBoundary - boundary = AreaBoundary(lon_sides, lat_sides) + # Define GeographicBoundary + boundary = GeographicBoundary(lon_sides, lat_sides) # Assert vertices expected_vertices = np.array([[1., 6.], @@ -104,7 +104,7 @@ def test_vertices_property(self): assert np.allclose(boundary.vertices, expected_vertices) def test_contour(self): - """Test that AreaBoundary.contour(closed=False) returns the correct (lon,lat) tuple.""" + """Test that GeographicBoundary.contour(closed=False) returns the correct (lon,lat) tuple.""" lon_sides = [np.array([1.0, 1.5, 2.0]), np.array([2.0, 3.0]), np.array([3.0, 3.5, 4.0]), @@ -113,14 +113,14 @@ def test_contour(self): np.array([7.0, 8.0]), np.array([8.0, 8.5, 9.0]), np.array([9.0, 6.0])] - # Define AreaBoundary - boundary = AreaBoundary(lon_sides, lat_sides) + # Define GeographicBoundary + boundary = GeographicBoundary(lon_sides, lat_sides) lons, lats = boundary.contour() assert np.allclose(lons, np.array([1., 1.5, 2., 3., 3.5, 4.])) assert np.allclose(lats, np.array([6., 6.5, 7., 8., 8.5, 9.])) def test_contour_closed(self): - """Test that AreaBoundary.contour(closed=True) returns the correct (lon,lat) tuple.""" + """Test that GeographicBoundary.contour(closed=True) returns the correct (lon,lat) tuple.""" lon_sides = [np.array([1.0, 1.5, 2.0]), np.array([2.0, 3.0]), np.array([3.0, 3.5, 4.0]), @@ -129,8 +129,8 @@ def test_contour_closed(self): np.array([7.0, 8.0]), np.array([8.0, 8.5, 9.0]), np.array([9.0, 6.0])] - # Define AreaBoundary - boundary = AreaBoundary(lon_sides, lat_sides) + # Define GeographicBoundary + boundary = GeographicBoundary(lon_sides, lat_sides) lons, lats = boundary.contour(closed=True) assert np.allclose(lons, np.array([1., 1.5, 2., 3., 3.5, 4., 1.])) assert np.allclose(lats, np.array([6., 6.5, 7., 8., 8.5, 9., 6.])) diff --git a/pyresample/test/test_geometry/test_dummy.py b/pyresample/test/test_geometry/test_dummy.py deleted file mode 100644 index d36eec60..00000000 --- a/pyresample/test/test_geometry/test_dummy.py +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Thu Nov 23 12:26:45 2023 - -@author: ghiggi -""" - -# Copyright (C) 2010-2022 Pyresample developers -# -# This program is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -"""Test AreaDefinition objects.""" -import io -import sys -from glob import glob -from unittest.mock import MagicMock, patch - -import dask.array as da -import numpy as np -import pytest -import xarray as xr -from pyproj import CRS, Proj - -import pyresample -import pyresample.geometry -from pyresample import geo_filter, parse_area_file -from pyresample.future.geometry import AreaDefinition, SwathDefinition -from pyresample.future.geometry.area import ( - _get_geostationary_bounding_box_in_lonlats, - get_full_geostationary_bounding_box_in_proj_coords, - get_geostationary_angle_extent, - get_geostationary_bounding_box_in_proj_coords, - ignore_pyproj_proj_warnings, -) -from pyresample.future.geometry.base import get_array_hashable -from pyresample.geometry import AreaDefinition as LegacyAreaDefinition -from pyresample.test.utils import assert_future_geometry - - -def create_test_area(crs, shape, area_extent): - """Create an AreaDefinition object for testing.""" - area = AreaDefinition(crs=crs, shape=shape, area_extent=area_extent) - return area - - -@pytest.fixture -def geos_fd_area(): - """Create full disc geostationary area definition.""" - shape = (100, 100) - area_extent = (-5500000., -5500000., 5500000., 5500000.) - proj_dict = {'a': 6378169.00, 'b': 6356583.80, 'h': 35785831.0, - 'lon_0': 0, 'proj': 'geos', 'units': 'm'} - return create_test_area( - crs=proj_dict, - shape=shape, - area_extent=area_extent, - ) - - -@pytest.fixture -def geos_out_disk_area(): - """Create out of Earth diskc geostationary area definition.""" - shape = (10, 10) - area_extent = (-5500000., -5500000., -5300000., -5300000.) - proj_dict = {'a': 6378169.00, 'b': 6356583.80, 'h': 35785831.0, - 'lon_0': 0, 'proj': 'geos', 'units': 'm'} - return create_test_area( - crs=proj_dict, - shape=shape, - area_extent=area_extent, - ) - -@pytest.fixture -def geos_half_out_disk_area(): - """Create geostationary area definition with portion of boundary out of earth_disk.""" - shape = (100, 100) - area_extent = (-5500000., -10000., 0, 10000.) - proj_dict = {'a': 6378169.00, 'b': 6356583.80, 'h': 35785831.0, - 'lon_0': 0, 'proj': 'geos', 'units': 'm'} - return create_test_area( - crs=proj_dict, - shape=shape, - area_extent=area_extent, - ) - - -@pytest.fixture -def geos_conus_area(): - """Create CONUS geostationary area definition (portion is out-of-Earth disk).""" - shape = (30, 50) # (3000, 5000) for GOES-R CONUS/PACUS - proj_dict = {'h': 35786023, 'sweep': 'x', 'x_0': 0, 'y_0': 0, - 'ellps': 'GRS80', 'no_defs': None, 'type': 'crs', - 'lon_0': -75, 'proj': 'geos', 'units': 'm'} - area_extent = (-3627271.29128, 1583173.65752, 1382771.92872, 4589199.58952) - return create_test_area( - crs=proj_dict, - shape=shape, - area_extent=area_extent, - ) - - -class TestBoundary: - """Test 'boundary' method for AreaDefinition classes.""" - - def test_get_boundary_sides_call_geostationary_utility1(self, geos_fd_area): - """Test that the geostationary boundary sides are retrieved correctly.""" - area_def = geos_fd_area - - with patch.object(area_def, '_get_geostationary_boundary_sides') as mock_get_geo: - - # Call the method that could trigger the geostationary _get_geostationary_boundary_sides - _ = area_def._get_boundary_sides(coordinates="geographic", vertices_per_side=None) - # Assert _get_geostationary_boundary_sides was not called - mock_get_geo.assert_called_once() - - @pytest.mark.parametrize("area_def_name", ["geos_fd_area", "geos_conus_area", "geos_half_out_disk_area"]) - def test_get_boundary_sides_call_geostationary_utility2(self, request, area_def_name): - """Test that the geostationary boundary sides are retrieved correctly.""" - area_def = request.getfixturevalue(area_def_name) - - with patch.object(area_def, '_get_geostationary_boundary_sides') as mock_get_geo: - - # Call the method that could trigger the geostationary _get_geostationary_boundary_sides - _ = area_def._get_boundary_sides(coordinates="geographic", vertices_per_side=None) - # Assert _get_geostationary_boundary_sides was not called - mock_get_geo.assert_called_once() - - - - @pytest.mark.parametrize('area_def_name,assert_is_called', [ - ("geos_fd_area", True), - ("geos_out_disk_area", True), - ("geos_half_out_disk_area", True), - ("geos_conus_area", True), - ]) - def test_get_boundary_sides_call_geostationary_utility(self, request, area_def_name, assert_is_called): - area_def = request.getfixturevalue(area_def_name) - - with patch.object(area_def, '_get_geostationary_boundary_sides') as mock_get_geo: - - # Call the method that could trigger the geostationary _get_geostationary_boundary_sides - _ = area_def._get_boundary_sides(coordinates="geographic", vertices_per_side=None) - # Assert _get_geostationary_boundary_sides was not called - if assert_is_called: - mock_get_geo.assert_called_once() - else: - mock_get_geo.assert_not_called() - - - - - \ No newline at end of file diff --git a/pyresample/test/test_geometry/test_swath.py b/pyresample/test/test_geometry/test_swath.py index b9aeee5f..4c7b6f1f 100644 --- a/pyresample/test/test_geometry/test_swath.py +++ b/pyresample/test/test_geometry/test_swath.py @@ -602,7 +602,7 @@ def test_swath_definition(self, create_test_swath): lats = np.array([[65.9, 65.86, 65.82, 65.78], [65.89, 65.86, 65.82, 65.78]]) - # Define SwathDefinition and retrieve AreaBoundary + # Define SwathDefinition and retrieve GeographicBoundary swath_def = create_test_swath(lons, lats) boundary = swath_def.boundary(force_clockwise=False) diff --git a/pyresample/visualization/__init__.py b/pyresample/visualization/__init__.py index 22c4a7d1..40fd322f 100644 --- a/pyresample/visualization/__init__.py +++ b/pyresample/visualization/__init__.py @@ -1,8 +1,18 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # -*- coding: utf-8 -*- -""" -Created on Thu Nov 23 14:00:00 2023 - -@author: ghiggi -""" - +# +# Copyright (c) 2014-2021 Pyresample developers +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""pyresample tools for visualization.""" diff --git a/pyresample/visualization/geometries.py b/pyresample/visualization/geometries.py index 7b64624a..192e4069 100644 --- a/pyresample/visualization/geometries.py +++ b/pyresample/visualization/geometries.py @@ -18,23 +18,23 @@ """Define how to plot a shapely geometry.""" -def _add_map_background(ax): +def _add_map_background(ax): """Add cartopy map background.""" ax.stock_img() ax.coastlines() gl = ax.gridlines(draw_labels=True, linestyle="--") gl.top_labels = False gl.right_labels = False - return ax + return ax def _check_subplot_kw(subplot_kw): """Check subplot_kw arguments.""" import cartopy.crs as ccrs - + if subplot_kw is None: - subplot_kw = dict(projection=ccrs.PlateCarree()) - if not isinstance(subplot_kw, dict): + subplot_kw = dict(projection=ccrs.PlateCarree()) + if not isinstance(subplot_kw, dict): raise TypeError("'subplot_kw' must be a dictionary.'") if "projection" not in subplot_kw: raise ValueError("Specify a cartopy 'projection' in subplot_kw.") @@ -44,15 +44,15 @@ def _check_subplot_kw(subplot_kw): def _initialize_plot(ax=None, subplot_kw=None): """Initialize plot.""" import matplotlib.pyplot as plt - + if ax is None: subplot_kw = _check_subplot_kw(subplot_kw) fig, ax = plt.subplots(subplot_kw=subplot_kw) return fig, ax, True - else: + else: return None, ax, False - - + + def plot_geometries(geometries, crs, ax=None, subplot_kw=None, **kwargs): """Plot geometries in cartopy.""" # Create figure if ax not provided @@ -64,8 +64,6 @@ def plot_geometries(geometries, crs, ax=None, subplot_kw=None, **kwargs): ax.add_geometries(geometries, crs=crs, **kwargs) # Return Figure / Axis if initialized_here: - return fig + return fig else: return ax - -