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

Bas Couwenberg gitlab at salsa.debian.org
Mon Sep 17 17:33:27 BST 2018


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


Commits:
afa80682 by Bas Couwenberg at 2018-09-17T16:00:25Z
New upstream version 1.0.4
- - - - -


24 changed files:

- CHANGES.txt
- rasterio/__init__.py
- rasterio/_crs.pyx
- rasterio/_io.pxd
- rasterio/_io.pyx
- rasterio/_warp.pyx
- rasterio/coords.py
- rasterio/enums.py
- rasterio/env.py
- rasterio/io.py
- rasterio/rio/env.py
- rasterio/rio/main.py
- rasterio/rio/overview.py
- + rasterio/session.py
- rasterio/vrt.py
- rasterio/windows.py
- + tests/data/issue1446.geojson
- tests/test_coords.py
- tests/test_env.py
- tests/test_overviews.py
- + tests/test_session.py
- tests/test_warp.py
- tests/test_windows.py
- tests/test_write.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,34 @@
 Changes
 =======
 
+1.0.4 (2018-09-17)
+------------------
+
+Bug fixes:
+
+- Boundless reads of datasets without a coordinate reference system have been
+  fixed (#1448).
+- A y-directional error in disjoint_bounds (#1459) has been fixed.
+- Prevent geometries from being thrown near projection singularities (#1446).
+- Missing --aws-no-sign-requests and --aws-requester-pays options added to
+  the main rio command (#1460).
+- Add missing bilinear, cubic spline, lanczos resampling modes for overviews
+  (#1457).
+- Raise ValueError if get_writer_for_driver() is called without a driver
+  name.
+- Windows are now frozen so that they are hashable (#1452).
+
+Refactoring:
+
+- Use of InMemoryRaster eliminates redundant code in the _warp module (#1427,
+  #816).
+- Generalize sessions to support cloud providers other than AWS (#1429).
+
+1.0.3.post1 (2018-09-07)
+------------------------
+
+This version corrects errors made in building binary wheels for 1.0.3. There
+are no bug fixes or new features in this version.
 
 1.0.3 (2018-08-01)
 ------------------


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


=====================================
rasterio/_crs.pyx
=====================================
@@ -65,6 +65,12 @@ class _CRS(UserDict):
         cdef int retval
 
         try:
+            if (
+                isinstance(other, self.__class__) and
+                self.data == other.data
+            ):
+                return True
+
             if not self or not other:
                 return not self and not other
 


=====================================
rasterio/_io.pxd
=====================================
@@ -29,7 +29,7 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
 cdef class InMemoryRaster:
     cdef GDALDatasetH _hds
     cdef double gdal_transform[6]
-    cdef int band_ids[1]
+    cdef int* band_ids
     cdef np.ndarray _image
     cdef object crs
     cdef object transform  # this is an Affine object.


=====================================
rasterio/_io.pyx
=====================================
@@ -1540,7 +1540,10 @@ cdef class DatasetWriterBase(DatasetReaderBase):
             # (no corresponding member in the warp enum).
             resampling_map = {
                 0: 'NEAREST',
+                1: 'BILINEAR',
                 2: 'CUBIC',
+                3: 'CUBICSPLINE',
+                4: 'LANCZOS',
                 5: 'AVERAGE',
                 6: 'MODE',
                 7: 'GAUSS'}
@@ -1619,7 +1622,6 @@ cdef class InMemoryRaster:
     This class is only intended for internal use within rasterio to support
     IO with GDAL.  Other memory based operations should use numpy arrays.
     """
-
     def __cinit__(self, image=None, dtype='uint8', count=1, width=None,
                   height=None, transform=None, gcps=None, crs=None):
         """
@@ -1634,9 +1636,6 @@ cdef class InMemoryRaster:
         (see rasterio.dtypes.dtype_rev)
         :param transform: Affine transform object
         """
-
-        self._image = image
-
         cdef int i = 0  # avoids Cython warning in for loop below
         cdef const char *srcwkt = NULL
         cdef OGRSpatialReferenceH osr = NULL
@@ -1644,9 +1643,9 @@ cdef class InMemoryRaster:
         cdef GDAL_GCP *gcplist = NULL
 
         if image is not None:
-            if len(image.shape) == 3:
+            if image.ndim == 3:
                 count, height, width = image.shape
-            elif len(image.shape) == 2:
+            elif image.ndim == 2:
                 count = 1
                 height, width = image.shape
             dtype = image.dtype.name
@@ -1657,9 +1656,18 @@ cdef class InMemoryRaster:
         if width is None or width == 0:
             raise ValueError("width must be > 0")
 
-        self.band_ids[0] = 1
+        self.band_ids = <int *>CPLMalloc(count*sizeof(int))
+        for i in range(1, count + 1):
+            self.band_ids[i-1] = i
+
+        try:
+            memdriver = exc_wrap_pointer(GDALGetDriverByName("MEM"))
+        except Exception:
+            raise DriverRegistrationError(
+                "'MEM' driver not found. Check that this call is contained "
+                "in a `with rasterio.Env()` or `with rasterio.open()` "
+                "block.")
 
-        memdriver = exc_wrap_pointer(GDALGetDriverByName("MEM"))
         datasetname = str(uuid.uuid4()).encode('utf-8')
         self._hds = exc_wrap_pointer(
             GDALCreate(memdriver, <const char *>datasetname, width, height,
@@ -1670,39 +1678,42 @@ cdef class InMemoryRaster:
             gdal_transform = transform.to_gdal()
             for i in range(6):
                 self.gdal_transform[i] = gdal_transform[i]
-            err = GDALSetGeoTransform(self._hds, self.gdal_transform)
-            if err:
-                raise ValueError("transform not set: %s" % transform)
-
+            exc_wrap_int(GDALSetGeoTransform(self._hds, self.gdal_transform))
             if crs:
                 osr = _osr_from_crs(crs)
-                OSRExportToWkt(osr, <char**>&srcwkt)
-                GDALSetProjection(self._hds, srcwkt)
-                log.debug("Set CRS on temp source dataset: %s", srcwkt)
-                CPLFree(<void *>srcwkt)
-                _safe_osr_release(osr)
+                try:
+                    OSRExportToWkt(osr, &srcwkt)
+                    exc_wrap_int(GDALSetProjection(self._hds, srcwkt))
+                    log.debug("Set CRS on temp dataset: %s", srcwkt)
+                finally:
+                    CPLFree(srcwkt)
+                    _safe_osr_release(osr)
 
         elif gcps and crs:
-            gcplist = <GDAL_GCP *>CPLMalloc(len(gcps) * sizeof(GDAL_GCP))
-            for i, obj in enumerate(gcps):
-                ident = str(i).encode('utf-8')
-                info = "".encode('utf-8')
-                gcplist[i].pszId = ident
-                gcplist[i].pszInfo = info
-                gcplist[i].dfGCPPixel = obj.col
-                gcplist[i].dfGCPLine = obj.row
-                gcplist[i].dfGCPX = obj.x
-                gcplist[i].dfGCPY = obj.y
-                gcplist[i].dfGCPZ = obj.z or 0.0
+            try:
+                gcplist = <GDAL_GCP *>CPLMalloc(len(gcps) * sizeof(GDAL_GCP))
+                for i, obj in enumerate(gcps):
+                    ident = str(i).encode('utf-8')
+                    info = "".encode('utf-8')
+                    gcplist[i].pszId = ident
+                    gcplist[i].pszInfo = info
+                    gcplist[i].dfGCPPixel = obj.col
+                    gcplist[i].dfGCPLine = obj.row
+                    gcplist[i].dfGCPX = obj.x
+                    gcplist[i].dfGCPY = obj.y
+                    gcplist[i].dfGCPZ = obj.z or 0.0
 
-            osr = _osr_from_crs(crs)
-            OSRExportToWkt(osr, <char**>&srcwkt)
-            GDALSetGCPs(self._hds, len(gcps), gcplist, srcwkt)
-            CPLFree(gcplist)
-            CPLFree(<void *>srcwkt)
+                osr = _osr_from_crs(crs)
+                OSRExportToWkt(osr, &srcwkt)
+                exc_wrap_int(GDALSetGCPs(self._hds, len(gcps), gcplist, srcwkt))
+            finally:
+                CPLFree(gcplist)
+                CPLFree(srcwkt)
+                _safe_osr_release(osr)
 
-        if self._image is not None:
-            self.write(self._image)
+        self._image = None
+        if image is not None:
+            self.write(image)
 
     def __enter__(self):
         return self
@@ -1710,6 +1721,9 @@ cdef class InMemoryRaster:
     def __exit__(self, *args, **kwargs):
         self.close()
 
+    def __dealloc__(self):
+        CPLFree(self.band_ids)
+
     cdef GDALDatasetH handle(self) except NULL:
         """Return the object's GDAL dataset handle"""
         return self._hds
@@ -1735,11 +1749,22 @@ cdef class InMemoryRaster:
             self._hds = NULL
 
     def read(self):
-        io_auto(self._image, self.band(1), False)
+        if self._image is None:
+            raise IOError("You need to write data before you can read the data.")
+
+        if self._image.ndim == 2:
+            exc_wrap_int(io_auto(self._image, self.band(1), False))
+        else:
+            exc_wrap_int(io_auto(self._image, self._hds, False))
         return self._image
 
-    def write(self, image):
-        io_auto(image, self.band(1), True)
+    def write(self, np.ndarray image):
+        self._image = image
+        if image.ndim == 2:
+            exc_wrap_int(io_auto(self._image, self.band(1), True))
+        else:
+            exc_wrap_int(io_auto(self._image, self._hds, True))
+
 
 
 cdef class BufferedDatasetWriterBase(DatasetWriterBase):


=====================================
rasterio/_warp.pyx
=====================================
@@ -17,6 +17,7 @@ from rasterio._err import (
 from rasterio import dtypes
 from rasterio.control import GroundControlPoint
 from rasterio.enums import Resampling, MaskFlags, ColorInterp
+from rasterio.env import GDALVersion
 from rasterio.crs import CRS
 from rasterio.errors import (
     DriverRegistrationError, CRSError, RasterioIOError,
@@ -67,11 +68,15 @@ def _transform_geom(
         _safe_osr_release(dst)
         raise
 
-    # Transform options.
-    valb = str(antimeridian_offset).encode('utf-8')
-    options = CSLSetNameValue(options, "DATELINEOFFSET", <const char *>valb)
-    if antimeridian_cutting:
-        options = CSLSetNameValue(options, "WRAPDATELINE", "YES")
+    if GDALVersion().runtime() < GDALVersion.parse('2.2'):
+        valb = str(antimeridian_offset).encode('utf-8')
+        options = CSLSetNameValue(options, "DATELINEOFFSET", <const char *>valb)
+        if antimeridian_cutting:
+            options = CSLSetNameValue(options, "WRAPDATELINE", "YES")
+    else:
+        # GDAL cuts on the antimeridian by default and using different
+        # logic in versions >= 2.2.
+        pass
 
     try:
         factory = new OGRGeometryFactory()
@@ -286,48 +291,17 @@ def _reproject(
     out: None
         Output is written to destination.
     """
-    cdef int retval
-    cdef int rows
-    cdef int cols
     cdef int src_count
-    cdef GDALDriverH driver = NULL
     cdef GDALDatasetH src_dataset = NULL
     cdef GDALDatasetH dst_dataset = NULL
-    cdef GDALAccess GA
-    cdef double gt[6]
-    cdef char *srcwkt = NULL
-    cdef char *dstwkt= NULL
-    cdef OGRSpatialReferenceH src_osr = NULL
-    cdef OGRSpatialReferenceH dst_osr = NULL
     cdef char **warp_extras = NULL
     cdef const char* pszWarpThread = NULL
     cdef int i
     cdef double tolerance = 0.125
-    cdef GDAL_GCP *gcplist = NULL
     cdef void *hTransformArg = NULL
     cdef GDALTransformerFunc pfnTransformer = NULL
     cdef GDALWarpOptions *psWOptions = NULL
 
-    # If working with identity transform, assume it is crs-less data
-    # and that translating the matrix very slightly will avoid #674
-    eps = 1e-100
-
-    if src_transform:
-
-        src_transform = guard_transform(src_transform)
-        # if src_transform is like `identity` with positive or negative `e`,
-        # translate matrix very slightly to avoid #674 and #1272.
-        if src_transform.almost_equals(identity) or src_transform.almost_equals(Affine(1, 0, 0, 0, -1, 0)):
-            src_transform = src_transform.translation(eps, eps)
-        src_transform = src_transform.to_gdal()
-
-    if dst_transform:
-
-        dst_transform = guard_transform(dst_transform)
-        if dst_transform.almost_equals(identity) or dst_transform.almost_equals(Affine(1, 0, 0, 0, -1, 0)):
-            dst_transform = dst_transform.translation(eps, eps)
-        dst_transform = dst_transform.to_gdal()
-
     # Validate nodata values immediately.
     if src_nodata is not None:
         if not in_dtype_range(src_nodata, source.dtype):
@@ -338,91 +312,43 @@ def _reproject(
         if not in_dtype_range(dst_nodata, destination.dtype):
             raise ValueError("dst_nodata must be in valid range for "
                              "destination dtype")
+        
+    def format_transform(in_transform):
+        if not in_transform:
+            return in_transform
+        in_transform = guard_transform(in_transform)
+        # If working with identity transform, assume it is crs-less data
+        # and that translating the matrix very slightly will avoid #674 and #1272
+        eps = 1e-100
+        if in_transform.almost_equals(identity) or in_transform.almost_equals(Affine(1, 0, 0, 0, -1, 0)):
+            in_transform = in_transform.translation(eps, eps)
+        return in_transform
 
     # If the source is an ndarray, we copy to a MEM dataset.
     # 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
         # Convert 2D single-band arrays to 3D multi-band.
         if len(source.shape) == 2:
             source = source.reshape(1, *source.shape)
         src_count = source.shape[0]
         src_bidx = range(1, src_count + 1)
-        rows = source.shape[1]
-        cols = source.shape[2]
-        dtype = np.dtype(source.dtype).name
-
-        if src_nodata is None and hasattr(source, 'fill_value'):
-            # source is a masked array
-            src_nodata = source.fill_value
-
-        try:
-            driver = exc_wrap_pointer(GDALGetDriverByName("MEM"))
-        except:
-            raise DriverRegistrationError(
-                "'MEM' driver not found. Check that this call is contained "
-                "in a `with rasterio.Env()` or `with rasterio.open()` "
-                "block.")
-
-        datasetname = str(uuid.uuid4()).encode('utf-8')
-        src_dataset = exc_wrap_pointer(
-            GDALCreate(driver, <const char *>datasetname, cols, rows,
-                       src_count, dtypes.dtype_rev[dtype], NULL))
-
-        GDALSetDescription(
-            src_dataset, "Temporary source dataset for _reproject()")
-
-        log.debug("Created temp source dataset")
-
-        try:
-            src_osr = _osr_from_crs(src_crs)
-            OSRExportToWkt(src_osr, &srcwkt)
-
-            if src_transform:
-                for i in range(6):
-                    gt[i] = src_transform[i]
-
-                exc_wrap_int(GDALSetGeoTransform(src_dataset, gt))
-
-                exc_wrap_int(GDALSetProjection(src_dataset, srcwkt))
-
-                log.debug("Set CRS on temp source dataset: %s", srcwkt)
-
-            elif gcps:
-                gcplist = <GDAL_GCP *>CPLMalloc(len(gcps) * sizeof(GDAL_GCP))
-                try:
-                    for i, obj in enumerate(gcps):
-                        ident = str(i).encode('utf-8')
-                        info = "".encode('utf-8')
-                        gcplist[i].pszId = ident
-                        gcplist[i].pszInfo = info
-                        gcplist[i].dfGCPPixel = obj.col
-                        gcplist[i].dfGCPLine = obj.row
-                        gcplist[i].dfGCPX = obj.x
-                        gcplist[i].dfGCPY = obj.y
-                        gcplist[i].dfGCPZ = obj.z or 0.0
-
-                    exc_wrap_int(GDALSetGCPs(src_dataset, len(gcps), gcplist, srcwkt))
-                finally:
-                    CPLFree(gcplist)
-
-        finally:
-            CPLFree(srcwkt)
-            _safe_osr_release(src_osr)
-
-        # Copy arrays to the dataset.
-        exc_wrap_int(io_auto(source, src_dataset, 1))
-
-        log.debug("Wrote array to temp source dataset")
-
+        src_dataset = InMemoryRaster(image=source,
+                                     transform=format_transform(src_transform),
+                                     gcps=gcps,
+                                     crs=src_crs).handle()
     # If the source is a rasterio MultiBand, no copy necessary.
-    # A MultiBand is a tuple: (dataset, bidx, dtype, shape(2d)).
+    # A MultiBand is a tuple: (dataset, bidx, dtype, shape(2d))
     elif isinstance(source, tuple):
         rdr, src_bidx, dtype, shape = source
         if isinstance(src_bidx, int):
             src_bidx = [src_bidx]
         src_count = len(src_bidx)
-        rows, cols = shape
         src_dataset = (<DatasetReaderBase?>rdr).handle()
         if src_nodata is None:
             src_nodata = rdr.nodata
@@ -431,6 +357,8 @@ def _reproject(
 
     # Next, do the same for the destination raster.
     if dtypes.is_ndarray(destination):
+        if not dst_crs:
+            raise CRSError("Missing dst_crs.")
         if len(destination.shape) == 2:
             destination = destination.reshape(1, *destination.shape)
 
@@ -443,21 +371,9 @@ def _reproject(
                 raise ValueError("Invalid destination shape")
             dst_bidx = src_bidx
 
-        try:
-            driver = exc_wrap_pointer(GDALGetDriverByName("MEM"))
-        except:
-            raise DriverRegistrationError(
-                "'MEM' driver not found. Check that this call is contained "
-                "in a `with rasterio.Env()` or `with rasterio.open()` "
-                "block.")
-
-        count, rows, cols = destination.shape
-
-        datasetname = str(uuid.uuid4()).encode('utf-8')
-        dst_dataset = exc_wrap_pointer(
-            GDALCreate(driver, <const char *>datasetname, cols, rows, count,
-                dtypes.dtype_rev[np.dtype(destination.dtype).name], NULL))
-
+        dst_dataset = InMemoryRaster(image=destination,
+                                     transform=format_transform(dst_transform),
+                                     crs=dst_crs).handle()
         if dst_alpha:
             for i in range(destination.shape[0]):
                 try:
@@ -472,26 +388,6 @@ def _reproject(
 
         log.debug("Created temp destination dataset.")
 
-        for i in range(6):
-            gt[i] = dst_transform[i]
-
-        exc_wrap_int(GDALSetGeoTransform(dst_dataset, gt))
-
-        try:
-            dst_osr = _osr_from_crs(dst_crs)
-            OSRExportToWkt(dst_osr, &dstwkt)
-
-            log.debug("CRS for temp destination dataset: %s.", dstwkt)
-
-            exc_wrap_int(GDALSetProjection(dst_dataset, dstwkt))
-        finally:
-            CPLFree(dstwkt)
-            _safe_osr_release(dst_osr)
-
-        exc_wrap_int(io_auto(destination, dst_dataset, 1))
-
-        log.debug("Wrote array to temp output dataset")
-
         if dst_nodata is None:
             if hasattr(destination, "fill_value"):
                 # destination is a masked array
@@ -503,7 +399,6 @@ def _reproject(
         udr, dst_bidx, _, _ = destination
         if isinstance(dst_bidx, int):
             dst_bidx = [dst_bidx]
-        udr = destination.ds
         dst_dataset = (<DatasetReaderBase?>udr).handle()
         if dst_nodata is None:
             dst_nodata = udr.nodata
@@ -581,6 +476,8 @@ def _reproject(
     # Now that the transformer and warp options are set up, we init
     # and run the warper.
     cdef GDALWarpOperation oWarper
+    cdef int rows
+    cdef int cols
     try:
         exc_wrap_int(oWarper.Initialize(psWOptions))
         rows, cols = destination.shape[-2:]


=====================================
rasterio/coords.py
=====================================
@@ -52,7 +52,7 @@ def disjoint_bounds(bounds1, bounds2):
 
     if bounds1_north_up:
         return (bounds1[0] > bounds2[2] or bounds2[0] > bounds1[2] or
-                bounds1[1] > bounds2[3] or bounds2[1] > bounds2[3])
+                bounds1[1] > bounds2[3] or bounds2[1] > bounds1[3])
     else:
         return (bounds1[0] > bounds2[2] or bounds2[0] > bounds1[2] or
-                bounds1[3] > bounds2[1] or bounds2[3] > bounds2[1])
+                bounds1[3] > bounds2[1] or bounds2[3] > bounds1[1])


=====================================
rasterio/enums.py
=====================================
@@ -94,4 +94,4 @@ class PhotometricInterp(Enum):
 class MergeAlg(Enum):
     """Available rasterization algorithms"""
     replace = 'REPLACE'
-    add = 'ADD'
\ No newline at end of file
+    add = 'ADD'


=====================================
rasterio/env.py
=====================================
@@ -1,19 +1,21 @@
 """Rasterio's GDAL/AWS environment"""
 
+import attr
 from functools import wraps, total_ordering
 import logging
-import threading
 import re
-import attr
-
+import threading
+import warnings
 
 import rasterio
 from rasterio._env import (
     GDALEnv, del_gdal_config, get_gdal_config, set_gdal_config)
 from rasterio.compat import string_types, getargspec
 from rasterio.dtypes import check_dtype
-from rasterio.errors import EnvError, GDALVersionError
-from rasterio.path import parse_path
+from rasterio.errors import (
+    EnvError, GDALVersionError, RasterioDeprecationWarning)
+from rasterio.path import parse_path, UnparsedPath, ParsedPath
+from rasterio.session import Session, AWSSession
 from rasterio.transform import guard_transform
 
 
@@ -106,7 +108,8 @@ class Env(object):
     def __init__(
             self, session=None, aws_unsigned=False, aws_access_key_id=None,
             aws_secret_access_key=None, aws_session_token=None,
-            region_name=None, profile_name=None, **options):
+            region_name=None, profile_name=None, session_class=AWSSession,
+            **options):
         """Create a new GDAL/AWS environment.
 
         Note: this class is a context manager. GDAL isn't configured
@@ -115,7 +118,7 @@ class Env(object):
         Parameters
         ----------
         session : optional
-            A boto3 session object.
+            A Session object.
         aws_unsigned : bool, optional (default: False)
             If True, requests will be unsigned.
         aws_access_key_id : str, optional
@@ -128,6 +131,8 @@ class Env(object):
             A region name, as per boto3.
         profile_name : str, optional
             A shared credentials profile name, as per boto3.
+        session_class : Session, optional
+            A sub-class of Session.
         **options : optional
             A mapping of GDAL configuration options, e.g.,
             `CPL_DEBUG=True, CHECK_WITH_INVERT_PROJ=False`.
@@ -142,25 +147,60 @@ class Env(object):
         AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY are given. AWS
         credentials are handled exclusively by boto3.
 
+        Examples
+        --------
+
+        >>> with Env(CPL_DEBUG=True, CPL_CURL_VERBOSE=True):
+        ...     with rasterio.open("https://example.com/a.tif") as src:
+        ...         print(src.profile)
+
+        For access to secured cloud resources, a Rasterio Session or a
+        foreign session object may be passed to the constructor.
+
+        >>> import boto3
+        >>> from rasterio.session import AWSSession
+        >>> boto3_session = boto3.Session(...)
+        >>> with Env(AWSSession(boto3_session)):
+        ...     with rasterio.open("s3://mybucket/a.tif") as src:
+        ...         print(src.profile)
+
         """
         if ('AWS_ACCESS_KEY_ID' in options or
                 'AWS_SECRET_ACCESS_KEY' in options):
             raise EnvError(
                 "GDAL's AWS config options can not be directly set. "
                 "AWS credentials are handled exclusively by boto3.")
-        self.aws_unsigned = aws_unsigned
-        self.aws_access_key_id = aws_access_key_id
-        self.aws_secret_access_key = aws_secret_access_key
-        self.aws_session_token = aws_session_token
-        self.region_name = region_name
-        self.profile_name = profile_name
-        self.session = session
+
+        if session:
+            # Passing a session via keyword argument is the canonical
+            # way to configure access to secured cloud resources.
+            if not isinstance(session, Session):
+                warnings.warn(
+                    "Passing a boto3 session is deprecated. Pass a Rasterio "
+                    "AWSSession object instead.",
+                    RasterioDeprecationWarning
+                )
+                session = AWSSession(session=session)
+            self.session = session
+        else:
+            # Before 1.0, Rasterio only supported AWS. We will special
+            # case AWS in 1.0.x. TODO: warn deprecation in 1.1.
+            warnings.warn(
+                "Passing abstract session keyword arguments is deprecated. "
+                "Pass a Rasterio AWSSession object instead.",
+                RasterioDeprecationWarning
+            )
+            self.session = AWSSession(
+                aws_access_key_id=aws_access_key_id,
+                aws_secret_access_key=aws_secret_access_key,
+                aws_session_token=aws_session_token,
+                region_name=region_name,
+                profile_name=profile_name,
+                aws_unsigned=aws_unsigned)
 
         self.options = options.copy()
         self.context_options = {}
 
-        self._creds = None
-
     @classmethod
     def from_defaults(cls, *args, **kwargs):
         """Create an environment with default config options
@@ -193,7 +233,7 @@ class Env(object):
         -------
         bool
         """
-        return bool(self._creds)
+        return hascreds()  # bool(self.session)
 
     def credentialize(self):
         """Get credentials and configure GDAL
@@ -204,54 +244,21 @@ class Env(object):
         Returns
         -------
         None
+
         """
         if hascreds():
             pass
-
         else:
-            import boto3
-            if not self.session and not self.aws_access_key_id and not self.profile_name:
-                self.session = boto3.Session()
-            elif not self.session:
-                self.session = boto3.Session(
-                    aws_access_key_id=self.aws_access_key_id,
-                    aws_secret_access_key=self.aws_secret_access_key,
-                    aws_session_token=self.aws_session_token,
-                    region_name=self.region_name,
-                    profile_name=self.profile_name)
-            else:
-                # use self.session
-                pass
-            self._creds = self.session._session.get_credentials()
-
-            # Pass these credentials to the GDAL environment.
-            cred_opts = {}
-
-            if self.aws_unsigned:
-                cred_opts['AWS_NO_SIGN_REQUEST'] = 'YES'
-            else:
-                if self._creds.access_key:  # pragma: no branch
-                    cred_opts['AWS_ACCESS_KEY_ID'] = self._creds.access_key
-                if self._creds.secret_key:  # pragma: no branch
-                    cred_opts['AWS_SECRET_ACCESS_KEY'] = self._creds.secret_key
-                if self._creds.token:
-                    cred_opts['AWS_SESSION_TOKEN'] = self._creds.token
-                if self.session.region_name:
-                    cred_opts['AWS_REGION'] = self.session.region_name
-
+            cred_opts = self.session.get_credential_options()
             self.options.update(**cred_opts)
             setenv(**cred_opts)
 
-    def can_credentialize_on_enter(self):
-        return bool(self.session or self.aws_access_key_id or self.profile_name)
-
     def drivers(self):
         """Return a mapping of registered drivers."""
         return local._env.drivers()
 
     def __enter__(self):
         log.debug("Entering env context: %r", self)
-        # No parent Rasterio environment exists.
         if local._env is None:
             log.debug("Starting outermost env")
             self._has_parent_env = False
@@ -274,8 +281,7 @@ class Env(object):
             self.context_options = getenv()
             setenv(**self.options)
 
-        if self.can_credentialize_on_enter():
-            self.credentialize()
+        self.credentialize()
 
         log.debug("Entered env context: %r", self)
         return self
@@ -389,17 +395,14 @@ def ensure_env_credentialled(f):
             env_ctor = Env
         else:
             env_ctor = Env.from_defaults
-        with env_ctor() as wrapper_env:
-            if isinstance(args[0], str):
-                path = parse_path(args[0])
-                scheme = getattr(path, 'scheme', None)
-                if scheme == 's3':
-                    wrapper_env.credentialize()
-                    log.debug("Credentialized: {!r}".format(getenv()))
-                else:
-                    pass
-            else:
-                pass
+
+        if isinstance(args[0], str):
+            session = Session.from_path(args[0])
+        else:
+            session = Session.from_path(None)
+
+        with env_ctor(session=session):
+            log.debug("Credentialized: {!r}".format(getenv()))
             return f(*args, **kwds)
 
     return wrapper


=====================================
rasterio/io.py
=====================================
@@ -175,6 +175,8 @@ class ZipMemoryFile(MemoryFile):
 
 def get_writer_for_driver(driver):
     """Return the writer class appropriate for the specified driver."""
+    if not driver:
+        raise ValueError("'driver' is required to write dataset.")
     cls = None
     if driver_can_create(driver):
         cls = DatasetWriter


=====================================
rasterio/rio/env.py
=====================================
@@ -11,7 +11,7 @@ import rasterio
 @click.option('--formats', 'key', flag_value='formats', default=True,
               help="Enumerate the available formats.")
 @click.option('--credentials', 'key', flag_value='credentials', default=False,
-              help="Print AWS credentials.")
+              help="Print credentials.")
 @click.pass_context
 def env(ctx, key):
     """Print information about the Rasterio environment."""
@@ -20,7 +20,4 @@ def env(ctx, key):
             for k, v in sorted(env.drivers().items()):
                 click.echo("{0}: {1}".format(k, v))
         elif key == 'credentials':
-            click.echo(json.dumps({
-                'aws_access_key_id': env._creds.access_key,
-                'aws_secret_access_key': env._creds.secret_key,
-                'aws_session_token': env._creds.token}))
+            click.echo(json.dumps(env.session.credentials))


=====================================
rasterio/rio/main.py
=====================================
@@ -41,6 +41,7 @@ import cligj
 
 from . import options
 import rasterio
+from rasterio.session import AWSSession
 
 
 def configure_logging(verbosity):
@@ -51,28 +52,50 @@ def configure_logging(verbosity):
 def gdal_version_cb(ctx, param, value):
     if not value or ctx.resilient_parsing:
         return
+
     click.echo("{0}".format(rasterio.__gdal_version__), color=ctx.color)
     ctx.exit()
 
 
- at with_plugins(ep for ep in list(iter_entry_points('rasterio.rio_commands')) +
-              list(iter_entry_points('rasterio.rio_plugins')))
+ at with_plugins(
+    ep
+    for ep in list(iter_entry_points("rasterio.rio_commands"))
+    + list(iter_entry_points("rasterio.rio_plugins"))
+)
 @click.group()
 @cligj.verbose_opt
 @cligj.quiet_opt
- at click.option('--aws-profile',
-              help="Selects a profile from your shared AWS credentials file")
- at click.version_option(version=rasterio.__version__, message='%(version)s')
- at click.option('--gdal-version', is_eager=True, is_flag=True,
-              callback=gdal_version_cb)
+ at click.option(
+    "--aws-profile", help="Select a profile from the AWS credentials file"
+)
+ at click.option("--aws-no-sign-requests", is_flag=True, help="Make requests anonymously")
+ at click.option(
+    "--aws-requester-pays", is_flag=True, help="Requester pays data transfer costs"
+)
+ at click.version_option(version=rasterio.__version__, message="%(version)s")
+ at click.option("--gdal-version", is_eager=True, is_flag=True, callback=gdal_version_cb)
 @click.pass_context
-def main_group(ctx, verbose, quiet, aws_profile, gdal_version):
+def main_group(
+    ctx,
+    verbose,
+    quiet,
+    aws_profile,
+    aws_no_sign_requests,
+    aws_requester_pays,
+    gdal_version,
+):
     """Rasterio command line interface.
     """
     verbosity = verbose - quiet
     configure_logging(verbosity)
     ctx.obj = {}
-    ctx.obj['verbosity'] = verbosity
-    ctx.obj['aws_profile'] = aws_profile
-    ctx.obj['env'] = rasterio.Env(CPL_DEBUG=(verbosity > 2),
-                                  profile_name=aws_profile)
+    ctx.obj["verbosity"] = verbosity
+    ctx.obj["aws_profile"] = aws_profile
+    ctx.obj["env"] = rasterio.Env(
+        session=AWSSession(
+            profile_name=aws_profile,
+            aws_unsigned=aws_no_sign_requests,
+            requester_pays=aws_requester_pays,
+        ),
+        CPL_DEBUG=(verbosity > 2)
+    )


=====================================
rasterio/rio/overview.py
=====================================
@@ -38,7 +38,7 @@ def build_handler(ctx, param, value):
               is_flag=True, default=False)
 @click.option('--resampling', help="Resampling algorithm.",
               type=click.Choice(
-                  [it.name for it in Resampling if it.value in [0, 2, 5, 6, 7]]),
+                  [it.name for it in Resampling if it.value in [0, 1, 2, 3, 4, 5, 6, 7]]),
               default='nearest', show_default=True)
 @click.pass_context
 def overview(ctx, input, build, ls, rebuild, resampling):


=====================================
rasterio/session.py
=====================================
@@ -0,0 +1,190 @@
+"""Abstraction for sessions in various clouds."""
+
+
+from rasterio.path import parse_path, UnparsedPath, ParsedPath
+
+
+class Session(object):
+    """Base for classes that configure access to secured resources.
+
+    Attributes
+    ----------
+    credentials : dict
+        Keys and values for session credentials.
+
+    Notes
+    -----
+    This class is not intended to be instantiated.
+
+    """
+
+    def get_credential_options(self):
+        """Get credentials as GDAL configuration options
+
+        Returns
+        -------
+        dict
+
+        """
+        return NotImplementedError
+
+    @staticmethod
+    def from_foreign_session(session, cls=None):
+        """Create a session object matching the foreign `session`.
+
+        Parameters
+        ----------
+        session : obj
+            A foreign session object.
+        cls : Session class, optional
+            The class to return.
+
+        Returns
+        -------
+        Session
+
+        """
+        if not cls:
+            return DummySession()
+        else:
+            return cls(session)
+
+    @staticmethod
+    def from_path(path, *args, **kwargs):
+        """Create a session object suited to the data at `path`.
+
+        Parameters
+        ----------
+        path : str
+            A dataset path or identifier.
+        args : sequence
+            Positional arguments for the foreign session constructor.
+        kwargs : dict
+            Keyword arguments for the foreign session constructor.
+
+        Returns
+        -------
+        Session
+
+        """
+        if not path:
+            return DummySession()
+
+        path = parse_path(path)
+
+        if isinstance(path, UnparsedPath) or path.is_local:
+            return DummySession()
+
+        elif path.scheme == "s3" or "amazonaws.com" in path.path:
+            return AWSSession(*args, **kwargs)
+
+        # This factory can be extended to other cloud providers here.
+        # elif path.scheme == "cumulonimbus":  # for example.
+        #     return CumulonimbusSession(*args, **kwargs)
+
+        else:
+            return DummySession()
+
+
+class DummySession(Session):
+    """A dummy session.
+
+    Attributes
+    ----------
+    credentials : dict
+        The session credentials.
+
+    """
+
+    def __init__(self, *args, **kwargs):
+        self._session = None
+        self.credentials = {}
+
+    def get_credential_options(self):
+        """Get credentials as GDAL configuration options
+
+        Returns
+        -------
+        dict
+
+        """
+        return {}
+
+
+class AWSSession(Session):
+    """Configures access to secured resources stored in AWS S3.
+    """
+
+    def __init__(
+            self, session=None, aws_unsigned=False, aws_access_key_id=None,
+            aws_secret_access_key=None, aws_session_token=None,
+            region_name=None, profile_name=None, requester_pays=False):
+        """Create a new boto3 session
+
+        Parameters
+        ----------
+        session : optional
+            A boto3 session object.
+        aws_unsigned : bool, optional (default: False)
+            If True, requests will be unsigned.
+        aws_access_key_id : str, optional
+            An access key id, as per boto3.
+        aws_secret_access_key : str, optional
+            A secret access key, as per boto3.
+        aws_session_token : str, optional
+            A session token, as per boto3.
+        region_name : str, optional
+            A region name, as per boto3.
+        profile_name : str, optional
+            A shared credentials profile name, as per boto3.
+        requester_pays : bool, optional
+            True if the requester agrees to pay transfer costs (default:
+            False)
+        """
+        import boto3
+
+        if session:
+            self._session = session
+        else:
+            if not aws_access_key_id and not profile_name:
+                self._session = boto3.Session()
+            else:
+                self._session = boto3.Session(
+                    aws_access_key_id=aws_access_key_id,
+                    aws_secret_access_key=aws_secret_access_key,
+                    aws_session_token=aws_session_token,
+                    region_name=region_name,
+                    profile_name=profile_name)
+
+        self.requester_pays = requester_pays
+        self.unsigned = aws_unsigned
+        self._creds = self._session._session.get_credentials()
+
+    @property
+    def credentials(self):
+        """The session credentials as a dict"""
+        creds = {}
+        if self._creds.access_key:  # pragma: no branch
+            creds['aws_access_key_id'] = self._creds.access_key
+        if self._creds.secret_key:  # pragma: no branch
+            creds['aws_secret_access_key'] = self._creds.secret_key
+        if self._creds.token:
+            creds['aws_session_token'] = self._creds.token
+        if self._session.region_name:
+            creds['aws_region'] = self._session.region_name
+        if self.requester_pays:
+            creds['aws_request_payer'] = 'requester'
+        return creds
+
+    def get_credential_options(self):
+        """Get credentials as GDAL configuration options
+
+        Returns
+        -------
+        dict
+
+        """
+        if self.unsigned:
+            return {'AWS_NO_SIGN_REQUEST': 'YES'}
+        else:
+            return {k.upper(): v for k, v in self.credentials.items()}


=====================================
rasterio/vrt.py
=====================================
@@ -81,7 +81,7 @@ def _boundless_vrt_doc(src_dataset, nodata=None, width=None, height=None, transf
     vrtdataset.attrib['rasterYSize'] = str(height)
     vrtdataset.attrib['rasterXSize'] = str(width)
     srs = ET.SubElement(vrtdataset, 'SRS')
-    srs.text = src_dataset.crs.wkt
+    srs.text = src_dataset.crs.wkt if src_dataset.crs else ""
     geotransform = ET.SubElement(vrtdataset, 'GeoTransform')
     geotransform.text = ','.join([str(v) for v in transform.to_gdal()])
 


=====================================
rasterio/windows.py
=====================================
@@ -475,7 +475,8 @@ def validate_length_value(instance, attribute, value):
         raise ValueError("Number of columns or rows must be non-negative")
 
 
- at attr.s(slots=True)
+ at attr.s(slots=True,
+        frozen=True)
 class Window(object):
     """Windows are rectangular subsets of rasters.
 


=====================================
tests/data/issue1446.geojson
=====================================
@@ -0,0 +1,14 @@
+{
+"type": "FeatureCollection",
+"name": "test_lines",
+"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
+"features": [
+{ "type": "Feature", "properties": { "fid": 183521, "uuid": "{2c66f90e-befb-49d5-8083-c5642194be06}" }, "geometry": { "type": "LineString", "coordinates": [ [ -9.146176034327512, 2.172296541854189 ], [ -9.146450423923667, 2.172488614571499 ], [ -9.146649356380882, 2.172845321046502 ], [ -9.146697374560208, 2.172989375584485 ], [ -9.146834569358287, 2.173099131422947 ], [ -9.146944325196749, 2.173380380759008 ], [ -9.146951184936654, 2.17349013659747 ], [ -9.146923745977038, 2.173757666453723 ], [ -9.146717953779921, 2.174196689807574 ], [ -9.146621917421266, 2.174395622264787 ], [ -9.146553320022226, 2.174553396282577 ], [ -9.146436704443861, 2.174704310560464 ], [ -9.146004540829914, 2.175074736515274 ], [ -9.145572377215966, 2.17541772351047 ], [ -9.145270548660195, 2.175644094927299 ] ] } },
+{ "type": "Feature", "properties": { "fid": 183518, "uuid": "{a84f6b24-082c-4e78-a39d-46f2d96582e4}" }, "geometry": { "type": "LineString", "coordinates": [ [ -9.148784450425975, 2.175074736515274 ], [ -9.148949084183668, 2.174889523537869 ], [ -9.149120577681266, 2.174807206659022 ], [ -9.149381247797615, 2.174642572901328 ], [ -9.149614478954348, 2.174457359923922 ], [ -9.149827130891369, 2.174299585906132 ], [ -9.150026063348584, 2.173936019691225 ], [ -9.150190697106277, 2.17349013659747 ], [ -9.150348471124067, 2.172996235324388 ], [ -9.150417068523106, 2.172783583387367 ], [ -9.150458226962529, 2.172399437952748 ], [ -9.15054740358128, 2.171994713298417 ], [ -9.150595421760608, 2.17175462240178 ], [ -9.150588562020706, 2.171617427603702 ], [ -9.150691458119262, 2.171349897747449 ], [ -9.150753195778398, 2.17112352633062 ] ] } },
+{ "type": "Feature", "properties": { "fid": 183519, "uuid": "{be89730e-95de-4744-a18c-406730800b73}" }, "geometry": { "type": "LineString", "coordinates": [ [ -9.147330185566345, 2.175877326084033 ], [ -9.147583995942789, 2.17557549752826 ], [ -9.147782928400003, 2.175355985851335 ], [ -9.147940702417793, 2.175198211833545 ], [ -9.148180793314429, 2.175019858596043 ], [ -9.148324847852413, 2.174834645618637 ], [ -9.148448323170683, 2.174670011860944 ], [ -9.148647255627896, 2.174532817062865 ], [ -9.148784450425975, 2.174340744345556 ], [ -9.148866767304822, 2.174100653448919 ], [ -9.148921645224053, 2.173977178130648 ], [ -9.149072559501938, 2.173750806713819 ], [ -9.14912743742117, 2.173558733996509 ], [ -9.149182315340401, 2.173195167781602 ], [ -9.149168595860594, 2.172982515844581 ], [ -9.149141156900978, 2.172824741826791 ], [ -9.14928521143896, 2.172673827548905 ], [ -9.149394967277424, 2.172612089889769 ], [ -9.149504723115886, 2.17257779119025 ] ] } },
+{ "type": "Feature", "properties": { "fid": 183517, "uuid": "{e02bf433-920a-4edd-a869-8bfabc832149}" }, "geometry": { "type": "LineString", "coordinates": [ [ -9.150869811356765, 2.174711170300367 ], [ -9.151041304854362, 2.174594554722001 ], [ -9.15117849965244, 2.174464219663826 ], [ -9.151267676271191, 2.174347604085459 ], [ -9.151329413930327, 2.174182970327766 ], [ -9.151349993150038, 2.174011476830168 ], [ -9.151281395750999, 2.173689069054684 ], [ -9.151226517831768, 2.173476417117663 ], [ -9.151130481473112, 2.173222606741218 ], [ -9.151048164594268, 2.173009954804196 ], [ -9.151034445114458, 2.17270126650852 ], [ -9.15105502433417, 2.172433736652267 ], [ -9.151164780172632, 2.172269102894574 ], [ -9.151205938612057, 2.172166206796015 ], [ -9.151233377571671, 2.171898676939762 ], [ -9.151247097051479, 2.171672305522933 ], [ -9.151315694450519, 2.171473373065719 ], [ -9.151391151589461, 2.171240141908986 ], [ -9.151446029508692, 2.171075508151293 ] ] } },
+{ "type": "Feature", "properties": { "fid": 183522, "uuid": "{662d526c-0ef9-4aab-b533-7ac0a64ee014}" }, "geometry": { "type": "LineString", "coordinates": [ [ -9.146133160953113, 2.172913918445542 ], [ -9.146194898612247, 2.173037393763812 ], [ -9.146332093410326, 2.173154009342179 ], [ -9.146421270029077, 2.17324318596093 ], [ -9.146441849248788, 2.17340095997872 ], [ -9.146359532369942, 2.173558733996509 ], [ -9.146284075230998, 2.173689069054684 ], [ -9.146105721993496, 2.174073214489303 ], [ -9.146043984334362, 2.174230988507093 ], [ -9.146043984334362, 2.174272146946516 ] ] } },
+{ "type": "Feature", "properties": { "fid": 183523, "uuid": "{d54434ff-2013-4897-bb9c-856468743b13}" }, "geometry": { "type": "LineString", "coordinates": [ [ -9.14416441560069, 2.172234804195054 ], [ -9.144377067537711, 2.172529773010922 ], [ -9.144438805196845, 2.172708126248424 ], [ -9.144589719474732, 2.172955076884965 ], [ -9.144651457133866, 2.173133430122467 ], [ -9.144802371411753, 2.173133430122467 ], [ -9.145008163608871, 2.173119710642659 ], [ -9.145138498667045, 2.17308541194314 ] ] } },
+{ "type": "Feature", "properties": { "fid": 183520, "uuid": "{8bfc07f0-8550-479b-9682-741a3c3b1c75}" }, "geometry": { "type": "LineString", "coordinates": [ [ -9.148954228988597, 2.171761482141684 ], [ -9.148981667948213, 2.172090749657072 ], [ -9.148967948468405, 2.172344560033517 ], [ -9.148871912109751, 2.172625809369577 ], [ -9.148576943293881, 2.172797302867175 ], [ -9.148350571877053, 2.173044253503716 ], [ -9.148336852397245, 2.17317458856189 ], [ -9.148398590056379, 2.173298063880161 ], [ -9.148357431616956, 2.17352443529699 ], [ -9.148206517339069, 2.173572453476317 ], [ -9.147987005662147, 2.174025196309976 ], [ -9.147808652424644, 2.174340744345556 ], [ -9.147630299187142, 2.174615133941712 ], [ -9.147417647250119, 2.174923822237389 ], [ -9.147150117393867, 2.1752942481922 ], [ -9.146868868057807, 2.175589217008068 ] ] } }
+]
+}


=====================================
tests/test_coords.py
=====================================
@@ -1,23 +1,41 @@
-import rasterio
 import numpy as np
 
+import rasterio
+from rasterio.coords import BoundingBox, disjoint_bounds
+
+
 def test_bounds():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         assert src.bounds == (101985.0, 2611485.0, 339315.0, 2826915.0)
 
+
 def test_ul():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         assert src.xy(0, 0, offset='ul') == (101985.0, 2826915.0)
         assert src.xy(1, 0, offset='ul') == (101985.0, 2826614.95821727)
         assert src.xy(src.height, src.width, offset='ul') == (339315.0, 2611485.0)
 
+
 def test_res():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         assert tuple(round(v, 6) for v in src.res) == (300.037927, 300.041783)
 
+
 def test_rotated_bounds():
     with rasterio.open('tests/data/rotated.tif') as src:
         assert src.res == (20.0, 10.0)
         np.testing.assert_almost_equal(
             src.bounds,
             (100.0, 70.0961894323342, 348.20508075688775, 300.0))
+
+
+def test_disjoint_bounds_issue1459():
+    a = BoundingBox(left=478038, bottom=57155, right=703888, top=266344)
+    b = BoundingBox(left=584184, bottom=469629, right=740727, top=626172)
+    assert disjoint_bounds(a, b)
+
+
+def test_disjoint_bounds_issue1459_south_up():
+    a = BoundingBox(left=0.0, bottom=1.0, right=1.0, top=0.0)
+    b = BoundingBox(left=0.0, bottom=2.0, right=1.0, top=1.01)
+    assert disjoint_bounds(a, b)


=====================================
tests/test_env.py
=====================================
@@ -167,10 +167,10 @@ def test_aws_session(gdalenv):
         aws_access_key_id='id', aws_secret_access_key='key',
         aws_session_token='token', region_name='null-island-1')
     with rasterio.env.Env(session=aws_session) as s:
-        assert s._creds.access_key == 'id'
-        assert s._creds.secret_key == 'key'
-        assert s._creds.token == 'token'
-        assert s.session.region_name == 'null-island-1'
+        assert s.session._creds.access_key == 'id'
+        assert s.session._creds.secret_key == 'key'
+        assert s.session._creds.token == 'token'
+        assert s.session._session.region_name == 'null-island-1'
 
 
 def test_aws_session_credentials(gdalenv):


=====================================
tests/test_overviews.py
=====================================
@@ -8,9 +8,9 @@ import pytest
 
 import rasterio
 from rasterio.enums import Resampling
+from rasterio.env import GDALVersion
 
-
-logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+gdal_version = GDALVersion()
 
 
 def test_count_overviews_zero(data):
@@ -40,6 +40,18 @@ def test_build_overviews_two(data):
         assert src.overviews(2) == [2, 4]
         assert src.overviews(3) == [2, 4]
 
+ at pytest.mark.xfail(
+    gdal_version < 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:
+        overview_factors = [2, 4]
+        src.build_overviews(overview_factors, resampling=Resampling.bilinear)
+        assert src.overviews(1) == [2, 4]
+        assert src.overviews(2) == [2, 4]
+        assert src.overviews(3) == [2, 4]
+
 
 def test_build_overviews_average(data):
     inputfile = str(data.join('RGB.byte.tif'))


=====================================
tests/test_session.py
=====================================
@@ -0,0 +1,116 @@
+"""Tests of session module"""
+
+import pytest
+
+from rasterio.session import DummySession, AWSSession, Session
+
+
+def test_dummy_session():
+    """DummySession works"""
+    sesh = DummySession()
+    assert sesh._session is None
+    assert sesh.get_credential_options() == {}
+
+
+def test_aws_session_class():
+    """AWSSession works"""
+    sesh = AWSSession(aws_access_key_id='foo', aws_secret_access_key='bar')
+    assert sesh._session
+    assert sesh.get_credential_options()['AWS_ACCESS_KEY_ID'] == 'foo'
+    assert sesh.get_credential_options()['AWS_SECRET_ACCESS_KEY'] == 'bar'
+
+
+def test_aws_session_class_session():
+    """AWSSession works"""
+    boto3 = pytest.importorskip("boto3")
+    sesh = AWSSession(session=boto3.session.Session(aws_access_key_id='foo', aws_secret_access_key='bar'))
+    assert sesh._session
+    assert sesh.get_credential_options()['AWS_ACCESS_KEY_ID'] == 'foo'
+    assert sesh.get_credential_options()['AWS_SECRET_ACCESS_KEY'] == 'bar'
+
+
+def test_aws_session_class_unsigned():
+    """AWSSession works"""
+    pytest.importorskip("boto3")
+    sesh = AWSSession(aws_unsigned=True)
+    assert sesh._session
+    assert sesh.get_credential_options()['AWS_NO_SIGN_REQUEST'] == 'YES'
+
+
+def test_aws_session_class_profile(tmpdir, monkeypatch):
+    """Confirm that profile_name kwarg works."""
+    pytest.importorskip("boto3")
+    credentials_file = tmpdir.join('credentials')
+    credentials_file.write("[testing]\n"
+                           "aws_access_key_id = foo\n"
+                           "aws_secret_access_key = bar\n"
+                           "aws_session_token = baz")
+    monkeypatch.setenv('AWS_SHARED_CREDENTIALS_FILE', str(credentials_file))
+    monkeypatch.setenv('AWS_SESSION_TOKEN', 'ignore_me')
+    sesh = AWSSession(profile_name='testing')
+    assert sesh._session
+    assert sesh.get_credential_options()['AWS_ACCESS_KEY_ID'] == 'foo'
+    assert sesh.get_credential_options()['AWS_SECRET_ACCESS_KEY'] == 'bar'
+    assert sesh.get_credential_options()['AWS_SESSION_TOKEN'] == 'baz'
+    monkeypatch.undo()
+
+
+def test_session_factory_unparsed():
+    """Get a DummySession for unparsed paths"""
+    sesh = Session.from_path("/vsicurl/lolwut")
+    assert isinstance(sesh, DummySession)
+
+
+def test_session_factory_empty():
+    """Get a DummySession for no path"""
+    sesh = Session.from_path("")
+    assert isinstance(sesh, DummySession)
+
+
+def test_session_factory_local():
+    """Get a DummySession for local paths"""
+    sesh = Session.from_path("file:///lolwut")
+    assert isinstance(sesh, DummySession)
+
+
+def test_session_factory_unknown():
+    """Get a DummySession for unknown paths"""
+    sesh = Session.from_path("https://fancy-cloud.com/lolwut")
+    assert isinstance(sesh, DummySession)
+
+
+def test_session_factory_s3():
+    """Get an AWSSession for s3:// paths"""
+    pytest.importorskip("boto3")
+    sesh = Session.from_path("s3://lol/wut")
+    assert isinstance(sesh, AWSSession)
+
+
+def test_session_factory_s3_kwargs():
+    """Get an AWSSession for s3:// paths with keywords"""
+    pytest.importorskip("boto3")
+    sesh = Session.from_path("s3://lol/wut", aws_access_key_id='foo', aws_secret_access_key='bar')
+    assert isinstance(sesh, AWSSession)
+    assert sesh._session.get_credentials().access_key == 'foo'
+    assert sesh._session.get_credentials().secret_key == 'bar'
+
+
+def test_foreign_session_factory_dummy():
+    sesh = Session.from_foreign_session(None)
+    assert isinstance(sesh, DummySession)
+
+
+def test_foreign_session_factory_s3():
+    boto3 = pytest.importorskip("boto3")
+    aws_session = boto3.Session(aws_access_key_id='foo', aws_secret_access_key='bar')
+    sesh = Session.from_foreign_session(aws_session, cls=AWSSession)
+    assert isinstance(sesh, AWSSession)
+    assert sesh._session.get_credentials().access_key == 'foo'
+    assert sesh._session.get_credentials().secret_key == 'bar'
+
+
+def test_requester_pays():
+    """GDAL is configured with requester pays"""
+    sesh = AWSSession(requester_pays=True)
+    assert sesh._session
+    assert sesh.get_credential_options()['AWS_REQUEST_PAYER'] == 'requester'


=====================================
tests/test_warp.py
=====================================
@@ -1,4 +1,4 @@
-
+import json
 import logging
 import sys
 
@@ -8,14 +8,20 @@ import numpy as np
 
 import rasterio
 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)
 from rasterio.warp import (
-    reproject, transform_geom, transform, transform_bounds,
-    calculate_default_transform, aligned_target, SUPPORTED_RESAMPLING,
-    GDAL2_RESAMPLING)
+    reproject,
+    transform_geom,
+    transform,
+    transform_bounds,
+    calculate_default_transform,
+    aligned_target,
+    SUPPORTED_RESAMPLING,
+    GDAL2_RESAMPLING,
+)
 from rasterio import windows
 
 from .conftest import requires_gdal22
@@ -25,8 +31,7 @@ gdal_version = GDALVersion.runtime()
 logging.basicConfig(level=logging.DEBUG)
 
 
-DST_TRANSFORM = Affine(300.0, 0.0, -8789636.708,
-                       0.0, -300.0, 2943560.235)
+DST_TRANSFORM = Affine(300.0, 0.0, -8789636.708, 0.0, -300.0, 2943560.235)
 
 
 def flatten_coords(coordinates):
@@ -34,21 +39,21 @@ def flatten_coords(coordinates):
     for elem in coordinates:
         if isinstance(elem, (float, int)):
             yield elem
+
         else:
             for x in flatten_coords(elem):
                 yield x
 
 
 reproj_expected = (
-    ({'CHECK_WITH_INVERT_PROJ': False}, 7608),
-    ({'CHECK_WITH_INVERT_PROJ': True}, 2216))
+    ({"CHECK_WITH_INVERT_PROJ": False}, 7608), ({"CHECK_WITH_INVERT_PROJ": True}, 2216)
+)
 
 
 class ReprojectParams(object):
     """Class to assist testing reprojection by encapsulating parameters."""
 
-    def __init__(self, left, bottom, right, top, width, height, src_crs,
-                 dst_crs):
+    def __init__(self, left, bottom, right, top, width, height, src_crs, dst_crs):
         self.width = width
         self.height = height
         src_res = float(right - left) / float(width)
@@ -57,7 +62,8 @@ class ReprojectParams(object):
         self.dst_crs = dst_crs
 
         dt, dw, dh = calculate_default_transform(
-            src_crs, dst_crs, width, height, left, bottom, right, top)
+            src_crs, dst_crs, width, height, left, bottom, right, top
+        )
         self.dst_transform = dt
         self.dst_width = dw
         self.dst_height = dh
@@ -71,8 +77,9 @@ def default_reproject_params():
         top=70,
         width=80,
         height=80,
-        src_crs={'init': 'EPSG:4326'},
-        dst_crs={'init': 'EPSG:2163'})
+        src_crs={"init": "EPSG:4326"},
+        dst_crs={"init": "EPSG:2163"},
+    )
 
 
 def uninvertable_reproject_params():
@@ -83,11 +90,12 @@ def uninvertable_reproject_params():
         top=70,
         width=80,
         height=80,
-        src_crs={'init': 'EPSG:4326'},
-        dst_crs={'init': 'EPSG:26836'})
+        src_crs={"init": "EPSG:4326"},
+        dst_crs={"init": "EPSG:26836"},
+    )
 
 
-WGS84_crs = {'init': 'EPSG:4326'}
+WGS84_crs = {"init": "EPSG:4326"}
 
 
 def test_transform_src_crs_none():
@@ -122,96 +130,106 @@ def test_transform_geom_dst_crs_none():
 
 def test_reproject_src_crs_none():
     with pytest.raises(CRSError):
-        reproject(np.ones((2, 2)), np.zeros((2, 2)),
-                  src_transform=Affine.identity(),
-                  dst_transform=Affine.identity(), dst_crs=WGS84_crs)
+        reproject(
+            np.ones((2, 2)),
+            np.zeros((2, 2)),
+            src_transform=Affine.identity(),
+            dst_transform=Affine.identity(),
+            dst_crs=WGS84_crs,
+        )
 
 
 def test_reproject_dst_crs_none():
     with pytest.raises(CRSError):
-        reproject(np.ones((2, 2)), np.zeros((2, 2)),
-                  src_transform=Affine.identity(),
-                  dst_transform=Affine.identity(), src_crs=WGS84_crs)
+        reproject(
+            np.ones((2, 2)),
+            np.zeros((2, 2)),
+            src_transform=Affine.identity(),
+            dst_transform=Affine.identity(),
+            src_crs=WGS84_crs,
+        )
 
 
 def test_transform():
     """2D and 3D."""
-    WGS84_crs = {'init': 'EPSG:4326'}
+    WGS84_crs = {"init": "EPSG:4326"}
     WGS84_points = ([12.492269], [41.890169], [48.])
-    ECEF_crs = {'init': 'EPSG:4978'}
+    ECEF_crs = {"init": "EPSG:4978"}
     ECEF_points = ([4642610.], [1028584.], [4236562.])
     ECEF_result = transform(WGS84_crs, ECEF_crs, *WGS84_points)
     assert np.allclose(np.array(ECEF_result), np.array(ECEF_points))
 
-    UTM33_crs = {'init': 'EPSG:32633'}
+    UTM33_crs = {"init": "EPSG:32633"}
     UTM33_points = ([291952], [4640623])
     UTM33_result = transform(WGS84_crs, UTM33_crs, *WGS84_points[:2])
     assert np.allclose(np.array(UTM33_result), np.array(UTM33_points))
 
 
 def test_transform_bounds():
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         l, b, r, t = src.bounds
         assert np.allclose(
-            transform_bounds(src.crs, {'init': 'EPSG:4326'}, l, b, r, t),
+            transform_bounds(src.crs, {"init": "EPSG:4326"}, l, b, r, t),
             (
-                -78.95864996545055, 23.564991210854686,
-                -76.57492370013823, 25.550873767433984
-            )
+                -78.95864996545055,
+                23.564991210854686,
+                -76.57492370013823,
+                25.550873767433984,
+            ),
         )
 
 
 def test_transform_bounds_densify():
     # This transform is non-linear along the edges, so densification produces
     # a different result than otherwise
