[Git][debian-gis-team/rasterio][upstream] New upstream version 1.1.6

Bas Couwenberg gitlab at salsa.debian.org
Tue Sep 15 06:22:56 BST 2020



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


Commits:
fa275678 by Bas Couwenberg at 2020-09-15T06:46:08+02:00
New upstream version 1.1.6
- - - - -


30 changed files:

- CHANGES.txt
- docs/topics/virtual-warping.rst
- rasterio/__init__.py
- rasterio/_crs.pxd
- rasterio/_crs.pyx
- rasterio/_io.pyx
- rasterio/_warp.pyx
- rasterio/crs.py
- rasterio/errors.py
- rasterio/merge.py
- rasterio/path.py
- rasterio/plot.py
- rasterio/rio/clip.py
- rasterio/rio/convert.py
- rasterio/rio/helpers.py
- rasterio/rio/merge.py
- rasterio/rio/warp.py
- rasterio/session.py
- rasterio/vrt.py
- rasterio/warp.py
- + tests/test_creation_options.py
- tests/test_crs.py
- tests/test_overviews.py
- tests/test_path.py
- tests/test_read_boundless.py
- tests/test_rio_convert.py
- tests/test_rio_helpers.py
- tests/test_rio_merge.py
- tests/test_session.py
- tests/test_warp.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,27 @@
 Changes
 =======
 
