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

Bas Couwenberg gitlab at salsa.debian.org
Tue Sep 29 16:38:07 BST 2020



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


Commits:
a0fdde0c by Bas Couwenberg at 2020-09-29T17:21:40+02:00
New upstream version 1.1.7
- - - - -
79d965a5 by Bas Couwenberg at 2020-09-29T17:22:18+02:00
Update upstream source from tag 'upstream/1.1.7'

Update to upstream version '1.1.7'
with Debian dir dbf683f93388a55b419909b4a67acb6ea5a9ec03
- - - - -
36cf2320 by Bas Couwenberg at 2020-09-29T17:22:43+02:00
New upstream release.

- - - - -
eadf201f by Bas Couwenberg at 2020-09-29T17:23:39+02:00
Set distribution to unstable.

- - - - -


19 changed files:

- CHANGES.txt
- debian/changelog
- rasterio/__init__.py
- rasterio/_base.pyx
- rasterio/_crs.pyx
- rasterio/_err.pxd
- rasterio/_err.pyx
- rasterio/_io.pyx
- rasterio/_warp.pyx
- rasterio/crs.py
- rasterio/errors.py
- rasterio/gdal.pxi
- rasterio/io.py
- rasterio/merge.py
- rasterio/rio/merge.py
- tests/test_boundless_read.py
- tests/test_crs.py
- tests/test_rio_merge.py
- tests/test_warpedvrt.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,21 @@
 Changes
 =======
 
