[Git][debian-gis-team/rasterio][master] 4 commits: New upstream version 1.0.10

Bas Couwenberg gitlab at salsa.debian.org
Sat Nov 17 07:02:02 GMT 2018


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


Commits:
635117fd by Bas Couwenberg at 2018-11-17T06:43:31Z
New upstream version 1.0.10
- - - - -
f94a3d96 by Bas Couwenberg at 2018-11-17T06:43:35Z
Merge tag 'upstream/1.0.10'

Upstream version 1.0.10

- - - - -
9d5d2ae4 by Bas Couwenberg at 2018-11-17T06:43:56Z
New upstream release.

- - - - -
9f0db4a2 by Bas Couwenberg at 2018-11-17T06:45:07Z
Set distribution to unstable.

- - - - -


22 changed files:

- CHANGES.txt
- debian/changelog
- rasterio/__init__.py
- rasterio/_base.pxd
- rasterio/_base.pyx
- rasterio/_crs.pyx
- rasterio/_env.pyx
- rasterio/_io.pyx
- rasterio/env.py
- rasterio/gdal.pxi
- rasterio/path.py
- rasterio/session.py
- tests/conftest.py
- + tests/data/compdcs.vrt
- + tests/data/test_esri_wkt.tif
- + tests/test__env.py
- tests/test_crs.py
- tests/test_env.py
- tests/test_rio_warp.py
- + tests/test_scale_offset.py
- tests/test_session.py
- tests/test_warp.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,17 @@
 Changes
 =======
 
