[Git][debian-gis-team/pyresample][master] 4 commits: New upstream version 1.27.1

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Fri Jun 23 07:19:46 BST 2023



Antonio Valentino pushed to branch master at Debian GIS Project / pyresample


Commits:
db4994c7 by Antonio Valentino at 2023-06-23T06:16:06+00:00
New upstream version 1.27.1
- - - - -
5ed6aefa by Antonio Valentino at 2023-06-23T06:17:14+00:00
Update upstream source from tag 'upstream/1.27.1'

Update to upstream version '1.27.1'
with Debian dir 5e581fd8811b020fa80d8da5ed2c1f09aacbf314
- - - - -
c14afe93 by Antonio Valentino at 2023-06-23T06:18:17+00:00
New upstream release

- - - - -
f75559e3 by Antonio Valentino at 2023-06-23T06:19:13+00:00
Set distribution to unstable

- - - - -


11 changed files:

- .github/workflows/ci.yaml
- .readthedocs.yml
- CHANGELOG.md
- debian/changelog
- pyresample/geometry.py
- pyresample/resampler.py
- pyresample/test/test_geometry/test_area.py
- pyresample/test/test_geometry_legacy.py
- pyresample/test/test_gradient.py
- pyresample/test/test_resamplers/test_resampler.py
- pyresample/version.py


Changes:

=====================================
.github/workflows/ci.yaml
=====================================
@@ -8,37 +8,6 @@ concurrency:
 on: [push, pull_request]
 
 jobs:
-  website:
-    name: build website
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout source
-        uses: actions/checkout at v3
-        with:
-          fetch-depth: 0
-
-      - name: Setup Conda Environment
-        uses: conda-incubator/setup-miniconda at v2
-        with:
-          miniforge-variant: Mambaforge
-          miniforge-version: latest
-          use-mamba: true
-          python-version: "3.11"
-          environment-file: continuous_integration/environment.yaml
-          activate-environment: test-environment
-
-      - name: Install Satpy
-        shell: bash -l {0}
-        run: |
-          pip install sphinx sphinx_rtd_theme sphinxcontrib-apidoc sphinx-reredirects
-          pip install --no-deps -e .
-
-      - name: Run Sphinx Build
-        shell: bash -l {0}
-        run: |
-          cd docs
-          make html SPHINXOPTS="-W"
-
   test:
     runs-on: ${{ matrix.os }}
     continue-on-error: ${{ matrix.experimental }}


=====================================
.readthedocs.yml
=====================================
@@ -5,6 +5,11 @@ build:
   tools:
     python: "mambaforge-4.10"
 
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+  configuration: docs/source/conf.py
+  fail_on_warning: true
+
 conda:
     environment: docs/environment.yml
 