+1.1.6 (2020-09-14)
+------------------
+
+- Remove background layer from boundless VRT (#1982). It's not needed since
+  fixes in GDAL after 3.1.3. Wheels on PyPI for rasterio 1.1.6 will patch GDAL
+  2.4.4 to fix those GDAL issues.
+- Clean up VSI files left by MemoryFileBase, resolving #1953.
+- Do not pass empty coordinate arrays to warp._transform to avoid crashes with
+  some versions of GDAL as reported in #1952. Instead, directly return empty
+  output arrays.
+- Properly convert block size `--co` option values to int in rio-clip and
+  rio-warp to prevent exceptions reported in #1989.
+- Fail gracefully when rio-convert lacks an input file (#1985).
+- Allow merge.merge() to open one dataset at a time (#1831).
+- Optimize CRS.__eq__() for CRS described by EPSG codes.
+- Fix bug in ParsedPath.is_remote() reported in #1967.
+- The reproject() method accepts objects that provide `__array__` in addition
+  to instances of numpy.ndarray (#1957, #1959).
+- Custom labels may be used with show_hist() by giving the `label` keyword
+  argument a sequence of label strings, one per band.
+
 1.1.5 (2020-06-02)
 ------------------
 


=====================================
docs/topics/virtual-warping.rst
=====================================
@@ -45,8 +45,8 @@ extract pixels corresponding to its central zoom 9 tile, do the following.
           # for the dataset read window and then scale it by the dimensions
           # of the output array.
           dst_transform = vrt.window_transform(dst_window)
-          scaling = Affine.scale(dst_window.num_cols / 512,
-                                 dst_window.num_rows / 512)
+          scaling = Affine.scale(dst_window.height / 512,
+                                 dst_window.width / 512)
           dst_transform *= scaling
           profile['transform'] = dst_transform
 


=====================================
rasterio/__init__.py
=====================================
@@ -41,7 +41,7 @@ import rasterio.enums
 import rasterio.path
 
 __all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.1.5"
+__version__ = "1.1.6"
 __gdal_version__ = gdal_version()
 
 # Rasterio attaches NullHandler to the 'rasterio' logger and its
@@ -85,9 +85,12 @@ def open(fp, mode='r', driver=None, width=None, height=None, count=None,
         sequentially until a match is found. When multiple drivers are
         available for a format such as JPEG2000, one of them can be
         selected by using this keyword argument.
-    width, height : int, optional
-        The numbers of rows and columns of the raster dataset. Required
-        in 'w' or 'w+' modes, they are ignored in 'r' or 'r+' modes.
+    width : int, optional
+        The number of columns of the raster dataset. Required in 'w' or
+        'w+' modes, it is ignored in 'r' or 'r+' modes.
+    height : int, optional
+        The number of rows of the raster dataset. Required in 'w' or
+        'w+' modes, it is ignored in 'r' or 'r+' modes.
     count : int, optional
         The count of dataset bands. Required in 'w' or 'w+' modes, it is
         ignored in 'r' or 'r+' modes.


=====================================
rasterio/_crs.pxd
=====================================
@@ -6,3 +6,4 @@ include "gdal.pxi"
 cdef class _CRS:
 
     cdef OGRSpatialReferenceH _osr
+    cdef object _epsg


=====================================
rasterio/_crs.pyx
=====================================
@@ -172,19 +172,22 @@ cdef class _CRS(object):
         """
         cdef OGRSpatialReferenceH osr = NULL
 
-        try:
-            osr = exc_wrap_pointer(OSRClone(self._osr))
-            exc_wrap_ogrerr(OSRMorphFromESRI(osr))
-            if OSRAutoIdentifyEPSG(osr) == 0:
-                epsg_code = OSRGetAuthorityCode(osr, NULL)
-                if epsg_code != NULL:
-                    return int(epsg_code.decode('utf-8'))
+        if self._epsg is None:
+            try:
+                osr = exc_wrap_pointer(OSRClone(self._osr))
+                exc_wrap_ogrerr(OSRMorphFromESRI(osr))
+                if OSRAutoIdentifyEPSG(osr) == 0:
+                    epsg_code = OSRGetAuthorityCode(osr, NULL)
+                    if epsg_code != NULL:
+                        self._epsg = int(epsg_code.decode('utf-8'))
+                    else:
+                        self._epsg = None
                 else:
-                    return None
-            else:
-                return None
-        finally:
-            _safe_osr_release(osr)
+                    self._epsg = None
+            finally:
+                _safe_osr_release(osr)
+
+        return self._epsg
 
     @staticmethod
     def from_epsg(code):
@@ -217,6 +220,7 @@ cdef class _CRS(object):
             raise CRSError("The EPSG code is unknown. {}".format(exc))
         else:
             osr_set_traditional_axis_mapping_strategy(obj._osr)
+            obj._epsg = code
             return obj
 
     @staticmethod


=====================================
rasterio/_io.pyx
=====================================
@@ -210,7 +210,9 @@ cdef class DatasetReaderBase(DatasetBase):
             are not cached.
 
         fill_value : scalar
-            Fill value applied in the `boundless=True` case only.
+            Fill value applied in the `boundless=True` case only. Like
+            the fill_value of numpy.ma.MaskedArray, should be value
+            valid for the dataset's data type.
 
         Returns
         -------
@@ -936,6 +938,7 @@ cdef class MemoryFileBase:
         if self._vsif != NULL:
             VSIFCloseL(self._vsif)
         self._vsif = NULL
+        _delete_dataset_if_exists(self.name)
         VSIRmdir(self._dirname.encode("utf-8"))
         self.closed = True
 


=====================================
rasterio/_warp.pyx
=====================================
@@ -338,13 +338,13 @@ def _reproject(
         # We need a src_transform and src_dst in this case. These will
         # be copied to the MEM dataset.
         if dtypes.is_ndarray(source):
-
             if not src_crs:
                 raise CRSError("Missing src_crs.")
-
             if src_nodata is None and hasattr(source, 'fill_value'):
                 # source is a masked array
                 src_nodata = source.fill_value
+            # ensure data converted to numpy array
+            source = np.array(source, copy=False)
             # Convert 2D single-band arrays to 3D multi-band.
             if len(source.shape) == 2:
                 source = source.reshape(1, *source.shape)
@@ -381,10 +381,10 @@ def _reproject(
     try:
 
         if dtypes.is_ndarray(destination):
-
             if not dst_crs:
                 raise CRSError("Missing dst_crs.")
-
+            # ensure data converted to numpy array
+            destination = np.array(destination, copy=False)
             if len(destination.shape) == 2:
                 destination = destination.reshape(1, *destination.shape)
 


=====================================
rasterio/crs.py
=====================================
@@ -359,12 +359,11 @@ class CRS(Mapping):
             else:
                 return cls.from_dict(**val)
 
-        elif '+' in string and '=' in string:
-            return cls.from_proj4(string)
-
-        else:
+        elif string.strip().endswith("]"):
             return cls.from_wkt(string, morph_from_esri_dialect=morph_from_esri_dialect)
 
+        return cls.from_proj4(string)
+
     @classmethod
     def from_proj4(cls, proj):
         """Make a CRS from a PROJ4 string


=====================================
rasterio/errors.py
=====================================
@@ -122,3 +122,7 @@ class PathError(RasterioError):
 
 class ResamplingAlgorithmError(RasterioError):
     """Raised when a resampling algorithm is invalid or inapplicable"""
+
+
+class TransformError(RasterioError):
+    """Raised when transform arguments are invalid"""


=====================================
rasterio/merge.py
=====================================
@@ -1,13 +1,15 @@
 """Copy valid pixels from input files to an output file."""
 
-
+from contextlib import contextmanager
 import logging
 import math
 import warnings
 
 import numpy as np
 
+import rasterio
 from rasterio import windows
+from rasterio.compat import string_types
 from rasterio.transform import Affine
 
 
@@ -33,7 +35,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
 
     Parameters
     ----------
-    datasets: list of dataset objects opened in 'r' mode
+    datasets : list of dataset objects opened in 'r' mode or filenames
         source datasets to be merged.
     bounds: tuple, optional
         Bounds of the output image (left, bottom, right, top).
@@ -94,23 +96,37 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
             out_transform: affine.Affine()
                 Information for mapping pixel coordinates in `dest` to another
                 coordinate system
-    """
-    first = datasets[0]
-    first_res = first.res
-    nodataval = first.nodatavals[0]
-    dt = first.dtypes[0]
 
+    """
     if method not in MERGE_METHODS and not callable(method):
         raise ValueError('Unknown method {0}, must be one of {1} or callable'
                          .format(method, MERGE_METHODS))
 
-    # Determine output band count
-    if indexes is None:
-        src_count = first.count
-    elif isinstance(indexes, int):
-        src_count = indexes
+    # Create a dataset_opener object to use in several places in this function.
+    if isinstance(datasets[0], string_types):
+        dataset_opener = rasterio.open
     else:
-        src_count = len(indexes)
+
+        @contextmanager
+        def nullcontext(obj):
+            try:
+                yield obj
+            finally:
+                pass
+
+        dataset_opener = nullcontext
+
+    with dataset_opener(datasets[0]) as first:
+        first_res = first.res
+        nodataval = first.nodatavals[0]
+        dt = first.dtypes[0]
+
+        if indexes is None:
+            src_count = first.count
+        elif isinstance(indexes, int):
+            src_count = indexes
+        else:
+            src_count = len(indexes)
 
     if not output_count:
         output_count = src_count
@@ -122,8 +138,9 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
         # scan input files
         xs = []
         ys = []
-        for src in datasets:
-            left, bottom, right, top = src.bounds
+        for dataset in datasets:
+            with dataset_opener(dataset) as src:
+                left, bottom, right, top = src.bounds
             xs.extend([left, right])
             ys.extend([bottom, top])
         dst_w, dst_s, dst_e, dst_n = min(xs), min(ys), max(xs), max(ys)
@@ -218,36 +235,43 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
     else:
         raise ValueError(method)
 
-    for idx, src in enumerate(datasets):
-        # Real World (tm) use of boundless reads.
-        # This approach uses the maximum amount of memory to solve the
-        # problem. Making it more efficient is a TODO.
-
-        # 1. Compute spatial intersection of destination and source
-        src_w, src_s, src_e, src_n = src.bounds
-
-        int_w = src_w if src_w > dst_w else dst_w
-        int_s = src_s if src_s > dst_s else dst_s
-        int_e = src_e if src_e < dst_e else dst_e
-        int_n = src_n if src_n < dst_n else dst_n
-
-        # 2. Compute the source window
-        src_window = windows.from_bounds(
-            int_w, int_s, int_e, int_n, src.transform, precision=precision)
-        logger.debug("Src %s window: %r", src.name, src_window)
-
-        src_window = src_window.round_shape()
-
-        # 3. Compute the destination window
-        dst_window = windows.from_bounds(
-            int_w, int_s, int_e, int_n, output_transform, precision=precision)
-
-        # 4. Read data in source window into temp
-        trows, tcols = (
-            int(round(dst_window.height)), int(round(dst_window.width)))
-        temp_shape = (src_count, trows, tcols)
-        temp = src.read(out_shape=temp_shape, window=src_window,
-                        boundless=False, masked=True, indexes=indexes)
+    for idx, dataset in enumerate(datasets):
+        with dataset_opener(dataset) as src:
+            # Real World (tm) use of boundless reads.
+            # This approach uses the maximum amount of memory to solve the
+            # problem. Making it more efficient is a TODO.
+
+            # 1. Compute spatial intersection of destination and source
+            src_w, src_s, src_e, src_n = src.bounds
+
+            int_w = src_w if src_w > dst_w else dst_w
+            int_s = src_s if src_s > dst_s else dst_s
+            int_e = src_e if src_e < dst_e else dst_e
+            int_n = src_n if src_n < dst_n else dst_n
+
+            # 2. Compute the source window
+            src_window = windows.from_bounds(
+                int_w, int_s, int_e, int_n, src.transform, precision=precision
+            )
+            logger.debug("Src %s window: %r", src.name, src_window)
+
+            src_window = src_window.round_shape()
+
+            # 3. Compute the destination window
+            dst_window = windows.from_bounds(
+                int_w, int_s, int_e, int_n, output_transform, precision=precision
+            )
+
+            # 4. Read data in source window into temp
+            trows, tcols = (int(round(dst_window.height)), int(round(dst_window.width)))
+            temp_shape = (src_count, trows, tcols)
+            temp = src.read(
+                out_shape=temp_shape,
+                window=src_window,
+                boundless=False,
+                masked=True,
+                indexes=indexes,
+            )
 
         # 5. Copy elements of temp into dest
         roff, coff = (


=====================================
rasterio/path.py
=====================================
@@ -84,7 +84,7 @@ class ParsedPath(Path):
     @property
     def is_remote(self):
         """Test if the path is a remote, network URI"""
-        return self.scheme and self.scheme.split('+')[-1] in REMOTESCHEMES
+        return bool(self.scheme) and self.scheme.split("+")[-1] in REMOTESCHEMES
 
     @property
     def is_local(self):


=====================================
rasterio/plot.py
=====================================
@@ -222,7 +222,7 @@ def reshape_as_raster(arr):
     return im
 
 
-def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, **kwargs):
+def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, label=None, **kwargs):
     """Easily display a histogram with matplotlib.
 
     Parameters
@@ -240,6 +240,9 @@ def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, **kwargs
         Title for the figure.
     ax : matplotlib axes (opt)
         The raster will be added to this axes if passed.
+    label : matplotlib labels (opt)
+        If passed, matplotlib will use this label list.
+        Otherwise, a default label list will be automatically created
     **kwargs : optional keyword arguments
         These will be passed to the matplotlib hist method. See full list at:
         http://matplotlib.org/api/axes_api.html?highlight=imshow#matplotlib.axes.Axes.hist
@@ -273,12 +276,17 @@ def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, **kwargs
     else:
         colors = colors[:arr.shape[-1]]
 
-    # If a rasterio.Band() is given make sure the proper index is displayed
-    # in the legend.
-    if isinstance(source, (tuple, rasterio.Band)):
-        labels = [str(source[1])]
+    # if the user used the label argument, pass them drectly to matplotlib
+    if label:
+        labels = label
+    # else, create default labels
     else:
-        labels = (str(i + 1) for i in range(len(arr)))
+        # If a rasterio.Band() is given make sure the proper index is displayed
+        # in the legend.
+        if isinstance(source, (tuple, rasterio.Band)):
+            labels = [str(source[1])]
+        else:
+            labels = (str(i + 1) for i in range(len(arr)))
 
     if ax:
         show = False


=====================================
rasterio/rio/clip.py
=====================================
@@ -12,10 +12,8 @@ from rasterio.coords import disjoint_bounds
 from rasterio.crs import CRS
 from rasterio.windows import Window
 
-
 logger = logging.getLogger(__name__)
 
-
 # Geographic (default), projected, or Mercator switch.
 projection_geographic_opt = click.option(
     '--geographic',
@@ -144,12 +142,16 @@ def clip(
                 'transform': src.window_transform(out_window)})
             out_kwargs.update(**creation_options)
 
-            if 'blockxsize' in out_kwargs and out_kwargs['blockxsize'] > width:
-                del out_kwargs['blockxsize']
-                logger.warning("Blockxsize removed from creation options to accomodate small output width")
-            if 'blockysize' in out_kwargs and out_kwargs['blockysize'] > height:
-                del out_kwargs['blockysize']
-                logger.warning("Blockysize removed from creation options to accomodate small output height")
+            if "blockxsize" in out_kwargs and int(out_kwargs["blockxsize"]) > width:
+                del out_kwargs["blockxsize"]
+                logger.warning(
+                    "Blockxsize removed from creation options to accomodate small output width"
+                )
+            if "blockysize" in out_kwargs and int(out_kwargs["blockysize"]) > height:
+                del out_kwargs["blockysize"]
+                logger.warning(
+                    "Blockysize removed from creation options to accomodate small output height"
+                )
 
             with rasterio.open(output, "w", **out_kwargs) as out:
                 out.write(


=====================================
rasterio/rio/convert.py
=====================================
@@ -51,7 +51,9 @@ def convert(
     """
     with ctx.obj['env']:
 
-        outputfile, files = resolve_inout(files=files, output=output, overwrite=overwrite)
+        outputfile, files = resolve_inout(
+            files=files, output=output, overwrite=overwrite, num_inputs=1
+        )
         inputfile = files[0]
 
         with rasterio.open(inputfile) as src:


=====================================
rasterio/rio/helpers.py
=====================================
@@ -5,6 +5,8 @@ Helper objects used by multiple CLI commands.
 import json
 import os
 
+import click
+
 from rasterio.errors import FileOverwriteError
 
 
@@ -59,31 +61,57 @@ def write_features(
         fobj.write('\n')
 
 
-def resolve_inout(input=None, output=None, files=None, overwrite=False):
+def resolve_inout(
+    input=None, output=None, files=None, overwrite=False, num_inputs=None
+):
     """Resolves inputs and outputs from standard args and options.
 
-    :param input: a single input filename, optional.
-    :param output: a single output filename, optional.
-    :param files: a sequence of filenames in which the last is the
-        output filename.
-    :param overwrite: whether to force overwriting the output
-        file, bool.
-    :return: the resolved output filename and input filenames as a
-        tuple of length 2.
-
-    If provided, the :param:`output` file may be overwritten. An output
-    file extracted from :param:`files` will not be overwritten unless
-    :param:`overwrite` is `True`.
+    Parameters
+    ----------
+    input : str
+        A single input filename, optional.
+    output : str
+        A single output filename, optional.
+    files : str
+        A sequence of filenames in which the last is the output filename.
+    overwrite : bool
+        Whether to force overwriting the output file.
+    num_inputs : int
+        Raise exceptions if the number of resolved input files is higher
+        or lower than this number.
+
+    Returns
+    -------
+    tuple (str, list of str)
+        The resolved output filename and input filenames as a tuple of
+        length 2.
+
+    If provided, the output file may be overwritten. An output
+    file extracted from files will not be overwritten unless
+    overwrite is True.
+
+    Raises
+    ------
+    click.BadParameter
+
     """
     resolved_output = output or (files[-1] if files else None)
-    if not overwrite and resolved_output and os.path.exists(
-            resolved_output):
+
+    if not overwrite and resolved_output and os.path.exists(resolved_output):
         raise FileOverwriteError(
-            "file exists and won't be overwritten without use of the "
-            "`--overwrite` option.")
+            "file exists and won't be overwritten without use of the `--overwrite` option."
+        )
+
     resolved_inputs = (
         [input] if input else [] +
         list(files[:-1 if not output else None]) if files else [])
+
+    if num_inputs is not None:
+        if len(resolved_inputs) < num_inputs:
+            raise click.BadParameter("Insufficient inputs")
+        elif len(resolved_inputs) > num_inputs:
+            raise click.BadParameter("Too many inputs")
+
     return resolved_output, resolved_inputs
 
 


=====================================
rasterio/rio/merge.py
=====================================
@@ -49,30 +49,35 @@ def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
     output, files = resolve_inout(
         files=files, output=output, overwrite=overwrite)
 
-    with ctx.obj['env']:
-        datasets = [rasterio.open(f) for f in files]
-        dest, output_transform = merge_tool(datasets, bounds=bounds, res=res,
-                                            nodata=nodata, precision=precision,
-                                            indexes=(bidx or None))
-
-        profile = datasets[0].profile
-        profile['transform'] = output_transform
-        profile['height'] = dest.shape[1]
-        profile['width'] = dest.shape[2]
-        profile['driver'] = driver
-        profile['count'] = dest.shape[0]
-
-        if nodata is not None:
-            profile['nodata'] = nodata
-
-        profile.update(**creation_options)
-
-        with rasterio.open(output, 'w', **profile) as dst:
-            dst.write(dest)
-
-            # uses the colormap in the first input raster.
-            try:
-                colormap = datasets[0].colormap(1)
-                dst.write_colormap(1, colormap)
-            except ValueError:
-                pass
+    with ctx.obj["env"]:
+        dest, output_transform = merge_tool(
+            files,
+            bounds=bounds,
+            res=res,
+            nodata=nodata,
+            precision=precision,
+            indexes=(bidx or None),
+        )
+
+        with rasterio.open(files[0]) as first:
+            profile = first.profile
+            profile["transform"] = output_transform
+            profile["height"] = dest.shape[1]
+            profile["width"] = dest.shape[2]
+            profile["driver"] = driver
+            profile["count"] = dest.shape[0]
+
+            if nodata is not None:
+                profile["nodata"] = nodata
+
+            profile.update(**creation_options)
+
+            with rasterio.open(output, "w", **profile) as dst:
+                dst.write(dest)
+
+                # uses the colormap in the first input raster.
+                try:
+                    colormap = first.colormap(1)
+                    dst.write_colormap(1, colormap)
+                except ValueError:
+                    pass


=====================================
rasterio/rio/warp.py
=====================================
@@ -1,5 +1,6 @@
 """$ rio warp"""
 
+import logging
 from math import ceil
 
 import click
@@ -16,6 +17,7 @@ from rasterio.warp import (
     reproject, Resampling, SUPPORTED_RESAMPLING, transform_bounds,
     aligned_target, calculate_default_transform as calcdt)
 
+logger = logging.getLogger(__name__)
 
 # Improper usage of rio-warp can lead to accidental creation of
 # extremely large datasets. We'll put a hard limit on the size of
@@ -298,12 +300,18 @@ def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds,
             )
 
             # Adjust block size if necessary.
-            if ('blockxsize' in out_kwargs and
-                    dst_width < out_kwargs['blockxsize']):
-                del out_kwargs['blockxsize']
-            if ('blockysize' in out_kwargs and
-                    dst_height < out_kwargs['blockysize']):
-                del out_kwargs['blockysize']
+            if "blockxsize" in out_kwargs and dst_width < int(out_kwargs["blockxsize"]):
+                del out_kwargs["blockxsize"]
+                logger.warning(
+                    "Blockxsize removed from creation options to accomodate small output width"
+                )
+            if "blockysize" in out_kwargs and dst_height < int(
+                out_kwargs["blockysize"]
+            ):
+                del out_kwargs["blockysize"]
+                logger.warning(
+                    "Blockxsize removed from creation options to accomodate small output height"
+                )
 
             out_kwargs.update(**creation_options)
 


=====================================
rasterio/session.py
=====================================
@@ -1,6 +1,7 @@
 """Abstraction for sessions in various clouds."""
 
 import logging
+import os
 
 from rasterio.path import parse_path, UnparsedPath
 
@@ -276,9 +277,13 @@ class AWSSession(Session):
                 profile_name=profile_name)
 
         self.requester_pays = requester_pays
-        self.unsigned = aws_unsigned
+        self.unsigned = bool(os.getenv("AWS_NO_SIGN_REQUEST", aws_unsigned))
         self.endpoint_url = endpoint_url
-        self._creds = self._session._session.get_credentials() if self._session else None
+        self._creds = (
+            self._session._session.get_credentials()
+            if not self.unsigned and self._session
+            else None
+        )
 
     @classmethod
     def hascreds(cls, config):


=====================================
rasterio/vrt.py
=====================================
@@ -164,9 +164,9 @@ def _boundless_vrt_doc(
         vrtrasterband.attrib['dataType'] = _gdal_typename(dtype)
         vrtrasterband.attrib['band'] = str(bidx)
 
-        if nodata is not None:
+        if background is not None or nodata is not None:
             nodatavalue = ET.SubElement(vrtrasterband, 'NoDataValue')
-            nodatavalue.text = str(nodata)
+            nodatavalue.text = str(background or nodata)
 
             if hidenodata:
                 hidenodatavalue = ET.SubElement(vrtrasterband, 'HideNoDataValue')
@@ -175,35 +175,6 @@ def _boundless_vrt_doc(
         colorinterp = ET.SubElement(vrtrasterband, 'ColorInterp')
         colorinterp.text = ci.name.capitalize()
 
-        if background is not None:
-            complexsource = ET.SubElement(vrtrasterband, 'ComplexSource')
-            sourcefilename = ET.SubElement(complexsource, 'SourceFilename')
-            sourcefilename.attrib['relativeToVRT'] = '1'
-            sourcefilename.attrib["shared"] = "0"
-            sourcefilename.text = "dummy.tif"
-            sourceband = ET.SubElement(complexsource, 'SourceBand')
-            sourceband.text = str(bidx)
-            sourceproperties = ET.SubElement(complexsource, 'SourceProperties')
-            sourceproperties.attrib['RasterXSize'] = str(width)
-            sourceproperties.attrib['RasterYSize'] = str(height)
-            sourceproperties.attrib['dataType'] = _gdal_typename(dtype)
-            sourceproperties.attrib['BlockYSize'] = str(block_shape[0])
-            sourceproperties.attrib['BlockXSize'] = str(block_shape[1])
-            srcrect = ET.SubElement(complexsource, 'SrcRect')
-            srcrect.attrib['xOff'] = '0'
-            srcrect.attrib['yOff'] = '0'
-            srcrect.attrib['xSize'] = '1'
-            srcrect.attrib['ySize'] = '1'
-            dstrect = ET.SubElement(complexsource, 'DstRect')
-            dstrect.attrib['xOff'] = '0'
-            dstrect.attrib['yOff'] = '0'
-            dstrect.attrib['xSize'] = '1'
-            dstrect.attrib['ySize'] = '1'
-            scaleratio = ET.SubElement(complexsource, 'ScaleRatio')
-            scaleratio.text = '0'
-            scaleoffset = ET.SubElement(complexsource, 'ScaleOffset')
-            scaleoffset.text = str(background)
-
         complexsource = ET.SubElement(vrtrasterband, 'ComplexSource')
         sourcefilename = ET.SubElement(complexsource, 'SourceFilename')
         sourcefilename.attrib['relativeToVRT'] = "0"


=====================================
rasterio/warp.py
=====================================
@@ -12,7 +12,7 @@ from rasterio._base import _transform
 from rasterio._warp import _calculate_default_transform, _reproject, _transform_geom
 from rasterio.enums import Resampling
 from rasterio.env import GDALVersion, ensure_env, require_gdal_version
-from rasterio.errors import GDALBehaviorChangeException
+from rasterio.errors import GDALBehaviorChangeException, TransformError
 from rasterio.transform import array_bounds
 
 # Gauss (7) is not supported for warp
@@ -49,9 +49,16 @@ def transform(src_crs, dst_crs, xs, ys, zs=None):
     out: tuple of array_like, (xs, ys, [zs])
         Tuple of x, y, and optionally z vectors, transformed into the target
         coordinate reference system.
-    """
 
-    return _transform(src_crs, dst_crs, xs, ys, zs)
+    """
+    if len(xs) != len(ys):
+        raise TransformError("xs and ys arrays must be the same length")
+    elif zs is not None and len(xs) != len(zs):
+        raise TransformError("zs, xs, and ys arrays must be the same length")
+    if len(xs) == 0:
+        return ([], [], []) if zs is not None else ([], [])
+    else:
+        return _transform(src_crs, dst_crs, xs, ys, zs)
 
 
 @ensure_env


=====================================
tests/test_creation_options.py
=====================================
@@ -0,0 +1,30 @@
+"""Tests of creation option behavior"""
+
+import logging
+
+import rasterio
+from rasterio.profiles import DefaultGTiffProfile
+
+from .conftest import requires_gdal2
+
+
+ at requires_gdal2(reason="GDAL 1.x warning text is obsolete")
+def test_warning(tmpdir, caplog):
+    """Be warned about invalid creation options"""
+    profile = DefaultGTiffProfile(
+        count=1, height=256, width=256, compression="lolwut", foo="bar"
+    )
+
+    with rasterio.Env(GDAL_VALIDATE_CREATION_OPTIONS=True):
+        rasterio.open(str(tmpdir.join("test.tif")), "w", **profile)
+
+    assert [
+        "CPLE_NotSupported in driver GTiff does not support creation option COMPRESSION",
+        "CPLE_NotSupported in driver GTiff does not support creation option FOO",
+    ] == sorted(
+        [
+            rec.message
+            for rec in caplog.records
+            if rec.levelno == logging.WARNING and rec.name == "rasterio._env"
+        ]
+    )


=====================================
tests/test_crs.py
=====================================
@@ -510,4 +510,33 @@ def test_equals_different_type(other):
 
 def test_from_user_input_custom_crs_class():
     """Support comparison to foreign objects that provide to_wkt()"""
-    assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
\ No newline at end of file
+    assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
+
+
+def test_from_string__wkt_with_proj():
+    wkt = (
+        'PROJCS["WGS 84 / Pseudo-Mercator",'
+        'GEOGCS["WGS 84",'
+        '    DATUM["WGS_1984",'
+        '        SPHEROID["WGS 84",6378137,298.257223563,'
+        '            AUTHORITY["EPSG","7030"]],'
+        '        AUTHORITY["EPSG","6326"]],'
+        '    PRIMEM["Greenwich",0,'
+        '        AUTHORITY["EPSG","8901"]],'
+        '    UNIT["degree",0.0174532925199433,'
+        '        AUTHORITY["EPSG","9122"]],'
+        '    AUTHORITY["EPSG","4326"]],'
+        'PROJECTION["Mercator_1SP"],'
+        'PARAMETER["central_meridian",0],'
+        'PARAMETER["scale_factor",1],'
+        'PARAMETER["false_easting",0],'
+        'PARAMETER["false_northing",0],'
+        'UNIT["metre",1,'
+        '    AUTHORITY["EPSG","9001"]],'
+        'AXIS["Easting",EAST],'
+        'AXIS["Northing",NORTH],'
+        'EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0 '
+        '+lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs"],'
+        'AUTHORITY["EPSG","3857"]] '
+    )
+    assert CRS.from_string(wkt).to_epsg() == 3857


=====================================
tests/test_overviews.py
=====================================
@@ -10,9 +10,6 @@ from rasterio.env import GDALVersion
 from rasterio.errors import OverviewCreationError
 
 
-gdal_version = GDALVersion()
-
-
 def test_count_overviews_zero(data):
     inputfile = str(data.join('RGB.byte.tif'))
     with rasterio.open(inputfile) as src:
@@ -42,8 +39,9 @@ def test_build_overviews_two(data):
 
 
 @pytest.mark.xfail(
-    gdal_version < GDALVersion.parse('2.0'),
-    reason="Bilinear resampling not supported by GDAL < 2.0")
+    GDALVersion.runtime() < GDALVersion.parse("2.0"),
+    reason="Bilinear resampling not supported by GDAL < 2.0",
+)
 def test_build_overviews_bilinear(data):
     inputfile = str(data.join('RGB.byte.tif'))
     with rasterio.open(inputfile, 'r+') as src:


=====================================
tests/test_path.py
=====================================
@@ -36,12 +36,26 @@ def test_parsed_path_remote(scheme):
     assert ParsedPath('example.com/foo.tif', None, scheme).is_remote
 
 
+ at pytest.mark.parametrize("uri", ["/test.tif", "file:///test.tif"])
+def test_parsed_path_not_remote(uri):
+    """Check for paths that are not remote"""
+    assert False == ParsedPath.from_uri(uri).is_remote
+
+
 @pytest.mark.parametrize('scheme', [None, '', 'zip', 'tar', 'file', 'zip+file'])
 def test_parsed_path_file_local(scheme):
     """A parsed path is remote"""
     assert ParsedPath('foo.tif', None, scheme).is_local
 
 
+ at pytest.mark.parametrize(
+    "uri", ["s3://bucket/test.tif", "https://example.com/test.tif"]
+)
+def test_parsed_path_not_local(uri):
+    """Check for paths that are not local"""
+    assert False == ParsedPath.from_uri(uri).is_local
+
+
 def test_parse_path_zip():
     """Correctly parse zip scheme URL"""
     parsed = parse_path('zip://tests/data/files.zip!foo.tif')


=====================================
tests/test_read_boundless.py
=====================================
@@ -5,6 +5,7 @@ import numpy as np
 import pytest
 
 import rasterio
+from rasterio.enums import Resampling
 from rasterio.windows import Window
 
 
@@ -146,3 +147,28 @@ def test_msk_read_masks(path_rgb_msk_byte_tif):
         assert not msk[0:195,0:195].any()
         # We have the valid data expected in the center.
         assert msk.mean() > 90
+
+
+ at pytest.mark.xfail(reason="GDAL 3.1 skips overviews because of background layer")
+def test_issue1982(capfd):
+    """See a curl request for overview file"""
+    # Note: the underlying GDAL issue has been fixed after 3.1.3. The
+    # rasterio 1.1.6 wheels published to PyPI will include a patched
+    # 2.4.4 that also fixes the issue.  This test will XPASS in the
+    # rasterio-wheels tests.
+    with rasterio.Env(CPL_CURL_VERBOSE=True), rasterio.open(
+        "https://raw.githubusercontent.com/mapbox/rasterio/master/tests/data/green.tif"
+    ) as src:
+        image = src.read(
+            indexes=[1, 2, 3],
+            window=Window(col_off=-32, row_off=-32, width=64, height=64),
+            resampling=Resampling.cubic,
+            boundless=True,
+            out_shape=(3, 10, 10),
+            fill_value=42,
+        )
+    captured = capfd.readouterr()
+    assert "green.tif" in captured.err
+    assert "green.tif.ovr" in captured.err
+    assert (42 == image[:, :3, :]).all()
+    assert (42 == image[:, :, :3]).all()


=====================================
tests/test_rio_convert.py
=====================================
@@ -287,3 +287,20 @@ def test_convert_overwrite_with_option(runner, tmpdir):
         'convert', 'tests/data/RGB.byte.tif', '-o', outputname, '-f', 'JPEG',
         '--overwrite'])
     assert result.exit_code == 0
+
+
+def test_convert_no_input(runner, tmpdir):
+    """Test fix of issue1985"""
+    outputname = str(tmpdir.join("test.tif"))
+    result = runner.invoke(main_group, ["convert", "-o", outputname, "-f", "JPEG"])
+    assert result.exit_code == 2
+
+
+def test_convert_no_input_overwrite(runner, tmpdir):
+    """Test fix of issue1985"""
+    outputname = str(tmpdir.join("test.tif"))
+    result = runner.invoke(
+        main_group, ["convert", "--overwrite", outputname, "-f", "JPEG"]
+    )
+    assert result.exit_code == 2
+    assert "Insufficient inputs" in result.output


=====================================
tests/test_rio_helpers.py
=====================================
@@ -1,3 +1,6 @@
+"""Test of CLI helpers"""
+
+import click
 import pytest
 
 from rasterio.errors import FileOverwriteError
@@ -21,6 +24,18 @@ def test_resolve_files_inout__inout_files_output_o():
         files=('a', 'b', 'c'), output='out') == ('out', ['a', 'b', 'c'])
 
 
