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

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Wed Oct 30 14:18:27 GMT 2024



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


Commits:
6ccc37e8 by Bas Couwenberg at 2024-10-30T15:05:09+01:00
New upstream version 1.4.2
- - - - -


15 changed files:

- .github/workflows/tests.yaml
- CHANGES.txt
- docs/topics/tags.rst
- rasterio/__init__.py
- rasterio/_warp.pyx
- rasterio/crs.pxd
- rasterio/crs.pyx
- rasterio/enums.py
- rasterio/gdal.pxi
- rasterio/transform.py
- tests/test_colorinterp.py
- tests/test_crs.py
- tests/test_rio_edit_info.py
- tests/test_transform.py
- tests/test_warp.py


Changes:

=====================================
.github/workflows/tests.yaml
=====================================
@@ -2,7 +2,7 @@ name: Tests
 
 on:
   push:
-    branches: [ main ]
+    branches: [ main, maint-1.4 ]
     paths:
       - '.github/workflows/tests.yaml'
       - 'requirements*.txt'
@@ -14,7 +14,7 @@ on:
       - 'rasterio/**'
       - 'tests/**'
   pull_request:
-    branches: [ main ]
+    branches: [ main, maint-1.4 ]
     paths:
       - '.github/workflows/tests.yaml'
       - 'requirements*.txt'
@@ -98,8 +98,10 @@ jobs:
       fail-fast: false
       matrix:
         python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
-        gdal-version: ['3.9.2']
+        gdal-version: ['3.9.3']
         include:
+          - python-version: '3.12'
+            gdal-version: 'latest'
           - python-version: '3.9'
             gdal-version: '3.8.5'
           - python-version: '3.9'


=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,23 @@
 Changes
 =======
 
