Repository: rasterio/rasterio

Issue:
Problem with reproject and identity affine
A follow up to #524 and the unique madness that is the identity transform.

**Problem**: `reproject` fails if its `src_transform` is equal to the identity matrix.

The following test

``` python
import numpy as np
from affine import Affine
import rasterio
from rasterio.warp import reproject
from rasterio.enums import Resampling


def test_reproject_nonidentity():
    # note the affines are both positive e, but NOT identity
    src = np.random.random(2500).reshape((1, 50, 50))
    srcaff = Affine(1.1, 0.0, 0.0, 0.0, 1.1, 0.0)  # not quite
    srccrs = {'init': 'epsg:3857'}

    dst = np.empty(shape=(1, 110, 110))
    dstaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
    dstcrs = {'init': 'epsg:3857'}

    with rasterio.drivers():
        reproject(
            src, dst,
            src_transform=srcaff,
            src_crs=srccrs,
            dst_transform=dstaff,
            dst_crs=dstcrs,
            resampling=Resampling.nearest)
```

Works 👌  despite the positive affine.e (more on that in a minute)

But as soon as I alter the test to use the identity matrix for an affine

``` python
def test_reproject_identity():
    # note the affines are both positive e, one is identity
    src = np.random.random(2500).reshape((1, 50, 50))
    srcaff = Affine(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)  # !!! Identity
    srccrs = {'init': 'epsg:3857'}

    dst = np.empty(shape=(1, 100, 100))
    dstaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
    dstcrs = {'init': 'epsg:3857'}

    with rasterio.drivers():
        reproject(
            src, dst,
            src_transform=srcaff,
            src_crs=srccrs,
            dst_transform=dstaff,
            dst_crs=dstcrs,
            resampling=Resampling.nearest)
```

This fails with the following error

```
Traceback (most recent call last):
  File "_test_err.py", line 51, in <module>
    test_reproject_identity()
  File "_test_err.py", line 45, in test_reproject_identity
    resampling=Resampling.nearest)
  File "/Users/mperry/work/rasterio/rasterio/warp.py", line 257, in reproject
    **kwargs)
  File "rasterio/_warp.pyx", line 391, in rasterio._warp._reproject (rasterio/_warp.cpp:8343)
  File "rasterio/_warp.pyx", line 380, in rasterio._warp._reproject (rasterio/_warp.cpp:8219)
  File "rasterio/_warp.pyx", line 384, in rasterio._warp._reproject (rasterio/_warp.cpp:8133)
  File "rasterio/_err.pyx", line 190, in rasterio._err.CPLErrors.check (rasterio/_err.c:1502)
rasterio._err.CPLE_AppDefined: (3, 1, 'The transformation is already "north up" or a transformation 
between pixel/line and georeferenced coordinates cannot be computed for Temporary source dataset 
for _reproject(). There is no affine transformation and no GCPs. Specify transformation option 
SRC_METHOD=NO_GEOTRANSFORM to bypass this check.')
```

The reference to "north up" threw me for a while... thought we might be dealing with #525 . But no, it's just a simple case of the identity matrix causing problems.

This is likely to cause problems when e.g. trying to resample rasterio sources without a specified transform.

thanks to @celoyd for uncovering this 🐛 

Problem with reproject and identity affine
A follow up to #524 and the unique madness that is the identity transform.

**Problem**: `reproject` fails if its `src_transform` is equal to the identity matrix.

The following test

``` python
import numpy as np
from affine import Affine
import rasterio
from rasterio.warp import reproject
from rasterio.enums import Resampling


def test_reproject_nonidentity():
    # note the affines are both positive e, but NOT identity
    src = np.random.random(2500).reshape((1, 50, 50))
    srcaff = Affine(1.1, 0.0, 0.0, 0.0, 1.1, 0.0)  # not quite
    srccrs = {'init': 'epsg:3857'}

    dst = np.empty(shape=(1, 110, 110))
    dstaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
    dstcrs = {'init': 'epsg:3857'}

    with rasterio.drivers():
        reproject(
            src, dst,
            src_transform=srcaff,
            src_crs=srccrs,
            dst_transform=dstaff,
            dst_crs=dstcrs,
            resampling=Resampling.nearest)
```

Works 👌  despite the positive affine.e (more on that in a minute)

But as soon as I alter the test to use the identity matrix for an affine

``` python
def test_reproject_identity():
    # note the affines are both positive e, one is identity
    src = np.random.random(2500).reshape((1, 50, 50))
    srcaff = Affine(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)  # !!! Identity
    srccrs = {'init': 'epsg:3857'}

    dst = np.empty(shape=(1, 100, 100))
    dstaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
    dstcrs = {'init': 'epsg:3857'}

    with rasterio.drivers():
        reproject(
            src, dst,
            src_transform=srcaff,
            src_crs=srccrs,
            dst_transform=dstaff,
            dst_crs=dstcrs,
            resampling=Resampling.nearest)
```

This fails with the following error

