[Git][debian-gis-team/rasterio][master] 4 commits: New upstream version 1.1.7
Bas Couwenberg
gitlab at salsa.debian.org
Tue Sep 29 16:38:07 BST 2020
Bas Couwenberg pushed to branch master at Debian GIS Project / rasterio
Commits:
a0fdde0c by Bas Couwenberg at 2020-09-29T17:21:40+02:00
New upstream version 1.1.7
- - - - -
79d965a5 by Bas Couwenberg at 2020-09-29T17:22:18+02:00
Update upstream source from tag 'upstream/1.1.7'
Update to upstream version '1.1.7'
with Debian dir dbf683f93388a55b419909b4a67acb6ea5a9ec03
- - - - -
36cf2320 by Bas Couwenberg at 2020-09-29T17:22:43+02:00
New upstream release.
- - - - -
eadf201f by Bas Couwenberg at 2020-09-29T17:23:39+02:00
Set distribution to unstable.
- - - - -
19 changed files:
- CHANGES.txt
- debian/changelog
- rasterio/__init__.py
- rasterio/_base.pyx
- rasterio/_crs.pyx
- rasterio/_err.pxd
- rasterio/_err.pyx
- rasterio/_io.pyx
- rasterio/_warp.pyx
- rasterio/crs.py
- rasterio/errors.py
- rasterio/gdal.pxi
- rasterio/io.py
- rasterio/merge.py
- rasterio/rio/merge.py
- tests/test_boundless_read.py
- tests/test_crs.py
- tests/test_rio_merge.py
- tests/test_warpedvrt.py
Changes:
=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,21 @@
Changes
=======
+1.1.7 (2020-09-29)
+------------------
+
+- Add missing methods needed to determine whether GDAL treats a CRS as lat/long
+ or northing/easting (#1943).
+- Wrap calls to GDALChecksumImage so that errors set by GDAL are propagated to
+ Python as a RasterioIOError.
+- Raise RasterioDeprecationWarning when a dataset opened in modes other than
+ 'r' is given to the WarpedVRT constructor.
+- Base RasterioDeprecationWarning on FutureWarning, following the
+ recommendation of PEP 565.
+- Fix a segmentation fault that occurs when a WarpedVRT closes after the
+ dataset it references has been previously closed (#2001).
+- Add resampling option to merge and rio-merge (#1996).
+
1.1.6 (2020-09-14)
------------------
=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+rasterio (1.1.7-1) unstable; urgency=medium
+
+ * Team upload.
+ * New upstream release.
+
+ -- Bas Couwenberg <sebastic at debian.org> Tue, 29 Sep 2020 17:23:23 +0200
+
rasterio (1.1.6-1) unstable; urgency=medium
* Team upload.
=====================================
rasterio/__init__.py
=====================================
@@ -41,7 +41,7 @@ import rasterio.enums
import rasterio.path
__all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.1.6"
+__version__ = "1.1.7"
__gdal_version__ = gdal_version()
# Rasterio attaches NullHandler to the 'rasterio' logger and its
=====================================
rasterio/_base.pyx
=====================================
@@ -14,7 +14,7 @@ from libc.string cimport strncmp
from rasterio._err import (
GDALError, CPLE_BaseError, CPLE_IllegalArgError, CPLE_OpenFailedError,
CPLE_NotSupportedError)
-from rasterio._err cimport exc_wrap_pointer, exc_wrap_int
+from rasterio._err cimport exc_wrap_pointer, exc_wrap_int, exc_wrap
from rasterio._shim cimport open_dataset, osr_get_name, osr_set_traditional_axis_mapping_strategy
from rasterio.compat import string_types
@@ -309,8 +309,12 @@ cdef class DatasetBase(object):
return [gt[i] for i in range(6)]
+ def start(self):
+ """Start the dataset's life cycle"""
+ pass
+
def stop(self):
- """Ends the dataset's life cycle"""
+ """Close the GDAL dataset handle"""
if self._hds != NULL:
refcount = GDALDereferenceDataset(self._hds)
if refcount == 0:
@@ -1212,7 +1216,11 @@ cdef class DatasetBase(object):
yoff = window.row_off
height = window.height
- return GDALChecksumImage(band, xoff, yoff, width, height)
+ try:
+ return exc_wrap(GDALChecksumImage(band, xoff, yoff, width, height))
+ except CPLE_BaseError as err:
+ raise RasterioIOError(str(err))
+
def get_gcps(self):
"""Get GCPs and their associated CRS."""
=====================================
rasterio/_crs.pyx
=====================================
@@ -28,6 +28,48 @@ def gdal_version():
return info_b.decode("utf-8")
+def _epsg_treats_as_latlong(input_crs):
+ """Test if the CRS is in latlon order
+
+ Parameters
+ ----------
+ input_crs : _CRS
+ rasterio _CRS object
+
+ Returns
+ -------
+ bool
+
+ """
+ cdef _CRS crs = input_crs
+
+ try:
+ return bool(OSREPSGTreatsAsLatLong(crs._osr) == 1)
+ except CPLE_BaseError as exc:
+ raise CRSError("{}".format(exc))
+
+
+def _epsg_treats_as_northingeasting(input_crs):
+ """Test if the CRS should be treated as having northing/easting coordinate ordering
+
+ Parameters
+ ----------
+ input_crs : _CRS
+ rasterio _CRS object
+
+ Returns
+ -------
+ bool
+
+ """
+ cdef _CRS crs = input_crs
+
+ try:
+ return bool(OSREPSGTreatsAsNorthingEasting(crs._osr) == 1)
+ except CPLE_BaseError as exc:
+ raise CRSError("{}".format(exc))
+
+
cdef class _CRS(object):
"""Cython extension class"""
@@ -170,24 +212,43 @@ cdef class _CRS(object):
int
"""
- cdef OGRSpatialReferenceH osr = NULL
+ if self._epsg is not None:
+ return self._epsg
+
+ auth = self.to_authority()
+ if auth is None:
+ return None
+ name, code = auth
+ if name.upper() == "EPSG":
+ self._epsg = int(code)
+ return self._epsg
- if self._epsg is None:
- try:
- osr = exc_wrap_pointer(OSRClone(self._osr))
- exc_wrap_ogrerr(OSRMorphFromESRI(osr))
- if OSRAutoIdentifyEPSG(osr) == 0:
- epsg_code = OSRGetAuthorityCode(osr, NULL)
- if epsg_code != NULL:
- self._epsg = int(epsg_code.decode('utf-8'))
- else:
- self._epsg = None
- else:
- self._epsg = None
- finally:
- _safe_osr_release(osr)
+ def to_authority(self):
+ """The authority name and code of the CRS
- return self._epsg
+ Returns
+ -------
+ (str, str) or None
+
+ """
+ cdef OGRSpatialReferenceH osr = NULL
+ code = None
+ name = None
+ try:
+ osr = exc_wrap_pointer(OSRClone(self._osr))
+ exc_wrap_ogrerr(OSRMorphFromESRI(osr))
+ if OSRAutoIdentifyEPSG(osr) == 0:
+ c_code = OSRGetAuthorityCode(osr, NULL)
+ c_name = OSRGetAuthorityName(osr, NULL)
+ if c_code != NULL and c_name != NULL:
+ code = c_code.decode('utf-8')
+ name = c_name.decode('utf-8')
+ finally:
+ _safe_osr_release(osr)
+
+ if None not in (name, code):
+ return (name, code)
+ return None
@staticmethod
def from_epsg(code):
=====================================
rasterio/_err.pxd
=====================================
@@ -9,6 +9,7 @@ cdef extern from "ogr_core.h":
ctypedef int OGRErr
+cdef int exc_wrap(int retval) except -1
cdef int exc_wrap_int(int retval) except -1
cdef OGRErr exc_wrap_ogrerr(OGRErr retval) except -1
cdef void *exc_wrap_pointer(void *ptr) except NULL
=====================================
rasterio/_err.pyx
=====================================
@@ -139,7 +139,7 @@ class GDALError(IntEnum):
cdef inline object exc_check():
"""Checks GDAL error stack for fatal or non-fatal errors
-
+
Returns
-------
An Exception, SystemExit, or None
@@ -171,6 +171,14 @@ cdef inline object exc_check():
return
+cdef int exc_wrap(int retval) except -1:
+ """Wrap a GDAL function that returns int without checking the retval"""
+ exc = exc_check()
+ if exc:
+ raise exc
+ return retval
+
+
cdef int exc_wrap_int(int err) except -1:
"""Wrap a GDAL/OGR function that returns CPLErr or OGRErr (int)
=====================================
rasterio/_io.pyx
=====================================
@@ -65,28 +65,28 @@ def _delete_dataset_if_exists(path):
"""
cdef GDALDatasetH h_dataset = NULL
- cdef const char *c_path = NULL
-
- b_path = path.encode('utf-8')
- c_path = b_path
-
- with silence_errors():
- with nogil:
- h_dataset = GDALOpen(c_path, <GDALAccess>0)
+ cdef GDALDriverH h_driver = NULL
+ cdef const char *path_c = NULL
try:
- h_dataset = exc_wrap_pointer(h_dataset)
+ h_dataset = open_dataset(path, 0x40, None, None, None)
- except (CPLE_OpenFailedError, CPLE_AWSObjectNotFoundError):
+ except (CPLE_OpenFailedError, CPLE_AWSObjectNotFoundError) as exc:
log.debug(
- "Skipped delete for overwrite. Dataset does not exist: %s", path)
+ "Skipped delete for overwrite. Dataset does not exist: %r", path)
else:
h_driver = GDALGetDatasetDriver(h_dataset)
+ GDALClose(h_dataset)
+ h_dataset = NULL
if h_driver != NULL:
+ path_b = path.encode("utf-8")
+ path_c = path_b
with nogil:
- GDALDeleteDataset(h_driver, c_path)
+ err = GDALDeleteDataset(h_driver, path_c)
+ exc_wrap_int(err)
+
finally:
if h_dataset != NULL:
@@ -839,7 +839,7 @@ def silence_errors():
cdef class MemoryFileBase:
"""Base for a BytesIO-like class backed by an in-memory file."""
- def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
+ def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext='.tif'):
"""A file in an in-memory filesystem.
Parameters
@@ -1230,16 +1230,6 @@ cdef class DatasetWriterBase(DatasetReaderBase):
self.name,
self.mode)
- def start(self):
- pass
-
- def stop(self):
- """Ends the dataset's life cycle"""
- if self._hds != NULL:
- GDALClose(self._hds)
- self._hds = NULL
- log.debug("Dataset %r has been stopped.", self)
-
def _set_crs(self, crs):
"""Writes a coordinate reference system to the dataset."""
crs = CRS.from_user_input(crs)
@@ -1900,7 +1890,7 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
modes.
sharing : bool
A flag that allows sharing of dataset handles. Default is
- `False`. Should be set to `False` in a multithreaded:w program.
+ `False`. Should be set to `False` in a multithreaded program.
kwargs : optional
These are passed to format drivers as directives for creating or
interpreting datasets. For example: in 'w' or 'w+' modes
@@ -2086,8 +2076,10 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
if temp != NULL:
GDALClose(temp)
if self._hds != NULL:
- GDALClose(self._hds)
- self._hds = NULL
+ refcount = GDALDereferenceDataset(self._hds)
+ if refcount == 0:
+ GDALClose(self._hds)
+ self._hds = NULL
def virtual_file_to_buffer(filename):
=====================================
rasterio/_warp.pyx
=====================================
@@ -24,7 +24,7 @@ from rasterio.crs import CRS
from rasterio.errors import (
GDALOptionNotImplementedError,
DriverRegistrationError, CRSError, RasterioIOError,
- RasterioDeprecationWarning, WarpOptionsError)
+ RasterioDeprecationWarning, WarpOptionsError, WarpedVRTError)
from rasterio.transform import Affine, from_bounds, guard_transform, tastes_like_gdal
cimport numpy as np
@@ -650,7 +650,7 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
Parameters
----------
src_dataset : dataset object
- The warp source.
+ The warp source dataset. Must be opened in "r" mode.
src_crs : CRS or str, optional
Overrides the coordinate reference system of `src_dataset`.
src_transfrom : Affine, optional
@@ -701,7 +701,11 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
Returns
-------
WarpedVRT
+
"""
+ if src_dataset.mode != "r":
+ warnings.warn("Source dataset should be opened in read-only mode. Use of datasets opened in modes other than 'r' will be disallowed in a future version.", RasterioDeprecationWarning, stacklevel=2)
+
self.mode = 'r'
self.options = {}
self._count = 0
@@ -999,17 +1003,6 @@ cdef class WarpedVRTReaderBase(DatasetReaderBase):
"""The dataset's coordinate reference system"""
return self.dst_crs
- def start(self):
- """Starts the VRT's life cycle."""
- log.debug("Dataset %r is started.", self)
-
- def stop(self):
- """Ends the VRT's life cycle"""
- if self._hds != NULL:
- GDALClose(self._hds)
- self._hds = NULL
- self._closed = True
-
def read(self, indexes=None, out=None, window=None, masked=False,
out_shape=None, boundless=False, resampling=Resampling.nearest,
fill_value=None, out_dtype=None):
=====================================
rasterio/crs.py
=====================================
@@ -13,7 +13,7 @@ used.
import json
import pickle
-from rasterio._crs import _CRS, all_proj_keys
+from rasterio._crs import _CRS, all_proj_keys, _epsg_treats_as_latlong, _epsg_treats_as_northingeasting
from rasterio.compat import Mapping, string_types
from rasterio.errors import CRSError
@@ -155,6 +155,18 @@ class CRS(Mapping):
"""
return self._crs.to_epsg()
+ def to_authority(self):
+ """The authority name and code of the CRS
+
+ .. versionadded:: 1.1.7
+
+ Returns
+ -------
+ (str, str) or None
+
+ """
+ return self._crs.to_authority()
+
def to_dict(self):
"""Convert CRS to a PROJ4 dict
@@ -284,9 +296,9 @@ class CRS(Mapping):
str
"""
- epsg_code = self.to_epsg()
- if epsg_code:
- return 'EPSG:{}'.format(epsg_code)
+ auth = self.to_authority()
+ if auth:
+ return ":".join(auth)
else:
return self.to_wkt() or self.to_proj4()
@@ -321,6 +333,25 @@ class CRS(Mapping):
obj._crs = _CRS.from_epsg(code)
return obj
+ @classmethod
+ def from_authority(cls, auth_name, code):
+ """Make a CRS from an authority name and authority code
+
+ .. versionadded:: 1.1.7
+
+ Parameters
+ ----------
+ auth_name: str
+ The name of the authority.
+ code : int or str
+ The code used by the authority.
+
+ Returns
+ -------
+ CRS
+ """
+ return cls.from_string("{auth_name}:{code}".format(auth_name=auth_name, code=code))
+
@classmethod
def from_string(cls, string, morph_from_esri_dialect=False):
"""Make a CRS from an EPSG, PROJ, or WKT string
@@ -338,11 +369,16 @@ class CRS(Mapping):
CRS
"""
+ try:
+ string = string.strip()
+ except AttributeError:
+ pass
+
if not string:
raise CRSError("CRS is empty or invalid: {!r}".format(string))
- elif string.strip().upper().startswith('EPSG:'):
- auth, val = string.strip().split(':')
+ elif string.upper().startswith('EPSG:'):
+ auth, val = string.split(':')
if not val:
raise CRSError("Invalid CRS: {!r}".format(string))
return cls.from_epsg(val)
@@ -358,11 +394,14 @@ class CRS(Mapping):
raise CRSError("CRS is empty JSON")
else:
return cls.from_dict(**val)
-
- elif string.strip().endswith("]"):
+ elif string.endswith("]"):
return cls.from_wkt(string, morph_from_esri_dialect=morph_from_esri_dialect)
+ elif "=" in string:
+ return cls.from_proj4(string)
- return cls.from_proj4(string)
+ obj = cls()
+ obj._crs = _CRS.from_user_input(string, morph_from_esri_dialect=morph_from_esri_dialect)
+ return obj
@classmethod
def from_proj4(cls, proj):
@@ -454,8 +493,83 @@ class CRS(Mapping):
elif isinstance(value, dict):
return cls(**value)
elif isinstance(value, string_types):
- obj = cls()
- obj._crs = _CRS.from_user_input(value, morph_from_esri_dialect=morph_from_esri_dialect)
- return obj
+ return cls.from_string(value, morph_from_esri_dialect=morph_from_esri_dialect)
else:
raise CRSError("CRS is invalid: {!r}".format(value))
+
+
+def epsg_treats_as_latlong(input):
+ """Test if the CRS is in latlon order
+
+ From GDAL docs:
+
+ > This method returns TRUE if EPSG feels this geographic coordinate
+ system should be treated as having lat/long coordinate ordering.
+
+ > Currently this returns TRUE for all geographic coordinate systems with
+ an EPSG code set, and axes set defining it as lat, long.
+
+ > FALSE will be returned for all coordinate systems that are not
+ geographic, or that do not have an EPSG code set.
+
+ > **Note**
+
+ > Important change of behavior since GDAL 3.0.
+ In previous versions, geographic CRS imported with importFromEPSG()
+ would cause this method to return FALSE on them, whereas now it returns
+ TRUE, since importFromEPSG() is now equivalent to importFromEPSGA().
+
+ Parameters
+ ----------
+ input : CRS
+ Coordinate reference system, as a rasterio CRS object
+ Example: CRS({'init': 'EPSG:4326'})
+
+ Returns
+ -------
+ bool
+
+ """
+ if not isinstance(input, CRS):
+ input = CRS.from_user_input(input)
+
+ return _epsg_treats_as_latlong(input._crs)
+
+
+def epsg_treats_as_northingeasting(input):
+ """Test if the CRS should be treated as having northing/easting coordinate ordering
+
+ From GDAL docs:
+
+ > This method returns TRUE if EPSG feels this projected coordinate
+ system should be treated as having northing/easting coordinate ordering.
+
+ > Currently this returns TRUE for all projected coordinate systems with
+ an EPSG code set, and axes set defining it as northing, easting.
+
+ > FALSE will be returned for all coordinate systems that are not
+ projected, or that do not have an EPSG code set.
+
+ > **Note**
+
+ > Important change of behavior since GDAL 3.0.
+ In previous versions, projected CRS with northing, easting axis order
+ imported with importFromEPSG() would cause this method to return FALSE
+ on them, whereas now it returns TRUE, since importFromEPSG() is now
+ equivalent to importFromEPSGA().
+
+ Parameters
+ ----------
+ input : CRS
+ Coordinate reference system, as a rasterio CRS object
+ Example: CRS({'init': 'EPSG:4326'})
+
+ Returns
+ -------
+ bool
+
+ """
+ if not isinstance(input, CRS):
+ input = CRS.from_user_input(input)
+
+ return _epsg_treats_as_northingeasting(input._crs)
=====================================
rasterio/errors.py
=====================================
@@ -88,8 +88,12 @@ class WindowEvaluationError(ValueError):
"""Raised when window evaluation fails"""
-class RasterioDeprecationWarning(UserWarning):
- """Rasterio module deprecations"""
+class RasterioDeprecationWarning(FutureWarning):
+ """Rasterio module deprecations
+
+ Following https://www.python.org/dev/peps/pep-0565/#additional-use-case-for-futurewarning
+ we base this on FutureWarning while continuing to support Python < 3.7.
+ """
class RasterBlockError(RasterioError):
@@ -126,3 +130,7 @@ class ResamplingAlgorithmError(RasterioError):
class TransformError(RasterioError):
"""Raised when transform arguments are invalid"""
+
+
+class WarpedVRTError(RasterioError):
+ """Raised when WarpedVRT can't be initialized"""
=====================================
rasterio/gdal.pxi
=====================================
@@ -115,6 +115,8 @@ cdef extern from "ogr_srs_api.h" nogil:
int OSRSetFromUserInput(OGRSpatialReferenceH srs, const char *input)
OGRErr OSRValidate(OGRSpatialReferenceH srs)
double OSRGetLinearUnits(OGRSpatialReferenceH srs, char **ppszName)
+ int OSREPSGTreatsAsLatLong(OGRSpatialReferenceH srs)
+ int OSREPSGTreatsAsNorthingEasting(OGRSpatialReferenceH srs)
cdef extern from "gdal.h" nogil:
=====================================
rasterio/io.py
=====================================
@@ -85,7 +85,7 @@ class MemoryFile(MemoryFileBase):
'width': 791}
"""
- def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext=''):
+ def __init__(self, file_or_bytes=None, dirname=None, filename=None, ext='.tif'):
"""Create a new file in memory
Parameters
@@ -141,8 +141,8 @@ class MemoryFile(MemoryFileBase):
return self
def __exit__(self, *args, **kwargs):
- self._env.__exit__()
self.close()
+ self._env.__exit__()
class ZipMemoryFile(MemoryFile):
=====================================
rasterio/merge.py
=====================================
@@ -10,6 +10,7 @@ import numpy as np
import rasterio
from rasterio import windows
from rasterio.compat import string_types
+from rasterio.enums import Resampling
from rasterio.transform import Affine
@@ -19,7 +20,8 @@ MERGE_METHODS = ('first', 'last', 'min', 'max')
def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10,
- indexes=None, output_count=None, method='first'):
+ indexes=None, output_count=None, resampling=Resampling.nearest,
+ method='first'):
"""Copy valid pixels from input files to an output file.
All files must have the same number of bands, data type, and
@@ -57,6 +59,9 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
output_count: int, optional
If using callable it may be useful to have additional bands in the output
in addition to the indexes specified for read
+ resampling : Resampling, optional
+ Resampling algorithm used when reading input files.
+ Default: `Resampling.nearest`.
method : str or callable
pre-defined method:
first: reverse painting
@@ -271,6 +276,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
boundless=False,
masked=True,
indexes=indexes,
+ resampling=resampling,
)
# 5. Copy elements of temp into dest
=====================================
rasterio/rio/merge.py
=====================================
@@ -5,6 +5,7 @@ import click
from cligj import format_opt
import rasterio
+from rasterio.enums import Resampling
from rasterio.rio import options
from rasterio.rio.helpers import resolve_inout
@@ -15,6 +16,10 @@ from rasterio.rio.helpers import resolve_inout
@format_opt
@options.bounds_opt
@options.resolution_opt
+ at click.option('--resampling',
+ type=click.Choice([r.name for r in Resampling if r.value <= 7]),
+ default='nearest', help="Resampling method.",
+ show_default=True)
@options.nodata_opt
@options.bidx_mult_opt
@options.overwrite_opt
@@ -23,8 +28,8 @@ from rasterio.rio.helpers import resolve_inout
"pixels")
@options.creation_options
@click.pass_context
-def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
- precision, creation_options):
+def merge(ctx, files, output, driver, bounds, res, resampling,
+ nodata, bidx, overwrite, precision, creation_options):
"""Copy valid pixels from input files to an output file.
All files must have the same number of bands, data type, and
@@ -49,6 +54,8 @@ def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
output, files = resolve_inout(
files=files, output=output, overwrite=overwrite)
+ resampling = Resampling[resampling]
+
with ctx.obj["env"]:
dest, output_transform = merge_tool(
files,
@@ -57,6 +64,7 @@ def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
nodata=nodata,
precision=precision,
indexes=(bidx or None),
+ resampling=resampling,
)
with rasterio.open(files[0]) as first:
=====================================
tests/test_boundless_read.py
=====================================
@@ -72,6 +72,7 @@ def test_boundless_mask_not_all_valid():
assert masked.mask[-1, :].all()
+ at pytest.mark.xfail(reason="fill_value requires GDAL 3.1.4 or equivalent patches")
def test_boundless_fill_value():
"""Confirm resolution of issue #1471"""
with rasterio.open("tests/data/red.tif") as src:
@@ -102,6 +103,7 @@ def test_boundless_mask_special():
assert not mask[-1, :].any()
+ at pytest.mark.xfail(reason="fill_value requires GDAL 3.1.4 or equivalent patches")
def test_boundless_fill_value_overview_masks():
"""Confirm a more general resolution to issue #1471"""
with rasterio.open("tests/data/cogeo.tif") as src:
@@ -109,6 +111,7 @@ def test_boundless_fill_value_overview_masks():
assert (data[:, 0] == 5).all()
+ at pytest.mark.xfail(reason="fill_value requires GDAL 3.1.4 or equivalent patches")
def test_boundless_masked_fill_value_overview_masks():
"""Confirm a more general resolution to issue #1471"""
with rasterio.open("tests/data/cogeo.tif") as src:
=====================================
tests/test_crs.py
=====================================
@@ -11,11 +11,11 @@ import pytest
import rasterio
from rasterio._base import _can_create_osr
-from rasterio.crs import CRS
+from rasterio.crs import CRS, epsg_treats_as_latlong, epsg_treats_as_northingeasting
from rasterio.env import env_ctx_if_needed, Env
from rasterio.errors import CRSError
-from .conftest import requires_gdal21, requires_gdal22, requires_gdal_lt_3
+from .conftest import requires_gdal21, requires_gdal22, requires_gdal_lt_3, requires_gdal3
# Items like "D_North_American_1983" characterize the Esri dialect
@@ -540,3 +540,69 @@ def test_from_string__wkt_with_proj():
'AUTHORITY["EPSG","3857"]] '
)
assert CRS.from_string(wkt).to_epsg() == 3857
+
+
+ at requires_gdal3
+def test_esri_auth__from_string():
+ assert CRS.from_string('ESRI:54009').to_string() == 'ESRI:54009'
+
+
+ at requires_gdal3
+def test_esri_auth__to_epsg():
+ assert CRS.from_user_input('ESRI:54009').to_epsg() is None
+
+
+ at requires_gdal3
+def test_esri_auth__to_authority():
+ assert CRS.from_user_input('ESRI:54009').to_authority() == ('ESRI', '54009')
+
+
+def test_from_authority__to_authority():
+ assert CRS.from_authority("EPSG", 4326).to_authority() == ("EPSG", "4326")
+
+
+def test_to_authority__no_code_available():
+ lcc_crs = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc '
+ '+x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0')
+ assert lcc_crs.to_authority() is None
+
+
+ at pytest.mark.parametrize(
+ 'crs_obj,result',
+ [
+ (CRS.from_user_input("http://www.opengis.net/def/crs/OGC/1.3/CRS84"), False),
+ (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/2193"), False),
+ (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/4326"), True),
+ ]
+)
+def test_is_latlong(crs_obj, result):
+ """Test if CRS should be treated as latlon."""
+ assert epsg_treats_as_latlong(crs_obj) == result
+
+
+ at pytest.mark.parametrize(
+ 'crs_obj,result',
+ [
+ (CRS.from_user_input("http://www.opengis.net/def/crs/OGC/1.3/CRS84"), False),
+ (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/2193"), True),
+ (CRS.from_user_input("http://www.opengis.net/def/crs/EPSG/0/4326"), False),
+ ]
+)
+def test_is_northingeasting(crs_obj, result):
+ """Test if CRS should be treated as having northing/easting coordinate ordering."""
+ assert epsg_treats_as_northingeasting(crs_obj) == result
+
+
+ at requires_gdal_lt_3
+ at pytest.mark.parametrize('crs_obj', [CRS.from_epsg(4326), CRS.from_epsg(2193)])
+def test_latlong_northingeasting_gdal2(crs_obj):
+ """Check CRS created from epsg with GDAL 2 always return False."""
+ assert not epsg_treats_as_latlong(crs_obj)
+ assert not epsg_treats_as_northingeasting(crs_obj)
+
+
+ at requires_gdal3
+def test_latlong_northingeasting_gdal3():
+ """Check CRS created from epsg with GDAL 3."""
+ assert epsg_treats_as_latlong(CRS.from_epsg(4326))
+ assert epsg_treats_as_northingeasting(CRS.from_epsg(2193))
=====================================
tests/test_rio_merge.py
=====================================
@@ -12,6 +12,7 @@ from pytest import fixture
import pytest
import rasterio
+from rasterio.enums import Resampling
from rasterio.merge import merge
from rasterio.rio.main import main_group
from rasterio.transform import Affine
@@ -286,7 +287,6 @@ def test_merge_overlapping(test_data_dir_overlapping):
def test_merge_overlapping_callable_long(test_data_dir_overlapping, runner):
- outputname = str(test_data_dir_overlapping.join('merged.tif'))
inputs = [str(x) for x in test_data_dir_overlapping.listdir()]
datasets = [rasterio.open(x) for x in inputs]
test_merge_overlapping_callable_long.index = 0
@@ -304,13 +304,14 @@ def test_merge_overlapping_callable_long(test_data_dir_overlapping, runner):
def test_custom_callable_merge(test_data_dir_overlapping, runner):
inputs = ['tests/data/world.byte.tif'] * 3
datasets = [rasterio.open(x) for x in inputs]
- meta = datasets[0].meta
output_count = 4
def mycallable(old_data, new_data, old_nodata, new_nodata,
index=None, roff=None, coff=None):
# input data are bytes, test output doesn't overflow
- old_data[index] = (index + 1) * 259 # use a number > 255 but divisible by 3 for testing
+ old_data[index] = (
+ index + 1
+ ) * 259 # use a number > 255 but divisible by 3 for testing
# update additional band that we specified in output_count
old_data[3, :, :] += index
@@ -562,3 +563,56 @@ def test_merge_filenames(tiffs):
inputs = [str(x) for x in tiffs.listdir()]
inputs.sort()
merge(inputs, res=2)
+
+
+ at fixture(scope='function')
+def test_data_dir_resampling(tmpdir):
+ kwargs = {
+ "crs": {'init': 'epsg:4326'},
+ "transform": affine.Affine(0.2, 0, 0,
+ 0, -0.2, 0),
+ "count": 1,
+ "dtype": rasterio.uint8,
+ "driver": "GTiff",
+ "width": 9,
+ "height": 1,
+ "nodata": 1
+ }
+
+ with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
+ data = np.ones((1, 9), dtype=rasterio.uint8)
+ data[:, :3] = 100
+ data[:, 3:6] = 255
+ dst.write(data, indexes=1)
+
+ return tmpdir
+
+
+ at pytest.mark.xfail(
+ gdal_version.major == 1, reason="Mode resampling is unreliable for GDAL 1.11"
+)
+ at pytest.mark.parametrize(
+ "resampling",
+ [resamp for resamp in Resampling if resamp < 7]
+ + [pytest.param(Resampling.gauss, marks=pytest.mark.xfail)],
+)
+def test_merge_resampling(test_data_dir_resampling, resampling, runner):
+ outputname = str(test_data_dir_resampling.join('merged.tif'))
+ inputs = [str(x) for x in test_data_dir_resampling.listdir()]
+ with rasterio.open(inputs[0]) as src:
+ bounds = src.bounds
+ res = src.res[0]
+ expected_raster = src.read(
+ out_shape=tuple(dim * 2 for dim in src.shape),
+ resampling=resampling
+ )
+ result = runner.invoke(
+ main_group, ['merge'] + inputs + [outputname] +
+ ['--res', res / 2, '--resampling', resampling.name] +
+ ['--bounds', ' '.join(map(str, bounds))])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+ with rasterio.open(outputname) as dst:
+ output_raster = dst.read()
+
+ np.testing.assert_array_equal(output_raster, expected_raster)
=====================================
tests/test_warpedvrt.py
=====================================
@@ -9,16 +9,19 @@ import boto3
import numpy
import pytest
-from .conftest import requires_gdal21, requires_gdal2
import rasterio
from rasterio.crs import CRS
-from rasterio.enums import Resampling, MaskFlags, ColorInterp
-from rasterio.errors import RasterioDeprecationWarning, WarpOptionsError
+from rasterio.enums import Resampling, MaskFlags
+from rasterio.errors import RasterioDeprecationWarning, WarpOptionsError, WarpedVRTError
+from rasterio.io import MemoryFile
from rasterio import shutil as rio_shutil
from rasterio.vrt import WarpedVRT
from rasterio.warp import transform_bounds
from rasterio.windows import Window
+from .conftest import requires_gdal21, requires_gdal2
+
+log = logging.getLogger(__name__)
# Custom markers.
credentials = pytest.mark.skipif(
@@ -102,6 +105,7 @@ def test_warped_vrt_add_alpha(dsrec, path_rgb_byte_tif):
assert len(records) == 1
assert "1 N GTiff" in records[0]
+
@requires_gdal21(reason="Nodata deletion requires GDAL 2.1+")
def test_warped_vrt_msk_add_alpha(path_rgb_msk_byte_tif, caplog):
"""Add an alpha band to the VRT to access per-dataset mask of a source"""
@@ -565,3 +569,42 @@ def test_warped_vrt_resizing_repro():
with WarpedVRT(rgb, crs="EPSG:3857", height=10, width=10) as vrt:
assert vrt.height == 10
assert vrt.width == 10
+
+
+def test_vrt_src_mode(path_rgb_byte_tif):
+ """VRT source dataset must be opened in read mode"""
+
+ with rasterio.open(path_rgb_byte_tif) as src:
+ profile = src.profile
+ bands = src.read()
+
+ with MemoryFile() as memfile:
+
+ with memfile.open(**profile) as dst:
+ dst.write(bands)
+
+ with pytest.warns(FutureWarning):
+ vrt = WarpedVRT(dst, crs="EPSG:3857")
+
+
+def test_vrt_src_kept_alive(path_rgb_byte_tif):
+ """VRT source dataset is kept alive, preventing crashes"""
+
+ with rasterio.open(path_rgb_byte_tif) as dst:
+ vrt = WarpedVRT(dst, crs="EPSG:3857")
+
+ assert (vrt.read() != 0).any()
+ vrt.close()
+
+
+def test_vrt_mem_src_kept_alive(path_rgb_byte_tif):
+ """VRT in-memory source dataset is kept alive, preventing crashes"""
+
+ with open(path_rgb_byte_tif, "rb") as fp:
+ bands = fp.read()
+
+ with MemoryFile(bands) as memfile, memfile.open() as dst:
+ vrt = WarpedVRT(dst, crs="EPSG:3857")
+
+ assert (vrt.read() != 0).any()
+ vrt.close()
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/73af3ef34492bf3a9face7f72863832cbb5306b9...eadf201f67396c6b57291ef29f1437055157334a
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/73af3ef34492bf3a9face7f72863832cbb5306b9...eadf201f67396c6b57291ef29f1437055157334a
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20200929/646211cc/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list