[Git][debian-gis-team/rasterio][experimental] 4 commits: New upstream version 1.2.2

Bas Couwenberg gitlab at salsa.debian.org
Wed Apr 7 14:59:48 BST 2021



Bas Couwenberg pushed to branch experimental at Debian GIS Project / rasterio


Commits:
11360c97 by Bas Couwenberg at 2021-04-07T15:32:21+02:00
New upstream version 1.2.2
- - - - -
165b119c by Bas Couwenberg at 2021-04-07T15:32:54+02:00
Update upstream source from tag 'upstream/1.2.2'

Update to upstream version '1.2.2'
with Debian dir 2f84c02103c9ae09c077a0c9590828f5ac5f6b45
- - - - -
a0035050 by Bas Couwenberg at 2021-04-07T15:39:22+02:00
New upstream release.

- - - - -
55e5da9f by Bas Couwenberg at 2021-04-07T15:40:30+02:00
Set distribution to experimental.

- - - - -


17 changed files:

- CHANGES.txt
- debian/changelog
- pyproject.toml
- rasterio/__init__.py
- rasterio/_warp.pyx
- rasterio/enums.py
- rasterio/env.py
- rasterio/merge.py
- rasterio/rio/clip.py
- rasterio/session.py
- rasterio/warp.py
- rasterio/windows.py
- + tests/test_merge.py
- tests/test_rio_clip.py
- tests/test_session.py
- tests/test_warp.py
- tests/test_windows.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,23 @@
 Changes
 =======
 