-    src_crs = {'init': 'EPSG:4326'}
-    dst_crs = {'init': 'EPSG:2163'}
+    src_crs = {"init": "EPSG:4326"}
+    dst_crs = {"init": "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))
+        transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=0),
+        (-1684649.41338, -350356.81377, 1684649.41338, 2234551.18559),
+    )
 
     assert np.allclose(
-        transform_bounds(
-            src_crs,
-            dst_crs,
-            -120, 40, -80, 64,
-            densify_pts=100),
-        (-1684649.41338, -555777.79210, 1684649.41338, 2234551.18559))
+        transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=100),
+        (-1684649.41338, -555777.79210, 1684649.41338, 2234551.18559),
+    )
 
 
 def test_transform_bounds_no_change():
     """Make sure that going from and to the same crs causes no change."""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         l, b, r, t = src.bounds
-        assert np.allclose(
-            transform_bounds(src.crs, src.crs, l, b, r, t),
-            src.bounds
-        )
+        assert np.allclose(transform_bounds(src.crs, src.crs, l, b, r, t), src.bounds)
 
 
 def test_transform_bounds_densify_out_of_bounds():
     with pytest.raises(ValueError):
         transform_bounds(
-            {'init': 'EPSG:4326'},
-            {'init': 'EPSG:32610'},
-            -120, 40, -80, 64,
-            densify_pts=-10
+            {"init": "EPSG:4326"},
+            {"init": "EPSG:32610"},
+            -120,
+            40,
+            -80,
+            64,
+            densify_pts=-10,
         )
 
 
 def test_calculate_default_transform():
     target_transform = Affine(
-        0.0028535715391804096, 0.0, -78.95864996545055,
-        0.0, -0.0028535715391804096, 25.550873767433984)
+        0.0028535715391804096,
+        0.0,
+        -78.95864996545055,
+        0.0,
+        -0.0028535715391804096,
+        25.550873767433984,
+    )
 
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
-        wgs84_crs = {'init': 'EPSG:4326'}
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
+        wgs84_crs = {"init": "EPSG:4326"}
         dst_transform, width, height = calculate_default_transform(
-            src.crs, wgs84_crs, src.width, src.height, *src.bounds)
+            src.crs, wgs84_crs, src.width, src.height, *src.bounds
+        )
 
         assert dst_transform.almost_equals(target_transform)
         assert width == 835