+1.1.7 (2020-09-29)
+------------------
+
+- Add missing methods needed to determine whether GDAL treats a CRS as lat/long
+  or northing/easting (#1943).
+- Wrap calls to GDALChecksumImage so that errors set by GDAL are propagated to
+  Python as a RasterioIOError.
+- Raise RasterioDeprecationWarning when a dataset opened in modes other than
+  'r' is given to the WarpedVRT constructor.
+- Base RasterioDeprecationWarning on FutureWarning, following the
+  recommendation of PEP 565.
+- Fix a segmentation fault that occurs when a WarpedVRT closes after the
+  dataset it references has been previously closed (#2001).
+- Add resampling option to merge and rio-merge (#1996).
+
 1.1.6 (2020-09-14)
 ------------------
 


=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+rasterio (1.1.7-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Tue, 29 Sep 2020 17:23:23 +0200
+
 rasterio (1.1.6-1) unstable; urgency=medium
 
   * Team upload.


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


=====================================
rasterio/_base.pyx
=====================================
@@ -14,7 +14,7 @@ from libc.string cimport strncmp
 from rasterio._err import (
     GDALError, CPLE_BaseError, CPLE_IllegalArgError, CPLE_OpenFailedError,
     CPLE_NotSupportedError)
-from rasterio._err cimport exc_wrap_pointer, exc_wrap_int
+from rasterio._err cimport exc_wrap_pointer, exc_wrap_int, exc_wrap
 from rasterio._shim cimport open_dataset, osr_get_name, osr_set_traditional_axis_mapping_strategy
 
 from rasterio.compat import string_types
@@ -309,8 +309,12 @@ cdef class DatasetBase(object):
 
         return [gt[i] for i in range(6)]
 
+    def start(self):
+        """Start the dataset's life cycle"""
+        pass
+
     def stop(self):
-        """Ends the dataset's life cycle"""
+        """Close the GDAL dataset handle"""
         if self._hds != NULL:
             refcount = GDALDereferenceDataset(self._hds)
             if refcount == 0:
@@ -1212,7 +1216,11 @@ cdef class DatasetBase(object):
             yoff = window.row_off
             height = window.height
 
-        return GDALChecksumImage(band, xoff, yoff, width, height)
+        try:
+            return exc_wrap(GDALChecksumImage(band, xoff, yoff, width, height))
+        except CPLE_BaseError as err:
+            raise RasterioIOError(str(err))
+
 
     def get_gcps(self):
         """Get GCPs and their associated CRS."""


=====================================
rasterio/_crs.pyx
=====================================
@@ -28,6 +28,48 @@ def gdal_version():
     return info_b.decode("utf-8")
 
 
+def _epsg_treats_as_latlong(input_crs):
+    """Test if the CRS is in latlon order
+
+    Parameters
+    ----------
+    input_crs : _CRS
+        rasterio _CRS object
+
+    Returns
+    -------
+    bool
+
+    """
+    cdef _CRS crs = input_crs
+
+    try:
+        return bool(OSREPSGTreatsAsLatLong(crs._osr) == 1)
+    except CPLE_BaseError as exc:
+        raise CRSError("{}".format(exc))
+
+
+def _epsg_treats_as_northingeasting(input_crs):
+    """Test if the CRS should be treated as having northing/easting coordinate ordering
+
+    Parameters
+    ----------
+    input_crs : _CRS
+        rasterio _CRS object
+
+    Returns
+    -------
+    bool
+
+    """
+    cdef _CRS crs = input_crs
+
+    try:
+        return bool(OSREPSGTreatsAsNorthingEasting(crs._osr) == 1)
+    except CPLE_BaseError as exc:
+        raise CRSError("{}".format(exc))
+
+
 cdef class _CRS(object):
     """Cython extension class"""
 
@@ -170,24 +212,43 @@ cdef class _CRS(object):
         int
 
         """
-        cdef OGRSpatialReferenceH osr = NULL
+        if self._epsg is not None:
+            return self._epsg
+
+        auth = self.to_authority()
+        if auth is None:
+            return None
+        name, code = auth
+        if name.upper() == "EPSG":
+            self._epsg = int(code)
+        return self._epsg
 
-        if self._epsg is None:
-            try:
-                osr = exc_wrap_pointer(OSRClone(self._osr))
-                exc_wrap_ogrerr(OSRMorphFromESRI(osr))
-                if OSRAutoIdentifyEPSG(osr) == 0:
-                    epsg_code = OSRGetAuthorityCode(osr, NULL)
-                    if epsg_code != NULL:
-                        self._epsg = int(epsg_code.decode('utf-8'))
-                    else:
-                        self._epsg = None
-                else:
-                    self._epsg = None
-            finally:
-                _safe_osr_release(osr)
+    def to_authority(self):
+        """The authority name and code of the CRS
 
-        return self._epsg
+        Returns
+        -------
+        (str, str) or None
+
+        """
+        cdef OGRSpatialReferenceH osr = NULL
+        code = None
+        name = None
+        try:
+            osr = exc_wrap_pointer(OSRClone(self._osr))
+            exc_wrap_ogrerr(OSRMorphFromESRI(osr))
+            if OSRAutoIdentifyEPSG(osr) == 0:
+                c_code = OSRGetAuthorityCode(osr, NULL)
+                c_name = OSRGetAuthorityName(osr, NULL)
+                if c_code != NULL and c_name != NULL:
+                    code = c_code.decode('utf-8')
+                    name = c_name.decode('utf-8')
+        finally:
+            _safe_osr_release(osr)
+
+        if None not in (name, code):
+            return (name, code)
+        return None
 
     @staticmethod
     def from_epsg(code):


=====================================
rasterio/_err.pxd
=====================================
@@ -9,6 +9,7 @@ cdef extern from "ogr_core.h":
     ctypedef int OGRErr
 
 
+cdef int exc_wrap(int retval) except -1
 cdef int exc_wrap_int(int retval) except -1
 cdef OGRErr exc_wrap_ogrerr(OGRErr retval) except -1
 cdef void *exc_wrap_pointer(void *ptr) except NULL


=====================================
rasterio/_err.pyx
=====================================
@@ -139,7 +139,7 @@ class GDALError(IntEnum):
 
 cdef inline object exc_check():
     """Checks GDAL error stack for fatal or non-fatal errors
-    
+
     Returns
     -------
     An Exception, SystemExit, or None
@@ -171,6 +171,14 @@ cdef inline object exc_check():
         return
 
 
+cdef int exc_wrap(int retval) except -1:
+    """Wrap a GDAL function that returns int without checking the retval"""
+    exc = exc_check()
+    if exc:
+        raise exc
+    return retval
+
+
 cdef int exc_wrap_int(int err) except -1:
     """Wrap a GDAL/OGR function that returns CPLErr or OGRErr (int)
 


=====================================
rasterio/_io.pyx
=====================================
@@ -65,28 +65,28 @@ def _delete_dataset_if_exists(path):
     """
 
     cdef GDALDatasetH h_dataset = NULL
-    cdef const char *c_path = NULL
-
-    b_path = path.encode('utf-8')
-    c_path = b_path
-
-    with silence_errors():
-        with nogil:
-            h_dataset = GDALOpen(c_path, <GDALAccess>0)
+    cdef GDALDriverH h_driver = NULL
+    cdef const char *path_c = NULL
 
     try:
-        h_dataset = exc_wrap_pointer(h_dataset)
+        h_dataset = open_dataset(path, 0x40, None, None, None)
 
-    except (CPLE_OpenFailedError, CPLE_AWSObjectNotFoundError):
+    except (CPLE_OpenFailedError, CPLE_AWSObjectNotFoundError) as exc:
         log.debug(
-            "Skipped delete for overwrite. Dataset does not exist: %s", path)
+            "Skipped delete for overwrite. Dataset does not exist: %r", path)
 
     else:
         h_driver = GDALGetDatasetDriver(h_dataset)
+        GDALClose(h_dataset)
+        h_dataset = NULL
 
         if h_driver != NULL:
+            path_b = path.encode("utf-8")
+            path_c = path_b
             with nogil:
-                GDALDeleteDataset(h_driver, c_path)
+                err = GDALDeleteDataset(h_driver, path_c)
+            exc_wrap_int(err)
+
 
     finally:
         if h_dataset != NULL:
@@ -839,7 +839,7 @@ def silence_errors():
 cdef class MemoryFileBase:
     """Base for a BytesIO-like class backed by an in-memory file."""
 
-    def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
+    def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext='.tif'):
         """A file in an in-memory filesystem.
 
         Parameters
@@ -1230,16 +1230,6 @@ cdef class DatasetWriterBase(DatasetReaderBase):
             self.name,
             self.mode)
 
-    def start(self):
-        pass
-
-    def stop(self):
-        """Ends the dataset's life cycle"""
-        if self._hds != NULL:
-            GDALClose(self._hds)
-        self._hds = NULL
-        log.debug("Dataset %r has been stopped.", self)
-
     def _set_crs(self, crs):
         """Writes a coordinate reference system to the dataset."""
         crs = CRS.from_user_input(crs)
@@ -1900,7 +1890,7 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
             modes.
         sharing : bool
             A flag that allows sharing of dataset handles. Default is
-            `False`. Should be set to `False` in a multithreaded:w program.
+            `False`. Should be set to `False` in a multithreaded program.
         kwargs : optional
             These are passed to format drivers as directives for creating or
             interpreting datasets. For example: in 'w' or 'w+' modes
@@ -2086,8 +2076,10 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
             if temp != NULL:
                 GDALClose(temp)
             if self._hds != NULL:
-                GDALClose(self._hds)
-                self._hds = NULL
+                refcount = GDALDereferenceDataset(self._hds)
+                if refcount == 0:
+                    GDALClose(self._hds)
+            self._hds = NULL
 
 
 def virtual_file_to_buffer(filename):


=====================================
rasterio/_warp.pyx
=====================================
@@ -24,7 +24,7 @@ from rasterio.crs import CRS
 from rasterio.errors import (
     GDALOptionNotImplementedError,
     DriverRegistrationError, CRSError, RasterioIOError,
-    RasterioDeprecationWarning, WarpOptionsError)
+    RasterioDeprecationWarning, WarpOptionsError, WarpedVRTError)
 from rasterio.transform import Affine, from_bounds, guard_transform, tastes_like_gdal
 
 cimport numpy as np
@@ -650,7 +650,7 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
         Parameters
         ----------
         src_dataset : dataset object
-            The warp source.
+            The warp source dataset. Must be opened in "r" mode.
         src_crs : CRS or str, optional
             Overrides the coordinate reference system of `src_dataset`.
         src_transfrom : Affine, optional
@@ -701,7 +701,11 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
         Returns
         -------
         WarpedVRT
+
         """
+        if src_dataset.mode != "r":
+            warnings.warn("Source dataset should be opened in read-only mode. Use of datasets opened in modes other than 'r' will be disallowed in a future version.", RasterioDeprecationWarning, stacklevel=2)
+
         self.mode = 'r'
         self.options = {}
         self._count = 0
@@ -999,17 +1003,6 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
         """The dataset's coordinate reference system"""
         return self.dst_crs
 
-    def start(self):
-        """Starts the VRT's life cycle."""
-        log.debug("Dataset %r is started.", self)
-
-    def stop(self):
-        """Ends the VRT's life cycle"""
-        if self._hds != NULL:
-            GDALClose(self._hds)
-        self._hds = NULL
-        self._closed = True
-
     def read(self, indexes=None, out=None, window=None, masked=False,
             out_shape=None, boundless=False, resampling=Resampling.nearest,
             fill_value=None, out_dtype=None):


=====================================
rasterio/crs.py
=====================================
@@ -13,7 +13,7 @@ used.
 import json
 import pickle
 
-from rasterio._crs import _CRS, all_proj_keys
+from rasterio._crs import _CRS, all_proj_keys, _epsg_treats_as_latlong, _epsg_treats_as_northingeasting
 from rasterio.compat import Mapping, string_types
 from rasterio.errors import CRSError
 
@@ -155,6 +155,18 @@ class CRS(Mapping):
         """
         return self._crs.to_epsg()
 
+    def to_authority(self):
+        """The authority name and code of the CRS
+
+        .. versionadded:: 1.1.7
+
+        Returns
+        -------
+        (str, str) or None
+
+        """
+        return self._crs.to_authority()
+
     def to_dict(self):
         """Convert CRS to a PROJ4 dict
 
@@ -284,9 +296,9 @@ class CRS(Mapping):
         str
 
         """
-        epsg_code = self.to_epsg()
-        if epsg_code:
-            return 'EPSG:{}'.format(epsg_code)
+        auth = self.to_authority()
+        if auth:
+            return ":".join(auth)
         else:
             return self.to_wkt() or self.to_proj4()
 
@@ -321,6 +333,25 @@ class CRS(Mapping):
         obj._crs = _CRS.from_epsg(code)
         return obj
 
+    @classmethod
+    def from_authority(cls, auth_name, code):
+        """Make a CRS from an authority name and authority code
+
+        .. versionadded:: 1.1.7
+
+        Parameters
+        ----------
+        auth_name: str
+            The name of the authority.
+        code : int or str
+            The code used by the authority.
+
+        Returns
+        -------
+        CRS
+        """
+        return cls.from_string("{auth_name}:{code}".format(auth_name=auth_name, code=code))
+
     @classmethod
     def from_string(cls, string, morph_from_esri_dialect=False):
         """Make a CRS from an EPSG, PROJ, or WKT string
@@ -338,11 +369,16 @@ class CRS(Mapping):
         CRS
 
         """
+        try:
+            string = string.strip()
+        except AttributeError:
+            pass
+
         if not string:
             raise CRSError("CRS is empty or invalid: {!r}".format(string))
 
-        elif string.strip().upper().startswith('EPSG:'):
-            auth, val = string.strip().split(':')
+        elif string.upper().startswith('EPSG:'):
+            auth, val = string.split(':')
             if not val:
                 raise CRSError("Invalid CRS: {!r}".format(string))
             return cls.from_epsg(val)
@@ -358,11 +394,14 @@ class CRS(Mapping):
                 raise CRSError("CRS is empty JSON")
             else:
                 return cls.from_dict(**val)
-
-        elif string.strip().endswith("]"):
+        elif string.endswith("]"):
             return cls.from_wkt(string, morph_from_esri_dialect=morph_from_esri_dialect)
+        elif "=" in string:
+            return cls.from_proj4(string)
 
-        return cls.from_proj4(string)
+        obj = cls()
+        obj._crs = _CRS.from_user_input(string, morph_from_esri_dialect=morph_from_esri_dialect)
+        return obj
 
     @classmethod
     def from_proj4(cls, proj):
@@ -454,8 +493,83 @@ class CRS(Mapping):
         elif isinstance(value, dict):
             return cls(**value)
         elif isinstance(value, string_types):
-            obj = cls()
-            obj._crs = _CRS.from_user_input(value, morph_from_esri_dialect=morph_from_esri_dialect)
-            return obj
+            return cls.from_string(value, morph_from_esri_dialect=morph_from_esri_dialect)
         else:
             raise CRSError("CRS is invalid: {!r}".format(value))
+
+
+def epsg_treats_as_latlong(input):
+    """Test if the CRS is in latlon order
+
+    From GDAL docs:
+
+    > This method returns TRUE if EPSG feels this geographic coordinate
+    system should be treated as having lat/long coordinate ordering.
+
+    > Currently this returns TRUE for all geographic coordinate systems with
+    an EPSG code set, and axes set defining it as lat, long.
+
+    > FALSE will be returned for all coordinate systems that are not
+    geographic, or that do not have an EPSG code set.
+
+    > **Note**
+
+    > Important change of behavior since GDAL 3.0.
+    In previous versions, geographic CRS imported with importFromEPSG()
+    would cause this method to return FALSE on them, whereas now it returns
+    TRUE, since importFromEPSG() is now equivalent to importFromEPSGA().
+
+    Parameters
+    ----------
+    input : CRS
+        Coordinate reference system, as a rasterio CRS object
+        Example: CRS({'init': 'EPSG:4326'})
+
+    Returns
+    -------
+    bool
+
+    """
+    if not isinstance(input, CRS):
+        input = CRS.from_user_input(input)
+
+    return _epsg_treats_as_latlong(input._crs)
+
+
+def epsg_treats_as_northingeasting(input):
+    """Test if the CRS should be treated as having northing/easting coordinate ordering
+
+    From GDAL docs:
+
+    > This method returns TRUE if EPSG feels this projected coordinate
+    system should be treated as having northing/easting coordinate ordering.
+
+    > Currently this returns TRUE for all projected coordinate systems with
+    an EPSG code set, and axes set defining it as northing, easting.
+
+    > FALSE will be returned for all coordinate systems that are not
+    projected, or that do not have an EPSG code set.
+
+    > **Note**
+
+    > Important change of behavior since GDAL 3.0.
+    In previous versions, projected CRS with northing, easting axis order
+    imported with importFromEPSG() would cause this method to return FALSE
+    on them, whereas now it returns TRUE, since importFromEPSG() is now 
+    equivalent to importFromEPSGA().
+
+    Parameters
+    ----------
+    input : CRS
+        Coordinate reference system, as a rasterio CRS object
+        Example: CRS({'init': 'EPSG:4326'})
+
+    Returns
+    -------
+    bool
+
+    """
+    if not isinstance(input, CRS):
+        input = CRS.from_user_input(input)
+
+    return _epsg_treats_as_northingeasting(input._crs)


=====================================
rasterio/errors.py
=====================================
@@ -88,8 +88,12 @@ class WindowEvaluationError(ValueError):
     """Raised when window evaluation fails"""
 
 
-class RasterioDeprecationWarning(UserWarning):
-    """Rasterio module deprecations"""
+class RasterioDeprecationWarning(FutureWarning):
+    """Rasterio module deprecations
+
+    Following https://www.python.org/dev/peps/pep-0565/#additional-use-case-for-futurewarning
+    we base this on FutureWarning while continuing to support Python < 3.7.
+    """
 
 
 class RasterBlockError(RasterioError):
@@ -126,3 +130,7 @@ class ResamplingAlgorithmError(RasterioError):
 
 class TransformError(RasterioError):
     """Raised when transform arguments are invalid"""
+
+
+class WarpedVRTError(RasterioError):
+    """Raised when WarpedVRT can't be initialized"""


=====================================
rasterio/gdal.pxi
=====================================
@@ -115,6 +115,8 @@ cdef extern from "ogr_srs_api.h" nogil:
     int OSRSetFromUserInput(OGRSpatialReferenceH srs, const char *input)
     OGRErr OSRValidate(OGRSpatialReferenceH srs)
     double OSRGetLinearUnits(OGRSpatialReferenceH srs, char **ppszName)
+    int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH srs)
+    int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH srs)
 
 cdef extern from "gdal.h" nogil:
 