+def test_resolve_files_insufficient_inputs():
+    with pytest.raises(click.BadParameter) as excinfo:
+        helpers.resolve_inout(files=["a"], num_inputs=1)
+    assert "Insufficient inputs" in str(excinfo.value)
+
+
+def test_resolve_files_too_many_inputs():
+    with pytest.raises(click.BadParameter) as excinfo:
+        helpers.resolve_inout(files=["a", "b", "c"], num_inputs=1)
+    assert "Too many inputs" in str(excinfo.value)
+
+
 def test_fail_overwrite(tmpdir):
     """Unforced overwrite of existing file fails."""
     foo_tif = tmpdir.join('foo.tif')
@@ -29,6 +44,7 @@ def test_fail_overwrite(tmpdir):
         helpers.resolve_inout(files=[str(x) for x in tmpdir.listdir()])
         assert "file exists and won't be overwritten without use of the " in str(excinfo.value)
 
+
 def test_overwrite(tmpdir):
     """Forced overwrite of existing file succeeds."""
     foo_tif = tmpdir.join('foo.tif')


=====================================
tests/test_rio_merge.py
=====================================
@@ -556,3 +556,9 @@ def test_merge_precision(tmpdir, precision):
     result = runner.invoke(main_group, ["merge", "-f", "AAIGrid"] + precision + inputs + [outputname])
     assert result.exit_code == 0
     assert open(outputname).read() == textwrap.dedent(expected)
