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

Bas Couwenberg gitlab at salsa.debian.org
Wed Jun 3 05:03:19 BST 2020



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


Commits:
8d980ac1 by Bas Couwenberg at 2020-06-03T05:46:59+02:00
New upstream version 1.1.5
- - - - -


26 changed files:

- .travis.yml
- CHANGES.txt
- rasterio/__init__.py
- rasterio/_io.pxd
- rasterio/_io.pyx
- rasterio/_shim.pxd
- rasterio/_shim1.pyx
- rasterio/_warp.pyx
- rasterio/env.py
- rasterio/errors.py
- rasterio/gdal.pxi
- rasterio/io.py
- rasterio/merge.py
- rasterio/shim_rasterioex.pxi
- rasterio/vrt.py
- rasterio/warp.py
- requirements.txt
- setup.py
- tests/test__env.py
- tests/test_boundless_read.py
- tests/test_env.py
- tests/test_memoryfile.py
- tests/test_read_resample.py
- tests/test_rio_merge.py
- tests/test_warp.py
- tests/test_warpedvrt.py


Changes:

=====================================
.travis.yml
=====================================
@@ -17,16 +17,14 @@ jobs:
       env: GDALVERSION="1.11.5" PROJVERSION="4.8.0"
     - python: "2.7"
       env: GDALVERSION="2.2.4" PROJVERSION="4.9.3"
-    - python: "2.7"
-      env: GDALVERSION="2.3.3" PROJVERSION="4.9.3"
     - python: "3.6"
       env: GDALVERSION="2.2.4" PROJVERSION="4.9.3"
     - python: "3.6"
       env: GDALVERSION="2.3.3" PROJVERSION="4.9.3"
     - python: "3.6"
-      env: GDALVERSION="2.4.3" PROJVERSION="4.9.3"
+      env: GDALVERSION="2.4.4" PROJVERSION="4.9.3"
     - python: "3.6"
-      env: GDALVERSION="3.0.1" PROJVERSION="6.1.1"
+      env: GDALVERSION="3.0.4" PROJVERSION="6.2.1"
 
 addons:
   apt:


=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,31 @@
 Changes
 =======
 
