[rasterio] 01/03: Imported Upstream version 0.17
Johan Van de Wauw
johanvdw-guest at moszumanska.debian.org
Thu Jan 15 21:54:20 UTC 2015
This is an automated email from the git hooks/post-receive script.
johanvdw-guest pushed a commit to branch master
in repository rasterio.
commit f460b6c2e07202fd3ee327d75c7fa1da20ce8ef8
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date: Thu Jan 15 22:34:48 2015 +0100
Imported Upstream version 0.17
---
.travis.yml | 3 +-
CHANGES.txt | 7 ++
README.rst | 60 ++++++-----
build-wheels.sh | 18 ++++
docs/cli.rst | 12 +--
rasterio/__init__.py | 2 +-
rasterio/_base.pyx | 5 +-
rasterio/_drivers.pyx | 14 ++-
rasterio/_io.pyx | 221 +++++++++++++++++++++++++++-----------
rasterio/rio/bands.py | 20 ++--
rasterio/rio/cli.py | 41 +++----
rasterio/rio/features.py | 82 ++++++--------
rasterio/rio/info.py | 3 +-
rasterio/rio/merge.py | 127 +++++++++++++++++-----
rasterio/rio/options.py | 21 ----
rasterio/rio/rio.py | 76 +++++--------
rasterio/tool.py | 61 ++++++-----
requirements-dev.txt | 13 ++-
requirements.txt | 6 +-
setup.py | 106 +++++++++++++------
tests/test_cli.py | 115 --------------------
tests/test_indexing.py | 10 +-
tests/test_read.py | 13 ---
tests/test_read_boundless.py | 51 +++++++++
tests/test_read_resample.py | 33 ++++++
tests/test_rio_bands.py | 12 +--
tests/test_rio_features.py | 99 +++++++++++++++++
tests/test_rio_info.py | 18 +++-
tests/test_rio_merge.py | 247 +++++++++++++++++++++++++++++++++++++++++++
tests/test_rio_options.py | 15 ---
tests/test_rio_rio.py | 38 ++++---
tests/test_tool.py | 44 ++++++++
32 files changed, 1073 insertions(+), 520 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 0fc4c5c..97cb081 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,6 @@ install:
- "pip install coveralls"
- "pip install -e ."
script:
- - py.test
- - coverage run --source=rasterio --omit='*.pxd,*.pyx,*/tests/*,*/docs/*,*/examples/*,*/benchmarks/*' -m py.test
+ - coverage run --source=rasterio --omit='*.pxd,*.pyx,*/tests/*,*/docs/*,*/examples/*,*/benchmarks/*,*/rio/main.py,*/rio/__init__.py' -m py.test
after_success:
- coveralls
diff --git a/CHANGES.txt b/CHANGES.txt
index 6830152..fc51402 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,13 @@
Changes
=======
+0.17.0 (2015-01-15)
+-------------------
+- Enhancements to rio-merge: relaxation of same-extent and same-resolution
+ constraints, addition of --bounds and --res options (#242, 247).
+- Data files in support of binary wheels (#239).
+- Fix for reading bands with undefined nodata (#237, #240).
+
0.16.0 (2014-12-16)
-------------------
- More graceful, slice-like handling of windows (#191).
diff --git a/README.rst b/README.rst
index 067dc11..14dfc7e 100644
--- a/README.rst
+++ b/README.rst
@@ -14,7 +14,7 @@ Rasterio employs GDAL under the hood for file I/O and raster formatting. Its
functions typically accept and return Numpy ndarrays. Rasterio is designed to
make working with geospatial raster data more productive and more fun.
-Rasterio is pronounced raw-STIER-ee-oh.
+Rasterio is pronounced raw-STEER-ee-oh.
Example
=======
@@ -164,36 +164,48 @@ using Python.
>>> b.min(), b.max(), b.mean()
(1, 255, 44.434478650699106)
-Dependencies
+Installation
============
-C library dependecies:
+Dependencies
+------------
-- GDAL 1.9+
+Rasterio has one C library dependency: GDAL >=1.9. GDAL itself depends on a
+number of other libraries provided by most major operating systems and also
+depends on the non standard GEOS and PROJ4 libraries.
-Python package dependencies (see also requirements.txt):
+Python package dependencies (see also requirements.txt): affine, cligj (and
+click), enum34, numpy.
-- affine
-- Numpy
-- setuptools
+Development also requires (see requirements-dev.txt) Cython and other packages.
-Development also requires (see requirements-dev.txt)
+Rasterio binaries for OS X
+--------------------------
-- Cython
-- pytest
+Binary wheels with the GDAL, GEOS, and PROJ4 libraries included are available
+for OS X versions 10.7+ starting with Rasterio version 0.17. To install, just
+run ``pip install rasterio``. These binary wheels are preferred by newer
+versions of pip. If you don't want these wheels and want to install from
+a source distribution, run ``pip install rasterio --no-use-wheel`` instead.
-Installation
-============
+The included GDAL library is fairly minimal, providing only the format drivers
+that ship with GDAL and are enabled by default. To get access to more formats,
+you must build from a source distribution (see below).
-Rasterio is a C extension and to install on Linux or OS X you'll need a working
-compiler (XCode on OS X etc). You'll also need Numpy preinstalled; the Numpy
-headers are required to run the rasterio setup script. Numpy has to be
-installed (via the indicated requirements file) before rasterio can be
-installed. See rasterio's Travis `configuration
+Binary wheels for other operating systems will be available in a future
+release.
+
+Installing from the source distribution
+---------------------------------------
+
+Rasterio is a Python C extension and to build you'll need a working compiler
+(XCode on OS X etc). You'll also need Numpy preinstalled; the Numpy headers are
+required to run the rasterio setup script. Numpy has to be installed (via the
+indicated requirements file) before rasterio can be installed. See rasterio's
+Travis `configuration
<https://github.com/mapbox/rasterio/blob/master/.travis.yml>`__ for more
guidance.
-
Linux
-----
@@ -202,9 +214,8 @@ The following commands are adapted from Rasterio's Travis-CI configuration.
.. code-block:: console
$ sudo add-apt-repository ppa:ubuntugis/ppa
- $ sudo apt-get update -qq
+ $ sudo apt-get update
$ sudo apt-get install python-numpy libgdal1h gdal-bin libgdal-dev
- $ pip install -r https://raw.githubusercontent.com/mapbox/rasterio/master/requirements.txt
$ pip install rasterio
Adapt them as necessary for your Linux system.
@@ -212,17 +223,13 @@ Adapt them as necessary for your Linux system.
OS X
----
-Wheels are available on PyPI for Homebrew based Python environments.
+For a Homebrew based Python environment, do the following.
.. code-block:: console
$ brew install gdal
- $ pip install -r https://raw.githubusercontent.com/mapbox/rasterio/master/requirements.txt
$ pip install rasterio
-The wheels are incompatible with MacPorts. MacPorts users will need to specify
-a source installation instead: ``pip install --no-use-wheel``.
-
Windows
-------
@@ -257,4 +264,3 @@ Changes
-------
See CHANGES.txt
-
diff --git a/build-wheels.sh b/build-wheels.sh
new file mode 100644
index 0000000..36e68dc
--- /dev/null
+++ b/build-wheels.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Automation of this is a TODO. For now, it depends on manually built libraries
+# as detailed in https://gist.github.com/sgillies/a8a2fb910a98a8566d0a.
+
+export MACOSX_DEPLOYMENT_TARGET=10.6
+export GDAL_CONFIG="/usr/local/bin/gdal-config"
+export PACKAGE_DATA=1
+
+VERSION=$1
+
+source $HOME/envs/riowhl27/bin/activate
+CFLAGS="`$GDAL_CONFIG --cflags`" LDFLAGS="`$GDAL_CONFIG --libs` `$GDAL_CONFIG --dep-libs`" python setup.py bdist_wheel -d wheels/$VERSION
+source $HOME/envs/riowhl34/bin/activate
+CFLAGS="`$GDAL_CONFIG --cflags`" LDFLAGS="`$GDAL_CONFIG --libs` `$GDAL_CONFIG --dep-libs`" python setup.py bdist_wheel -d wheels/$VERSION
+
+parallel delocate-wheel -w fixed_wheels/$VERSION --require-archs=intel -v {} ::: wheels/$VERSION/*.whl
+parallel cp {} {.}.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl ::: fixed_wheels/$VERSION/*.whl
diff --git a/docs/cli.rst b/docs/cli.rst
index a31e0ff..a744c4a 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -136,12 +136,12 @@ The insp command opens a dataset and an interpreter.
merge
-----
-The merge command can be used to flatten a stack of identically layed out
+The merge command can be used to flatten a stack of identically structured
datasets.
.. code-block:: console
- $ rio merge rasterio/tests/data/R*.tif -o result.tif
+ $ rio merge rasterio/tests/data/R*.tif merged.tif
shapes
------
@@ -188,10 +188,10 @@ Examples using the Rasterio testing dataset that produce a copy of it.
.. code-block:: console
- $ rio stack RGB.byte.tif -o stacked.tif
- $ rio stack RGB.byte.tif --bidx 1,2,3 -o stacked.tif
- $ rio stack RGB.byte.tif --bidx 1..3 -o stacked.tif
- $ rio stack RGB.byte.tif --bidx ..2 RGB.byte.tif --bidx 3.. -o stacked.tif
+ $ rio stack RGB.byte.tif stacked.tif
+ $ rio stack RGB.byte.tif --bidx 1,2,3 stacked.tif
+ $ rio stack RGB.byte.tif --bidx 1..3 stacked.tif
+ $ rio stack RGB.byte.tif --bidx ..2 RGB.byte.tif --bidx 3.. stacked.tif
transform
---------
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
index 4bbc8d0..531dae2 100644
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -18,7 +18,7 @@ from rasterio.transform import Affine, guard_transform
__all__ = [
'band', 'open', 'drivers', 'copy', 'pad']
-__version__ = "0.16"
+__version__ = "0.17"
log = logging.getLogger('rasterio')
class NullHandler(logging.Handler):
diff --git a/rasterio/_base.pyx b/rasterio/_base.pyx
index e529efd..660d112 100644
--- a/rasterio/_base.pyx
+++ b/rasterio/_base.pyx
@@ -377,10 +377,7 @@ cdef class DatasetReader(object):
"""Returns the window corresponding to the world bounding box."""
ul = self.index(left, top)
lr = self.index(right, bottom)
- if ul[0] < 0 or ul[1] < 0 or lr[0] > self.height or lr[1] > self.width:
- raise ValueError("Bounding box overflows the dataset extents")
- else:
- return tuple(zip(ul, lr))
+ return tuple(zip(ul, lr))
@property
def meta(self):
diff --git a/rasterio/_drivers.pyx b/rasterio/_drivers.pyx
index 7d470e2..b8080da 100644
--- a/rasterio/_drivers.pyx
+++ b/rasterio/_drivers.pyx
@@ -95,9 +95,17 @@ cdef class GDALEnv(object):
raise ValueError("Drivers not registered")
if 'GDAL_DATA' not in os.environ:
- datadir = os.path.join(sys.prefix, 'share/gdal')
- if os.path.exists(os.path.join(datadir, 'pcs.csv')):
- os.environ['GDAL_DATA'] = datadir
+ whl_datadir = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), "gdal_data"))
+ share_datadir = os.path.join(sys.prefix, 'share/gdal')
+ if os.path.exists(os.path.join(whl_datadir, 'pcs.csv')):
+ os.environ['GDAL_DATA'] = whl_datadir
+ elif os.path.exists(os.path.join(share_datadir, 'pcs.csv')):
+ os.environ['GDAL_DATA'] = share_datadir
+ if 'PROJ_LIB' not in os.environ:
+ whl_datadir = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), "proj_data"))
+ os.environ['PROJ_LIB'] = whl_datadir
for key, val in self.options.items():
key_b = key.upper().encode('utf-8')
diff --git a/rasterio/_io.pyx b/rasterio/_io.pyx
index 38edae0..98e18e1 100644
--- a/rasterio/_io.pyx
+++ b/rasterio/_io.pyx
@@ -38,7 +38,7 @@ else:
# Single band IO functions.
cdef int io_ubyte(
- void *hband,
+ void *hband,
int mode,
int xoff,
int yoff,
@@ -51,7 +51,7 @@ cdef int io_ubyte(
&buffer[0, 0], buffer.shape[1], buffer.shape[0], 1, 0, 0)
cdef int io_uint16(
- void *hband,
+ void *hband,
int mode,
int xoff,
int yoff,
@@ -547,31 +547,50 @@ cdef class RasterReader(_base.DatasetReader):
"""
return self.read(bidx, out=out, window=window, masked=masked)
- def read(self, indexes=None, out=None, window=None, masked=None):
- """Read raster bands as a multidimensional array
- If `indexes` is a list, the result is a 3D array, but
- is a 2D array if it is a band index number.
-
- Optional `out` argument is a reference to an output array with the
- same dimensions and shape.
+ def read(self, indexes=None, out=None, window=None, masked=None,
+ boundless=False):
+ """Read raster bands as a multidimensional array
- See `read_band` for usage of the optional `window` argument.
+ Parameters
+ ----------
+ indexes : list of ints or a single int, optional
+ If `indexes` is a list, the result is a 3D array, but is
+ a 2D array if it is a band index number.
+
+ out: numpy ndarray, optional
+ An optional reference to an output array with the same
+ dimensions and shape.
+
+ window : a pair (tuple) of pairs of ints, optional
+ The optional `window` argument is a 2 item tuple. The first
+ item is a tuple containing the indexes of the rows at which
+ the window starts and stops and the second is a tuple
+ containing the indexes of the columns at which the window
+ starts and stops. For example, ((0, 2), (0, 2)) defines
+ a 2x2 window at the upper left of the raster dataset.
+
+ masked : bool, optional
+ The return type will be either a regular NumPy array, or
+ a masked NumPy array depending on the `masked` argument. The
+ return type is forced if either `True` or `False`, but will
+ be chosen if `None`. For `masked=None` (default), the array
+ will be the same type as `out` (if used), or will be masked
+ if any of the nodatavals are not `None`.
+
+ boundless : bool, optional (default `False`)
+ If `True`, windows that extend beyond the dataset's extent
+ are permitted and partially or completely filled arrays will
+ be returned as appropriate.
+
+ Returns
+ -------
+ Numpy ndarray
- The return type will be either a regular NumPy array, or a masked
- NumPy array depending on the `masked` argument. The return type is
- forced if either `True` or `False`, but will be chosen if `None`.
- For `masked=None` (default), the array will be the same type as
- `out` (if used), or will be masked if any of the nodatavals are
- not `None`.
"""
- cdef int height, width, xoff, yoff, aix, bidx, indexes_count
- cdef int retval = 0
- return2d = False
- if self._hds == NULL:
- raise ValueError("can't read closed raster file")
- if indexes is None: # Default: read all bands
+ return2d = False
+ if indexes is None:
indexes = self.indexes
elif isinstance(indexes, int):
indexes = [indexes]
@@ -580,6 +599,7 @@ cdef class RasterReader(_base.DatasetReader):
out.shape = (1,) + out.shape
if not indexes:
raise ValueError("No indexes to read")
+
check_dtypes = set()
nodatavals = []
# Check each index before processing 3D array
@@ -589,44 +609,139 @@ cdef class RasterReader(_base.DatasetReader):
idx = self.indexes.index(bidx)
check_dtypes.add(self.dtypes[idx])
nodatavals.append(self._nodatavals[idx])
+ # Mixed dtype reads are not supported at this time.
if len(check_dtypes) > 1:
raise ValueError("more than one 'dtype' found")
elif len(check_dtypes) == 0:
dtype = self.dtypes[0]
- else: # unique dtype; normal case
+ else:
dtype = check_dtypes.pop()
- # Windows are always limited to the dataset's extent.
+ # Get the natural shape of the read window, boundless or not.
+ win_shape = (len(indexes),)
if window:
- window = eval_window(window, self.height, self.width)
- window = ((
- min(window[0][0] or 0, self.height),
- min(window[0][1] or self.height, self.height)), (
- min(window[1][0] or 0, self.width),
- min(window[1][1] or self.width, self.width)))
-
- out_shape = (len(indexes),) + (
- window
- and window_shape(window, self.height, self.width)
- or self.shape)
+ if boundless:
+ win_shape += (
+ window[0][1]-window[0][0], window[1][1]-window[1][0])
+ else:
+ w = eval_window(window, self.height, self.width)
+ minr = min(max(w[0][0], 0), self.height)
+ maxr = max(0, min(w[0][1], self.height))
+ minc = min(max(w[1][0], 0), self.width)
+ maxc = max(0, min(w[1][1], self.width))
+ win_shape += (maxr - minr, maxc - minc)
+ window = ((minr, maxr), (minc, maxc))
+ else:
+ win_shape += self.shape
+
if out is not None:
if out.dtype != dtype:
raise ValueError(
"the array's dtype '%s' does not match "
"the file's dtype '%s'" % (out.dtype, dtype))
- if out.shape[0] != out_shape[0]:
+ if out.shape[0] != win_shape[0]:
raise ValueError(
- "'out' shape %s does not mach raster slice shape %s" %
- (out.shape, out_shape))
+ "'out' shape %s does not match window shape %s" %
+ (out.shape, win_shape))
if masked is None:
masked = hasattr(out, 'mask')
if masked is None:
masked = any([x is not None for x in nodatavals])
if out is None:
- if masked:
- out = np.ma.empty(out_shape, dtype)
+ out = np.zeros(win_shape, dtype)
+ for ndv, arr in zip(
+ self.nodatavals, out if len(win_shape) == 3 else [out]):
+ if ndv is not None:
+ arr.fill(ndv)
+
+ # We can jump straight to _read() in some cases. We can ignore
+ # the boundless flag if there's no given window.
+ if not boundless or not window:
+ out = self._read(indexes, out, window, dtype)
+
+ else:
+ # Compute the overlap between the dataset and the boundless window.
+ overlap = ((
+ max(min(window[0][0] or 0, self.height), 0),
+ max(min(window[0][1] or self.height, self.height), 0)), (
+ max(min(window[1][0] or 0, self.width), 0),
+ max(min(window[1][1] or self.width, self.width), 0)))
+
+ if overlap != ((0, 0), (0, 0)):
+ # Prepare a buffer.
+ window_h, window_w = win_shape[-2:]
+ overlap_h = overlap[0][1] - overlap[0][0]
+ overlap_w = overlap[1][1] - overlap[1][0]
+ scaling_h = float(out.shape[-2:][0])/window_h
+ scaling_w = float(out.shape[-2:][1])/window_w
+ buffer_shape = (int(overlap_h*scaling_h), int(overlap_w*scaling_w))
+ data = np.empty(win_shape[:-2] + buffer_shape, dtype)
+ data = self._read(indexes, data, overlap, dtype)
+ else:
+ data = None
+
+ if data is not None:
+ # Determine where to put the data in the output window.
+ data_h, data_w = data.shape[-2:]
+ roff = 0
+ coff = 0
+ if window[0][0] < 0:
+ roff = int(window_h*scaling_h) - data_h
+ if window[1][0] < 0:
+ coff = int(window_w*scaling_w) - data_w
+ for dst, src in zip(
+ out if len(out.shape) == 3 else [out],
+ data if len(data.shape) == 3 else [data]):
+ dst[roff:roff+data_h, coff:coff+data_w] = src
+
+ # Masking the output. TODO: explain the logic better.
+ if masked:
+ if len(set(nodatavals)) == 1:
+ if nodatavals[0] is None:
+ out = np.ma.masked_array(out, copy=False)
+ elif np.isnan(nodatavals[0]):
+ out = np.ma.masked_where(np.isnan(out), out, copy=False)
+ else:
+ out = np.ma.masked_equal(out, nodatavals[0], copy=False)
else:
- out = np.empty(out_shape, dtype)
+ out = np.ma.masked_array(out, copy=False)
+ for aix in range(len(indexes)):
+ if nodatavals[aix] is None:
+ band_mask = False
+ elif np.isnan(nodatavals[aix]):
+ band_mask = np.isnan(out[aix])
+ else:
+ band_mask = out[aix] == nodatavals[aix]
+ out[aix].mask = band_mask
+ if return2d:
+ out.shape = out.shape[1:]
+
+ return out
+
+
+ def _read(self, indexes, out, window, dtype):
+ """Read raster bands as a multidimensional array
+
+ If `indexes` is a list, the result is a 3D array, but
+ is a 2D array if it is a band index number.
+
+ Optional `out` argument is a reference to an output array with the
+ same dimensions and shape.
+
+ See `read_band` for usage of the optional `window` argument.
+
+ The return type will be either a regular NumPy array, or a masked
+ NumPy array depending on the `masked` argument. The return type is
+ forced if either `True` or `False`, but will be chosen if `None`.
+ For `masked=None` (default), the array will be the same type as
+ `out` (if used), or will be masked if any of the nodatavals are
+ not `None`.
+ """
+ cdef int height, width, xoff, yoff, aix, bidx, indexes_count
+ cdef int retval = 0
+
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
# Prepare the IO window.
if window:
@@ -696,31 +811,9 @@ cdef class RasterReader(_base.DatasetReader):
elif retval == 4:
raise ValueError("NULL band")
- # Masking the output. TODO: explain the logic better.
- if masked:
- test1nodata = set(nodatavals)
- if len(test1nodata) == 1:
- if nodatavals[0] is None:
- out = np.ma.masked_array(out, copy=False)
- elif np.isnan(nodatavals[0]):
- out = np.ma.masked_where(np.isnan(out), out, copy=False)
- else:
- out = np.ma.masked_equal(out, nodatavals[0], copy=False)
- else:
- out = np.ma.masked_array(out, copy=False)
- for aix in range(len(indexes)):
- if nodatavals[aix] is None:
- band_mask = False
- elif np.isnan(nodatavals[aix]):
- band_mask = np.isnan(out[aix])
- else:
- band_mask = out[aix] == nodatavals[aix]
- out[aix].mask = band_mask
-
- if return2d:
- out.shape = out.shape[1:]
return out
+
def read_mask(self, out=None, window=None):
"""Read the mask band into an `out` array if provided,
otherwise return a new array containing the dataset's
diff --git a/rasterio/rio/bands.py b/rasterio/rio/bands.py
index cab2c7b..0350e27 100644
--- a/rasterio/rio/bands.py
+++ b/rasterio/rio/bands.py
@@ -3,6 +3,7 @@ import os.path
import sys
import click
+from cligj import files_inout_arg, format_opt
import rasterio
@@ -23,20 +24,15 @@ PHOTOMETRIC_CHOICES = [val.lower() for val in [
# Stack command.
@cli.command(short_help="Stack a number of bands into a multiband dataset.")
- at click.argument('input', nargs=-1,
- type=click.Path(exists=True, resolve_path=True), required=True)
+ at files_inout_arg
+ at format_opt
@click.option('--bidx', multiple=True,
help="Indexes of input file bands.")
@click.option('--photometric', default=None,
type=click.Choice(PHOTOMETRIC_CHOICES),
help="Photometric interpretation")
- at click.option('-o','--output',
- type=click.Path(exists=False, resolve_path=True), required=True,
- help="Path to output file.")
- at click.option('-f', '--format', '--driver', default='GTiff',
- help="Output format driver")
@click.pass_context
-def stack(ctx, input, bidx, photometric, output, driver):
+def stack(ctx, files, driver, bidx, photometric):
"""Stack a number of bands from one or more input files into a
multiband dataset.
@@ -74,9 +70,11 @@ def stack(ctx, input, bidx, photometric, output, driver):
logger = logging.getLogger('rio')
try:
with rasterio.drivers(CPL_DEBUG=verbosity>2):
+ output = files[-1]
+ files = files[:-1]
output_count = 0
indexes = []
- for path, item in zip_longest(input, bidx, fillvalue=None):
+ for path, item in zip_longest(files, bidx, fillvalue=None):
with rasterio.open(path) as src:
src_indexes = src.indexes
if item is None:
@@ -99,7 +97,7 @@ def stack(ctx, input, bidx, photometric, output, driver):
indexes.append(parts)
output_count += len(parts)
- with rasterio.open(input[0]) as first:
+ with rasterio.open(files[0]) as first:
kwargs = first.meta
kwargs['transform'] = kwargs.pop('affine')
@@ -112,7 +110,7 @@ def stack(ctx, input, bidx, photometric, output, driver):
with rasterio.open(output, 'w', **kwargs) as dst:
dst_idx = 1
- for path, index in zip(input, indexes):
+ for path, index in zip(files, indexes):
with rasterio.open(path) as src:
if isinstance(index, int):
data = src.read(index)
diff --git a/rasterio/rio/cli.py b/rasterio/rio/cli.py
index cf54fda..37f2ff2 100644
--- a/rasterio/rio/cli.py
+++ b/rasterio/rio/cli.py
@@ -3,9 +3,10 @@ import logging
import sys
import click
+from cligj import verbose_opt, quiet_opt
import rasterio
-from rasterio.rio import options
+
def configure_logging(verbosity):
log_level = max(10, 30 - 10*verbosity)
@@ -14,9 +15,9 @@ def configure_logging(verbosity):
# The CLI command group.
@click.group(help="Rasterio command line interface.")
- at options.verbose
- at options.quiet
- at options.version
+ at verbose_opt
+ at quiet_opt
+ at click.version_option(version=rasterio.__version__, message='%(version)s')
@click.pass_context
def cli(ctx, verbose, quiet):
verbosity = verbose - quiet
@@ -43,41 +44,41 @@ def coords(obj):
yield f
-def write_features(file, collection,
- agg_mode='obj', expression='feature', use_rs=False,
+def write_features(
+ fobj, collection, sequence=False, geojson_type='feature', use_rs=False,
**dump_kwds):
"""Read an iterator of (feat, bbox) pairs and write to file using
the selected modes."""
# Sequence of features expressed as bbox, feature, or collection.
- if agg_mode == 'seq':
+ if sequence:
for feat in collection():
xs, ys = zip(*coords(feat))
bbox = (min(xs), min(ys), max(xs), max(ys))
if use_rs:
- file.write(u'\u001e')
- if expression == 'feature':
- file.write(json.dumps(feat, **dump_kwds))
- elif expression == 'bbox':
- file.write(json.dumps(bbox, **dump_kwds))
+ fobj.write(u'\u001e')
+ if geojson_type == 'feature':
+ fobj.write(json.dumps(feat, **dump_kwds))
+ elif geojson_type == 'bbox':
+ fobj.write(json.dumps(bbox, **dump_kwds))
else:
- file.write(
+ fobj.write(
json.dumps({
'type': 'FeatureCollection',
'bbox': bbox,
'features': [feat]}, **dump_kwds))
- file.write('\n')
+ fobj.write('\n')
# Aggregate all features into a single object expressed as
# bbox or collection.
else:
features = list(collection())
- if expression == 'bbox':
- file.write(json.dumps(collection.bbox, **dump_kwds))
- elif expression == 'feature':
- file.write(json.dumps(features[0], **dump_kwds))
+ if geojson_type == 'bbox':
+ fobj.write(json.dumps(collection.bbox, **dump_kwds))
+ elif geojson_type == 'feature':
+ fobj.write(json.dumps(features[0], **dump_kwds))
else:
- file.write(json.dumps({
+ fobj.write(json.dumps({
'bbox': collection.bbox,
'type': 'FeatureCollection',
'features': features},
**dump_kwds))
- file.write('\n')
+ fobj.write('\n')
diff --git a/rasterio/rio/features.py b/rasterio/rio/features.py
index ede426f..f6d4c24 100644
--- a/rasterio/rio/features.py
+++ b/rasterio/rio/features.py
@@ -5,6 +5,10 @@ import sys
import warnings
import click
+from cligj import (
+ precision_opt, indent_opt, compact_opt, projection_geographic_opt,
+ projection_projected_opt, sequence_opt, use_rs_opt,
+ geojson_type_feature_opt, geojson_type_bbox_opt)
import rasterio
from rasterio.transform import Affine
@@ -17,39 +21,15 @@ warnings.simplefilter('default')
# Shapes command.
@cli.command(short_help="Write the shapes of features.")
@click.argument('input', type=click.Path(exists=True))
-# Coordinate precision option.
- at click.option('--precision', type=int, default=-1,
- help="Decimal precision of coordinates.")
-# JSON formatting options.
- at click.option('--indent', default=None, type=int,
- help="Indentation level for JSON output")
- at click.option('--compact/--no-compact', default=False,
- help="Use compact separators (',', ':').")
-# Geographic (default) or Mercator switch.
- at click.option('--geographic', 'projected', flag_value='geographic',
- default=True,
- help="Output in geographic coordinates (the default).")
- at click.option('--projected', 'projected', flag_value='projected',
- help="Output in projected coordinates.")
-# JSON object (default) or sequence (experimental) switch.
- at click.option('--json-obj', 'json_mode', flag_value='obj', default=True,
- help="Write a single JSON object (the default).")
- at click.option('--x-json-seq', 'json_mode', flag_value='seq',
- help="Write a JSON sequence. Experimental.")
-# Use ASCII RS control code to signal a sequence item (False is default).
-# See http://tools.ietf.org/html/draft-ietf-json-text-sequence-05.
-# Experimental.
- at click.option('--x-json-seq-rs/--x-json-seq-no-rs', default=False,
- help="Use RS as text separator. Experimental.")
-# GeoJSON feature (default), bbox, or collection switch. Meaningful only
-# when --x-json-seq is used.
- at click.option('--collection', 'output_mode', flag_value='collection',
- default=True,
- help="Output as a GeoJSON feature collection (the default).")
- at click.option('--feature', 'output_mode', flag_value='feature',
- help="Output as sequence of GeoJSON features.")
- at click.option('--bbox', 'output_mode', flag_value='bbox',
- help="Output as a GeoJSON bounding box array.")
+ at precision_opt
+ at indent_opt
+ at compact_opt
+ at projection_geographic_opt
+ at projection_projected_opt
+ at sequence_opt
+ at use_rs_opt
+ at geojson_type_feature_opt(True)
+ at geojson_type_bbox_opt(False)
@click.option('--bands/--mask', default=True,
help="Extract shapes from one of the dataset bands or from "
"its nodata mask")
@@ -61,17 +41,17 @@ warnings.simplefilter('default')
help="Include or do not include (the default) nodata regions.")
@click.pass_context
def shapes(
- ctx, input, precision, indent, compact, projected, json_mode,
- x_json_seq_rs, output_mode, bands, bidx, sampling, with_nodata):
+ ctx, input, precision, indent, compact, projection, sequence,
+ use_rs, geojson_type, bands, bidx, sampling, with_nodata):
"""Writes features of a dataset out as GeoJSON. It's intended for
use with single-band rasters and reads from the first band.
"""
- # These import numpy, which we don't want to do unless its needed.
+ # These import numpy, which we don't want to do unless it's needed.
import numpy
import rasterio.features
import rasterio.warp
- verbosity = ctx.obj['verbosity']
+ verbosity = ctx.obj['verbosity'] if ctx.obj else 1
logger = logging.getLogger('rio')
dump_kwds = {'sort_keys': True}
if indent:
@@ -93,6 +73,8 @@ def shapes(
def __call__(self):
with rasterio.open(input) as src:
+ img = None
+ nodata_mask = None
if bands:
if sampling == 1:
img = src.read_band(bidx)
@@ -104,22 +86,22 @@ def shapes(
dtype=src.dtypes[src.indexes.index(bidx)])
img = src.read_band(bidx, img)
transform = src.affine * Affine.scale(float(sampling))
- else:
+ if not bands or not with_nodata:
if sampling == 1:
- img = src.read_mask()
+ nodata_mask = src.read_mask()
transform = src.transform
# Decimate the mask.
else:
- img = numpy.zeros(
+ nodata_mask = numpy.zeros(
(src.height//sampling, src.width//sampling),
dtype=numpy.uint8)
- img = src.read_mask(img)
+ nodata_mask = src.read_mask(nodata_mask)
transform = src.affine * Affine.scale(float(sampling))
bounds = src.bounds
xs = [bounds[0], bounds[2]]
ys = [bounds[1], bounds[3]]
- if projected == 'geographic':
+ if projection == 'geographic':
xs, ys = rasterio.warp.transform(
src.crs, {'init': 'epsg:4326'}, xs, ys)
if precision >= 0:
@@ -129,10 +111,13 @@ def shapes(
self._ys = ys
kwargs = {'transform': transform}
- if not bands and not with_nodata:
- kwargs['mask'] = (img==255)
+ # Default is to exclude nodata features.
+ if nodata_mask is not None:
+ kwargs['mask'] = (nodata_mask==255)
+ if img is None:
+ img = nodata_mask
for g, i in rasterio.features.shapes(img, **kwargs):
- if projected == 'geographic':
+ if projection == 'geographic':
g = rasterio.warp.transform_geom(
src.crs, 'EPSG:4326', g,
antimeridian_cutting=True, precision=precision)
@@ -144,11 +129,14 @@ def shapes(
'bbox': [min(xs), min(ys), max(xs), max(ys)],
'geometry': g }
+ if not sequence:
+ geojson_type = 'collection'
+
try:
with rasterio.drivers(CPL_DEBUG=verbosity>2):
write_features(
- stdout, Collection(), agg_mode=json_mode,
- expression=output_mode, use_rs=x_json_seq_rs,
+ stdout, Collection(), sequence=sequence,
+ geojson_type=geojson_type, use_rs=use_rs,
**dump_kwds)
sys.exit(0)
except Exception:
diff --git a/rasterio/rio/info.py b/rasterio/rio/info.py
index a3e79e0..9c34a9d 100644
--- a/rasterio/rio/info.py
+++ b/rasterio/rio/info.py
@@ -59,7 +59,8 @@ def env(ctx, key):
@click.option('--crs', 'meta_member', flag_value='crs',
help="Print the CRS as a PROJ.4 string.")
@click.option('--bounds', 'meta_member', flag_value='bounds',
- help="Print the nodata value.")
+ help="Print the boundary coordinates "
+ "(left, bottom, right, top).")
@click.pass_context
def info(ctx, input, aspect, indent, namespace, meta_member):
"""Print metadata about the dataset as JSON.
diff --git a/rasterio/rio/merge.py b/rasterio/rio/merge.py
index a2a92f5..dd9335b 100644
--- a/rasterio/rio/merge.py
+++ b/rasterio/rio/merge.py
@@ -1,66 +1,142 @@
# Merge command.
import logging
+import math
import os.path
import sys
+import warnings
import click
+from cligj import files_inout_arg, format_opt
import rasterio
-
from rasterio.rio.cli import cli
+from rasterio.transform import Affine
@cli.command(short_help="Merge a stack of raster datasets.")
- at click.argument('input', nargs=-1,
- type=click.Path(exists=True, resolve_path=True),
- required=True)
- at click.option('-o','--output',
- type=click.Path(exists=False, resolve_path=True),
- required=True,
- help="Path to output file.")
- at click.option('-f', '--format', '--driver', default='GTiff',
- help="Output format driver")
+ at files_inout_arg
+ at format_opt
+ at click.option('--bounds', nargs=4, type=float, default=None,
+ help="Output bounds: left, bottom, right, top.")
+ at click.option('--res', nargs=2, type=float, default=None,
+ help="Output dataset resolution: pixel width, pixel height")
+ at click.option('--nodata', '-n', type=float, default=None,
+ help="Override nodata values defined in input datasets")
@click.pass_context
-def merge(ctx, input, output, driver):
- """Copy valid pixels from input files to the output file.
+def merge(ctx, files, driver, bounds, res, nodata):
+ """Copy valid pixels from input files to an output file.
+
+ All files must have the same number of bands, data type, and
+ coordinate reference system.
- All files must have the same shape, number of bands, and data type.
+ Input files are merged in their listed order using the reverse
+ painter's algorithm. If the output file exists, its values will be
+ overwritten by input values.
- Input files are merged in their listed order using a reverse
- painter's algorithm.
+ Geospatial bounds and resolution of a new output file in the
+ units of the input file coordinate reference system may be provided
+ and are otherwise taken from the first input file.
"""
import numpy as np
- verbosity = ctx.obj['verbosity']
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
logger = logging.getLogger('rio')
+
try:
with rasterio.drivers(CPL_DEBUG=verbosity>2):
+ output = files[-1]
+ files = files[:-1]
- with rasterio.open(input[0]) as first:
+ with rasterio.open(files[0]) as first:
+ first_res = first.res
kwargs = first.meta
- kwargs['transform'] = kwargs.pop('affine')
- dest = np.empty((3,) + first.shape, dtype=first.dtypes[0])
+ kwargs.pop('affine')
+ nodataval = first.nodatavals[0]
+ dtype = first.dtypes[0]
if os.path.exists(output):
+ # TODO: prompt user to update existing file (-i option) like:
+ # overwrite b.tif? (y/n [n]) n
+ # not overwritten
dst = rasterio.open(output, 'r+')
nodataval = dst.nodatavals[0]
+ dtype = dst.dtypes[0]
+ dest = np.zeros((dst.count,) + dst.shape, dtype=dtype)
else:
+ # Create new output file.
+ # Extent from option or extent of all inputs.
+ if not bounds:
+ # scan input files.
+ xs = []
+ ys = []
+ for f in files:
+ with rasterio.open(f) as src:
+ left, bottom, right, top = src.bounds
+ xs.extend([left, right])
+ ys.extend([bottom, top])
+ bounds = min(xs), min(ys), max(xs), max(ys)
+ output_transform = Affine.translation(bounds[0], bounds[3])
+
+ # Resolution/pixel size.
+ if not res:
+ res = first_res
+ output_transform *= Affine.scale(res[0], -res[1])
+
+ # Dataset shape.
+ output_width = int(math.ceil((bounds[2]-bounds[0])/res[0]))
+ output_height = int(math.ceil((bounds[3]-bounds[1])/res[1]))
+
kwargs['driver'] == driver
- dst = rasterio.open(output, 'w', **kwargs)
- nodataval = first.nodatavals[0]
+ kwargs['transform'] = output_transform
+ kwargs['width'] = output_width
+ kwargs['height'] = output_height
- dest.fill(nodataval)
+ dst = rasterio.open(output, 'w', **kwargs)
+ dest = np.zeros((first.count, output_height, output_width),
+ dtype=dtype)
+
+ if nodata is not None:
+ nodataval = nodata
+
+ if nodataval is not None:
+ # Only fill if the nodataval is within dtype's range.
+ inrange = False
+ if np.dtype(dtype).kind in ('i', 'u'):
+ info = np.iinfo(dtype)
+ inrange = (info.min <= nodataval <= info.max)
+ elif np.dtype(dtype).kind == 'f':
+ info = np.finfo(dtype)
+ inrange = (info.min <= nodataval <= info.max)
+ if inrange:
+ dest.fill(nodataval)
+ else:
+ warnings.warn(
+ "Input file's nodata value, %s, is beyond the valid "
+ "range of its data type, %s. Consider overriding it "
+ "using the --nodata option for better results." % (
+ nodataval, dtype))
+ else:
+ nodataval = 0
- for fname in reversed(input):
+ for fname in reversed(files):
with rasterio.open(fname) as src:
- data = src.read()
+ # 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.
+ window = src.window(*dst.bounds)
+ data = np.zeros_like(dest)
+ data = src.read(
+ out=data,
+ window=window,
+ boundless=True,
+ masked=True)
np.copyto(dest, data,
where=np.logical_and(
dest==nodataval, data.mask==False))
if dst.mode == 'r+':
- data = dst.read()
+ data = dst.read(masked=True)
np.copyto(dest, data,
where=np.logical_and(
dest==nodataval, data.mask==False))
@@ -72,4 +148,3 @@ def merge(ctx, input, output, driver):
except Exception:
logger.exception("Failed. Exception caught")
sys.exit(1)
-
diff --git a/rasterio/rio/options.py b/rasterio/rio/options.py
deleted file mode 100644
index 7f8340f..0000000
--- a/rasterio/rio/options.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import click
-
-from rasterio import __version__ as rio_version
-
-
-def print_version(ctx, param, value):
- if not value or ctx.resilient_parsing:
- return
- click.echo(rio_version)
- ctx.exit()
-
-
-verbose = click.option('--verbose', '-v', count=True,
- help="Increase verbosity.")
-
-quiet = click.option('--quiet', '-q', count=True,
- help="Decrease verbosity.")
-
-version = click.option('--version', is_flag=True, callback=print_version,
- expose_value=False, is_eager=True,
- help="Print Rasterio version.")
diff --git a/rasterio/rio/rio.py b/rasterio/rio/rio.py
index 10df923..78b1662 100644
--- a/rasterio/rio/rio.py
+++ b/rasterio/rio/rio.py
@@ -9,9 +9,13 @@ import sys
import warnings
import click
+from cligj import (
+ precision_opt, indent_opt, compact_opt, projection_geographic_opt,
+ projection_projected_opt, projection_mercator_opt,
+ sequence_opt, use_rs_opt, geojson_type_collection_opt,
+ geojson_type_feature_opt, geojson_type_bbox_opt)
import rasterio
-
from rasterio.rio.cli import cli, write_features
@@ -27,7 +31,11 @@ warnings.simplefilter('default')
# Insp command.
@cli.command(short_help="Open a data file and start an interpreter.")
@click.argument('input', type=click.Path(exists=True))
- at click.option('--mode', type=click.Choice(['r', 'r+']), default='r', help="File mode (default 'r').")
+ at click.option(
+ '--mode',
+ type=click.Choice(['r', 'r+']),
+ default='r',
+ help="File mode (default 'r').")
@click.pass_context
def insp(ctx, input, mode):
import rasterio.tool
@@ -54,44 +62,20 @@ def insp(ctx, input, mode):
# One or more files, the bounds of each are a feature in the collection
# object or feature sequence.
@click.argument('input', nargs=-1, type=click.Path(exists=True))
-# Coordinate precision option.
- at click.option('--precision', type=int, default=-1,
- help="Decimal precision of coordinates.")
-# JSON formatting options.
- at click.option('--indent', default=None, type=int,
- help="Indentation level for JSON output")
- at click.option('--compact/--no-compact', default=False,
- help="Use compact separators (',', ':').")
-# Geographic (default) or Mercator switch.
- at click.option('--geographic', 'projected', flag_value='geographic',
- default=True,
- help="Output in geographic coordinates (the default).")
- at click.option('--projected', 'projected', flag_value='projected',
- help="Output in projected coordinates.")
- at click.option('--mercator', 'projected', flag_value='mercator',
- help="Output in Web Mercator coordinates.")
-# JSON object (default) or sequence (experimental) switch.
- at click.option('--json-obj', 'json_mode', flag_value='obj', default=True,
- help="Write a single JSON object (the default).")
- at click.option('--x-json-seq', 'json_mode', flag_value='seq',
- help="Write a JSON sequence. Experimental.")
-# Use ASCII RS control code to signal a sequence item (False is default).
-# See http://tools.ietf.org/html/draft-ietf-json-text-sequence-05.
-# Experimental.
- at click.option('--x-json-seq-rs/--x-json-seq-no-rs', default=False,
- help="Use RS as text separator. Experimental.")
-# GeoJSON feature (default), bbox, or collection switch. Meaningful only
-# when --x-json-seq is used.
- at click.option('--collection', 'output_mode', flag_value='collection',
- default=True,
- help="Output as a GeoJSON feature collection (the default).")
- at click.option('--feature', 'output_mode', flag_value='feature',
- help="Output as sequence of GeoJSON features.")
- at click.option('--bbox', 'output_mode', flag_value='bbox',
- help="Output as a GeoJSON bounding box array.")
+ at precision_opt
+ at indent_opt
+ at compact_opt
+ at projection_geographic_opt
+ at projection_projected_opt
+ at projection_mercator_opt
+ at sequence_opt
+ at use_rs_opt
+ at geojson_type_collection_opt(True)
+ at geojson_type_feature_opt(False)
+ at geojson_type_bbox_opt(False)
@click.pass_context
-def bounds(ctx, input, precision, indent, compact, projected, json_mode,
- x_json_seq_rs, output_mode):
+def bounds(ctx, input, precision, indent, compact, projection, sequence,
+ use_rs, geojson_type):
"""Write bounding boxes to stdout as GeoJSON for use with, e.g.,
geojsonio
@@ -125,10 +109,10 @@ def bounds(ctx, input, precision, indent, compact, projected, json_mode,
bounds = src.bounds
xs = [bounds[0], bounds[2]]
ys = [bounds[1], bounds[3]]
- if projected == 'geographic':
+ if projection == 'geographic':
xs, ys = rasterio.warp.transform(
src.crs, {'init': 'epsg:4326'}, xs, ys)
- if projected == 'mercator':
+ if projection == 'mercator':
xs, ys = rasterio.warp.transform(
src.crs, {'init': 'epsg:3857'}, xs, ys)
if precision >= 0:
@@ -153,15 +137,14 @@ def bounds(ctx, input, precision, indent, compact, projected, json_mode,
self._xs.extend(bbox[::2])
self._ys.extend(bbox[1::2])
- collection = Collection()
-
+ col = Collection()
# Use the generator defined above as input to the generic output
# writing function.
try:
with rasterio.drivers(CPL_DEBUG=verbosity>2):
write_features(
- stdout, collection, agg_mode=json_mode,
- expression=output_mode, use_rs=x_json_seq_rs,
+ stdout, col, sequence=sequence,
+ geojson_type=geojson_type, use_rs=use_rs,
**dump_kwds)
sys.exit(0)
except Exception:
@@ -174,8 +157,7 @@ def bounds(ctx, input, precision, indent, compact, projected, json_mode,
@click.argument('input', default='-', required=False)
@click.option('--src_crs', default='EPSG:4326', help="Source CRS.")
@click.option('--dst_crs', default='EPSG:4326', help="Destination CRS.")
- at click.option('--precision', type=int, default=-1,
- help="Decimal precision of coordinates.")
+ at precision_opt
@click.pass_context
def transform(ctx, input, src_crs, dst_crs, precision):
import rasterio.warp
diff --git a/rasterio/tool.py b/rasterio/tool.py
index d0f368a..a7c2d15 100644
--- a/rasterio/tool.py
+++ b/rasterio/tool.py
@@ -18,36 +18,41 @@ logger = logging.getLogger('rasterio')
Stats = collections.namedtuple('Stats', ['min', 'max', 'mean'])
-def main(banner, dataset):
+# Collect dictionary of functions for use in the interpreter in main()
+funcs = locals()
+
+
+def show(source, cmap='gray'):
+ """Show a raster using matplotlib.
+
+ The raster may be either an ndarray or a (dataset, bidx)
+ tuple.
+ """
+ if isinstance(source, tuple):
+ arr = source[0].read_band(source[1])
+ else:
+ arr = source
+ if plt is not None:
+ plt.imshow(arr, cmap=cmap)
+ plt.show()
+ else:
+ raise ImportError("matplotlib could not be imported")
- def show(source, cmap='gray'):
- """Show a raster using matplotlib.
-
- The raster may be either an ndarray or a (dataset, bidx)
- tuple.
- """
- if isinstance(source, tuple):
- arr = source[0].read_band(source[1])
- else:
- arr = source
- if plt is not None:
- plt.imshow(arr, cmap=cmap)
- plt.show()
- else:
- raise ImportError("matplotlib could not be imported")
-
- def stats(source):
- """Return a tuple with raster min, max, and mean.
- """
- if isinstance(source, tuple):
- arr = source[0].read_band(source[1])
- else:
- arr = source
- return Stats(numpy.min(arr), numpy.max(arr), numpy.mean(arr))
+def stats(source):
+ """Return a tuple with raster min, max, and mean.
+ """
+ if isinstance(source, tuple):
+ arr = source[0].read_band(source[1])
+ else:
+ arr = source
+ return Stats(numpy.min(arr), numpy.max(arr), numpy.mean(arr))
+
+
+def main(banner, dataset):
+ """ Main entry point for use with interpreter """
code.interact(
- banner,
- local=dict(
- locals(), src=dataset, np=numpy, rio=rasterio, plt=plt))
+ banner,
+ local=dict(funcs, src=dataset, np=numpy, rio=rasterio, plt=plt))
return 0
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 41a8d9f..fb826ea 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,7 +1,10 @@
-git+https://github.com/sgillies/affine.git#egg=affine
-Cython>=0.20
-Numpy>=1.8.0
-pytest
+affine
+cligj
coveralls>=0.4
+cython>=0.20
+delocate
+enum34
+numpy>=1.8.0
+pytest
setuptools
-six
+wheel
diff --git a/requirements.txt b/requirements.txt
index e095453..66a3935 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
-affine>=1.0
-click
+affine
+cligj
enum34
-numpy>=1.8
+numpy>=1.8.0
setuptools
diff --git a/setup.py b/setup.py
index 7e39bfa..cd491cf 100755
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,17 @@
#!/usr/bin/env python
+
+# Two environmental variables influence this script.
+#
+# GDAL_CONFIG: the path to a gdal-config program that points to GDAL headers,
+# libraries, and data files.
+#
+# PACKAGE_DATA: if defined, GDAL and PROJ4 data files will be copied into the
+# source or binary distribution. This is essential when creating self-contained
+# binary wheels.
+
import logging
import os
+import shutil
import subprocess
import sys
from setuptools import setup
@@ -43,13 +54,15 @@ except ImportError:
sys.exit(1)
try:
- gdal_config = "gdal-config"
+ gdal_config = os.environ.get('GDAL_CONFIG', 'gdal-config')
with open("gdal-config.txt", "w") as gcfg:
subprocess.call([gdal_config, "--cflags"], stdout=gcfg)
subprocess.call([gdal_config, "--libs"], stdout=gcfg)
+ subprocess.call([gdal_config, "--datadir"], stdout=gcfg)
with open("gdal-config.txt", "r") as gcfg:
cflags = gcfg.readline().strip()
libs = gcfg.readline().strip()
+ datadir = gcfg.readline().strip()
for item in cflags.split():
if item.startswith("-I"):
include_dirs.extend(item[2:].split(":"))
@@ -61,9 +74,30 @@ try:
else:
# e.g. -framework GDAL
extra_link_args.append(item)
+
+ # Conditionally copy the GDAL data. To be used in conjunction with
+ # the bdist_wheel command to make self-contained binary wheels.
+ if os.environ.get('PACKAGE_DATA'):
+ try:
+ shutil.rmtree('rasterio/gdal_data')
+ except OSError:
+ pass
+ shutil.copytree(datadir, 'rasterio/gdal_data')
+
except Exception as e:
log.warning("Failed to get options via gdal-config: %s", str(e))
+# Conditionally copy PROJ.4 data. Presumes PROJ.4 is installed locally
+# with --prefix=/usr/local.
+if os.environ.get('PACKAGE_DATA'):
+ projdatadir = '/usr/local/share/proj'
+ if os.path.exists(projdatadir):
+ try:
+ shutil.rmtree('rasterio/proj_data')
+ except OSError:
+ pass
+ shutil.copytree(projdatadir, 'rasterio/proj_data')
+
ext_options = dict(
include_dirs=include_dirs,
library_dirs=library_dirs,
@@ -124,43 +158,47 @@ with open('README.rst') as f:
# Runtime requirements.
inst_reqs = [
'affine>=1.0',
- 'click>=3.0',
+ 'cligj',
'Numpy>=1.7' ]
if sys.version_info < (3, 4):
inst_reqs.append('enum34')
-setup(name='rasterio',
- version=version,
- description=(
- "Fast and direct raster I/O for Python programmers who use Numpy"),
- long_description=readme,
- classifiers=[
- 'Development Status :: 4 - Beta',
- 'Intended Audience :: Developers',
- 'Intended Audience :: Information Technology',
- 'Intended Audience :: Science/Research',
- 'License :: OSI Approved :: BSD License',
- 'Programming Language :: C',
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Topic :: Multimedia :: Graphics :: Graphics Conversion',
- 'Topic :: Scientific/Engineering :: GIS'
- ],
- keywords='raster gdal',
- author='Sean Gillies',
- author_email='sean at mapbox.com',
- url='https://github.com/mapbox/rasterio',
- license='BSD',
- package_dir={'': '.'},
- packages=['rasterio', 'rasterio.rio'],
- entry_points='''
+setup_args = dict(
+ name='rasterio',
+ version=version,
+ description="Fast and direct raster I/O for use with Numpy and SciPy",
+ long_description=readme,
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Information Technology',
+ 'Intended Audience :: Science/Research',
+ 'License :: OSI Approved :: BSD License',
+ 'Programming Language :: C',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Topic :: Multimedia :: Graphics :: Graphics Conversion',
+ 'Topic :: Scientific/Engineering :: GIS'],
+ keywords='raster gdal',
+ author='Sean Gillies',
+ author_email='sean at mapbox.com',
+ url='https://github.com/mapbox/rasterio',
+ license='BSD',
+ package_dir={'': '.'},
+ packages=['rasterio', 'rasterio.rio'],
+ entry_points='''
[console_scripts]
rio=rasterio.rio.main:cli
- ''',
- include_package_data=True,
- ext_modules=ext_modules,
- zip_safe=False,
- install_requires=inst_reqs )
+ ''',
+ include_package_data=True,
+ ext_modules=ext_modules,
+ zip_safe=False,
+ install_requires=inst_reqs)
+
+if os.environ.get('PACKAGE_DATA'):
+ setup_args['package_data'] = {'rasterio': ['gdal_data/*', 'proj_data/*']}
+
+setup(**setup_args)
diff --git a/tests/test_cli.py b/tests/test_cli.py
deleted file mode 100644
index 35cbabb..0000000
--- a/tests/test_cli.py
+++ /dev/null
@@ -1,115 +0,0 @@
-import subprocess
-
-
-def test_cli_bounds_obj_bbox():
- result = subprocess.check_output(
- 'rio bounds tests/data/RGB.byte.tif --bbox --precision 6',
- shell=True)
- assert result.decode('utf-8').strip() == '[-78.898133, 23.564991, -76.599438, 25.550874]'
-
-
-def test_cli_bounds_obj_bbox_mercator():
- result = subprocess.check_output(
- 'rio bounds tests/data/RGB.byte.tif --bbox --mercator --precision 3',
- shell=True)
- assert result.decode('utf-8').strip() == '[-8782900.033, 2700489.278, -8527010.472, 2943560.235]'
-
-
-def test_cli_bounds_obj_feature():
- result = subprocess.check_output(
- 'rio bounds tests/data/RGB.byte.tif --feature --precision 6',
- shell=True)
- assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}'
-
-
-def test_cli_bounds_obj_collection():
- result = subprocess.check_output(
- 'rio bounds tests/data/RGB.byte.tif --precision 6',
- shell=True)
- assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
-
-
-def test_cli_bounds_seq_feature_rs():
- result = subprocess.check_output(
- 'rio bounds tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --feature --precision 6',
- shell=True)
- assert result.decode('utf-8').startswith(u'\x1e')
- assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}'
-
-
-def test_cli_bounds_seq_collection():
- result = subprocess.check_output(
- 'rio bounds tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --precision 6',
- shell=True)
- assert result.decode('utf-8').startswith(u'\x1e')
- assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
-
-
-def test_cli_bounds_seq_bbox():
- result = subprocess.check_output(
- 'rio bounds tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --bbox --precision 6',
- shell=True)
- assert result.decode('utf-8').startswith(u'\x1e')
- assert result.decode('utf-8').strip() == '[-78.898133, 23.564991, -76.599438, 25.550874]'
-
-
-def test_cli_bounds_seq_collection_multi(tmpdir):
- filename = str(tmpdir.join("test.json"))
- tmp = open(filename, 'w')
-
- subprocess.check_call(
- 'rio bounds tests/data/RGB.byte.tif tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --precision 6',
- stdout=tmp,
- shell=True)
-
- tmp.close()
- tmp = open(filename, 'r')
- json_texts = []
- text = ""
- for line in tmp:
- rs_idx = line.find(u'\x1e')
- if rs_idx >= 0:
- if text:
- text += line[:rs_idx]
- json_texts.append(text)
- text = line[rs_idx+1:]
- else:
- text += line
- else:
- json_texts.append(text)
-
- assert len(json_texts) == 2
- assert json_texts[0].strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
- assert json_texts[1].strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "1", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
-
-
-def test_cli_info_count():
- result = subprocess.check_output(
- 'if [ `rio info tests/data/RGB.byte.tif --count` -eq 3 ]; '
- 'then echo "True"; fi',
- shell=True)
- assert result.decode('utf-8').strip() == 'True'
-
-
-def test_cli_info_nodata():
- result = subprocess.check_output(
- 'if [ `rio info tests/data/RGB.byte.tif --nodata` = "0.0" ]; '
- 'then echo "True"; fi',
- shell=True)
- assert result.decode('utf-8').strip() == 'True'
-
-
-def test_cli_info_dtype():
- result = subprocess.check_output(
- 'if [ `rio info tests/data/RGB.byte.tif --dtype` = "uint8" ]; '
- 'then echo "True"; fi',
- shell=True)
- assert result.decode('utf-8').strip() == 'True'
-
-
-def test_cli_info_shape():
- result = subprocess.check_output(
- 'if [[ `rio info tests/data/RGB.byte.tif --shape` == "718 791" ]]; '
- 'then echo "True"; fi',
- shell=True, executable='/bin/bash')
- assert result.decode('utf-8').strip() == 'True'
diff --git a/tests/test_indexing.py b/tests/test_indexing.py
index fbc57b4..317ddb3 100644
--- a/tests/test_indexing.py
+++ b/tests/test_indexing.py
@@ -13,13 +13,9 @@ def test_full_window():
with rasterio.open('tests/data/RGB.byte.tif') as src:
assert src.window(*src.bounds) == tuple(zip((0, 0), src.shape))
-def test_window_exception():
+def test_window_no_exception():
with rasterio.open('tests/data/RGB.byte.tif') as src:
left, bottom, right, top = src.bounds
left -= 1000.0
- try:
- _ = src.window(left, bottom, right, top)
- assert False
- except ValueError:
- assert True
-
+ assert src.window(left, bottom, right, top) == (
+ (0, src.height), (-3, src.width))
diff --git a/tests/test_read.py b/tests/test_read.py
index 61bd736..98cf22d 100644
--- a/tests/test_read.py
+++ b/tests/test_read.py
@@ -90,19 +90,6 @@ class ReaderContextTest(unittest.TestCase):
except:
assert "failed to catch exception" is False
- def test_read_out_shape_resample(self):
- with rasterio.open('tests/data/RGB.byte.tif') as s:
- a = numpy.zeros((7, 8), dtype=rasterio.ubyte)
- s.read_band(1, a)
- self.assert_(
- repr(a) == """array([[ 0, 8, 5, 7, 0, 0, 0, 0],
- [ 0, 6, 61, 15, 27, 15, 24, 128],
- [ 0, 20, 152, 23, 15, 19, 28, 0],
- [ 0, 17, 255, 25, 255, 22, 32, 0],
- [ 9, 7, 14, 16, 19, 18, 36, 0],
- [ 6, 27, 43, 207, 38, 31, 73, 0],
- [ 0, 0, 0, 0, 74, 23, 0, 0]], dtype=uint8)""", a)
-
def test_read_basic(self):
with rasterio.open('tests/data/shade.tif') as s:
a = s.read() # Gray
diff --git a/tests/test_read_boundless.py b/tests/test_read_boundless.py
new file mode 100644
index 0000000..e6c2a1f
--- /dev/null
+++ b/tests/test_read_boundless.py
@@ -0,0 +1,51 @@
+import logging
+import sys
+
+import numpy
+
+import rasterio
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_read_boundless_natural_extent():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ data = src.read(boundless=True)
+ assert data.shape == (3, src.height, src.width)
+ assert abs(data[0].mean() - src.read(1).mean()) < 0.0001
+ assert data.any()
+
+def test_read_boundless_beyond():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ data = src.read(window=((-200, -100), (-200, -100)), boundless=True)
+ assert data.shape == (3, 100, 100)
+ assert not data.any()
+
+
+def test_read_boundless_beyond2():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ data = src.read(window=((1000, 1100), (1000, 1100)), boundless=True)
+ assert data.shape == (3, 100, 100)
+ assert not data.any()
+
+
+def test_read_boundless_overlap():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ data = src.read(window=((-200, 200), (-200, 200)), boundless=True)
+ assert data.shape == (3, 400, 400)
+ assert data.any()
+ assert data[0,399,399] == 13
+
+
+def test_read_boundless_resample():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ out = numpy.zeros((3, 800, 800), dtype=numpy.uint8)
+ data = src.read(
+ out=out,
+ window=((-200, 200), (-200, 200)),
+ masked=True,
+ boundless=True)
+ assert data.shape == (3, 800, 800)
+ assert data.any()
+ assert data[0,798,798] == 13
diff --git a/tests/test_read_resample.py b/tests/test_read_resample.py
new file mode 100644
index 0000000..f3eb1bc
--- /dev/null
+++ b/tests/test_read_resample.py
@@ -0,0 +1,33 @@
+import numpy
+
+import rasterio
+
+
+# Rasterio exposes GDAL's resampling/decimation on I/O. These are the tests
+# that it does this correctly.
+#
+# Rasterio's test dataset is 718 rows by 791 columns.
+
+def test_read_out_shape_resample_down():
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ out = numpy.zeros((7, 8), dtype=rasterio.ubyte)
+ data = s.read(1, out=out)
+ expected = numpy.array([
+ [ 0, 8, 5, 7, 0, 0, 0, 0],
+ [ 0, 6, 61, 15, 27, 15, 24, 128],
+ [ 0, 20, 152, 23, 15, 19, 28, 0],
+ [ 0, 17, 255, 25, 255, 22, 32, 0],
+ [ 9, 7, 14, 16, 19, 18, 36, 0],
+ [ 6, 27, 43, 207, 38, 31, 73, 0],
+ [ 0, 0, 0, 0, 74, 23, 0, 0]], dtype=numpy.uint8)
+ assert (data == expected).all() # all True.
+
+
+def test_read_out_shape_resample_up():
+ # Instead of testing array items, test statistics. Upsampling by an even
+ # constant factor shouldn't change the mean.
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ out = numpy.zeros((7180, 7910), dtype=rasterio.ubyte)
+ data = s.read(1, out=out, masked=True)
+ assert data.shape == (7180, 7910)
+ assert data.mean() == s.read(1).mean()
diff --git a/tests/test_rio_bands.py b/tests/test_rio_bands.py
index b25b293..dfc6b18 100644
--- a/tests/test_rio_bands.py
+++ b/tests/test_rio_bands.py
@@ -14,7 +14,7 @@ def test_stack(tmpdir):
runner = CliRunner()
result = runner.invoke(
bands.stack,
- ['tests/data/RGB.byte.tif', '-o', outputname],
+ ['tests/data/RGB.byte.tif', outputname],
catch_exceptions=False)
assert result.exit_code == 0
with rasterio.open(outputname) as out:
@@ -26,7 +26,7 @@ def test_stack_list(tmpdir):
runner = CliRunner()
result = runner.invoke(
bands.stack,
- ['tests/data/RGB.byte.tif', '--bidx', '1,2,3', '-o', outputname])
+ ['tests/data/RGB.byte.tif', '--bidx', '1,2,3', outputname])
assert result.exit_code == 0
with rasterio.open(outputname) as out:
assert out.count == 3
@@ -40,7 +40,7 @@ def test_stack_slice(tmpdir):
[
'tests/data/RGB.byte.tif', '--bidx', '..2',
'tests/data/RGB.byte.tif', '--bidx', '3..',
- '-o', outputname])
+ outputname])
assert result.exit_code == 0
with rasterio.open(outputname) as out:
assert out.count == 3
@@ -55,7 +55,7 @@ def test_stack_single_slice(tmpdir):
'tests/data/RGB.byte.tif', '--bidx', '1',
'tests/data/RGB.byte.tif', '--bidx', '2..',
'--photometric', 'rgb',
- '-o', outputname])
+ outputname])
assert result.exit_code == 0
with rasterio.open(outputname) as out:
assert out.count == 3
@@ -66,7 +66,7 @@ def test_format_jpeg(tmpdir):
runner = CliRunner()
result = runner.invoke(
bands.stack,
- ['tests/data/RGB.byte.tif', '-o', outputname, '--format', 'JPEG'])
+ ['tests/data/RGB.byte.tif', outputname, '--format', 'JPEG'])
assert result.exit_code == 0
@@ -75,5 +75,5 @@ def test_error(tmpdir):
runner = CliRunner()
result = runner.invoke(
bands.stack,
- ['tests/data/RGB.byte.tif', '-o', outputname, '--driver', 'BOGUS'])
+ ['tests/data/RGB.byte.tif', outputname, '--driver', 'BOGUS'])
assert result.exit_code == 1
diff --git a/tests/test_rio_features.py b/tests/test_rio_features.py
new file mode 100644
index 0000000..c886515
--- /dev/null
+++ b/tests/test_rio_features.py
@@ -0,0 +1,99 @@
+import logging
+import re
+import sys
+
+import click
+from click.testing import CliRunner
+
+from rasterio.rio import features
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_err():
+ runner = CliRunner()
+ result = runner.invoke(
+ features.shapes, ['tests/data/shade.tif', '--bidx', '4'])
+ assert result.exit_code == 1
+
+
+def test_shapes():
+ runner = CliRunner()
+ result = runner.invoke(features.shapes, ['tests/data/shade.tif'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 1
+ assert result.output.count('"Feature"') == 232
+
+
+def test_shapes_sequence():
+ runner = CliRunner()
+ result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--sequence'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 0
+ assert result.output.count('"Feature"') == 232
+
+
+def test_shapes_sequence_rs():
+ runner = CliRunner()
+ result = runner.invoke(
+ features.shapes, [
+ 'tests/data/shade.tif',
+ '--sequence',
+ '--rs'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 0
+ assert result.output.count('"Feature"') == 232
+ assert result.output.count(u'\u001e') == 232
+
+
+def test_shapes_with_nodata():
+ runner = CliRunner()
+ result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--with-nodata'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 1
+ assert result.output.count('"Feature"') == 288
+
+
+def test_shapes_indent():
+ runner = CliRunner()
+ result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--indent', '2'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 1
+ assert result.output.count('\n') == 70139
+
+
+def test_shapes_compact():
+ runner = CliRunner()
+ result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--compact'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 1
+ assert result.output.count(', ') == 0
+ assert result.output.count(': ') == 0
+
+
+def test_shapes_sampling():
+ runner = CliRunner()
+ result = runner.invoke(
+ features.shapes, ['tests/data/shade.tif', '--sampling', '10'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 1
+ assert result.output.count('"Feature"') == 124
+
+
+def test_shapes_precision():
+ runner = CliRunner()
+ result = runner.invoke(
+ features.shapes, ['tests/data/shade.tif', '--precision', '1'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 1
+ # Find no numbers with 2+ decimal places.
+ assert re.search(r'\d*\.\d{2,}', result.output) is None
+
+
+def test_shapes_mask():
+ runner = CliRunner()
+ result = runner.invoke(features.shapes, ['tests/data/RGB.byte.tif', '--mask'])
+ assert result.exit_code == 0
+ assert result.output.count('"FeatureCollection"') == 1
+ assert result.output.count('"Feature"') == 9
diff --git a/tests/test_rio_info.py b/tests/test_rio_info.py
index fd56e20..fce04eb 100644
--- a/tests/test_rio_info.py
+++ b/tests/test_rio_info.py
@@ -3,7 +3,7 @@ from click.testing import CliRunner
import rasterio
-from rasterio.rio import info
+from rasterio.rio import cli, info
def test_env():
@@ -30,6 +30,22 @@ def test_info():
assert '"count": 3' in result.output
+def test_info_verbose():
+ runner = CliRunner()
+ result = runner.invoke(
+ cli.cli,
+ ['-v', 'info', 'tests/data/RGB.byte.tif'])
+ assert result.exit_code == 0
+
+
+def test_info_quiet():
+ runner = CliRunner()
+ result = runner.invoke(
+ cli.cli,
+ ['-q', 'info', 'tests/data/RGB.byte.tif'])
+ assert result.exit_code == 0
+
+
def test_info_count():
runner = CliRunner()
result = runner.invoke(
diff --git a/tests/test_rio_merge.py b/tests/test_rio_merge.py
new file mode 100644
index 0000000..ddcb0a2
--- /dev/null
+++ b/tests/test_rio_merge.py
@@ -0,0 +1,247 @@
+import sys
+import os
+import logging
+import click
+import numpy
+from click.testing import CliRunner
+from pytest import fixture
+
+import rasterio
+from rasterio.rio.merge import merge
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+# Fixture to create test datasets within temporary directory
+ at fixture(scope='function')
+def test_data_dir_1(tmpdir):
+ kwargs = {
+ "crs": {'init': 'epsg:4326'},
+ "transform": (-114, 0.2, 0, 46, 0, -0.2),
+ "count": 1,
+ "dtype": rasterio.uint8,
+ "driver": "GTiff",
+ "width": 10,
+ "height": 10,
+ "nodata": 1
+ }
+
+ with rasterio.drivers():
+
+ with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
+ data = numpy.ones((10, 10), dtype=rasterio.uint8)
+ data[0:6, 0:6] = 255
+ dst.write_band(1, data)
+
+ with rasterio.open(str(tmpdir.join('b.tif')), 'w', **kwargs) as dst:
+ data = numpy.ones((10, 10), dtype=rasterio.uint8)
+ data[4:8, 4:8] = 254
+ dst.write_band(1, data)
+
+ return tmpdir
+
+
+ at fixture(scope='function')
+def test_data_dir_2(tmpdir):
+ kwargs = {
+ "crs": {'init': 'epsg:4326'},
+ "transform": (-114, 0.2, 0, 46, 0, -0.1),
+ "count": 1,
+ "dtype": rasterio.uint8,
+ "driver": "GTiff",
+ "width": 10,
+ "height": 10
+ # these files have undefined nodata.
+ }
+
+ with rasterio.drivers():
+
+ with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
+ data = numpy.zeros((10, 10), dtype=rasterio.uint8)
+ data[0:6, 0:6] = 255
+ dst.write_band(1, data)
+
+ with rasterio.open(str(tmpdir.join('b.tif')), 'w', **kwargs) as dst:
+ data = numpy.zeros((10, 10), dtype=rasterio.uint8)
+ data[4:8, 4:8] = 254
+ dst.write_band(1, data)
+
+ return tmpdir
+
+
+def test_merge_with_nodata(test_data_dir_1):
+ outputname = str(test_data_dir_1.join('merged.tif'))
+ inputs = [str(x) for x in test_data_dir_1.listdir()]
+ inputs.sort()
+ runner = CliRunner()
+ result = runner.invoke(merge, inputs + [outputname])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+ with rasterio.open(outputname) as out:
+ assert out.count == 1
+ data = out.read_band(1, masked=False)
+ expected = numpy.ones((10, 10), dtype=rasterio.uint8)
+ expected[0:6, 0:6] = 255
+ expected[4:8, 4:8] = 254
+ assert numpy.all(data == expected)
+
+
+def test_merge_warn(test_data_dir_1):
+ outputname = str(test_data_dir_1.join('merged.tif'))
+ inputs = [str(x) for x in test_data_dir_1.listdir()]
+ inputs.sort()
+ runner = CliRunner()
+ result = runner.invoke(merge, inputs + [outputname] + ['-n', '-1'])
+ assert result.exit_code == 0
+ assert "using the --nodata option for better results" in result.output
+
+
+def test_merge_without_nodata(test_data_dir_2):
+ outputname = str(test_data_dir_2.join('merged.tif'))
+ inputs = [str(x) for x in test_data_dir_2.listdir()]
+ inputs.sort()
+ runner = CliRunner()
+ result = runner.invoke(merge, inputs + [outputname])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+ with rasterio.open(outputname) as out:
+ assert out.count == 1
+ data = out.read_band(1, masked=False)
+ expected = numpy.zeros((10, 10), dtype=rasterio.uint8)
+ expected[0:6, 0:6] = 255
+ expected[4:8, 4:8] = 254
+ assert numpy.all(data == expected)
+
+
+def test_merge_output_exists(tmpdir):
+ outputname = str(tmpdir.join('merged.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ merge,
+ ['tests/data/RGB.byte.tif', outputname])
+ assert result.exit_code == 0
+ result = runner.invoke(
+ merge,
+ ['tests/data/RGB.byte.tif', outputname])
+ assert os.path.exists(outputname)
+ with rasterio.open(outputname) as out:
+ assert out.count == 3
+
+
+def test_merge_output_exists_without_nodata(test_data_dir_2):
+ runner = CliRunner()
+ result = runner.invoke(
+ merge,
+ [str(test_data_dir_2.join('a.tif')),
+ str(test_data_dir_2.join('b.tif'))])
+ assert result.exit_code == 0
+
+
+def test_merge_err():
+ runner = CliRunner()
+ result = runner.invoke(
+ merge,
+ ['tests'])
+ assert result.exit_code == 1
+
+
+def test_format_jpeg(tmpdir):
+ outputname = str(tmpdir.join('stacked.jpg'))
+ runner = CliRunner()
+ result = runner.invoke(
+ merge,
+ ['tests/data/RGB.byte.tif', outputname, '--format', 'JPEG'])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+
+# Non-coincident datasets test fixture.
+# Two overlapping GeoTIFFs, one to the NW and one to the SE.
+ at fixture(scope='function')
+def test_data_dir_overlapping(tmpdir):
+ kwargs = {
+ "crs": {'init': 'epsg:4326'},
+ "transform": (-114, 0.2, 0, 46, 0, -0.2),
+ "count": 1,
+ "dtype": rasterio.uint8,
+ "driver": "GTiff",
+ "width": 10,
+ "height": 10,
+ "nodata": 0
+ }
+
+ with rasterio.drivers():
+ with rasterio.open(str(tmpdir.join('nw.tif')), 'w', **kwargs) as dst:
+ data = numpy.ones((10, 10), dtype=rasterio.uint8)
+ dst.write_band(1, data)
+
+ kwargs['transform'] = (-113, 0.2, 0, 45, 0, -0.2)
+ with rasterio.open(str(tmpdir.join('se.tif')), 'w', **kwargs) as dst:
+ data = numpy.ones((10, 10), dtype=rasterio.uint8) * 2
+ dst.write_band(1, data)
+
+ return tmpdir
+
+
+def test_merge_overlapping(test_data_dir_overlapping):
+ outputname = str(test_data_dir_overlapping.join('merged.tif'))
+ inputs = [str(x) for x in test_data_dir_overlapping.listdir()]
+ inputs.sort()
+ runner = CliRunner()
+ result = runner.invoke(merge, inputs + [outputname])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+ with rasterio.open(outputname) as out:
+ assert out.count == 1
+ assert out.shape == (15, 15)
+ assert out.bounds == (-114, 43, -111, 46)
+ data = out.read_band(1, masked=False)
+ expected = numpy.zeros((15, 15), dtype=rasterio.uint8)
+ expected[0:10, 0:10] = 1
+ expected[5:, 5:] = 2
+ assert numpy.all(data == expected)
+
+
+# Fixture to create test datasets within temporary directory
+ at fixture(scope='function')
+def test_data_dir_float(tmpdir):
+ kwargs = {
+ "crs": {'init': 'epsg:4326'},
+ "transform": (-114, 0.2, 0, 46, 0, -0.2),
+ "count": 1,
+ "dtype": rasterio.float64,
+ "driver": "GTiff",
+ "width": 10,
+ "height": 10,
+ "nodata": 0
+ }
+
+ with rasterio.drivers():
+ with rasterio.open(str(tmpdir.join('one.tif')), 'w', **kwargs) as dst:
+ data = numpy.zeros((10, 10), dtype=rasterio.float64)
+ data[0:6, 0:6] = 255
+ dst.write_band(1, data)
+
+ with rasterio.open(str(tmpdir.join('two.tif')), 'w', **kwargs) as dst:
+ data = numpy.zeros((10, 10), dtype=rasterio.float64)
+ data[4:8, 4:8] = 254
+ dst.write_band(1, data)
+ return tmpdir
+
+
+def test_merge_float(test_data_dir_float):
+ outputname = str(test_data_dir_float.join('merged.tif'))
+ inputs = [str(x) for x in test_data_dir_float.listdir()]
+ inputs.sort()
+ runner = CliRunner()
+ result = runner.invoke(merge, inputs + [outputname] + ['-n', '-1.5'])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+ with rasterio.open(outputname) as out:
+ assert out.count == 1
+ data = out.read_band(1, masked=False)
+ expected = numpy.ones((10, 10), dtype=rasterio.float64) * -1.5
+ expected[0:6, 0:6] = 255
+ expected[4:8, 4:8] = 254
+ assert numpy.all(data == expected)
diff --git a/tests/test_rio_options.py b/tests/test_rio_options.py
deleted file mode 100644
index a4b3e31..0000000
--- a/tests/test_rio_options.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import click
-from click.testing import CliRunner
-
-
-import rasterio
-from rasterio.rio import rio
-
-
-def test_insp():
- runner = CliRunner()
- result = runner.invoke(
- rio.cli,
- ['--version'])
- assert result.exit_code == 0
- assert result.output.strip() == rasterio.__version__
diff --git a/tests/test_rio_rio.py b/tests/test_rio_rio.py
index 4780e33..06a7774 100644
--- a/tests/test_rio_rio.py
+++ b/tests/test_rio_rio.py
@@ -3,7 +3,14 @@ from click.testing import CliRunner
import rasterio
-from rasterio.rio import rio
+from rasterio.rio import cli, rio
+
+
+def test_version():
+ runner = CliRunner()
+ result = runner.invoke(cli.cli, ['--version'])
+ assert result.exit_code == 0
+ assert rasterio.__version__ in result.output
def test_insp():
@@ -39,6 +46,15 @@ def test_bounds_err():
assert result.exit_code == 1
+def test_bounds_feature():
+ runner = CliRunner()
+ result = runner.invoke(
+ rio.bounds,
+ ['tests/data/RGB.byte.tif', '--feature'])
+ assert result.exit_code == 0
+ assert result.output.count('Polygon') == 1
+
+
def test_bounds_obj_bbox():
runner = CliRunner()
result = runner.invoke(
@@ -88,29 +104,25 @@ def test_bounds_seq():
runner = CliRunner()
result = runner.invoke(
rio.bounds,
- ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--x-json-seq', '--bbox', '--precision', '2'])
+ ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--sequence'])
assert result.exit_code == 0
- assert result.output == '[-78.9, 23.56, -76.6, 25.55]\n[-78.9, 23.56, -76.6, 25.55]\n'
- assert '\x1e' not in result.output
-
+ assert result.output.count('Polygon') == 2
-def test_bounds_seq_rs():
- runner = CliRunner()
result = runner.invoke(
rio.bounds,
- ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--x-json-seq', '--x-json-seq-rs', '--bbox', '--precision', '2'])
+ ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--sequence', '--bbox', '--precision', '2'])
assert result.exit_code == 0
- assert result.output == '\x1e[-78.9, 23.56, -76.6, 25.55]\n\x1e[-78.9, 23.56, -76.6, 25.55]\n'
-
+ assert result.output == '[-78.9, 23.56, -76.6, 25.55]\n[-78.9, 23.56, -76.6, 25.55]\n'
+ assert '\x1e' not in result.output
-def test_bounds_obj_feature():
+def test_bounds_seq_rs():
runner = CliRunner()
result = runner.invoke(
rio.bounds,
- ['tests/data/RGB.byte.tif', '--feature', '--precision', '6'])
+ ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--sequence', '--rs', '--bbox', '--precision', '2'])
assert result.exit_code == 0
- assert result.output.strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}'
+ assert result.output == '\x1e[-78.9, 23.56, -76.6, 25.55]\n\x1e[-78.9, 23.56, -76.6, 25.55]\n'
def test_transform_err():
diff --git a/tests/test_tool.py b/tests/test_tool.py
new file mode 100644
index 0000000..5fa4b03
--- /dev/null
+++ b/tests/test_tool.py
@@ -0,0 +1,44 @@
+import numpy as np
+
+try:
+ import matplotlib.pyplot as plt
+except ImportError:
+ plt = None
+
+import rasterio
+from rasterio.tool import show, stats
+
+
+def test_stats():
+ with rasterio.drivers():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ results = stats((src, 1))
+ assert results[0] == 1
+ assert results[1] == 255
+ assert np.isclose(results[2], 44.4344)
+
+ results2 = stats(src.read_band(1))
+ assert np.allclose(np.array(results), np.array(results2))
+
+
+def test_show():
+ """
+ This test only verifies that code up to the point of plotting with
+ matplotlib works correctly. Tests do not exercise matplotlib.
+ """
+ if plt:
+ # Return because plotting causes the tests to block until the plot
+ # window is closed.
+ return
+
+ with rasterio.drivers():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ try:
+ show((src, 1))
+ except ImportError:
+ pass
+
+ try:
+ show(src.read_band(1))
+ except ImportError:
+ pass
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/rasterio.git
More information about the Pkg-grass-devel
mailing list