@@ -219,15 +237,23 @@ def test_calculate_default_transform():
 
 
 def test_calculate_default_transform_single_resolution():
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         target_resolution = 0.1
         target_transform = Affine(
-            target_resolution, 0.0, -78.95864996545055,
-            0.0, -target_resolution, 25.550873767433984
+            target_resolution,
+            0.0,
+            -78.95864996545055,
+            0.0,
+            -target_resolution,
+            25.550873767433984,
         )
         dst_transform, width, height = calculate_default_transform(
-            src.crs, {'init': 'EPSG:4326'}, src.width, src.height,
-            *src.bounds, resolution=target_resolution
+            src.crs,
+            {"init": "EPSG:4326"},
+            src.width,
+            src.height,
+            *src.bounds,
+            resolution=target_resolution
         )
 
         assert dst_transform.almost_equals(target_transform)
@@ -236,16 +262,24 @@ def test_calculate_default_transform_single_resolution():
 
 
 def test_calculate_default_transform_multiple_resolutions():
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         target_resolution = (0.2, 0.1)
         target_transform = Affine(
-            target_resolution[0], 0.0, -78.95864996545055,
-            0.0, -target_resolution[1], 25.550873767433984
+            target_resolution[0],
+            0.0,
+            -78.95864996545055,
+            0.0,
+            -target_resolution[1],
+            25.550873767433984,
         )
 
         dst_transform, width, height = calculate_default_transform(
-            src.crs, {'init': 'EPSG:4326'}, src.width, src.height,
-            *src.bounds, resolution=target_resolution
+            src.crs,
+            {"init": "EPSG:4326"},
+            src.width,
+            src.height,
+            *src.bounds,
+            resolution=target_resolution
         )
 
         assert dst_transform.almost_equals(target_transform)