+1.1.5 (2020-06-02)
+------------------
+
+- Earlier versions of rasterio set the CHECK_WITH_INVERT_PROJ config option to
+  `True` by default. This is very rarely necessary and broke the GRIB format
+  driver (#1248), so we no longer set this option (#1942). Users of rasterio
+  1.1.5 in combination with GDAL 1.11 and PROJ 4.8 may see some small
+  differences, compared to rasterio versions < 1.1.5, in results of warping
+  global datasets.
+- WarpedVRT can properly handle two use cases that weren't ruled out in version
+  1.1.4: simple scaling of datasets and control over the scaling of reprojected
+  output (#1921, #1936).
+- The error in making boundless reads of datasets opened using the
+  OVERVIEW_LEVEL reported in #1929 has been resolved by #1939.
+- The pixel shift in reads from datasets reported in the user discussion group
+  and #1932, has been fixed (#1938).
+- We have extended the signature of merge's method function (#1933).
+- The MemoryFile implementation has been improved so that it can support
+  multi-part S3 downloads (#1926).
+- Members of the Resampling enum with a value > 7 can only be used in warp
+  operations (#1930). We now raise a ResamplingAlgorithmError if they are used
+  with non-warp read and writes.
+- To help users of poetry, the conditional requirements in setup.py have been
+  changed to use PEP 496 environment markers exclusively (#1777).
+
 1.1.4 (2020-05-07)
 ------------------
 


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


=====================================
rasterio/_io.pxd
=====================================
@@ -1,9 +1,9 @@
+include "gdal.pxi"
+
 cimport numpy as np
 
 from rasterio._base cimport DatasetBase
 
-include "gdal.pxi"
-
 
 cdef class DatasetReaderBase(DatasetBase):
     pass
@@ -32,6 +32,10 @@ cdef class InMemoryRaster:
     cdef GDALRasterBandH band(self, int) except NULL
 
 
+cdef class MemoryFileBase:
+    cdef VSILFILE * _vsif
+
+
 ctypedef np.uint8_t DTYPE_UBYTE_t
 ctypedef np.uint16_t DTYPE_UINT16_t
 ctypedef np.int16_t DTYPE_INT16_t


=====================================
rasterio/_io.pyx
=====================================
@@ -834,10 +834,10 @@ def silence_errors():
         CPLPopErrorHandler()
 
 
-cdef class MemoryFileBase(object):
+cdef class MemoryFileBase:
     """Base for a BytesIO-like class backed by an in-memory file."""
 
-    def __init__(self, file_or_bytes=None, filename=None, ext=''):
+    def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
         """A file in an in-memory filesystem.
 
         Parameters
@@ -849,8 +849,9 @@ cdef class MemoryFileBase(object):
         ext : str
             A file extension for the in-memory file under /vsimem. Ignored if
             filename was provided.
+
         """
-        cdef VSILFILE *vsi_handle = NULL
+        cdef VSILFILE *fp = NULL
 
         if file_or_bytes:
             if hasattr(file_or_bytes, 'read'):
@@ -864,33 +865,37 @@ cdef class MemoryFileBase(object):
         else:
             initial_bytes = b''
 
+        # Make an in-memory directory specific to this dataset to help organize
+        # auxiliary files.
+        self._dirname = dirname or str(uuid4())
+        VSIMkdir("/vsimem/{0}".format(self._dirname).encode("utf-8"), 0666)
+
         if filename:
             # GDAL's SRTMHGT driver requires the filename to be "correct" (match
             # the bounds being written)
-            self.name = '/vsimem/{0}'.format(filename)
+            self.name = "/vsimem/{0}/{1}".format(self._dirname, filename)
         else:
             # GDAL 2.1 requires a .zip extension for zipped files.
-            self.name = '/vsimem/{0}.{1}'.format(uuid4(), ext.lstrip('.'))
+            self.name = "/vsimem/{0}/{0}.{1}".format(self._dirname, ext.lstrip('.'))
 
         self._path = self.name.encode('utf-8')
-        self._pos = 0
-        self.closed = False
 
         self._initial_bytes = initial_bytes
         cdef unsigned char *buffer = self._initial_bytes
 
         if self._initial_bytes:
+            self._vsif = VSIFileFromMemBuffer(
+               self._path, buffer, len(self._initial_bytes), 0)
+            self.mode = "r"
 
-            vsi_handle = VSIFileFromMemBuffer(
-                self._path, buffer, len(self._initial_bytes), 0)
+        else:
+            self._vsif = VSIFOpenL(self._path, "w+")
+            self.mode = "w+"
 
-            if vsi_handle == NULL:
-                raise IOError(
-                    "Failed to create in-memory file using initial bytes.")
+        if self._vsif == NULL:
+            raise IOError("Failed to open in-memory file.")
 
-            if VSIFCloseL(vsi_handle) != 0:
-                raise IOError(
-                    "Failed to properly close in-memory file.")
+        self.closed = False
 
     def exists(self):
         """Test if the in-memory file exists.
@@ -899,18 +904,10 @@ cdef class MemoryFileBase(object):
         -------
         bool
             True if the in-memory file exists.
-        """
-        cdef VSILFILE *fp = NULL
-        cdef const char *cypath = self._path
 
-        with nogil:
-            fp = VSIFOpenL(cypath, 'r')
-
-        if fp != NULL:
-            VSIFCloseL(fp)
-            return True
-        else:
-            return False
+        """
+        cdef VSIStatBufL st_buf
+        return VSIStatL(self._path, &st_buf) == 0
 
     def __len__(self):
         """Length of the file's buffer in number of bytes.
@@ -921,105 +918,65 @@ cdef class MemoryFileBase(object):
         """
         return self.getbuffer().size
 
+    def getbuffer(self):
+        """Return a view on bytes of the file."""
+        cdef unsigned char *buffer = NULL
+        cdef vsi_l_offset buffer_len = 0
+        cdef np.uint8_t [:] buff_view
+
+        buffer = VSIGetMemFileBuffer(self._path, &buffer_len, 0)
+
+        if buffer == NULL or buffer_len == 0:
+            buff_view = np.array([], dtype='uint8')
+        else:
+            buff_view = <np.uint8_t[:buffer_len]>buffer
+        return buff_view
+
     def close(self):
-        """Close MemoryFile and release allocated memory."""
-        VSIUnlink(self._path)
-        self._pos = 0
-        self._initial_bytes = None
+        if self._vsif != NULL:
+            VSIFCloseL(self._vsif)
+        self._vsif = NULL
+        VSIRmdir(self._dirname.encode("utf-8"))
         self.closed = True
 
-    def read(self, size=-1):
-        """Read size bytes from MemoryFile."""
-        cdef VSILFILE *fp = NULL
-        # Return no bytes immediately if the position is at or past the
-        # end of the file.
-        length = len(self)
-
-        if self._pos >= length:
-            self._pos = length
-            return b''
+    def seek(self, offset, whence=0):
+        return VSIFSeekL(self._vsif, offset, whence)
 
-        if size == -1:
-            size = length - self._pos
+    def tell(self):
+        if self._vsif != NULL:
+            return VSIFTellL(self._vsif)
         else:
-            size = min(size, length - self._pos)
+            return 0
 
-        cdef unsigned char *buffer = <unsigned char *>CPLMalloc(size)
+    def read(self, size=-1):
+        """Read size bytes from MemoryFile."""
         cdef bytes result
+        cdef unsigned char *buffer = NULL
+        cdef vsi_l_offset buffer_len = 0
 
-        fp = VSIFOpenL(self._path, 'r')
+        if size < 0:
+            buffer = VSIGetMemFileBuffer(self._path, &buffer_len, 0)
+            size = buffer_len
 
-        try:
-            fp = exc_wrap_vsilfile(fp)
-            if VSIFSeekL(fp, self._pos, 0) < 0:
-                raise IOError(
-                    "Failed to seek to offset %s in %s.",
-                    self._pos, self.name)
+        buffer = <unsigned char *>CPLMalloc(size)
 
-            objects_read = VSIFReadL(buffer, 1, size, fp)
+        try:
+            objects_read = VSIFReadL(buffer, 1, size, self._vsif)
             result = <bytes>buffer[:objects_read]
 
         finally:
-            VSIFCloseL(fp)
             CPLFree(buffer)
 
-        self._pos += len(result)
         return result
 
-    def seek(self, offset, whence=0):
-        """Seek to position in MemoryFile."""
-        if whence == 0:
-            pos = offset
-        elif whence == 1:
-            pos = self._pos + offset
-        elif whence == 2:
-            pos = len(self) - offset
-        if pos < 0:
-            raise ValueError("negative seek position: {}".format(pos))
-        if pos > len(self):
-            raise ValueError("seek position past end of file: {}".format(pos))
-        self._pos = pos
-        return self._pos
-
-    def tell(self):
-        """Tell current position in MemoryFile."""
-        return self._pos
-
     def write(self, data):
         """Write data bytes to MemoryFile"""
-        cdef VSILFILE *fp = NULL
         cdef const unsigned char *view = <bytes>data
         n = len(data)
-
-        if not self.exists():
-            fp = exc_wrap_vsilfile(VSIFOpenL(self._path, 'w'))
-        else:
-            fp = exc_wrap_vsilfile(VSIFOpenL(self._path, 'r+'))
-            if VSIFSeekL(fp, self._pos, 0) < 0:
-                raise IOError(
-                    "Failed to seek to offset %s in %s.", self._pos, self.name)
-
-        result = VSIFWriteL(view, 1, n, fp)
-        VSIFFlushL(fp)
-        VSIFCloseL(fp)
-
-        self._pos += result
+        result = VSIFWriteL(view, 1, n, self._vsif)
+        VSIFFlushL(self._vsif)
         return result
 
-    def getbuffer(self):
-        """Return a view on bytes of the file."""
-        cdef unsigned char *buffer = NULL
-        cdef vsi_l_offset buffer_len = 0
-        cdef np.uint8_t [:] buff_view
-
-        buffer = VSIGetMemFileBuffer(self._path, &buffer_len, 0)
-
-        if buffer == NULL or buffer_len == 0:
-            buff_view = np.array([], dtype='uint8')
-        else:
-            buff_view = <np.uint8_t[:buffer_len]>buffer
-        return buff_view
-
 
 cdef class DatasetWriterBase(DatasetReaderBase):
     """Read-write access to raster data and metadata


=====================================
rasterio/_shim.pxd
=====================================
@@ -2,9 +2,9 @@ include "gdal.pxi"
 
 cdef GDALDatasetH open_dataset(object filename, int mode, object allowed_drivers, object open_options, object siblings) except NULL
 cdef int delete_nodata_value(GDALRasterBandH hBand) except 3
-cdef int io_band(GDALRasterBandH band, int mode, float xoff, float yoff, float width, float height, object data, int resampling=*) except -1
-cdef int io_multi_band(GDALDatasetH hds, int mode, float xoff, float yoff, float width, float height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
-cdef int io_multi_mask(GDALDatasetH hds, int mode, float xoff, float yoff, float width, float height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
+cdef int io_band(GDALRasterBandH band, int mode, double xoff, double yoff, double width, double height, object data, int resampling=*) except -1
+cdef int io_multi_band(GDALDatasetH hds, int mode, double xoff, double yoff, double width, double height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
+cdef int io_multi_mask(GDALDatasetH hds, int mode, double xoff, double yoff, double width, double height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
 cdef const char* osr_get_name(OGRSpatialReferenceH hSrs)
 cdef void osr_set_traditional_axis_mapping_strategy(OGRSpatialReferenceH hSrs)
 cdef void set_proj_search_path(object path)


=====================================
rasterio/_shim1.pyx
=====================================
@@ -47,8 +47,8 @@ cdef int delete_nodata_value(GDALRasterBandH hBand) except 3:
 
 
 cdef int io_band(
-        GDALRasterBandH band, int mode, float x0, float y0,
-        float width, float height, object data, int resampling=0) except -1:
+        GDALRasterBandH band, int mode, double x0, double y0,
+        double width, double height, object data, int resampling=0) except -1:
     """Read or write a region of data for the band.
 
     Implicit are
@@ -83,8 +83,8 @@ cdef int io_band(
 
 
 cdef int io_multi_band(
-        GDALDatasetH hds, int mode, float x0, float y0, float width,
-        float height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
+        GDALDatasetH hds, int mode, double x0, double y0, double width,
+        double height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
     """Read or write a region of data for multiple bands.
 
     Implicit are
@@ -130,8 +130,8 @@ cdef int io_multi_band(
 
 
 cdef int io_multi_mask(
-        GDALDatasetH hds, int mode, float x0, float y0, float width,
-        float height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
+        GDALDatasetH hds, int mode, double x0, double y0, double width,
+        double height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
     """Read or write a region of data for multiple band masks.
 
     Implicit are


=====================================
rasterio/_warp.pyx
=====================================
@@ -8,7 +8,7 @@ import uuid
 import warnings
 import xml.etree.ElementTree as ET
 
-from affine import identity
+from affine import Affine, identity
 import numpy as np
 
 import rasterio
@@ -729,19 +729,17 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
             nodata = dst_nodata
 
         # Deprecate dst_width.
-        if dst_width is not None:
+        if dst_width is not None and width is None:
             warnings.warn(
                 "dst_width will be removed in 1.1, use width",
                 RasterioDeprecationWarning)
-        if width is None:
             width = dst_width
 
         # Deprecate dst_height.
-        if dst_height is not None:
+        if dst_height is not None and height is None:
             warnings.warn(
                 "dst_height will be removed in 1.1, use height",
                 RasterioDeprecationWarning)
-        if height is None:
             height = dst_height
 
         # Deprecate dst_transform.
@@ -811,15 +809,6 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
         if not self.src_transform:
             self.src_transform = self.src_dataset.transform
 
-        if self.dst_transform:
-            t = self.src_transform.to_gdal()
-            for i in range(6):
-                src_gt[i] = t[i]
-
-            t = self.dst_transform.to_gdal()
-            for i in range(6):
-                dst_gt[i] = t[i]
-
         if not self.src_crs:
             self.src_crs = self.src_dataset.crs
 
@@ -856,11 +845,12 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
             if GDALGetRasterColorInterpretation(hBand) == GCI_AlphaBand:
                 src_alpha_band = bidx
 
+        # Adding an alpha band when the source has one is trouble.
+        # It will result in suprisingly unmasked data. We will 
+        # raise an exception instead.
+
         if add_alpha:
 
-            # Adding an alpha band when the source has one is trouble.
-            # It will result in suprisingly unmasked data. We will 
-            # raise an exception instead.
             if src_alpha_band:
                 raise WarpOptionsError(
                     "The VRT already has an alpha band, adding a new one is not supported")
@@ -886,56 +876,98 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
 
         psWOptions.hSrcDS = hds
 
-        try:
+        # We handle four different use cases.
+        #
+        # 1. Destination transform, height, and width are provided by
+        #    the caller.
+        # 2. Pure scaling: source and destination CRS are the same,
+        #    destination height and width are provided by the caller,
+        #    destination transform is not provided; it is computed by
+        #    scaling the source transform.
+        # 3. Warp with scaling: CRS are different, destination height
+        #    and width are provided by the caller, destination transform
+        #    is computed and scaled to preserve given dimensions.
+        # 4. Warp with destination height, width, and transform
+        #    auto-generated by GDAL.
+
+        # Case 4
+        if not self.dst_transform and (not self.dst_width or not self.dst_height):
 
-            if self.dst_width and self.dst_height and self.dst_transform:
-                # set up transform args (otherwise handled in
-                # GDALAutoCreateWarpedVRT)
-                try:
+            with nogil:
+                hds_warped = GDALAutoCreateWarpedVRT(hds, src_crs_wkt, dst_crs_wkt, c_resampling, c_tolerance, psWOptions)
 
-                    hTransformArg = exc_wrap_pointer(
-                        GDALCreateGenImgProjTransformer3(
-                            src_crs_wkt, src_gt, dst_crs_wkt, dst_gt))
+        else:
 
-                    if c_tolerance > 0.0:
-                        hTransformArg = exc_wrap_pointer(
-                            GDALCreateApproxTransformer(
-                                GDALGenImgProjTransform,
-                                hTransformArg,
-                                c_tolerance))
+            # Case 1
+            if self.dst_transform and self.dst_width and self.dst_height:
+                pass
 
-                        psWOptions.pfnTransformer = GDALApproxTransform
+            # Case 2
+            elif self.src_crs == self.dst_crs and self.dst_width and self.dst_height and not self.dst_transform:
 
-                        GDALApproxTransformerOwnsSubtransformer(
-                            hTransformArg, 1)
+                self.dst_transform = Affine.scale(self.src_dataset.width / self.dst_width, self.src_dataset.height / self.dst_height) * self.src_transform
 
-                    log.debug("Created transformer and options.")
-                    psWOptions.pTransformerArg = hTransformArg
+            # Case 3
+            elif self.src_crs != self.dst_crs and self.dst_width and self.dst_height and not self.dst_transform:
 
-                except Exception:
-                    GDALDestroyApproxTransformer(hTransformArg)
-                    raise
+                left, bottom, right, top = self.src_dataset.bounds
+                self.dst_transform, width, height = _calculate_default_transform(self.src_crs, self.dst_crs, self.src_dataset.width, self.src_dataset.height, left=left, bottom=bottom, right=right, top=top)
+                self.dst_transform = Affine.scale(width / self.dst_width, height / self.dst_height ) * self.dst_transform
 
-                with nogil:
-                    hds_warped = GDALCreateWarpedVRT(
-                        hds, c_width, c_height, dst_gt, psWOptions)
-                    GDALSetProjection(hds_warped, dst_crs_wkt)
+            # If we get here it's because the tests above are buggy. We raise a Python exception to indicate that.
+            else:
+                raise RuntimeError(
+                    "Parameterization error: src_crs={!r}, dst_crs={!r}, dst_width={!r}, dst_height={!r}, dst_transform={!r}".format(self.src_crs, self.dst_crs, self.dst_width, self.dst_height, self.dst_transform))
 
-                self._hds = exc_wrap_pointer(hds_warped)
+            t = self.src_transform.to_gdal()
+            for i in range(6):
+                src_gt[i] = t[i]
+
+            t = self.dst_transform.to_gdal()
+            for i in range(6):
+                dst_gt[i] = t[i]
+
+            try:
+                hTransformArg = exc_wrap_pointer(
+                    GDALCreateGenImgProjTransformer3(
+                        src_crs_wkt, src_gt, dst_crs_wkt, dst_gt))
+
+                if c_tolerance > 0.0:
+
+                    hTransformArg = exc_wrap_pointer(
+                        GDALCreateApproxTransformer(
+                            GDALGenImgProjTransform,
+                            hTransformArg,
+                            c_tolerance))
+
+                    psWOptions.pfnTransformer = GDALApproxTransform
+                    GDALApproxTransformerOwnsSubtransformer(hTransformArg, 1)
+
+            except Exception:
+                CPLFree(dst_crs_wkt)
+                CPLFree(src_crs_wkt)
+                CSLDestroy(c_warp_extras)
+                if psWOptions != NULL:
+                    GDALDestroyWarpOptions(psWOptions)
 
             else:
-                with nogil:
-                    hds_warped = GDALAutoCreateWarpedVRT(
-                        hds, src_crs_wkt, dst_crs_wkt, c_resampling,
-                        c_tolerance, psWOptions)
+                psWOptions.pTransformerArg = hTransformArg
+
+            with nogil:
+                hds_warped = GDALCreateWarpedVRT(hds, c_width, c_height, dst_gt, psWOptions)
+                GDALSetProjection(hds_warped, dst_crs_wkt)
 
-                self._hds = exc_wrap_pointer(hds_warped)
+        # End of the 4 cases.
+
+        try:
+            self._hds = exc_wrap_pointer(hds_warped)
 
         except CPLE_OpenFailedError as err:
             raise RasterioIOError(err.errmsg)
 
         finally:
             CPLFree(dst_crs_wkt)
+            CPLFree(src_crs_wkt)
             CSLDestroy(c_warp_extras)
             if psWOptions != NULL:
                 GDALDestroyWarpOptions(psWOptions)
@@ -946,6 +978,7 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
                     delete_nodata_value(self.band(i))
                 except NotImplementedError as exc:
                     log.warn(str(exc))
+
         else:
             for i in self.indexes:
                 GDALSetRasterNoDataValue(self.band(i), self.dst_nodata)
@@ -1089,7 +1122,7 @@ def _suggested_proxy_vrt_doc(width, height, transform=None, crs=None, gcps=None)
             gcp.attrib['X'] = str(point.x)
             gcp.attrib['Y'] = str(point.y)
             gcp.attrib['Z'] = str(point.z)
-    else:
+    elif transform:
         geotransform = ET.SubElement(vrtdataset, 'GeoTransform')
         geotransform.text = ','.join([str(v) for v in transform.to_gdal()])
 


=====================================
rasterio/env.py
=====================================
@@ -98,7 +98,6 @@ class Env(object):
         dict
         """
         return {
-            'CHECK_WITH_INVERT_PROJ': True,
             'GTIFF_IMPLICIT_JPEG_OVR': False,
             "RASTERIO_ENV": True
         }


=====================================
rasterio/errors.py
=====================================
@@ -118,3 +118,7 @@ class DatasetAttributeError(RasterioError, NotImplementedError):
 
 class PathError(RasterioError):
     """Raised when a dataset path is malformed or invalid"""
+
+
+class ResamplingAlgorithmError(RasterioError):
+    """Raised when a resampling algorithm is invalid or inapplicable"""


=====================================
rasterio/gdal.pxi
=====================================
@@ -53,10 +53,16 @@ cdef extern from "cpl_string.h" nogil:
     const char* CPLParseNameValue(const char *pszNameValue, char **ppszKey)
 
 
+cdef extern from "sys/stat.h" nogil:
+    struct stat:
+        pass
+
+
 cdef extern from "cpl_vsi.h" nogil:
 
     ctypedef int vsi_l_offset
     ctypedef FILE VSILFILE
+    ctypedef stat VSIStatBufL
 
     unsigned char *VSIGetMemFileBuffer(const char *path,
                                        vsi_l_offset *data_len,
@@ -66,14 +72,15 @@ cdef extern from "cpl_vsi.h" nogil:
     VSILFILE* VSIFOpenL(const char *path, const char *mode)
     int VSIFCloseL(VSILFILE *fp)
     int VSIUnlink(const char *path)
-
+    int VSIMkdir(const char *path, long mode)
+    int VSIRmdir(const char *path)
     int VSIFFlushL(VSILFILE *fp)
     size_t VSIFReadL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp)
     int VSIFSeekL(VSILFILE *fp, vsi_l_offset nOffset, int nWhence)
     vsi_l_offset VSIFTellL(VSILFILE *fp)
     int VSIFTruncateL(VSILFILE *fp, vsi_l_offset nNewSize)
     size_t VSIFWriteL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp)
-
+    int VSIStatL(const char *pszFilename, VSIStatBufL *psStatBuf)
 
 cdef extern from "ogr_srs_api.h" nogil:
 


=====================================
rasterio/io.py
=====================================
@@ -85,7 +85,7 @@ class MemoryFile(MemoryFileBase):
      'width': 791}
 
     """
-    def __init__(self, file_or_bytes=None, filename=None, ext=''):
+    def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
         """Create a new file in memory
 
         Parameters
@@ -102,7 +102,7 @@ class MemoryFile(MemoryFileBase):
         MemoryFile
         """
         super(MemoryFile, self).__init__(
-            file_or_bytes=file_or_bytes, filename=filename, ext=ext)
+            file_or_bytes=file_or_bytes, dirname=dirname, filename=filename, ext=ext)
 
     @ensure_env
     def open(self, driver=None, width=None, height=None, count=None, crs=None,
@@ -125,7 +125,7 @@ class MemoryFile(MemoryFileBase):
 
         if self.closed:
             raise IOError("I/O operation on closed file.")
-        if self.exists():
+        if len(self) > 0:
             log.debug("VSI path: {}".format(mempath.path))
             return DatasetReader(mempath, driver=driver, sharing=sharing, **kwargs)
         else:


=====================================
rasterio/merge.py
=====================================
@@ -16,8 +16,8 @@ logger = logging.getLogger(__name__)
 MERGE_METHODS = ('first', 'last', 'min', 'max')
 
 
-def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=None,
-          method='first'):
+def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10,
+          indexes=None, output_count=None, method='first'):
     """Copy valid pixels from input files to an output file.
 
     All files must have the same number of bands, data type, and
@@ -45,10 +45,16 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
     nodata: float, optional
         nodata value to use in output file. If not set, uses the nodata value
         in the first input raster.
+    dtype: numpy dtype or string
+        dtype to use in outputfile. If not set, uses the dtype value in the
+        first input raster.
     precision: float, optional
         Number of decimal points of precision when computing inverse transform.
     indexes : list of ints or a single int, optional
         bands to read and merge
+    output_count: int, optional
+        If using callable it may be useful to have additional bands in the output
+        in addition to the indexes specified for read
     method : str or callable
         pre-defined method:
             first: reverse painting
@@ -57,7 +63,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
             max: pixel-wise max of existing and new
         or custom callable with signature:
 
-        def function(old_data, new_data, old_nodata, new_nodata):
+        def function(old_data, new_data, old_nodata, new_nodata, index=None, roff=None, coff=None):
 
             Parameters
             ----------
@@ -69,6 +75,12 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
             old_nodata, new_data : array_like
                 boolean masks where old/new data is nodata
                 same shape as old_data
+            index: int
+                index of the current dataset within the merged dataset collection
+            roff: int
+                row offset in base array
+            coff: int
+                column offset in base array
 
     Returns
     -------
@@ -86,7 +98,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
     first = datasets[0]
     first_res = first.res
     nodataval = first.nodatavals[0]
-    dtype = first.dtypes[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'
@@ -94,11 +106,14 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
 
     # Determine output band count
     if indexes is None:
-        output_count = first.count
+        src_count = first.count
     elif isinstance(indexes, int):
-        output_count = 1
+        src_count = indexes
     else:
-        output_count = len(indexes)
+        src_count = len(indexes)
+
+    if not output_count:
+        output_count = src_count
 
     # Extent from option or extent of all inputs
     if bounds:
@@ -137,8 +152,12 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
     logger.debug("Output width: %d, height: %d", output_width, output_height)
     logger.debug("Adjusted bounds: %r", (dst_w, dst_s, dst_e, dst_n))
 
+    if dtype is not None:
+        dt = dtype
+        logger.debug("Set dtype: %s", dt)
+
     # create destination array
-    dest = np.zeros((output_count, output_height, output_width), dtype=dtype)
+    dest = np.zeros((output_count, output_height, output_width), dtype=dt)
 
     if nodata is not None:
         nodataval = nodata
@@ -168,17 +187,17 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
         nodataval = 0
 
     if method == 'first':
-        def copyto(old_data, new_data, old_nodata, new_nodata):
+        def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
             mask = np.logical_and(old_nodata, ~new_nodata)
             old_data[mask] = new_data[mask]
 
     elif method == 'last':
-        def copyto(old_data, new_data, old_nodata, new_nodata):
+        def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
             mask = ~new_nodata
             old_data[mask] = new_data[mask]
 
     elif method == 'min':
-        def copyto(old_data, new_data, old_nodata, new_nodata):
+        def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
             mask = np.logical_and(~old_nodata, ~new_nodata)
             old_data[mask] = np.minimum(old_data[mask], new_data[mask])
 
@@ -186,7 +205,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
             old_data[mask] = new_data[mask]
 
     elif method == 'max':
-        def copyto(old_data, new_data, old_nodata, new_nodata):
+        def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
             mask = np.logical_and(~old_nodata, ~new_nodata)
             old_data[mask] = np.maximum(old_data[mask], new_data[mask])
 
@@ -199,7 +218,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
     else:
         raise ValueError(method)
 
-    for src in datasets:
+    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.
@@ -226,7 +245,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
         # 4. Read data in source window into temp
         trows, tcols = (
             int(round(dst_window.height)), int(round(dst_window.width)))
-        temp_shape = (output_count, trows, tcols)
+        temp_shape = (src_count, trows, tcols)
         temp = src.read(out_shape=temp_shape, window=src_window,
                         boundless=False, masked=True, indexes=indexes)
 
@@ -242,6 +261,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
             region_nodata = region == nodataval
             temp_nodata = temp.mask
 
-        copyto(region, temp, region_nodata, temp_nodata)
+        copyto(region, temp, region_nodata, temp_nodata,
+               index=idx, roff=roff, coff=coff)
 
     return dest, output_transform


=====================================
rasterio/shim_rasterioex.pxi
=====================================
@@ -3,6 +3,7 @@
 
 from rasterio import dtypes
 from rasterio.enums import Resampling
+from rasterio.errors import ResamplingAlgorithmError
 
 cimport numpy as np
 
@@ -32,8 +33,8 @@ cdef extern from "gdal.h" nogil:
     cdef CPLErr GDALDatasetRasterIOEx(GDALDatasetH hDS, GDALRWFlag eRWFlag, int nDSXOff, int nDSYOff, int nDSXSize, int nDSYSize, void *pBuffer, int nBXSize, int nBYSize, GDALDataType eBDataType, int nBandCount, int *panBandCount, GSpacing nPixelSpace, GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
 
 
-cdef int io_band(GDALRasterBandH band, int mode, float x0, float y0,
-                 float width, float height, object data, int resampling=0) except -1:
+cdef int io_band(GDALRasterBandH band, int mode, double x0, double y0,
+                 double width, double height, object data, int resampling=0) except -1:
     """Read or write a region of data for the band.
 
     Implicit are
@@ -43,7 +44,11 @@ cdef int io_band(GDALRasterBandH band, int mode, float x0, float y0,
 
     The striding of `data` is passed to GDAL so that it can navigate
     the layout of ndarray views.
+
     """
+    if resampling > 7:
+        raise ResamplingAlgorithmError("{!r} can be used for warp operations but not for reads and writes".format(Resampling(resampling)))
+
     # GDAL handles all the buffering indexing, so a typed memoryview,
     # as in previous versions, isn't needed.
     cdef void *buf = <void *>np.PyArray_DATA(data)
@@ -78,8 +83,8 @@ cdef int io_band(GDALRasterBandH band, int mode, float x0, float y0,
     return exc_wrap_int(retval)
 
 
-cdef int io_multi_band(GDALDatasetH hds, int mode, float x0, float y0,
-                       float width, float height, object data,
+cdef int io_multi_band(GDALDatasetH hds, int mode, double x0, double y0,
+                       double width, double height, object data,
                        Py_ssize_t[:] indexes, int resampling=0) except -1:
     """Read or write a region of data for multiple bands.
 
@@ -90,7 +95,11 @@ cdef int io_multi_band(GDALDatasetH hds, int mode, float x0, float y0,
 
     The striding of `data` is passed to GDAL so that it can navigate
     the layout of ndarray views.
+
     """
+    if resampling > 7:
+        raise ResamplingAlgorithmError("{!r} can be used for warp operations but not for reads and writes".format(Resampling(resampling)))
+
     cdef int i = 0
     cdef int retval = 3
     cdef int *bandmap = NULL
@@ -136,8 +145,8 @@ cdef int io_multi_band(GDALDatasetH hds, int mode, float x0, float y0,
         CPLFree(bandmap)
 
 
-cdef int io_multi_mask(GDALDatasetH hds, int mode, float x0, float y0,
-                       float width, float height, object data,
+cdef int io_multi_mask(GDALDatasetH hds, int mode, double x0, double y0,
+                       double width, double height, object data,
                        Py_ssize_t[:] indexes, int resampling=0) except -1:
     """Read or write a region of data for multiple band masks.
 
@@ -148,7 +157,11 @@ cdef int io_multi_mask(GDALDatasetH hds, int mode, float x0, float y0,
 
     The striding of `data` is passed to GDAL so that it can navigate
     the layout of ndarray views.
+
     """
+    if resampling > 7:
+        raise ResamplingAlgorithmError("{!r} can be used for warp operations but not for reads and writes".format(Resampling(resampling)))
+
     cdef int i = 0
     cdef int j = 0
     cdef int retval = 3


=====================================
rasterio/vrt.py
=====================================
@@ -232,6 +232,13 @@ def _boundless_vrt_doc(
             nodata_elem = ET.SubElement(complexsource, 'NODATA')
             nodata_elem.text = str(src_dataset.nodata)
 
+        if src_dataset.options is not None:
+            openoptions = ET.SubElement(complexsource, 'OpenOptions')
+            for ookey, oovalue in src_dataset.options.items():
+                ooi = ET.SubElement(openoptions, 'OOI')
+                ooi.attrib['key'] = str(ookey)
+                ooi.text = str(oovalue)
+
         # Effectively replaces all values of the source dataset with
         # 255.  Due to GDAL optimizations, the source dataset will not
         # be read, so we get a performance improvement.


=====================================
rasterio/warp.py
=====================================
@@ -453,7 +453,7 @@ def calculate_default_transform(
     if any(x is None for x in (left, bottom, right, top)) and not gcps:
         raise ValueError("Either four bounding values or ground control points"
                          "must be specified")
-    
+
     if (dst_width is None) != (dst_height is None):
         raise ValueError("Either dst_width and dst_height must be specified "
                          "or none of them.")
@@ -467,7 +467,8 @@ def calculate_default_transform(
         raise ValueError("Resolution cannot be used with dst_width and dst_height.")
 
     dst_affine, dst_width, dst_height = _calculate_default_transform(
-        src_crs, dst_crs, width, height, left, bottom, right, top, gcps)
+        src_crs, dst_crs, width, height, left, bottom, right, top, gcps
+    )
 
     # If resolution is specified, Keep upper-left anchored
     # adjust the transform resolutions


=====================================
requirements.txt
=====================================
@@ -1,5 +1,5 @@
 affine~=2.3.0
-attrs>=17.4.0
+attrs>=19.2.0
 boto3>=1.2.4
 click==7.0
 click-plugins


=====================================
setup.py
=====================================
@@ -352,10 +352,8 @@ with open('README.rst') as f:
 
 # Runtime requirements.
 inst_reqs = [
-    'affine', 'attrs', 'click>=4.0,<8', 'cligj>=0.5', 'numpy', 'snuggs>=1.4.1', 'click-plugins']
-
-if sys.version_info < (3, 4):
-    inst_reqs.append('enum34')
+    'affine', 'attrs', 'click>=4.0,<8', 'cligj>=0.5', 'numpy', 'snuggs>=1.4.1', 'click-plugins',
+    'enum34 ; python_version < "3.4"']
 
 extra_reqs = {
     'ipython': ['ipython>=2.0'],
@@ -363,13 +361,9 @@ extra_reqs = {
     'plot': ['matplotlib'],
     'test': [
         'pytest>=2.8.2', 'pytest-cov>=2.2.0', 'boto3>=1.2.4', 'packaging',
-        'hypothesis'],
+        'hypothesis', 'futures;python_version<"3.2"', 'mock;python_version<"3.2"'],
     'docs': ['ghp-import', 'numpydoc', 'sphinx', 'sphinx-rtd-theme']}
 
-# Add futures to 'test' for Python < 3.2.
-if sys.version_info < (3, 2):
-    extra_reqs['test'].extend(['futures', 'mock'])
-
 # Add all extra requirements
 extra_reqs['all'] = list(set(itertools.chain(*extra_reqs.values())))
 


=====================================
tests/test__env.py
=====================================
@@ -36,6 +36,7 @@ def mock_debian(tmpdir):
     tmpdir.ensure("share/gdal/2.3/header.dxf")
     tmpdir.ensure("share/gdal/2.4/header.dxf")
     tmpdir.ensure("share/gdal/3.0/header.dxf")
+    tmpdir.ensure("share/gdal/3.1/header.dxf")
     tmpdir.ensure("share/proj/epsg")
     return tmpdir
 


=====================================
tests/test_boundless_read.py
=====================================
@@ -8,7 +8,7 @@ import pytest
 import rasterio
 from rasterio.windows import Window
 
-from .conftest import requires_gdal21
+from .conftest import requires_gdal21, gdal_version
 
 
 @requires_gdal21(reason="Pixel equality tests require float windows and GDAL 2.1")
@@ -115,3 +115,16 @@ def test_boundless_masked_fill_value_overview_masks():
         data = src.read(1, masked=True, boundless=True, window=Window(-300, -335, 1000, 1000), fill_value=5, out_shape=(512, 512))
     assert data.fill_value == 5
     assert data.mask[:, 0].all()
+
+
+ at pytest.mark.xfail(
+    gdal_version.major == 1,
+    reason="GDAL versions < 2 do not support OVERVIEW_LEVEL open option",
+)
+def test_boundless_open_options():
+    """Open options are taken into account"""
+    with rasterio.open("tests/data/cogeo.tif", overview_level=1) as src:
+        data1 = src.read(1, boundless=True)
+    with rasterio.open("tests/data/cogeo.tif", overview_level=2) as src:
+        data2 = src.read(1, boundless=True)
+    assert not numpy.array_equal(data1, data2)


=====================================
tests/test_env.py
=====================================
@@ -204,7 +204,6 @@ def test_env_defaults(gdalenv):
     assert env.options['foo'] == 'x'
     assert not env.context_options
     with env:
-        assert get_gdal_config('CHECK_WITH_INVERT_PROJ') is True
         assert get_gdal_config('GTIFF_IMPLICIT_JPEG_OVR') is False
         assert get_gdal_config("RASTERIO_ENV") is True
 
@@ -781,6 +780,7 @@ def test_require_gdal_version_chaining():
     assert message in exc_info.value.args[0]
 
 
+ at pytest.mark.network
 def test_rio_env_no_credentials(tmpdir, monkeypatch, runner):
     """Confirm that we can get drivers without any credentials"""
     credentials_file = tmpdir.join('credentials')


=====================================
tests/test_memoryfile.py
=====================================
@@ -49,6 +49,13 @@ def rgb_data_and_profile(path_rgb_byte_tif):
     return data, profile
 
 
+def test_initial_empty():
+    with MemoryFile() as memfile:
+        assert len(memfile) == 0
+        assert len(memfile.getbuffer()) == 0
+        assert memfile.tell() == 0
+
+
 def test_initial_not_bytes():
     """Creating a MemoryFile from not bytes fails."""
     with pytest.raises(TypeError):
@@ -116,6 +123,21 @@ def test_non_initial_bytes_in_two(rgb_file_bytes):
             assert src.read().shape == (3, 718, 791)
 
 
+def test_non_initial_bytes_in_two_reverse(rgb_file_bytes):
+    """MemoryFile contents can be read from bytes in two steps, tail first, and opened.
+    Demonstrates fix of #1926."""
+    with MemoryFile() as memfile:
+        memfile.seek(600000)
+        assert memfile.write(rgb_file_bytes[600000:]) == len(rgb_file_bytes) - 600000
+        memfile.seek(0)
+        assert memfile.write(rgb_file_bytes[:600000]) == 600000
+        with memfile.open() as src:
+            assert src.driver == "GTiff"
+            assert src.count == 3
+            assert src.dtypes == ("uint8", "uint8", "uint8")
+            assert src.read().shape == (3, 718, 791)
+
+
 def test_no_initial_bytes(rgb_data_and_profile):
     """An empty MemoryFile can be opened and written into."""
     data, profile = rgb_data_and_profile
@@ -264,10 +286,10 @@ def test_memfile_copyfiles(path_rgb_msk_byte_tif):
     """Multiple files can be copied to a MemoryFile using copyfiles"""
     with rasterio.open(path_rgb_msk_byte_tif) as src:
         src_basename = os.path.basename(src.name)
-        with MemoryFile(filename=src_basename) as memfile:
+        with MemoryFile(dirname="foo", filename=src_basename) as memfile:
             copyfiles(src.name, memfile.name)
             with memfile.open() as rgb2:
-                assert sorted(rgb2.files) == sorted(['/vsimem/{}'.format(src_basename), '/vsimem/{}.msk'.format(src_basename)])
+                assert sorted(rgb2.files) == sorted(['/vsimem/foo/{}'.format(src_basename), '/vsimem/foo/{}.msk'.format(src_basename)])
 
 
 def test_multi_memfile(path_rgb_msk_byte_tif):
@@ -277,9 +299,9 @@ def test_multi_memfile(path_rgb_msk_byte_tif):
     with open(path_rgb_msk_byte_tif + '.msk', 'rb') as msk_fp:
         msk_bytes = msk_fp.read()
 
-    with MemoryFile(tif_bytes, filename='foo.tif') as tifmemfile, MemoryFile(msk_bytes, filename='foo.tif.msk') as mskmemfile:
+    with MemoryFile(tif_bytes, dirname="bar", filename='foo.tif') as tifmemfile, MemoryFile(msk_bytes, dirname="bar", filename='foo.tif.msk') as mskmemfile:
         with tifmemfile.open() as src:
-            assert sorted(src.files) == sorted(['/vsimem/foo.tif', '/vsimem/foo.tif.msk'])
+            assert sorted(os.path.basename(fn) for fn in src.files) == sorted(['foo.tif', 'foo.tif.msk'])
             assert src.mask_flag_enums == ([MaskFlags.per_dataset],) * 3
 
 


=====================================
tests/test_read_resample.py
=====================================
@@ -4,9 +4,11 @@ that it does this correctly.
 """
 
 import numpy as np
+import pytest
 
 import rasterio
 from rasterio.enums import Resampling
+from rasterio.errors import ResamplingAlgorithmError
 from rasterio.windows import Window
 
 from .conftest import requires_gdal2
@@ -86,3 +88,11 @@ def test_float_window():
         out_shape = (401, 401)
         window = Window(300.5, 300.5, 200.5, 200.5)
         s.read(1, window=window, out_shape=out_shape)
+
+
+ at requires_gdal2
+def test_resampling_alg_error():
+    """Get an exception instead of a crash when using warp-only algs for read or write, see issue #1930"""
+    with pytest.raises(ResamplingAlgorithmError):
+        with rasterio.open("tests/data/RGB.byte.tif") as src:
+            src.read(1, out_shape=(1, 10, 10), resampling=Resampling.max)


=====================================
tests/test_rio_merge.py
=====================================
@@ -16,7 +16,7 @@ from rasterio.merge import merge
 from rasterio.rio.main import main_group
 from rasterio.transform import Affine
 
-from .conftest import requires_gdal22
+from .conftest import requires_gdal22, gdal_version
 
 
 # Fixture to create test datasets within temporary directory
@@ -285,6 +285,41 @@ def test_merge_overlapping(test_data_dir_overlapping):
         assert np.all(data == expected)
 
 
+def test_merge_overlapping_callable_long(test_data_dir_overlapping, runner):
+    outputname = str(test_data_dir_overlapping.join('merged.tif'))
+    inputs = [str(x) for x in test_data_dir_overlapping.listdir()]
+    datasets = [rasterio.open(x) for x in inputs]
+    test_merge_overlapping_callable_long.index = 0
+
+    def mycallable(old_data, new_data, old_nodata, new_nodata,
+                   index=None, roff=None, coff=None):
+        assert old_data.shape[0] == 5
+        assert new_data.shape[0] == 1
+        assert test_merge_overlapping_callable_long.index == index
+        test_merge_overlapping_callable_long.index += 1
+
+    merge(datasets, output_count=5, method=mycallable)
+
+
+def test_custom_callable_merge(test_data_dir_overlapping, runner):
+    inputs = ['tests/data/world.byte.tif'] * 3
+    datasets = [rasterio.open(x) for x in inputs]
+    meta = datasets[0].meta
+    output_count = 4
+
+    def mycallable(old_data, new_data, old_nodata, new_nodata,
+                   index=None, roff=None, coff=None):
+        # input data are bytes, test output doesn't overflow
+        old_data[index] = (index + 1) * 259 # use a number > 255 but divisible by 3 for testing
+        # update additional band that we specified in output_count
+        old_data[3, :, :] += index
+
+    arr, _ = merge(datasets, output_count=output_count, method=mycallable, dtype=np.uint64)
+
+    np.testing.assert_array_equal(np.mean(arr[:3], axis=0), 518)
+    np.testing.assert_array_equal(arr[3, :, :], 3)
+
+
 # Fixture to create test datasets within temporary directory
 @fixture(scope='function')
 def test_data_dir_float(tmpdir):
@@ -443,6 +478,10 @@ def test_merge_tiny_res_bounds(tiffs):
         assert data[0, 1, 1] == 0
 
 
+ at pytest.mark.xfail(
+    gdal_version.major == 1,
+    reason="GDAL versions < 2 do not support data read/write with float sizes and offsets",
+)
 def test_merge_rgb(tmpdir):
     """Get back original image"""
     outputname = str(tmpdir.join('merged.tif'))
@@ -466,6 +505,10 @@ def test_merge_tiny_intres(tiffs):
     merge(datasets, res=2)
 
 
+ at pytest.mark.xfail(
+    gdal_version.major == 1,
+    reason="GDAL versions < 2 do not support data read/write with float sizes and offsets",
+)
 @pytest.mark.parametrize("precision", [[], ["--precision", "9"]])
 def test_merge_precision(tmpdir, precision):
     """See https://github.com/mapbox/rasterio/issues/1837"""


=====================================
tests/test_warp.py
=====================================
@@ -1,12 +1,12 @@
-import json
 """rasterio.warp module tests"""
 
+import json
 import sys
 
-import pytest
 from affine import Affine
 import numpy as np
 from numpy.testing import assert_almost_equal
+import pytest
 
 import rasterio
 from rasterio.control import GroundControlPoint
@@ -1062,18 +1062,18 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
     # Expected count of nonzero pixels for each resampling method, based
     # on running rasterio with each of the following configurations
     expected = {
-        Resampling.nearest: 438113,
-        Resampling.bilinear: 439280,
-        Resampling.cubic: 437888,
-        Resampling.cubic_spline: 440475,
-        Resampling.lanczos: 436001,
-        Resampling.average: 439419,
-        Resampling.mode: 437298,
-        Resampling.max: 439464,
-        Resampling.min: 436397,
-        Resampling.med: 437194,
-        Resampling.q1: 436397,
-        Resampling.q3: 438948,
+        Resampling.nearest: [438113],
+        Resampling.bilinear: [439280],
+        Resampling.cubic: [437888],
+        Resampling.cubic_spline: [440475],
+        Resampling.lanczos: [436001],
+        Resampling.average: [439419, 439172],  # latter value for GDAL 3.1
+        Resampling.mode: [437298],
+        Resampling.max: [439464],
+        Resampling.min: [436397],
+        Resampling.med: [437194],
+        Resampling.q1: [436397],
+        Resampling.q3: [438948],
     }
 
     with rasterio.open(path_rgb_byte_tif) as src:
@@ -1086,11 +1086,11 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
         src_transform=src.transform,
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
-        dst_crs={"init": "epsg:3857"},
+        dst_crs="EPSG:3857",
         resampling=method,
     )
 
-    assert np.count_nonzero(out) == expected[method]
+    assert np.count_nonzero(out) in expected[method]
 
 
 @pytest.mark.parametrize("method", SUPPORTED_RESAMPLING)
@@ -1099,18 +1099,18 @@ def test_reproject_resampling_alpha(method):
     # Expected count of nonzero pixels for each resampling method, based
     # on running rasterio with each of the following configurations
     expected = {
-        Resampling.nearest: 438113,
-        Resampling.bilinear: 439280,
-        Resampling.cubic: 437888,
-        Resampling.cubic_spline: 440475,
-        Resampling.lanczos: 436001,
-        Resampling.average: 439419,
-        Resampling.mode: 437298,
-        Resampling.max: 439464,
-        Resampling.min: 436397,
-        Resampling.med: 437194,
-        Resampling.q1: 436397,
-        Resampling.q3: 438948,
+        Resampling.nearest: [438113],
+        Resampling.bilinear: [439280],
+        Resampling.cubic: [437888],
+        Resampling.cubic_spline: [440475],
+        Resampling.lanczos: [436001],
+        Resampling.average: [439419, 439172],  # latter value for GDAL 3.1
+        Resampling.mode: [437298],
+        Resampling.max: [439464],
+        Resampling.min: [436397],
+        Resampling.med: [437194],
+        Resampling.q1: [436397],
+        Resampling.q3: [438948],
     }
 
     with rasterio.open("tests/data/RGBA.byte.tif") as src:
@@ -1123,11 +1123,11 @@ def test_reproject_resampling_alpha(method):
         src_transform=src.transform,
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
-        dst_crs={"init": "epsg:3857"},
+        dst_crs="EPSG:3857",
         resampling=method,
     )
 
-    assert np.count_nonzero(out) == expected[method]
+    assert np.count_nonzero(out) in expected[method]
 
 
 @pytest.mark.skipif(
@@ -1139,7 +1139,7 @@ def test_reproject_not_yet_supported_resampling(method):
     with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {"init": "epsg:32619"}
+    dst_crs = "EPSG:32619"
     out = np.empty(src.shape, dtype=np.uint8)
     with pytest.raises(GDALVersionError):
         reproject(
@@ -1158,7 +1158,7 @@ def test_reproject_unsupported_resampling():
     with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {"init": "epsg:32619"}
+    dst_crs = "EPSG:32619"
     out = np.empty(src.shape, dtype=np.uint8)
     with pytest.raises(ValueError):
         reproject(
@@ -1177,7 +1177,7 @@ def test_reproject_unsupported_resampling_guass():
     with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {"init": "epsg:32619"}
+    dst_crs = "EPSG:32619"
     out = np.empty(src.shape, dtype=np.uint8)
     with pytest.raises(ValueError):
         reproject(
@@ -1199,9 +1199,9 @@ def test_resample_default_invert_proj(method):
 
     with rasterio.open("tests/data/world.rgb.tif") as src:
         source = src.read(1)
-        profile = src.profile.copy()
+        profile = src.profile
 
-    dst_crs = {"init": "epsg:32619"}
+    dst_crs = "EPSG:32619"
 
     # Calculate the ideal dimensions and transformation in the new crs
     dst_affine, dst_width, dst_height = calculate_default_transform(
@@ -1213,16 +1213,23 @@ def test_resample_default_invert_proj(method):
 
     out = np.empty(shape=(dst_height, dst_width), dtype=np.uint8)
 
-    out = np.empty(src.shape, dtype=np.uint8)
-    reproject(
-        source,
-        out,
-        src_transform=src.transform,
-        src_crs=src.crs,
-        dst_transform=dst_affine,
-        dst_crs=dst_crs,
-        resampling=method,
-    )
+    # GDAL 1.11 needs to have this config option set on to match the
+    # default results in later versions.
+    if gdal_version.major == 1:
+        options = dict(CHECK_WITH_INVERT_PROJ=True)
+    else:
+        options = {}
+
+    with rasterio.Env(**options):
+        reproject(
+            source,
+            out,
+            src_transform=src.transform,
+            src_crs=src.crs,
+            dst_transform=dst_affine,
+            dst_crs=dst_crs,
+            resampling=method,
+        )
 
     assert out.mean() > 0
 
@@ -1233,7 +1240,7 @@ def test_target_aligned_pixels():
         source = src.read(1)
         profile = src.profile.copy()
 
-    dst_crs = {"init": "epsg:3857"}
+    dst_crs = "EPSG:3857"
 
     with rasterio.Env(CHECK_WITH_INVERT_PROJ=False):
         # Calculate the ideal dimensions and transformation in the new crs
@@ -1289,7 +1296,7 @@ def test_resample_no_invert_proj(method):
             source = src.read(1)
             profile = src.profile.copy()
 
-        dst_crs = {"init": "epsg:32619"}
+        dst_crs = "EPSG:32619"
 
         # Calculate the ideal dimensions and transformation in the new crs
         dst_affine, dst_width, dst_height = calculate_default_transform(
@@ -1414,7 +1421,7 @@ def test_reproject_gcps(rgb_byte_profile):
     reproject(
         source,
         out,
-        src_crs="epsg:32618",
+        src_crs="EPSG:32618",
         gcps=src_gcps,
         dst_transform=rgb_byte_profile["transform"],
         dst_crs=rgb_byte_profile["crs"],
@@ -1445,7 +1452,7 @@ def test_issue1056():
     """Warp sucessfully from RGB's upper bands to an array"""
     with rasterio.open("tests/data/RGB.byte.tif") as src:
 
-        dst_crs = {"init": "EPSG:3857"}
+        dst_crs = "EPSG:3857"
         out = np.zeros(src.shape, dtype=np.uint8)
         reproject(
             rasterio.band(src, 2),
@@ -1463,7 +1470,7 @@ def test_reproject_dst_nodata():
     with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {"init": "epsg:3857"}
+    dst_crs = "EPSG:3857"
     out = np.empty(src.shape, dtype=np.float32)
     reproject(
         source,
@@ -1485,7 +1492,7 @@ def test_reproject_dst_nodata():
 def test_issue1401():
     """The warp_mem_limit keyword argument is in effect"""
     with rasterio.open("tests/data/RGB.byte.tif") as src:
-        dst_crs = {"init": "epsg:3857"}
+        dst_crs = "EPSG:3857"
         out = np.zeros(src.shape, dtype=np.uint8)
         reproject(
             rasterio.band(src, 2),
@@ -1514,7 +1521,7 @@ def test_reproject_dst_alpha(path_rgb_msk_byte_tif):
             src_transform=src.transform,
             src_crs=src.crs,
             dst_transform=DST_TRANSFORM,
-            dst_crs={"init": "epsg:3857"},
+            dst_crs="EPSG:3857",
             dst_alpha=4,
         )
 
@@ -1532,7 +1539,7 @@ def test_issue1350():
     """Warp bands other than 1 or All"""
 
     with rasterio.open("tests/data/RGB.byte.tif") as src:
-        dst_crs = {"init": "epsg:3857"}
+        dst_crs = "EPSG:3857"
 
         reprojected = []
 


=====================================
tests/test_warpedvrt.py
=====================================
@@ -549,3 +549,19 @@ def dsrec(capfd):
         records = captured.err.strip("\n").split("\n")[1:]
         return records
     return func
+
+
+def test_warped_vrt_resizing():
+    """Confirm fix of #1921"""
+    with rasterio.open("tests/data/RGB.byte.tif") as rgb:
+        with WarpedVRT(rgb, height=10, width=10) as vrt:
+            assert vrt.height == 10
+            assert vrt.width == 10
+
+
+def test_warped_vrt_resizing_repro():
+    """Confirm fix of #1921"""
+    with rasterio.open("tests/data/RGB.byte.tif") as rgb:
+        with WarpedVRT(rgb, crs="EPSG:3857", height=10, width=10) as vrt:
+            assert vrt.height == 10
+            assert vrt.width == 10



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/commit/8d980ac17cd8e697d0d775b1b8c99e201e69c1d8
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/20200603/b63cb7f1/attachment-0001.html>


More information about the Pkg-grass-devel mailing list