+1.0.10 (2019-11-16)
+-------------------
+
+- Avoid segmentation fault when OSRGetAuthority* functions return null
+  pointers, making Rasterio more robust when reading CRS that don't map
+  perfectly to PROJ.
+- Ensure that GDAL and PROJ support files can be found in Rasterio wheels when
+  we call CRS methods (#1539).
+- Accomodate the Esri flavor of projection WKT (#1537).
+- Add missing raster offsets and scales properties (#1527).
+
 1.0.9 (2019-10-25)
 ------------------
 


=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+rasterio (1.0.10-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Sat, 17 Nov 2018 07:44:56 +0100
+
 rasterio (1.0.9-1) unstable; urgency=medium
 
   * Team upload.


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


=====================================
rasterio/_base.pxd
=====================================
@@ -23,6 +23,8 @@ cdef class DatasetBase:
     cdef public object _nodatavals
     cdef public object _units
     cdef public object _descriptions
+    cdef public object _scales
+    cdef public object _offsets
     cdef public object _read
     cdef public object _gcps
     cdef GDALDatasetH handle(self) except NULL


=====================================
rasterio/_base.pyx
=====================================
@@ -18,13 +18,12 @@ from rasterio._shim cimport open_dataset
 
 from rasterio.compat import string_types
 from rasterio.control import GroundControlPoint
-from rasterio.crs import CRS
 from rasterio import dtypes
 from rasterio.coords import BoundingBox
 from rasterio.crs import CRS
 from rasterio.enums import (
     ColorInterp, Compression, Interleaving, MaskFlags, PhotometricInterp)
-from rasterio.env import Env
+from rasterio.env import Env, env_ctx_if_needed
 from rasterio.errors import (
     RasterioIOError, CRSError, DriverRegistrationError, NotGeoreferencedWarning,
     RasterBlockError, BandOverviewError)
@@ -208,6 +207,8 @@ cdef class DatasetBase(object):
         self._nodatavals = []
         self._units = ()
         self._descriptions = ()
+        self._scales = ()
+        self._offsets = ()
         self._gcps = None
         self._read = False
 
@@ -261,70 +262,60 @@ cdef class DatasetBase(object):
 
     def _handle_crswkt(self, wkt):
         """Return the GDAL dataset's stored CRS"""
-        cdef char *proj = NULL
         cdef OGRSpatialReferenceH osr = NULL
+        cdef const char *auth_key = NULL
+        cdef const char *auth_val = NULL
+
+        if not wkt:
+            log.debug("No projection detected.")
+            return None
+
         wkt_b = wkt.encode('utf-8')
         cdef const char *wkt_c = wkt_b
 
-        # Test that the WKT definition isn't just an empty string, which
-        # can happen when the source dataset is not georeferenced.
-        if len(wkt) > 0:
-            crs = CRS()
+        with env_ctx_if_needed():
+            try:
 
-            osr = OSRNewSpatialReference(wkt_c)
-            if osr == NULL:
-                raise ValueError("Unexpected NULL spatial reference")
-            log.debug("Got coordinate system")
-
-            # Try to find an EPSG code in the spatial referencing.
-            if OSRAutoIdentifyEPSG(osr) == 0:
-                key = OSRGetAuthorityName(osr, NULL)
-                val = OSRGetAuthorityCode(osr, NULL)
-                log.debug("Authority key: %s, value: %s", key, val)
-                crs['init'] = u'epsg:' + val
-            else:
-                log.debug("Failed to auto identify EPSG")
-                OSRExportToProj4(osr, &proj)
-                if proj == NULL:
-                    raise ValueError("Unexpected Null spatial reference")
-
-                value = proj
-                value = value.strip()
-
-                for param in value.split():
-                    kv = param.split("=")
-                    if len(kv) == 2:
-                        k, v = kv
-                        try:
-                            v = float(v)
-                            if v % 1 == 0:
-                                v = int(v)
-                        except ValueError:
-                            # Leave v as a string
-                            pass
-                    elif len(kv) == 1:
-                        k, v = kv[0], True
-                    else:
-                        raise ValueError(
-                            "Unexpected proj parameter %s" % param)
-                    k = k.lstrip("+")
-                    crs[k] = v
-
-            CPLFree(proj)
-            _safe_osr_release(osr)
-            return crs
+                osr = exc_wrap_pointer(OSRNewSpatialReference(wkt_c))
+                log.debug("Got coordinate system")
 
-        else:
-            log.debug("No projection detected.")
+                retval = OSRAutoIdentifyEPSG(osr)
+
+                if retval > 0:
+                    log.debug("Failed to auto identify EPSG: %d", retval)
+
+                else:
+                    log.debug("Auto identified EPSG: %d", retval)
+
+                    try:
+                        auth_key = OSRGetAuthorityName(osr, NULL)
+                        auth_val = OSRGetAuthorityCode(osr, NULL)
+
+                    except CPLE_NotSupportedError as exc:
+                        log.debug("{}".format(exc))
+
+                    if auth_key != NULL and auth_val != NULL:
+                        return CRS({'init': u'{}:{}'.format(auth_key.lower(), auth_val)})
+
+                return CRS.from_wkt(wkt)
+
+            except CPLE_BaseError as exc:
+                raise CRSError("{}".format(exc))
+
+            finally:
+                 _safe_osr_release(osr)
 
     def read_crs(self):
         """Return the GDAL dataset's stored CRS"""
-        cdef const char *wkt_b = GDALGetProjectionRef(self._hds)
-        if wkt_b == NULL:
-            raise ValueError("Unexpected NULL spatial reference")
+        cdef const char *wkt_b = NULL
 
-        wkt = wkt_b
-        return self._handle_crswkt(wkt)
+        with env_ctx_if_needed():
+            wkt_b = GDALGetProjectionRef(self._hds)
+            if wkt_b == NULL:
+                raise ValueError("Unexpected NULL spatial reference")
+            wkt = wkt_b
+            log.debug("WKT: %r", wkt)
+            return self._handle_crswkt(wkt)
 
     def read_transform(self):
         """Return the stored GDAL GeoTransform"""
@@ -571,6 +562,12 @@ cdef class DatasetBase(object):
     def _set_all_descriptions(self, value):
         raise NotImplementedError
 
+    def _set_all_scales(self, value):
+        raise NotImplementedError
+
+    def _set_all_offsets(self, value):
+        raise NotImplementedError
+
     def _set_all_units(self, value):
         raise NotImplementedError
 
@@ -612,6 +609,44 @@ cdef class DatasetBase(object):
         def __set__(self, value):
             self.write_transform(value.to_gdal())
 
+    property offsets:
+        """Raster offset for each dataset band
+
+        To set offsets, one for each band is required.
+
+        Returns
+        -------
+        list of float
+        """
+        def __get__(self):
+            cdef int success = 0
+            if not self._offsets:
+                offsets = [GDALGetRasterOffset(self.band(j), &success) for j in self.indexes]
+                self._offsets = tuple(offsets)
+            return self._offsets
+
+        def __set__(self, value):
+            self._set_all_offsets(value)
+
+    property scales:
+        """Raster scale for each dataset band
+
+        To set scales, one for each band is required.
+
+        Returns
+        -------
+        list of float
+        """
+        def __get__(self):
+            cdef int success = 0
+            if not self._scales:
+                scales = [GDALGetRasterScale(self.band(j), &success) for j in self.indexes]
+                self._scales = tuple(scales)
+            return self._scales
+
+        def __set__(self, value):
+            self._set_all_scales(value)
+
     property units:
         """A list of str: one units string for each dataset band
 
@@ -1240,10 +1275,19 @@ def _transform(src_crs, dst_crs, xs, ys, zs):
     try:
         transform = OCTNewCoordinateTransformation(src, dst)
         transform = exc_wrap_pointer(transform)
+        exc_wrap_int(OCTTransform(transform, n, x, y, z))
 
-        err = OCTTransform(transform, n, x, y, z)
-        err = exc_wrap_int(err)
+    except CPLE_BaseError as exc:
+        log.debug("{}".format(exc))
 
+    except:
+        CPLFree(x)
+        CPLFree(y)
+        CPLFree(z)
+        _safe_osr_release(src)
+        _safe_osr_release(dst)
+
+    try:
         res_xs = [0]*n
         res_ys = [0]*n
         for i in range(n):
@@ -1253,12 +1297,9 @@ def _transform(src_crs, dst_crs, xs, ys, zs):
             res_zs = [0]*n
             for i in range(n):
                 res_zs[i] = z[i]
-            retval = (res_xs, res_ys, res_zs)
+            return (res_xs, res_ys, res_zs)
         else:
-            retval = (res_xs, res_ys)
-
-    except CPLE_NotSupportedError as exc:
-        raise CRSError(str(exc))
+            return (res_xs, res_ys)
 
     finally:
         CPLFree(x)
@@ -1267,45 +1308,25 @@ def _transform(src_crs, dst_crs, xs, ys, zs):
         _safe_osr_release(src)
         _safe_osr_release(dst)
 
-    return retval
-
 
 cdef OGRSpatialReferenceH _osr_from_crs(object crs) except NULL:
     """Returns a reference to memory that must be deallocated
     by the caller."""
-    if not crs:
-        raise CRSError("A defined coordinate reference system is required")
-
-    cdef OGRSpatialReferenceH osr = OSRNewSpatialReference(NULL)
+    crs = CRS.from_user_input(crs)
 
-    if isinstance(crs, string_types):
-        proj = crs.encode('utf-8')
+    # EPSG is a special case.
+    init = crs.get('init')
+    if init:
+        auth, val = init.strip().split(':')
 
-    # Make a CRS object from provided dict.
+        if not val or auth.upper() != 'EPSG':
+            raise CRSError("Invalid CRS: {!r}".format(crs))
+        proj = 'EPSG:{}'.format(val).encode('utf-8')
     else:
-        crs = CRS(crs)
-        # EPSG is a special case.
-        init = crs.get('init')
-        if init:
-            auth, val = init.split(':')
-
-            if not val:
-                _safe_osr_release(osr)
-                raise CRSError("Invalid CRS: {!r}".format(crs))
-
-            if auth.upper() == 'EPSG':
-                proj = 'EPSG:{}'.format(val).encode('utf-8')
-        else:
-            params = []
-            for k, v in crs.items():
-                if v is True or (k in ('no_defs', 'wktext') and v):
-                    params.append("+%s" % k)
-                else:
-                    params.append("+%s=%s" % (k, v))
-            proj = " ".join(params)
-            log.debug("PROJ.4 to be imported: %r", proj)
-            proj = proj.encode('utf-8')
+        proj = crs.to_string().encode('utf-8')
+        log.debug("PROJ.4 to be imported: %r", proj)
 
+    cdef OGRSpatialReferenceH osr = OSRNewSpatialReference(NULL)
     try:
         retval = exc_wrap_int(OSRSetFromUserInput(osr, <const char *>proj))
         if retval:


=====================================
rasterio/_crs.pyx
=====================================
@@ -6,10 +6,10 @@ include "gdal.pxi"
 import json
 import logging
 
-from rasterio._err import CPLE_BaseError
-from rasterio.compat import UserDict
-from rasterio.compat import string_types
+from rasterio._err import CPLE_BaseError, CPLE_NotSupportedError
+from rasterio.compat import UserDict, string_types
 from rasterio.errors import CRSError
+from rasterio.env import env_ctx_if_needed
 
 from rasterio._base cimport _osr_from_crs as osr_from_crs
 from rasterio._base cimport _safe_osr_release
@@ -33,12 +33,13 @@ class _CRS(UserDict):
         cdef OGRSpatialReferenceH osr_crs = NULL
         cdef int retval
 
-        try:
-            osr_crs = osr_from_crs(self)
-            retval = OSRIsGeographic(osr_crs)
-            return bool(retval == 1)
-        finally:
-            _safe_osr_release(osr_crs)
+        with env_ctx_if_needed():
+            try:
+                osr_crs = osr_from_crs(self)
+                retval = OSRIsGeographic(osr_crs)
+                return bool(retval == 1)
+            finally:
+                _safe_osr_release(osr_crs)
 
     @property
     def is_projected(self):
@@ -51,12 +52,13 @@ class _CRS(UserDict):
         cdef OGRSpatialReferenceH osr_crs = NULL
         cdef int retval
 
-        try:
-            osr_crs = osr_from_crs(self)
-            retval = OSRIsProjected(osr_crs)
-            return bool(retval == 1)
-        finally:
-            _safe_osr_release(osr_crs)
+        with env_ctx_if_needed():
+            try:
+                osr_crs = osr_from_crs(self)
+                retval = OSRIsProjected(osr_crs)
+                return bool(retval == 1)
+            finally:
+                _safe_osr_release(osr_crs)
 
     def __eq__(self, other):
         cdef OGRSpatialReferenceH osr_crs1 = NULL
@@ -93,13 +95,14 @@ class _CRS(UserDict):
         cdef char *srcwkt = NULL
         cdef OGRSpatialReferenceH osr = NULL
 
-        try:
-            osr = osr_from_crs(self)
-            OSRExportToWkt(osr, &srcwkt)
-            return srcwkt.decode('utf-8')
-        finally:
-            CPLFree(srcwkt)
-            _safe_osr_release(osr)
+        with env_ctx_if_needed():
+            try:
+                osr = osr_from_crs(self)
+                OSRExportToWkt(osr, &srcwkt)
+                return srcwkt.decode('utf-8')
+            finally:
+                CPLFree(srcwkt)
+                _safe_osr_release(osr)
 
     def to_epsg(self):
         """The epsg code of the CRS
@@ -110,14 +113,16 @@ class _CRS(UserDict):
         """
         cdef OGRSpatialReferenceH osr = NULL
 
-        try:
-            osr = osr_from_crs(self)
-            if OSRAutoIdentifyEPSG(osr) == 0:
-                epsg_code = OSRGetAuthorityCode(osr, NULL)
-                return int(epsg_code.decode('utf-8'))
-        finally:
-            _safe_osr_release(osr)
-        return None
+        with env_ctx_if_needed():
+            try:
+                osr = osr_from_crs(self)
+                if OSRAutoIdentifyEPSG(osr) == 0:
+                    epsg_code = OSRGetAuthorityCode(osr, NULL)
+                    return int(epsg_code.decode('utf-8'))
+                else:
+                    return None
+            finally:
+                _safe_osr_release(osr)
 
     @classmethod
     def from_epsg(cls, code):
@@ -137,7 +142,7 @@ class _CRS(UserDict):
         CRS
         """
         if int(code) <= 0:
-            raise ValueError("EPSG codes are positive integers")
+            raise CRSError("EPSG codes are positive integers")
         return cls(init="epsg:%s" % code, no_defs=True)
 
     @classmethod
@@ -157,7 +162,10 @@ class _CRS(UserDict):
             raise CRSError("CRS is empty or invalid: {!r}".format(s))
 
         elif s.strip().upper().startswith('EPSG:'):
-            return cls.from_epsg(s.strip().split(':')[1])
+            auth, val = s.strip().split(':')
+            if not val:
+                raise CRSError("Invalid CRS: {!r}".format(s))
+            return cls.from_epsg(val)
 
         elif '{' in s:
             # may be json, try to decode it
@@ -222,19 +230,29 @@ class _CRS(UserDict):
 
         if isinstance(s, string_types):
             b_s = s.encode('utf-8')
+            log.debug("Encoded WKT: %r", b_s)
 
         try:
-            retval = exc_wrap_int(OSRSetFromUserInput(osr, <const char *>b_s))
-            if retval:
-                _safe_osr_release(osr)
-                raise CRSError("Invalid CRS: {!r}".format(s))
+            exc_wrap_int(OSRSetFromUserInput(osr, <const char *>b_s))
+        except CPLE_NotSupportedError as exc:
+            log.debug("{}".format(exc))
         except CPLE_BaseError as exc:
             _safe_osr_release(osr)
             raise CRSError(str(exc))
 
         try:
-            OSRExportToProj4(osr, &prj)
+            exc_wrap_int(OSRExportToProj4(osr, &prj))
+        except CPLE_NotSupportedError as exc:
+            log.debug("{}".format(exc))
+
+        try:
             return cls.from_string(prj.decode('utf-8'))
+        except CRSError:
+            if OSRMorphFromESRI(osr) == 0:
+                OSRExportToProj4(osr, &prj)
+                return cls.from_string(prj.decode('utf-8'))
+            else:
+                raise
         finally:
             CPLFree(prj)
             _safe_osr_release(osr)
@@ -260,7 +278,7 @@ class _CRS(UserDict):
             return cls.from_epsg(value)
         elif isinstance(value, dict):
             return cls(**value)
-        elif isinstance(value, str):
+        elif isinstance(value, string_types):
             return cls.from_string(value)
         else:
             raise CRSError("CRS is invalid: {!r}".format(value))


=====================================
rasterio/_env.pyx
=====================================
@@ -186,6 +186,59 @@ cdef class ConfigEnv(object):
         return {k: get_gdal_config(k) for k in self.options}
 
 
+class GDALDataFinder(object):
+    """Finds GDAL and PROJ data files"""
+
+    def search(self, prefix=None):
+        """Returns GDAL_DATA location"""
+        path = self.search_wheel(prefix or __file__)
+        if not path:
+            path = self.search_prefix(prefix or sys.prefix)
+            if not path:
+                path = self.search_debian(prefix or sys.prefix)
+        return path
+
+    def search_wheel(self, prefix=None):
+        """Check wheel location"""
+        if prefix is None:
+            prefix = __file__
+        datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "gdal_data"))
+        return datadir if os.path.exists(os.path.join(datadir, 'pcs.csv')) else None
+
+    def search_prefix(self, prefix=sys.prefix):
+        """Check sys.prefix location"""
+        datadir = os.path.join(prefix, 'share/gdal')
+        return datadir if os.path.exists(os.path.join(datadir, 'pcs.csv')) else None
+
+    def search_debian(self, prefix=sys.prefix):
+        """Check Debian locations"""
+        gdal_release_name = GDALVersionInfo("RELEASE_NAME")
+        datadir = os.path.join(prefix, 'share/gdal', '{}.{}'.format(*gdal_release_name.split('.')[:2]))
+        return datadir if os.path.exists(os.path.join(datadir, 'pcs.csv')) else None
+
+
+class PROJDataFinder(object):
+
+    def search(self, prefix=None):
+        """Returns PROJ_LIB location"""
+        path = self.search_wheel(prefix or __file__)
+        if not path:
+            path = self.search_prefix(prefix or sys.prefix)
+        return path
+
+    def search_wheel(self, prefix=None):
+        """Check wheel location"""
+        if prefix is None:
+            prefix = __file__
+        datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "proj_data"))
+        return datadir if os.path.exists(datadir) else None
+
+    def search_prefix(self, prefix=sys.prefix):
+        """Check sys.prefix location"""
+        datadir = os.path.join(prefix, 'share/proj')
+        return datadir if os.path.exists(datadir) else None
+
+
 cdef class GDALEnv(ConfigEnv):
     """Configuration and driver management"""
 