=====================================
rasterio/io.py
=====================================
@@ -85,7 +85,7 @@ class MemoryFile(MemoryFileBase):
      'width': 791}
 
     """
-    def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
+    def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext='.tif'):
         """Create a new file in memory
 
         Parameters
@@ -141,8 +141,8 @@ class MemoryFile(MemoryFileBase):
         return self
 
     def __exit__(self, *args, **kwargs):
-        self._env.__exit__()
         self.close()
+        self._env.__exit__()
 
 
 class ZipMemoryFile(MemoryFile):


=====================================
rasterio/merge.py
=====================================
@@ -10,6 +10,7 @@ import numpy as np
 import rasterio
 from rasterio import windows
 from rasterio.compat import string_types
+from rasterio.enums import Resampling
 from rasterio.transform import Affine
 
 
@@ -19,7 +20,8 @@ MERGE_METHODS = ('first', 'last', 'min', 'max')
 
 
 def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10,
-          indexes=None, output_count=None, method='first'):
+          indexes=None, output_count=None, resampling=Resampling.nearest,
+          method='first'):
     """Copy valid pixels from input files to an output file.
 
     All files must have the same number of bands, data type, and
@@ -57,6 +59,9 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
     output_count: int, optional
         If using callable it may be useful to have additional bands in the output
         in addition to the indexes specified for read
+    resampling : Resampling, optional
+        Resampling algorithm used when reading input files.
+        Default: `Resampling.nearest`.
     method : str or callable
         pre-defined method:
             first: reverse painting
@@ -271,6 +276,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
                 boundless=False,
                 masked=True,
                 indexes=indexes,
+                resampling=resampling,
             )
 
         # 5. Copy elements of temp into dest


=====================================
rasterio/rio/merge.py
=====================================
@@ -5,6 +5,7 @@ import click
 from cligj import format_opt
 
 import rasterio
+from rasterio.enums import Resampling
 from rasterio.rio import options
 from rasterio.rio.helpers import resolve_inout
 
@@ -15,6 +16,10 @@ from rasterio.rio.helpers import resolve_inout
 @format_opt
 @options.bounds_opt
 @options.resolution_opt
+ at click.option('--resampling',
+              type=click.Choice([r.name for r in Resampling if r.value <= 7]),
+              default='nearest', help="Resampling method.",
+              show_default=True)
 @options.nodata_opt
 @options.bidx_mult_opt
 @options.overwrite_opt
@@ -23,8 +28,8 @@ from rasterio.rio.helpers import resolve_inout
                    "pixels")
 @options.creation_options
 @click.pass_context
-def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
-          precision, creation_options):
+def merge(ctx, files, output, driver, bounds, res, resampling,
+          nodata, bidx, overwrite, precision, creation_options):
     """Copy valid pixels from input files to an output file.
 
     All files must have the same number of bands, data type, and