+
+
+def test_merge_filenames(tiffs):
+    inputs = [str(x) for x in tiffs.listdir()]
+    inputs.sort()
+    merge(inputs, res=2)


=====================================
tests/test_session.py
=====================================
@@ -224,3 +224,15 @@ def test_session_aws_or_dummy_dummy(monkeypatch):
     with monkeypatch.context() as mpctx:
         mpctx.setattr("rasterio.session.boto3", None)
         assert isinstance(Session.aws_or_dummy(), DummySession)
+
+
+def test_no_sign_request(monkeypatch):
+    """If AWS_NO_SIGN_REQUEST is set do not default to aws_unsigned=False"""
+    monkeypatch.setenv("AWS_NO_SIGN_REQUEST", "YES")
+    assert AWSSession().unsigned
+
+
+def test_no_credentialization_if_unsigned(monkeypatch):
+    """Don't get credentials if we're not signing, see #1984"""
+    sesh = AWSSession(aws_unsigned=True)
+    assert sesh._creds is None


=====================================
tests/test_warp.py
=====================================
@@ -13,7 +13,12 @@ from rasterio.control import GroundControlPoint
 from rasterio.crs import CRS
 from rasterio.enums import Resampling
 from rasterio.env import GDALVersion
-from rasterio.errors import (GDALBehaviorChangeException, CRSError, GDALVersionError)
+from rasterio.errors import (
+    GDALBehaviorChangeException,
+    CRSError,
+    GDALVersionError,
+    TransformError,
+)
 from rasterio.warp import (
     reproject,
     transform_geom,
@@ -101,12 +106,12 @@ WGS84_crs = CRS.from_epsg(4326)
 
 def test_transform_src_crs_none():
     with pytest.raises(CRSError):
-        transform(None, WGS84_crs, [], [])
+        transform(None, WGS84_crs, [1], [1])
 
 
 def test_transform_dst_crs_none():
     with pytest.raises(CRSError):
-        transform(WGS84_crs, None, [], [])
+        transform(WGS84_crs, None, [1], [1])
 
 
 def test_transform_bounds_src_crs_none():
@@ -217,19 +222,23 @@ def test_transform_bounds__esri_wkt():
     )
 
 