@@ -210,40 +263,23 @@ cdef class GDALEnv(ConfigEnv):
 
                     if 'GDAL_DATA' not in os.environ:
 
-                        # We will try a few well-known paths, starting with the
-                        # official wheel path.
-                        whl_datadir = os.path.abspath(
-                            os.path.join(os.path.dirname(__file__), "gdal_data"))
-                        fhs_share_datadir = os.path.join(sys.prefix, 'share/gdal')
-
-                        # Debian supports multiple GDAL installs.
-                        gdal_release_name = GDALVersionInfo("RELEASE_NAME")
-                        deb_share_datadir = os.path.join(
-                            fhs_share_datadir,
-                            "{}.{}".format(*gdal_release_name.split('.')[:2]))
-
-                        # If we find GDAL data at the well-known paths, we will
-                        # add a GDAL_DATA key to the config options dict.
-                        if os.path.exists(os.path.join(whl_datadir, 'pcs.csv')):
-                            self.update_config_options(GDAL_DATA=whl_datadir)
+                        path = GDALDataFinder().search()
 
-                        elif os.path.exists(os.path.join(deb_share_datadir, 'pcs.csv')):
-                            self.update_config_options(GDAL_DATA=deb_share_datadir)
+                        if path:
+                            log.debug("GDAL data found in %r", path)
+                            self.update_config_options(GDAL_DATA=path)
 