=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,26 @@
+## Version 1.27.1 (2023/06/21)
+
+### Issues Closed
+
+* [Issue 517](https://github.com/pytroll/pyresample/issues/517) - EWA resampling in 1.27 slows down four times than 1.26.1 ([PR 520](https://github.com/pytroll/pyresample/pull/520) by [@djhoese](https://github.com/djhoese))
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 524](https://github.com/pytroll/pyresample/pull/524) - Preserve get_area_slices behavior when area to cover has an invalid boundary
+* [PR 523](https://github.com/pytroll/pyresample/pull/523) - Fix DynamicAreaDefinition not preserving user's requested resolution ([517](https://github.com/pytroll/pyresample/issues/517))
+* [PR 520](https://github.com/pytroll/pyresample/pull/520) - Fix performance regression in base resampler class when comparing geometries ([517](https://github.com/pytroll/pyresample/issues/517))
+
+#### Documentation changes
+
+* [PR 518](https://github.com/pytroll/pyresample/pull/518) - Add configuration for readthedocs to fail on warnings
+
+In this release 4 pull requests were closed.
+
+
 ## Version 1.27.0 (2023/05/17)
 
 ### Issues Closed


=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+pyresample (1.27.1-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it>  Fri, 23 Jun 2023 06:18:51 +0000
+
 pyresample (1.27.0-1) unstable; urgency=medium
 
   [ Bas Couwenberg ]


=====================================
pyresample/geometry.py
=====================================
@@ -1127,15 +1127,24 @@ class DynamicAreaDefinition(object):
             height, width = shape
             x_resolution = (corners[2] - corners[0]) * 1.0 / (width - 1)
             y_resolution = (corners[3] - corners[1]) * 1.0 / (height - 1)
+            area_extent = (corners[0] - x_resolution / 2,
+                           corners[1] - y_resolution / 2,
+                           corners[2] + x_resolution / 2,
+                           corners[3] + y_resolution / 2)
         else:
             x_resolution, y_resolution = resolution
-            width = int(np.rint((corners[2] - corners[0]) * 1.0 / x_resolution + 1))
-            height = int(np.rint((corners[3] - corners[1]) * 1.0 / y_resolution + 1))
+            half_x = x_resolution / 2
+            half_y = y_resolution / 2
+            # align extents with pixel resolution
+            area_extent = (
+                math.floor((corners[0] - half_x) / x_resolution) * x_resolution,
+                math.floor((corners[1] - half_y) / y_resolution) * y_resolution,
+                math.ceil((corners[2] + half_x) / x_resolution) * x_resolution,
+                math.ceil((corners[3] + half_y) / y_resolution) * y_resolution,
+            )
+            width = int(round((area_extent[2] - area_extent[0]) / x_resolution))
+            height = int(round((area_extent[3] - area_extent[1]) / y_resolution))
 
-        area_extent = (corners[0] - x_resolution / 2,
-                       corners[1] - y_resolution / 2,
-                       corners[2] + x_resolution / 2,
-                       corners[3] + y_resolution / 2)
         return area_extent, width, height
 
     def _update_corners_for_full_extent(self, corners, shape, resolution, projection):
@@ -2592,14 +2601,8 @@ class AreaDefinition(_ProjectionDefinition):
                                       "equal.")
 
         data_boundary = Boundary(*get_geostationary_bounding_box_in_lonlats(self))
-        if area_to_cover.is_geostationary:
-            area_boundary = Boundary(
-                *get_geostationary_bounding_box_in_lonlats(area_to_cover))
-        else:
-            area_boundary = AreaDefBoundary(area_to_cover, 100)
-
-        intersection = data_boundary.contour_poly.intersection(
-            area_boundary.contour_poly)
+        area_boundary = self._get_area_to_cover_boundary(area_to_cover)
+        intersection = data_boundary.contour_poly.intersection(area_boundary.contour_poly)
         if intersection is None:
             logger.debug('Cannot determine appropriate slicing. '
                          "Data and projection area do not overlap.")
@@ -2619,6 +2622,15 @@ class AreaDefinition(_ProjectionDefinition):
         return (check_slice_orientation(x_slice),
                 check_slice_orientation(y_slice))
 
+    @staticmethod
+    def _get_area_to_cover_boundary(area_to_cover: AreaDefinition) -> Boundary:
+        try:
+            if area_to_cover.is_geostationary:
+                return Boundary(*get_geostationary_bounding_box_in_lonlats(area_to_cover))
+            return AreaDefBoundary(area_to_cover, 100)
+        except ValueError:
+            raise NotImplementedError("Can't determine boundary of area to cover")
+
     def crop_around(self, other_area):
         """Crop this area around `other_area`."""
         xslice, yslice = self.get_area_slices(other_area)


=====================================
pyresample/resampler.py
=====================================
@@ -121,7 +121,7 @@ class BaseResampler:
         Returns (xarray.DataArray): Data resampled to the target area
 
         """
-        if self.source_geo_def == self.target_geo_def:
+        if self._geometries_are_the_same():
             return data
         # default is to mask areas for SwathDefinitions
         if mask_area is None and isinstance(
@@ -143,6 +143,42 @@ class BaseResampler:
         cache_id = self.precompute(cache_dir=cache_dir, **kwargs)
         return self.compute(data, cache_id=cache_id, **kwargs)
 
+    def _geometries_are_the_same(self):
+        """Check if two geometries are the same object and resampling isn't needed.
+
+        For area definitions this is a simple comparison using the ``==``.
+        When swaths are involved care is taken to not check coordinate equality
+        to avoid the expensive computation. A swath and an area are never
+        considered equal in this case even if they describe the same geographic
+        region.
+
+        Two swaths are only considered equal if the underlying arrays are the
+        exact same objects. Otherwise, they are considered not equal and
+        coordinate values are never checked. This has
+        the downside that if two SwathDefinitions have equal coordinates but
+        are loaded or created separately they will be considered not equal.
+
+        """
+        if self.source_geo_def is self.target_geo_def:
+            return True
+        if type(self.source_geo_def) is not type(self.target_geo_def):  # noqa
+            # these aren't the exact same class
+            return False
+        if isinstance(self.source_geo_def, AreaDefinition):
+            return self.source_geo_def == self.target_geo_def
+        # swath or coordinate definitions
+        src_lons, src_lats = self.source_geo_def.get_lonlats()
+        dst_lons, dst_lats = self.target_geo_def.get_lonlats()
+        if (src_lons is dst_lons) and (src_lats is dst_lats):
+            return True
+
+        if not all(isinstance(arr, da.Array) for arr in (src_lons, src_lats, dst_lons, dst_lats)):
+            # they aren't the same object and they aren't dask arrays so not equal
+            return False
+        # if dask task names are the same then they are the same even if the
+        # dask Array instance itself is different
+        return src_lons.name == dst_lons.name and src_lats.name == dst_lats.name
+
     def _create_cache_filename(self, cache_dir=None, prefix='',
                                fmt='.zarr', **kwargs):
         """Create filename for the cached resampling parameters."""


=====================================
pyresample/test/test_geometry/test_area.py
=====================================
@@ -1814,6 +1814,17 @@ class TestAreaDefGetAreaSlices:
         assert slice_lines == expected_slice_lines
         assert slice_cols == expected_slice_cols
 
+    def test_area_to_cover_all_nan_bounds(self, geos_src_area, create_test_area):
+        """Check area slicing when the target doesn't have a valid boundary."""
+        area_def = geos_src_area
+        # An area that is a subset of the original one
+        area_to_cover = create_test_area(
+            {"proj": "moll"},
+            1000, 1000,
+            area_extent=(-18000000.0, -9000000.0, 18000000.0, 9000000.0))
+        with pytest.raises(NotImplementedError):
+            area_def.get_area_slices(area_to_cover)
+
 
 class TestBoundary:
     """Test 'boundary' method for AreaDefinition classes."""


=====================================
pyresample/test/test_geometry_legacy.py
=====================================
@@ -291,29 +291,32 @@ class TestDynamicAreaDefinition:
                              resolution=3000,
                              proj_info={'lon_0': 16, 'lat_0': 58})
 
-        np.testing.assert_allclose(result.area_extent, (-432079.38952,
-                                                        -872594.690447,
-                                                        432079.38952,
-                                                        904633.303964))
+        np.testing.assert_allclose(result.area_extent, (-435000.0,
+                                                        -873000.0,
+                                                        435000.0,
+                                                        906000.0))
         assert result.proj_dict['lon_0'] == 16
         assert result.proj_dict['lat_0'] == 58
-        assert result.width == 288
-        assert result.height == 592
+        assert result.width == 290
+        assert result.height == 593
+        assert result.pixel_size_x == 3000
+        assert result.pixel_size_y == 3000
 
-        # make sure that setting `proj_info` once doesn't
-        # set it in the dynamic area
+        # make sure that setting `proj_info` once doesn't set it in the dynamic area
         result = area.freeze((lons, lats),
                              resolution=3000,
                              proj_info={'lon_0': 0})
-        np.testing.assert_allclose(result.area_extent, (538546.7274949469,
-                                                        5380808.879250369,
-                                                        1724415.6519203288,
-                                                        6998895.701001488))
+        np.testing.assert_allclose(result.area_extent, (537000.0,
+                                                        5379000.0,
+                                                        1725000.0,
+                                                        6999000.0))
         assert result.proj_dict['lon_0'] == 0
         # lat_0 could be provided or not depending on version of pyproj
         assert result.proj_dict.get('lat_0', 0) == 0
-        assert result.width == 395
-        assert result.height == 539
+        assert result.width == 396
+        assert result.height == 540
+        assert result.pixel_size_x == 3000
+        assert result.pixel_size_y == 3000
 
     def test_freeze_when_area_is_optimized_and_has_a_resolution(self):
         """Test freezing an optimized area with a resolution."""
@@ -391,11 +394,11 @@ class TestDynamicAreaDefinition:
         if is_pole:
             assert extent[0] < -178
             assert extent[2] > 178
-            assert result.width == 64088
+            assert result.width == 64090
         else:
             assert extent[0] > 0
             assert extent[2] > 0
-            assert result.width == 1787
+            assert result.width == 1788
         assert result.height == 2680
 
     def test_freeze_with_bb(self):
@@ -454,10 +457,10 @@ class TestDynamicAreaDefinition:
             "exclude_proj_components"
         ),
         [
-            (None, (21, 59), (164.75, 24.75, 194.25, 35.25), tuple(), ("+pm=180",)),
-            ("modify_extents", (21, 59), (164.75, 24.75, 194.25, 35.25), tuple(), ("+pm=180",)),
-            ("modify_crs", (21, 59), (164.75 - 180.0, 24.75, 194.25 - 180.0, 35.25), ("+pm=180",), tuple()),
-            ("global_extents", (21, 720), (-180.0, 24.75, 180.0, 35.25), tuple(), ("+pm=180",)),
+            (None, (22, 60), (164.5, 24.5, 194.5, 35.5), tuple(), ("+pm=180",)),
+            ("modify_extents", (22, 60), (164.5, 24.5, 194.5, 35.5), tuple(), ("+pm=180",)),
+            ("modify_crs", (22, 60), (164.5 - 180.0, 24.5, 194.5 - 180.0, 35.5), ("+pm=180",), tuple()),
+            ("global_extents", (22, 720), (-180.0, 24.5, 180.0, 35.5), tuple(), ("+pm=180",)),
         ],
     )
     @pytest.mark.parametrize("use_dask", [False, True])


=====================================
pyresample/test/test_gradient.py
=====================================
@@ -485,7 +485,6 @@ class TestRBGradientSearchResamplerArea2Area:
         res = self.resampler.compute(
             data, method='nn',
             fill_value=2.0).compute(scheduler='single-threaded').values
-        print(res)
         np.testing.assert_allclose(res, expected_resampled_data)
         assert res.shape == dst_area.shape
 


=====================================
pyresample/test/test_resamplers/test_resampler.py
=====================================
@@ -20,12 +20,14 @@ from __future__ import annotations
 
 from unittest import mock
 
+import dask.array as da
 import numpy as np
 import pytest
+import xarray as xr
 from pytest_lazyfixture import lazy_fixture
 
 from pyresample.future.resamplers.resampler import Resampler
-from pyresample.geometry import AreaDefinition
+from pyresample.geometry import AreaDefinition, SwathDefinition
 from pyresample.resampler import BaseResampler
 
 
@@ -76,13 +78,68 @@ def test_resampler(src, dst):
     assert resample_results.shape == dst.shape
 
 
-def test_base_resampler_does_nothing_when_src_and_dst_areas_are_equal():
+ at pytest.mark.parametrize(
+    ("use_swaths", "copy_dst_swath"),
+    [
+        (False, None),
+        (True, None),  # same objects are equal
+        (True, "dask"),  # same dask tasks are equal
+        (True, "swath_def"),  # same underlying arrays are equal
+    ])
+def test_base_resampler_does_nothing_when_src_and_dst_areas_are_equal(_geos_area, use_swaths, copy_dst_swath):
     """Test that the BaseResampler does nothing when the source and target areas are the same."""
+    src_geom = _geos_area if not use_swaths else _xarray_swath_def_from_area(_geos_area)
+    dst_geom = src_geom
+    if copy_dst_swath == "dask":
+        dst_geom = _xarray_swath_def_from_area(_geos_area)
+    elif copy_dst_swath == "swath_def":
+        dst_geom = SwathDefinition(dst_geom.lons, dst_geom.lats)
+
+    resampler = BaseResampler(src_geom, dst_geom)
+    some_data = xr.DataArray(da.zeros(src_geom.shape, dtype=np.float64), dims=('y', 'x'))
+    assert resampler.resample(some_data) is some_data
+
+
+ at pytest.mark.parametrize(
+    ("src_area", "numpy_swath"),
+    [
+        (False, False),
+        (False, True),
+        (True, False),
+    ])
+ at pytest.mark.parametrize("dst_area", [False, True])
+def test_base_resampler_unequal_geometries(_geos_area, _geos_area2, src_area, numpy_swath, dst_area):
+    """Test cases where BaseResampler geometries are not considered equal."""
+    src_geom = _geos_area if src_area else _xarray_swath_def_from_area(_geos_area, numpy_swath)
+    dst_geom = _geos_area2 if dst_area else _xarray_swath_def_from_area(_geos_area2)
+    resampler = BaseResampler(src_geom, dst_geom)
+    some_data = xr.DataArray(da.zeros(src_geom.shape, dtype=np.float64), dims=('y', 'x'))
+    with pytest.raises(NotImplementedError):
+        resampler.resample(some_data)
+
+
+def _xarray_swath_def_from_area(area_def, use_numpy=False):
+    chunks = None if use_numpy else -1
+    lons_da, lats_da = area_def.get_lonlats(chunks=chunks)
+    lons = xr.DataArray(lons_da, dims=('y', 'x'))
+    lats = xr.DataArray(lats_da, dims=('y', 'x'))
+    swath_def = SwathDefinition(lons, lats)
+    return swath_def
+
+
+ at pytest.fixture
+def _geos_area():
     src_area = AreaDefinition('src', 'src area', None,
                               {'ellps': 'WGS84', 'h': '35785831', 'proj': 'geos'},
                               100, 100,
                               (5550000.0, 5550000.0, -5550000.0, -5550000.0))
+    return src_area
 
-    resampler = BaseResampler(src_area, src_area)
-    some_data = np.zeros(src_area.shape, dtype=np.float64)
-    assert resampler.resample(some_data) is some_data
+
+ at pytest.fixture
+def _geos_area2():
+    src_area = AreaDefinition('src', 'src area', None,
+                              {'ellps': 'WGS84', 'h': '35785831', 'proj': 'geos'},
+                              200, 200,
+                              (5550000.0, 5550000.0, -5550000.0, -5550000.0))
+    return src_area


=====================================
pyresample/version.py
=====================================
@@ -25,9 +25,9 @@ def get_keywords():
     # setup.py/versioneer.py will grep for the variable names, so they must
     # each be defined on a line of their own. _version.py will just call
     # get_keywords().
-    git_refnames = " (tag: v1.27.0)"
-    git_full = "3fa952985b8b4a62f4407e1cdeef166719523e47"
-    git_date = "2023-05-17 08:51:02 +0200"
+    git_refnames = " (HEAD -> main, tag: v1.27.1)"
+    git_full = "12892b875221846f0fbc7f68775dbdadd8b68584"
+    git_date = "2023-06-21 16:14:02 +0200"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/pyresample/-/compare/dabdfd077f9a4d31798aacfd8e89250e57ee43d2...f75559e324ed4d48e5a8b7dde330f919de16a92e

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyresample/-/compare/dabdfd077f9a4d31798aacfd8e89250e57ee43d2...f75559e324ed4d48e5a8b7dde330f919de16a92e
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20230623/750f7d19/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list