@@ -49,6 +54,8 @@ def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
     output, files = resolve_inout(
         files=files, output=output, overwrite=overwrite)
 
+    resampling = Resampling[resampling]
+
     with ctx.obj["env"]:
         dest, output_transform = merge_tool(
             files,
@@ -57,6 +64,7 @@ def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
             nodata=nodata,
             precision=precision,
             indexes=(bidx or None),
+            resampling=resampling,
         )
 
         with rasterio.open(files[0]) as first:


=====================================
tests/test_boundless_read.py
=====================================
@@ -72,6 +72,7 @@ def test_boundless_mask_not_all_valid():
     assert masked.mask[-1, :].all()
 
 
+ at pytest.mark.xfail(reason="fill_value requires GDAL 3.1.4 or equivalent patches")
 def test_boundless_fill_value():
     """Confirm resolution of issue #1471"""
     with rasterio.open("tests/data/red.tif") as src:
@@ -102,6 +103,7 @@ def test_boundless_mask_special():
     assert not mask[-1, :].any()
 
 
+ at pytest.mark.xfail(reason="fill_value requires GDAL 3.1.4 or equivalent patches")
 def test_boundless_fill_value_overview_masks():
     """Confirm a more general resolution to issue #1471"""
     with rasterio.open("tests/data/cogeo.tif") as src:
@@ -109,6 +111,7 @@ def test_boundless_fill_value_overview_masks():
     assert (data[:, 0] == 5).all()
 
 
+ at pytest.mark.xfail(reason="fill_value requires GDAL 3.1.4 or equivalent patches")
 def test_boundless_masked_fill_value_overview_masks():
     """Confirm a more general resolution to issue #1471"""
     with rasterio.open("tests/data/cogeo.tif") as src:


=====================================
tests/test_crs.py
=====================================
@@ -11,11 +11,11 @@ import pytest
 
 import rasterio
 from rasterio._base import _can_create_osr
-from rasterio.crs import CRS
+from rasterio.crs import CRS, epsg_treats_as_latlong, epsg_treats_as_northingeasting
 from rasterio.env import env_ctx_if_needed, Env
 from rasterio.errors import CRSError
 
-from .conftest import requires_gdal21, requires_gdal22, requires_gdal_lt_3
+from .conftest import requires_gdal21, requires_gdal22, requires_gdal_lt_3, requires_gdal3
 
 
 # Items like "D_North_American_1983" characterize the Esri dialect
@@ -540,3 +540,69 @@ def test_from_string__wkt_with_proj():
         'AUTHORITY["EPSG","3857"]] '
     )
     assert CRS.from_string(wkt).to_epsg() == 3857