-                        elif os.path.exists(os.path.join(fhs_share_datadir, 'pcs.csv')):
-                            self.update_config_options(GDAL_DATA=fhs_share_datadir)
+                    else:
+                        self.update_config_options(GDAL_DATA=os.environ['GDAL_DATA'])
 
                     if 'PROJ_LIB' not in os.environ:
 
-                        whl_datadir = os.path.abspath(
-                            os.path.join(os.path.dirname(__file__), 'proj_data'))
-                        share_datadir = os.path.join(sys.prefix, 'share/proj')
+                        path = PROJDataFinder().search()
 
-                        if os.path.exists(whl_datadir):
-                            os.environ['PROJ_LIB'] = whl_datadir
+                        if path:
+                            log.debug("PROJ data found in %r", path)
+                            os.environ['PROJ_LIB'] = path
 
-                        elif os.path.exists(share_datadir):
-                            os.environ['PROJ_LIB'] = share_datadir
 
                     if driver_count() == 0:
                         CPLPopErrorHandler()


=====================================
rasterio/_io.pyx
=====================================
@@ -1296,6 +1296,30 @@ cdef class DatasetWriterBase(DatasetReaderBase):
         else:
             raise ValueError("One description for each band is required")
 
+    def _set_all_scales(self, value):
+        """Supports the scales property setter"""
+        cdef GDALRasterBandH hband = NULL
+        if len(value) == self.count:
+            for (bidx, val) in zip(self.indexes, value):
+                hband = self.band(bidx)
+                GDALSetRasterScale(hband, val)
+            # Invalidate cached scales.
+            self._scales = ()
+        else:
+            raise ValueError("One scale for each band is required")
+
+    def _set_all_offsets(self, value):
+        """Supports the offsets property setter"""
+        cdef GDALRasterBandH hband = NULL
+        if len(value) == self.count:
+            for (bidx, val) in zip(self.indexes, value):
+                hband = self.band(bidx)
+                GDALSetRasterOffset(hband, val)
+            # Invalidate cached offsets.
+            self._offsets = ()
+        else:
+            raise ValueError("One offset for each band is required")
+
     def _set_all_units(self, value):
         """Supports the units property setter"""
         # require that we have a unit for every band.


=====================================
rasterio/env.py
=====================================
@@ -338,6 +338,29 @@ def delenv():
     local._env = None
 
 
