[Git][debian-gis-team/rasterio][upstream] New upstream version 1.1.6
Bas Couwenberg
gitlab at salsa.debian.org
Tue Sep 15 06:22:56 BST 2020
Bas Couwenberg pushed to branch upstream at Debian GIS Project / rasterio
Commits:
fa275678 by Bas Couwenberg at 2020-09-15T06:46:08+02:00
New upstream version 1.1.6
- - - - -
30 changed files:
- CHANGES.txt
- docs/topics/virtual-warping.rst
- rasterio/__init__.py
- rasterio/_crs.pxd
- rasterio/_crs.pyx
- rasterio/_io.pyx
- rasterio/_warp.pyx
- rasterio/crs.py
- rasterio/errors.py
- rasterio/merge.py
- rasterio/path.py
- rasterio/plot.py
- rasterio/rio/clip.py
- rasterio/rio/convert.py
- rasterio/rio/helpers.py
- rasterio/rio/merge.py
- rasterio/rio/warp.py
- rasterio/session.py
- rasterio/vrt.py
- rasterio/warp.py
- + tests/test_creation_options.py
- tests/test_crs.py
- tests/test_overviews.py
- tests/test_path.py
- tests/test_read_boundless.py
- tests/test_rio_convert.py
- tests/test_rio_helpers.py
- tests/test_rio_merge.py
- tests/test_session.py
- tests/test_warp.py
Changes:
=====================================
CHANGES.txt
=====================================
@@ -1,6 +1,27 @@
Changes
=======
+1.1.6 (2020-09-14)
+------------------
+
+- Remove background layer from boundless VRT (#1982). It's not needed since
+ fixes in GDAL after 3.1.3. Wheels on PyPI for rasterio 1.1.6 will patch GDAL
+ 2.4.4 to fix those GDAL issues.
+- Clean up VSI files left by MemoryFileBase, resolving #1953.
+- Do not pass empty coordinate arrays to warp._transform to avoid crashes with
+ some versions of GDAL as reported in #1952. Instead, directly return empty
+ output arrays.
+- Properly convert block size `--co` option values to int in rio-clip and
+ rio-warp to prevent exceptions reported in #1989.
+- Fail gracefully when rio-convert lacks an input file (#1985).
+- Allow merge.merge() to open one dataset at a time (#1831).
+- Optimize CRS.__eq__() for CRS described by EPSG codes.
+- Fix bug in ParsedPath.is_remote() reported in #1967.
+- The reproject() method accepts objects that provide `__array__` in addition
+ to instances of numpy.ndarray (#1957, #1959).
+- Custom labels may be used with show_hist() by giving the `label` keyword
+ argument a sequence of label strings, one per band.
+
1.1.5 (2020-06-02)
------------------
=====================================
docs/topics/virtual-warping.rst
=====================================
@@ -45,8 +45,8 @@ extract pixels corresponding to its central zoom 9 tile, do the following.
# for the dataset read window and then scale it by the dimensions
# of the output array.
dst_transform = vrt.window_transform(dst_window)
- scaling = Affine.scale(dst_window.num_cols / 512,
- dst_window.num_rows / 512)
+ scaling = Affine.scale(dst_window.height / 512,
+ dst_window.width / 512)
dst_transform *= scaling
profile['transform'] = dst_transform
=====================================
rasterio/__init__.py
=====================================
@@ -41,7 +41,7 @@ import rasterio.enums
import rasterio.path
__all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.1.5"
+__version__ = "1.1.6"
__gdal_version__ = gdal_version()
# Rasterio attaches NullHandler to the 'rasterio' logger and its
@@ -85,9 +85,12 @@ def open(fp, mode='r', driver=None, width=None, height=None, count=None,
sequentially until a match is found. When multiple drivers are
available for a format such as JPEG2000, one of them can be
selected by using this keyword argument.
- width, height : int, optional
- The numbers of rows and columns of the raster dataset. Required
- in 'w' or 'w+' modes, they are ignored in 'r' or 'r+' modes.
+ width : int, optional
+ The number of columns of the raster dataset. Required in 'w' or
+ 'w+' modes, it is ignored in 'r' or 'r+' modes.
+ height : int, optional
+ The number of rows of the raster dataset. Required in 'w' or
+ 'w+' modes, it is ignored in 'r' or 'r+' modes.
count : int, optional
The count of dataset bands. Required in 'w' or 'w+' modes, it is
ignored in 'r' or 'r+' modes.
=====================================
rasterio/_crs.pxd
=====================================
@@ -6,3 +6,4 @@ include "gdal.pxi"
cdef class _CRS:
cdef OGRSpatialReferenceH _osr
+ cdef object _epsg
=====================================
rasterio/_crs.pyx
=====================================
@@ -172,19 +172,22 @@ cdef class _CRS(object):
"""
cdef OGRSpatialReferenceH osr = NULL
- 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:
- return int(epsg_code.decode('utf-8'))
+ 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:
- return None
- else:
- return None
- finally:
- _safe_osr_release(osr)
+ self._epsg = None
+ finally:
+ _safe_osr_release(osr)
+
+ return self._epsg
@staticmethod
def from_epsg(code):
@@ -217,6 +220,7 @@ cdef class _CRS(object):
raise CRSError("The EPSG code is unknown. {}".format(exc))
else:
osr_set_traditional_axis_mapping_strategy(obj._osr)
+ obj._epsg = code
return obj
@staticmethod
=====================================
rasterio/_io.pyx
=====================================
@@ -210,7 +210,9 @@ cdef class DatasetReaderBase(DatasetBase):
are not cached.
fill_value : scalar
- Fill value applied in the `boundless=True` case only.
+ Fill value applied in the `boundless=True` case only. Like
+ the fill_value of numpy.ma.MaskedArray, should be value
+ valid for the dataset's data type.
Returns
-------
@@ -936,6 +938,7 @@ cdef class MemoryFileBase:
if self._vsif != NULL:
VSIFCloseL(self._vsif)
self._vsif = NULL
+ _delete_dataset_if_exists(self.name)
VSIRmdir(self._dirname.encode("utf-8"))
self.closed = True
=====================================
rasterio/_warp.pyx
=====================================
@@ -338,13 +338,13 @@ def _reproject(
# We need a src_transform and src_dst in this case. These will
# be copied to the MEM dataset.
if dtypes.is_ndarray(source):
-
if not src_crs:
raise CRSError("Missing src_crs.")
-
if src_nodata is None and hasattr(source, 'fill_value'):
# source is a masked array
src_nodata = source.fill_value
+ # ensure data converted to numpy array
+ source = np.array(source, copy=False)
# Convert 2D single-band arrays to 3D multi-band.
if len(source.shape) == 2:
source = source.reshape(1, *source.shape)
@@ -381,10 +381,10 @@ def _reproject(
try:
if dtypes.is_ndarray(destination):
-
if not dst_crs:
raise CRSError("Missing dst_crs.")
-
+ # ensure data converted to numpy array
+ destination = np.array(destination, copy=False)
if len(destination.shape) == 2:
destination = destination.reshape(1, *destination.shape)
=====================================
rasterio/crs.py
=====================================
@@ -359,12 +359,11 @@ class CRS(Mapping):
else:
return cls.from_dict(**val)
- elif '+' in string and '=' in string:
- return cls.from_proj4(string)
-
- else:
+ elif string.strip().endswith("]"):
return cls.from_wkt(string, morph_from_esri_dialect=morph_from_esri_dialect)
+ return cls.from_proj4(string)
+
@classmethod
def from_proj4(cls, proj):
"""Make a CRS from a PROJ4 string
=====================================
rasterio/errors.py
=====================================
@@ -122,3 +122,7 @@ class PathError(RasterioError):
class ResamplingAlgorithmError(RasterioError):
"""Raised when a resampling algorithm is invalid or inapplicable"""
+
+
+class TransformError(RasterioError):
+ """Raised when transform arguments are invalid"""
=====================================
rasterio/merge.py
=====================================
@@ -1,13 +1,15 @@
"""Copy valid pixels from input files to an output file."""
-
+from contextlib import contextmanager
import logging
import math
import warnings
import numpy as np
+import rasterio
from rasterio import windows
+from rasterio.compat import string_types
from rasterio.transform import Affine
@@ -33,7 +35,7 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
Parameters
----------
- datasets: list of dataset objects opened in 'r' mode
+ datasets : list of dataset objects opened in 'r' mode or filenames
source datasets to be merged.
bounds: tuple, optional
Bounds of the output image (left, bottom, right, top).
@@ -94,23 +96,37 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
out_transform: affine.Affine()
Information for mapping pixel coordinates in `dest` to another
coordinate system
- """
- first = datasets[0]
- first_res = first.res
- nodataval = first.nodatavals[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'
.format(method, MERGE_METHODS))
- # Determine output band count
- if indexes is None:
- src_count = first.count
- elif isinstance(indexes, int):
- src_count = indexes
+ # Create a dataset_opener object to use in several places in this function.
+ if isinstance(datasets[0], string_types):
+ dataset_opener = rasterio.open
else:
- src_count = len(indexes)
+
+ @contextmanager
+ def nullcontext(obj):
+ try:
+ yield obj
+ finally:
+ pass
+
+ dataset_opener = nullcontext
+
+ with dataset_opener(datasets[0]) as first:
+ first_res = first.res
+ nodataval = first.nodatavals[0]
+ dt = first.dtypes[0]
+
+ if indexes is None:
+ src_count = first.count
+ elif isinstance(indexes, int):
+ src_count = indexes
+ else:
+ src_count = len(indexes)
if not output_count:
output_count = src_count
@@ -122,8 +138,9 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
# scan input files
xs = []
ys = []
- for src in datasets:
- left, bottom, right, top = src.bounds
+ for dataset in datasets:
+ with dataset_opener(dataset) as src:
+ left, bottom, right, top = src.bounds
xs.extend([left, right])
ys.extend([bottom, top])
dst_w, dst_s, dst_e, dst_n = min(xs), min(ys), max(xs), max(ys)
@@ -218,36 +235,43 @@ def merge(datasets, bounds=None, res=None, nodata=None, dtype=None, precision=10
else:
raise ValueError(method)
- 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.
-
- # 1. Compute spatial intersection of destination and source
- src_w, src_s, src_e, src_n = src.bounds
-
- int_w = src_w if src_w > dst_w else dst_w
- int_s = src_s if src_s > dst_s else dst_s
- int_e = src_e if src_e < dst_e else dst_e
- int_n = src_n if src_n < dst_n else dst_n
-
- # 2. Compute the source window
- src_window = windows.from_bounds(
- int_w, int_s, int_e, int_n, src.transform, precision=precision)
- logger.debug("Src %s window: %r", src.name, src_window)
-
- src_window = src_window.round_shape()
-
- # 3. Compute the destination window
- dst_window = windows.from_bounds(
- int_w, int_s, int_e, int_n, output_transform, precision=precision)
-
- # 4. Read data in source window into temp
- trows, tcols = (
- int(round(dst_window.height)), int(round(dst_window.width)))
- temp_shape = (src_count, trows, tcols)
- temp = src.read(out_shape=temp_shape, window=src_window,
- boundless=False, masked=True, indexes=indexes)
+ for idx, dataset in enumerate(datasets):
+ with dataset_opener(dataset) as src:
+ # 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.
+
+ # 1. Compute spatial intersection of destination and source
+ src_w, src_s, src_e, src_n = src.bounds
+
+ int_w = src_w if src_w > dst_w else dst_w
+ int_s = src_s if src_s > dst_s else dst_s
+ int_e = src_e if src_e < dst_e else dst_e
+ int_n = src_n if src_n < dst_n else dst_n
+
+ # 2. Compute the source window
+ src_window = windows.from_bounds(
+ int_w, int_s, int_e, int_n, src.transform, precision=precision
+ )
+ logger.debug("Src %s window: %r", src.name, src_window)
+
+ src_window = src_window.round_shape()
+
+ # 3. Compute the destination window
+ dst_window = windows.from_bounds(
+ int_w, int_s, int_e, int_n, output_transform, precision=precision
+ )
+
+ # 4. Read data in source window into temp
+ trows, tcols = (int(round(dst_window.height)), int(round(dst_window.width)))
+ temp_shape = (src_count, trows, tcols)
+ temp = src.read(
+ out_shape=temp_shape,
+ window=src_window,
+ boundless=False,
+ masked=True,
+ indexes=indexes,
+ )
# 5. Copy elements of temp into dest
roff, coff = (
=====================================
rasterio/path.py
=====================================
@@ -84,7 +84,7 @@ class ParsedPath(Path):
@property
def is_remote(self):
"""Test if the path is a remote, network URI"""
- return self.scheme and self.scheme.split('+')[-1] in REMOTESCHEMES
+ return bool(self.scheme) and self.scheme.split("+")[-1] in REMOTESCHEMES
@property
def is_local(self):
=====================================
rasterio/plot.py
=====================================
@@ -222,7 +222,7 @@ def reshape_as_raster(arr):
return im
-def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, **kwargs):
+def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, label=None, **kwargs):
"""Easily display a histogram with matplotlib.
Parameters
@@ -240,6 +240,9 @@ def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, **kwargs
Title for the figure.
ax : matplotlib axes (opt)
The raster will be added to this axes if passed.
+ label : matplotlib labels (opt)
+ If passed, matplotlib will use this label list.
+ Otherwise, a default label list will be automatically created
**kwargs : optional keyword arguments
These will be passed to the matplotlib hist method. See full list at:
http://matplotlib.org/api/axes_api.html?highlight=imshow#matplotlib.axes.Axes.hist
@@ -273,12 +276,17 @@ def show_hist(source, bins=10, masked=True, title='Histogram', ax=None, **kwargs
else:
colors = colors[:arr.shape[-1]]
- # If a rasterio.Band() is given make sure the proper index is displayed
- # in the legend.
- if isinstance(source, (tuple, rasterio.Band)):
- labels = [str(source[1])]
+ # if the user used the label argument, pass them drectly to matplotlib
+ if label:
+ labels = label
+ # else, create default labels
else:
- labels = (str(i + 1) for i in range(len(arr)))
+ # If a rasterio.Band() is given make sure the proper index is displayed
+ # in the legend.
+ if isinstance(source, (tuple, rasterio.Band)):
+ labels = [str(source[1])]
+ else:
+ labels = (str(i + 1) for i in range(len(arr)))
if ax:
show = False
=====================================
rasterio/rio/clip.py
=====================================
@@ -12,10 +12,8 @@ from rasterio.coords import disjoint_bounds
from rasterio.crs import CRS
from rasterio.windows import Window
-
logger = logging.getLogger(__name__)
-
# Geographic (default), projected, or Mercator switch.
projection_geographic_opt = click.option(
'--geographic',
@@ -144,12 +142,16 @@ def clip(
'transform': src.window_transform(out_window)})
out_kwargs.update(**creation_options)
- if 'blockxsize' in out_kwargs and out_kwargs['blockxsize'] > width:
- del out_kwargs['blockxsize']
- logger.warning("Blockxsize removed from creation options to accomodate small output width")
- if 'blockysize' in out_kwargs and out_kwargs['blockysize'] > height:
- del out_kwargs['blockysize']
- logger.warning("Blockysize removed from creation options to accomodate small output height")
+ if "blockxsize" in out_kwargs and int(out_kwargs["blockxsize"]) > width:
+ del out_kwargs["blockxsize"]
+ logger.warning(
+ "Blockxsize removed from creation options to accomodate small output width"
+ )
+ if "blockysize" in out_kwargs and int(out_kwargs["blockysize"]) > height:
+ del out_kwargs["blockysize"]
+ logger.warning(
+ "Blockysize removed from creation options to accomodate small output height"
+ )
with rasterio.open(output, "w", **out_kwargs) as out:
out.write(
=====================================
rasterio/rio/convert.py
=====================================
@@ -51,7 +51,9 @@ def convert(
"""
with ctx.obj['env']:
- outputfile, files = resolve_inout(files=files, output=output, overwrite=overwrite)
+ outputfile, files = resolve_inout(
+ files=files, output=output, overwrite=overwrite, num_inputs=1
+ )
inputfile = files[0]
with rasterio.open(inputfile) as src:
=====================================
rasterio/rio/helpers.py
=====================================
@@ -5,6 +5,8 @@ Helper objects used by multiple CLI commands.
import json
import os
+import click
+
from rasterio.errors import FileOverwriteError
@@ -59,31 +61,57 @@ def write_features(
fobj.write('\n')
-def resolve_inout(input=None, output=None, files=None, overwrite=False):
+def resolve_inout(
+ input=None, output=None, files=None, overwrite=False, num_inputs=None
+):
"""Resolves inputs and outputs from standard args and options.
- :param input: a single input filename, optional.
- :param output: a single output filename, optional.
- :param files: a sequence of filenames in which the last is the
- output filename.
- :param overwrite: whether to force overwriting the output
- file, bool.
- :return: the resolved output filename and input filenames as a
- tuple of length 2.
-
- If provided, the :param:`output` file may be overwritten. An output
- file extracted from :param:`files` will not be overwritten unless
- :param:`overwrite` is `True`.
+ Parameters
+ ----------
+ input : str
+ A single input filename, optional.
+ output : str
+ A single output filename, optional.
+ files : str
+ A sequence of filenames in which the last is the output filename.
+ overwrite : bool
+ Whether to force overwriting the output file.
+ num_inputs : int
+ Raise exceptions if the number of resolved input files is higher
+ or lower than this number.
+
+ Returns
+ -------
+ tuple (str, list of str)
+ The resolved output filename and input filenames as a tuple of
+ length 2.
+
+ If provided, the output file may be overwritten. An output
+ file extracted from files will not be overwritten unless
+ overwrite is True.
+
+ Raises
+ ------
+ click.BadParameter
+
"""
resolved_output = output or (files[-1] if files else None)
- if not overwrite and resolved_output and os.path.exists(
- resolved_output):
+
+ if not overwrite and resolved_output and os.path.exists(resolved_output):
raise FileOverwriteError(
- "file exists and won't be overwritten without use of the "
- "`--overwrite` option.")
+ "file exists and won't be overwritten without use of the `--overwrite` option."
+ )
+
resolved_inputs = (
[input] if input else [] +
list(files[:-1 if not output else None]) if files else [])
+
+ if num_inputs is not None:
+ if len(resolved_inputs) < num_inputs:
+ raise click.BadParameter("Insufficient inputs")
+ elif len(resolved_inputs) > num_inputs:
+ raise click.BadParameter("Too many inputs")
+
return resolved_output, resolved_inputs
=====================================
rasterio/rio/merge.py
=====================================
@@ -49,30 +49,35 @@ def merge(ctx, files, output, driver, bounds, res, nodata, bidx, overwrite,
output, files = resolve_inout(
files=files, output=output, overwrite=overwrite)
- with ctx.obj['env']:
- datasets = [rasterio.open(f) for f in files]
- dest, output_transform = merge_tool(datasets, bounds=bounds, res=res,
- nodata=nodata, precision=precision,
- indexes=(bidx or None))
-
- profile = datasets[0].profile
- profile['transform'] = output_transform
- profile['height'] = dest.shape[1]
- profile['width'] = dest.shape[2]
- profile['driver'] = driver
- profile['count'] = dest.shape[0]
-
- if nodata is not None:
- profile['nodata'] = nodata
-
- profile.update(**creation_options)
-
- with rasterio.open(output, 'w', **profile) as dst:
- dst.write(dest)
-
- # uses the colormap in the first input raster.
- try:
- colormap = datasets[0].colormap(1)
- dst.write_colormap(1, colormap)
- except ValueError:
- pass
+ with ctx.obj["env"]:
+ dest, output_transform = merge_tool(
+ files,
+ bounds=bounds,
+ res=res,
+ nodata=nodata,
+ precision=precision,
+ indexes=(bidx or None),
+ )
+
+ with rasterio.open(files[0]) as first:
+ profile = first.profile
+ profile["transform"] = output_transform
+ profile["height"] = dest.shape[1]
+ profile["width"] = dest.shape[2]
+ profile["driver"] = driver
+ profile["count"] = dest.shape[0]
+
+ if nodata is not None:
+ profile["nodata"] = nodata
+
+ profile.update(**creation_options)
+
+ with rasterio.open(output, "w", **profile) as dst:
+ dst.write(dest)
+
+ # uses the colormap in the first input raster.
+ try:
+ colormap = first.colormap(1)
+ dst.write_colormap(1, colormap)
+ except ValueError:
+ pass
=====================================
rasterio/rio/warp.py
=====================================
@@ -1,5 +1,6 @@
"""$ rio warp"""
+import logging
from math import ceil
import click
@@ -16,6 +17,7 @@ from rasterio.warp import (
reproject, Resampling, SUPPORTED_RESAMPLING, transform_bounds,
aligned_target, calculate_default_transform as calcdt)
+logger = logging.getLogger(__name__)
# Improper usage of rio-warp can lead to accidental creation of
# extremely large datasets. We'll put a hard limit on the size of
@@ -298,12 +300,18 @@ def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds,
)
# Adjust block size if necessary.
- if ('blockxsize' in out_kwargs and
- dst_width < out_kwargs['blockxsize']):
- del out_kwargs['blockxsize']
- if ('blockysize' in out_kwargs and
- dst_height < out_kwargs['blockysize']):
- del out_kwargs['blockysize']
+ if "blockxsize" in out_kwargs and dst_width < int(out_kwargs["blockxsize"]):
+ del out_kwargs["blockxsize"]
+ logger.warning(
+ "Blockxsize removed from creation options to accomodate small output width"
+ )
+ if "blockysize" in out_kwargs and dst_height < int(
+ out_kwargs["blockysize"]
+ ):
+ del out_kwargs["blockysize"]
+ logger.warning(
+ "Blockxsize removed from creation options to accomodate small output height"
+ )
out_kwargs.update(**creation_options)
=====================================
rasterio/session.py
=====================================
@@ -1,6 +1,7 @@
"""Abstraction for sessions in various clouds."""
import logging
+import os
from rasterio.path import parse_path, UnparsedPath
@@ -276,9 +277,13 @@ class AWSSession(Session):
profile_name=profile_name)
self.requester_pays = requester_pays
- self.unsigned = aws_unsigned
+ self.unsigned = bool(os.getenv("AWS_NO_SIGN_REQUEST", aws_unsigned))
self.endpoint_url = endpoint_url
- self._creds = self._session._session.get_credentials() if self._session else None
+ self._creds = (
+ self._session._session.get_credentials()
+ if not self.unsigned and self._session
+ else None
+ )
@classmethod
def hascreds(cls, config):
=====================================
rasterio/vrt.py
=====================================
@@ -164,9 +164,9 @@ def _boundless_vrt_doc(
vrtrasterband.attrib['dataType'] = _gdal_typename(dtype)
vrtrasterband.attrib['band'] = str(bidx)
- if nodata is not None:
+ if background is not None or nodata is not None:
nodatavalue = ET.SubElement(vrtrasterband, 'NoDataValue')
- nodatavalue.text = str(nodata)
+ nodatavalue.text = str(background or nodata)
if hidenodata:
hidenodatavalue = ET.SubElement(vrtrasterband, 'HideNoDataValue')
@@ -175,35 +175,6 @@ def _boundless_vrt_doc(
colorinterp = ET.SubElement(vrtrasterband, 'ColorInterp')
colorinterp.text = ci.name.capitalize()
- if background is not None:
- complexsource = ET.SubElement(vrtrasterband, 'ComplexSource')
- sourcefilename = ET.SubElement(complexsource, 'SourceFilename')
- sourcefilename.attrib['relativeToVRT'] = '1'
- sourcefilename.attrib["shared"] = "0"
- sourcefilename.text = "dummy.tif"
- sourceband = ET.SubElement(complexsource, 'SourceBand')
- sourceband.text = str(bidx)
- sourceproperties = ET.SubElement(complexsource, 'SourceProperties')
- sourceproperties.attrib['RasterXSize'] = str(width)
- sourceproperties.attrib['RasterYSize'] = str(height)
- sourceproperties.attrib['dataType'] = _gdal_typename(dtype)
- sourceproperties.attrib['BlockYSize'] = str(block_shape[0])
- sourceproperties.attrib['BlockXSize'] = str(block_shape[1])
- srcrect = ET.SubElement(complexsource, 'SrcRect')
- srcrect.attrib['xOff'] = '0'
- srcrect.attrib['yOff'] = '0'
- srcrect.attrib['xSize'] = '1'
- srcrect.attrib['ySize'] = '1'
- dstrect = ET.SubElement(complexsource, 'DstRect')
- dstrect.attrib['xOff'] = '0'
- dstrect.attrib['yOff'] = '0'
- dstrect.attrib['xSize'] = '1'
- dstrect.attrib['ySize'] = '1'
- scaleratio = ET.SubElement(complexsource, 'ScaleRatio')
- scaleratio.text = '0'
- scaleoffset = ET.SubElement(complexsource, 'ScaleOffset')
- scaleoffset.text = str(background)
-
complexsource = ET.SubElement(vrtrasterband, 'ComplexSource')
sourcefilename = ET.SubElement(complexsource, 'SourceFilename')
sourcefilename.attrib['relativeToVRT'] = "0"
=====================================
rasterio/warp.py
=====================================
@@ -12,7 +12,7 @@ from rasterio._base import _transform
from rasterio._warp import _calculate_default_transform, _reproject, _transform_geom
from rasterio.enums import Resampling
from rasterio.env import GDALVersion, ensure_env, require_gdal_version
-from rasterio.errors import GDALBehaviorChangeException
+from rasterio.errors import GDALBehaviorChangeException, TransformError
from rasterio.transform import array_bounds
# Gauss (7) is not supported for warp
@@ -49,9 +49,16 @@ def transform(src_crs, dst_crs, xs, ys, zs=None):
out: tuple of array_like, (xs, ys, [zs])
Tuple of x, y, and optionally z vectors, transformed into the target
coordinate reference system.
- """
- return _transform(src_crs, dst_crs, xs, ys, zs)
+ """
+ if len(xs) != len(ys):
+ raise TransformError("xs and ys arrays must be the same length")
+ elif zs is not None and len(xs) != len(zs):
+ raise TransformError("zs, xs, and ys arrays must be the same length")
+ if len(xs) == 0:
+ return ([], [], []) if zs is not None else ([], [])
+ else:
+ return _transform(src_crs, dst_crs, xs, ys, zs)
@ensure_env
=====================================
tests/test_creation_options.py
=====================================
@@ -0,0 +1,30 @@
+"""Tests of creation option behavior"""
+
+import logging
+
+import rasterio
+from rasterio.profiles import DefaultGTiffProfile
+
+from .conftest import requires_gdal2
+
+
+ at requires_gdal2(reason="GDAL 1.x warning text is obsolete")
+def test_warning(tmpdir, caplog):
+ """Be warned about invalid creation options"""
+ profile = DefaultGTiffProfile(
+ count=1, height=256, width=256, compression="lolwut", foo="bar"
+ )
+
+ with rasterio.Env(GDAL_VALIDATE_CREATION_OPTIONS=True):
+ rasterio.open(str(tmpdir.join("test.tif")), "w", **profile)
+
+ assert [
+ "CPLE_NotSupported in driver GTiff does not support creation option COMPRESSION",
+ "CPLE_NotSupported in driver GTiff does not support creation option FOO",
+ ] == sorted(
+ [
+ rec.message
+ for rec in caplog.records
+ if rec.levelno == logging.WARNING and rec.name == "rasterio._env"
+ ]
+ )
=====================================
tests/test_crs.py
=====================================
@@ -510,4 +510,33 @@ def test_equals_different_type(other):
def test_from_user_input_custom_crs_class():
"""Support comparison to foreign objects that provide to_wkt()"""
- assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
\ No newline at end of file
+ assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
+
+
+def test_from_string__wkt_with_proj():
+ wkt = (
+ 'PROJCS["WGS 84 / Pseudo-Mercator",'
+ 'GEOGCS["WGS 84",'
+ ' DATUM["WGS_1984",'
+ ' SPHEROID["WGS 84",6378137,298.257223563,'
+ ' AUTHORITY["EPSG","7030"]],'
+ ' AUTHORITY["EPSG","6326"]],'
+ ' PRIMEM["Greenwich",0,'
+ ' AUTHORITY["EPSG","8901"]],'
+ ' UNIT["degree",0.0174532925199433,'
+ ' AUTHORITY["EPSG","9122"]],'
+ ' AUTHORITY["EPSG","4326"]],'
+ 'PROJECTION["Mercator_1SP"],'
+ 'PARAMETER["central_meridian",0],'
+ 'PARAMETER["scale_factor",1],'
+ 'PARAMETER["false_easting",0],'
+ 'PARAMETER["false_northing",0],'
+ 'UNIT["metre",1,'
+ ' AUTHORITY["EPSG","9001"]],'
+ 'AXIS["Easting",EAST],'
+ 'AXIS["Northing",NORTH],'
+ 'EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0 '
+ '+lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs"],'
+ 'AUTHORITY["EPSG","3857"]] '
+ )
+ assert CRS.from_string(wkt).to_epsg() == 3857
=====================================
tests/test_overviews.py
=====================================
@@ -10,9 +10,6 @@ from rasterio.env import GDALVersion
from rasterio.errors import OverviewCreationError
-gdal_version = GDALVersion()
-
-
def test_count_overviews_zero(data):
inputfile = str(data.join('RGB.byte.tif'))
with rasterio.open(inputfile) as src:
@@ -42,8 +39,9 @@ def test_build_overviews_two(data):
@pytest.mark.xfail(
- gdal_version < GDALVersion.parse('2.0'),
- reason="Bilinear resampling not supported by GDAL < 2.0")
+ GDALVersion.runtime() < GDALVersion.parse("2.0"),
+ reason="Bilinear resampling not supported by GDAL < 2.0",
+)
def test_build_overviews_bilinear(data):
inputfile = str(data.join('RGB.byte.tif'))
with rasterio.open(inputfile, 'r+') as src:
=====================================
tests/test_path.py
=====================================
@@ -36,12 +36,26 @@ def test_parsed_path_remote(scheme):
assert ParsedPath('example.com/foo.tif', None, scheme).is_remote
+ at pytest.mark.parametrize("uri", ["/test.tif", "file:///test.tif"])
+def test_parsed_path_not_remote(uri):
+ """Check for paths that are not remote"""
+ assert False == ParsedPath.from_uri(uri).is_remote
+
+
@pytest.mark.parametrize('scheme', [None, '', 'zip', 'tar', 'file', 'zip+file'])
def test_parsed_path_file_local(scheme):
"""A parsed path is remote"""
assert ParsedPath('foo.tif', None, scheme).is_local
+ at pytest.mark.parametrize(
+ "uri", ["s3://bucket/test.tif", "https://example.com/test.tif"]
+)
+def test_parsed_path_not_local(uri):
+ """Check for paths that are not local"""
+ assert False == ParsedPath.from_uri(uri).is_local
+
+
def test_parse_path_zip():
"""Correctly parse zip scheme URL"""
parsed = parse_path('zip://tests/data/files.zip!foo.tif')
=====================================
tests/test_read_boundless.py
=====================================
@@ -5,6 +5,7 @@ import numpy as np
import pytest
import rasterio
+from rasterio.enums import Resampling
from rasterio.windows import Window
@@ -146,3 +147,28 @@ def test_msk_read_masks(path_rgb_msk_byte_tif):
assert not msk[0:195,0:195].any()
# We have the valid data expected in the center.
assert msk.mean() > 90
+
+
+ at pytest.mark.xfail(reason="GDAL 3.1 skips overviews because of background layer")
+def test_issue1982(capfd):
+ """See a curl request for overview file"""
+ # Note: the underlying GDAL issue has been fixed after 3.1.3. The
+ # rasterio 1.1.6 wheels published to PyPI will include a patched
+ # 2.4.4 that also fixes the issue. This test will XPASS in the
+ # rasterio-wheels tests.
+ with rasterio.Env(CPL_CURL_VERBOSE=True), rasterio.open(
+ "https://raw.githubusercontent.com/mapbox/rasterio/master/tests/data/green.tif"
+ ) as src:
+ image = src.read(
+ indexes=[1, 2, 3],
+ window=Window(col_off=-32, row_off=-32, width=64, height=64),
+ resampling=Resampling.cubic,
+ boundless=True,
+ out_shape=(3, 10, 10),
+ fill_value=42,
+ )
+ captured = capfd.readouterr()
+ assert "green.tif" in captured.err
+ assert "green.tif.ovr" in captured.err
+ assert (42 == image[:, :3, :]).all()
+ assert (42 == image[:, :, :3]).all()
=====================================
tests/test_rio_convert.py
=====================================
@@ -287,3 +287,20 @@ def test_convert_overwrite_with_option(runner, tmpdir):
'convert', 'tests/data/RGB.byte.tif', '-o', outputname, '-f', 'JPEG',
'--overwrite'])
assert result.exit_code == 0
+
+
+def test_convert_no_input(runner, tmpdir):
+ """Test fix of issue1985"""
+ outputname = str(tmpdir.join("test.tif"))
+ result = runner.invoke(main_group, ["convert", "-o", outputname, "-f", "JPEG"])
+ assert result.exit_code == 2
+
+
+def test_convert_no_input_overwrite(runner, tmpdir):
+ """Test fix of issue1985"""
+ outputname = str(tmpdir.join("test.tif"))
+ result = runner.invoke(
+ main_group, ["convert", "--overwrite", outputname, "-f", "JPEG"]
+ )
+ assert result.exit_code == 2
+ assert "Insufficient inputs" in result.output
=====================================
tests/test_rio_helpers.py
=====================================
@@ -1,3 +1,6 @@
+"""Test of CLI helpers"""
+
+import click
import pytest
from rasterio.errors import FileOverwriteError
@@ -21,6 +24,18 @@ def test_resolve_files_inout__inout_files_output_o():
files=('a', 'b', 'c'), output='out') == ('out', ['a', 'b', 'c'])
+def test_resolve_files_insufficient_inputs():
+ with pytest.raises(click.BadParameter) as excinfo:
+ helpers.resolve_inout(files=["a"], num_inputs=1)
+ assert "Insufficient inputs" in str(excinfo.value)
+
+
+def test_resolve_files_too_many_inputs():
+ with pytest.raises(click.BadParameter) as excinfo:
+ helpers.resolve_inout(files=["a", "b", "c"], num_inputs=1)
+ assert "Too many inputs" in str(excinfo.value)
+
+
def test_fail_overwrite(tmpdir):
"""Unforced overwrite of existing file fails."""
foo_tif = tmpdir.join('foo.tif')
@@ -29,6 +44,7 @@ def test_fail_overwrite(tmpdir):
helpers.resolve_inout(files=[str(x) for x in tmpdir.listdir()])
assert "file exists and won't be overwritten without use of the " in str(excinfo.value)
+
def test_overwrite(tmpdir):
"""Forced overwrite of existing file succeeds."""
foo_tif = tmpdir.join('foo.tif')
=====================================
tests/test_rio_merge.py
=====================================
@@ -556,3 +556,9 @@ def test_merge_precision(tmpdir, precision):
result = runner.invoke(main_group, ["merge", "-f", "AAIGrid"] + precision + inputs + [outputname])
assert result.exit_code == 0
assert open(outputname).read() == textwrap.dedent(expected)
+
+
+def test_merge_filenames(tiffs):
+ inputs = [str(x) for x in tiffs.listdir()]
+ inputs.sort()
+ merge(inputs, res=2)
=====================================
tests/test_session.py
=====================================
@@ -224,3 +224,15 @@ def test_session_aws_or_dummy_dummy(monkeypatch):
with monkeypatch.context() as mpctx:
mpctx.setattr("rasterio.session.boto3", None)
assert isinstance(Session.aws_or_dummy(), DummySession)
+
+
+def test_no_sign_request(monkeypatch):
+ """If AWS_NO_SIGN_REQUEST is set do not default to aws_unsigned=False"""
+ monkeypatch.setenv("AWS_NO_SIGN_REQUEST", "YES")
+ assert AWSSession().unsigned
+
+
+def test_no_credentialization_if_unsigned(monkeypatch):
+ """Don't get credentials if we're not signing, see #1984"""
+ sesh = AWSSession(aws_unsigned=True)
+ assert sesh._creds is None
=====================================
tests/test_warp.py
=====================================
@@ -13,7 +13,12 @@ from rasterio.control import GroundControlPoint
from rasterio.crs import CRS
from rasterio.enums import Resampling
from rasterio.env import GDALVersion
-from rasterio.errors import (GDALBehaviorChangeException, CRSError, GDALVersionError)
+from rasterio.errors import (
+ GDALBehaviorChangeException,
+ CRSError,
+ GDALVersionError,
+ TransformError,
+)
from rasterio.warp import (
reproject,
transform_geom,
@@ -101,12 +106,12 @@ WGS84_crs = CRS.from_epsg(4326)
def test_transform_src_crs_none():
with pytest.raises(CRSError):
- transform(None, WGS84_crs, [], [])
+ transform(None, WGS84_crs, [1], [1])
def test_transform_dst_crs_none():
with pytest.raises(CRSError):
- transform(WGS84_crs, None, [], [])
+ transform(WGS84_crs, None, [1], [1])
def test_transform_bounds_src_crs_none():
@@ -217,19 +222,23 @@ def test_transform_bounds__esri_wkt():
)
-def test_transform_bounds_densify():
+ at pytest.mark.parametrize(
+ "density,expected",
+ [
+ (0, (-1684649.41338, -350356.81377, 1684649.41338, 2234551.18559)),
+ (100, (-1684649.41338, -555777.79210, 1684649.41338, 2234551.18559)),
+ ],
+)
+def test_transform_bounds_densify(density, expected):
# This transform is non-linear along the edges, so densification produces
# a different result than otherwise
src_crs = CRS.from_epsg(4326)
- dst_crs = CRS.from_epsg(2163)
- assert np.allclose(
- transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=0),
- (-1684649.41338, -350356.81377, 1684649.41338, 2234551.18559),
+ dst_crs = CRS.from_proj4(
+ "+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs"
)
-
assert np.allclose(
- transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=100),
- (-1684649.41338, -555777.79210, 1684649.41338, 2234551.18559),
+ expected,
+ transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=density),
)
@@ -1092,6 +1101,59 @@ def test_reproject_resampling(path_rgb_byte_tif, method):
assert np.count_nonzero(out) in expected[method]
+ at pytest.mark.parametrize("test3d,count_nonzero", [(True, 1309625), (False, 437686)])
+def test_reproject_array_interface(test3d, count_nonzero, path_rgb_byte_tif):
+ class DataArray:
+ def __init__(self, data):
+ self.data = data
+
+ def __array__(self, dtype=None):
+ return self.data
+
+ @property
+ def dtype(self):
+ return self.data.dtype
+
+ with rasterio.open(path_rgb_byte_tif) as src:
+ if test3d:
+ source = DataArray(src.read())
+ else:
+ source = DataArray(src.read(1))
+ out = DataArray(np.empty(source.data.shape, dtype=np.uint8))
+ reproject(
+ source,
+ out,
+ src_transform=src.transform,
+ src_crs=src.crs,
+ src_nodata=src.nodata,
+ dst_transform=DST_TRANSFORM,
+ dst_crs="EPSG:3857",
+ dst_nodata=99,
+ )
+ assert isinstance(out, DataArray)
+ assert np.count_nonzero(out.data[out.data != 99]) == count_nonzero
+
+
+ at pytest.mark.parametrize("test3d,count_nonzero", [(True, 1309625), (False, 437686)])
+def test_reproject_masked(test3d, count_nonzero, path_rgb_byte_tif):
+ with rasterio.open(path_rgb_byte_tif) as src:
+ if test3d:
+ source = src.read(masked=True)
+ else:
+ source = src.read(1, masked=True)
+ out = np.empty(source.shape, dtype=np.uint8)
+ reproject(
+ source,
+ out,
+ src_transform=src.transform,
+ src_crs=src.crs,
+ dst_transform=DST_TRANSFORM,
+ dst_crs="EPSG:3857",
+ dst_nodata=99,
+ )
+ assert np.ma.is_masked(source)
+ assert np.count_nonzero(out[out != 99]) == count_nonzero
+
@pytest.mark.parametrize("method", SUPPORTED_RESAMPLING)
def test_reproject_resampling_alpha(method):
@@ -1628,3 +1690,29 @@ def test_reproject_init_dest_nodata():
src_nodata=0, init_dest_nodata=False
)
assert destination.all()
+
+
+def test_empty_transform_inputs():
+ """Check for fix of #1952"""
+ assert ([], []) == rasterio.warp.transform(
+ "EPSG:3857", "EPSG:4326", [], [], zs=None
+ )
+
+
+def test_empty_transform_inputs_z():
+ """Check for fix of #1952"""
+ assert ([], [], []) == rasterio.warp.transform(
+ "EPSG:3857", "EPSG:4326", [], [], zs=[]
+ )
+
+
+def test_empty_transform_inputs_length():
+ """Get an exception of inputs have different lengths"""
+ with pytest.raises(TransformError):
+ rasterio.warp.transform("EPSG:3857", "EPSG:4326", [1], [1, 2])
+
+
+def test_empty_transform_inputs_length_z():
+ """Get an exception of inputs have different lengths"""
+ with pytest.raises(TransformError):
+ rasterio.warp.transform("EPSG:3857", "EPSG:4326", [1, 2], [1, 2], zs=[0])
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/commit/fa275678b4b2ef28c7a3e872dcbc0e8042d19f3b
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/commit/fa275678b4b2ef28c7a3e872dcbc0e8042d19f3b
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/20200915/b03d3447/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list