[Git][debian-gis-team/rasterio][master] 4 commits: New upstream version 1.1.4
Bas Couwenberg
gitlab at salsa.debian.org
Thu May 7 14:33:45 BST 2020
Bas Couwenberg pushed to branch master at Debian GIS Project / rasterio
Commits:
aee4b2f3 by Bas Couwenberg at 2020-05-07T15:17:39+02:00
New upstream version 1.1.4
- - - - -
51aba796 by Bas Couwenberg at 2020-05-07T15:18:15+02:00
Update upstream source from tag 'upstream/1.1.4'
Update to upstream version '1.1.4'
with Debian dir 1b00551671adee4e5dd58c55c607d69364cb292c
- - - - -
fcf3a7c7 by Bas Couwenberg at 2020-05-07T15:18:42+02:00
New upstream release.
- - - - -
cc6f0e00 by Bas Couwenberg at 2020-05-07T15:22:37+02:00
Set distribution to unstable.
- - - - -
27 changed files:
- .travis.yml
- CHANGES.txt
- debian/changelog
- docs/topics/reproject.rst
- docs/topics/resampling.rst
- rasterio/__init__.py
- rasterio/_base.pyx
- rasterio/_features.pyx
- rasterio/_io.pyx
- rasterio/_warp.pyx
- rasterio/compat.py
- rasterio/errors.py
- rasterio/features.py
- rasterio/io.py
- rasterio/path.py
- rasterio/rio/clip.py
- rasterio/shutil.pyx
- rasterio/vrt.py
- rasterio/warp.py
- requirements-dev.txt
- requirements.txt
- tests/test_env.py
- tests/test_features.py
- tests/test_path.py
- tests/test_rio_convert.py
- tests/test_rio_rasterize.py
- tests/test_rio_warp.py
Changes:
=====================================
.travis.yml
=====================================
@@ -56,7 +56,7 @@ install:
- "if [ \"$GDALVERSION\" == \"master\" -o $(gdal-config --version) == \"$GDALVERSION\" ]; then echo \"Using gdal $GDALVERSION\"; else echo \"NOT using gdal $GDALVERSION as expected; aborting\"; exit 1; fi"
- "python -m pip wheel -r requirements-dev.txt"
- "python -m pip install -r requirements-dev.txt"
- - "GDAL_CONFIG=$GDALINST/gdal-$GDALVERSION/bin/gdal-config python -m pip install --upgrade --force-reinstall --no-use-pep517 -e .[test,plot]"
+ - "GDAL_CONFIG=$GDALINST/gdal-$GDALVERSION/bin/gdal-config python -m pip install --no-deps --force-reinstall --no-use-pep517 -e ."
- "python -m pip install coveralls>=1.1"
- "rio --version"
- "rio --gdal-version"
=====================================
CHANGES.txt
=====================================
@@ -1,13 +1,30 @@
Changes
=======
+1.1.4 (2020-05-07)
+------------------
+
+Bug fixes:
+
+- Raise a more helpful exception when asked to open a dataset in write mode
+ using a read-only driver (#1919).
+- Missing support for geometry collections in the rasterio.features module
+ (#1914) has been fixed (#1915).
+- Support for the int8 data type has been extended to the InMemoryRaster class
+ (#1880).
+- rasterio.plot.show_hist caused a Python 3.8 syntax warning. This was fixed by
+ #1874.
+- Make the strict intersection in rio-clip optional with a --with-complement
+ option (#1907).
+- Add a new as_vsi() method to the Path class for internal use and improve
+ Windows path handling and tests thereof (#1895).
+
1.1.3 (2020-02-24)
------------------
Bug fixes:
- Raise RasterioIOError when errors occur while creating a dataset (#1796).
-- Ensure the output of read_masks contains only 255 and 0 (#1721).
- Filter more categories of invalid features in rasterize (#1815).
- Fall back to use of a DummySession instead of failing when boto3 isn't
available (#1864).
=====================================
debian/changelog
=====================================
@@ -1,10 +1,11 @@
-rasterio (1.1.3-2) UNRELEASED; urgency=medium
+rasterio (1.1.4-1) unstable; urgency=medium
* Team upload.
+ * New upstream release.
* Bump debhelper compat to 10, changes:
- Drop --parallel option, enabled by default
- -- Bas Couwenberg <sebastic at debian.org> Thu, 19 Mar 2020 20:39:32 +0100
+ -- Bas Couwenberg <sebastic at debian.org> Thu, 07 May 2020 15:22:24 +0200
rasterio (1.1.3-1) unstable; urgency=medium
=====================================
docs/topics/reproject.rst
=====================================
@@ -27,7 +27,7 @@ transform.
# degrees N, each pixel covering 15".
rows, cols = src_shape = (512, 512)
d = 1.0/240 # decimal degrees per pixel
- # The following is equivalent to
+ # The following is equivalent to
# A(d, 0, -cols*d/2, 0, -d, rows*d/2).
src_transform = A.translation(-cols*d/2, rows*d/2) * A.scale(d, -d)
src_crs = {'init': 'EPSG:4326'}
@@ -36,13 +36,13 @@ transform.
# Destination: a 1024 x 1024 dataset in Web Mercator (EPSG:3857)
# with origin at 0.0, 0.0.
dst_shape = (1024, 1024)
- dst_transform = [-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0]
+ dst_transform = A.translation(-237481.5, 237536.4) * A.scale(425.0, -425.0)
dst_crs = {'init': 'EPSG:3857'}
destination = np.zeros(dst_shape, np.uint8)
reproject(
- source,
- destination,
+ source,
+ destination,
src_transform=src_transform,
src_crs=src_crs,
dst_transform=dst_transform,
@@ -64,7 +64,7 @@ Estimating optimal output shape
Rasterio provides a :func:`rasterio.warp.calculate_default_transform()` function to
determine the optimal resolution and transform for the destination raster.
-Given a source dataset in a known coordinate reference system, this
+Given a source dataset in a known coordinate reference system, this
function will return a ``transform, width, height`` tuple which is calculated
by libgdal.
@@ -120,8 +120,6 @@ See ``rasterio/rio/warp.py`` for more complex examples of reprojection based on
new bounds, dimensions, and resolution (as well as a command-line interface
described :ref:`here <warp>`).
-
-
It is also possible to use :func:`~rasterio.warp.reproject()` to create an output dataset zoomed
out by a factor of 2. Methods of the :class:`rasterio.Affine` class help us generate
the output dataset's transform matrix and, thereby, its spatial extent.
@@ -170,4 +168,3 @@ References
----------
.. [1] https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.geometric_transform.html#scipy.ndimage.geometric_transform
-
=====================================
docs/topics/resampling.rst
=====================================
@@ -1,7 +1,8 @@
Resampling
==========
-For details on changing coordinate reference systems, see `Reprojection`.
+For details on changing coordinate reference systems, see
+:doc:`Reprojection <reproject>`.
Up and downsampling
-------------------
@@ -26,43 +27,42 @@ method.
import rasterio
from rasterio.enums import Resampling
+ upscale_factor = 2
+
with rasterio.open("example.tif") as dataset:
+
+ # resample data to target shape
data = dataset.read(
- out_shape=(dataset.height * 2, dataset.width * 2, dataset.count),
+ out_shape=(
+ dataset.count,
+ int(dataset.height * upscale_factor),
+ int(dataset.width * upscale_factor)
+ ),
resampling=Resampling.bilinear
)
-Here is an example of downsampling by a factor of 2 using the average resampling
-method.
-
-.. code-block:: python
-
- with rasterio.open("example.tif") as dataset:
- data = dataset.read(
- out_shape=(dataset.height / 2, dataset.width / 2, dataset.count),
- resampling=Resampling.average
+ # scale image transform
+ transform = dataset.transform * dataset.transform.scale(
+ (dataset.width / data.shape[-1]),
+ (dataset.height / data.shape[-2])
)
-.. note::
-
- After these resolution changing operations, the dataset's resolution and the
- resolution components of its affine *transform* property no longer apply to
- the new arrays.
+Downsampling to 1/2 of the resolution can be done with ``upscale_factor = 1/2``.
Resampling Methods
------------------
-When you change the raster cell grid, you must recalulate the pixel values.
+When you change the raster cell grid, you must recalculate the pixel values.
There is no "correct" way to do this as all methods involve some interpolation.
-The current resampling methods can be found in the `rasterio.enums`_ source.
+The current resampling methods can be found in the
+:class:`rasterio.enums.Resampling()` class.
-Of note, the default ``Resampling.nearest`` method may not be suitable for
-continuous data. In those cases, ``Resampling.bilinear`` and
-``Resampling.cubic`` are better suited. Some specialized statistical
-resampling method exist, e.g. ``Resampling.average``, which may be useful when
+Of note, the default :attr:`~rasterio.enums.Resampling.nearest` method may not
+be suitable for continuous data. In those cases,
+:attr:`~rasterio.enums.Resampling.bilinear` and
+:attr:`~rasterio.enums.Resampling.cubic` are better suited.
+Some specialized statistical resampling method exist, e.g.
+:attr:`~rasterio.enums.Resampling.average`, which may be useful when
certain numerical properties of the data are to be retained.
-
-
-.. _rasterio.enums: https://github.com/mapbox/rasterio/blob/master/rasterio/enums.py#L28
=====================================
rasterio/__init__.py
=====================================
@@ -25,7 +25,7 @@ from rasterio.dtypes import (
bool_, ubyte, sbyte, uint8, int8, uint16, int16, uint32, int32, float32, float64,
complex_, check_dtype)
from rasterio.env import ensure_env_with_credentials, Env
-from rasterio.errors import RasterioIOError
+from rasterio.errors import RasterioIOError, DriverCapabilityError
from rasterio.compat import string_types
from rasterio.io import (
DatasetReader, get_writer_for_path, get_writer_for_driver, MemoryFile)
@@ -40,9 +40,8 @@ import rasterio.coords
import rasterio.enums
import rasterio.path
-
__all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.1.3"
+__version__ = "1.1.4"
__gdal_version__ = gdal_version()
# Rasterio attaches NullHandler to the 'rasterio' logger and its
@@ -92,10 +91,6 @@ def open(fp, mode='r', driver=None, width=None, height=None, count=None,
count : int, optional
The count of dataset bands. Required in 'w' or 'w+' modes, it is
ignored in 'r' or 'r+' modes.
- dtype : str or numpy dtype
- The data type for bands. For example: 'uint8' or
- ``rasterio.uint16``. Required in 'w' or 'w+' modes, it is
- ignored in 'r' or 'r+' modes.
crs : str, dict, or CRS; optional
The coordinate reference system. Required in 'w' or 'w+' modes,
it is ignored in 'r' or 'r+' modes.
@@ -103,6 +98,10 @@ def open(fp, mode='r', driver=None, width=None, height=None, count=None,
Affine transformation mapping the pixel space to geographic
space. Required in 'w' or 'w+' modes, it is ignored in 'r' or
'r+' modes.
+ dtype : str or numpy dtype
+ The data type for bands. For example: 'uint8' or
+ ``rasterio.uint16``. Required in 'w' or 'w+' modes, it is
+ ignored in 'r' or 'r+' modes.
nodata : int, float, or nan; optional
Defines the pixel value to be interpreted as not valid data.
Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+'
@@ -217,18 +216,26 @@ def open(fp, mode='r', driver=None, width=None, height=None, count=None,
# None.
if mode == 'r':
s = DatasetReader(path, driver=driver, sharing=sharing, **kwargs)
- elif mode == 'r+':
- s = get_writer_for_path(path)(path, mode, driver=driver, sharing=sharing, **kwargs)
+ elif mode == "r+":
+ s = get_writer_for_path(path, driver=driver)(
+ path, mode, driver=driver, sharing=sharing, **kwargs
+ )
elif mode.startswith("w"):
- s = get_writer_for_driver(driver)(path, mode, driver=driver,
- width=width, height=height,
- count=count, crs=crs,
- transform=transform,
- dtype=dtype, nodata=nodata,
- sharing=sharing,
- **kwargs)
+ writer = get_writer_for_driver(driver)
+ if writer is not None:
+ s = writer(path, mode, driver=driver,
+ width=width, height=height,
+ count=count, crs=crs,
+ transform=transform,
+ dtype=dtype, nodata=nodata,
+ sharing=sharing,
+ **kwargs)
+ else:
+ raise DriverCapabilityError(
+ "Writer does not exist for driver: %s" % str(driver)
+ )
else:
- raise ValueError(
+ raise DriverCapabilityError(
"mode must be one of 'r', 'r+', or 'w', not %s" % mode)
return s
=====================================
rasterio/_base.pyx
=====================================
@@ -31,7 +31,7 @@ from rasterio.errors import (
RasterBlockError, BandOverviewError)
from rasterio.profiles import Profile
from rasterio.transform import Affine, guard_transform, tastes_like_gdal
-from rasterio.path import parse_path, vsi_path
+from rasterio.path import parse_path
from rasterio import windows
include "gdal.pxi"
@@ -60,7 +60,7 @@ def get_dataset_driver(path):
Parameters
----------
- path : rasterio.path.Path
+ path : rasterio.path.Path or str
A remote or local dataset path.
Returns
@@ -70,7 +70,7 @@ def get_dataset_driver(path):
cdef GDALDatasetH dataset = NULL
cdef GDALDriverH driver = NULL
- path = vsi_path(path)
+ path = parse_path(path).as_vsi()
path = path.encode('utf-8')
try:
@@ -179,7 +179,7 @@ cdef class DatasetBase(object):
Parameters
----------
- path : rasterio.path.Path
+ path : rasterio.path.Path or str
Path of the local or remote dataset.
driver : str or list of str
A single driver name or a list of driver names to consider when
@@ -202,7 +202,7 @@ cdef class DatasetBase(object):
self._hds = NULL
if path is not None:
- filename = vsi_path(path)
+ filename = parse_path(path).as_vsi()
# driver may be a string or list of strings. If the
# former, we put it into a list.
=====================================
rasterio/_features.pyx
=====================================
@@ -380,7 +380,7 @@ def _explode(coords):
def _bounds(geometry, north_up=True, transform=None):
- """Bounding box of a GeoJSON geometry.
+ """Bounding box of a GeoJSON geometry, GeometryCollection, or FeatureCollection.
left, bottom, right, top
*not* xmin, ymin, xmax, ymax
@@ -390,7 +390,9 @@ def _bounds(geometry, north_up=True, transform=None):
From Fiona 1.4.8 with updates here to handle feature collections.
TODO: add to Fiona.
"""
+
if 'features' in geometry:
+ # Input is a FeatureCollection
xmins = []
ymins = []
xmaxs = []
@@ -406,7 +408,25 @@ def _bounds(geometry, north_up=True, transform=None):
else:
return min(xmins), max(ymaxs), max(xmaxs), min(ymins)
- else:
+ elif 'geometries' in geometry:
+ # Input is a geometry collection
+ xmins = []
+ ymins = []
+ xmaxs = []
+ ymaxs = []
+ for geometry in geometry['geometries']:
+ xmin, ymin, xmax, ymax = _bounds(geometry)
+ xmins.append(xmin)
+ ymins.append(ymin)
+ xmaxs.append(xmax)
+ ymaxs.append(ymax)
+ if north_up:
+ return min(xmins), min(ymins), max(xmaxs), max(ymaxs)
+ else:
+ return min(xmins), max(ymaxs), max(xmaxs), min(ymins)
+
+ elif 'coordinates' in geometry:
+ # Input is a singular geometry object
if transform is not None:
xyz = list(_explode(geometry['coordinates']))
xyz_px = [transform * point for point in xyz]
@@ -419,6 +439,11 @@ def _bounds(geometry, north_up=True, transform=None):
else:
return min(xyz[0]), max(xyz[1]), max(xyz[0]), min(xyz[1])
+ # all valid inputs returned above, so whatever falls through is an error
+ raise ValueError(
+ "geometry must be a GeoJSON-like geometry, GeometryCollection, "
+ "or FeatureCollection"
+ )
# Mapping of OGR integer geometry types to GeoJSON type names.
GEOMETRY_TYPES = {
=====================================
rasterio/_io.pyx
=====================================
@@ -1,6 +1,5 @@
"""Rasterio input/output."""
-
# cython: boundscheck=False, c_string_type=unicode, c_string_encoding=utf8
from __future__ import absolute_import
@@ -32,7 +31,7 @@ from rasterio.errors import (
)
from rasterio.sample import sample_gen
from rasterio.transform import Affine
-from rasterio.path import parse_path, vsi_path, UnparsedPath
+from rasterio.path import parse_path, UnparsedPath
from rasterio.vrt import _boundless_vrt_doc
from rasterio.windows import Window, intersection
@@ -1052,10 +1051,6 @@ cdef class DatasetWriterBase(DatasetReaderBase):
count : int, optional
The count of dataset bands. Required in 'w' or 'w+' modes, it is
ignored in 'r' or 'r+' modes.
- dtype : str or numpy dtype
- The data type for bands. For example: 'uint8' or
- ``rasterio.uint16``. Required in 'w' or 'w+' modes, it is
- ignored in 'r' or 'r+' modes.
crs : str, dict, or CRS; optional
The coordinate reference system. Required in 'w' or 'w+' modes,
it is ignored in 'r' or 'r+' modes.
@@ -1063,6 +1058,10 @@ cdef class DatasetWriterBase(DatasetReaderBase):
Affine transformation mapping the pixel space to geographic
space. Required in 'w' or 'w+' modes, it is ignored in 'r' or
'r+' modes.
+ dtype : str or numpy dtype
+ The data type for bands. For example: 'uint8' or
+ ``rasterio.uint16``. Required in 'w' or 'w+' modes, it is
+ ignored in 'r' or 'r+' modes.
nodata : int, float, or nan; optional
Defines the pixel value to be interpreted as not valid data.
Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+'
@@ -1114,7 +1113,7 @@ cdef class DatasetWriterBase(DatasetReaderBase):
# Make and store a GDAL dataset handle.
filename = path.name
- path = vsi_path(path)
+ path = path.as_vsi()
name_b = path.encode('utf-8')
fname = name_b
@@ -1136,7 +1135,7 @@ cdef class DatasetWriterBase(DatasetReaderBase):
if k.lower() in ['affine']:
continue
- k, v = k.upper(), str(v).upper()
+ k, v = k.upper(), str(v)
if k in ['BLOCKXSIZE', 'BLOCKYSIZE'] and not tiled:
continue
@@ -1746,6 +1745,7 @@ cdef class InMemoryRaster:
cdef OGRSpatialReferenceH osr = NULL
cdef GDALDriverH mdriver = NULL
cdef GDAL_GCP *gcplist = NULL
+ cdef char **options = NULL
if image is not None:
if image.ndim == 3:
@@ -1773,10 +1773,13 @@ cdef class InMemoryRaster:
"in a `with rasterio.Env()` or `with rasterio.open()` "
"block.")
+ if dtype == 'int8':
+ options = CSLSetNameValue(options, 'PIXELTYPE', 'SIGNEDBYTE')
+
datasetname = str(uuid4()).encode('utf-8')
self._hds = exc_wrap_pointer(
GDALCreate(memdriver, <const char *>datasetname, width, height,
- count, <GDALDataType>dtypes.dtype_rev[dtype], NULL))
+ count, <GDALDataType>dtypes.dtype_rev[dtype], options))
if transform is not None:
self.transform = transform
@@ -1816,6 +1819,9 @@ cdef class InMemoryRaster:
CPLFree(srcwkt)
_safe_osr_release(osr)
+ if options != NULL:
+ CSLDestroy(options)
+
self._image = None
if image is not None:
self.write(image)
@@ -1917,10 +1923,6 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
count : int, optional
The count of dataset bands. Required in 'w' or 'w+' modes, it is
ignored in 'r' or 'r+' modes.
- dtype : str or numpy dtype
- The data type for bands. For example: 'uint8' or
- ``rasterio.uint16``. Required in 'w' or 'w+' modes, it is
- ignored in 'r' or 'r+' modes.
crs : str, dict, or CRS; optional
The coordinate reference system. Required in 'w' or 'w+' modes,
it is ignored in 'r' or 'r+' modes.
@@ -1928,6 +1930,10 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
Affine transformation mapping the pixel space to geographic
space. Required in 'w' or 'w+' modes, it is ignored in 'r' or
'r+' modes.
+ dtype : str or numpy dtype
+ The data type for bands. For example: 'uint8' or
+ ``rasterio.uint16``. Required in 'w' or 'w+' modes, it is
+ ignored in 'r' or 'r+' modes.
nodata : int, float, or nan; optional
Defines the pixel value to be interpreted as not valid data.
Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+'
@@ -2005,7 +2011,7 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
# Parse the path to determine if there is scheme-specific
# configuration to be done.
- path = vsi_path(path)
+ path = path.as_vsi()
name_b = path.encode('utf-8')
memdrv = GDALGetDriverByName("MEM")
@@ -2015,6 +2021,9 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
# We've mapped numpy scalar types to GDAL types so see
# if we can crosswalk those.
if hasattr(self._init_dtype, 'type'):
+ if self._init_dtype == 'int8':
+ options = CSLSetNameValue(options, 'PIXELTYPE', 'SIGNEDBYTE')
+
tp = self._init_dtype.type
if tp not in dtypes.dtype_rev:
raise ValueError(
@@ -2026,7 +2035,7 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
self._hds = exc_wrap_pointer(
GDALCreate(memdrv, "temp", self.width, self.height,
- self._count, gdal_dtype, NULL))
+ self._count, gdal_dtype, options))
if self._init_nodata is not None:
for i in range(self._count):
@@ -2097,7 +2106,7 @@ cdef class BufferedDatasetWriterBase(DatasetWriterBase):
# Skip items that are definitely *not* valid driver options.
if k.lower() in ['affine']:
continue
- k, v = k.upper(), str(v).upper()
+ k, v = k.upper(), str(v)
key_b = k.encode('utf-8')
val_b = v.encode('utf-8')
key_c = key_b
=====================================
rasterio/_warp.pyx
=====================================
@@ -25,7 +25,6 @@ from rasterio.errors import (
GDALOptionNotImplementedError,
DriverRegistrationError, CRSError, RasterioIOError,
RasterioDeprecationWarning, WarpOptionsError)
-from rasterio.path import parse_path, vsi_path
from rasterio.transform import Affine, from_bounds, guard_transform, tastes_like_gdal
cimport numpy as np
@@ -443,7 +442,7 @@ def _reproject(
# Set up GDALCreateGenImgProjTransformer2 keyword arguments.
cdef char **imgProjOptions = NULL
- CSLSetNameValue(imgProjOptions, "GCPS_OK", "TRUE")
+ imgProjOptions = CSLSetNameValue(imgProjOptions, "GCPS_OK", "TRUE")
# See http://www.gdal.org/gdal__alg_8h.html#a94cd172f78dbc41d6f407d662914f2e3
# for a list of supported options. I (Sean) don't see harm in
=====================================
rasterio/compat.py
=====================================
@@ -15,7 +15,7 @@ if sys.version_info[0] >= 3: # pragma: no cover
from collections import UserDict
from collections.abc import Iterable, Mapping
from inspect import getfullargspec as getargspec
- from pathlib import Path
+ import pathlib
else: # pragma: no cover
warnings.warn("Python 2 compatibility will be removed after version 1.1", DeprecationWarning)
@@ -28,5 +28,4 @@ else: # pragma: no cover
from UserDict import UserDict
from inspect import getargspec
from collections import Iterable, Mapping
- class Path(object):
- pass
+ pathlib = None
=====================================
rasterio/errors.py
=====================================
@@ -21,6 +21,10 @@ class EnvError(RasterioError):
or modified."""
+class DriverCapabilityError(RasterioError, ValueError):
+ """Raised when a format driver can't a feature such as writing."""
+
+
class DriverRegistrationError(ValueError):
"""Raised when a format driver is requested but is not registered."""
=====================================
rasterio/features.py
=====================================
@@ -373,6 +373,14 @@ def bounds(geometry, north_up=True, transform=None):
return tuple(geometry['bbox'])
geom = geometry.get('geometry') or geometry
+
+ # geometry must be a geometry, GeometryCollection, or FeatureCollection
+ if not ('coordinates' in geom or 'geometries' in geom or 'features' in geom):
+ raise ValueError(
+ "geometry must be a GeoJSON-like geometry, GeometryCollection, "
+ "or FeatureCollection"
+ )
+
return _bounds(geom, north_up=north_up, transform=transform)
=====================================
rasterio/io.py
=====================================
@@ -121,16 +121,16 @@ class MemoryFile(MemoryFileBase):
Other parameters are optional and have the same semantics as the
parameters of `rasterio.open()`.
"""
- vsi_path = UnparsedPath(self.name)
+ mempath = UnparsedPath(self.name)
if self.closed:
raise IOError("I/O operation on closed file.")
if self.exists():
- log.debug("VSI path: {}".format(vsi_path.path))
- return DatasetReader(vsi_path, driver=driver, sharing=sharing, **kwargs)
+ log.debug("VSI path: {}".format(mempath.path))
+ return DatasetReader(mempath, driver=driver, sharing=sharing, **kwargs)
else:
writer = get_writer_for_driver(driver)
- return writer(vsi_path, 'w+', driver=driver, width=width,
+ return writer(mempath, 'w+', driver=driver, width=width,
height=height, count=count, crs=crs,
transform=transform, dtype=dtype,
nodata=nodata, sharing=sharing, **kwargs)
@@ -172,11 +172,11 @@ class ZipMemoryFile(MemoryFile):
-------
A Rasterio dataset object
"""
- vsi_path = UnparsedPath('/vsizip{0}/{1}'.format(self.name, path.lstrip('/')))
+ zippath = UnparsedPath('/vsizip{0}/{1}'.format(self.name, path.lstrip('/')))
if self.closed:
raise IOError("I/O operation on closed file.")
- return DatasetReader(vsi_path, driver=driver, sharing=sharing, **kwargs)
+ return DatasetReader(zippath, driver=driver, sharing=sharing, **kwargs)
def get_writer_for_driver(driver):
@@ -191,7 +191,8 @@ def get_writer_for_driver(driver):
return cls
-def get_writer_for_path(path):
+def get_writer_for_path(path, driver=None):
"""Return the writer class appropriate for the existing dataset."""
- driver = get_dataset_driver(path)
+ if not driver:
+ driver = get_dataset_driver(path)
return get_writer_for_driver(driver)
=====================================
rasterio/path.py
=====================================
@@ -1,13 +1,11 @@
"""Dataset paths, identifiers, and filenames"""
-import os.path
import re
import sys
import attr
-from rasterio.compat import Path as PathlibPath
-from rasterio.compat import string_types, urlparse
+from rasterio.compat import pathlib, string_types, urlparse
from rasterio.errors import PathError
# Supported URI schemes and their mapping to GDAL's VSI suffix.
@@ -34,6 +32,9 @@ REMOTESCHEMES = set([k for k, v in SCHEMES.items() if v in ('curl', 's3', 'oss',
class Path(object):
"""Base class for dataset paths"""
+ def as_vsi(self):
+ return vsi_path(self)
+
@attr.s(slots=True)
class ParsedPath(Path):
@@ -129,13 +130,16 @@ def parse_path(path):
if isinstance(path, Path):
return path
- elif isinstance(path, PathlibPath):
- return ParsedPath(path, None, None)
+ elif pathlib and isinstance(path, pathlib.PurePath):
+ return ParsedPath(path.as_posix(), None, None)
elif isinstance(path, string_types):
- if sys.platform == "win32" and re.match("^[a-zA-Z]\\:", path):
- return UnparsedPath(path)
+ if sys.platform == "win32" and re.match(r"^[a-zA-Z]\:", path):
+ if pathlib:
+ return ParsedPath(pathlib.Path(path).as_posix(), None, None)
+ else:
+ return UnparsedPath(path)
elif path.startswith('/vsi'):
return UnparsedPath(path)
@@ -167,6 +171,7 @@ def vsi_path(path):
Returns
-------
str
+
"""
if isinstance(path, UnparsedPath):
return path.path
=====================================
rasterio/rio/clip.py
=====================================
@@ -50,9 +50,24 @@ projection_projected_opt = click.option(
@projection_projected_opt
@options.overwrite_opt
@options.creation_options
+ at click.option(
+ "--with-complement/--without-complement",
+ default=False,
+ help="Include the relative complement of the raster in the given bounds (giving a larger result), else return results only from the intersection of the raster and the bounds (the default).",
+)
@click.pass_context
-def clip(ctx, files, output, bounds, like, driver, projection,
- overwrite, creation_options):
+def clip(
+ ctx,
+ files,
+ output,
+ bounds,
+ like,
+ driver,
+ projection,
+ overwrite,
+ creation_options,
+ with_complement,
+):
"""Clips a raster using projected or geographic bounds.
\b
@@ -108,8 +123,11 @@ def clip(ctx, files, output, bounds, like, driver, projection,
raise click.UsageError('--bounds or --like required')
bounds_window = src.window(*bounds)
- bounds_window = bounds_window.intersection(
- Window(0, 0, src.width, src.height))
+
+ if not with_complement:
+ bounds_window = bounds_window.intersection(
+ Window(0, 0, src.width, src.height)
+ )
# Get the window with integer height
# and width that contains the bounds window.
@@ -133,6 +151,11 @@ def clip(ctx, files, output, bounds, like, driver, projection,
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(src.read(window=out_window,
- out_shape=(src.count, height, width)))
+ with rasterio.open(output, "w", **out_kwargs) as out:
+ out.write(
+ src.read(
+ window=out_window,
+ out_shape=(src.count, height, width),
+ boundless=True,
+ )
+ )
=====================================
rasterio/shutil.pyx
=====================================
@@ -15,7 +15,7 @@ from rasterio._err cimport exc_wrap_int, exc_wrap_pointer
from rasterio.env import ensure_env_with_credentials
from rasterio._err import CPLE_OpenFailedError
from rasterio.errors import DriverRegistrationError, RasterioIOError
-from rasterio.path import parse_path, vsi_path
+from rasterio.path import parse_path
log = logging.getLogger(__name__)
@@ -34,8 +34,8 @@ def exists(path):
cdef GDALDatasetH h_dataset = NULL
- gdal_path = vsi_path(parse_path(path))
- b_path = gdal_path.encode('utf-8')
+ vsipath = parse_path(path).as_vsi()
+ b_path = vsipath.encode('utf-8')
cdef char* c_path = b_path
with nogil:
@@ -110,7 +110,7 @@ def copy(src, dst, driver='GTiff', strict=True, **creation_options):
# Open a new GDAL dataset if src is a string.
if isinstance(src, str):
- if vsi_path(parse_path(src)) == vsi_path(parse_path(dst)):
+ if parse_path(src).as_vsi() == parse_path(dst).as_vsi():
raise RasterioIOError("{} and {} identify the same dataset.".format(src, dst))
src = src.encode('utf-8')
@@ -123,7 +123,7 @@ def copy(src, dst, driver='GTiff', strict=True, **creation_options):
# Try to use the existing GDAL dataset handle otherwise.
else:
- if src.name == vsi_path(parse_path(dst)):
+ if src.name == parse_path(dst).as_vsi():
raise RasterioIOError("{} and {} identify the same dataset.".format(src.name, dst))
src_dataset = (<DatasetReaderBase?>src).handle()
@@ -177,13 +177,13 @@ def copyfiles(src, dst):
src_path = parse_path(src)
dst_path = parse_path(dst)
- if vsi_path(src_path) == vsi_path(dst_path):
+ if src_path.as_vsi() == dst_path.as_vsi():
raise RasterioIOError("{} and {} identify the same dataset.".format(src, dst))
# VFS paths probabaly don't work, but its hard to be completely certain
# so just attempt to use them.
- gdal_src_path = vsi_path(src_path)
- gdal_dst_path = vsi_path(dst_path)
+ gdal_src_path = src_path.as_vsi()
+ gdal_dst_path = dst_path.as_vsi()
b_gdal_src_path = gdal_src_path.encode('utf-8')
b_gdal_dst_path = gdal_dst_path.encode('utf-8')
cdef char* c_gdal_src_path = b_gdal_src_path
@@ -223,7 +223,7 @@ def delete(path, driver=None):
cdef GDALDatasetH h_dataset = NULL
cdef GDALDriverH h_driver = NULL
- gdal_path = vsi_path(parse_path(path))
+ gdal_path = parse_path(path).as_vsi()
b_path = gdal_path.encode('utf-8')
cdef char* c_path = b_path
=====================================
rasterio/vrt.py
=====================================
@@ -7,7 +7,7 @@ from rasterio._warp import WarpedVRTReaderBase
from rasterio.dtypes import _gdal_typename
from rasterio.enums import MaskFlags
from rasterio.env import env_ctx_if_needed
-from rasterio.path import parse_path, vsi_path
+from rasterio.path import parse_path
from rasterio.transform import TransformMethodsMixin
from rasterio.windows import WindowMethodsMixin
@@ -208,7 +208,7 @@ def _boundless_vrt_doc(
sourcefilename = ET.SubElement(complexsource, 'SourceFilename')
sourcefilename.attrib['relativeToVRT'] = "0"
sourcefilename.attrib["shared"] = "0"
- sourcefilename.text = vsi_path(parse_path(src_dataset.name))
+ sourcefilename.text = parse_path(src_dataset.name).as_vsi()
sourceband = ET.SubElement(complexsource, 'SourceBand')
sourceband.text = str(bidx)
sourceproperties = ET.SubElement(complexsource, 'SourceProperties')
@@ -250,7 +250,7 @@ def _boundless_vrt_doc(
sourcefilename = ET.SubElement(simplesource, 'SourceFilename')
sourcefilename.attrib['relativeToVRT'] = "0"
sourcefilename.attrib["shared"] = "0"
- sourcefilename.text = vsi_path(parse_path(src_dataset.name))
+ sourcefilename.text = parse_path(src_dataset.name).as_vsi()
sourceband = ET.SubElement(simplesource, 'SourceBand')
sourceband.text = 'mask,1'
=====================================
rasterio/warp.py
=====================================
@@ -75,7 +75,7 @@ def transform_geom(
Example: CRS({'init': 'EPSG:4326'})
dst_crs: CRS or dict
Target coordinate reference system.
- geom: GeoJSON like dict object
+ geom: GeoJSON like dict object or iterable of GeoJSON like objects.
antimeridian_cutting: bool, optional
If True, cut geometries at the antimeridian, otherwise geometries
will not be cut (default). If False and GDAL is 2.2.0 or newer
@@ -91,8 +91,8 @@ def transform_geom(
Returns
---------
- out: GeoJSON like dict object
- Transformed geometry in GeoJSON dict format
+ out: GeoJSON like dict object or list of GeoJSON like objects.
+ Transformed geometry(s) in GeoJSON dict format
"""
return _transform_geom(
=====================================
requirements-dev.txt
=====================================
@@ -6,7 +6,7 @@ delocate
hypothesis
numpydoc
packaging
-pytest>=3.1.0
+pytest~=4.6.0
pytest-cov>=2.2.0
sphinx
sphinx-rtd-theme
=====================================
requirements.txt
=====================================
@@ -1,9 +1,11 @@
-affine>=1.3.0
-attrs>=16.0.0
+affine~=2.3.0
+attrs>=17.4.0
boto3>=1.2.4
click==7.0
+click-plugins
cligj>=0.5
-enum34;python_version<"3.4"
+enum34; python_version < "3.4"
+matplotlib
numpy>=1.10
-snuggs>=1.4.1
-setuptools>=0.9.8
+snuggs~=1.4.0
+setuptools>=20.0
=====================================
tests/test_env.py
=====================================
@@ -791,9 +791,13 @@ aws_access_key_id = bar
""")
monkeypatch.setenv('AWS_SHARED_CREDENTIALS_FILE', str(credentials_file))
monkeypatch.delenv('AWS_ACCESS_KEY_ID', raising=False)
- # Assert that we don't have any AWS credentials by accident.
- with pytest.raises(Exception):
- rasterio.open("s3://mapbox/rasterio/RGB.byte.tif")
+
+ # To verify that we're unauthenticated, we make a request for an known existing object that will return 404 Not Found.
+ with pytest.raises(Exception) as exc_info:
+ s3 = boto3.client("s3")
+ s3.head_object(Bucket="landsat-pds", Key="L8/139/045/LC81390452014295LGN00/LC81390452014295LGN00_B1.TIF")
+
+ assert exc_info.value.response["Error"]["Code"] == "403"
with rasterio.Env() as env:
assert env.drivers()
=====================================
tests/test_features.py
=====================================
@@ -48,17 +48,42 @@ def test_bounds_z():
assert bounds(g) == (10, 10, 10, 10)
assert bounds(MockGeoInterface(g)) == (10, 10, 10, 10)
-
-def test_bounds_invalid_obj():
- with pytest.raises(KeyError):
- bounds({'type': 'bogus', 'not_coordinates': []})
+ at pytest.mark.parametrize('geometry', [
+ {'type': 'Polygon'},
+ {'type': 'Polygon', 'not_coordinates': []},
+ {'type': 'bogus', 'not_coordinates': []},
+ {
+ 'type': 'GeometryCollection',
+ 'geometries': [
+ {'type': 'Point', 'coordinates': [1, 1]},
+ {'type': 'LineString', 'not_coordinates': [[-10, -20], [10, 20]]},
+ ]
+ }
+])
+def test_bounds_invalid_obj(geometry):
+ with pytest.raises(ValueError, match="geometry must be a GeoJSON-like geometry, GeometryCollection, or FeatureCollection"):
+ bounds(geometry)
-def test_feature_collection(basic_featurecollection):
+def test_bounds_feature_collection(basic_featurecollection):
fc = basic_featurecollection
assert bounds(fc) == bounds(fc['features'][0]) == (2, 2, 4.25, 4.25)
+def test_bounds_geometry_collection():
+ gc = {
+ 'type': 'GeometryCollection',
+ 'geometries': [
+ {'type': 'Point', 'coordinates': [1, 1]},
+ {'type': 'LineString', 'coordinates': [[-10, -20], [10, 20]]},
+ {'type': 'Polygon', 'coordinates': [[[5, 5], [25, 50], [25, 5]]]}
+ ]
+ }
+
+ assert bounds(gc) == (-10, -20, 25, 50)
+ assert bounds(MockGeoInterface(gc)) == (-10, -20, 25, 50)
+
+
def test_bounds_existing_bbox(basic_featurecollection):
"""Test with existing bbox in geojson.
=====================================
tests/test_path.py
=====================================
@@ -5,6 +5,7 @@ import sys
import pytest
import rasterio
+import rasterio.compat
from rasterio.errors import PathError
from rasterio.path import parse_path, vsi_path, ParsedPath, UnparsedPath
@@ -92,6 +93,11 @@ def test_vsi_path_scheme():
assert vsi_path(ParsedPath('/foo.tif', 'tests/data/files.zip', 'zip')) == '/vsizip/tests/data/files.zip/foo.tif'
+def test_path_as_vsi_scheme():
+ """Correctly make a vsi path"""
+ assert ParsedPath('/foo.tif', 'tests/data/files.zip', 'zip').as_vsi() == '/vsizip/tests/data/files.zip/foo.tif'
+
+
def test_vsi_path_file():
"""Correctly make and ordinary file path from a file path"""
assert vsi_path(ParsedPath('foo.tif', None, 'file')) == 'foo.tif'
@@ -107,6 +113,11 @@ def test_vsi_path_unparsed():
assert vsi_path(UnparsedPath("foo")) == "foo"
+def test_path_as_vsi_unparsed():
+ """Correctly make GDAL filename from unparsed path"""
+ assert UnparsedPath("foo").as_vsi() == "foo"
+
+
def test_vsi_path_error():
"""Raise ValuError if argument is not a path"""
with pytest.raises(ValueError):
@@ -182,3 +193,14 @@ def test_path_error(path):
def test_parse_path():
pathlib = pytest.importorskip("pathlib")
assert isinstance(parse_path(pathlib.Path("/foo/bar.tif")), ParsedPath)
+
+
+def test_parse_path_win():
+ pathlib = pytest.importorskip("pathlib")
+ assert isinstance(parse_path(pathlib.PureWindowsPath(r"C:\foo\bar.tif")), ParsedPath)
+
+
+def test_parse_path_win_no_pathlib(monkeypatch):
+ monkeypatch.setattr(rasterio.path.sys, "platform", "win32")
+ monkeypatch.setattr(rasterio.path, "pathlib", None)
+ assert isinstance(parse_path(r"C:\foo\bar.tif"), UnparsedPath)
=====================================
tests/test_rio_convert.py
=====================================
@@ -1,15 +1,13 @@
-import sys
import os
-import logging
-import numpy as np
+
from click.testing import CliRunner
+import numpy as np
+import pytest
import rasterio
from rasterio.rio.main import main_group
-logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
-
TEST_BBOX = [-11850000, 4804000, -11840000, 4808000]
@@ -17,11 +15,12 @@ def bbox(*args):
return ' '.join([str(x) for x in args])
-def test_clip_bounds(runner, tmpdir):
+ at pytest.mark.parametrize("bounds", [bbox(*TEST_BBOX)])
+def test_clip_bounds(runner, tmpdir, bounds):
output = str(tmpdir.join('test.tif'))
result = runner.invoke(
- main_group,
- ['clip', 'tests/data/shade.tif', output, '--bounds', bbox(*TEST_BBOX)])
+ main_group, ["clip", "tests/data/shade.tif", output, "--bounds", bounds]
+ )
assert result.exit_code == 0
assert os.path.exists(output)
@@ -29,6 +28,29 @@ def test_clip_bounds(runner, tmpdir):
assert out.shape == (419, 173)
+ at pytest.mark.parametrize("bounds", [bbox(*TEST_BBOX)])
+def test_clip_bounds_with_complement(runner, tmpdir, bounds):
+ output = str(tmpdir.join("test.tif"))
+ result = runner.invoke(
+ main_group,
+ [
+ "clip",
+ "tests/data/shade.tif",
+ output,
+ "--bounds",
+ bounds,
+ "--with-complement",
+ ],
+ )
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+
+ with rasterio.open(output) as out:
+ assert out.shape == (419, 1047)
+ data = out.read()
+ assert (data[420:, :] == 255).all()
+
+
def test_clip_bounds_geographic(runner, tmpdir):
output = str(tmpdir.join('test.tif'))
result = runner.invoke(
@@ -106,9 +128,16 @@ def test_clip_overwrite_with_option(runner, tmpdir):
assert result.exit_code == 0
result = runner.invoke(
- main_group, [
- 'clip', 'tests/data/shade.tif', output, '--bounds', bbox(*TEST_BBOX),
- '--overwrite'])
+ main_group,
+ [
+ "clip",
+ "tests/data/shade.tif",
+ output,
+ "--bounds",
+ bbox(*TEST_BBOX),
+ "--overwrite",
+ ],
+ )
assert result.exit_code == 0
=====================================
tests/test_rio_rasterize.py
=====================================
@@ -215,7 +215,8 @@ def test_rasterize_invalid_like_raster(tmpdir, runner, basic_feature):
input=json.dumps(basic_feature))
assert result.exit_code == 2
- assert 'Invalid value for "--like":' in result.output
+ assert "Invalid value for" in result.output
+ assert "--like" in result.output
def test_rasterize_like_raster_src_crs_mismatch(tmpdir, runner, basic_feature,
=====================================
tests/test_rio_warp.py
=====================================
@@ -607,4 +607,5 @@ def test_warp_resampling_not_yet_supported(
'--resampling', method.name])
assert result.exit_code == 2
- assert 'Invalid value for "--resampling"' in result.output
+ assert "Invalid value for" in result.output
+ assert "--resampling" in result.output
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/ecab57ed0505a5faf8011d1a0ae7c355826f8443...cc6f0e00c4a35837586413cf52542cd12320dd64
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/rasterio/-/compare/ecab57ed0505a5faf8011d1a0ae7c355826f8443...cc6f0e00c4a35837586413cf52542cd12320dd64
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/20200507/3ca753ac/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list