+class NullContextManager(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __exit__(self, *args):
+        pass
+
+
+def env_ctx_if_needed():
+    """Return an Env if one does not exist
+
+    Returns
+    -------
+    Env or a do-nothing context manager
+
+    """
+    if local._env:
+        return NullContextManager()
+    else:
+        return Env.from_defaults()
+
+
 def ensure_env(f):
     """A decorator that ensures an env exists before a function
     calls any GDAL C functions."""


=====================================
rasterio/gdal.pxi
=====================================
@@ -83,6 +83,7 @@ cdef extern from "ogr_srs_api.h" nogil:
     int OCTTransform(OGRCoordinateTransformationH ct, int nCount, double *x,
                      double *y, double *z)
     int OSRAutoIdentifyEPSG(OGRSpatialReferenceH srs)
+    int OSRMorphFromESRI(OGRSpatialReferenceH srs)
     void OSRCleanup()
     OGRSpatialReferenceH OSRClone(OGRSpatialReferenceH srs)
     int OSRExportToProj4(OGRSpatialReferenceH srs, char **params)
@@ -265,6 +266,10 @@ cdef extern from "gdal.h" nogil:
     char** GDALGetFileList(GDALDatasetH hDS)
     CPLErr GDALCopyDatasetFiles (GDALDriverH hDriver, const char * pszNewName, const char * pszOldName)
 
+    double GDALGetRasterScale(GDALRasterBandH hBand, int * pbSuccess)
+    double GDALGetRasterOffset(GDALRasterBandH hBand, int * pbSuccess)
+    CPLErr GDALSetRasterScale(GDALRasterBandH hBand, double dfNewScale)
+    CPLErr GDALSetRasterOffset(GDALRasterBandH hBand, double dfNewOffset)
 
 cdef extern from "ogr_api.h" nogil:
 


=====================================
rasterio/path.py
=====================================
@@ -17,13 +17,14 @@ SCHEMES = {
     's3': 's3',
     'tar': 'tar',
     'zip': 'zip',
-    'file': 'file'
+    'file': 'file',
+    'oss': 'oss'
 }
 
 CURLSCHEMES = set([k for k, v in SCHEMES.items() if v == 'curl'])
 
 # TODO: extend for other cloud plaforms.
-REMOTESCHEMES = set([k for k, v in SCHEMES.items() if v in ('curl', 's3')])
+REMOTESCHEMES = set([k for k, v in SCHEMES.items() if v in ('curl', 's3', 'oss')])
 
 
 class Path(object):


=====================================
rasterio/session.py
=====================================
@@ -92,6 +92,9 @@ class Session(object):
         elif path.scheme == "s3" or "amazonaws.com" in path.path:
             return AWSSession
 
+        elif path.scheme == "oss" or "aliyuncs.com" in path.path:
+            return OSSSession
+
         # This factory can be extended to other cloud providers here.
         # elif path.scheme == "cumulonimbus":  # for example.
         #     return CumulonimbusSession(*args, **kwargs)
@@ -257,3 +260,62 @@ class AWSSession(Session):
             return {'AWS_NO_SIGN_REQUEST': 'YES'}
         else:
             return {k.upper(): v for k, v in self.credentials.items()}
+
+
+class OSSSession(Session):
+    """Configures access to secured resources stored in Alibaba Cloud OSS.
+    """
+    def __init__(self, oss_access_key_id, oss_secret_access_key, oss_endpoint='oss-us-east-1.aliyuncs.com'):
+        """Create new Alibaba Cloud OSS session
+
+        Parameters
+        ----------
+        oss_access_key_id: string
+            An access key id
+        oss_secret_access_key: string
+            An secret access key
+        oss_endpoint: string, default 'oss-us-east-1.aliyuncs.com'
+            the region attached to the bucket
+        """
+
+        self._creds = {
+            "oss_access_key_id": oss_access_key_id,
+            "oss_secret_access_key": oss_secret_access_key,
+            "oss_endpoint": oss_endpoint
+        }
+    
+    @classmethod
+    def hascreds(cls, config):
+        """Determine if the given configuration has proper credentials
+
+        Parameters
+        ----------
+        cls : class
+            A Session class.
+        config : dict
+            GDAL configuration as a dict.
+
+        Returns
+        -------
+        bool
+
+        """
+        return 'OSS_ACCESS_KEY_ID' in config and 'OSS_SECRET_ACCESS_KEY' in config
+
+    @property
+    def credentials(self):
+        """The session credentials as a dict"""
+        return self._creds
+
+    def get_credential_options(self):
+        """Get credentials as GDAL configuration options
+
+        Returns
+        -------
+        dict
+
+        """
+        return {k.upper(): v for k, v in self.credentials.items()}
+
+    
+


=====================================
tests/conftest.py
=====================================
@@ -48,18 +48,16 @@ def runner():
 
 
 @pytest.fixture(scope='function')
-def data():
+def data(tmpdir):
     """A temporary directory containing a copy of the files in data."""
-    tmpdir = py.test.ensuretemp('tests/data')
     for filename in test_files:
         shutil.copy(filename, str(tmpdir))
     return tmpdir
 
 
 @pytest.fixture(scope='function')
-def red_green():
+def red_green(tmpdir):
     """A temporary directory containing copies of red.tif, green.tif."""
-    tmpdir = py.test.ensuretemp('tests/data')
     for filename in ['tests/data/red.tif', 'tests/data/red.tif.ovr', 'tests/data/green.tif', 'tests/data/green.tif.ovr']:
         shutil.copy(filename, str(tmpdir))
     return tmpdir
@@ -560,15 +558,15 @@ def path_alpha_tif(data_dir):
 
 
 @pytest.fixture(scope='session')
-def path_zip_file():
+def path_zip_file(data_dir):
     """Creates ``coutwildrnp.zip`` if it does not exist and returns
     the absolute file path."""
-    path = '{}/white-gemini-iv.zip'.format(data_dir())
+    path = '{}/white-gemini-iv.zip'.format(data_dir)
     if not os.path.exists(path):
         with zipfile.ZipFile(path, 'w') as zip:
             for filename in ['white-gemini-iv.vrt',
                              '389225main_sw_1965_1024.jpg']:
-                zip.write(os.path.join(data_dir(), filename), filename)
+                zip.write(os.path.join(data_dir, filename), filename)
     return path
 
 


=====================================
tests/data/compdcs.vrt
=====================================
@@ -0,0 +1,37 @@
+<VRTDataset rasterXSize="1024" rasterYSize="768">
+<SRS>COMPD_CS["unknown",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],VERT_CS["unknown",VERT_DATUM["unknown",2005],UNIT["metre",1.0,AUTHORITY["EPSG","9001"]],AXIS["Up",UP]]]</SRS>
+<GCPList Projection="COMPD_CS["unknown",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],VERT_CS["unknown",VERT_DATUM["unknown",2005],UNIT["metre",1.0,AUTHORITY["EPSG","9001"]],AXIS["Up",UP]]]">
+  <GCP Id="1" Info="a" Pixel="0.0" Line="0.0" X="157168.0" Y="2818194.0" Z="0.0" />
+  <GCP Id="2" Info="b" Pixel="1024.0" Line="0.0" X="338615.0" Y="2786088.0" Z="0.0" />
+  <GCP Id="3" Info="c" Pixel="0.0" Line="768.0" X="116792" Y="2651340.0" Z="0.0" />
+</GCPList>
+  <VRTRasterBand dataType="Byte" band="1">
+    <ColorInterp>Gray</ColorInterp>
+    <SimpleSource>
+      <SourceFilename relativeToVRT="1">389225main_sw_1965_1024.jpg</SourceFilename>
+      <SourceBand>1</SourceBand>
+      <SrcRect xOff="0" yOff="0" xSize="1024" ySize="768"/>
+      <DstRect xOff="0" yOff="0" xSize="1024" ySize="768"/>
+    </SimpleSource>
+  </VRTRasterBand>
+  <VRTRasterBand dataType="Byte" band="2">
+    <ColorInterp>Gray</ColorInterp>
+    <SimpleSource>
+      <SourceFilename relativeToVRT="1">389225main_sw_1965_1024.jpg</SourceFilename>
+      <SourceBand>2</SourceBand>
+      <SrcRect xOff="0" yOff="0" xSize="1024" ySize="768"/>
+      <DstRect xOff="0" yOff="0" xSize="1024" ySize="768"/>
+    </SimpleSource>
+  </VRTRasterBand>
+  <VRTRasterBand dataType="Byte" band="3">
+    <ColorInterp>Gray</ColorInterp>
+    <SimpleSource>
+      <SourceFilename relativeToVRT="1">389225main_sw_1965_1024.jpg</SourceFilename>
+      <SourceBand>3</SourceBand>
+      <SrcRect xOff="0" yOff="0" xSize="1024" ySize="768"/>
+      <DstRect xOff="0" yOff="0" xSize="1024" ySize="768"/>
+    </SimpleSource>
+  </VRTRasterBand>
+
+</VRTDataset>
+