+1.4.2 (TBD)
+-----------
+
+Version 1.4.2 fixes a regression and further improves compatibility with
+GDAL 3.10.
+
+Bug fixes:
+
+- The reproject() function now always returns a 2-D array, masked or
+  non-masked, when requested (#3223).
+- The various rowcol() methods once again return integers by default as
+  they did in 1.3.11 (#3219).
+- Internal usage of CRS.to_epsg(), which is slow, has been reduced, and
+  CRS.__eq__() has been made much faster (#3216).
+- The warper's use of a MEM:: dataset has been made fully compatible with
+  changes coming in GDAL 3.10 (#3212).
+
 1.4.1 (2024-09-30)
 ------------------
 
@@ -20,6 +37,7 @@ Other changes:
 - New color interpretation constants of GDAL 3.10 have been added to the
   ColorInterp enum (#3194).
 
+
 1.4.0 (2024-09-23)
 ------------------
 


=====================================
docs/topics/tags.rst
=====================================
@@ -23,8 +23,6 @@ namespace, call :meth:`~.DatasetReader.tags` with no arguments.
 
 .. code-block:: pycon
 
-    >>> import rasterio
-    >>> src = rasterio.open("tests/data/RGB.byte.tif")
     >>> src.tags()
     {'AREA_OR_POINT': 'Area'}
 


=====================================
rasterio/__init__.py
=====================================
@@ -81,7 +81,7 @@ except ImportError:
     have_vsi_plugin = False
 
 __all__ = ['band', 'open', 'pad', 'Band', 'Env', 'CRS']
-__version__ = "1.4.1"
+__version__ = "1.4.2"
 __gdal_version__ = gdal_version()
 __proj_version__ = ".".join([str(version) for version in get_proj_version()])
 __geos_version__ = ".".join([str(version) for version in get_geos_version()])


=====================================
rasterio/_warp.pyx
=====================================
@@ -22,7 +22,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.env import Env, GDALVersion
 from rasterio.crs import CRS
 from rasterio.errors import (
     GDALOptionNotImplementedError,
@@ -423,6 +423,8 @@ def _reproject(
         raise
 
     # Next, do the same for the destination raster.
+    dest_2d = False
+
     try:
         if dtypes.is_ndarray(destination):
             if not dst_crs:
@@ -436,6 +438,7 @@ def _reproject(
                 destination = np.asanyarray(destination)
 
             if len(destination.shape) == 2:
+                dest_2d = True
                 destination = destination.reshape(1, *destination.shape)
 
             if destination.shape[0] == src_count:
@@ -531,9 +534,10 @@ def _reproject(
         log.debug("Set _reproject Transformer option {0!r}={1!r}".format(key, val))
 
     try:
-        hTransformArg = exc_wrap_pointer(
-            GDALCreateGenImgProjTransformer2(
-                src_dataset, dst_dataset, imgProjOptions))
+        with Env(GDAL_MEM_ENABLE_OPEN=True):
+            hTransformArg = exc_wrap_pointer(
+                GDALCreateGenImgProjTransformer2(
+                    src_dataset, dst_dataset, imgProjOptions))
         if bUseApproxTransformer:
             hTransformArg = exc_wrap_pointer(
                 GDALCreateApproxTransformer(
@@ -623,7 +627,7 @@ def _reproject(
             0, 0, cols, rows)
 
         try:
-            with stack_errors() as checker:
+            with stack_errors() as checker, Env(GDAL_MEM_ENABLE_OPEN=True):
                 if num_threads > 1:
                     with nogil:
                         err = oWarper.ChunkAndWarpMulti(0, 0, cols, rows)
@@ -646,7 +650,7 @@ def _reproject(
                 alpha_arr = np.empty((height, width), dtype=dest_arr.dtype)
                 io_band(mem_raster.band(count), 0, 0.0, 0.0, width, height, alpha_arr)
                 destination = np.ma.masked_array(
-                    destination.data, 
+                    destination.data,
                     mask=np.repeat(
                         ~(alpha_arr.astype("bool"))[np.newaxis, :, :],
                         count - 1,
@@ -656,6 +660,9 @@ def _reproject(
             else:
                 exc_wrap_int(io_auto(destination, dst_dataset, 0))
 
+            if dest_2d:
+                destination = destination[0]
+
             return destination
 
     # Clean up transformer, warp options, and dataset handles.
@@ -778,9 +785,10 @@ def _calculate_default_transform(
             )
             bUseApproxTransformer = False
 
-        hTransformArg = exc_wrap_pointer(
-            GDALCreateGenImgProjTransformer2(hds, NULL, imgProjOptions)
-        )
+        with Env(GDAL_MEM_ENABLE_OPEN=True):
+            hTransformArg = exc_wrap_pointer(
+                GDALCreateGenImgProjTransformer2(hds, NULL, imgProjOptions)
+            )
 
         pfnTransformer = GDALGenImgProjTransform
         log.debug("Created exact transformer")


=====================================
rasterio/crs.pxd
=====================================
@@ -5,5 +5,5 @@ cdef class CRS:
 
     cdef OGRSpatialReferenceH _osr
     cdef object _data
-    cdef object _epsg
-    cdef object _wkt
+    cdef public object _epsg
+    cdef public object _wkt


=====================================
rasterio/crs.pyx
=====================================
@@ -78,7 +78,6 @@ cdef class CRS:
     The from_string method takes a variety of input.
 
     >>> crs = CRS.from_string("EPSG:3005")
-
     """
     def __init__(self, initialdata=None, **kwargs):
         """Make a CRS from a PROJ dict or mapping.
@@ -140,7 +139,7 @@ cdef class CRS:
 
         """
         try:
-            return bool(self.to_epsg())
+            return bool(self._epsg)
         except CRSError:
             return False
 
@@ -300,10 +299,6 @@ cdef class CRS:
             text = self._projjson()
             return json.loads(text) if text else {}
 
-        epsg_code = self.to_epsg()
-
-        if epsg_code:
-            return {'init': 'epsg:{}'.format(epsg_code)}
         else:
             try:
                 osr = exc_wrap_pointer(OSRClone(self._osr))
@@ -659,45 +654,59 @@ cdef class CRS:
         CRSError
 
         """
+        cdef const char *text_c = NULL
+        cdef CRS obj
+
         if initialdata is not None:
             data = dict(initialdata.items())
         else:
             data = {}
         data.update(**kwargs)
 
-        if not ("init" in data or "proj" in data):
-            # We've been given a PROJ JSON-encoded text.
-            return CRS.from_user_input(json.dumps(data))
-
         # "+init=epsg:xxxx" is deprecated in GDAL. If we find this, we will
         # extract the epsg code and dispatch to from_epsg.
         if 'init' in data and data['init'].lower().startswith('epsg:'):
             epsg_code = int(data['init'].split(':')[1])
             return CRS.from_epsg(epsg_code)
 
-        # Continue with the general case.
-        pjargs = []
-        for key in data.keys() & all_proj_keys:
-            val = data[key]
-            if val is None or val is True:
-                pjargs.append('+{}'.format(key))
-            elif val is False:
-                pass
+        elif not ("init" in data or "proj" in data):
+            # We've been given a PROJ JSON-encoded text.
+            text_b = json.dumps(data).encode('utf-8')
+            text_c = text_b
+            obj = CRS.__new__(CRS)
+            try:
+                errcode = exc_wrap_ogrerr(OSRSetFromUserInput(obj._osr, text_c))
+            except CPLE_BaseError as exc:
+                raise CRSError("The WKT could not be parsed. {}".format(exc))
             else:
-                pjargs.append('+{}={}'.format(key, val))
+                osr_set_traditional_axis_mapping_strategy(obj._osr)
+                obj._data = data
+                return obj
 
-        proj = ' '.join(pjargs)
-        b_proj = proj.encode('utf-8')
+        else:
+            # Continue with the general case.
+            pjargs = []
+            for key in data.keys() & all_proj_keys:
+                val = data[key]
+                if val is None or val is True:
+                    pjargs.append('+{}'.format(key))
+                elif val is False:
+                    pass
+                else:
+                    pjargs.append('+{}={}'.format(key, val))
 
-        cdef CRS obj = CRS.__new__(CRS)
+            proj = ' '.join(pjargs)
+            b_proj = proj.encode('utf-8')
+            obj = CRS.__new__(CRS)
 
-        try:
-            exc_wrap_ogrerr(OSRImportFromProj4(obj._osr, <const char *>b_proj))
-        except CPLE_BaseError as exc:
-            raise CRSError("The PROJ4 dict could not be understood. {}".format(exc))
-        else:
-            osr_set_traditional_axis_mapping_strategy(obj._osr)
-            return obj
+            try:
+                exc_wrap_ogrerr(OSRImportFromProj4(obj._osr, <const char *>b_proj))
+            except CPLE_BaseError as exc:
+                raise CRSError("The PROJ4 dict could not be understood. {}".format(exc))
+            else:
+                osr_set_traditional_axis_mapping_strategy(obj._osr)
+                obj._data = data
+                return obj
 
     @staticmethod
     def from_wkt(wkt, morph_from_esri_dialect=False):
@@ -769,20 +778,9 @@ cdef class CRS:
         elif isinstance(value, int):
             return CRS.from_epsg(value)
         elif isinstance(value, dict):
-            return CRS(**value)
-
+            return CRS.from_dict(value)
         elif isinstance(value, str):
-            text_b = value.encode('utf-8')
-            text_c = text_b
-            obj = CRS.__new__(CRS)
-            try:
-                errcode = exc_wrap_ogrerr(OSRSetFromUserInput(obj._osr, text_c))
-            except CPLE_BaseError as exc:
-                raise CRSError("The WKT could not be parsed. {}".format(exc))
-            else:
-                osr_set_traditional_axis_mapping_strategy(obj._osr)
-                return obj
-
+            return CRS.from_string(value)
         else:
             raise CRSError("CRS is invalid: {!r}".format(value))
 
@@ -795,7 +793,6 @@ cdef class CRS:
         Parameters
         ----------
         auth_name: str
-            The name of the authority.
         code : int or str
             The code used by the authority.
 
@@ -829,8 +826,10 @@ cdef class CRS:
         Raises
         ------
         CRSError
-
         """
+        cdef const char *text_c = NULL
+        cdef CRS obj
+
         try:
             value = value.strip()
         except AttributeError:
@@ -862,7 +861,16 @@ cdef class CRS:
         elif "=" in value:
             return CRS.from_proj4(value)
         else:
-            return CRS.from_user_input(value, morph_from_esri_dialect=morph_from_esri_dialect)
+            text_b = value.encode('utf-8')
+            text_c = text_b
+            obj = CRS.__new__(CRS)
+            try:
+                errcode = exc_wrap_ogrerr(OSRSetFromUserInput(obj._osr, text_c))
+            except CPLE_BaseError as exc:
+                raise CRSError("The WKT could not be parsed. {}".format(exc))
+            else:
+                osr_set_traditional_axis_mapping_strategy(obj._osr)
+                return obj
 
     def __cinit__(self):
         self._osr = OSRNewSpatialReference(NULL)
@@ -914,38 +922,23 @@ cdef class CRS:
         return self.to_string()
 
     def __repr__(self):
-        epsg_code = self.to_epsg()
-        if epsg_code:
-            return "CRS.from_epsg({})".format(epsg_code)
+        if self._epsg:
+            return "CRS.from_epsg({})".format(self._epsg)
         else:
             return "CRS.from_wkt('{}')".format(self.wkt)
 
     def __eq__(self, other):
-        cdef OGRSpatialReferenceH osr_s = NULL
-        cdef OGRSpatialReferenceH osr_o = NULL
         cdef CRS crs_o
+        cdef const char* options[2]
+        options[0] = b"IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=NO"
+        options[1] = NULL
 
         try:
             crs_o = CRS.from_user_input(other)
+            return bool(OSRIsSameEx(self._osr, crs_o._osr, options) == 1)
         except CRSError:
             return False
 
-        epsg_s = self.to_epsg()
-        epsg_o = crs_o.to_epsg()
-
-        if epsg_s is not None and epsg_o is not None and epsg_s == epsg_o:
-            return True
-
-        else:
-            try:
-                osr_s = exc_wrap_pointer(OSRClone(self._osr))
-                osr_o = exc_wrap_pointer(OSRClone(crs_o._osr))
-                return bool(OSRIsSame(osr_s, osr_o) == 1)
-
-            finally:
-                _safe_osr_release(osr_s)
-                _safe_osr_release(osr_o)
-
 
     def _projjson(self):
         """Get a PROJ JSON representation.


=====================================
rasterio/enums.py
=====================================
@@ -43,6 +43,28 @@ class ColorInterp(IntEnum):
     Y = 14
     Cb = 15
     Cr = 16
+    # Below values since GDAL 3.10
+    pan = 17
+    coastal = 18
+    rededge = 19
+    nir = 20
+    swir = 21
+    mwir = 22
+    lwir = 23
+    tir = 24
+    other_ir = 25
+    # GCI_IR_Reserved_1 = 26
+    # GCI_IR_Reserved_2 = 27
+    # GCI_IR_Reserved_3 = 28
+    # GCI_IR_Reserved_4 = 29
+    sar_ka = 30
+    sar_k = 31
+    sar_ku = 32
+    sar_x = 33
+    sar_c = 34
+    sar_s = 35
+    sar_l = 36
+    sar_p = 37
 
 
 class Resampling(IntEnum):


=====================================
rasterio/gdal.pxi
=====================================
@@ -176,6 +176,7 @@ cdef extern from "ogr_srs_api.h" nogil:
     int OSRIsGeographic(OGRSpatialReferenceH srs)
     int OSRIsProjected(OGRSpatialReferenceH srs)
     int OSRIsSame(OGRSpatialReferenceH srs1, OGRSpatialReferenceH srs2)
+    int OSRIsSameEx(OGRSpatialReferenceH srs1, OGRSpatialReferenceH srs2, const char* const* options)
     OGRSpatialReferenceH OSRNewSpatialReference(const char *wkt)
     void OSRRelease(OGRSpatialReferenceH srs)
     int OSRSetFromUserInput(OGRSpatialReferenceH srs, const char *input)
@@ -265,22 +266,70 @@ cdef extern from "gdal.h" nogil:
         GRIORA_Mode
         GRIORA_Gauss
 
-    ctypedef enum GDALColorInterp:
-        GCI_Undefined
-        GCI_GrayIndex
-        GCI_PaletteIndex
-        GCI_RedBand
-        GCI_GreenBand
-        GCI_BlueBand
-        GCI_AlphaBand
-        GCI_HueBand
-        GCI_SaturationBand
-        GCI_LightnessBand
-        GCI_CyanBand
-        GCI_YCbCr_YBand
-        GCI_YCbCr_CbBand
-        GCI_YCbCr_CrBand
-        GCI_Max
+
+IF (CTE_GDAL_MAJOR_VERSION, CTE_GDAL_MINOR_VERSION) >= (3, 10):
+    cdef extern from "gdal.h" nogil:
+
+        ctypedef enum GDALColorInterp:
+            GCI_Undefined
+            GCI_GrayIndex
+            GCI_PaletteIndex
+            GCI_RedBand
+            GCI_GreenBand
+            GCI_BlueBand
+            GCI_AlphaBand
+            GCI_HueBand
+            GCI_SaturationBand
+            GCI_LightnessBand
+            GCI_CyanBand
+            GCI_YCbCr_YBand
+            GCI_YCbCr_CbBand
+            GCI_YCbCr_CrBand
+            GCI_PanBand
+            GCI_CoastalBand
+            GCI_RedEdgeBand
+            GCI_NIRBand
+            GCI_SWIRBand
+            GCI_MWIRBand
+            GCI_LWIRBand
+            GCI_TIRBand
+            GCI_OtherIRBand
+            GCI_IR_Reserved_1
+            GCI_IR_Reserved_2
+            GCI_IR_Reserved_3
+            GCI_IR_Reserved_4
+            GCI_SAR_Ka_Band
+            GCI_SAR_K_Band
+            GCI_SAR_Ku_Band
+            GCI_SAR_X_Band
+            GCI_SAR_C_Band
+            GCI_SAR_S_Band
+            GCI_SAR_L_Band
+            GCI_SAR_P_Band
+            GCI_SAR_Reserved_1
+            GCI_SAR_Reserved_2
+            GCI_Max
+ELSE:
+    cdef extern from "gdal.h" nogil:
+
+        ctypedef enum GDALColorInterp:
+            GCI_Undefined
+            GCI_GrayIndex
+            GCI_PaletteIndex
+            GCI_RedBand
+            GCI_GreenBand
+            GCI_BlueBand
+            GCI_AlphaBand
+            GCI_HueBand
+            GCI_SaturationBand
+            GCI_LightnessBand
+            GCI_CyanBand
+            GCI_YCbCr_YBand
+            GCI_YCbCr_CbBand
+            GCI_YCbCr_CrBand
+
+
+cdef extern from "gdal.h" nogil:
 
     ctypedef struct GDALColorEntry:
         short c1


=====================================
rasterio/transform.py
=====================================
@@ -86,7 +86,7 @@ class TransformMethodsMixin:
         x,
         y,
         z=None,
-        op=np.floor,
+        op=None,
         precision=None,
         transform_method=TransformMethod.affine,
         **rpc_options
@@ -100,14 +100,15 @@ class TransformMethodsMixin:
         y : float
             y value in coordinate reference system
         z : float, optional
-            Height associated with coordinates. Primarily used for RPC based
-            coordinate transformations. Ignored for affine based
+            Height associated with coordinates. Primarily used for RPC
+            based coordinate transformations. Ignored for affine based
             transformations. Default: 0.
-        op : function, optional (default: math.floor)
-            Function to convert fractional pixels to whole numbers (floor,
-            ceiling, round)
+        op : function, optional (default: numpy.floor)
+            Function to convert fractional pixels to whole numbers
+            (floor, ceiling, round)
         transform_method: TransformMethod, optional
-            The coordinate transformation method. Default: `TransformMethod.affine`.
+            The coordinate transformation method. Default:
+            `TransformMethod.affine`.
         rpc_options: dict, optional
             Additional arguments passed to GDALCreateRPCTransformer
         precision : int, optional
@@ -116,7 +117,7 @@ class TransformMethodsMixin:
 
         Returns
         -------
-        tuple
+        tuple: int, int
             (row index, col index)
 
         """
@@ -251,13 +252,22 @@ def xy(transform, rows, cols, zs=None, offset='center', **rpc_options):
         return transformer.xy(rows, cols, zs=zs, offset=offset)
 
 
-def rowcol(transform, xs, ys, zs=None, op=np.floor, precision=None, **rpc_options):
+def rowcol(
+    transform,
+    xs,
+    ys,
+    zs=None,
+    op=None,
+    precision=None,
+    **rpc_options,
+):
     """Get rows and cols of the pixels containing (x, y).
 
     Parameters
     ----------
     transform : Affine or sequence of GroundControlPoint or RPC
-        Transform suitable for input to AffineTransformer, GCPTransformer, or RPCTransformer.
+        Transform suitable for input to AffineTransformer,
+        GCPTransformer, or RPCTransformer.
     xs : list or float
         x values in coordinate reference system.
     ys : list or float
@@ -266,9 +276,9 @@ def rowcol(transform, xs, ys, zs=None, op=np.floor, precision=None, **rpc_option
         Height associated with coordinates. Primarily used for RPC based
         coordinate transformations. Ignored for affine based
         transformations. Default: 0.
-    op : function
-        Function to convert fractional pixels to whole numbers (floor, ceiling,
-        round).
+    op : function, optional (default: numpy.floor)
+        Function to convert fractional pixels to whole numbers (floor,
+        ceiling, round)
     precision : int or float, optional
         This parameter is unused, deprecated in rasterio 1.3.0, and
         will be removed in version 2.0.0.
@@ -277,10 +287,10 @@ def rowcol(transform, xs, ys, zs=None, op=np.floor, precision=None, **rpc_option
 
     Returns
     -------
-    rows : list of ints
-        list of row indices
-    cols : list of ints
-        list of column indices
+    rows : array of ints or floats
+    cols : array of ints or floats
+        Integers are the default. The numerical type is determined by
+        the type returned by op().
 
     """
     if precision is not None:
@@ -350,7 +360,7 @@ class TransformerBase:
     def __exit__(self, *args):
         pass
 
-    def rowcol(self, xs, ys, zs=None, op=np.floor, precision=None):
+    def rowcol(self, xs, ys, zs=None, op=None, precision=None):
         """Get rows and cols coordinates given geographic coordinates.
 
         Parameters
@@ -358,24 +368,28 @@ class TransformerBase:
         xs, ys : float or list of float
             Geographic coordinates
         zs : float or list of float, optional
-            Height associated with coordinates. Primarily used for RPC based
-            coordinate transformations. Ignored for affine based
+            Height associated with coordinates. Primarily used for RPC
+            based coordinate transformations. Ignored for affine based
             transformations. Default: 0.
-        op : function, optional (default: math.floor)
-            Function to convert fractional pixels to whole numbers (floor,
-            ceiling, round)
+        op : function, optional (default: numpy.floor)
+            Function to convert fractional pixels to whole numbers
+            (floor, ceiling, round)
         precision : int, optional (default: None)
             This parameter is unused, deprecated in rasterio 1.3.0, and
             will be removed in version 2.0.0.
 
         Raises
         ------
+        TypeError
+            If coordinate transformation fails.
         ValueError
-            If input coordinates are not all equal length
+            If input coordinates are not all equal length.
 
         Returns
         -------
-        tuple of float or list of float.
+        tuple of numbers or array of numbers.
+            Integers are the default. The numerical type is determined
+            by the type returned by op().
 
         """
         if precision is not None:
@@ -392,8 +406,10 @@ class TransformerBase:
                 xs, ys, zs, transform_direction=TransformDirection.reverse
             )
 
-            is_op_ufunc = isinstance(op, np.ufunc)
-            if is_op_ufunc:
+            if op is None:
+                new_rows = np.floor(new_rows).astype(dtype="int32")
+                new_cols = np.floor(new_cols).astype(dtype="int32")
+            elif isinstance(op, np.ufunc):
                 op(new_rows, out=new_rows)
                 op(new_cols, out=new_cols)
             else:
@@ -404,6 +420,7 @@ class TransformerBase:
                 return new_rows[0], new_cols[0]
             else:
                 return new_rows, new_cols
+
         except TypeError:
             raise TransformError("Invalid inputs")
 


=====================================
tests/test_colorinterp.py
=====================================
@@ -4,7 +4,9 @@ import pytest
 
 import rasterio
 from rasterio.enums import ColorInterp
+from rasterio.env import GDALVersion
 
+from .conftest import gdal_version
 
 def test_cmyk_interp(tmpdir):
     """A CMYK TIFF has cyan, magenta, yellow, black bands."""
@@ -80,7 +82,7 @@ def test_set_colorinterp(path_rgba_byte_tif, tmpdir, dtype):
         assert src.colorinterp == dst_ci
 
 
- at pytest.mark.parametrize("ci", ColorInterp.__members__.values())
+ at pytest.mark.parametrize("ci", ColorInterp.__members__.values() if gdal_version >= GDALVersion(3, 10) else list(filter(lambda x: x <= 16, ColorInterp.__members__.values())))
 def test_set_colorinterp_all(path_4band_no_colorinterp, ci):
     """Test setting with all color interpretations."""
 


=====================================
tests/test_crs.py
=====================================
@@ -45,21 +45,19 @@ class CustomCRS:
 def test_crs_constructor_dict():
     """Can create a CRS from a dict"""
     crs = CRS({'init': 'epsg:3857'})
-    assert crs['init'] == 'epsg:3857'
     assert 'PROJCS["WGS 84 / Pseudo-Mercator"' in crs.wkt
 
 
 def test_crs_constructor_keywords():
     """Can create a CRS from keyword args, ignoring unknowns"""
+    # Note: `init="epsg:dddd"` is no longer recommended usage.
     crs = CRS(init='epsg:3857', foo='bar')
-    assert crs['init'] == 'epsg:3857'
     assert 'PROJCS["WGS 84 / Pseudo-Mercator"' in crs.wkt
 
 
 def test_crs_constructor_crs_obj():
     """Can create a CRS from a CRS obj"""
-    crs = CRS(CRS(init='epsg:3857'))
-    assert crs['init'] == 'epsg:3857'
+    crs = CRS(CRS.from_epsg(3857))
     assert 'PROJCS["WGS 84 / Pseudo-Mercator"' in crs.wkt
 
 
@@ -123,21 +121,21 @@ def test_from_proj4_json():
 
 
 def test_from_epsg():
-    crs_dict = CRS.from_epsg(4326)
-    assert crs_dict['init'].lower() == 'epsg:4326'
+    assert CRS.from_epsg(4326)._epsg == 4326
 
-    # Test with invalid EPSG code
+
+def test_from_epsg_fail():
     with pytest.raises(ValueError):
-        assert CRS.from_epsg(0)
+        CRS.from_epsg(0)
 
 
 def test_from_epsg_string():
-    crs_dict = CRS.from_string('epsg:4326')
-    assert crs_dict['init'].lower() == 'epsg:4326'
+    assert CRS.from_string("epsg:4326")._epsg == 4326
+
 
-    # Test with invalid EPSG code
+def test_from_epsg_string_fail():
     with pytest.raises(ValueError):
-        assert CRS.from_string('epsg:xyz')
+        CRS.from_string("epsg:xyz")
 
 
 def test_from_epsg_overflow():
@@ -147,12 +145,20 @@ def test_from_epsg_overflow():
 
 
 def test_from_string():
-    wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')
-    assert wgs84_crs.to_dict() == {'init': 'epsg:4326'}
+    wgs84_crs = CRS.from_string("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
+    assert wgs84_crs.to_dict() == {"proj": "longlat", "datum": "WGS84", "no_defs": True}
 
+
+def test_from_string_2():
     # Make sure this doesn't get handled using the from_epsg() even though 'epsg' is in the string
-    epsg_init_crs = CRS.from_string('+init=epsg:26911')
-    assert epsg_init_crs.to_dict() == {'init': 'epsg:26911'}
+    epsg_init_crs = CRS.from_string("+init=epsg:26911")
+    assert epsg_init_crs.to_dict() == {
+        "proj": "utm",
+        "zone": 11,
+        "datum": "NAD83",
+        "units": "m",
+        "no_defs": True,
+    }
 
 
 @pytest.mark.parametrize('proj,expected', [({'init': 'epsg:4326'}, True), ({'init': 'epsg:3857'}, False)])
@@ -194,15 +200,6 @@ def test_equality_from_dict(epsg_code):
 
 
 def test_is_same_crs():
-    crs1 = CRS({'init': 'epsg:4326'})
-    crs2 = CRS({'init': 'epsg:3857'})
-
-    assert crs1 == crs1
-    assert crs1 != crs2
-
-    wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')
-    assert crs1 == wgs84_crs
-
     # Make sure that same projection with different parameter are not equal
     lcc_crs1 = 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')
     lcc_crs2 = 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=45 +lat_0=0')
@@ -291,9 +288,7 @@ def test_epsg__no_code_available():
 def test_crs_OSR_equivalence():
     crs1 = CRS.from_string('+proj=longlat +datum=WGS84 +no_defs')
     crs2 = CRS.from_string('+proj=latlong +datum=WGS84 +no_defs')
-    crs3 = CRS({'init': 'epsg:4326'})
     assert crs1 == crs2
-    assert crs1 == crs3
 
 
 def test_crs_OSR_no_equivalence():
@@ -328,7 +323,7 @@ def test_from_wkt_invalid():
 
 
 def test_from_user_input_epsg():
-    assert 'init' in CRS.from_user_input('epsg:4326')
+    assert CRS.from_user_input("epsg:4326")._epsg == 4326
 
 
 @pytest.mark.parametrize("version", ["WKT2_2019", WktVersion.WKT2_2019])
@@ -658,3 +653,9 @@ def test_crs_proj_json__from_string():
 
 def test_crs_compound_epsg():
     assert CRS.from_string("EPSG:4326+3855").to_wkt().startswith("COMPD")
+
+
+ at pytest.mark.parametrize("crs", [CRS.from_epsg(4326), CRS.from_string("EPSG:4326")])
+def test_epsg_4326_ogc_crs84(crs):
+    """EPSG:4326 not equivalent to OGC:CRS84."""
+    assert CRS.from_string("OGC:CRS84") != crs


=====================================
tests/test_rio_edit_info.py
=====================================
@@ -8,10 +8,15 @@ import numpy
 import pytest
 
 import rasterio
+from rasterio import CRS
 from rasterio.enums import ColorInterp
 from rasterio.rio.edit_info import (
-    all_handler, crs_handler, tags_handler, transform_handler,
-    colorinterp_handler)
+    all_handler,
+    crs_handler,
+    tags_handler,
+    transform_handler,
+    colorinterp_handler,
+)
 from rasterio.rio.main import main_group
 import rasterio.shutil
 
@@ -97,7 +102,7 @@ def test_edit_crs_epsg(data, runner):
         main_group, ['edit-info', inputfile, '--crs', 'EPSG:32618'])
     assert result.exit_code == 0
     with rasterio.open(inputfile) as src:
-        assert src.crs == {'init': 'epsg:32618'}
+        assert src.crs == CRS.from_epsg(32618)
 
 
 def test_edit_crs_proj4(data, runner):
@@ -106,7 +111,7 @@ def test_edit_crs_proj4(data, runner):
         main_group, ['edit-info', inputfile, '--crs', '+init=epsg:32618'])
     assert result.exit_code == 0
     with rasterio.open(inputfile) as src:
-        assert src.crs == {'init': 'epsg:32618'}
+        assert src.crs == CRS.from_epsg(32618)
 
 
 def test_edit_crs_obj(data, runner):
@@ -116,7 +121,13 @@ def test_edit_crs_obj(data, runner):
         ['edit-info', inputfile, '--crs', '{"init": "epsg:32618"}'])
     assert result.exit_code == 0
     with rasterio.open(inputfile) as src:
-        assert src.crs.to_dict() == {'init': 'epsg:32618'}
+        assert src.crs.to_dict() == {
+            "datum": "WGS84",
+            "no_defs": True,
+            "proj": "utm",
+            "units": "m",
+            "zone": 18,
+        }
 
 
 def test_edit_transform_err_not_json(data, runner):
@@ -192,12 +203,12 @@ def test_edit_crs_like(data, runner):
     # Set up the file to be edited.
     inputfile = str(data.join('RGB.byte.tif'))
     with rasterio.open(inputfile, 'r+') as dst:
-        dst.crs = {'init': 'epsg:32617'}
+        dst.crs = "EPSG:32617"
         dst.nodata = 1.0
 
     # Double check.
     with rasterio.open(inputfile) as src:
-        assert src.crs == {'init': 'epsg:32617'}
+        assert src.crs == CRS.from_epsg(32617)
         assert src.nodata == 1.0
 
     # The test.
@@ -215,12 +226,12 @@ def test_edit_nodata_like(data, runner):
     # Set up the file to be edited.
     inputfile = str(data.join('RGB.byte.tif'))
     with rasterio.open(inputfile, 'r+') as dst:
-        dst.crs = {'init': 'epsg:32617'}
+        dst.crs = "EPSG:32617"
         dst.nodata = 1.0
 
     # Double check.
     with rasterio.open(inputfile) as src:
-        assert src.crs == {'init': 'epsg:32617'}
+        assert src.crs == CRS.from_epsg(32617)
         assert src.nodata == 1.0
 
     # The test.
@@ -230,19 +241,19 @@ def test_edit_nodata_like(data, runner):
         ['edit-info', inputfile, '--like', templatefile, '--nodata', 'like'])
     assert result.exit_code == 0
     with rasterio.open(inputfile) as src:
-        assert src.crs == {'init': 'epsg:32617'}
+        assert src.crs == CRS.from_epsg(32617)
         assert src.nodata == 0.0
 
 
 def test_edit_all_like(data, runner):
     inputfile = str(data.join('RGB.byte.tif'))
     with rasterio.open(inputfile, 'r+') as dst:
-        dst.crs = {'init': 'epsg:32617'}
+        dst.crs = "EPSG:32617"
         dst.nodata = 1.0
 
     # Double check.
     with rasterio.open(inputfile) as src:
-        assert src.crs == {'init': 'epsg:32617'}
+        assert src.crs == CRS.from_epsg(32617)
         assert src.nodata == 1.0
 
     templatefile = 'tests/data/RGB.byte.tif'


=====================================
tests/test_transform.py
=====================================
@@ -525,8 +525,16 @@ def test_gcp_transformer_tps_option():
             assert gcp.col == pytest.approx(col_)
 
 
-def test_transform_grid():
+def test_transform_xy_grid():
     """Accept a grid, see gh-3191."""
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         cols, rows = numpy.mgrid[0:3, 0:3]
         xs, ys = src.xy(cols, rows)
+
+
+def test_transform_rowcol_grid():
+    """Accept a grid, see gh-3191."""
+    with rasterio.open("tests/data/RGB.byte.tif") as src:
+        left, bottom, right, top = src.bounds
+        xs, ys = numpy.mgrid[left:right:3j, bottom:top:3j]
+        rows, cols = AffineTransformer(src.transform).rowcol(xs, ys)


=====================================
tests/test_warp.py
=====================================
@@ -1362,6 +1362,7 @@ def test_reproject_masked(test3d, count_nonzero, path_rgb_byte_tif):
         dst_nodata=99,
     )
     assert np.ma.is_masked(source)
+    assert source.shape == out.shape
     assert np.count_nonzero(out[out != 99]) == count_nonzero
     assert not np.ma.is_masked(out)
 
@@ -1400,6 +1401,7 @@ def test_reproject_masked_masked_output(test3d, count_nonzero, path_rgb_byte_tif
         dst_transform=DST_TRANSFORM,
         dst_crs="EPSG:3857",
     )
+    assert inp.shape == out.shape
     assert np.count_nonzero(out[out != np.ma.masked]) == count_nonzero
 
 
@@ -2147,7 +2149,7 @@ def test_reproject_error_propagation(http_error_server, caplog):
                 dst_transform=src.transform,
             )
 
-    assert len([rec for rec in caplog.records if "Retrying again" in rec.message]) == 2
+    assert len([rec for rec in caplog.records if "Retrying again" in rec.message]) >= 2
 
 
 def test_reproject_to_specified_output_bands():



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/commit/6ccc37e8b4decfb798a44db8ab31bbf2dae02d3e
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/20241030/c9561042/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list