```
Traceback (most recent call last):
  File "_test_err.py", line 51, in <module>
    test_reproject_identity()
  File "_test_err.py", line 45, in test_reproject_identity
    resampling=Resampling.nearest)
  File "/Users/mperry/work/rasterio/rasterio/warp.py", line 257, in reproject
    **kwargs)
  File "rasterio/_warp.pyx", line 391, in rasterio._warp._reproject (rasterio/_warp.cpp:8343)
  File "rasterio/_warp.pyx", line 380, in rasterio._warp._reproject (rasterio/_warp.cpp:8219)
  File "rasterio/_warp.pyx", line 384, in rasterio._warp._reproject (rasterio/_warp.cpp:8133)
  File "rasterio/_err.pyx", line 190, in rasterio._err.CPLErrors.check (rasterio/_err.c:1502)
rasterio._err.CPLE_AppDefined: (3, 1, 'The transformation is already "north up" or a transformation 
between pixel/line and georeferenced coordinates cannot be computed for Temporary source dataset 
for _reproject(). There is no affine transformation and no GCPs. Specify transformation option 
SRC_METHOD=NO_GEOTRANSFORM to bypass this check.')
```

The reference to "north up" threw me for a while... thought we might be dealing with #525 . But no, it's just a simple case of the identity matrix causing problems.

This is likely to cause problems when e.g. trying to resample rasterio sources without a specified transform.

thanks to @celoyd for uncovering this 🐛 



Discussion:
working on this, creating an xfailed test case first

working on this, creating an xfailed test case first


Resolving patch:
diff --git a/rasterio/warp.py b/rasterio/warp.py
--- a/rasterio/warp.py
+++ b/rasterio/warp.py
@@ -244,11 +244,25 @@ def reproject(
                 ['Resampling.{0}'.format(k) for k in
                  Resampling.__members__.keys() if k != 'gauss'])))
 
+    # If working with identity transform, assume it is crs-less data
+    # and that translating the matrix very slightly will avoid #674
+    eps = 1e-100
+    if src_transform and guard_transform(src_transform).is_identity:
+        src_transform = src_transform.translation(eps, eps)
+    if dst_transform and guard_transform(dst_transform).is_identity:
+        dst_transform = dst_transform.translation(eps, eps)
+
     if src_transform:
         src_transform = guard_transform(src_transform).to_gdal()
     if dst_transform:
         dst_transform = guard_transform(dst_transform).to_gdal()
 
+    # Passing None can cause segfault, use empty dict
+    if src_crs is None:
+        src_crs = {}
+    if dst_crs is None:
+        dst_crs = {}
+
     _reproject(
         source,
         destination,


Test patch:
diff --git a/tests/test_warp.py b/tests/test_warp.py
--- a/tests/test_warp.py
+++ b/tests/test_warp.py
@@ -595,3 +595,61 @@ def test_reproject_unsupported_resampling_guass():
                 dst_transform=DST_TRANSFORM,
                 dst_crs=dst_crs,
                 resampling=Resampling.gauss)
+
+
+def test_reproject_crs_none():
+    """Reproject with crs is None should not cause segfault"""
+    src = np.random.random(25).reshape((1, 5, 5))
+    srcaff = Affine(1.1, 0.0, 0.0, 0.0, 1.1, 0.0)
+    srccrs = None
+    dst = np.empty(shape=(1, 11, 11))
+    dstaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
+    dstcrs = None
+
+    with rasterio.Env():
+        reproject(
+            src, dst,
+            src_transform=srcaff,
+            src_crs=srccrs,
+            dst_transform=dstaff,
+            dst_crs=dstcrs,
+            resampling=Resampling.nearest)
+
+
+def test_reproject_identity():
+    """Reproject with an identity matrix."""
+    # note the affines are both positive e, src is identity
+    src = np.random.random(25).reshape((1, 5, 5))
+    srcaff = Affine(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)  # Identity
+    srccrs = {'init': 'epsg:3857'}
+
+    dst = np.empty(shape=(1, 10, 10))
+    dstaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
+    dstcrs = {'init': 'epsg:3857'}
+
+    with rasterio.Env():
+        reproject(
+            src, dst,
+            src_transform=srcaff,
+            src_crs=srccrs,
+            dst_transform=dstaff,
+            dst_crs=dstcrs,
+            resampling=Resampling.nearest)
+
+    # note the affines are both positive e, dst is identity
+    src = np.random.random(100).reshape((1, 10, 10))
+    srcaff = Affine(0.5, 0.0, 0.0, 0.0, 0.5, 0.0)
+    srccrs = {'init': 'epsg:3857'}
+
+    dst = np.empty(shape=(1, 5, 5))
+    dstaff = Affine(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)  # Identity
+    dstcrs = {'init': 'epsg:3857'}
+
+    with rasterio.Env():
+        reproject(
+            src, dst,
+            src_transform=srcaff,
+            src_crs=srccrs,
+            dst_transform=dstaff,
+            dst_crs=dstcrs,
+            resampling=Resampling.nearest)