@@ -254,16 +288,25 @@ def test_calculate_default_transform_multiple_resolutions():
 
 
 def test_calculate_default_transform_dimensions():
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         dst_width, dst_height = (113, 103)
         target_transform = Affine(
-            0.02108612597535966, 0.0, -78.95864996545055,
-            0.0, -0.0192823863230055, 25.550873767433984
+            0.02108612597535966,
+            0.0,
+            -78.95864996545055,
+            0.0,
+            -0.0192823863230055,
+            25.550873767433984,
         )
 
         dst_transform, width, height = calculate_default_transform(
-            src.crs, {'init': 'EPSG:4326'}, src.width, src.height,
-            *src.bounds, dst_width=dst_width, dst_height=dst_height
+            src.crs,
+            {"init": "EPSG:4326"},
+            src.width,
+            src.height,
+            *src.bounds,
+            dst_width=dst_width,
+            dst_height=dst_height
         )
 
         assert dst_transform.almost_equals(target_transform)
@@ -272,11 +315,11 @@ def test_calculate_default_transform_dimensions():
 
 
 def test_reproject_ndarray():
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
     dst_crs = dict(
-        proj='merc',
+        proj="merc",
         a=6378137,
         b=6378137,
         lat_ts=0.0,
@@ -284,10 +327,11 @@ def test_reproject_ndarray():
         x_0=0.0,
         y_0=0,
         k=1.0,
-        units='m',
-        nadgrids='@null',
+        units="m",
+        nadgrids="@null",
         wktext=True,
-        no_defs=True)
+        no_defs=True,
+    )
     out = np.empty(src.shape, dtype=np.uint8)
     reproject(
         source,
@@ -296,13 +340,14 @@ def test_reproject_ndarray():
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
         dst_crs=dst_crs,
-        resampling=Resampling.nearest)
+        resampling=Resampling.nearest,
+    )
     assert (out > 0).sum() == 438113
 
 
 def test_reproject_view():
     """Source views are reprojected properly"""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
     window = windows.Window(100, 100, 500, 500)