-def test_transform_bounds_densify():
+ at pytest.mark.parametrize(
+    "density,expected",
+    [
+        (0, (-1684649.41338, -350356.81377, 1684649.41338, 2234551.18559)),
+        (100, (-1684649.41338, -555777.79210, 1684649.41338, 2234551.18559)),
+    ],
+)
+def test_transform_bounds_densify(density, expected):
     # This transform is non-linear along the edges, so densification produces
     # a different result than otherwise
     src_crs = CRS.from_epsg(4326)
-    dst_crs = CRS.from_epsg(2163)
-    assert np.allclose(
-        transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=0),
-        (-1684649.41338, -350356.81377, 1684649.41338, 2234551.18559),
+    dst_crs = CRS.from_proj4(
+        "+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs"
     )
-
     assert np.allclose(
-        transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=100),
-        (-1684649.41338, -555777.79210, 1684649.41338, 2234551.18559),
+        expected,
+        transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=density),
     )
 
 
@@ -1092,6 +1101,59 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
 
     assert np.count_nonzero(out) in expected[method]
 
+ at pytest.mark.parametrize("test3d,count_nonzero", [(True, 1309625), (False, 437686)])
+def test_reproject_array_interface(test3d, count_nonzero, path_rgb_byte_tif):
+    class DataArray:
+        def __init__(self, data):
+            self.data = data
+
+        def __array__(self, dtype=None):
+            return self.data
+
+        @property
+        def dtype(self):
+            return self.data.dtype
+
+    with rasterio.open(path_rgb_byte_tif) as src:
+        if test3d:
+            source = DataArray(src.read())
+        else:
+            source = DataArray(src.read(1))
+    out = DataArray(np.empty(source.data.shape, dtype=np.uint8))
+    reproject(
+        source,
+        out,
+        src_transform=src.transform,
+        src_crs=src.crs,
+        src_nodata=src.nodata,
+        dst_transform=DST_TRANSFORM,
+        dst_crs="EPSG:3857",
+        dst_nodata=99,
+    )
+    assert isinstance(out, DataArray)
+    assert np.count_nonzero(out.data[out.data != 99]) == count_nonzero
+
+
+ at pytest.mark.parametrize("test3d,count_nonzero", [(True, 1309625), (False, 437686)])
+def test_reproject_masked(test3d, count_nonzero, path_rgb_byte_tif):
+    with rasterio.open(path_rgb_byte_tif) as src:
+        if test3d:
+            source = src.read(masked=True)
+        else:
+            source = src.read(1, masked=True)
+    out = np.empty(source.shape, dtype=np.uint8)
+    reproject(
+        source,
+        out,
+        src_transform=src.transform,
+        src_crs=src.crs,
+        dst_transform=DST_TRANSFORM,
+        dst_crs="EPSG:3857",
+        dst_nodata=99,
+    )
+    assert np.ma.is_masked(source)
+    assert np.count_nonzero(out[out != 99]) == count_nonzero
+
 
 @pytest.mark.parametrize("method", SUPPORTED_RESAMPLING)
 def test_reproject_resampling_alpha(method):
