[Git][debian-gis-team/rasterio][master] 5 commits: New upstream version 1.1.5
Bas Couwenberg
gitlab at salsa.debian.org
Wed Jun 3 05:03:04 BST 2020
Bas Couwenberg pushed to branch master at Debian GIS Project / rasterio
Commits:
8d980ac1 by Bas Couwenberg at 2020-06-03T05:46:59+02:00
New upstream version 1.1.5
- - - - -
82e02ef9 by Bas Couwenberg at 2020-06-03T05:47:32+02:00
Update upstream source from tag 'upstream/1.1.5'
Update to upstream version '1.1.5'
with Debian dir 60bd2fbc494ac2766c71c536a53458f8a3a12387
- - - - -
9c7da62e by Bas Couwenberg at 2020-06-03T05:51:33+02:00
New upstream release.
- - - - -
14774078 by Bas Couwenberg at 2020-06-03T05:52:47+02:00
Refresh patches.
- - - - -
64ec8cc5 by Bas Couwenberg at 2020-06-03T05:52:58+02:00
Set distribution to unstable.
- - - - -
28 changed files:
- .travis.yml
- CHANGES.txt
- debian/changelog
- debian/patches/0001-Rename-rio-to-rasterio-Closes-788463.patch
- rasterio/__init__.py
- rasterio/_io.pxd
- rasterio/_io.pyx
- rasterio/_shim.pxd
- rasterio/_shim1.pyx
- rasterio/_warp.pyx
- rasterio/env.py
- rasterio/errors.py
- rasterio/gdal.pxi
- rasterio/io.py
- rasterio/merge.py
- rasterio/shim_rasterioex.pxi
- rasterio/vrt.py
- rasterio/warp.py
- requirements.txt
- setup.py
- tests/test__env.py
- tests/test_boundless_read.py
- tests/test_env.py
- tests/test_memoryfile.py
- tests/test_read_resample.py
- tests/test_rio_merge.py
- tests/test_warp.py
- tests/test_warpedvrt.py
Changes:
=====================================
.travis.yml
=====================================
@@ -17,16 +17,14 @@ jobs:
env: GDALVERSION="1.11.5" PROJVERSION="4.8.0"
- python: "2.7"
env: GDALVERSION="2.2.4" PROJVERSION="4.9.3"
- - python: "2.7"
- env: GDALVERSION="2.3.3" PROJVERSION="4.9.3"
- python: "3.6"
env: GDALVERSION="2.2.4" PROJVERSION="4.9.3"
- python: "3.6"
env: GDALVERSION="2.3.3" PROJVERSION="4.9.3"
- python: "3.6"
- env: GDALVERSION="2.4.3" PROJVERSION="4.9.3"
+ env: GDALVERSION="2.4.4" PROJVERSION="4.9.3"
- python: "3.6"
- env: GDALVERSION="3.0.1" PROJVERSION="6.1.1"
+ env: GDALVERSION="3.0.4" PROJVERSION="6.2.1"
addons:
apt:
=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,31 @@
Changes
=======
+1.1.5 (2020-06-02)
+------------------
+
+- Earlier versions of rasterio set the CHECK_WITH_INVERT_PROJ config option to
+ `True` by default. This is very rarely necessary and broke the GRIB format
+ driver (#1248), so we no longer set this option (#1942). Users of rasterio
+ 1.1.5 in combination with GDAL 1.11 and PROJ 4.8 may see some small
+ differences, compared to rasterio versions < 1.1.5, in results of warping
+ global datasets.
+- WarpedVRT can properly handle two use cases that weren't ruled out in version
+ 1.1.4: simple scaling of datasets and control over the scaling of reprojected
+ output (#1921, #1936).
+- The error in making boundless reads of datasets opened using the
+ OVERVIEW_LEVEL reported in #1929 has been resolved by #1939.
+- The pixel shift in reads from datasets reported in the user discussion group
+ and #1932, has been fixed (#1938).
+- We have extended the signature of merge's method function (#1933).
+- The MemoryFile implementation has been improved so that it can support
+ multi-part S3 downloads (#1926).
+- Members of the Resampling enum with a value > 7 can only be used in warp
+ operations (#1930). We now raise a ResamplingAlgorithmError if they are used
+ with non-warp read and writes.
+- To help users of poetry, the conditional requirements in setup.py have been
+ changed to use PEP 496 environment markers exclusively (#1777).
+
1.1.4 (2020-05-07)
------------------
=====================================
debian/changelog
=====================================
@@ -1,3 +1,11 @@
+rasterio (1.1.5-1) unstable; urgency=medium
+
+ * Team upload.
+ * New upstream release.
+ * Refresh patches.
+
+ -- Bas Couwenberg <sebastic at debian.org> Wed, 03 Jun 2020 05:52:48 +0200
+
rasterio (1.1.4-1) unstable; urgency=medium
* Team upload.
=====================================
debian/patches/0001-Rename-rio-to-rasterio-Closes-788463.patch
=====================================
@@ -9,7 +9,7 @@ There is already another package providing a binary "rio".
--- a/setup.py
+++ b/setup.py
-@@ -405,7 +405,7 @@ setup_args = dict(
+@@ -399,7 +399,7 @@ setup_args = dict(
packages=['rasterio', 'rasterio.rio'],
entry_points='''
[console_scripts]
=====================================
rasterio/__init__.py
=====================================
@@ -41,7 +41,7 @@ import rasterio.enums
import rasterio.path
__all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.1.4"
+__version__ = "1.1.5"
__gdal_version__ = gdal_version()
# Rasterio attaches NullHandler to the 'rasterio' logger and its
=====================================
rasterio/_io.pxd
=====================================
@@ -1,9 +1,9 @@
+include "gdal.pxi"
+
cimport numpy as np
from rasterio._base cimport DatasetBase
-include "gdal.pxi"
-
cdef class DatasetReaderBase(DatasetBase):
pass
@@ -32,6 +32,10 @@ cdef class InMemoryRaster:
cdef GDALRasterBandH band(self, int) except NULL
+cdef class MemoryFileBase:
+ cdef VSILFILE * _vsif
+
+
ctypedef np.uint8_t DTYPE_UBYTE_t
ctypedef np.uint16_t DTYPE_UINT16_t
ctypedef np.int16_t DTYPE_INT16_t
=====================================
rasterio/_io.pyx
=====================================
@@ -834,10 +834,10 @@ def silence_errors():
CPLPopErrorHandler()
-cdef class MemoryFileBase(object):
+cdef class MemoryFileBase:
"""Base for a BytesIO-like class backed by an in-memory file."""
- def __init__(self, file_or_bytes=None, filename=None, ext=''):
+ def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
"""A file in an in-memory filesystem.
Parameters
@@ -849,8 +849,9 @@ cdef class MemoryFileBase(object):
ext : str
A file extension for the in-memory file under /vsimem. Ignored if
filename was provided.
+
"""
- cdef VSILFILE *vsi_handle = NULL
+ cdef VSILFILE *fp = NULL
if file_or_bytes:
if hasattr(file_or_bytes, 'read'):
@@ -864,33 +865,37 @@ cdef class MemoryFileBase(object):
else:
initial_bytes = b''
+ # Make an in-memory directory specific to this dataset to help organize
+ # auxiliary files.
+ self._dirname = dirname or str(uuid4())
+ VSIMkdir("/vsimem/{0}".format(self._dirname).encode("utf-8"), 0666)
+
if filename:
# GDAL's SRTMHGT driver requires the filename to be "correct" (match
# the bounds being written)
- self.name = '/vsimem/{0}'.format(filename)
+ self.name = "/vsimem/{0}/{1}".format(self._dirname, filename)
else:
# GDAL 2.1 requires a .zip extension for zipped files.
- self.name = '/vsimem/{0}.{1}'.format(uuid4(), ext.lstrip('.'))
+ self.name = "/vsimem/{0}/{0}.{1}".format(self._dirname, ext.lstrip('.'))
self._path = self.name.encode('utf-8')
- self._pos = 0
- self.closed = False
self._initial_bytes = initial_bytes
cdef unsigned char *buffer = self._initial_bytes
if self._initial_bytes:
+ self._vsif = VSIFileFromMemBuffer(
+ self._path, buffer, len(self._initial_bytes), 0)
+ self.mode = "r"
- vsi_handle = VSIFileFromMemBuffer(
- self._path, buffer, len(self._initial_bytes), 0)
+ else:
+ self._vsif = VSIFOpenL(self._path, "w+")
+ self.mode = "w+"
- if vsi_handle == NULL:
- raise IOError(
- "Failed to create in-memory file using initial bytes.")
+ if self._vsif == NULL:
+ raise IOError("Failed to open in-memory file.")
- if VSIFCloseL(vsi_handle) != 0:
- raise IOError(
- "Failed to properly close in-memory file.")
+ self.closed = False
def exists(self):
"""Test if the in-memory file exists.
@@ -899,18 +904,10 @@ cdef class MemoryFileBase(object):
-------
bool
True if the in-memory file exists.
- """
- cdef VSILFILE *fp = NULL
- cdef const char *cypath = self._path
- with nogil:
- fp = VSIFOpenL(cypath, 'r')
-
- if fp != NULL:
- VSIFCloseL(fp)
- return True
- else:
- return False
+ """
+ cdef VSIStatBufL st_buf
+ return VSIStatL(self._path, &st_buf) == 0
def __len__(self):
"""Length of the file's buffer in number of bytes.
@@ -921,105 +918,65 @@ cdef class MemoryFileBase(object):
"""
return self.getbuffer().size
+ def getbuffer(self):
+ """Return a view on bytes of the file."""
+ cdef unsigned char *buffer = NULL
+ cdef vsi_l_offset buffer_len = 0
+ cdef np.uint8_t [:] buff_view
+
+ buffer = VSIGetMemFileBuffer(self._path, &buffer_len, 0)
+
+ if buffer == NULL or buffer_len == 0:
+ buff_view = np.array([], dtype='uint8')
+ else:
+ buff_view = <np.uint8_t[:buffer_len]>buffer
+ return buff_view
+
def close(self):
- """Close MemoryFile and release allocated memory."""
- VSIUnlink(self._path)
- self._pos = 0
- self._initial_bytes = None
+ if self._vsif != NULL:
+ VSIFCloseL(self._vsif)
+ self._vsif = NULL
+ VSIRmdir(self._dirname.encode("utf-8"))
self.closed = True
- def read(self, size=-1):
- """Read size bytes from MemoryFile."""
- cdef VSILFILE *fp = NULL
- # Return no bytes immediately if the position is at or past the
- # end of the file.
- length = len(self)
-
- if self._pos >= length:
- self._pos = length
- return b''
+ def seek(self, offset, whence=0):
+ return VSIFSeekL(self._vsif, offset, whence)
- if size == -1:
- size = length - self._pos
+ def tell(self):
+ if self._vsif != NULL:
+ return VSIFTellL(self._vsif)
else:
- size = min(size, length - self._pos)
+ return 0
- cdef unsigned char *buffer = <unsigned char *>CPLMalloc(size)
+ def read(self, size=-1):
+ """Read size bytes from MemoryFile."""
cdef bytes result
+ cdef unsigned char *buffer = NULL
+ cdef vsi_l_offset buffer_len = 0
- fp = VSIFOpenL(self._path, 'r')
+ if size < 0:
+ buffer = VSIGetMemFileBuffer(self._path, &buffer_len, 0)
+ size = buffer_len
- try:
- fp = exc_wrap_vsilfile(fp)
- if VSIFSeekL(fp, self._pos, 0) < 0:
- raise IOError(
- "Failed to seek to offset %s in %s.",
- self._pos, self.name)
+ buffer = <unsigned char *>CPLMalloc(size)
- objects_read = VSIFReadL(buffer, 1, size, fp)
+ try:
+ objects_read = VSIFReadL(buffer, 1, size, self._vsif)
result = <bytes>buffer[:objects_read]
finally:
- VSIFCloseL(fp)
CPLFree(buffer)
- self._pos += len(result)
return result
- def seek(self, offset, whence=0):
- """Seek to position in MemoryFile."""
- if whence == 0:
- pos = offset
- elif whence == 1:
- pos = self._pos + offset
- elif whence == 2:
- pos = len(self) - offset
- if pos < 0:
- raise ValueError("negative seek position: {}".format(pos))
- if pos > len(self):
- raise ValueError("seek position past end of file: {}".format(pos))
- self._pos = pos
- return self._pos
-
- def tell(self):
- """Tell current position in MemoryFile."""
- return self._pos
-
def write(self, data):
"""Write data bytes to MemoryFile"""
- cdef VSILFILE *fp = NULL
cdef const unsigned char *view = <bytes>data
n = len(data)
-
- if not self.exists():
- fp = exc_wrap_vsilfile(VSIFOpenL(self._path, 'w'))
- else:
- fp = exc_wrap_vsilfile(VSIFOpenL(self._path, 'r+'))
- if VSIFSeekL(fp, self._pos, 0) < 0:
- raise IOError(
- "Failed to seek to offset %s in %s.", self._pos, self.name)
-
- result = VSIFWriteL(view, 1, n, fp)
- VSIFFlushL(fp)
- VSIFCloseL(fp)
-
- self._pos += result
+ result = VSIFWriteL(view, 1, n, self._vsif)
+ VSIFFlushL(self._vsif)
return result
- def getbuffer(self):
- """Return a view on bytes of the file."""
- cdef unsigned char *buffer = NULL
- cdef vsi_l_offset buffer_len = 0
- cdef np.uint8_t [:] buff_view
-
- buffer = VSIGetMemFileBuffer(self._path, &buffer_len, 0)
-
- if buffer == NULL or buffer_len == 0:
- buff_view = np.array([], dtype='uint8')
- else:
- buff_view = <np.uint8_t[:buffer_len]>buffer
- return buff_view
-
cdef class DatasetWriterBase(DatasetReaderBase):
"""Read-write access to raster data and metadata
=====================================
rasterio/_shim.pxd
=====================================
@@ -2,9 +2,9 @@ include "gdal.pxi"
cdef GDALDatasetH open_dataset(object filename, int mode, object allowed_drivers, object open_options, object siblings) except NULL
cdef int delete_nodata_value(GDALRasterBandH hBand) except 3
-cdef int io_band(GDALRasterBandH band, int mode, float xoff, float yoff, float width, float height, object data, int resampling=*) except -1
-cdef int io_multi_band(GDALDatasetH hds, int mode, float xoff, float yoff, float width, float height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
-cdef int io_multi_mask(GDALDatasetH hds, int mode, float xoff, float yoff, float width, float height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
+cdef int io_band(GDALRasterBandH band, int mode, double xoff, double yoff, double width, double height, object data, int resampling=*) except -1
+cdef int io_multi_band(GDALDatasetH hds, int mode, double xoff, double yoff, double width, double height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
+cdef int io_multi_mask(GDALDatasetH hds, int mode, double xoff, double yoff, double width, double height, object data, Py_ssize_t[:] indexes, int resampling=*) except -1
cdef const char* osr_get_name(OGRSpatialReferenceH hSrs)
cdef void osr_set_traditional_axis_mapping_strategy(OGRSpatialReferenceH hSrs)
cdef void set_proj_search_path(object path)
=====================================
rasterio/_shim1.pyx
=====================================
@@ -47,8 +47,8 @@ cdef int delete_nodata_value(GDALRasterBandH hBand) except 3:
cdef int io_band(
- GDALRasterBandH band, int mode, float x0, float y0,
- float width, float height, object data, int resampling=0) except -1:
+ GDALRasterBandH band, int mode, double x0, double y0,
+ double width, double height, object data, int resampling=0) except -1:
"""Read or write a region of data for the band.
Implicit are
@@ -83,8 +83,8 @@ cdef int io_band(
cdef int io_multi_band(
- GDALDatasetH hds, int mode, float x0, float y0, float width,
- float height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
+ GDALDatasetH hds, int mode, double x0, double y0, double width,
+ double height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
"""Read or write a region of data for multiple bands.
Implicit are
@@ -130,8 +130,8 @@ cdef int io_multi_band(
cdef int io_multi_mask(
- GDALDatasetH hds, int mode, float x0, float y0, float width,
- float height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
+ GDALDatasetH hds, int mode, double x0, double y0, double width,
+ double height, object data, Py_ssize_t[:] indexes, int resampling=0) except -1:
"""Read or write a region of data for multiple band masks.
Implicit are
=====================================
rasterio/_warp.pyx
=====================================
@@ -8,7 +8,7 @@ import uuid
import warnings
import xml.etree.ElementTree as ET
-from affine import identity
+from affine import Affine, identity
import numpy as np
import rasterio
@@ -729,19 +729,17 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
nodata = dst_nodata
# Deprecate dst_width.
- if dst_width is not None:
+ if dst_width is not None and width is None:
warnings.warn(
"dst_width will be removed in 1.1, use width",
RasterioDeprecationWarning)
- if width is None:
width = dst_width
# Deprecate dst_height.
- if dst_height is not None:
+ if dst_height is not None and height is None:
warnings.warn(
"dst_height will be removed in 1.1, use height",
RasterioDeprecationWarning)
- if height is None:
height = dst_height
# Deprecate dst_transform.
@@ -811,15 +809,6 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
if not self.src_transform:
self.src_transform = self.src_dataset.transform
- if self.dst_transform:
- t = self.src_transform.to_gdal()
- for i in range(6):
- src_gt[i] = t[i]
-
- t = self.dst_transform.to_gdal()
- for i in range(6):
- dst_gt[i] = t[i]
-
if not self.src_crs:
self.src_crs = self.src_dataset.crs
@@ -856,11 +845,12 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
if GDALGetRasterColorInterpretation(hBand) == GCI_AlphaBand:
src_alpha_band = bidx
+ # Adding an alpha band when the source has one is trouble.
+ # It will result in suprisingly unmasked data. We will
+ # raise an exception instead.
+
if add_alpha:
- # Adding an alpha band when the source has one is trouble.
- # It will result in suprisingly unmasked data. We will
- # raise an exception instead.
if src_alpha_band:
raise WarpOptionsError(
"The VRT already has an alpha band, adding a new one is not supported")
@@ -886,56 +876,98 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
psWOptions.hSrcDS = hds
- try:
+ # We handle four different use cases.
+ #
+ # 1. Destination transform, height, and width are provided by
+ # the caller.
+ # 2. Pure scaling: source and destination CRS are the same,
+ # destination height and width are provided by the caller,
+ # destination transform is not provided; it is computed by
+ # scaling the source transform.
+ # 3. Warp with scaling: CRS are different, destination height
+ # and width are provided by the caller, destination transform
+ # is computed and scaled to preserve given dimensions.
+ # 4. Warp with destination height, width, and transform
+ # auto-generated by GDAL.
+
+ # Case 4
+ if not self.dst_transform and (not self.dst_width or not self.dst_height):
- if self.dst_width and self.dst_height and self.dst_transform:
- # set up transform args (otherwise handled in
- # GDALAutoCreateWarpedVRT)
- try:
+ with nogil:
+ hds_warped = GDALAutoCreateWarpedVRT(hds, src_crs_wkt, dst_crs_wkt, c_resampling, c_tolerance, psWOptions)
- hTransformArg = exc_wrap_pointer(
- GDALCreateGenImgProjTransformer3(
- src_crs_wkt, src_gt, dst_crs_wkt, dst_gt))
+ else:
- if c_tolerance > 0.0:
- hTransformArg = exc_wrap_pointer(
- GDALCreateApproxTransformer(
- GDALGenImgProjTransform,
- hTransformArg,
- c_tolerance))
+ # Case 1
+ if self.dst_transform and self.dst_width and self.dst_height:
+ pass
- psWOptions.pfnTransformer = GDALApproxTransform
+ # Case 2
+ elif self.src_crs == self.dst_crs and self.dst_width and self.dst_height and not self.dst_transform:
- GDALApproxTransformerOwnsSubtransformer(
- hTransformArg, 1)
+ self.dst_transform = Affine.scale(self.src_dataset.width / self.dst_width, self.src_dataset.height / self.dst_height) * self.src_transform
- log.debug("Created transformer and options.")
- psWOptions.pTransformerArg = hTransformArg
+ # Case 3
+ elif self.src_crs != self.dst_crs and self.dst_width and self.dst_height and not self.dst_transform:
- except Exception:
- GDALDestroyApproxTransformer(hTransformArg)
- raise
+ left, bottom, right, top = self.src_dataset.bounds
+ self.dst_transform, width, height = _calculate_default_transform(self.src_crs, self.dst_crs, self.src_dataset.width, self.src_dataset.height, left=left, bottom=bottom, right=right, top=top)
+ self.dst_transform = Affine.scale(width / self.dst_width, height / self.dst_height ) * self.dst_transform
- with nogil:
- hds_warped = GDALCreateWarpedVRT(
- hds, c_width, c_height, dst_gt, psWOptions)
- GDALSetProjection(hds_warped, dst_crs_wkt)
+ # If we get here it's because the tests above are buggy. We raise a Python exception to indicate that.
+ else:
+ raise RuntimeError(
+ "Parameterization error: src_crs={!r}, dst_crs={!r}, dst_width={!r}, dst_height={!r}, dst_transform={!r}".format(self.src_crs, self.dst_crs, self.dst_width, self.dst_height, self.dst_transform))
- self._hds = exc_wrap_pointer(hds_warped)
+ t = self.src_transform.to_gdal()
+ for i in range(6):
+ src_gt[i] = t[i]
+
+ t = self.dst_transform.to_gdal()
+ for i in range(6):
+ dst_gt[i] = t[i]
+
+ try:
+ hTransformArg = exc_wrap_pointer(
+ GDALCreateGenImgProjTransformer3(
+ src_crs_wkt, src_gt, dst_crs_wkt, dst_gt))
+
+ if c_tolerance > 0.0:
+
+ hTransformArg = exc_wrap_pointer(
+ GDALCreateApproxTransformer(
+ GDALGenImgProjTransform,
+ hTransformArg,
+ c_tolerance))
+
+ psWOptions.pfnTransformer = GDALApproxTransform
+ GDALApproxTransformerOwnsSubtransformer(hTransformArg, 1)
+
+ except Exception:
+ CPLFree(dst_crs_wkt)
+ CPLFree(src_crs_wkt)
+ CSLDestroy(c_warp_extras)
+ if psWOptions != NULL:
+ GDALDestroyWarpOptions(psWOptions)
else:
- with nogil:
- hds_warped = GDALAutoCreateWarpedVRT(
- hds, src_crs_wkt, dst_crs_wkt, c_resampling,
- c_tolerance, psWOptions)
+ psWOptions.pTransformerArg = hTransformArg
+
+ with nogil:
+ hds_warped = GDALCreateWarpedVRT(hds, c_width, c_height, dst_gt, psWOptions)
+ GDALSetProjection(hds_warped, dst_crs_wkt)
- self._hds = exc_wrap_pointer(hds_warped)
+ # End of the 4 cases.
+
+ try:
+ self._hds = exc_wrap_pointer(hds_warped)
except CPLE_OpenFailedError as err:
raise RasterioIOError(err.errmsg)
finally:
CPLFree(dst_crs_wkt)
+ CPLFree(src_crs_wkt)
CSLDestroy(c_warp_extras)
if psWOptions != NULL:
GDALDestroyWarpOptions(psWOptions)
@@ -946,6 +978,7 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
delete_nodata_value(self.band(i))
except NotImplementedError as exc:
log.warn(str(exc))
+
else:
for i in self.indexes:
GDALSetRasterNoDataValue(self.band(i), self.dst_nodata)
@@ -1089,7 +1122,7 @@ def _suggested_proxy_vrt_doc(width, height, transform=None, crs=None, gcps=None)
gcp.attrib['X'] = str(point.x)
gcp.attrib['Y'] = str(point.y)
gcp.attrib['Z'] = str(point.z)
- else:
+ elif transform:
geotransform = ET.SubElement(vrtdataset, 'GeoTransform')
geotransform.text = ','.join([str(v) for v in transform.to_gdal()])
=====================================
rasterio/env.py
=====================================
@@ -98,7 +98,6 @@ class Env(object):
dict
"""
return {
- 'CHECK_WITH_INVERT_PROJ': True,
'GTIFF_IMPLICIT_JPEG_OVR': False,
"RASTERIO_ENV": True
}
=====================================
rasterio/errors.py
=====================================
@@ -118,3 +118,7 @@ class DatasetAttributeError(RasterioError, NotImplementedError):
class PathError(RasterioError):
"""Raised when a dataset path is malformed or invalid"""
+
+
+class ResamplingAlgorithmError(RasterioError):
+ """Raised when a resampling algorithm is invalid or inapplicable"""
=====================================
rasterio/gdal.pxi
=====================================
@@ -53,10 +53,16 @@ cdef extern from "cpl_string.h" nogil:
const char* CPLParseNameValue(const char *pszNameValue, char **ppszKey)
+cdef extern from "sys/stat.h" nogil:
+ struct stat:
+ pass
+
+
cdef extern from "cpl_vsi.h" nogil:
ctypedef int vsi_l_offset
ctypedef FILE VSILFILE
+ ctypedef stat VSIStatBufL
unsigned char *VSIGetMemFileBuffer(const char *path,
vsi_l_offset *data_len,
@@ -66,14 +72,15 @@ cdef extern from "cpl_vsi.h" nogil:
VSILFILE* VSIFOpenL(const char *path, const char *mode)
int VSIFCloseL(VSILFILE *fp)
int VSIUnlink(const char *path)
-
+ int VSIMkdir(const char *path, long mode)
+ int VSIRmdir(const char *path)
int VSIFFlushL(VSILFILE *fp)
size_t VSIFReadL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp)
int VSIFSeekL(VSILFILE *fp, vsi_l_offset nOffset, int nWhence)
vsi_l_offset VSIFTellL(VSILFILE *fp)
int VSIFTruncateL(VSILFILE *fp, vsi_l_offset nNewSize)
size_t VSIFWriteL(void *buffer, size_t nSize, size_t nCount, VSILFILE *fp)
-
+ int VSIStatL(const char *pszFilename, VSIStatBufL *psStatBuf)
cdef extern from "ogr_srs_api.h" nogil:
=====================================
rasterio/io.py
=====================================
@@ -85,7 +85,7 @@ class MemoryFile(MemoryFileBase):
'width': 791}
"""
- def __init__(self, file_or_bytes=None, filename=None, ext=''):
+ def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
"""Create a new file in memory
Parameters
@@ -102,7 +102,7 @@ class MemoryFile(MemoryFileBase):
MemoryFile
"""
super(MemoryFile, self).__init__(
- file_or_bytes=file_or_bytes, filename=filename, ext=ext)
+ file_or_bytes=file_or_bytes, dirname=dirname, filename=filename, ext=ext)
@ensure_env
def open(self, driver=None, width=None, height=None, count=None, crs=None,
@@ -125,7 +125,7 @@ class MemoryFile(MemoryFileBase):
if self.closed:
raise IOError("I/O operation on closed file.")
- if self.exists():
+ if len(self) > 0:
log.debug("VSI path: {}".format(mempath.path))
return DatasetReader(mempath, driver=driver, sharing=sharing, **kwargs)
else:
=====================================
rasterio/merge.py
=====================================
@@ -16,8 +16,8 @@ logger = logging.getLogger(__name__)
MERGE_METHODS = ('first', 'last', 'min', 'max')
-def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=None,
- method='first'):
+def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10,
+ indexes=None, output_count=None, method='first'):
"""Copy valid pixels from input files to an output file.
All files must have the same number of bands, data type, and
@@ -45,10 +45,16 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
nodata: float, optional
nodata value to use in output file. If not set, uses the nodata value
in the first input raster.
+ dtype: numpy dtype or string
+ dtype to use in outputfile. If not set, uses the dtype value in the
+ first input raster.
precision: float, optional
Number of decimal points of precision when computing inverse transform.
indexes : list of ints or a single int, optional
bands to read and merge
+ 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
method : str or callable
pre-defined method:
first: reverse painting
@@ -57,7 +63,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
max: pixel-wise max of existing and new
or custom callable with signature:
- def function(old_data, new_data, old_nodata, new_nodata):
+ def function(old_data, new_data, old_nodata, new_nodata, index=None, roff=None, coff=None):
Parameters
----------
@@ -69,6 +75,12 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
old_nodata, new_data : array_like
boolean masks where old/new data is nodata
same shape as old_data
+ index: int
+ index of the current dataset within the merged dataset collection
+ roff: int
+ row offset in base array
+ coff: int
+ column offset in base array
Returns
-------
@@ -86,7 +98,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
first = datasets[0]
first_res = first.res
nodataval = first.nodatavals[0]
- dtype = first.dtypes[0]
+ dt = first.dtypes[0]
if method not in MERGE_METHODS and not callable(method):
raise ValueError('Unknown method {0}, must be one of {1} or callable'
@@ -94,11 +106,14 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
# Determine output band count
if indexes is None:
- output_count = first.count
+ src_count = first.count
elif isinstance(indexes, int):
- output_count = 1
+ src_count = indexes
else:
- output_count = len(indexes)
+ src_count = len(indexes)
+
+ if not output_count:
+ output_count = src_count
# Extent from option or extent of all inputs
if bounds:
@@ -137,8 +152,12 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
logger.debug("Output width: %d, height: %d", output_width, output_height)
logger.debug("Adjusted bounds: %r", (dst_w, dst_s, dst_e, dst_n))
+ if dtype is not None:
+ dt = dtype
+ logger.debug("Set dtype: %s", dt)
+
# create destination array
- dest = np.zeros((output_count, output_height, output_width), dtype=dtype)
+ dest = np.zeros((output_count, output_height, output_width), dtype=dt)
if nodata is not None:
nodataval = nodata
@@ -168,17 +187,17 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
nodataval = 0
if method == 'first':
- def copyto(old_data, new_data, old_nodata, new_nodata):
+ def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
mask = np.logical_and(old_nodata, ~new_nodata)
old_data[mask] = new_data[mask]
elif method == 'last':
- def copyto(old_data, new_data, old_nodata, new_nodata):
+ def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
mask = ~new_nodata
old_data[mask] = new_data[mask]
elif method == 'min':
- def copyto(old_data, new_data, old_nodata, new_nodata):
+ def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
mask = np.logical_and(~old_nodata, ~new_nodata)
old_data[mask] = np.minimum(old_data[mask], new_data[mask])
@@ -186,7 +205,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
old_data[mask] = new_data[mask]
elif method == 'max':
- def copyto(old_data, new_data, old_nodata, new_nodata):
+ def copyto(old_data, new_data, old_nodata, new_nodata, **kwargs):
mask = np.logical_and(~old_nodata, ~new_nodata)
old_data[mask] = np.maximum(old_data[mask], new_data[mask])
@@ -199,7 +218,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
else:
raise ValueError(method)
- for src in datasets:
+ for idx, src in enumerate(datasets):
# Real World (tm) use of boundless reads.
# This approach uses the maximum amount of memory to solve the
# problem. Making it more efficient is a TODO.
@@ -226,7 +245,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
# 4. Read data in source window into temp
trows, tcols = (
int(round(dst_window.height)), int(round(dst_window.width)))
- temp_shape = (output_count, trows, tcols)
+ temp_shape = (src_count, trows, tcols)
temp = src.read(out_shape=temp_shape, window=src_window,
boundless=False, masked=True, indexes=indexes)
@@ -242,6 +261,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, precision=10, indexes=No
region_nodata = region == nodataval
temp_nodata = temp.mask
- copyto(region, temp, region_nodata, temp_nodata)
+ copyto(region, temp, region_nodata, temp_nodata,
+ index=idx, roff=roff, coff=coff)
return dest, output_transform
=====================================
rasterio/shim_rasterioex.pxi
=====================================
@@ -3,6 +3,7 @@
from rasterio import dtypes
from rasterio.enums import Resampling
+from rasterio.errors import ResamplingAlgorithmError
cimport numpy as np
@@ -32,8 +33,8 @@ cdef extern from "gdal.h" nogil:
cdef CPLErr GDALDatasetRasterIOEx(GDALDatasetH hDS, GDALRWFlag eRWFlag, int nDSXOff, int nDSYOff, int nDSXSize, int nDSYSize, void *pBuffer, int nBXSize, int nBYSize, GDALDataType eBDataType, int nBandCount, int *panBandCount, GSpacing nPixelSpace, GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
-cdef int io_band(GDALRasterBandH band, int mode, float x0, float y0,
- float width, float height, object data, int resampling=0) except -1:
+cdef int io_band(GDALRasterBandH band, int mode, double x0, double y0,
+ double width, double height, object data, int resampling=0) except -1:
"""Read or write a region of data for the band.
Implicit are
@@ -43,7 +44,11 @@ cdef int io_band(GDALRasterBandH band, int mode, float x0, float y0,
The striding of `data` is passed to GDAL so that it can navigate
the layout of ndarray views.
+
"""
+ if resampling > 7:
+ raise ResamplingAlgorithmError("{!r} can be used for warp operations but not for reads and writes".format(Resampling(resampling)))
+
# GDAL handles all the buffering indexing, so a typed memoryview,
# as in previous versions, isn't needed.
cdef void *buf = <void *>np.PyArray_DATA(data)
@@ -78,8 +83,8 @@ cdef int io_band(GDALRasterBandH band, int mode, float x0, float y0,
return exc_wrap_int(retval)
-cdef int io_multi_band(GDALDatasetH hds, int mode, float x0, float y0,
- float width, float height, object data,
+cdef int io_multi_band(GDALDatasetH hds, int mode, double x0, double y0,
+ double width, double height, object data,
Py_ssize_t[:] indexes, int resampling=0) except -1:
"""Read or write a region of data for multiple bands.
@@ -90,7 +95,11 @@ cdef int io_multi_band(GDALDatasetH hds, int mode, float x0, float y0,
The striding of `data` is passed to GDAL so that it can navigate
the layout of ndarray views.
+
"""
+ if resampling > 7:
+ raise ResamplingAlgorithmError("{!r} can be used for warp operations but not for reads and writes".format(Resampling(resampling)))
+
cdef int i = 0
cdef int retval = 3
cdef int *bandmap = NULL
@@ -136,8 +145,8 @@ cdef int io_multi_band(GDALDatasetH hds, int mode, float x0, float y0,
CPLFree(bandmap)
-cdef int io_multi_mask(GDALDatasetH hds, int mode, float x0, float y0,
- float width, float height, object data,
+cdef int io_multi_mask(GDALDatasetH hds, int mode, double x0, double y0,
+ double width, double height, object data,
Py_ssize_t[:] indexes, int resampling=0) except -1:
"""Read or write a region of data for multiple band masks.
@@ -148,7 +157,11 @@ cdef int io_multi_mask(GDALDatasetH hds, int mode, float x0, float y0,
The striding of `data` is passed to GDAL so that it can navigate
the layout of ndarray views.
+
"""
+ if resampling > 7:
+ raise ResamplingAlgorithmError("{!r} can be used for warp operations but not for reads and writes".format(Resampling(resampling)))
+
cdef int i = 0
cdef int j = 0
cdef int retval = 3
=====================================
rasterio/vrt.py
=====================================
@@ -232,6 +232,13 @@ def _boundless_vrt_doc(
nodata_elem = ET.SubElement(complexsource, 'NODATA')
nodata_elem.text = str(src_dataset.nodata)
+ if src_dataset.options is not None:
+ openoptions = ET.SubElement(complexsource, 'OpenOptions')
+ for ookey, oovalue in src_dataset.options.items():
+ ooi = ET.SubElement(openoptions, 'OOI')
+ ooi.attrib['key'] = str(ookey)
+ ooi.text = str(oovalue)
+
# Effectively replaces all values of the source dataset with
# 255. Due to GDAL optimizations, the source dataset will not
# be read, so we get a performance improvement.
=====================================
rasterio/warp.py
=====================================
@@ -453,7 +453,7 @@ def calculate_default_transform(
if any(x is None for x in (left, bottom, right, top)) and not gcps:
raise ValueError("Either four bounding values or ground control points"
"must be specified")
-
+
if (dst_width is None) != (dst_height is None):
raise ValueError("Either dst_width and dst_height must be specified "
"or none of them.")
@@ -467,7 +467,8 @@ def calculate_default_transform(
raise ValueError("Resolution cannot be used with dst_width and dst_height.")
dst_affine, dst_width, dst_height = _calculate_default_transform(
- src_crs, dst_crs, width, height, left, bottom, right, top, gcps)
+ src_crs, dst_crs, width, height, left, bottom, right, top, gcps
+ )
# If resolution is specified, Keep upper-left anchored
# adjust the transform resolutions
=====================================
requirements.txt
=====================================
@@ -1,5 +1,5 @@
affine~=2.3.0
-attrs>=17.4.0
+attrs>=19.2.0
boto3>=1.2.4
click==7.0
click-plugins
=====================================
setup.py
=====================================
@@ -352,10 +352,8 @@ with open('README.rst') as f:
# Runtime requirements.
inst_reqs = [
- 'affine', 'attrs', 'click>=4.0,<8', 'cligj>=0.5', 'numpy', 'snuggs>=1.4.1', 'click-plugins']
-
-if sys.version_info < (3, 4):
- inst_reqs.append('enum34')
+ 'affine', 'attrs', 'click>=4.0,<8', 'cligj>=0.5', 'numpy', 'snuggs>=1.4.1', 'click-plugins',
+ 'enum34 ; python_version < "3.4"']
extra_reqs = {
'ipython': ['ipython>=2.0'],
@@ -363,13 +361,9 @@ extra_reqs = {
'plot': ['matplotlib'],
'test': [
'pytest>=2.8.2', 'pytest-cov>=2.2.0', 'boto3>=1.2.4', 'packaging',
- 'hypothesis'],
+ 'hypothesis', 'futures;python_version<"3.2"', 'mock;python_version<"3.2"'],
'docs': ['ghp-import', 'numpydoc', 'sphinx', 'sphinx-rtd-theme']}
-# Add futures to 'test' for Python < 3.2.
-if sys.version_info < (3, 2):
- extra_reqs['test'].extend(['futures', 'mock'])
-
# Add all extra requirements
extra_reqs['all'] = list(set(itertools.chain(*extra_reqs.values())))
=====================================
tests/test__env.py
=====================================
@@ -36,6 +36,7 @@ def mock_debian(tmpdir):
tmpdir.ensure("share/gdal/2.3/header.dxf")
tmpdir.ensure("share/gdal/2.4/header.dxf")
tmpdir.ensure("share/gdal/3.0/header.dxf")
+ tmpdir.ensure("share/gdal/3.1/header.dxf")
tmpdir.ensure("share/proj/epsg")
return tmpdir
=====================================
tests/test_boundless_read.py
=====================================
@@ -8,7 +8,7 @@ import pytest
import rasterio
from rasterio.windows import Window
-from .conftest import requires_gdal21
+from .conftest import requires_gdal21, gdal_version
@requires_gdal21(reason="Pixel equality tests require float windows and GDAL 2.1")
@@ -115,3 +115,16 @@ def test_boundless_masked_fill_value_overview_masks():
data = src.read(1, masked=True, boundless=True, window=Window(-300, -335, 1000, 1000), fill_value=5, out_shape=(512, 512))
assert data.fill_value == 5
assert data.mask[:, 0].all()
+
+
+ at pytest.mark.xfail(
+ gdal_version.major == 1,
+ reason="GDAL versions < 2 do not support OVERVIEW_LEVEL open option",
+)
+def test_boundless_open_options():
+ """Open options are taken into account"""
+ with rasterio.open("tests/data/cogeo.tif", overview_level=1) as src:
+ data1 = src.read(1, boundless=True)
+ with rasterio.open("tests/data/cogeo.tif", overview_level=2) as src:
+ data2 = src.read(1, boundless=True)
+ assert not numpy.array_equal(data1, data2)
=====================================
tests/test_env.py
=====================================
@@ -204,7 +204,6 @@ def test_env_defaults(gdalenv):
assert env.options['foo'] == 'x'
assert not env.context_options
with env:
- assert get_gdal_config('CHECK_WITH_INVERT_PROJ') is True
assert get_gdal_config('GTIFF_IMPLICIT_JPEG_OVR') is False
assert get_gdal_config("RASTERIO_ENV") is True
@@ -781,6 +780,7 @@ def test_require_gdal_version_chaining():
assert message in exc_info.value.args[0]
+ at pytest.mark.network
def test_rio_env_no_credentials(tmpdir, monkeypatch, runner):
"""Confirm that we can get drivers without any credentials"""
credentials_file = tmpdir.join('credentials')
=====================================
tests/test_memoryfile.py
=====================================
@@ -49,6 +49,13 @@ def rgb_data_and_profile(path_rgb_byte_tif):
return data, profile
+def test_initial_empty():
+ with MemoryFile() as memfile:
+ assert len(memfile) == 0
+ assert len(memfile.getbuffer()) == 0
+ assert memfile.tell() == 0
+
+
def test_initial_not_bytes():
"""Creating a MemoryFile from not bytes fails."""
with pytest.raises(TypeError):
@@ -116,6 +123,21 @@ def test_non_initial_bytes_in_two(rgb_file_bytes):
assert src.read().shape == (3, 718, 791)
+def test_non_initial_bytes_in_two_reverse(rgb_file_bytes):
+ """MemoryFile contents can be read from bytes in two steps, tail first, and opened.
+ Demonstrates fix of #1926."""
+ with MemoryFile() as memfile:
+ memfile.seek(600000)
+ assert memfile.write(rgb_file_bytes[600000:]) == len(rgb_file_bytes) - 600000
+ memfile.seek(0)
+ assert memfile.write(rgb_file_bytes[:600000]) == 600000
+ with memfile.open() as src:
+ assert src.driver == "GTiff"
+ assert src.count == 3
+ assert src.dtypes == ("uint8", "uint8", "uint8")
+ assert src.read().shape == (3, 718, 791)
+
+
def test_no_initial_bytes(rgb_data_and_profile):
"""An empty MemoryFile can be opened and written into."""
data, profile = rgb_data_and_profile
@@ -264,10 +286,10 @@ def test_memfile_copyfiles(path_rgb_msk_byte_tif):
"""Multiple files can be copied to a MemoryFile using copyfiles"""
with rasterio.open(path_rgb_msk_byte_tif) as src:
src_basename = os.path.basename(src.name)
- with MemoryFile(filename=src_basename) as memfile:
+ with MemoryFile(dirname="foo", filename=src_basename) as memfile:
copyfiles(src.name, memfile.name)
with memfile.open() as rgb2:
- assert sorted(rgb2.files) == sorted(['/vsimem/{}'.format(src_basename), '/vsimem/{}.msk'.format(src_basename)])
+ assert sorted(rgb2.files) == sorted(['/vsimem/foo/{}'.format(src_basename), '/vsimem/foo/{}.msk'.format(src_basename)])
def test_multi_memfile(path_rgb_msk_byte_tif):
@@ -277,9 +299,9 @@ def test_multi_memfile(path_rgb_msk_byte_tif):
with open(path_rgb_msk_byte_tif + '.msk', 'rb') as msk_fp:
msk_bytes = msk_fp.read()
- with MemoryFile(tif_bytes, filename='foo.tif') as tifmemfile, MemoryFile(msk_bytes, filename='foo.tif.msk') as mskmemfile:
+ with MemoryFile(tif_bytes, dirname="bar", filename='foo.tif') as tifmemfile, MemoryFile(msk_bytes, dirname="bar", filename='foo.tif.msk') as mskmemfile:
with tifmemfile.open() as src:
- assert sorted(src.files) == sorted(['/vsimem/foo.tif', '/vsimem/foo.tif.msk'])
+ assert sorted(os.path.basename(fn) for fn in src.files) == sorted(['foo.tif', 'foo.tif.msk'])
assert src.mask_flag_enums == ([MaskFlags.per_dataset],) * 3
=====================================
tests/test_read_resample.py
=====================================
@@ -4,9 +4,11 @@ that it does this correctly.
"""
import numpy as np
+import pytest
import rasterio
from rasterio.enums import Resampling
+from rasterio.errors import ResamplingAlgorithmError
from rasterio.windows import Window
from .conftest import requires_gdal2
@@ -86,3 +88,11 @@ def test_float_window():
out_shape = (401, 401)
window = Window(300.5, 300.5, 200.5, 200.5)
s.read(1, window=window, out_shape=out_shape)
+
+
+ at requires_gdal2
+def test_resampling_alg_error():
+ """Get an exception instead of a crash when using warp-only algs for read or write, see issue #1930"""
+ with pytest.raises(ResamplingAlgorithmError):
+ with rasterio.open("tests/data/RGB.byte.tif") as src:
+ src.read(1, out_shape=(1, 10, 10), resampling=Resampling.max)
=====================================
tests/test_rio_merge.py
=====================================
@@ -16,7 +16,7 @@ from rasterio.merge import merge
from rasterio.rio.main import main_group
from rasterio.transform import Affine
-from .conftest import requires_gdal22
+from .conftest import requires_gdal22, gdal_version
# Fixture to create test datasets within temporary directory
@@ -285,6 +285,41 @@ def test_merge_overlapping(test_data_dir_overlapping):
assert np.all(data == expected)
+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
+
+ def mycallable(old_data, new_data, old_nodata, new_nodata,
+ index=None, roff=None, coff=None):
+ assert old_data.shape[0] == 5
+ assert new_data.shape[0] == 1
+ assert test_merge_overlapping_callable_long.index == index
+ test_merge_overlapping_callable_long.index += 1
+
+ merge(datasets, output_count=5, method=mycallable)
+
+
+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
+ # update additional band that we specified in output_count
+ old_data[3, :, :] += index
+
+ arr, _ = merge(datasets, output_count=output_count, method=mycallable, dtype=np.uint64)
+
+ np.testing.assert_array_equal(np.mean(arr[:3], axis=0), 518)
+ np.testing.assert_array_equal(arr[3, :, :], 3)
+
+
# Fixture to create test datasets within temporary directory
@fixture(scope='function')
def test_data_dir_float(tmpdir):
@@ -443,6 +478,10 @@ def test_merge_tiny_res_bounds(tiffs):
assert data[0, 1, 1] == 0
+ at pytest.mark.xfail(
+ gdal_version.major == 1,
+ reason="GDAL versions < 2 do not support data read/write with float sizes and offsets",
+)
def test_merge_rgb(tmpdir):
"""Get back original image"""
outputname = str(tmpdir.join('merged.tif'))
@@ -466,6 +505,10 @@ def test_merge_tiny_intres(tiffs):
merge(datasets, res=2)
+ at pytest.mark.xfail(
+ gdal_version.major == 1,
+ reason="GDAL versions < 2 do not support data read/write with float sizes and offsets",
+)
@pytest.mark.parametrize("precision", [[], ["--precision", "9"]])
def test_merge_precision(tmpdir, precision):
"""See https://github.com/mapbox/rasterio/issues/1837"""
=====================================
tests/test_warp.py
=====================================
@@ -1,12 +1,12 @@
-import json
"""rasterio.warp module tests"""
+import json
import sys
-import pytest
from affine import Affine
import numpy as np
from numpy.testing import assert_almost_equal
+import pytest
import rasterio
from rasterio.control import GroundControlPoint
@@ -1062,18 +1062,18 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
# Expected count of nonzero pixels for each resampling method, based
# on running rasterio with each of the following configurations
expected = {
- Resampling.nearest: 438113,
- Resampling.bilinear: 439280,
- Resampling.cubic: 437888,
- Resampling.cubic_spline: 440475,
- Resampling.lanczos: 436001,
- Resampling.average: 439419,
- Resampling.mode: 437298,
- Resampling.max: 439464,
- Resampling.min: 436397,
- Resampling.med: 437194,
- Resampling.q1: 436397,
- Resampling.q3: 438948,
+ Resampling.nearest: [438113],
+ Resampling.bilinear: [439280],
+ Resampling.cubic: [437888],
+ Resampling.cubic_spline: [440475],
+ Resampling.lanczos: [436001],
+ Resampling.average: [439419, 439172], # latter value for GDAL 3.1
+ Resampling.mode: [437298],
+ Resampling.max: [439464],
+ Resampling.min: [436397],
+ Resampling.med: [437194],
+ Resampling.q1: [436397],
+ Resampling.q3: [438948],
}
with rasterio.open(path_rgb_byte_tif) as src:
@@ -1086,11 +1086,11 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
src_transform=src.transform,
src_crs=src.crs,
dst_transform=DST_TRANSFORM,
- dst_crs={"init": "epsg:3857"},
+ dst_crs="EPSG:3857",
resampling=method,
)
- assert np.count_nonzero(out) == expected[method]
+ assert np.count_nonzero(out) in expected[method]
@pytest.mark.parametrize("method", SUPPORTED_RESAMPLING)
@@ -1099,18 +1099,18 @@ def test_reproject_resampling_alpha(method):
# Expected count of nonzero pixels for each resampling method, based
# on running rasterio with each of the following configurations
expected = {
- Resampling.nearest: 438113,
- Resampling.bilinear: 439280,
- Resampling.cubic: 437888,
- Resampling.cubic_spline: 440475,
- Resampling.lanczos: 436001,
- Resampling.average: 439419,
- Resampling.mode: 437298,
- Resampling.max: 439464,
- Resampling.min: 436397,
- Resampling.med: 437194,
- Resampling.q1: 436397,
- Resampling.q3: 438948,
+ Resampling.nearest: [438113],
+ Resampling.bilinear: [439280],
+ Resampling.cubic: [437888],
+ Resampling.cubic_spline: [440475],
+ Resampling.lanczos: [436001],
+ Resampling.average: [439419, 439172], # latter value for GDAL 3.1
+ Resampling.mode: [437298],
+ Resampling.max: [439464],
+ Resampling.min: [436397],
+ Resampling.med: [437194],
+ Resampling.q1: [436397],
+ Resampling.q3: [438948],
}
with rasterio.open("tests/data/RGBA.byte.tif") as src:
@@ -1123,11 +1123,11 @@ def test_reproject_resampling_alpha(method):
src_transform=src.transform,
src_crs=src.crs,
dst_transform=DST_TRANSFORM,
- dst_crs={"init": "epsg:3857"},
+ dst_crs="EPSG:3857",
resampling=method,
)
- assert np.count_nonzero(out) == expected[method]
+ assert np.count_nonzero(out) in expected[method]
@pytest.mark.skipif(
@@ -1139,7 +1139,7 @@ def test_reproject_not_yet_supported_resampling(method):
with rasterio.open("tests/data/RGB.byte.tif") as src:
source = src.read(1)
- dst_crs = {"init": "epsg:32619"}
+ dst_crs = "EPSG:32619"
out = np.empty(src.shape, dtype=np.uint8)
with pytest.raises(GDALVersionError):
reproject(
@@ -1158,7 +1158,7 @@ def test_reproject_unsupported_resampling():
with rasterio.open("tests/data/RGB.byte.tif") as src:
source = src.read(1)
- dst_crs = {"init": "epsg:32619"}
+ dst_crs = "EPSG:32619"
out = np.empty(src.shape, dtype=np.uint8)
with pytest.raises(ValueError):
reproject(
@@ -1177,7 +1177,7 @@ def test_reproject_unsupported_resampling_guass():
with rasterio.open("tests/data/RGB.byte.tif") as src:
source = src.read(1)
- dst_crs = {"init": "epsg:32619"}
+ dst_crs = "EPSG:32619"
out = np.empty(src.shape, dtype=np.uint8)
with pytest.raises(ValueError):
reproject(
@@ -1199,9 +1199,9 @@ def test_resample_default_invert_proj(method):
with rasterio.open("tests/data/world.rgb.tif") as src:
source = src.read(1)
- profile = src.profile.copy()
+ profile = src.profile
- dst_crs = {"init": "epsg:32619"}
+ dst_crs = "EPSG:32619"
# Calculate the ideal dimensions and transformation in the new crs
dst_affine, dst_width, dst_height = calculate_default_transform(
@@ -1213,16 +1213,23 @@ def test_resample_default_invert_proj(method):
out = np.empty(shape=(dst_height, dst_width), dtype=np.uint8)
- out = np.empty(src.shape, dtype=np.uint8)
- reproject(
- source,
- out,
- src_transform=src.transform,
- src_crs=src.crs,
- dst_transform=dst_affine,
- dst_crs=dst_crs,
- resampling=method,
- )
+ # GDAL 1.11 needs to have this config option set on to match the
+ # default results in later versions.
+ if gdal_version.major == 1:
+ options = dict(CHECK_WITH_INVERT_PROJ=True)
+ else:
+ options = {}
+
+ with rasterio.Env(**options):
+ reproject(
+ source,
+ out,
+ src_transform=src.transform,
+ src_crs=src.crs,
+ dst_transform=dst_affine,
+ dst_crs=dst_crs,
+ resampling=method,
+ )
assert out.mean() > 0
@@ -1233,7 +1240,7 @@ def test_target_aligned_pixels():
source = src.read(1)
profile = src.profile.copy()
- dst_crs = {"init": "epsg:3857"}
+ dst_crs = "EPSG:3857"
with rasterio.Env(CHECK_WITH_INVERT_PROJ=False):
# Calculate the ideal dimensions and transformation in the new crs
@@ -1289,7 +1296,7 @@ def test_resample_no_invert_proj(method):
source = src.read(1)
profile = src.profile.copy()
- dst_crs = {"init": "epsg:32619"}
+ dst_crs = "EPSG:32619"
# Calculate the ideal dimensions and transformation in the new crs
dst_affine, dst_width, dst_height = calculate_default_transform(
@@ -1414,7 +1421,7 @@ def test_reproject_gcps(rgb_byte_profile):
reproject(
source,
out,
- src_crs="epsg:32618",
+ src_crs="EPSG:32618",
gcps=src_gcps,
dst_transform=rgb_byte_profile["transform"],
dst_crs=rgb_byte_profile["crs"],
@@ -1445,7 +1452,7 @@ def test_issue1056():
"""Warp sucessfully from RGB's upper bands to an array"""
with rasterio.open("tests/data/RGB.byte.tif") as src:
- dst_crs = {"init": "EPSG:3857"}
+ dst_crs = "EPSG:3857"
out = np.zeros(src.shape, dtype=np.uint8)
reproject(
rasterio.band(src, 2),
@@ -1463,7 +1470,7 @@ def test_reproject_dst_nodata():
with rasterio.open("tests/data/RGB.byte.tif") as src:
source = src.read(1)
- dst_crs = {"init": "epsg:3857"}
+ dst_crs = "EPSG:3857"
out = np.empty(src.shape, dtype=np.float32)
reproject(
source,
@@ -1485,7 +1492,7 @@ def test_reproject_dst_nodata():
def test_issue1401():
"""The warp_mem_limit keyword argument is in effect"""
with rasterio.open("tests/data/RGB.byte.tif") as src:
- dst_crs = {"init": "epsg:3857"}
+ dst_crs = "EPSG:3857"
out = np.zeros(src.shape, dtype=np.uint8)
reproject(
rasterio.band(src, 2),
@@ -1514,7 +1521,7 @@ def test_reproject_dst_alpha(path_rgb_msk_byte_tif):
src_transform=src.transform,
src_crs=src.crs,
dst_transform=DST_TRANSFORM,
- dst_crs={"init": "epsg:3857"},
+ dst_crs="EPSG:3857",
dst_alpha=4,
)
@@ -1532,7 +1539,7 @@ def test_issue1350():
"""Warp bands other than 1 or All"""
with rasterio.open("tests/data/RGB.byte.tif") as src:
- dst_crs = {"init": "epsg:3857"}
+ dst_crs = "EPSG:3857"
reprojected = []
=====================================
tests/test_warpedvrt.py
=====================================
@@ -549,3 +549,19 @@ def dsrec(capfd):
records = captured.err.strip("\n").split("\n")[1:]
return records
return func
+
+
+def test_warped_vrt_resizing():
+ """Confirm fix of #1921"""
+ with rasterio.open("tests/data/RGB.byte.tif") as rgb:
+ with WarpedVRT(rgb, height=10, width=10) as vrt:
+ assert vrt.height == 10
+ assert vrt.width == 10
+
+
+def test_warped_vrt_resizing_repro():
+ """Confirm fix of #1921"""
+ with rasterio.open("tests/data/RGB.byte.tif") as rgb:
+ with WarpedVRT(rgb, crs="EPSG:3857", height=10, width=10) as vrt:
+ assert vrt.height == 10
+ assert vrt.width == 10
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/cc6f0e00c4a35837586413cf52542cd12320dd64...64ec8cc5f03624aabd9da6f16f1705382f2e7240
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/cc6f0e00c4a35837586413cf52542cd12320dd64...64ec8cc5f03624aabd9da6f16f1705382f2e7240
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/20200603/15c4e7ce/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list