=====================================
tests/data/test_esri_wkt.tif
=====================================
Binary files /dev/null and b/tests/data/test_esri_wkt.tif differ


=====================================
tests/test__env.py
=====================================
@@ -0,0 +1,125 @@
+"""Tests of _env util module"""
+
+import pytest
+
+from rasterio._env import GDALDataFinder, PROJDataFinder
+
+from .conftest import gdal_version
+
+
+ at pytest.fixture
+def mock_wheel(tmpdir):
+    """A fake rasterio wheel"""
+    moduledir = tmpdir.mkdir("rasterio")
+    moduledir.ensure("__init__,py")
+    moduledir.ensure("_env.py")
+    moduledir.ensure("gdal_data/pcs.csv")
+    moduledir.ensure("proj_data/epsg")
+    return moduledir
+
+
+ at pytest.fixture
+def mock_fhs(tmpdir):
+    """A fake FHS system"""
+    tmpdir.ensure("share/gdal/pcs.csv")
+    tmpdir.ensure("share/proj/epsg")
+    return tmpdir
+
+
+ at pytest.fixture
+def mock_debian(tmpdir):
+    """A fake Debian multi-install system"""
+    tmpdir.ensure("share/gdal/1.11/pcs.csv")
+    tmpdir.ensure("share/gdal/2.0/pcs.csv")
+    tmpdir.ensure("share/gdal/2.1/pcs.csv")
+    tmpdir.ensure("share/gdal/2.2/pcs.csv")
+    tmpdir.ensure("share/gdal/2.3/pcs.csv")
+    tmpdir.ensure("share/gdal/2.4/pcs.csv")
+    tmpdir.ensure("share/proj/epsg")
+    return tmpdir
+
+
+def test_search_wheel_gdal_data_failure(tmpdir):
+    """Fail to find GDAL data in a non-wheel"""
+    finder = GDALDataFinder()
+    assert not finder.search_wheel(str(tmpdir))
+
+
+def test_search_wheel_gdal_data(mock_wheel):
+    """Find GDAL data in a wheel"""
+    finder = GDALDataFinder()
+    assert finder.search_wheel(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("gdal_data"))
+
+
+def test_search_prefix_gdal_data_failure(tmpdir):
+    """Fail to find GDAL data in a bogus prefix"""
+    finder = GDALDataFinder()
+    assert not finder.search_prefix(str(tmpdir))
+
+
+def test_search_prefix_gdal_data(mock_fhs):
+    """Find GDAL data under prefix"""
+    finder = GDALDataFinder()
+    assert finder.search_prefix(str(mock_fhs)) == str(mock_fhs.join("share/gdal"))
+
+
+def test_search_debian_gdal_data_failure(tmpdir):
+    """Fail to find GDAL data in a bogus Debian location"""
+    finder = GDALDataFinder()
+    assert not finder.search_debian(str(tmpdir))
+
+
+def test_search_debian_gdal_data(mock_debian):
+    """Find GDAL data under Debian locations"""
+    finder = GDALDataFinder()
+    assert finder.search_debian(str(mock_debian)) == str(mock_debian.join("share/gdal/{}".format(str(gdal_version))))
+
+
+def test_search_gdal_data_wheel(mock_wheel):
+    finder = GDALDataFinder()
+    assert finder.search(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("gdal_data"))
+
+
+def test_search_gdal_data_fhs(mock_fhs):
+    finder = GDALDataFinder()
+    assert finder.search(str(mock_fhs)) == str(mock_fhs.join("share/gdal"))
+
+
+def test_search_gdal_data_debian(mock_debian):
+    """Find GDAL data under Debian locations"""
+    finder = GDALDataFinder()
+    assert finder.search(str(mock_debian)) == str(mock_debian.join("share/gdal/{}".format(str(gdal_version))))
+
+
+def test_search_wheel_proj_data_failure(tmpdir):
+    """Fail to find GDAL data in a non-wheel"""
+    finder = PROJDataFinder()
+    assert not finder.search_wheel(str(tmpdir))
+
+
+def test_search_wheel_proj_data(mock_wheel):
+    """Find GDAL data in a wheel"""
+    finder = PROJDataFinder()
+    assert finder.search_wheel(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("proj_data"))
+
+
+def test_search_prefix_proj_data_failure(tmpdir):
+    """Fail to find GDAL data in a bogus prefix"""
+    finder = PROJDataFinder()
+    assert not finder.search_prefix(str(tmpdir))
+
+
+def test_search_prefix_proj_data(mock_fhs):
+    """Find GDAL data under prefix"""
+    finder = PROJDataFinder()
+    assert finder.search_prefix(str(mock_fhs)) == str(mock_fhs.join("share/proj"))
+
+
+def test_search_proj_data_wheel(mock_wheel):
+    finder = PROJDataFinder()
+    assert finder.search(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("proj_data"))
+
+
+def test_search_proj_data_fhs(mock_fhs):
+    finder = PROJDataFinder()
+    assert finder.search(str(mock_fhs)) == str(mock_fhs.join("share/proj"))


=====================================
tests/test_crs.py
=====================================
@@ -24,6 +24,22 @@ def test_read_epsg(tmpdir):
         assert src.crs.to_dict() == {'init': 'epsg:32618'}
 
 