@@ -314,7 +359,7 @@ def test_reproject_view():
     assert reduced_array.base is source
 
     dst_crs = dict(
-        proj='merc',
+        proj="merc",
         a=6378137,
         b=6378137,
         lat_ts=0.0,
@@ -322,10 +367,11 @@ def test_reproject_view():
         x_0=0.0,
         y_0=0,
         k=1.0,
-        units='m',
-        nadgrids='@null',
+        units="m",
+        nadgrids="@null",
         wktext=True,
-        no_defs=True)
+        no_defs=True,
+    )
 
     out = np.empty(src.shape, dtype=np.uint8)
 
@@ -336,16 +382,17 @@ def test_reproject_view():
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
         dst_crs=dst_crs,
-        resampling=Resampling.nearest)
+        resampling=Resampling.nearest,
+    )
 
     assert (out > 0).sum() == 299199
 
 
 def test_reproject_epsg():
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {'init': 'EPSG:3857'}
+    dst_crs = {"init": "EPSG:3857"}
     out = np.empty(src.shape, dtype=np.uint8)
     reproject(
         source,
@@ -354,7 +401,8 @@ def test_reproject_epsg():
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
         dst_crs=dst_crs,
-        resampling=Resampling.nearest)
+        resampling=Resampling.nearest,
+    )
     assert (out > 0).sum() == 438113
 
 