@@ -1628,3 +1690,29 @@ def test_reproject_init_dest_nodata():
         src_nodata=0, init_dest_nodata=False
     )
     assert destination.all()
+
+
+def test_empty_transform_inputs():
+    """Check for fix of #1952"""
+    assert ([], []) == rasterio.warp.transform(
+        "EPSG:3857", "EPSG:4326", [], [], zs=None
+    )
+
+
+def test_empty_transform_inputs_z():
+    """Check for fix of #1952"""
+    assert ([], [], []) == rasterio.warp.transform(
+        "EPSG:3857", "EPSG:4326", [], [], zs=[]
+    )
+
+
+def test_empty_transform_inputs_length():
+    """Get an exception of inputs have different lengths"""
+    with pytest.raises(TransformError):
+        rasterio.warp.transform("EPSG:3857", "EPSG:4326", [1], [1, 2])
+
+
+def test_empty_transform_inputs_length_z():
+    """Get an exception of inputs have different lengths"""
+    with pytest.raises(TransformError):
+        rasterio.warp.transform("EPSG:3857", "EPSG:4326", [1, 2], [1, 2], zs=[0])



View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/commit/fa275678b4b2ef28c7a3e872dcbc0e8042d19f3b

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/commit/fa275678b4b2ef28c7a3e872dcbc0e8042d19f3b
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/20200915/b03d3447/attachment-0001.html>


More information about the Pkg-grass-devel mailing list