+def test_read_esri_wkt(tmpdir):
+    with rasterio.open('tests/data/test_esri_wkt.tif') as src:
+        assert src.crs.to_dict() == {
+            'datum': 'NAD83',
+            'lat_0': 23,
+            'lat_1': 29.5,
+            'lat_2': 45.5,
+            'lon_0': -96,
+            'no_defs': True,
+            'proj': 'aea',
+            'units': 'm',
+            'x_0': 0,
+            'y_0': 0,
+        }
+
+
 def test_read_epsg3857(tmpdir):
     tiffname = str(tmpdir.join('lol.tif'))
     subprocess.call([
@@ -274,3 +290,40 @@ def test_from_wkt_invalid():
 
 def test_from_user_input_epsg():
     assert 'init' in CRS.from_user_input('EPSG:4326')
+
+
+def test_from_esri_wkt():
+    projection_string = (
+        'PROJCS["USA_Contiguous_Albers_Equal_Area_Conic_USGS_version",'
+        'GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",'
+        'SPHEROID["GRS_1980",6378137.0,298.257222101]],'
+        'PRIMEM["Greenwich",0.0],'
+        'UNIT["Degree",0.0174532925199433]],'
+        'PROJECTION["Albers"],'
+        'PARAMETER["false_easting",0.0],'
+        'PARAMETER["false_northing",0.0],'
+        'PARAMETER["central_meridian",-96.0],'
+        'PARAMETER["standard_parallel_1",29.5],'
+        'PARAMETER["standard_parallel_2",45.5],'
+        'PARAMETER["latitude_of_origin",23.0],'
+        'UNIT["Meter",1.0],'
+        'VERTCS["NAVD_1988",'
+        'VDATUM["North_American_Vertical_Datum_1988"],'
+        'PARAMETER["Vertical_Shift",0.0],'
+        'PARAMETER["Direction",1.0],UNIT["Centimeter",0.01]]]')
+    proj_crs_str = CRS.from_string(projection_string)
+    proj_crs_wkt = CRS.from_wkt(projection_string)
+    assert proj_crs_str.to_string() == proj_crs_wkt.to_string()
+    assert proj_crs_str.to_string() == \
+        ("+datum=NAD83 +lat_0=23 +lat_1=29.5 +lat_2=45.5 "
+         "+lon_0=-96 +no_defs +proj=aea +units=m +x_0=0 +y_0=0")
+
+
+def test_compound_crs():
+    wkt = """COMPD_CS["unknown",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],VERT_CS["unknown",VERT_DATUM["unknown",2005],UNIT["metre",1.0,AUTHORITY["EPSG","9001"]],AXIS["Up",UP]]]"""
+    assert CRS.from_wkt(wkt).wkt.startswith('GEOGCS["WGS 84"')
+
+
+def test_dataset_compound_crs():
+    with rasterio.open("tests/data/compdcs.vrt") as dataset:
+        assert dataset.crs.wkt.startswith('GEOGCS["WGS 84"')


=====================================
tests/test_env.py
=====================================
@@ -2,18 +2,20 @@
 # Collected here to make them easier to skip/xfail.
 
 import logging
+import os
 import sys
 
 import boto3
 import pytest
 
 import rasterio
+from rasterio import _env
 from rasterio._env import del_gdal_config, get_gdal_config, set_gdal_config
 from rasterio.env import Env, defenv, delenv, getenv, setenv, ensure_env, ensure_env_credentialled
 from rasterio.env import GDALVersion, require_gdal_version
 from rasterio.errors import EnvError, RasterioIOError, GDALVersionError
 from rasterio.rio.main import main_group
-from rasterio.session import AWSSession
+from rasterio.session import AWSSession, OSSSession
 
 from .conftest import requires_gdal21
 
@@ -112,6 +114,42 @@ def test_ensure_env_decorator(gdalenv):
     assert f() is True
 
 
+def test_ensure_env_decorator_sets_gdal_data(gdalenv, monkeypatch):
+    """ensure_env finds GDAL from environment"""
+    @ensure_env
+    def f():
+        return getenv()['GDAL_DATA']
+
+    monkeypatch.setenv('GDAL_DATA', '/lol/wut')
+    assert f() == '/lol/wut'
+
+
+def test_ensure_env_decorator_sets_gdal_data_prefix(gdalenv, monkeypatch, tmpdir):
+    """ensure_env finds GDAL data under a prefix"""
+    @ensure_env
+    def f():
+        return getenv()['GDAL_DATA']
+
+    tmpdir.ensure("share/gdal/pcs.csv")
+    monkeypatch.delenv('GDAL_DATA', raising=False)
+    monkeypatch.setattr(sys, 'prefix', str(tmpdir))
+
+    assert f() == str(tmpdir.join("share/gdal"))
+
+
+def test_ensure_env_decorator_sets_gdal_data_wheel(gdalenv, monkeypatch, tmpdir):
+    """ensure_env finds GDAL data in a wheel"""
+    @ensure_env
+    def f():
+        return getenv()['GDAL_DATA']
+
+    tmpdir.ensure("gdal_data/pcs.csv")
+    monkeypatch.delenv('GDAL_DATA', raising=False)
+    monkeypatch.setattr(_env, '__file__', str(tmpdir.join(os.path.basename(_env.__file__))))
+
+    assert f() == str(tmpdir.join("gdal_data"))
+
+
 def test_ensure_env_credentialled_decorator(monkeypatch, gdalenv):
     """Credentialization is ensured by wrapper"""
     monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'id')
@@ -730,6 +768,11 @@ def test_require_gdal_version_chaining():
 def test_rio_env_no_credentials(tmpdir, monkeypatch, runner):
     """Confirm that we can get drivers without any credentials"""
     credentials_file = tmpdir.join('credentials')
+    credentials_file.write("""
+[default]
+aws_secret_access_key = foo
+aws_access_key_id = bar
+""")
     monkeypatch.setenv('AWS_SHARED_CREDENTIALS_FILE', str(credentials_file))
     monkeypatch.delenv('AWS_ACCESS_KEY_ID', raising=False)
     # Assert that we don't have any AWS credentials by accident.
@@ -757,3 +800,16 @@ def test_nested_credentials(monkeypatch):
         gdalenv = fake_opener('s3://foo/bar')
         assert gdalenv['AWS_ACCESS_KEY_ID'] == 'foo'
         assert gdalenv['AWS_SECRET_ACCESS_KEY'] == 'bar'
+        
+
+def test_oss_session_credentials(gdalenv):
+    """Create an Env with a oss session."""
+    oss_session = OSSSession(
+        oss_access_key_id='id', 
+        oss_secret_access_key='key', 
+        oss_endpoint='null-island-1')
+    with rasterio.env.Env(session=oss_session) as s:
+        s.credentialize()
+        assert getenv()['OSS_ACCESS_KEY_ID'] == 'id'
+        assert getenv()['OSS_SECRET_ACCESS_KEY'] == 'key'
+        assert getenv()['OSS_ENDPOINT'] == 'null-island-1'


=====================================
tests/test_rio_warp.py
=====================================
@@ -46,7 +46,7 @@ def test_dst_crs_error_epsg(runner, tmpdir):
     result = runner.invoke(main_group, [
         'warp', srcname, outputname, '--dst-crs', 'EPSG:'])
     assert result.exit_code == 2
-    assert 'for dst_crs: invalid literal for int()' in result.output
+    assert "for dst_crs: Invalid CRS:" in result.output
 
 
 def test_dst_crs_error_epsg_2(runner, tmpdir):


=====================================
tests/test_scale_offset.py
=====================================
@@ -0,0 +1,48 @@
+import pytest
+
+import rasterio
+from rasterio.profiles import default_gtiff_profile
+
+
+def test_set_scales(tmpdir):
+    """Scales can be set when dataset is open"""
+    tmptiff = str(tmpdir.join('test.tif'))
+    with rasterio.open(
+            tmptiff, 'w', count=3, height=256, width=256,
+            **default_gtiff_profile) as dst:
+        assert dst.scales == (1.0,) * 3
+        dst.scales = [0.1] * 3
+        assert dst.scales == (0.1,) * 3
+
+
+ at pytest.mark.parametrize('value', [[0.1], [2.0] * 3, []])
+def test_set_scales_error(tmpdir, value):
+    """Number of values must match band count"""
+    tmptiff = str(tmpdir.join('test.tif'))
+    with rasterio.open(
+            tmptiff, 'w', count=2, height=256, width=256,
+            **default_gtiff_profile) as dst:
+        with pytest.raises(ValueError):
+            dst.scales = value
+
+
+def test_set_offsets(tmpdir):
+    """Scales can be set when dataset is open"""
+    tmptiff = str(tmpdir.join('test.tif'))
+    with rasterio.open(
+            tmptiff, 'w', count=3, height=256, width=256,
+            **default_gtiff_profile) as dst:
+        assert dst.offsets == (0.0,) * 3
+        dst.offsets = [0.1] * 3
+        assert dst.offsets == (0.1,) * 3
+
+
+ at pytest.mark.parametrize('value', [[0.1], [2.0] * 3, []])
+def test_set_offsets_error(tmpdir, value):
+    """Number of values must match band count"""
+    tmptiff = str(tmpdir.join('test.tif'))
+    with rasterio.open(
+            tmptiff, 'w', count=2, height=256, width=256,
+            **default_gtiff_profile) as dst:
+        with pytest.raises(ValueError):
+            dst.offsets = value


=====================================
tests/test_session.py
=====================================
@@ -2,7 +2,7 @@
 
 import pytest
 
-from rasterio.session import DummySession, AWSSession, Session
+from rasterio.session import DummySession, AWSSession, Session, OSSSession
 
 
 def test_dummy_session():
@@ -114,3 +114,22 @@ def test_requester_pays():
     sesh = AWSSession(aws_access_key_id='foo', aws_secret_access_key='bar', requester_pays=True)
     assert sesh._session
     assert sesh.get_credential_options()['AWS_REQUEST_PAYER'] == 'requester'
+
+
+def test_oss_session_class():
+    """OSSSession works"""
+    oss_session = OSSSession(
+        oss_access_key_id='foo', 
+        oss_secret_access_key='bar', 
+        oss_endpoint='null-island-1')
+    assert oss_session._creds
+    assert oss_session.get_credential_options()['OSS_ACCESS_KEY_ID'] == 'foo'
+    assert oss_session.get_credential_options()['OSS_SECRET_ACCESS_KEY'] == 'bar'
+
+
+def test_session_factory_oss_kwargs():
+    """Get an OSSSession for oss:// paths with keywords"""
+    sesh = Session.from_path("oss://lol/wut", oss_access_key_id='foo', oss_secret_access_key='bar')
+    assert isinstance(sesh, OSSSession)
+    assert sesh.get_credential_options()['OSS_ACCESS_KEY_ID'] == 'foo'
+    assert sesh.get_credential_options()['OSS_SECRET_ACCESS_KEY'] == 'bar'


=====================================
tests/test_warp.py
=====================================
@@ -179,6 +179,43 @@ def test_transform_bounds():
         )
 
 