@@ -363,10 +411,10 @@ def test_reproject_out_of_bounds():
 
     Should return blank image.
     """
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {'init': 'EPSG:32619'}
+    dst_crs = {"init": "EPSG:32619"}
     out = np.zeros(src.shape, dtype=np.uint8)
     reproject(
         source,
@@ -375,7 +423,8 @@ def test_reproject_out_of_bounds():
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
         dst_crs=dst_crs,
-        resampling=Resampling.nearest)
+        resampling=Resampling.nearest,
+    )
     assert not out.any()
 
 
@@ -386,8 +435,7 @@ def test_reproject_nodata(options, expected):
     with rasterio.Env(**options):
         params = uninvertable_reproject_params()
         source = np.ones((params.width, params.height), dtype=np.uint8)
-        out = np.zeros((params.dst_width, params.dst_height),
-                       dtype=source.dtype)
+        out = np.zeros((params.dst_width, params.dst_height), dtype=source.dtype)
         out.fill(120)  # Fill with arbitrary value
 
         reproject(
@@ -398,12 +446,13 @@ def test_reproject_nodata(options, expected):
             src_nodata=nodata,
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
-            dst_nodata=nodata
+            dst_nodata=nodata,
         )
 
         assert (out == 1).sum() == expected
-        assert (out == nodata).sum() == (params.dst_width *
-                                         params.dst_height - expected)
+        assert (out == nodata).sum() == (
+            params.dst_width * params.dst_height - expected
+        )
 
 
 @pytest.mark.parametrize("options, expected", reproj_expected)
@@ -412,8 +461,7 @@ def test_reproject_nodata_nan(options, expected):
     with rasterio.Env(**options):
         params = uninvertable_reproject_params()
         source = np.ones((params.width, params.height), dtype=np.float32)
-        out = np.zeros((params.dst_width, params.dst_height),
-                       dtype=source.dtype)
+        out = np.zeros((params.dst_width, params.dst_height), dtype=source.dtype)
         out.fill(120)  # Fill with arbitrary value
 
         reproject(
@@ -424,12 +472,11 @@ def test_reproject_nodata_nan(options, expected):
             src_nodata=np.nan,
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
-            dst_nodata=np.nan
+            dst_nodata=np.nan,
         )
 
         assert (out == 1).sum() == expected
-        assert np.isnan(out).sum() == (params.dst_width *
-                                       params.dst_height - expected)
+        assert np.isnan(out).sum() == (params.dst_width * params.dst_height - expected)
 
 
 @pytest.mark.parametrize("options, expected", reproj_expected)
@@ -439,8 +486,7 @@ def test_reproject_dst_nodata_default(options, expected):
     with rasterio.Env(**options):
         params = uninvertable_reproject_params()
         source = np.ones((params.width, params.height), dtype=np.uint8)
-        out = np.zeros((params.dst_width, params.dst_height),
-                       dtype=source.dtype)
+        out = np.zeros((params.dst_width, params.dst_height), dtype=source.dtype)
         out.fill(120)  # Fill with arbitrary value
 
         reproject(
@@ -449,12 +495,11 @@ def test_reproject_dst_nodata_default(options, expected):
             src_transform=params.src_transform,
             src_crs=params.src_crs,
             dst_transform=params.dst_transform,
-            dst_crs=params.dst_crs
+            dst_crs=params.dst_crs,
         )
 
         assert (out == 1).sum() == expected
-        assert (out == 0).sum() == (params.dst_width *
-                                    params.dst_height - expected)
+        assert (out == 0).sum() == (params.dst_width * params.dst_height - expected)
 
 
 def test_reproject_invalid_dst_nodata():
@@ -473,7 +518,7 @@ def test_reproject_invalid_dst_nodata():
             src_nodata=0,
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
-            dst_nodata=999999999
+            dst_nodata=999999999,
         )
 
 
@@ -493,7 +538,7 @@ def test_reproject_invalid_src_nodata():
             src_nodata=999999999,
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
-            dst_nodata=215
+            dst_nodata=215,
         )
 
 
@@ -501,7 +546,7 @@ def test_reproject_init_nodata_tofile(tmpdir):
     """Test that nodata is being initialized."""
     params = default_reproject_params()
 
-    tiffname = str(tmpdir.join('foo.tif'))
+    tiffname = str(tmpdir.join("foo.tif"))
 
     source1 = np.zeros((params.width, params.height), dtype=np.uint8)
     source2 = source1.copy()
@@ -512,16 +557,16 @@ def test_reproject_init_nodata_tofile(tmpdir):
     source2[rows // 2:, cols // 2:] = 100
 
     kwargs = {
-        'count': 1,
-        'width': params.width,
-        'height': params.height,
-        'dtype': np.uint8,
-        'driver': 'GTiff',
-        'crs': params.dst_crs,
-        'transform': params.dst_transform
+        "count": 1,
+        "width": params.width,
+        "height": params.height,
+        "dtype": np.uint8,
+        "driver": "GTiff",
+        "crs": params.dst_crs,
+        "transform": params.dst_transform,
     }
 
-    with rasterio.open(tiffname, 'w', **kwargs) as dst:
+    with rasterio.open(tiffname, "w", **kwargs) as dst:
         reproject(
             source1,
             rasterio.band(dst, 1),
@@ -530,7 +575,7 @@ def test_reproject_init_nodata_tofile(tmpdir):
             src_nodata=0.0,
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
-            dst_nodata=0.0
+            dst_nodata=0.0,
         )
 
         # 200s should be overwritten by 100s
@@ -542,7 +587,7 @@ def test_reproject_init_nodata_tofile(tmpdir):
             src_nodata=0.0,
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
-            dst_nodata=0.0
+            dst_nodata=0.0,
         )
 
     with rasterio.open(tiffname) as src:
@@ -553,7 +598,7 @@ def test_reproject_no_init_nodata_tofile(tmpdir):
     """Test that nodata is not being initialized."""
     params = default_reproject_params()
 
-    tiffname = str(tmpdir.join('foo.tif'))
+    tiffname = str(tmpdir.join("foo.tif"))
 
     source1 = np.zeros((params.width, params.height), dtype=np.uint8)
     source2 = source1.copy()
@@ -564,16 +609,16 @@ def test_reproject_no_init_nodata_tofile(tmpdir):
     source2[rows // 2:, cols // 2:] = 100
 
     kwargs = {
-        'count': 1,
-        'width': params.width,
-        'height': params.height,
-        'dtype': np.uint8,
-        'driver': 'GTiff',
-        'crs': params.dst_crs,
-        'transform': params.dst_transform
+        "count": 1,
+        "width": params.width,
+        "height": params.height,
+        "dtype": np.uint8,
+        "driver": "GTiff",
+        "crs": params.dst_crs,
+        "transform": params.dst_transform,
     }
 
-    with rasterio.open(tiffname, 'w', **kwargs) as dst:
+    with rasterio.open(tiffname, "w", **kwargs) as dst:
         reproject(
             source1,
             rasterio.band(dst, 1),
@@ -582,7 +627,7 @@ def test_reproject_no_init_nodata_tofile(tmpdir):
             src_nodata=0.0,
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
-            dst_nodata=0.0
+            dst_nodata=0.0,
         )
 
         reproject(
@@ -594,7 +639,7 @@ def test_reproject_no_init_nodata_tofile(tmpdir):
             dst_transform=params.dst_transform,
             dst_crs=params.dst_crs,
             dst_nodata=0.0,
-            init_dest_nodata=False
+            init_dest_nodata=False,
         )
 
     # 200s should remain along with 100s
@@ -625,7 +670,7 @@ def test_reproject_no_init_nodata_toarray():
         src_nodata=0.0,
         dst_transform=params.dst_transform,
         dst_crs=params.dst_crs,
-        dst_nodata=0.0
+        dst_nodata=0.0,
     )
 
     assert out.max() == 200
@@ -640,7 +685,7 @@ def test_reproject_no_init_nodata_toarray():
         dst_transform=params.dst_transform,
         dst_crs=params.dst_crs,
         dst_nodata=0.0,
-        init_dest_nodata=False
+        init_dest_nodata=False,
     )
 
     # 200s should NOT be overwritten by 100s
@@ -650,10 +695,10 @@ def test_reproject_no_init_nodata_toarray():
 
 def test_reproject_multi():
     """Ndarry to ndarray."""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read()
     dst_crs = dict(
-        proj='merc',
+        proj="merc",
         a=6378137,
         b=6378137,
         lat_ts=0.0,
@@ -661,10 +706,11 @@ def test_reproject_multi():
         x_0=0.0,
         y_0=0,
         k=1.0,
-        units='m',
-        nadgrids='@null',
+        units="m",
+        nadgrids="@null",
         wktext=True,
-        no_defs=True)
+        no_defs=True,
+    )
     destin = np.empty(source.shape, dtype=np.uint8)
     reproject(
         source,
@@ -673,15 +719,16 @@ def test_reproject_multi():
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
         dst_crs=dst_crs,
-        resampling=Resampling.nearest)
+        resampling=Resampling.nearest,
+    )
     assert destin.any()
 
 
 def test_warp_from_file():
     """File to ndarray."""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         dst_crs = dict(
-            proj='merc',
+            proj="merc",
             a=6378137,
             b=6378137,
             lat_ts=0.0,
@@ -689,25 +736,24 @@ def test_warp_from_file():
             x_0=0.0,
             y_0=0,
             k=1.0,
-            units='m',
-            nadgrids='@null',
+            units="m",
+            nadgrids="@null",
             wktext=True,
-            no_defs=True)
+            no_defs=True,
+        )
         destin = np.empty(src.shape, dtype=np.uint8)
         reproject(
-            rasterio.band(src, 1),
-            destin,
-            dst_transform=DST_TRANSFORM,
-            dst_crs=dst_crs)
+            rasterio.band(src, 1), destin, dst_transform=DST_TRANSFORM, dst_crs=dst_crs
+        )
     assert destin.any()
 
 
 def test_warp_from_to_file(tmpdir):
     """File to file."""
-    tiffname = str(tmpdir.join('foo.tif'))
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    tiffname = str(tmpdir.join("foo.tif"))
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         dst_crs = dict(
-            proj='merc',
+            proj="merc",
             a=6378137,
             b=6378137,
             lat_ts=0.0,
@@ -715,25 +761,24 @@ def test_warp_from_to_file(tmpdir):
             x_0=0.0,
             y_0=0,
             k=1.0,
-            units='m',
-            nadgrids='@null',
+            units="m",
+            nadgrids="@null",
             wktext=True,
-            no_defs=True)
+            no_defs=True,
+        )
         kwargs = src.meta.copy()
-        kwargs.update(
-            transform=DST_TRANSFORM,
-            crs=dst_crs)
-        with rasterio.open(tiffname, 'w', **kwargs) as dst:
+        kwargs.update(transform=DST_TRANSFORM, crs=dst_crs)
+        with rasterio.open(tiffname, "w", **kwargs) as dst:
             for i in (1, 2, 3):
                 reproject(rasterio.band(src, i), rasterio.band(dst, i))
 
 
 def test_warp_from_to_file_multi(tmpdir):
     """File to file."""
-    tiffname = str(tmpdir.join('foo.tif'))
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    tiffname = str(tmpdir.join("foo.tif"))
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         dst_crs = dict(
-            proj='merc',
+            proj="merc",
             a=6378137,
             b=6378137,
             lat_ts=0.0,
@@ -741,29 +786,26 @@ def test_warp_from_to_file_multi(tmpdir):
             x_0=0.0,
             y_0=0,
             k=1.0,
-            units='m',
-            nadgrids='@null',
+            units="m",
+            nadgrids="@null",
             wktext=True,
-            no_defs=True)
+            no_defs=True,
+        )
         kwargs = src.meta.copy()
-        kwargs.update(
-            transform=DST_TRANSFORM,
-            crs=dst_crs)
-        with rasterio.open(tiffname, 'w', **kwargs) as dst:
+        kwargs.update(transform=DST_TRANSFORM, crs=dst_crs)
+        with rasterio.open(tiffname, "w", **kwargs) as dst:
             for i in (1, 2, 3):
-                reproject(
-                    rasterio.band(src, i),
-                    rasterio.band(dst, i),
-                    num_threads=2)
+                reproject(rasterio.band(src, i), rasterio.band(dst, i), num_threads=2)
 
 
- at pytest.fixture(scope='function')
+ at pytest.fixture(scope="function")
 def polygon_3373():
     """An EPSG:3373 polygon."""
     return {
-        'type': 'Polygon',
-        'coordinates': (
-            ((798842.3090855901, 6569056.500655151),
+        "type": "Polygon",
+        "coordinates": (
+            (
+                (798842.3090855901, 6569056.500655151),
                 (756688.2826828464, 6412397.888771972),
                 (755571.0617232556, 6408461.009397383),
                 (677605.2284582685, 6425600.39266733),
@@ -793,71 +835,75 @@ def polygon_3373():
                 (662054.7979028501, 6772962.86384242),
                 (841909.6014891531, 6731793.200435557),
                 (840726.455490463, 6727039.8672589315),
-                (798842.3090855901, 6569056.500655151)),)}
+                (798842.3090855901, 6569056.500655151),
+            ),
+        ),
+    }
 
 
 def test_transform_geom_polygon_cutting(polygon_3373):
     geom = polygon_3373
-    result = transform_geom(
-        'EPSG:3373', 'EPSG:4326', geom, antimeridian_cutting=True)
-    assert result['type'] == 'MultiPolygon'
-    assert len(result['coordinates']) == 2
+    result = transform_geom("EPSG:3373", "EPSG:4326", geom, antimeridian_cutting=True)
+    assert result["type"] == "MultiPolygon"
+    assert len(result["coordinates"]) == 2
 
 
 def test_transform_geom_polygon_offset(polygon_3373):
     geom = polygon_3373
     result = transform_geom(
-        'EPSG:3373',
-        'EPSG:4326',
-        geom,
-        antimeridian_cutting=True,
-        antimeridian_offset=0)
-    assert result['type'] == 'MultiPolygon'
-    assert len(result['coordinates']) == 2
+        "EPSG:3373", "EPSG:4326", geom, antimeridian_cutting=True, antimeridian_offset=0
+    )
+    assert result["type"] == "MultiPolygon"
+    assert len(result["coordinates"]) == 2
 
 
 def test_transform_geom_polygon_precision(polygon_3373):
     geom = polygon_3373
-    result = transform_geom('EPSG:3373', 'EPSG:4326', geom, precision=1, antimeridian_cutting=True)
-    assert all(round(x, 1) == x for x in flatten_coords(result['coordinates']))
+    result = transform_geom(
+        "EPSG:3373", "EPSG:4326", geom, precision=1, antimeridian_cutting=True
+    )
+    assert all(round(x, 1) == x for x in flatten_coords(result["coordinates"]))
 
 
 def test_transform_geom_linestring_precision(polygon_3373):
-    ring = polygon_3373['coordinates'][0]
-    geom = {'type': 'LineString', 'coordinates': ring}
-    result = transform_geom('EPSG:3373', 'EPSG:4326', geom, precision=1, antimeridian_cutting=True)
-    assert all(round(x, 1) == x for x in flatten_coords(result['coordinates']))
+    ring = polygon_3373["coordinates"][0]
+    geom = {"type": "LineString", "coordinates": ring}
+    result = transform_geom(
+        "EPSG:3373", "EPSG:4326", geom, precision=1, antimeridian_cutting=True
+    )
+    assert all(round(x, 1) == x for x in flatten_coords(result["coordinates"]))
 
 
 def test_transform_geom_linestring_precision_iso(polygon_3373):
-    ring = polygon_3373['coordinates'][0]
-    geom = {'type': 'LineString', 'coordinates': ring}
-    result = transform_geom('EPSG:3373', 'EPSG:3373', geom, precision=1)
-    assert int(result['coordinates'][0][0] * 10) == 7988423
+    ring = polygon_3373["coordinates"][0]
+    geom = {"type": "LineString", "coordinates": ring}
+    result = transform_geom("EPSG:3373", "EPSG:3373", geom, precision=1)
+    assert int(result["coordinates"][0][0] * 10) == 7988423
 
 
 def test_transform_geom_linearring_precision(polygon_3373):
-    ring = polygon_3373['coordinates'][0]
-    geom = {'type': 'LinearRing', 'coordinates': ring}
-    result = transform_geom('EPSG:3373', 'EPSG:4326', geom, precision=1, antimeridian_cutting=True)
-    assert all(round(x, 1) == x for x in flatten_coords(result['coordinates']))
+    ring = polygon_3373["coordinates"][0]
+    geom = {"type": "LinearRing", "coordinates": ring}
+    result = transform_geom(
+        "EPSG:3373", "EPSG:4326", geom, precision=1, antimeridian_cutting=True
+    )
+    assert all(round(x, 1) == x for x in flatten_coords(result["coordinates"]))
 
 
 def test_transform_geom_linestring_precision_z(polygon_3373):
-    ring = polygon_3373['coordinates'][0]
+    ring = polygon_3373["coordinates"][0]
     x, y = zip(*ring)
     ring = list(zip(x, y, [0.0 for i in range(len(x))]))
-    geom = {'type': 'LineString', 'coordinates': ring}
-    result = transform_geom('EPSG:3373', 'EPSG:3373', geom, precision=1)
-    assert int(result['coordinates'][0][0] * 10) == 7988423
-    assert int(result['coordinates'][0][2] * 10) == 0
+    geom = {"type": "LineString", "coordinates": ring}
+    result = transform_geom("EPSG:3373", "EPSG:3373", geom, precision=1)
+    assert int(result["coordinates"][0][0] * 10) == 7988423
+    assert int(result["coordinates"][0][2] * 10) == 0
 
 
 def test_transform_geom_multipolygon(polygon_3373):
-    geom = {
-        'type': 'MultiPolygon', 'coordinates': [polygon_3373['coordinates']]}
-    result = transform_geom('EPSG:3373', 'EPSG:4326', geom, precision=1)
-    assert all(round(x, 1) == x for x in flatten_coords(result['coordinates']))
+    geom = {"type": "MultiPolygon", "coordinates": [polygon_3373["coordinates"]]}
+    result = transform_geom("EPSG:3373", "EPSG:4326", geom, precision=1)
+    assert all(round(x, 1) == x for x in flatten_coords(result["coordinates"]))
 
 
 @pytest.mark.parametrize("method", SUPPORTED_RESAMPLING)
@@ -876,7 +922,7 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
         Resampling.min: 436397,
         Resampling.med: 437194,
         Resampling.q1: 436397,
-        Resampling.q3: 438948
+        Resampling.q3: 438948,
     }
 
     with rasterio.open(path_rgb_byte_tif) as src:
@@ -889,8 +935,9 @@ 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'},
-        resampling=method)
+        dst_crs={"init": "EPSG:3857"},
+        resampling=method,
+    )
 
     assert np.count_nonzero(out) == expected[method]
 
@@ -912,10 +959,10 @@ def test_reproject_resampling_alpha(method):
         Resampling.min: 436397,
         Resampling.med: 437194,
         Resampling.q1: 436397,
-        Resampling.q3: 438948
+        Resampling.q3: 438948,
     }
 
-    with rasterio.open('tests/data/RGBA.byte.tif') as src:
+    with rasterio.open("tests/data/RGBA.byte.tif") as src:
         source = src.read(1)
 
     out = np.empty(src.shape, dtype=np.uint8)
@@ -925,22 +972,23 @@ def test_reproject_resampling_alpha(method):
         src_transform=src.transform,
         src_crs=src.crs,
         dst_transform=DST_TRANSFORM,
-        dst_crs={'init': 'EPSG:3857'},
-        resampling=method)
+        dst_crs={"init": "EPSG:3857"},
+        resampling=method,
+    )
 
     assert np.count_nonzero(out) == expected[method]
 
 
 @pytest.mark.skipif(
-    gdal_version.at_least('2.0'),
-    reason="Tests only applicable to GDAL < 2.0")
+    gdal_version.at_least("2.0"), reason="Tests only applicable to GDAL < 2.0"
+)
 @pytest.mark.parametrize("method", GDAL2_RESAMPLING)
 def test_reproject_not_yet_supported_resampling(method):
     """Test resampling methods not yet supported by this version of GDAL"""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {'init': 'EPSG:32619'}
+    dst_crs = {"init": "EPSG:32619"}
     out = np.empty(src.shape, dtype=np.uint8)
     with pytest.raises(GDALVersionError):
         reproject(
@@ -950,15 +998,16 @@ def test_reproject_not_yet_supported_resampling(method):
             src_crs=src.crs,
             dst_transform=DST_TRANSFORM,
             dst_crs=dst_crs,
-            resampling=method)
+            resampling=method,
+        )
 
 
 def test_reproject_unsupported_resampling():
     """Values not in enums. Resampling are not supported."""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {'init': 'EPSG:32619'}
+    dst_crs = {"init": "EPSG:32619"}
     out = np.empty(src.shape, dtype=np.uint8)
     with pytest.raises(ValueError):
         reproject(
@@ -968,15 +1017,16 @@ def test_reproject_unsupported_resampling():
             src_crs=src.crs,
             dst_transform=DST_TRANSFORM,
             dst_crs=dst_crs,
-            resampling=99)
+            resampling=99,
+        )
 
 
 def test_reproject_unsupported_resampling_guass():
     """Resampling.gauss is unsupported."""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {'init': 'EPSG:32619'}
+    dst_crs = {"init": "EPSG:32619"}
     out = np.empty(src.shape, dtype=np.uint8)
     with pytest.raises(ValueError):
         reproject(
@@ -986,7 +1036,8 @@ def test_reproject_unsupported_resampling_guass():
             src_crs=src.crs,
             dst_transform=DST_TRANSFORM,
             dst_crs=dst_crs,
-            resampling=Resampling.gauss)
+            resampling=Resampling.gauss,
+        )
 
 
 @pytest.mark.parametrize("method", SUPPORTED_RESAMPLING)
@@ -995,18 +1046,19 @@ def test_resample_default_invert_proj(method):
     with the default Env
     """
 