+
+
+ at requires_gdal3
+def test_esri_auth__from_string():
+    assert CRS.from_string('ESRI:54009').to_string() == 'ESRI:54009'
+
+
+ at requires_gdal3
+def test_esri_auth__to_epsg():
+    assert CRS.from_user_input('ESRI:54009').to_epsg() is None
+
+
+ at requires_gdal3
+def test_esri_auth__to_authority():
+    assert CRS.from_user_input('ESRI:54009').to_authority() == ('ESRI', '54009')
+
+
+def test_from_authority__to_authority():
+    assert CRS.from_authority("EPSG", 4326).to_authority() == ("EPSG", "4326")
+
+
+def test_to_authority__no_code_available():
+    lcc_crs = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc '
+                              '+x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0')
+    assert lcc_crs.to_authority() is None
+
+
+ at pytest.mark.parametrize(
+    'crs_obj,result',
+    [
+        (CRS.from_user_input("http://www.opengis.net/def/crs/OGC/1.3/CRS84"), False),
+        (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/2193"), False),
+        (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/4326"), True),
+    ]
+)
+def test_is_latlong(crs_obj, result):
+    """Test if CRS should be treated as latlon."""
+    assert epsg_treats_as_latlong(crs_obj) == result
+
+
+ at pytest.mark.parametrize(
+    'crs_obj,result',
+    [
+        (CRS.from_user_input("http://www.opengis.net/def/crs/OGC/1.3/CRS84"), False),
+        (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/2193"), True),
+        (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/4326"), False),
+    ]
+)
+def test_is_northingeasting(crs_obj, result):
+    """Test if CRS should be treated as having northing/easting coordinate ordering."""
+    assert epsg_treats_as_northingeasting(crs_obj) == result
+
+
+ at requires_gdal_lt_3
+ at pytest.mark.parametrize('crs_obj', [CRS.from_epsg(4326), CRS.from_epsg(2193)])
+def test_latlong_northingeasting_gdal2(crs_obj):
+    """Check CRS created from epsg with GDAL 2 always return False."""
+    assert not epsg_treats_as_latlong(crs_obj)
+    assert not epsg_treats_as_northingeasting(crs_obj)
+
+
+ at requires_gdal3
+def test_latlong_northingeasting_gdal3():
+    """Check CRS created from epsg with GDAL 3."""
+    assert epsg_treats_as_latlong(CRS.from_epsg(4326))
+    assert epsg_treats_as_northingeasting(CRS.from_epsg(2193))


=====================================
tests/test_rio_merge.py
=====================================
@@ -12,6 +12,7 @@ from pytest import fixture
 import pytest
 
 import rasterio
+from rasterio.enums import Resampling
 from rasterio.merge import merge
 from rasterio.rio.main import main_group
 from rasterio.transform import Affine
@@ -286,7 +287,6 @@ def test_merge_overlapping(test_data_dir_overlapping):
 
 
 def test_merge_overlapping_callable_long(test_data_dir_overlapping, runner):
-    outputname = str(test_data_dir_overlapping.join('merged.tif'))
     inputs = [str(x) for x in test_data_dir_overlapping.listdir()]
     datasets = [rasterio.open(x) for x in inputs]
     test_merge_overlapping_callable_long.index = 0
@@ -304,13 +304,14 @@ def test_merge_overlapping_callable_long(test_data_dir_overlapping, runner):
 def test_custom_callable_merge(test_data_dir_overlapping, runner):
     inputs = ['tests/data/world.byte.tif'] * 3
     datasets = [rasterio.open(x) for x in inputs]
-    meta = datasets[0].meta
     output_count = 4
 
     def mycallable(old_data, new_data, old_nodata, new_nodata,
                    index=None, roff=None, coff=None):
         # input data are bytes, test output doesn't overflow
-        old_data[index] = (index + 1) * 259 # use a number > 255 but divisible by 3 for testing
+        old_data[index] = (
+            index + 1
+        ) * 259  # use a number > 255 but divisible by 3 for testing
         # update additional band that we specified in output_count
         old_data[3, :, :] += index
 
@@ -562,3 +563,56 @@ def test_merge_filenames(tiffs):
     inputs = [str(x) for x in tiffs.listdir()]
     inputs.sort()
     merge(inputs, res=2)
+
+
+ at fixture(scope='function')
+def test_data_dir_resampling(tmpdir):
+    kwargs = {
+        "crs": {'init': 'epsg:4326'},
+        "transform": affine.Affine(0.2, 0, 0,
+                                   0, -0.2, 0),
+        "count": 1,
+        "dtype": rasterio.uint8,
+        "driver": "GTiff",
+        "width": 9,
+        "height": 1,
+        "nodata": 1
+    }
+
+    with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
+        data = np.ones((1, 9), dtype=rasterio.uint8)
+        data[:, :3] = 100
+        data[:, 3:6] = 255
+        dst.write(data, indexes=1)
+
+    return tmpdir
+
+
+ at pytest.mark.xfail(
+    gdal_version.major == 1, reason="Mode resampling is unreliable for GDAL 1.11"
+)
+ at pytest.mark.parametrize(
+    "resampling",
+    [resamp for resamp in Resampling if resamp < 7]
+    + [pytest.param(Resampling.gauss, marks=pytest.mark.xfail)],
+)
+def test_merge_resampling(test_data_dir_resampling, resampling, runner):
+    outputname = str(test_data_dir_resampling.join('merged.tif'))
+    inputs = [str(x) for x in test_data_dir_resampling.listdir()]
+    with rasterio.open(inputs[0]) as src:
+        bounds = src.bounds
+        res = src.res[0]
+        expected_raster = src.read(
+            out_shape=tuple(dim * 2 for dim in src.shape),
+            resampling=resampling
+        )
+    result = runner.invoke(
+        main_group, ['merge'] + inputs + [outputname] +
+        ['--res', res / 2, '--resampling', resampling.name] +
+        ['--bounds', ' '.join(map(str, bounds))])
+    assert result.exit_code == 0
+    assert os.path.exists(outputname)
+    with rasterio.open(outputname) as dst:
+        output_raster = dst.read()
+
+    np.testing.assert_array_equal(output_raster, expected_raster)


=====================================
tests/test_warpedvrt.py
=====================================
@@ -9,16 +9,19 @@ import boto3
 import numpy
 import pytest
 
-from .conftest import requires_gdal21, requires_gdal2
 import rasterio
 from rasterio.crs import CRS
-from rasterio.enums import Resampling, MaskFlags, ColorInterp
-from rasterio.errors import RasterioDeprecationWarning, WarpOptionsError
+from rasterio.enums import Resampling, MaskFlags
+from rasterio.errors import RasterioDeprecationWarning, WarpOptionsError, WarpedVRTError
+from rasterio.io import MemoryFile
 from rasterio import shutil as rio_shutil
 from rasterio.vrt import WarpedVRT
 from rasterio.warp import transform_bounds
 from rasterio.windows import Window
 
+from .conftest import requires_gdal21, requires_gdal2
+
+log = logging.getLogger(__name__)
 
 # Custom markers.
 credentials = pytest.mark.skipif(
@@ -102,6 +105,7 @@ def test_warped_vrt_add_alpha(dsrec, path_rgb_byte_tif):
         assert len(records) == 1
         assert "1 N GTiff" in records[0]
 
+
 @requires_gdal21(reason="Nodata deletion requires GDAL 2.1+")
 def test_warped_vrt_msk_add_alpha(path_rgb_msk_byte_tif, caplog):
     """Add an alpha band to the VRT to access per-dataset mask of a source"""
@@ -565,3 +569,42 @@ def test_warped_vrt_resizing_repro():
         with WarpedVRT(rgb, crs="EPSG:3857", height=10, width=10) as vrt:
             assert vrt.height == 10
             assert vrt.width == 10
+
+
+def test_vrt_src_mode(path_rgb_byte_tif):
+    """VRT source dataset must be opened in read mode"""
+
+    with rasterio.open(path_rgb_byte_tif) as src:
+        profile = src.profile
+        bands = src.read()
+
+    with MemoryFile() as memfile:
+
+        with memfile.open(**profile) as dst:
+            dst.write(bands)
+
+            with pytest.warns(FutureWarning):
+                vrt = WarpedVRT(dst, crs="EPSG:3857")
+
+
+def test_vrt_src_kept_alive(path_rgb_byte_tif):
+    """VRT source dataset is kept alive, preventing crashes"""
+
+    with rasterio.open(path_rgb_byte_tif) as dst:
+        vrt = WarpedVRT(dst, crs="EPSG:3857")
+
+    assert (vrt.read() != 0).any()
+    vrt.close()
+
+
+def test_vrt_mem_src_kept_alive(path_rgb_byte_tif):
+    """VRT in-memory source dataset is kept alive, preventing crashes"""
+
+    with open(path_rgb_byte_tif, "rb") as fp:
+        bands = fp.read()
+
+    with MemoryFile(bands) as memfile, memfile.open() as dst:
+        vrt = WarpedVRT(dst, crs="EPSG:3857")
+
+    assert (vrt.read() != 0).any()
+    vrt.close()



View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/73af3ef34492bf3a9face7f72863832cbb5306b9...eadf201f67396c6b57291ef29f1437055157334a

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/73af3ef34492bf3a9face7f72863832cbb5306b9...eadf201f67396c6b57291ef29f1437055157334a
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/20200929/646211cc/attachment-0001.html>


More information about the Pkg-grass-devel mailing list