+def test_transform_bounds__esri_wkt():
+    left, bottom, right, top = \
+        (-78.95864996545055, 23.564991210854686,
+         -76.57492370013823, 25.550873767433984)
+    dst_projection_string = (
+        'PROJCS["USA_Contiguous_Albers_Equal_Area_Conic_USGS_version",'
+        'GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",'
+        'SPHEROID["GRS_1980",6378137.0,298.257222101]],'
+        'PRIMEM["Greenwich",0.0],'
+        'UNIT["Degree",0.0174532925199433]],'
+        'PROJECTION["Albers"],'
+        'PARAMETER["false_easting",0.0],'
+        'PARAMETER["false_northing",0.0],'
+        'PARAMETER["central_meridian",-96.0],'
+        'PARAMETER["standard_parallel_1",29.5],'
+        'PARAMETER["standard_parallel_2",45.5],'
+        'PARAMETER["latitude_of_origin",23.0],'
+        'UNIT["Meter",1.0],'
+        'VERTCS["NAVD_1988",'
+        'VDATUM["North_American_Vertical_Datum_1988"],'
+        'PARAMETER["Vertical_Shift",0.0],'
+        'PARAMETER["Direction",1.0],UNIT["Centimeter",0.01]]]')
+    assert np.allclose(
+        transform_bounds({"init": "EPSG:4326"},
+                         dst_projection_string,
+                         left,
+                         bottom,
+                         right,
+                         top),
+        (
+            1721263.7931814701,
+            219684.49332178483,
+            2002926.56696663,
+            479360.16562217404),
+    )
+
+
 def test_transform_bounds_densify():
     # This transform is non-linear along the edges, so densification produces
     # a different result than otherwise



View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/compare/cfc76c7c02e7ca1478fa232ed381e2149c3cf944...9f0db4a2cea45bf555108d8057358660e4da7acf

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/compare/cfc76c7c02e7ca1478fa232ed381e2149c3cf944...9f0db4a2cea45bf555108d8057358660e4da7acf
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/20181117/9afcea6c/attachment-0001.html>


More information about the Pkg-grass-devel mailing list