-    with rasterio.open('tests/data/world.rgb.tif') as src:
+    with rasterio.open("tests/data/world.rgb.tif") as src:
         source = src.read(1)
         profile = src.profile.copy()
 
-    dst_crs = {'init': 'EPSG:32619'}
+    dst_crs = {"init": "EPSG:32619"}
 
     # Calculate the ideal dimensions and transformation in the new crs
     dst_affine, dst_width, dst_height = calculate_default_transform(
-        src.crs, dst_crs, src.width, src.height, *src.bounds)
+        src.crs, dst_crs, src.width, src.height, *src.bounds
+    )
 
-    profile['height'] = dst_height
-    profile['width'] = dst_width
+    profile["height"] = dst_height
+    profile["width"] = dst_width
 
     out = np.empty(shape=(dst_height, dst_width), dtype=np.uint8)
 
@@ -1018,28 +1070,32 @@ def test_resample_default_invert_proj(method):
         src_crs=src.crs,
         dst_transform=dst_affine,
         dst_crs=dst_crs,
-        resampling=method)
+        resampling=method,
+    )
 
     assert out.mean() > 0
 
 
 def test_target_aligned_pixels():
     """Issue 853 has been resolved"""
-    with rasterio.open('tests/data/world.rgb.tif') as src:
+    with rasterio.open("tests/data/world.rgb.tif") as src:
         source = src.read(1)
         profile = src.profile.copy()
 
-    dst_crs = {'init': 'epsg:3857'}
+    dst_crs = {"init": "epsg:3857"}
 
     with rasterio.Env(CHECK_WITH_INVERT_PROJ=False):
         # Calculate the ideal dimensions and transformation in the new crs
         dst_affine, dst_width, dst_height = calculate_default_transform(
-            src.crs, dst_crs, src.width, src.height, *src.bounds)
+            src.crs, dst_crs, src.width, src.height, *src.bounds
+        )
 