+1.2.2 (2021-04-06)
+------------------
+
+- Add a --nodata option to rio-clip to help support the relative complement use
+  case (#2087).
+- Remove boundless keyword argument from WarpedVRT method signatures (#2084).
+- Implement the advertised intent of warp.reproject's src_crs parameter. It can
+  override the source's crs (#2036).
+- A regression in the merge tool's min and max methods has been fixed (#2145).
+- windows.from_bounds raises WindowError when the given bounds and transform are
+  inconsistent with each other (#2138).
+- Support for GDAL's "sum" resampling algorith has been added (#2137).
+- Cython and numpy versions are pinned in pyproject.toml. Numpy versions
+  correspond to scipy's oldest supported versions for Python versions 3.6-3.10.
+- GDAL data path is configured in env.py with set_gdal_config (#2139).
+- AWS sessions are not created for pre-signed URLs (#2133).
+
 1.2.1 (2021-03-03)
 ------------------
 


=====================================
debian/changelog
=====================================
@@ -1,8 +1,10 @@
-rasterio (1.2.1-1~exp2) UNRELEASED; urgency=medium
+rasterio (1.2.2-1~exp1) experimental; urgency=medium
 
+  * Team upload.
+  * New upstream release.
   * Update watch file for GitHub URL changes.
 
- -- Bas Couwenberg <sebastic at debian.org>  Sat, 20 Mar 2021 09:14:41 +0100
+ -- Bas Couwenberg <sebastic at debian.org>  Wed, 07 Apr 2021 15:40:17 +0200
 
 rasterio (1.2.1-1~exp1) experimental; urgency=medium
 


=====================================
pyproject.toml
=====================================
@@ -1,3 +1,2 @@
 [build-system]
-# Minimum requirements for the build system to execute.
-requires = ["setuptools", "wheel", "cython", "numpy"]
+requires = ["setuptools", "wheel", "cython==0.29.21", "oldest-supported-numpy"]


=====================================
rasterio/__init__.py
=====================================
@@ -27,7 +27,7 @@ import rasterio.enums
 import rasterio.path
 
 __all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.2.1"
+__version__ = "1.2.2"
 __gdal_version__ = gdal_version()
 
 # Rasterio attaches NullHandler to the 'rasterio' logger and its


=====================================
rasterio/_warp.pyx
=====================================
@@ -477,7 +477,7 @@ def _reproject(
         if key == b"RPC_DEM":
             # don't .upper() since might be a path
             val = str(val).encode('utf-8')
-            
+
             if rpcs:
                 bUseApproxTransformer = False
         else:
@@ -1077,9 +1077,7 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
         """The dataset's coordinate reference system"""
         return self.dst_crs
 
-    def read(self, indexes=None, out=None, window=None, masked=False,
-            out_shape=None, boundless=False, resampling=Resampling.nearest,
-            fill_value=None, out_dtype=None):
+    def read(self, indexes=None, out=None, window=None, masked=False, out_shape=None, resampling=Resampling.nearest, fill_value=None, out_dtype=None, **kwargs):
         """Read a dataset's raw pixels as an N-d array
 
         This data is read from the dataset's band cache, which means
@@ -1130,11 +1128,6 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
             regular array. Masks will be exactly the inverse of the
             GDAL RFC 15 conforming arrays returned by read_masks().
 
-        boundless : bool, optional (default `False`)
-            If `True`, windows that extend beyond the dataset's extent
-            are permitted and partially or completely filled arrays will
-            be returned as appropriate.
-
         resampling : Resampling
             By default, pixel values are read raw or interpolated using
             a nearest neighbor algorithm from the band cache. Other
@@ -1144,6 +1137,10 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
         fill_value : scalar
             Fill value applied in the `boundless=True` case only.
 
+        kwargs : dict
+            This is only for backwards compatibility. No keyword arguments
+            are supported other than the ones named above.
+
         Returns
         -------
         Numpy ndarray or a view on a Numpy ndarray
@@ -1153,15 +1150,14 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
         preferentially used by callers.
 
         """
-        if boundless:
+        if kwargs.get("boundless", False):
             raise ValueError("WarpedVRT does not permit boundless reads")
         else:
             return super(WarpedVRTReaderBase, self).read(indexes=indexes, out=out, window=window, masked=masked, out_shape=out_shape, resampling=resampling, fill_value=fill_value, out_dtype=out_dtype)
 
-    def read_masks(self, indexes=None, out=None, out_shape=None, window=None,
-                   boundless=False, resampling=Resampling.nearest):
+    def read_masks(self, indexes=None, out=None, out_shape=None, window=None, resampling=Resampling.nearest, **kwargs):
         """Read raster band masks as a multidimensional array"""
-        if boundless:
+        if kwargs.get("boundless", False):
             raise ValueError("WarpedVRT does not permit boundless reads")
         else:
             return super(WarpedVRTReaderBase, self).read_masks(indexes=indexes, out=out, window=window, out_shape=out_shape, resampling=resampling)


=====================================
rasterio/enums.py
=====================================
@@ -37,6 +37,8 @@ class Resampling(IntEnum):
     'nearest', 'bilinear', 'cubic', 'cubic_spline', 'lanczos',
     'average', 'mode' are always available (GDAL >= 1.10).
 
+    'sum' is only supported in GDAL >= 3.1.
+
     'rms' is only supported in GDAL >= 3.3.
 
     Note: 'gauss' is not available to the functions in rio.warp.
@@ -54,6 +56,7 @@ class Resampling(IntEnum):
     med = 10
     q1 = 11
     q3 = 12
+    sum = 13
     rms = 14
 
 


=====================================
rasterio/env.py
=====================================
@@ -621,7 +621,7 @@ if 'GDAL_DATA' not in os.environ:
     path = GDALDataFinder().search_wheel()
 
     if path:
-        os.environ['GDAL_DATA'] = path
+        set_gdal_config("GDAL_DATA", path)
         log.debug("GDAL data found in package, GDAL_DATA set to %r.", path)
 
     # See https://github.com/mapbox/rasterio/issues/1631.
@@ -632,7 +632,7 @@ if 'GDAL_DATA' not in os.environ:
         path = GDALDataFinder().search()
 
         if path:
-            os.environ['GDAL_DATA'] = path
+            set_gdal_config("GDAL_DATA", path)
             log.debug("GDAL_DATA not found in environment, set to %r.", path)
 
 if "PROJ_LIB" in os.environ:


=====================================
rasterio/merge.py
=====================================
@@ -17,41 +17,37 @@ from rasterio.transform import Affine
 logger = logging.getLogger(__name__)
 
 
-def copy_first(old_data, new_data, old_nodata, new_nodata, **kwargs):
-    mask = np.empty_like(old_data, dtype='bool')
-    np.logical_not(new_nodata, out=mask)
-    np.logical_and(old_nodata, mask, out=mask)
-    np.copyto(old_data, new_data, where=mask)
+def copy_first(merged_data, new_data, merged_mask, new_mask, **kwargs):
+    mask = np.empty_like(merged_mask, dtype="bool")
+    np.logical_not(new_mask, out=mask)
+    np.logical_and(merged_mask, mask, out=mask)
+    np.copyto(merged_data, new_data, where=mask)
 
 
-def copy_last(old_data, new_data, old_nodata, new_nodata, **kwargs):
-    mask = np.empty_like(old_data, dtype='bool')
-    np.logical_not(new_nodata, out=mask)
-    np.copyto(old_data, new_data, where=mask)
+def copy_last(merged_data, new_data, merged_mask, new_mask, **kwargs):
+    mask = np.empty_like(merged_mask, dtype="bool")
+    np.logical_not(new_mask, out=mask)
+    np.copyto(merged_data, new_data, where=mask)
 
 
-def copy_min(old_data, new_data, old_nodata, new_nodata, **kwargs):
-    mask = np.empty_like(old_data, dtype='bool')
-    np.logical_or(old_nodata, new_nodata, out=mask)
+def copy_min(merged_data, new_data, merged_mask, new_mask, **kwargs):
+    mask = np.empty_like(merged_mask, dtype="bool")
+    np.logical_or(merged_mask, new_mask, out=mask)
     np.logical_not(mask, out=mask)
+    np.minimum(merged_data, new_data, out=merged_data, where=mask)
+    np.logical_not(new_mask, out=mask)
+    np.logical_and(merged_mask, mask, out=mask)
+    np.copyto(merged_data, new_data, where=mask)
 
-    np.minimum(old_data, new_data, out=old_data, where=mask)
 
-    np.logical_not(new_nodata, out=mask)
-    np.logical_and(old_data, mask, out=mask)
-    np.copyto(old_data, new_data, where=mask)
-
-
-def copy_max(old_data, new_data, old_nodata, new_nodata, **kwargs):
-    mask = np.empty_like(old_data, dtype='bool')
-    np.logical_or(old_nodata, new_nodata, out=mask)
+def copy_max(merged_data, new_data, merged_mask, new_mask, **kwargs):
+    mask = np.empty_like(merged_mask, dtype="bool")
+    np.logical_or(merged_mask, new_mask, out=mask)
     np.logical_not(mask, out=mask)
-
-    np.maximum(old_data, new_data, out=old_data, where=mask)
-
-    np.logical_not(new_nodata, out=mask)
-    np.logical_and(old_data, mask, out=mask)
-    np.copyto(old_data, new_data, where=mask)
+    np.maximum(merged_data, new_data, out=merged_data, where=mask)
+    np.logical_not(new_mask, out=mask)
+    np.logical_and(merged_mask, mask, out=mask)
+    np.copyto(merged_data, new_data, where=mask)
 
 
 MERGE_METHODS = {
@@ -124,18 +120,18 @@ def merge(
             max: pixel-wise max of existing and new
         or custom callable with signature:
 
-        def function(old_data, new_data, old_nodata, new_nodata, index=None, roff=None, coff=None):
+        def function(merged_data, new_data, merged_mask, new_mask, index=None, roff=None, coff=None):
 
             Parameters
             ----------
-            old_data : array_like
+            merged_data : array_like
                 array to update with new_data
             new_data : array_like
                 data to merge
-                same shape as old_data
-            old_nodata, new_data : array_like
-                boolean masks where old/new data is nodata
-                same shape as old_data
+                same shape as merged_data
+            merged_mask, new_mask : array_like
+                boolean masks where merged/new data pixels are invalid
+                same shape as merged_data
             index: int
                 index of the current dataset within the merged dataset collection
             roff: int
@@ -333,13 +329,13 @@ def merge(
         region = dest[:, roff:roff + trows, coff:coff + tcols]
 
         if math.isnan(nodataval):
-            region_nodata = np.isnan(region)
+            region_mask = np.isnan(region)
         else:
-            region_nodata = region == nodataval
-        temp_nodata = np.ma.getmaskarray(temp)
+            region_mask = region == nodataval
+
+        temp_mask = np.ma.getmask(temp)
 
-        copyto(region, temp, region_nodata, temp_nodata,
-               index=idx, roff=roff, coff=coff)
+        copyto(region, temp, region_mask, temp_mask, index=idx, roff=roff, coff=coff)
 
     if dst_path is None:
         return dest, output_transform


=====================================
rasterio/rio/clip.py
=====================================
@@ -43,6 +43,7 @@ projection_projected_opt = click.option(
     type=click.Path(exists=True),
     help='Raster dataset to use as a template for bounds')
 @options.format_opt
+ at options.nodata_opt
 @projection_geographic_opt
 @projection_projected_opt
 @options.overwrite_opt
@@ -60,6 +61,7 @@ def clip(
     bounds,
     like,
     driver,
+    nodata,
     projection,
     overwrite,
     creation_options,
@@ -140,13 +142,18 @@ def clip(
             width = int(out_window.width)
 
             out_kwargs = src.profile
-            out_kwargs.pop("driver", None)
+
             if driver:
                 out_kwargs["driver"] = driver
+
+            if nodata is not None:
+                out_kwargs["nodata"] = nodata
+
             out_kwargs.update({
                 'height': height,
                 'width': width,
                 'transform': src.window_transform(out_window)})
+
             out_kwargs.update(**creation_options)
 
             if "blockxsize" in out_kwargs and int(out_kwargs["blockxsize"]) > width:
@@ -166,5 +173,6 @@ def clip(
                         window=out_window,
                         out_shape=(src.count, height, width),
                         boundless=True,
+                        masked=True,
                     )
                 )


=====================================
rasterio/session.py
=====================================
@@ -100,7 +100,9 @@ class Session(object):
         if isinstance(path, UnparsedPath) or path.is_local:
             return DummySession
 
-        elif path.scheme == "s3" or "amazonaws.com" in path.path:
+        elif (
+            path.scheme == "s3" or "amazonaws.com" in path.path
+        ) and not "X-Amz-Signature" in path.path:
             if boto3 is not None:
                 return AWSSession
             else:


=====================================
rasterio/warp.py
=====================================
@@ -18,6 +18,9 @@ SUPPORTED_RESAMPLING = [r for r in Resampling if r.value < 7]
 GDAL2_RESAMPLING = [r for r in Resampling if r.value > 7 and r.value <= 12]
 if GDALVersion.runtime().at_least('2.0'):
     SUPPORTED_RESAMPLING.extend(GDAL2_RESAMPLING)
+# sum supported since GDAL 3.1
+if GDALVersion.runtime().at_least('3.1'):
+    SUPPORTED_RESAMPLING.append(Resampling.sum)
 # rms supported since GDAL 3.3
 if GDALVersion.runtime().at_least('3.3'):
     SUPPORTED_RESAMPLING.append(Resampling.rms)
@@ -319,7 +322,7 @@ def reproject(source, destination=None, src_transform=None, gcps=None, rpcs=None
             else:
                 src_count = 1
                 src_height, src_width = source.shape
-            
+
             # try to compute src_bounds if we don't have gcps
             if not (gcps or rpcs):
                 src_bounds = array_bounds(src_height, src_width, src_transform)
@@ -331,9 +334,11 @@ def reproject(source, destination=None, src_transform=None, gcps=None, rpcs=None
             if not (src_rdr.transform.is_identity and src_rdr.crs is None):
                 src_bounds = src_rdr.bounds
 
-            src_crs = src_rdr.crs
+            src_crs = src_crs or src_rdr.crs
+
             if isinstance(src_bidx, int):
                 src_bidx = [src_bidx]
+
             src_count = len(src_bidx)
             src_height, src_width = src_shape
             gcps = src_rdr.gcps[0] if src_rdr.gcps[0] else None


=====================================
rasterio/windows.py
=====================================
@@ -285,6 +285,12 @@ def from_bounds(
     if not isinstance(transform, Affine):  # TODO: RPCs?
         raise WindowError("A transform object is required to calculate the window")
 
+    if (right - left) / transform.a <= 0:
+        raise WindowError("Bounds and transform are inconsistent")
+
+    if (bottom - top) / transform.e <= 0:
+        raise WindowError("Bounds and transform are inconsistent")
+
     rows, cols = rowcol(
         transform,
         [left, right, right, left],


=====================================
tests/test_merge.py
=====================================
@@ -0,0 +1,53 @@
+"""Tests of rasterio.merge"""
+
+import numpy
+import pytest
+
+import affine
+import rasterio
+from rasterio.merge import merge
+
+# Non-coincident datasets test fixture.
+# Three overlapping GeoTIFFs, two to the NW and one to the SE.
+ at pytest.fixture(scope="function")
+def test_data_dir_overlapping(tmp_path):
+    kwargs = {
+        "crs": "EPSG:4326",
+        "transform": affine.Affine(0.2, 0, -114, 0, -0.2, 46),
+        "count": 1,
+        "dtype": rasterio.uint8,
+        "driver": "GTiff",
+        "width": 10,
+        "height": 10,
+        "nodata": 0,
+    }
+
+    with rasterio.open(tmp_path.joinpath("nw1.tif"), "w", **kwargs) as dst:
+        data = numpy.ones((10, 10), dtype=rasterio.uint8)
+        dst.write(data, indexes=1)
+
+    with rasterio.open(tmp_path.joinpath("nw3.tif"), "w", **kwargs) as dst:
+        data = numpy.ones((10, 10), dtype=rasterio.uint8) * 3
+        dst.write(data, indexes=1)
+
+    kwargs["transform"] = affine.Affine(0.2, 0, -113, 0, -0.2, 45)
+
+    with rasterio.open(tmp_path.joinpath("se.tif"), "w", **kwargs) as dst:
+        data = numpy.ones((10, 10), dtype=rasterio.uint8) * 2
+        dst.write(data, indexes=1)
+
+    return tmp_path
+
+
+ at pytest.mark.parametrize(
+    "method,value", [("first", 1), ("last", 2), ("min", 1), ("max", 3)]
+)
+def test_merge_method(test_data_dir_overlapping, method, value):
+    """Merge method produces expected values in intersection"""
+    inputs = sorted(list(test_data_dir_overlapping.iterdir()))  # nw is first.
+    datasets = [rasterio.open(x) for x in inputs]
+    output_count = 1
+    arr, _ = merge(
+        datasets, output_count=output_count, method=method, dtype=numpy.uint64
+    )
+    numpy.testing.assert_array_equal(arr[:, 5:10, 5:10], value)


=====================================
tests/test_rio_clip.py
=====================================
@@ -145,3 +145,31 @@ def test_clip_rotated(runner, tmpdir):
         main_group, ['clip', 'tests/data/rotated.tif', output])
     assert result.exit_code == 2
     assert 'Non-rectilinear' in result.output
+
+
+ at pytest.mark.parametrize("bounds", [bbox(31.0, -1.0, 33.0, 1.0)])
+def test_clip_bounds_with_complement_nodata(runner, tmpdir, bounds):
+    """Show fix of #2084"""
+    output = str(tmpdir.join("test.tif"))
+    result = runner.invoke(
+        main_group,
+        [
+            "clip",
+            "tests/data/green.tif",
+            output,
+            "--bounds",
+            bounds,
+            "--with-complement",
+            "--nodata",
+            "0",
+        ],
+    )
+    assert result.exit_code == 0
+    assert os.path.exists(output)
+
+    with rasterio.open(output) as out:
+        assert out.shape == (4, 4)
+        data = out.read(masked=True)
+        assert not data.mask[:, 2:, :2].any()
+        assert data.mask[:, :2, :].all()
+        assert data.mask[:, :, 2:].all()


=====================================
tests/test_session.py
=====================================
@@ -116,6 +116,12 @@ def test_session_factory_s3():
     assert isinstance(sesh, AWSSession)
 
 
+def test_session_factory_s3_presigned_url():
+    """Get a DummySession for presigned URLs"""
+    sesh = Session.from_path("https://fancy-cloud.com/lolwut?X-Amz-Signature=foo")
+    assert isinstance(sesh, DummySession)
+
+
 def test_session_factory_s3_no_boto3(monkeypatch):
     """Get an AWSSession for s3:// paths"""
     pytest.importorskip("boto3")


=====================================
tests/test_warp.py
=====================================
@@ -1116,6 +1116,7 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
         Resampling.med: [437194],
         Resampling.q1: [436397],
         Resampling.q3: [438948],
+        Resampling.sum: [439118],
         Resampling.rms: [439385],
     }
 
@@ -1207,6 +1208,7 @@ def test_reproject_resampling_alpha(method):
         Resampling.med: [437194],
         Resampling.q1: [436397],
         Resampling.q3: [438948],
+        Resampling.sum: [439118],
         Resampling.rms: [439385],
     }
 


=====================================
tests/test_windows.py
=====================================
@@ -606,3 +606,12 @@ def test_from_bounds_rotation():
     assert win.row_off == pytest.approx(-2.0)
     assert win.width == pytest.approx(2.0 * width)
     assert win.height == pytest.approx(2.0 * height)
+
+
+def test_issue_2138():
+    """WindowError is raised if bounds and transform are inconsistent"""
+    w, s, e, n = 1.0, 45.7, 1.2, 45.9
+    a = 0.001
+    transform = Affine.translation(w, n) * Affine.scale(a, -a)
+    with pytest.raises(WindowError):
+        from_bounds(w, n, e, s, transform)



View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/96b70e016215d1a8db747c976a79e520163afb17...55e5da9f10986f76959a12d4e6dde8fa5e44d18d

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/96b70e016215d1a8db747c976a79e520163afb17...55e5da9f10986f76959a12d4e6dde8fa5e44d18d
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/20210407/b91038f7/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list