-        dst_affine, dst_width, dst_height = aligned_target(dst_affine, dst_width, dst_height, 100000.0)
+        dst_affine, dst_width, dst_height = aligned_target(
+            dst_affine, dst_width, dst_height, 100000.0
+        )
 
-        profile['height'] = dst_height
-        profile['width'] = dst_width
+        profile["height"] = dst_height
+        profile["width"] = dst_width
 
         out = np.empty(shape=(dst_height, dst_width), dtype=np.uint8)
 
@@ -1050,7 +1106,8 @@ def test_target_aligned_pixels():
             src_crs=src.crs,
             dst_transform=dst_affine,
             dst_crs=dst_crs,
-            resampling=Resampling.nearest)
+            resampling=Resampling.nearest,
+        )
 
         # Check that there is no black borders
         assert out[:, 0].all()
@@ -1065,25 +1122,31 @@ def test_resample_no_invert_proj(method):
     CHECK_WITH_INVERT_PROJ = False
     """
 
-    if method in (Resampling.bilinear, Resampling.cubic,
-                  Resampling.cubic_spline, Resampling.lanczos):
+    if method in (
+        Resampling.bilinear,
+        Resampling.cubic,
+        Resampling.cubic_spline,
+        Resampling.lanczos,
+    ):
         pytest.xfail(
             reason="Some resampling methods succeed but produce blank images. "
-                   "See https://github.com/mapbox/rasterio/issues/614")
+            "See https://github.com/mapbox/rasterio/issues/614"
+        )
 
     with rasterio.Env(CHECK_WITH_INVERT_PROJ=False):
-        with rasterio.open('tests/data/world.rgb.tif') as src:
+        with rasterio.open("tests/data/world.rgb.tif") as src:
             source = src.read(1)
             profile = src.profile.copy()
 
-        dst_crs = {'init': 'EPSG:32619'}
+        dst_crs = {"init": "EPSG:32619"}
 
         # Calculate the ideal dimensions and transformation in the new crs
         dst_affine, dst_width, dst_height = calculate_default_transform(
-            src.crs, dst_crs, src.width, src.height, *src.bounds)
+            src.crs, dst_crs, src.width, src.height, *src.bounds
+        )
 
-        profile['height'] = dst_height
-        profile['width'] = dst_width
+        profile["height"] = dst_height
+        profile["width"] = dst_width
 
         out = np.empty(shape=(dst_height, dst_width), dtype=np.uint8)
 
@@ -1096,7 +1159,8 @@ def test_resample_no_invert_proj(method):
             src_crs=src.crs,
             dst_transform=dst_affine,
             dst_crs=dst_crs,
-            resampling=method)
+            resampling=method,
+        )
 
         assert out.mean() > 0
 
@@ -1112,12 +1176,14 @@ def test_reproject_crs_none():
 
     with pytest.raises(ValueError):
         reproject(
-            src, dst,
+            src,
+            dst,
             src_transform=srcaff,
             src_crs=srccrs,
             dst_transform=dstaff,
             dst_crs=dstcrs,
-            resampling=Resampling.nearest)
+            resampling=Resampling.nearest,
+        )
 
 
 def test_reproject_identity_src():
@@ -1125,7 +1191,7 @@ def test_reproject_identity_src():
     src = np.random.random(25).reshape((1, 5, 5))
     dst = np.empty(shape=(1, 10, 10))
     dstaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
-    crs = {'init': 'epsg:3857'}
+    crs = {"init": "epsg:3857"}
 
     src_affines = [
         Affine(1.0, 0.0, 0.0, 0.0, 1.0, 0.0),  # Identity both positive
@@ -1135,12 +1201,14 @@ def test_reproject_identity_src():
     for srcaff in src_affines:
         # reproject expected to not raise any error in any of the srcaff
         reproject(
-            src, dst,
+            src,
+            dst,
             src_transform=srcaff,
             src_crs=crs,
             dst_transform=dstaff,
             dst_crs=crs,
-            resampling=Resampling.nearest)
+            resampling=Resampling.nearest,
+        )
 
 
 def test_reproject_identity_dst():
@@ -1148,7 +1216,7 @@ def test_reproject_identity_dst():
     src = np.random.random(100).reshape((1, 10, 10))
     srcaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
     dst = np.empty(shape=(1, 5, 5))
-    crs = {'init': 'epsg:3857'}
+    crs = {"init": "epsg:3857"}
 
     dst_affines = [
         Affine(1.0, 0.0, 0.0, 0.0, 1.0, 0.0),  # Identity both positive
@@ -1158,17 +1226,19 @@ def test_reproject_identity_dst():
     for dstaff in dst_affines:
         # reproject expected to not raise any error in any of the dstaff
         reproject(
-            src, dst,
+            src,
+            dst,
             src_transform=srcaff,
             src_crs=crs,
             dst_transform=dstaff,
             dst_crs=crs,
-            resampling=Resampling.nearest)
+            resampling=Resampling.nearest,
+        )
 
 
- at pytest.fixture(scope='function')
+ at pytest.fixture(scope="function")
 def rgb_byte_profile():
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         return src.profile
 
 
@@ -1181,20 +1251,24 @@ def test_reproject_gcps_transform_exclusivity():
 def test_reproject_gcps(rgb_byte_profile):
     """Reproject using ground control points for the source"""
     source = np.ones((3, 800, 800), dtype=np.uint8) * 255
-    out = np.zeros((3, rgb_byte_profile['height'], rgb_byte_profile['height']), dtype=np.uint8)
+    out = np.zeros(
+        (3, rgb_byte_profile["height"], rgb_byte_profile["height"]), dtype=np.uint8
+    )
     src_gcps = [
         GroundControlPoint(row=0, col=0, x=156113, y=2818720, z=0),
         GroundControlPoint(row=0, col=800, x=338353, y=2785790, z=0),
         GroundControlPoint(row=800, col=800, x=297939, y=2618518, z=0),
-        GroundControlPoint(row=800, col=0, x=115698, y=2651448, z=0)]
+        GroundControlPoint(row=800, col=0, x=115698, y=2651448, z=0),
+    ]
     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'],
-        resampling=Resampling.nearest)
+        dst_transform=rgb_byte_profile["transform"],
+        dst_crs=rgb_byte_profile["crs"],
+        resampling=Resampling.nearest,
+    )
 
     assert not out.all()
     assert not out[:, 0, 0].any()
@@ -1204,26 +1278,23 @@ def test_reproject_gcps(rgb_byte_profile):
 
 
 @requires_gdal22(
-    reason="GDAL 2.2.0 and newer has different antimeridian cutting behavior.")
+    reason="GDAL 2.2.0 and newer has different antimeridian cutting behavior."
+)
 def test_transform_geom_gdal22():
     """Enabling `antimeridian_cutting` has no effect on GDAL 2.2.0 or newer
     where antimeridian cutting is always enabled.  This could produce
     unexpected geometries, so an exception is raised.
     """
-    geom = {
-        'type': 'Point',
-        'coordinates': [0, 0]
-    }
+    geom = {"type": "Point", "coordinates": [0, 0]}
     with pytest.raises(GDALVersionError):
-        transform_geom(
-            'EPSG:4326', 'EPSG:3857', geom, antimeridian_cutting=False)
+        transform_geom("EPSG:4326", "EPSG:3857", geom, antimeridian_cutting=False)
 
 
 def test_issue1056():
     """Warp sucessfully from RGB's upper bands to an array"""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
 
-        dst_crs = {'init': 'EPSG:3857'}
+        dst_crs = {"init": "EPSG:3857"}
         out = np.zeros(src.shape, dtype=np.uint8)
         reproject(
             rasterio.band(src, 2),
@@ -1232,15 +1303,16 @@ def test_issue1056():
             src_crs=src.crs,
             dst_transform=DST_TRANSFORM,
             dst_crs=dst_crs,
-            resampling=Resampling.nearest)
+            resampling=Resampling.nearest,
+        )
 
 
 def test_reproject_dst_nodata():
     """Affirm resolution of issue #1395"""
-    with rasterio.open('tests/data/RGB.byte.tif') as src:
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
         source = src.read(1)
 
-    dst_crs = {'init': 'EPSG:3857'}
+    dst_crs = {"init": "EPSG:3857"}
     out = np.empty(src.shape, dtype=np.float32)
     reproject(
         source,
@@ -1251,7 +1323,8 @@ def test_reproject_dst_nodata():
         dst_crs=dst_crs,
         src_nodata=0,
         dst_nodata=np.nan,
-        resampling=Resampling.nearest)
+        resampling=Resampling.nearest,
+    )
 
     assert (out > 0).sum() == 438113
     assert out[0, 0] != 0
@@ -1260,8 +1333,8 @@ 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'}
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
+        dst_crs = {"init": "EPSG:3857"}
         out = np.zeros(src.shape, dtype=np.uint8)
         reproject(
             rasterio.band(src, 2),
@@ -1271,7 +1344,8 @@ def test_issue1401():
             dst_transform=DST_TRANSFORM,
             dst_crs=dst_crs,
             resampling=Resampling.nearest,
-            warp_mem_limit=4000)
+            warp_mem_limit=4000,
+        )
 
 
 def test_reproject_dst_alpha(path_rgb_msk_byte_tif):
@@ -1289,21 +1363,25 @@ 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_alpha=4)
+            dst_crs={"init": "EPSG:3857"},
+            dst_alpha=4,
+        )
 
         assert dst_arr[3].any()
 
 
 @pytest.mark.xfail(
-    rasterio.__gdal_version__ in ['2.2.0', '2.2.1', '2.2.2', '2.2.3'],
-    reason=("GDAL had regression in 2.2.X series, fixed in 2.2.4,"
-            " reproject used dst index instead of src index when destination was single band"))
+    rasterio.__gdal_version__ in ["2.2.0", "2.2.1", "2.2.2", "2.2.3"],
+    reason=(
+        "GDAL had regression in 2.2.X series, fixed in 2.2.4,"
+        " reproject used dst index instead of src index when destination was single band"
+    ),
+)
 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'}
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
+        dst_crs = {"init": "EPSG:3857"}
 
         reprojected = []
 
@@ -1315,9 +1393,45 @@ def test_issue1350():
                 out,
                 resampling=Resampling.nearest,
                 dst_transform=DST_TRANSFORM,
-                dst_crs=dst_crs)
+                dst_crs=dst_crs,
+            )
 
             reprojected.append(out)
 
         for i in range(1, len(reprojected)):
             assert not (reprojected[0] == reprojected[i]).all()
+
+
+def test_issue_1446():
+    """Confirm resolution of #1446"""
+    g = transform_geom(
+        CRS.from_epsg(4326),
+        CRS.from_epsg(32610),
+        {"type": "Point", "coordinates": (-122.51403808499907, 38.06106733107932)},
+    )
+    assert round(g["coordinates"][0], 1) == 542630.9
+    assert round(g["coordinates"][1], 1) == 4212702.1
+
+
+def test_issue_1446_b():
+    """Confirm that lines aren't thrown as reported in #1446"""
+    src_crs = CRS({"init": "EPSG:4326"})
+    dst_crs = CRS(
+        {
+            "proj": "sinu",
+            "lon_0": 350.85607029556,
+            "x_0": 0,
+            "y_0": 0,
+            "a": 3396190,
+            "b": 3396190,
+            "units": "m",
+            "no_defs": True,
+        }
+    )
+    collection = json.load(open("tests/data/issue1446.geojson"))
+    geoms = {f["properties"]["fid"]: f["geometry"] for f in collection["features"]}
+    transformed_geoms = {
+        k: transform_geom(src_crs, dst_crs, g) for k, g in geoms.items()
+    }
+    # Before the fix, this geometry was thrown eastward of 0.0. It should be between -350 and -250.
+    assert all([-350 < x < -150 for x, y in transformed_geoms[183519]["coordinates"]])


=====================================
tests/test_windows.py
=====================================
@@ -571,3 +571,11 @@ def test_round_lengths_no_op_error():
 def test_round_offsets_no_op_error():
     with pytest.raises(WindowError):
         Window(0, 0, 1, 1).round_offsets(op='lolwut')
+
+
+def test_window_hashable():
+    a = Window(0, 0, 10, 10)
+    b = Window(0, 0, 10, 10)
+    c = Window(8, 8, 12, 12)
+    assert hash(a) == hash(b)
+    assert hash(a) != hash(c)


=====================================
tests/test_write.py
=====================================
@@ -320,3 +320,9 @@ def test_wplus_transform(tmpdir):
     with rasterio.open(name, 'w+', driver='GTiff', crs='epsg:4326', transform=transform, height=10, width=10, count=1, dtype='uint8') as dst:
         dst.write(np.ones((1, 10, 10), dtype='uint8'))
         assert dst.transform == transform
+
+
+def test_write_no_driver__issue_1203(tmpdir):
+    name = str(tmpdir.join("test.tif"))
+    with pytest.raises(ValueError), rasterio.open(name, 'w', height=1, width=1, count=1, dtype='uint8'):
+        print("TEST FAILED IF THIS IS REACHED.")



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

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


More information about the Pkg-grass-devel mailing list