[rasterio] 02/07: Imported Upstream version 0.25.0
Johan Van de Wauw
johanvdw-guest at moszumanska.debian.org
Sun Aug 16 19:47:06 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 3424e4f333c4a2b315ad9066e6d4ba606350d7eb
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date: Wed Jul 22 21:25:25 2015 +0200
Imported Upstream version 0.25.0
---
AUTHORS.txt | 13 +-
CHANGES.txt | 29 ++-
README.rst | 121 +++++++------
docs/cli.rst | 131 +++++++++++++-
rasterio/__init__.py | 8 +-
rasterio/_base.pxd | 1 +
rasterio/_base.pyx | 40 +++-
rasterio/_features.pxd | 20 +-
rasterio/_gdal.pxd | 9 +
rasterio/_io.pyx | 70 +++++--
rasterio/_warp.pyx | 13 +-
rasterio/crs.py | 40 +++-
rasterio/enums.py | 15 +-
rasterio/errors.py | 9 +
rasterio/rio/__init__.py | 4 +-
rasterio/rio/bands.py | 16 +-
rasterio/rio/calc.py | 28 +--
rasterio/rio/cli.py | 255 --------------------------
rasterio/rio/convert.py | 105 +++++++++++
rasterio/rio/features.py | 175 +++++++++++++++---
rasterio/rio/helpers.py | 79 ++++++++
rasterio/rio/info.py | 305 ++++++++++++++++++++++++-------
rasterio/rio/main.py | 62 ++++---
rasterio/rio/merge.py | 27 ++-
rasterio/rio/options.py | 156 ++++++++++++++++
rasterio/rio/overview.py | 101 +++++++++++
rasterio/rio/rio.py | 203 ---------------------
rasterio/rio/sample.py | 4 +-
rasterio/rio/warp.py | 208 +++++++++++++++++++++
rasterio/sample.py | 10 +
rasterio/tool.py | 17 +-
rasterio/warp.py | 24 ++-
requirements-dev.txt | 2 +-
setup.py | 24 ++-
tests/data/RGB.byte.tif | Bin 1713704 -> 1743350 bytes
tests/test_cli_main.py | 21 +++
tests/test_colormap.py | 26 ++-
tests/test_crs.py | 39 +++-
tests/test_indexing.py | 11 +-
tests/test_meta.py | 27 ++-
tests/test_options.py | 20 ++
tests/test_overviews.py | 40 ++++
tests/test_profile.py | 10 +
tests/test_rio_cli.py | 18 --
tests/test_rio_convert.py | 120 ++++++++++++
tests/test_rio_helpers.py | 23 +++
tests/test_rio_info.py | 443 ++++++++++++++++++++++++++++++++++++++++++++-
tests/test_rio_merge.py | 20 ++
tests/test_rio_overview.py | 67 +++++++
tests/test_rio_rio.py | 182 -------------------
tests/test_rio_warp.py | 238 ++++++++++++++++++++++++
tests/test_sampling.py | 9 +
tests/test_warp.py | 24 ++-
tests/test_write.py | 20 ++
54 files changed, 2694 insertions(+), 988 deletions(-)
diff --git a/AUTHORS.txt b/AUTHORS.txt
index cfbfc24..481e0ea 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -3,19 +3,22 @@ Authors
Sean Gillies <sean at mapbox.com>
Brendan Ward <bcward at consbio.org>
+Amit Kapadia <amit at planet.com>
+Kelsey Jordahl <kjordahl at alum.mit.edu>
+Kevin Wurster <wursterk at gmail.com>
+Maxim Dubinin <sim at gis-lab.info>
Ryan Grout <rgrout at continuum.io>
Mike Toews <mwtoews at gmail.com>
AsgerPetersen <asgerpetersen at gmail.com>
-Alessandro Amici <alexamici at gmail.com>
Joshua Arnott <josh at snorfalorpagus.net>
-Amit Kapadia <amit at planet.com>
+Alessandro Amici <alexamici at gmail.com>
Johan Van de Wauw <johan.vandewauw at gmail.com>
-Robin Wilson <robin at rtwilson.com>
James Seppi <james.seppi at gmail.com>
+Jacques Tardie <hi at jacquestardie.org>
Etienne B. Racine <etiennebr at gmail.com>
cgohlke <cgohlke at uci.edu>
-Kevin Wurster <wursterk at gmail.com>
-Aldo Culquicondor <alculquicondor at gmail.com>
Martijn Visser <mgvisser at gmail.com>
+Aldo Culquicondor <alculquicondor at gmail.com>
+Robin Wilson <robin at rtwilson.com>
See also https://github.com/mapbox/rasterio/graphs/contributors.
diff --git a/CHANGES.txt b/CHANGES.txt
index 3ecfe74..b5c549d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,8 +1,35 @@
Changes
=======
-0.24.0 (2015-05-27)
+0.25.0 (2015-07-17)
+-------------------
+- New rio-warp command (#264, #404).
+- Add driver-specific creation options (`--co`) to many commands (#379, #403).
+- Add support for arbitrary CRS output to rio-bounds (#385, #392).
+- Add support for getting values from template files in rio-edit-info with a
+ `--like` option (#387, #399).
+- New rio-overview command (#388, #408).
+- Fix rounding error in extracting shapes from decimated data (#391).
+- Remove creation options from meta property and move them to new profile
+ property (#405, #406).
+- Fix for bug in passing affine keyword argument to open() in 'w' mode (#411).
+- New rio-convert command (#414, #417), a replacement for gdal_translate
+ with more features to come by 1.0.
+- Improved error messages when seeking a driver when none are registered
+ (#415).
+- Replace read_band() with read() in the rio-insp banner (#418).
+- Fix an indexing error that prevented window() and window_bounds() from
+ round-tripping properly (#419).
+
+0.24.1 (2015-06-30)
-------------------
+- Improve safety of the sample() generator (#378).
+- Provide array masking features missing from Numpy<1.9 (#380, #389).
+- Guard against attempts to write RGBA colormap entries to TIFFs, which the
+ format can not support (#394, #395).
+
+0.24.0 (2015-05-27)
+-------------------#408).
- New rio-edit-info command (#358).
- Add option to package GDAL data in distributions (#362).
- Remove check that the path given to `rasterio.open()` in read mode is an
diff --git a/README.rst b/README.rst
index 5951445..a113f1e 100644
--- a/README.rst
+++ b/README.rst
@@ -21,77 +21,63 @@ Example
Here's a simple example of the basic features rasterio provides. Three bands
are read from an image and summed to produce something like a panchromatic
-band. This new band is then written to a new single band TIFF.
+band. This new band is then written to a new single band TIFF.
.. code-block:: python
import numpy
import rasterio
import subprocess
-
+
# Register GDAL format drivers and configuration options with a
# context manager.
-
- with rasterio.drivers(CPL_DEBUG=True):
-
+ with rasterio.drivers():
+
# Read raster bands directly to Numpy arrays.
#
with rasterio.open('tests/data/RGB.byte.tif') as src:
- b, g, r = src.read()
-
- # Combine arrays in place. Expecting that the sum will
- # temporarily exceed the 8-bit integer range, initialize it as
- # 16-bit. Adding other arrays to it in-place converts those
- # arrays "up" and preserves the type of the total array.
+ r, g, b = src.read()
- total = numpy.zeros(r.shape, dtype=rasterio.uint16)
+ # Combine arrays in place. Expecting that the sum will
+ # temporarily exceed the 8-bit integer range, initialize it as
+ # a 64-bit float (the numpy default) array. Adding other
+ # arrays to it in-place converts those arrays "up" and
+ # preserves the type of the total array.
+ total = numpy.zeros(r.shape)
for band in r, g, b:
total += band
total /= 3
# Write the product as a raster band to a new 8-bit file. For
- # keyword arguments, we start with the meta attributes of the
- # source file, but then change the band count to 1, set the
+ # the new file's profile, we start with the meta attributes of
+ # the source file, but then change the band count to 1, set the
# dtype to uint8, and specify LZW compression.
-
- kwargs = src.meta
- kwargs.update(
+ profile = src.profile
+ profile.update(
dtype=rasterio.uint8,
count=1,
compress='lzw')
-
- with rasterio.open('example-total.tif', 'w', **kwargs) as dst:
- dst.write_band(1, total.astype(rasterio.uint8))
+
+ with rasterio.open('example-total.tif', 'w', **profile) as dst:
+ dst.write(total.astype(rasterio.uint8), 1)
# At the end of the ``with rasterio.drivers()`` block, context
# manager exits and all drivers are de-registered.
- # Dump out gdalinfo's report card and open the image.
-
- info = subprocess.check_output(
- ['gdalinfo', '-stats', 'example-total.tif'])
- print(info)
- subprocess.call(['open', 'example-total.tif'])
+The output:
.. image:: http://farm6.staticflickr.com/5501/11393054644_74f54484d9_z_d.jpg
:width: 640
:height: 581
-The rasterio.drivers() function and context manager are new in 0.5. The example
-above shows the way to use it to register and de-register drivers in
-a deterministic and efficient way. Code written for rasterio 0.4 will continue
-to work: opened raster datasets may manage the global driver registry if no
-other manager is present.
-
API Overview
============
Simple access is provided to properties of a geospatial raster file.
.. code-block:: python
-
- with rasterio.drivers():
+ with rasterio.drivers():
with rasterio.open('tests/data/RGB.byte.tif') as src:
print(src.width, src.height)
print(src.crs)
@@ -107,18 +93,17 @@ Simple access is provided to properties of a geospatial raster file.
# 3
# [1, 2, 3]
-Rasterio also affords conversion of GeoTIFFs to other formats.
+A dataset also provides methods for getting extended array slices given
+georeferenced coordinates and vice versa.
+
.. code-block:: python
with rasterio.drivers():
-
- rasterio.copy(
- 'example-total.tif',
- 'example-total.jpg',
- driver='JPEG')
-
- subprocess.call(['open', 'example-total.jpg'])
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ print src.window(**src.window_bounds(((100, 200), (100, 200))))
+ # Output:
+ # ((100, 200), (100, 200))
Rasterio CLI
============
@@ -179,8 +164,11 @@ click), enum34, numpy.
Development also requires (see requirements-dev.txt) Cython and other packages.
-Rasterio binaries for OS X
---------------------------
+Installing from binaries
+------------------------
+
+OS X
+----
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
@@ -195,6 +183,23 @@ you must build from a source distribution (see below).
Binary wheels for other operating systems will be available in a future
release.
+Windows
+-------
+
+Binary wheels for rasterio and GDAL are created by Christoph Gohlke and are
+available from his website.
+
+To install rasterio, simply download both binaries for your system (`rasterio
+<http://www.lfd.uci.edu/~gohlke/pythonlibs/#rasterio>`__ and `GDAL
+<http://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal>`__) and run something like
+this from the downloads folder:
+
+.. code-block:: console
+
+ $ pip install -U pip
+ $ pip install GDAL-1.11.2-cp27-none-win32.whl
+ $ pip install rasterio-0.24.0-cp27-none-win32.whl
+
Installing from the source distribution
---------------------------------------
@@ -233,26 +238,28 @@ For a Homebrew based Python environment, do the following.
Windows
-------
-Windows binary packages created by Christoph Gohlke are available `here
-<http://www.lfd.uci.edu/~gohlke/pythonlibs/#rasterio>`_.
-
You can download a binary distribution of GDAL from `here
-<http://www.gisinternals.com/release.php>`_. You will also need to download
+<http://www.gisinternals.com/release.php>`__. You will also need to download
the compiled libraries and headers (include files).
When building from source on Windows, it is important to know that setup.py
-cannot rely on gdal-config, which is only present on UNIX systems, to discover
-the locations of header files and libraries that rasterio needs to compile its
-C extensions. On Windows, these paths need to be provided by the user.
-You will need to find the include files and the library files for gdal and
-use setup.py as follows.
+cannot rely on gdal-config, which is only present on UNIX systems, to discover
+the locations of header files and libraries that rasterio needs to compile its
+C extensions. On Windows, these paths need to be provided by the user. You
+will need to find the include files and the library files for gdal and use
+setup.py as follows.
.. code-block:: console
$ python setup.py build_ext -I<path to gdal include files> -lgdal_i -L<path to gdal library>
$ python setup.py install
-Note: The GDAL dll (gdal111.dll) and gdal-data directory need to be in your
+We have had success compiling code using the same version of Microsoft's
+Visual Studio used to compile the targeted version of Python (more info on
+versions used `here
+<https://docs.python.org/devguide/setup.html#windows>`__.).
+
+Note: The GDAL dll (gdal111.dll) and gdal-data directory need to be in your
Windows PATH otherwise rasterio will fail to work.
Testing
@@ -264,6 +271,12 @@ From the repo directory, run py.test
$ py.test
+
+Note: some tests do not succeed on Windows (see
+`#66
+<https://github.com/mapbox/rasterio/issues/66>`__.).
+
+
Documentation
-------------
diff --git a/docs/cli.rst b/docs/cli.rst
index f32ab95..8db75d5 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -1,11 +1,11 @@
Command Line Interface
======================
-Rasterio's new command line interface is a program named "rio".
+Rasterio's command line interface is a program named "rio".
.. code-block:: console
- $ rio
+ $ rio --help
Usage: rio [OPTIONS] COMMAND [ARGS]...
Rasterio command line interface.
@@ -18,17 +18,23 @@ Rasterio's new command line interface is a program named "rio".
Commands:
bounds Write bounding boxes to stdout as GeoJSON.
+ calc Raster data calculator.
+ convert Copy and convert raster dataset.
+ edit-info Edit dataset metadata.
env Print information about the rio environment.
info Print information about a data file.
insp Open a data file and start an interpreter.
mask Mask in raster using features.
merge Merge a stack of raster datasets.
+ overview Construct overviews in an existing dataset.
rasterize Rasterize features.
sample Sample a dataset.
- shapes Write the shapes of features.
+ shapes Write shapes extracted from bands or masks.
stack Stack a number of bands into a multiband dataset.
+ transform Transform coordinates.
+ warp Warp a raster dataset.
-It is developed using `Click <http://click.pocoo.org/3/>`__.
+It is developed using `Click <http://click.pocoo.org/>`__.
Commands are shown below. See ``--help`` of individual commands for more
details.
@@ -130,6 +136,41 @@ efficiently in Python.
Please see `calc.rst <calc.rst>`__ for more details.
+convert
+-------
+
+New in 0.25
+
+Like ``gdal_translate``, rio-convert copies and converts raster datasets to
+other data types and formats.
+
+Data values may be linearly scaled when copying by using the ``--scale-ratio``
+and ``--scale-offset`` options. Destination raster values are calculated as
+
+.. code-block:: python
+
+ dst = scale_ratio * src + scale_offset
+
+For example, to scale uint16 data with an actual range of 0-4095 to 0-255
+as uint8:
+
+.. code-block:: console
+
+ $ rio convert in16.tif out8.tif --dtype uint8 --scale-ratio 0.0625
+
+Format specific creation options may also be passed using --co. To tile a
+new GeoTIFF output file, add the following.
+
+.. code-block:: console
+
+ --co tiled=true --co blockxsize=256 --co blockysize=256
+
+To compress it using the LZW method, add
+
+.. code-block:: console
+
+ --co compress=LZW
+
edit-info
---------
@@ -156,7 +197,7 @@ set its `affine transformation matrix <https://github.com/mapbox/rasterio/blob/m
.. code-block:: console
- $ rio edit-info --transform "[300.0, 0.0, 101985.0, 0.0, -300.0, 2826915.0]"
+ $ rio edit-info --transform "[300.0, 0.0, 101985.0, 0.0, -300.0, 2826915.0]" example.tif
or set its nodata value to, e.g., `0`:
@@ -308,6 +349,36 @@ datasets.
$ rio merge rasterio/tests/data/R*.tif merged.tif
+overview
+--------
+
+New in 0.25
+
+A pyramid of overviews computed once and stored in the dataset using
+rio-overview can improve performance in some applications.
+
+The decimation levels at which to build overviews can be specified as a
+comma separated list
+
+.. code-block:: console
+
+ $ rio pyramid --build 2,4,8,16
+
+or a base and range of exponents.
+
+.. code-block:: console
+
+ $ rio pyramid --build 2^1..4
+
+Note that overviews can not currently be removed and are not automatically
+updated when the dataset's primary bands are modified.
+
+Information about existing overviews can be printed using the --ls option.
+
+.. code-block:: console
+
+ $ rio pyramid --ls
+
rasterize
---------
@@ -323,7 +394,7 @@ raster.
The resulting file will have an upper left coordinate determined by the bounds
of the GeoJSON (in EPSG:4326, which is the default), with a
pixel size of approximately 30 arc seconds. Pixels whose center is within the
-polygon or that are selected by brezenhams line algorithm will be burned in
+polygon or that are selected by Bresenham's line algorithm will be burned in
with a default value of 1.
It is possible to rasterize into an existing raster and use an alternative
@@ -446,4 +517,52 @@ a raster dataset, do the following.
$ echo "[-78.0, 23.0, -76.0, 25.0]" | rio transform - --dst-crs tests/data/RGB.byte.tif --precision 2
[192457.13, 2546667.68, 399086.97, 2765319.94]
+
+warp
+----
+
+New in 0.25
+
+The warp command warps (reprojects) a raster based on parameters that can be
+obtained from a template raster, or input directly. The output is always
+overwritten.
+
+
+To copy coordinate reference system, transform, and dimensions from a template
+raster, do the following:
+
+.. code-block:: console
+
+ $ rio warp input.tif output.tif --like template.tif
+
+You can specify an output coordinate system using a PROJ.4 or EPSG:nnnn string,
+or a JSON text-encoded PROJ.4 object:
+
+.. code-block:: console
+
+ $ rio warp input.tif output.tif --dst-crs EPSG:4326
+
+ $ rio warp input.tif output.tif --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84'
+
+You can also specify dimensions, which will automatically calculate appropriate
+resolution based on the relationship between the bounds in the target crs and
+these dimensions:
+
+.. code-block:: console
+
+ $ rio warp input.tif output.tif --dst-crs EPSG:4326 --dimensions 100 200
+
+Or provide output bounds (in source crs) and resolution:
+
+.. code-block:: console
+
+ $ rio warp input.tif output.tif --dst-crs EPSG:4326 --bounds -78 22 -76 24 --res 0.1
+
+Other options are available, see:
+
+.. code-block:: console
+
+ $ rio warp --help
+
+
Suggestions for other commands are welcome!
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
index 5b0f009..e03e1bf 100644
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -23,7 +23,7 @@ from rasterio import _err, coords, enums
__all__ = [
'band', 'open', 'drivers', 'copy', 'pad']
-__version__ = "0.24.0"
+__version__ = "0.25.0"
log = logging.getLogger('rasterio')
class NullHandler(logging.Handler):
@@ -89,9 +89,13 @@ def open(
raise TypeError("invalid mode: %r" % mode)
if driver and not isinstance(driver, string_types):
raise TypeError("invalid driver: %r" % driver)
+
if transform:
transform = guard_transform(transform)
-
+ elif 'affine' in kwargs:
+ affine = kwargs.pop('affine')
+ transform = guard_transform(affine)
+
if mode == 'r':
from rasterio._io import RasterReader
s = RasterReader(path)
diff --git a/rasterio/_base.pxd b/rasterio/_base.pxd
index ca070b8..a193da9 100644
--- a/rasterio/_base.pxd
+++ b/rasterio/_base.pxd
@@ -23,4 +23,5 @@ cdef class DatasetReader:
cdef void *band(self, int bidx)
+
cdef void *_osr_from_crs(object crs)
diff --git a/rasterio/_base.pyx b/rasterio/_base.pyx
index 0cd52b2..b514845 100644
--- a/rasterio/_base.pyx
+++ b/rasterio/_base.pyx
@@ -385,8 +385,8 @@ cdef class DatasetReader(object):
def window(self, left, bottom, right, top, boundless=False):
"""Returns the window corresponding to the world bounding box.
If boundless is False, window is limited to extent of this dataset."""
-
- window = tuple(zip(self.index(left, top), self.index(right, bottom)))
+ EPS = 1.0e-8
+ window = tuple(zip(self.index(left + EPS, top - EPS), self.index(right + EPS, bottom - EPS)))
if boundless:
return window
else:
@@ -406,6 +406,7 @@ cdef class DatasetReader(object):
@property
def meta(self):
+ """The basic metadata of this dataset."""
m = {
'driver': self.driver,
'dtype': self.dtypes[0],
@@ -416,12 +417,27 @@ cdef class DatasetReader(object):
'crs': self.crs,
'transform': self.affine.to_gdal(),
'affine': self.affine,
- 'blockxsize': self.block_shapes[0][1],
- 'blockysize': self.block_shapes[0][0],
- 'tiled': self.block_shapes[0][1] != self.width }
+ }
self._read = True
return m
+
+ property profile:
+ """Basic metadata and creation options of this dataset.
+
+ May be passed as keyword arguments to `rasterio.open()` to
+ create a clone of this dataset.
+ """
+ def __get__(self):
+ m = self.meta
+ m.update(self.tags(ns='rio_creation_kwds'))
+ m.update(
+ blockxsize=self.block_shapes[0][1],
+ blockysize=self.block_shapes[0][0],
+ tiled=self.block_shapes[0][1] != self.width)
+ return m
+
+
def lnglat(self):
w, s, e, n = self.bounds
cx = (w + e)/2.0
@@ -582,6 +598,20 @@ cdef class DatasetReader(object):
def kwds(self):
return self.tags(ns='rio_creation_kwds')
+
+ # Overviews.
+ def overviews(self, bidx):
+ cdef void *hovband = NULL
+ cdef void *hband = self.band(bidx)
+ num_overviews = _gdal.GDALGetOverviewCount(hband)
+ factors = []
+ for i in range(num_overviews):
+ hovband = _gdal.GDALGetOverview(hband, i)
+ # Compute the overview factor only from the xsize (width).
+ xsize = _gdal.GDALGetRasterBandXSize(hovband)
+ factors.append(int(round(float(self.width)/float(xsize))))
+ return factors
+
# Window utils
# A window is a 2D ndarray indexer in the form of a tuple:
# ((row_start, row_stop), (col_start, col_stop))
diff --git a/rasterio/_features.pxd b/rasterio/_features.pxd
index 9ffcacd..e4898ae 100644
--- a/rasterio/_features.pxd
+++ b/rasterio/_features.pxd
@@ -16,14 +16,14 @@ cdef class GeomBuilder:
cdef class OGRGeomBuilder:
- cdef void * _createOgrGeometry(self, int geom_type)
+ cdef void * _createOgrGeometry(self, int geom_type) except NULL
cdef _addPointToGeometry(self, void *cogr_geometry, object coordinate)
- cdef void * _buildPoint(self, object coordinates)
- cdef void * _buildLineString(self, object coordinates)
- cdef void * _buildLinearRing(self, object coordinates)
- cdef void * _buildPolygon(self, object coordinates)
- cdef void * _buildMultiPoint(self, object coordinates)
- cdef void * _buildMultiLineString(self, object coordinates)
- cdef void * _buildMultiPolygon(self, object coordinates)
- cdef void * _buildGeometryCollection(self, object coordinates)
- cdef void * build(self, object geom)
+ cdef void * _buildPoint(self, object coordinates) except NULL
+ cdef void * _buildLineString(self, object coordinates) except NULL
+ cdef void * _buildLinearRing(self, object coordinates) except NULL
+ cdef void * _buildPolygon(self, object coordinates) except NULL
+ cdef void * _buildMultiPoint(self, object coordinates) except NULL
+ cdef void * _buildMultiLineString(self, object coordinates) except NULL
+ cdef void * _buildMultiPolygon(self, object coordinates) except NULL
+ cdef void * _buildGeometryCollection(self, object coordinates) except NULL
+ cdef void * build(self, object geom) except NULL
diff --git a/rasterio/_gdal.pxd b/rasterio/_gdal.pxd
index bc42d2e..a4b4154 100644
--- a/rasterio/_gdal.pxd
+++ b/rasterio/_gdal.pxd
@@ -64,7 +64,13 @@ cdef extern from "gdal.h" nogil:
int GDALGetRasterXSize(void *ds)
int GDALGetRasterYSize(void *ds)
int GDALGetRasterCount(void *ds)
+
void * GDALGetRasterBand(void *ds, int num)
+ void * GDALGetOverview(void *hband, int num)
+
+ int GDALGetRasterBandXSize(void *hband)
+ int GDALGetRasterBandYSize(void *hband)
+
int GDALSetGeoTransform (void *ds, double *transform)
int GDALSetProjection(void *ds, const char *wkt)
@@ -125,6 +131,9 @@ cdef extern from "gdal.h" nogil:
void *GDALGetMaskBand (void *hBand)
int GDALCreateMaskBand (void *hDS, int flags)
+ int GDALGetOverviewCount (void *hBand)
+ int GDALBuildOverviews (void *hDS, const char *resampling, int nOverviews, int *overviews, int nBands, int *bands, void *progress_func, void *progress_data)
+
cdef extern from "gdalwarper.h":
ctypedef enum GDALResampleAlg:
diff --git a/rasterio/_io.pyx b/rasterio/_io.pyx
index 64ef9ec..b1bb71b 100644
--- a/rasterio/_io.pyx
+++ b/rasterio/_io.pyx
@@ -20,7 +20,8 @@ from rasterio import dtypes
from rasterio.coords import BoundingBox
from rasterio.five import text_type, string_types
from rasterio.transform import Affine
-from rasterio.enums import ColorInterp
+from rasterio.enums import ColorInterp, Resampling
+from rasterio.sample import sample_gen
log = logging.getLogger('rasterio')
@@ -38,7 +39,16 @@ log.addHandler(NullHandler())
cdef bint in_dtype_range(value, dtype):
"""Returns True if value is in the range of dtype, else False."""
infos = {
- 'c': np.finfo, 'f': np.finfo, 'i': np.iinfo, 'u': np.iinfo}
+ 'c': np.finfo,
+ 'f': np.finfo,
+ 'i': np.iinfo,
+ 'u': np.iinfo,
+ # Cython 0.22 returns dtype.kind as an int and will not cast to a char
+ 99: np.finfo,
+ 102: np.finfo,
+ 105: np.iinfo,
+ 117: np.iinfo
+ }
rng = infos[np.dtype(dtype).kind](dtype)
return rng.min <= value <= rng.max
@@ -1173,12 +1183,12 @@ cdef class RasterReader(_base.DatasetReader):
Iterable, yielding dataset values for the specified `indexes`
as an ndarray.
"""
- for x, y in xy:
- r, c = self.index(x, y)
- window = ((r, r+1), (c, c+1))
- data = self.read(
- indexes, window=window, masked=False, boundless=True)
- yield data[:,0,0]
+ # In https://github.com/mapbox/rasterio/issues/378 a user has
+ # found what looks to be a Cython generator bug. Until that can
+ # be confirmed and fixed, the workaround is a pure Python
+ # generator implemented in sample.py.
+ return sample_gen(self, xy, indexes)
+
cdef class RasterUpdater(RasterReader):
# Read-write access to raster data and metadata.
@@ -1670,14 +1680,26 @@ cdef class RasterUpdater(RasterReader):
# GPI_Gray=0, GPI_RGB=1, GPI_CMYK=2, GPI_HLS=3
hTable = _gdal.GDALCreateColorTable(1)
vals = range(256)
+
for i, rgba in colormap.items():
+
+ if len(rgba) == 4 and self.driver in ('GTiff'):
+ raise ValueError(
+ "Format '%s' doesn't support 4 component colormap entries"
+ % self.driver)
+
+ elif len(rgba) == 3:
+ rgba = tuple(rgba) + (255,)
+
if i not in vals:
log.warn("Invalid colormap key %d", i)
continue
+
color.c1, color.c2, color.c3, color.c4 = rgba
_gdal.GDALSetColorEntry(hTable, i, &color)
+
# TODO: other color interpretations?
- _gdal.GDALSetRasterColorInterpretation(hBand, 2)
+ _gdal.GDALSetRasterColorInterpretation(hBand, 1)
_gdal.GDALSetRasterColorTable(hBand, hTable)
_gdal.GDALDestroyColorTable(hTable)
@@ -1686,9 +1708,9 @@ cdef class RasterUpdater(RasterReader):
mask.
The optional `window` argument takes a tuple like:
-
+
((row_start, row_stop), (col_start, col_stop))
-
+
specifying a raster subset to write into.
"""
cdef void *hband
@@ -1727,7 +1749,31 @@ cdef class RasterUpdater(RasterReader):
else:
retval = io_ubyte(
hmask, 1, xoff, yoff, width, height, mask)
-
+
+ def build_overviews(self, factors, resampling=Resampling.nearest):
+ """Build overviews at one or more decimation factors for all
+ bands of the dataset."""
+ cdef int *factors_c = NULL
+ cdef const char *resampling_c = NULL
+
+ if self._hds == NULL:
+ raise ValueError("can't write closed raster file")
+
+ # Allocate arrays.
+ if factors:
+ factors_c = <int *>_gdal.CPLMalloc(len(factors)*sizeof(int))
+ for i, factor in enumerate(factors):
+ factors_c[i] = factor
+
+ with cpl_errs:
+ resampling_b = resampling.value.encode('utf-8')
+ resampling_c = resampling_b
+ err = _gdal.GDALBuildOverviews(self._hds, resampling_c,
+ len(factors), factors_c, 0, NULL, NULL, NULL)
+
+ if factors_c != NULL:
+ _gdal.CPLFree(factors_c)
+
cdef class InMemoryRaster:
"""
diff --git a/rasterio/_warp.pyx b/rasterio/_warp.pyx
index 8e13061..1e267af 100644
--- a/rasterio/_warp.pyx
+++ b/rasterio/_warp.pyx
@@ -8,6 +8,7 @@ cimport numpy as np
from rasterio cimport _base, _gdal, _ogr, _io, _features
from rasterio import dtypes
+from rasterio.errors import RasterioDriverRegistrationError
cdef extern from "gdalwarper.h" nogil:
@@ -259,7 +260,10 @@ def _reproject(
hrdriver = _gdal.GDALGetDriverByName("MEM")
if hrdriver == NULL:
- raise ValueError("NULL driver for 'MEM'")
+ raise RasterioDriverRegistrationError(
+ "'MEM' driver not found. Check that this call is contained "
+ "in a `with rasterio.drivers()` or `with rasterio.open()` "
+ "block.")
hdsin = _gdal.GDALCreate(
hrdriver, "input", cols, rows,
@@ -301,9 +305,14 @@ def _reproject(
destination = destination.reshape(1, *destination.shape)
if destination.shape[0] != src_count:
raise ValueError("Destination's shape is invalid")
+
hrdriver = _gdal.GDALGetDriverByName("MEM")
if hrdriver == NULL:
- raise ValueError("NULL driver for 'MEM'")
+ raise RasterioDriverRegistrationError(
+ "'MEM' driver not found. Check that this call is contained "
+ "in a `with rasterio.drivers()` or `with rasterio.open()` "
+ "block.")
+
_, rows, cols = destination.shape
hdsout = _gdal.GDALCreate(
hrdriver, "output", cols, rows, src_count,
diff --git a/rasterio/crs.py b/rasterio/crs.py
index c70f03e..775a54f 100644
--- a/rasterio/crs.py
+++ b/rasterio/crs.py
@@ -10,9 +10,15 @@
# {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True}
#
-from rasterio._base import is_geographic_crs, is_projected_crs
+import json
+from rasterio._base import is_geographic_crs, is_projected_crs, is_same_crs
from rasterio.five import string_types
+
+def is_valid_crs(crs):
+ return is_geographic_crs(crs) or is_projected_crs(crs)
+
+
def to_string(crs):
"""Turn a parameter mapping into a more conventional PROJ.4 string.
@@ -24,22 +30,39 @@ def to_string(crs):
items = []
for k, v in sorted(filter(
lambda x: x[0] in all_proj_keys and x[1] is not False and (
- isinstance(x[1], (bool, int, float)) or
+ isinstance(x[1], (bool, int, float)) or
isinstance(x[1], string_types)),
- crs.items() )):
+ crs.items())):
items.append(
"+" + "=".join(
map(str, filter(
- lambda y: (y or y == 0) and y is not True, (k, v)))) )
+ lambda y: (y or y == 0) and y is not True, (k, v)))))
return " ".join(items)
+
def from_string(prjs):
"""Turn a PROJ.4 string into a mapping of parameters.
Bare parameters like "+no_defs" are given a value of ``True``. All keys
are checked against the ``all_proj_keys`` list.
+
+ EPSG:nnnn is allowed.
+
+ JSON text-encoded strings are allowed.
"""
+
+ if '{' in prjs:
+ # may be json, try to decode it
+ try:
+ return json.loads(prjs, strict=False)
+ except ValueError:
+ raise ValueError('crs appears to be JSON but is not valid')
+
+ if 'EPSG:' in prjs.upper():
+ return from_epsg(prjs.split(':')[1])
+
parts = [o.lstrip('+') for o in prjs.strip().split()]
+
def parse(v):
if v in ('True', 'true'):
return True
@@ -54,10 +77,13 @@ def from_string(prjs):
return float(v)
except ValueError:
return v
+
items = map(
lambda kv: len(kv) == 2 and (kv[0], parse(kv[1])) or (kv[0], True),
- (p.split('=') for p in parts) )
- return dict((k,v) for k, v in items if k in all_proj_keys)
+ (p.split('=') for p in parts))
+
+ return dict((k, v) for k, v in items if k in all_proj_keys)
+
def from_epsg(code):
"""Given an integer code, returns an EPSG-like mapping.
@@ -183,5 +209,5 @@ _param_data = """
_lines = filter(lambda x: len(x) > 1, _param_data.split("\n"))
all_proj_keys = list(
- set(line.split()[0].lstrip("+").strip() for line in _lines)
+ set(line.split()[0].lstrip("+").strip() for line in _lines)
) + ['no_mayo']
diff --git a/rasterio/enums.py b/rasterio/enums.py
index 3926680..669538e 100644
--- a/rasterio/enums.py
+++ b/rasterio/enums.py
@@ -1,5 +1,6 @@
-from enum import IntEnum
+from enum import Enum, IntEnum
+
class ColorInterp(IntEnum):
undefined=0
@@ -16,4 +17,14 @@ class ColorInterp(IntEnum):
cyan=10
magenta=11
yellow=12
- black=13
\ No newline at end of file
+ black=13
+
+
+class Resampling(Enum):
+ nearest='NEAREST'
+ gauss='GAUSS'
+ cubic='CUBIC'
+ average='AVERAGE',
+ mode='MODE'
+ average_magphase='AVERAGE_MAGPHASE'
+ none='NONE'
diff --git a/rasterio/errors.py b/rasterio/errors.py
new file mode 100644
index 0000000..288fb0e
--- /dev/null
+++ b/rasterio/errors.py
@@ -0,0 +1,9 @@
+"""A module of errors."""
+
+
+class RasterioIOError(IOError):
+ """A failure to open a dataset using the presently registered drivers."""
+
+
+class RasterioDriverRegistrationError(ValueError):
+ """To be raised when, eg, _gdal.GDALGetDriverByName("MEM") returns NULL"""
diff --git a/rasterio/rio/__init__.py b/rasterio/rio/__init__.py
index e736ca1..570be93 100644
--- a/rasterio/rio/__init__.py
+++ b/rasterio/rio/__init__.py
@@ -1 +1,3 @@
-# module of CLI commands.
+"""
+Rasterio commandline interface components
+"""
diff --git a/rasterio/rio/bands.py b/rasterio/rio/bands.py
index b167f44..09b9c16 100644
--- a/rasterio/rio/bands.py
+++ b/rasterio/rio/bands.py
@@ -1,13 +1,12 @@
import logging
-import sys
import click
from cligj import files_inout_arg, format_opt
+from .helpers import resolve_inout
+from . import options
import rasterio
-
from rasterio.five import zip_longest
-from rasterio.rio.cli import cli, bidx_mult_opt, output_opt, resolve_inout
PHOTOMETRIC_CHOICES = [val.lower() for val in [
@@ -22,16 +21,17 @@ PHOTOMETRIC_CHOICES = [val.lower() for val in [
# Stack command.
- at cli.command(short_help="Stack a number of bands into a multiband dataset.")
+ at click.command(short_help="Stack a number of bands into a multiband dataset.")
@files_inout_arg
- at output_opt
+ at options.output_opt
@format_opt
- at bidx_mult_opt
+ at options.bidx_mult_opt
@click.option('--photometric', default=None,
type=click.Choice(PHOTOMETRIC_CHOICES),
help="Photometric interpretation")
+ at options.creation_options
@click.pass_context
-def stack(ctx, files, output, driver, bidx, photometric):
+def stack(ctx, files, output, driver, bidx, photometric, creation_options):
"""Stack a number of bands from one or more input files into a
multiband dataset.
@@ -63,7 +63,6 @@ def stack(ctx, files, output, driver, bidx, photometric):
rio stack RGB.byte.tif --bidx ..2 RGB.byte.tif --bidx 3.. -o stacked.tif
"""
- import numpy as np
verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 2
logger = logging.getLogger('rio')
@@ -97,6 +96,7 @@ def stack(ctx, files, output, driver, bidx, photometric):
with rasterio.open(files[0]) as first:
kwargs = first.meta
+ kwargs.update(**creation_options)
kwargs['transform'] = kwargs.pop('affine')
kwargs.update(
diff --git a/rasterio/rio/calc.py b/rasterio/rio/calc.py
index 9a8c4bb..7018174 100644
--- a/rasterio/rio/calc.py
+++ b/rasterio/rio/calc.py
@@ -1,18 +1,17 @@
# Calc command.
+from distutils.version import LooseVersion
import logging
-import sys
-import traceback
import click
import snuggs
from cligj import files_inout_arg
+from .helpers import resolve_inout
+from . import options
import rasterio
from rasterio.fill import fillnodata
from rasterio.features import sieve
-from rasterio.rio.cli import (
- cli, dtype_opt, masked_opt, output_opt, resolve_inout)
def get_bands(inputs, d, i=None):
@@ -31,18 +30,19 @@ def read_array(ix, subix=None, dtype=None):
return arr
- at cli.command(short_help="Raster data calculator.")
+ at click.command(short_help="Raster data calculator.")
@click.argument('command')
@files_inout_arg
- at output_opt
+ at options.output_opt
@click.option('--name', multiple=True,
help='Specify an input file with a unique short (alphas only) '
'name for use in commands like '
'"a=tests/data/RGB.byte.tif".')
- at dtype_opt
- at masked_opt
+ at options.dtype_opt
+ at options.masked_opt
+ at options.creation_options
@click.pass_context
-def calc(ctx, command, files, output, name, dtype, masked):
+def calc(ctx, command, files, output, name, dtype, masked, creation_options):
"""A raster data calculator
Evaluates an expression using input datasets and writes the result
@@ -96,6 +96,7 @@ def calc(ctx, command, files, output, name, dtype, masked):
with rasterio.open(inputs[0][1]) as first:
kwargs = first.meta
+ kwargs.update(**creation_options)
kwargs['transform'] = kwargs.pop('affine')
dtype = dtype or first.meta['dtype']
kwargs['dtype'] = dtype
@@ -122,6 +123,10 @@ def calc(ctx, command, files, output, name, dtype, masked):
res = snuggs.eval(command, **ctxkwds)
+ if (isinstance(res, np.ma.core.MaskedArray) and
+ tuple(LooseVersion(np.__version__).version) < (1, 9, 0)):
+ res = res.filled(kwargs['nodata'])
+
if len(res.shape) == 3:
results = np.ndarray.astype(res, dtype, copy=False)
else:
@@ -139,8 +144,3 @@ def calc(ctx, command, files, output, name, dtype, masked):
click.echo(' ' + ' ' * err.offset + "^")
click.echo(err)
raise click.Abort()
- #except Exception as err:
- # t, v, tb = sys.exc_info()
- # for line in traceback.format_exception_only(t, v):
- # click.echo(line, nl=False)
- # raise click.Abort()
diff --git a/rasterio/rio/cli.py b/rasterio/rio/cli.py
deleted file mode 100644
index 7bfea62..0000000
--- a/rasterio/rio/cli.py
+++ /dev/null
@@ -1,255 +0,0 @@
-"""Rasterio's command line interface core."""
-
-import json
-import logging
-import os
-import sys
-import traceback
-
-import click
-from cligj import verbose_opt, quiet_opt
-
-import rasterio
-
-
-def configure_logging(verbosity):
- log_level = max(10, 30 - 10*verbosity)
- logging.basicConfig(stream=sys.stderr, level=log_level)
-
-
-class BrokenCommand(click.Command):
- """A dummy command that provides help for broken plugins."""
-
- def __init__(self, name):
- click.Command.__init__(self, name)
- self.help = (
- "Warning: entry point could not be loaded. Contact "
- "its author for help.\n\n\b\n"
- + traceback.format_exc())
- self.short_help = (
- "Warning: could not load plugin. See `rio %s --help`." % self.name)
-
-
-class RioGroup(click.Group):
- """Custom formatting for the commands of broken plugins."""
-
- def format_commands(self, ctx, formatter):
- """Extra format methods for multi methods that adds all the commands
- after the options.
- """
- rows = []
- for subcommand in self.list_commands(ctx):
- cmd = self.get_command(ctx, subcommand)
- # What is this, the tool lied about a command. Ignore it
- if cmd is None:
- continue
-
- help = cmd.short_help or ''
-
- # Mark broken subcommands with a pile of poop.
- name = cmd.name
- if isinstance(cmd, BrokenCommand):
- if os.environ.get('RIO_HONESTLY'):
- name += u'\U0001F4A9'
- else:
- name += u'\u2020'
-
- rows.append((name, help))
-
- if rows:
- with formatter.section('Commands'):
- formatter.write_dl(rows)
-
-
-# The CLI command group.
- at click.group(help="Rasterio command line interface.", cls=RioGroup)
- at verbose_opt
- at quiet_opt
- at click.version_option(version=rasterio.__version__, message='%(version)s')
- at click.pass_context
-def cli(ctx, verbose, quiet):
- verbosity = verbose - quiet
- configure_logging(verbosity)
- ctx.obj = {}
- ctx.obj['verbosity'] = verbosity
-
-
-# Common arguments and options
-
-# TODO: move file_in_arg and file_out_arg to cligj
-
-# Singular input file
-file_in_arg = click.argument(
- 'INPUT',
- type=click.Path(exists=True, resolve_path=True))
-
-# Singular output file
-file_out_arg = click.argument(
- 'OUTPUT',
- type=click.Path(resolve_path=True))
-
-bidx_opt = click.option(
- '-b', '--bidx',
- type=int,
- default=1,
- help="Input file band index (default: 1)")
-
-bidx_mult_opt = click.option(
- '-b', '--bidx',
- multiple=True,
- help="Indexes of input file bands.")
-
-# TODO: may be better suited to cligj
-bounds_opt = click.option(
- '--bounds',
- nargs=4, type=float, default=None,
- help='Output bounds: left, bottom, right, top.')
-
-dtype_opt = click.option(
- '-t', '--dtype',
- type=click.Choice([
- 'ubyte', 'uint8', 'uint16', 'int16', 'uint32', 'int32',
- 'float32', 'float64']),
- default=None,
- help="Output data type (default: float64).")
-
-like_file_opt = click.option(
- '--like',
- type=click.Path(exists=True),
- help='Raster dataset to use as a template for obtaining affine '
- 'transform (bounds and resolution), crs, data type, and driver '
- 'used to create the output.')
-
-masked_opt = click.option(
- '--masked/--not-masked',
- default=True,
- help="Evaluate expressions using masked arrays (the default) or ordinary "
- "numpy arrays.")
-
-output_opt = click.option(
- '-o', '--output',
- default=None,
- type=click.Path(resolve_path=True),
- help="Path to output file (optional alternative to a positional arg "
- "for some commands).")
-
-
-resolution_opt = click.option(
- '-r', '--res',
- multiple=True, type=float, default=None,
- help='Output dataset resolution in units of coordinate '
- 'reference system. Pixels assumed to be square if this option '
- 'is used once, otherwise use: '
- '--res pixel_width --res pixel_height')
-
-"""
-Registry of command line options (also see cligj options):
--a, --all: Use all pixels touched by features. In rio-mask, rio-rasterize
---as-mask/--not-as-mask: interpret band as mask or not. In rio-shapes
---band/--mask: use band or mask. In rio-shapes
---bbox:
--b, --bidx: band index(es) (singular or multiple value versions).
- In rio-info, rio-sample, rio-shapes, rio-stack (different usages)
---bounds: bounds in world coordinates.
- In rio-info, rio-rasterize (different usages)
---count: count of bands. In rio-info
---crop: Crop raster to extent of features. In rio-mask
---crs: CRS of input raster. In rio-info
---default-value: default for rasterized pixels. In rio-rasterize
---dimensions: Output width, height. In rio-rasterize
---dst-crs: destination CRS. In rio-transform
---fill: fill value for pixels not covered by features. In rio-rasterize
---formats: list available formats. In rio-info
---height: height of raster. In rio-info
--i, --invert: Invert mask created from features: In rio-mask
--j, --geojson-mask: GeoJSON for masking raster. In rio-mask
---lnglat: geograhpic coordinates of center of raster. In rio-info
---masked/--not-masked: read masked data from source file.
- In rio-calc, rio-info
--m, --mode: output file mode (r, r+). In rio-insp
---name: input file name alias. In rio-calc
---nodata: nodata value. In rio-info, rio-merge (different usages)
---photometric: photometric interpretation. In rio-stack
---property: GeoJSON property to use as values for rasterize. In rio-rasterize
--r, --res: output resolution.
- In rio-info, rio-rasterize (different usages. TODO: try to combine
- usages, prefer rio-rasterize version)
---sampling: Inverse of sampling fraction. In rio-shapes
---shape: shape (width, height) of band. In rio-info
---src-crs: source CRS.
- In rio-insp, rio-rasterize (different usages. TODO: consolidate usages)
---stats: print raster stats. In rio-inf
--t, --dtype: data type. In rio-calc, rio-info (different usages)
---width: width of raster. In rio-info
---with-nodata/--without-nodata: include nodata regions or not. In rio-shapes.
--v, --tell-me-more, --verbose
-"""
-
-
-def coords(obj):
- """Yield all coordinate coordinate tuples from a geometry or feature.
- From python-geojson package."""
- if isinstance(obj, (tuple, list)):
- coordinates = obj
- elif 'geometry' in obj:
- coordinates = obj['geometry']['coordinates']
- else:
- coordinates = obj.get('coordinates', obj)
- for e in coordinates:
- if isinstance(e, (float, int)):
- yield tuple(coordinates)
- break
- else:
- for f in coords(e):
- yield f
-
-
-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 sequence:
- for feat in collection():
- xs, ys = zip(*coords(feat))
- bbox = (min(xs), min(ys), max(xs), max(ys))
- if use_rs:
- 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:
- fobj.write(
- json.dumps({
- 'type': 'FeatureCollection',
- 'bbox': bbox,
- 'features': [feat]}, **dump_kwds))
- fobj.write('\n')
- # Aggregate all features into a single object expressed as
- # bbox or collection.
- else:
- features = list(collection())
- 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:
- fobj.write(json.dumps({
- 'bbox': collection.bbox,
- 'type': 'FeatureCollection',
- 'features': features},
- **dump_kwds))
- fobj.write('\n')
-
-
-def resolve_inout(input=None, output=None, files=None):
- """Resolves inputs and outputs from standard args and options.
-
- Returns `output_filename, [input_filename0, ...]`."""
- resolved_output = output or (files[-1] if files else None)
- resolved_inputs = (
- [input] if input else [] +
- list(files[:-1 if not output else None]) if files else [])
- return resolved_output, resolved_inputs
diff --git a/rasterio/rio/convert.py b/rasterio/rio/convert.py
new file mode 100644
index 0000000..37fea24
--- /dev/null
+++ b/rasterio/rio/convert.py
@@ -0,0 +1,105 @@
+"""File translation command"""
+
+import logging
+import warnings
+
+import click
+from cligj import files_inout_arg, format_opt
+import numpy as np
+
+from .helpers import resolve_inout
+from . import options
+import rasterio
+
+
+warnings.simplefilter('default')
+
+
+ at click.command(short_help="Copy and convert raster dataset.")
+ at click.argument(
+ 'files',
+ nargs=-1,
+ type=click.Path(resolve_path=True),
+ required=True,
+ metavar="INPUT OUTPUT")
+ at options.output_opt
+ at format_opt
+ at options.dtype_opt
+ at click.option('--scale-ratio', type=float, default=None,
+ help="Source to destination scaling ratio.")
+ at click.option('--scale-offset', type=float, default=None,
+ help="Source to destination scaling offset.")
+ at options.creation_options
+ at click.pass_context
+def convert(
+ ctx, files, output, driver, dtype, scale_ratio, scale_offset,
+ creation_options):
+ """Copy and convert raster datasets to other data types and formats.
+
+ Data values may be linearly scaled when copying by using the
+ --scale-ratio and --scale-offset options. Destination raster values
+ are calculated as
+
+ dst = scale_ratio * src + scale_offset
+
+ For example, to scale uint16 data with an actual range of 0-4095 to
+ 0-255 as uint8:
+
+ $ rio convert in16.tif out8.tif --dtype uint8 --scale-ratio 0.0625
+
+ Format specific creation options may also be passed using --co. To
+ tile a new GeoTIFF output file, do the following.
+
+ --co tiled=true --co blockxsize=256 --co blockysize=256
+
+ To compress it using the LZW method, add
+
+ --co compress=LZW
+
+ """
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+
+ with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+
+ outputfile, files = resolve_inout(files=files, output=output)
+ inputfile = files[0]
+
+ with rasterio.open(inputfile) as src:
+
+ # Use the input file's profile, updated by CLI
+ # options, as the profile for the output file.
+ profile = src.profile
+
+ if 'affine' in profile:
+ profile['transform'] = profile.pop('affine')
+
+ if driver:
+ profile['driver'] = driver
+
+ if dtype:
+ profile['dtype'] = dtype
+ dst_dtype = profile['dtype']
+
+ profile.update(**creation_options)
+
+ with rasterio.open(outputfile, 'w', **profile) as dst:
+
+ data = src.read()
+
+ if scale_ratio:
+ # Cast to float64 before multiplying.
+ data = data.astype('float64', casting='unsafe', copy=False)
+ np.multiply(
+ data, scale_ratio, out=data, casting='unsafe')
+
+ if scale_offset:
+ # My understanding of copy=False is that this is a
+ # no-op if the array was cast for multiplication.
+ data = data.astype('float64', casting='unsafe', copy=False)
+ np.add(
+ data, scale_offset, out=data, casting='unsafe')
+
+ # Cast to the output dtype and write.
+ result = data.astype(dst_dtype, casting='unsafe', copy=False)
+ dst.write(result)
diff --git a/rasterio/rio/features.py b/rasterio/rio/features.py
index 2962096..31d84c2 100644
--- a/rasterio/rio/features.py
+++ b/rasterio/rio/features.py
@@ -2,21 +2,20 @@ import json
import logging
from math import ceil
import os
-import sys
import shutil
import click
+import cligj
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, files_inout_arg,
- format_opt)
+ projection_mercator_opt, projection_projected_opt, sequence_opt,
+ use_rs_opt, geojson_type_feature_opt, geojson_type_bbox_opt,
+ files_inout_arg, format_opt, geojson_type_collection_opt)
+from .helpers import coords, resolve_inout, write_features, to_lower
+from . import options
import rasterio
from rasterio.transform import Affine
-from rasterio.rio.cli import (
- cli, coords, write_features, file_in_arg, file_out_arg, like_file_opt,
- bounds_opt, resolution_opt, output_opt, resolve_inout)
logger = logging.getLogger('rio')
@@ -33,9 +32,9 @@ all_touched_opt = click.option(
# Mask command
- at cli.command(short_help='Mask in raster using features.')
- at files_inout_arg
- at output_opt
+ at click.command(short_help='Mask in raster using features.')
+ at cligj.files_inout_arg
+ at options.output_opt
@click.option('-j', '--geojson-mask', 'geojson_mask',
type=click.Path(), default=None,
help='GeoJSON file to use for masking raster. Use "-" to read '
@@ -50,6 +49,7 @@ all_touched_opt = click.option(
help='Inverts the mask, so that areas covered by features are'
'masked out and areas not covered are retained. Ignored '
'if using --crop')
+ at options.creation_options
@click.pass_context
def mask(
ctx,
@@ -59,7 +59,8 @@ def mask(
driver,
all_touched,
crop,
- invert):
+ invert,
+ creation_options):
"""Masks in raster using GeoJSON features (masks out all areas not covered
by features), and optionally crops the output raster to the extent of the
@@ -103,9 +104,10 @@ def mask(
with rasterio.drivers(CPL_DEBUG=verbosity > 2):
try:
- geojson = json.loads(click.open_file(geojson_mask).read())
+ with click.open_file(geojson_mask) as f:
+ geojson = json.loads(f.read())
except ValueError:
- raise click.BadParameter('GeoJSON could not be read from '
+ raise click.BadParameter('GeoJSON could not be read from '
'--geojson-mask or stdin',
param_hint='--geojson-mask')
@@ -150,6 +152,7 @@ def mask(
invert=invert)
meta = src.meta.copy()
+ meta.update(**creation_options)
meta.update({
'driver': driver,
'height': mask.shape[0],
@@ -165,9 +168,9 @@ def mask(
# Shapes command.
- at cli.command(short_help="Write shapes extracted from bands or masks.")
+ at click.command(short_help="Write shapes extracted from bands or masks.")
@click.argument('input', type=click.Path(exists=True))
- at output_opt
+ at options.output_opt
@precision_opt
@indent_opt
@compact_opt
@@ -256,10 +259,14 @@ def shapes(
msk = None
# Adjust transforms.
- if sampling == 1:
- transform = src.affine
- else:
- transform = src.affine * Affine.scale(float(sampling))
+ transform = src.affine
+ if sampling > 1:
+ # Decimation of the raster produces a georeferencing
+ # shift that we correct with a translation.
+ transform *= Affine.translation(
+ src.width%sampling, src.height%sampling)
+ # And follow by scaling.
+ transform *= Affine.scale(float(sampling))
# Most of the time, we'll use the valid data mask.
# We skip reading it if we're extracting every possible
@@ -354,15 +361,14 @@ def shapes(
# Rasterize command.
- at cli.command(short_help='Rasterize features.')
+ at click.command(short_help='Rasterize features.')
@files_inout_arg
- at output_opt
+ at options.output_opt
@format_opt
- at like_file_opt
- at bounds_opt
- at click.option('--dimensions', nargs=2, type=int, default=None,
- help='Output dataset width, height in number of pixels.')
- at resolution_opt
+ at options.like_file_opt
+ at options.bounds_opt
+ at options.dimensions_opt
+ at options.resolution_opt
@click.option('--src-crs', '--src_crs', 'src_crs', default=None,
help='Source coordinate reference system. Limited to EPSG '
'codes for now. Used as output coordinate system if output '
@@ -377,6 +383,7 @@ def shapes(
@click.option('--property', type=str, default=None, help='Property in '
'GeoJSON features to use for rasterized values. Any features '
'that lack this property will be given --default_value instead.')
+ at options.creation_options
@click.pass_context
def rasterize(
ctx,
@@ -391,7 +398,8 @@ def rasterize(
all_touched,
default_value,
fill,
- property):
+ property,
+ creation_options):
"""Rasterize GeoJSON into a new or existing raster.
@@ -440,7 +448,6 @@ def rasterize(
verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
output, files = resolve_inout(files=files, output=output)
- input = click.open_file(files.pop(0) if files else '-')
has_src_crs = src_crs is not None
src_crs = src_crs or 'EPSG:4326'
@@ -459,7 +466,8 @@ def rasterize(
return feature['properties'].get(property, default_value)
return default_value
- geojson = json.loads(input.read())
+ with click.open_file(files.pop(0) if files else '-') as gj_f:
+ geojson = json.loads(gj_f.read())
if 'features' in geojson:
geometries = []
for f in geojson['features']:
@@ -546,6 +554,7 @@ def rasterize(
raise click.BadParameter(
'pixel dimensions are required',
ctx, param=res, param_hint='--res')
+
elif len(res) == 1:
res = (res[0], res[0])
@@ -569,6 +578,7 @@ def rasterize(
bounds[3]),
'driver': driver
}
+ kwargs.update(**creation_options)
result = rasterize(
geometries,
@@ -588,6 +598,113 @@ def rasterize(
out.write_band(1, result)
+# Bounds command.
+ at click.command(short_help="Write bounding boxes to stdout as GeoJSON.")
+# One or more files, the bounds of each are a feature in the collection
+# object or feature sequence.
+ at click.argument('INPUT', nargs=-1, type=click.Path(exists=True))
+ at precision_opt
+ at indent_opt
+ at compact_opt
+ at projection_geographic_opt
+ at projection_projected_opt
+ at projection_mercator_opt
+ at click.option(
+ '--dst-crs', default='', metavar="EPSG:NNNN", callback=to_lower,
+ help="Output in specified coordinates.")
+ 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)
+ at click.pass_context
+def bounds(ctx, input, precision, indent, compact, projection, dst_crs,
+ sequence, use_rs, geojson_type):
+ """Write bounding boxes to stdout as GeoJSON for use with, e.g.,
+ geojsonio
+
+ $ rio bounds *.tif | geojsonio
+
+ If a destination crs is passed via dst_crs, it takes precedence over
+ the projection parameter.
+ """
+ import rasterio.warp
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+ dump_kwds = {'sort_keys': True}
+ if indent:
+ dump_kwds['indent'] = indent
+ if compact:
+ dump_kwds['separators'] = (',', ':')
+ stdout = click.get_text_stream('stdout')
+
+ # This is the generator for (feature, bbox) pairs.
+ class Collection(object):
+
+ def __init__(self):
+ self._xs = []
+ self._ys = []
+
+ @property
+ def bbox(self):
+ return min(self._xs), min(self._ys), max(self._xs), max(self._ys)
+
+ def __call__(self):
+ for i, path in enumerate(input):
+ with rasterio.open(path) as src:
+ bounds = src.bounds
+ xs = [bounds[0], bounds[2]]
+ ys = [bounds[1], bounds[3]]
+ if dst_crs:
+ xs, ys = rasterio.warp.transform(
+ src.crs, {'init': dst_crs}, xs, ys)
+ elif projection == 'mercator':
+ xs, ys = rasterio.warp.transform(
+ src.crs, {'init': 'epsg:3857'}, xs, ys)
+ elif projection == 'geographic':
+ xs, ys = rasterio.warp.transform(
+ src.crs, {'init': 'epsg:4326'}, xs, ys)
+
+ if precision >= 0:
+ xs = [round(v, precision) for v in xs]
+ ys = [round(v, precision) for v in ys]
+ bbox = [min(xs), min(ys), max(xs), max(ys)]
+
+ yield {
+ 'type': 'Feature',
+ 'bbox': bbox,
+ 'geometry': {
+ 'type': 'Polygon',
+ 'coordinates': [[
+ [xs[0], ys[0]],
+ [xs[1], ys[0]],
+ [xs[1], ys[1]],
+ [xs[0], ys[1]],
+ [xs[0], ys[0]] ]]},
+ 'properties': {
+ 'id': str(i),
+ 'title': path,
+ 'filename': os.path.basename(path)} }
+
+ self._xs.extend(bbox[::2])
+ self._ys.extend(bbox[1::2])
+
+ 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, col, sequence=sequence,
+ geojson_type=geojson_type, use_rs=use_rs,
+ **dump_kwds)
+
+ except Exception:
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
+
+
def _disjoint_bounds(bounds1, bounds2):
return (bounds1[0] > bounds2[2] or bounds1[2] < bounds2[0] or
bounds1[1] > bounds2[3] or bounds1[3] < bounds2[1])
+
diff --git a/rasterio/rio/helpers.py b/rasterio/rio/helpers.py
new file mode 100644
index 0000000..1300b4e
--- /dev/null
+++ b/rasterio/rio/helpers.py
@@ -0,0 +1,79 @@
+"""
+Helper objects used by multiple CLI commands.
+"""
+
+
+import json
+
+
+def coords(obj):
+ """Yield all coordinate coordinate tuples from a geometry or feature.
+ From python-geojson package."""
+ if isinstance(obj, (tuple, list)):
+ coordinates = obj
+ elif 'geometry' in obj:
+ coordinates = obj['geometry']['coordinates']
+ else:
+ coordinates = obj.get('coordinates', obj)
+ for e in coordinates:
+ if isinstance(e, (float, int)):
+ yield tuple(coordinates)
+ break
+ else:
+ for f in coords(e):
+ yield f
+
+
+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 sequence:
+ for feat in collection():
+ xs, ys = zip(*coords(feat))
+ bbox = (min(xs), min(ys), max(xs), max(ys))
+ if use_rs:
+ 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:
+ fobj.write(
+ json.dumps({
+ 'type': 'FeatureCollection',
+ 'bbox': bbox,
+ 'features': [feat]}, **dump_kwds))
+ fobj.write('\n')
+ # Aggregate all features into a single object expressed as
+ # bbox or collection.
+ else:
+ features = list(collection())
+ 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:
+ fobj.write(json.dumps({
+ 'bbox': collection.bbox,
+ 'type': 'FeatureCollection',
+ 'features': features},
+ **dump_kwds))
+ fobj.write('\n')
+
+
+def resolve_inout(input=None, output=None, files=None):
+ """Resolves inputs and outputs from standard args and options.
+
+ Returns `output_filename, [input_filename0, ...]`."""
+ resolved_output = output or (files[-1] if files else None)
+ resolved_inputs = (
+ [input] if input else [] +
+ list(files[:-1 if not output else None]) if files else [])
+ return resolved_output, resolved_inputs
+
+
+def to_lower(ctx, param, value):
+ return value.lower()
diff --git a/rasterio/rio/info.py b/rasterio/rio/info.py
index b627fdc..f6e1890 100644
--- a/rasterio/rio/info.py
+++ b/rasterio/rio/info.py
@@ -1,40 +1,166 @@
"""Fetch and edit raster dataset metadata from the command line."""
+
import json
import logging
+import os
import sys
import click
+from cligj import precision_opt
+from . import options
import rasterio
import rasterio.crs
-from rasterio.rio.cli import cli, bidx_opt, file_in_arg, masked_opt
from rasterio.transform import guard_transform
- at cli.command('edit-info', short_help="Edit dataset metadata.")
- at file_in_arg
- at click.option('--nodata', type=float, default=None,
+# Handlers for info module options.
+
+def from_like_context(ctx, param, value):
+ """Return the value for an option from the context if the option
+ or `--all` is given, else return None."""
+ if ctx.obj and ctx.obj.get('like') and (
+ value == 'like' or ctx.obj.get('all_like')):
+ return ctx.obj['like'][param.name]
+ else:
+ return None
+
+
+def all_handler(ctx, param, value):
+ """Get tags from a template file or command line."""
+ if ctx.obj and ctx.obj.get('like') and value is not None:
+ ctx.obj['all_like'] = value
+ value = ctx.obj.get('like')
+ return value
+
+
+def crs_handler(ctx, param, value):
+ """Get crs value from a template file or command line."""
+ retval = from_like_context(ctx, param, value)
+ if retval is None and value:
+ try:
+ retval = json.loads(value)
+ except ValueError:
+ retval = value
+ if not rasterio.crs.is_valid_crs(retval):
+ raise click.BadParameter(
+ "'%s' is not a recognized CRS." % retval,
+ param=param, param_hint='crs')
+ return retval
+
+
+def like_handler(ctx, param, value):
+ """Copy a dataset's meta property to the command context for access
+ from other callbacks."""
+ if ctx.obj is None:
+ ctx.obj = {}
+ if value:
+ with rasterio.open(value) as src:
+ metadata = src.meta
+ ctx.obj['like'] = metadata
+ ctx.obj['like']['transform'] = metadata['affine']
+ ctx.obj['like']['tags'] = src.tags()
+
+
+def nodata_handler(ctx, param, value):
+ """Get nodata value from a template file or command line."""
+ retval = from_like_context(ctx, param, value)
+ if retval is None and value is not None:
+ try:
+ retval = float(value)
+ except:
+ raise click.BadParameter(
+ "%s is not a number." % repr(value),
+ param=param, param_hint='nodata')
+ return retval
+
+
+def tags_handler(ctx, param, value):
+ """Get tags from a template file or command line."""
+ retval = from_like_context(ctx, param, value)
+ if retval is None and value:
+ try:
+ retval = dict(p.split('=') for p in value)
+ except:
+ raise click.BadParameter(
+ "'%s' contains a malformed tag." % value,
+ param=param, param_hint='transform')
+ return retval
+
+
+def transform_handler(ctx, param, value):
+ """Get transform value from a template file or command line."""
+ retval = from_like_context(ctx, param, value)
+ if retval is None and value:
+ try:
+ value = json.loads(value)
+ except ValueError:
+ pass
+ try:
+ retval = guard_transform(value)
+ except:
+ raise click.BadParameter(
+ "'%s' is not recognized as an Affine or GDAL "
+ "geotransform array." % value,
+ param=param, param_hint='transform')
+ return retval
+
+
+# The edit-info command.
+
+ at click.command('edit-info', short_help="Edit dataset metadata.")
+ at options.file_in_arg
+ at click.option('--nodata', callback=nodata_handler, default=None,
help="New nodata value")
- at click.option('--crs', help="New coordinate reference system")
- at click.option('--transform', help="New affine transform matrix")
- at click.option('--tag', 'tags', multiple=True, metavar='KEY=VAL',
- help="New tag.")
+ at click.option('--crs', callback=crs_handler, default=None,
+ help="New coordinate reference system")
+ at click.option('--transform', callback=transform_handler,
+ help="New affine transform matrix")
+ at click.option('--tag', 'tags', callback=tags_handler, multiple=True,
+ metavar='KEY=VAL', help="New tag.")
+ at click.option('--all', 'allmd', callback=all_handler, flag_value='like',
+ is_eager=True, default=False,
+ help="Copy all metadata items from the template file.")
+ at click.option(
+ '--like',
+ type=click.Path(exists=True),
+ callback=like_handler,
+ is_eager=True,
+ help="Raster dataset to use as a template for obtaining affine "
+ "transform (bounds and resolution), crs, and nodata values.")
@click.pass_context
-def edit(ctx, input, nodata, crs, transform, tags):
+def edit(ctx, input, nodata, crs, transform, tags, allmd, like):
"""Edit a dataset's metadata: coordinate reference system, affine
transformation matrix, nodata value, and tags.
- CRS may be either a PROJ.4 or EPSG:nnnn string, or a JSON-encoded
- PROJ.4 object.
+ The coordinate reference system may be either a PROJ.4 or EPSG:nnnn
+ string,
+
+ --crs 'EPSG:4326'
+
+ or a JSON text-encoded PROJ.4 object.
+
+ --crs '{"proj": "utm", "zone": 18, ...}'
+
+ Transforms are either JSON-encoded Affine objects (preferred),
+
+ --transform '[300.038, 0.0, 101985.0, 0.0, -300.042, 2826915.0]'
+
+ or JSON text-encoded GDAL geotransform arrays.
+
+ --transform '[101985.0, 300.038, 0.0, 2826915.0, 0.0, -300.042]'
+
+ Metadata items may also be read from an existing dataset using a
+ combination of the --like option with at least one of --all,
+ `--crs like`, `--nodata like`, and `--transform like`.
- Transforms are either JSON-encoded Affine objects (preferred) like
+ rio edit-info example.tif --like template.tif --all
- [300.038, 0.0, 101985.0, 0.0, -300.042, 2826915.0]
+ To get just the transform from the template:
- or JSON-encoded GDAL geotransform arrays like
+ rio edit-info example.tif --like template.tif --transform like
- [101985.0, 300.038, 0.0, 2826915.0, 0.0, -300.042]
"""
import numpy as np
@@ -48,64 +174,35 @@ def edit(ctx, input, nodata, crs, transform, tags):
return rng.min <= value <= rng.max
with rasterio.drivers(CPL_DEBUG=(verbosity > 2)) as env:
+
with rasterio.open(input, 'r+') as dst:
- # Update nodata.
- if nodata is not None:
+ if allmd:
+ nodata = allmd['nodata']
+ crs = allmd['crs']
+ transform = allmd['transform']
+ tags = allmd['tags']
+ if nodata is not None:
dtype = dst.dtypes[0]
if not in_dtype_range(nodata, dtype):
raise click.BadParameter(
"outside the range of the file's "
"data type (%s)." % dtype,
param=nodata, param_hint='nodata')
-
dst.nodata = nodata
- # Update CRS. Value might be a PROJ.4 string or a JSON
- # encoded dict.
if crs:
- new_crs = crs.strip()
- try:
- new_crs = json.loads(crs)
- except ValueError:
- pass
-
- if not (rasterio.crs.is_geographic_crs(new_crs) or
- rasterio.crs.is_projected_crs(new_crs)):
- raise click.BadParameter(
- "'%s' is not a recognized CRS." % crs,
- param=crs, param_hint='crs')
-
- dst.crs = new_crs
+ dst.crs = crs
- # Update transform. Value might be a JSON encoded
- # Affine object or a GDAL geotransform array.
if transform:
- try:
- transform_obj = json.loads(transform)
- except ValueError:
- raise click.BadParameter(
- "'%s' is not a JSON array." % transform,
- param=transform, param_hint='transform')
+ dst.transform = transform
- try:
- transform_obj = guard_transform(transform_obj)
- except:
- raise click.BadParameter(
- "'%s' is not recognized as an Affine or GDAL "
- "geotransform array." % transform,
- param=transform, param_hint='transform')
-
- dst.transform = transform_obj
-
- # Update tags.
if tags:
- tags = dict(p.split('=') for p in tags)
dst.update_tags(**tags)
- at cli.command(short_help="Print information about the rio environment.")
+ at click.command(short_help="Print information about the rio environment.")
@click.option('--formats', 'key', flag_value='formats', default=True,
help="Enumerate the available formats.")
@click.pass_context
@@ -123,8 +220,8 @@ def env(ctx, key):
stdout.write('\n')
- at cli.command(short_help="Print information about a data file.")
- at file_in_arg
+ at click.command(short_help="Print information about a data file.")
+ at options.file_in_arg
@click.option('--meta', 'aspect', flag_value='meta', default=True,
help="Show data file structure (default).")
@click.option('--tags', 'aspect', flag_value='tags',
@@ -162,11 +259,11 @@ def env(ctx, key):
"(use --bidx).")
@click.option('-v', '--tell-me-more', '--verbose', is_flag=True,
help="Output extra information.")
- at bidx_opt
- at masked_opt
+ at options.bidx_opt
+ at options.masked_opt
@click.pass_context
def info(ctx, input, aspect, indent, namespace, meta_member, verbose, bidx,
- masked):
+ masked):
"""Print metadata about the dataset as JSON.
Optionally print a single metadata item as a string.
@@ -210,8 +307,94 @@ def info(ctx, input, aspect, indent, namespace, meta_member, verbose, bidx,
else:
click.echo(json.dumps(info, indent=indent))
elif aspect == 'tags':
- click.echo(json.dumps(src.tags(ns=namespace),
- indent=indent))
+ click.echo(
+ json.dumps(src.tags(ns=namespace), indent=indent))
+ except Exception:
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
+
+
+# Insp command.
+ at click.command(short_help="Open a data file and start an interpreter.")
+ at options.file_in_arg
+ at click.option('--ipython', 'interpreter', flag_value='ipython',
+ help="Use IPython as interpreter.")
+ at click.option(
+ '-m',
+ '--mode',
+ type=click.Choice(['r', 'r+']),
+ default='r',
+ help="File mode (default 'r').")
+ at click.pass_context
+def insp(ctx, input, mode, interpreter):
+ """ Open the input file in a Python interpreter.
+
+ IPython will be used as the default interpreter, if available.
+ """
+ import rasterio.tool
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+ try:
+ with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+ with rasterio.open(input, mode) as src:
+ rasterio.tool.main(
+ 'Rasterio %s Interactive Inspector (Python %s)\n'
+ 'Type "src.meta", "src.read(1)", or "help(src)" '
+ 'for more information.' % (
+ rasterio.__version__,
+ '.'.join(map(str, sys.version_info[:3]))),
+ src, interpreter)
+ except Exception:
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
+
+
+# Transform command.
+ at click.command(short_help="Transform coordinates.")
+ at click.argument('INPUT', default='-', required=False)
+ at click.option('--src-crs', '--src_crs', default='EPSG:4326',
+ help="Source CRS.")
+ at click.option('--dst-crs', '--dst_crs', default='EPSG:4326',
+ help="Destination CRS.")
+ at precision_opt
+ at click.pass_context
+def transform(ctx, input, src_crs, dst_crs, precision):
+ import rasterio.warp
+
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+
+ # Handle the case of file, stream, or string input.
+ try:
+ src = click.open_file(input).readlines()
+ except IOError:
+ src = [input]
+
+ try:
+ with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+ if src_crs.startswith('EPSG'):
+ src_crs = {'init': src_crs}
+ elif os.path.exists(src_crs):
+ with rasterio.open(src_crs) as f:
+ src_crs = f.crs
+ if dst_crs.startswith('EPSG'):
+ dst_crs = {'init': dst_crs}
+ elif os.path.exists(dst_crs):
+ with rasterio.open(dst_crs) as f:
+ dst_crs = f.crs
+ for line in src:
+ coords = json.loads(line)
+ xs = coords[::2]
+ ys = coords[1::2]
+ xs, ys = rasterio.warp.transform(src_crs, dst_crs, xs, ys)
+ if precision >= 0:
+ xs = [round(v, precision) for v in xs]
+ ys = [round(v, precision) for v in ys]
+ result = [0]*len(coords)
+ result[::2] = xs
+ result[1::2] = ys
+ print(json.dumps(result))
+
except Exception:
logger.exception("Exception caught during processing")
raise click.Abort()
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
index 910f893..2732654 100644
--- a/rasterio/rio/main.py
+++ b/rasterio/rio/main.py
@@ -1,32 +1,38 @@
-# main: loader of all the command entry points.
+"""
+Main click group for CLI
+"""
-import sys
-import traceback
+import logging
from pkg_resources import iter_entry_points
+import sys
+
+import click
+import cligj
+import cligj.plugins
+
+import rasterio
+
+
+def configure_logging(verbosity):
+ log_level = max(10, 30 - 10*verbosity)
+ logging.basicConfig(stream=sys.stderr, level=log_level)
+
+
+ at cligj.plugins.group(plugins=(
+ ep for ep in list(iter_entry_points('rasterio.rio_commands')) +
+ list(iter_entry_points('rasterio.rio_plugins'))))
+ at cligj.verbose_opt
+ at cligj.quiet_opt
+ at click.version_option(version=rasterio.__version__, message='%(version)s')
+ at click.pass_context
+def main_group(ctx, verbose, quiet):
+
+ """
+ Rasterio command line interface.
+ """
-from rasterio.rio.cli import BrokenCommand, cli
-
-
-# Find and load all entry points in the rasterio.rio_commands group.
-# This includes the standard commands included with Rasterio as well
-# as commands provided by other packages.
-#
-# At a mimimum, commands must use the rasterio.rio.cli.cli command
-# group decorator like so:
-#
-# from rasterio.rio.cli import cli
-#
-# @cli.command()
-# def foo(...):
-# ...
-
-for entry_point in iter_entry_points('rasterio.rio_commands'):
- try:
- entry_point.load()
- except Exception:
- # Catch this so a busted plugin doesn't take down the CLI.
- # Handled by registering a dummy command that does nothing
- # other than explain the error.
- cli.add_command(
- BrokenCommand(entry_point.name))
+ verbosity = verbose - quiet
+ configure_logging(verbosity)
+ ctx.obj = {}
+ ctx.obj['verbosity'] = verbosity
diff --git a/rasterio/rio/merge.py b/rasterio/rio/merge.py
index a4c1afe..0d15e97 100644
--- a/rasterio/rio/merge.py
+++ b/rasterio/rio/merge.py
@@ -1,30 +1,31 @@
# Merge command.
+
import logging
import math
import os.path
-import sys
import warnings
import click
from cligj import files_inout_arg, format_opt
+from .helpers import resolve_inout
+from . import options
import rasterio
-from rasterio.rio.cli import cli, bounds_opt, output_opt, resolve_inout
from rasterio.transform import Affine
- at cli.command(short_help="Merge a stack of raster datasets.")
+ at click.command(short_help="Merge a stack of raster datasets.")
@files_inout_arg
- at output_opt
+ at options.output_opt
@format_opt
- at bounds_opt
- at click.option('-r', '--res', nargs=2, type=float, default=None,
- help="Output dataset resolution: pixel width, pixel height")
+ at options.bounds_opt
+ at options.resolution_opt
@click.option('--nodata', type=float, default=None,
help="Override nodata values defined in input datasets")
+ at options.creation_options
@click.pass_context
-def merge(ctx, files, output, driver, bounds, res, nodata):
+def merge(ctx, files, output, driver, bounds, res, nodata, creation_options):
"""Copy valid pixels from input files to an output file.
All files must have the same number of bands, data type, and
@@ -37,12 +38,19 @@ def merge(ctx, files, output, driver, bounds, res, nodata):
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.
+
+ Note: --res changed from 2 parameters in 0.25.
+ --res 0.1 0.1 => --res 0.1 (square)
+ --res 0.1 0.2 => --res 0.1 --res 0.2 (rectangular)
"""
import numpy as np
verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
logger = logging.getLogger('rio')
+ if len(res) == 1:
+ res = (res[0], res[0])
+
try:
with rasterio.drivers(CPL_DEBUG=verbosity>2):
output, files = resolve_inout(files=files, output=output)
@@ -50,6 +58,7 @@ def merge(ctx, files, output, driver, bounds, res, nodata):
with rasterio.open(files[0]) as first:
first_res = first.res
kwargs = first.meta
+ kwargs.update(**creation_options)
kwargs.pop('affine')
nodataval = first.nodatavals[0]
dtype = first.dtypes[0]
@@ -86,7 +95,7 @@ def merge(ctx, files, output, driver, bounds, res, nodata):
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
+ kwargs['driver'] = driver
kwargs['transform'] = output_transform
kwargs['width'] = output_width
kwargs['height'] = output_height
diff --git a/rasterio/rio/options.py b/rasterio/rio/options.py
new file mode 100644
index 0000000..1bd6521
--- /dev/null
+++ b/rasterio/rio/options.py
@@ -0,0 +1,156 @@
+"""
+Registry of common rio CLI options. See cligj for more options.
+
+-a, --all: Use all pixels touched by features. In rio-mask, rio-rasterize
+--as-mask/--not-as-mask: interpret band as mask or not. In rio-shapes
+--band/--mask: use band or mask. In rio-shapes
+--bbox:
+-b, --bidx: band index(es) (singular or multiple value versions).
+ In rio-info, rio-sample, rio-shapes, rio-stack (different usages)
+--bounds: bounds in world coordinates.
+ In rio-info, rio-rasterize (different usages)
+--count: count of bands. In rio-info
+--crop: Crop raster to extent of features. In rio-mask
+--crs: CRS of input raster. In rio-info
+--default-value: default for rasterized pixels. In rio-rasterize
+--dimensions: Output width, height. In rio-rasterize
+--dst-crs: destination CRS. In rio-transform
+--fill: fill value for pixels not covered by features. In rio-rasterize
+--formats: list available formats. In rio-info
+--height: height of raster. In rio-info
+-i, --invert: Invert mask created from features: In rio-mask
+-j, --geojson-mask: GeoJSON for masking raster. In rio-mask
+--lnglat: geograhpic coordinates of center of raster. In rio-info
+--masked/--not-masked: read masked data from source file.
+ In rio-calc, rio-info
+-m, --mode: output file mode (r, r+). In rio-insp
+--name: input file name alias. In rio-calc
+--nodata: nodata value. In rio-info, rio-merge (different usages)
+--photometric: photometric interpretation. In rio-stack
+--property: GeoJSON property to use as values for rasterize. In rio-rasterize
+-r, --res: output resolution.
+ In rio-info, rio-rasterize (different usages. TODO: try to combine
+ usages, prefer rio-rasterize version)
+--sampling: Inverse of sampling fraction. In rio-shapes
+--shape: shape (width, height) of band. In rio-info
+--src-crs: source CRS.
+ In rio-insp, rio-rasterize (different usages. TODO: consolidate usages)
+--stats: print raster stats. In rio-inf
+-t, --dtype: data type. In rio-calc, rio-info (different usages)
+--width: width of raster. In rio-info
+--with-nodata/--without-nodata: include nodata regions or not. In rio-shapes.
+-v, --tell-me-more, --verbose
+"""
+
+
+# TODO: move file_in_arg and file_out_arg to cligj
+
+
+import click
+
+
+def _cb_key_val(ctx, param, value):
+
+ """
+ click callback to validate `--opt KEY1=VAL1 --opt KEY2=VAL2` and collect
+ in a dictionary like the one below, which is what the CLI function receives.
+ If no value or `None` is received then an empty dictionary is returned.
+
+ {
+ 'KEY1': 'VAL1',
+ 'KEY2': 'VAL2'
+ }
+
+ Note: `==VAL` breaks this as `str.split('=', 1)` is used.
+ """
+
+ if not value:
+ return {}
+ else:
+ out = {}
+ for pair in value:
+ if '=' not in pair:
+ raise click.BadParameter("Invalid syntax for KEY=VAL arg: {}".format(pair))
+ else:
+ k, v = pair.split('=', 1)
+ out[k] = v
+
+ return out
+
+
+# Singular input file
+file_in_arg = click.argument(
+ 'INPUT',
+ type=click.Path(exists=True, resolve_path=True))
+
+# Singular output file
+file_out_arg = click.argument(
+ 'OUTPUT',
+ type=click.Path(resolve_path=True))
+
+bidx_opt = click.option(
+ '-b', '--bidx',
+ type=int,
+ default=1,
+ help="Input file band index (default: 1)")
+
+bidx_mult_opt = click.option(
+ '-b', '--bidx',
+ multiple=True,
+ help="Indexes of input file bands.")
+
+# TODO: may be better suited to cligj
+bounds_opt = click.option(
+ '--bounds',
+ nargs=4, type=float, default=None,
+ help='Output bounds: left bottom right top.')
+
+dimensions_opt = click.option(
+ '--dimensions',
+ nargs=2, type=int, default=None,
+ help='Output dataset width, height in number of pixels.')
+
+dtype_opt = click.option(
+ '-t', '--dtype',
+ type=click.Choice([
+ 'ubyte', 'uint8', 'uint16', 'int16', 'uint32', 'int32',
+ 'float32', 'float64']),
+ default=None,
+ help="Output data type.")
+
+like_file_opt = click.option(
+ '--like',
+ type=click.Path(exists=True),
+ help='Raster dataset to use as a template for obtaining affine '
+ 'transform (bounds and resolution), crs, data type, and driver '
+ 'used to create the output.')
+
+masked_opt = click.option(
+ '--masked/--not-masked',
+ default=True,
+ help="Evaluate expressions using masked arrays (the default) or ordinary "
+ "numpy arrays.")
+
+output_opt = click.option(
+ '-o', '--output',
+ default=None,
+ type=click.Path(resolve_path=True),
+ help="Path to output file (optional alternative to a positional arg "
+ "for some commands).")
+
+resolution_opt = click.option(
+ '-r', '--res',
+ multiple=True, type=float, default=None,
+ help='Output dataset resolution in units of coordinate '
+ 'reference system. Pixels assumed to be square if this option '
+ 'is used once, otherwise use: '
+ '--res pixel_width --res pixel_height')
+
+creation_options = click.option(
+ '--co', 'creation_options',
+ metavar='NAME=VALUE',
+ multiple=True,
+ callback=_cb_key_val,
+ help="Driver specific creation options."
+ "See the documentation for the selected output driver for "
+ "more information.")
\ No newline at end of file
diff --git a/rasterio/rio/overview.py b/rasterio/rio/overview.py
new file mode 100644
index 0000000..1c7892c
--- /dev/null
+++ b/rasterio/rio/overview.py
@@ -0,0 +1,101 @@
+# coding: utf-8
+"""Manage overviews of a dataset."""
+
+from functools import reduce
+import logging
+import operator
+
+import click
+
+import rasterio
+from rasterio.enums import Resampling
+
+from . import options
+
+
+def build_handler(ctx, param, value):
+ if value:
+ try:
+ if '^' in value:
+ base, exp_range = value.split('^')
+ exp_min, exp_max = (int(v) for v in exp_range.split('..'))
+ value = [pow(int(base), k) for k in range(exp_min, exp_max+1)]
+ else:
+ value = [int(v) for v in value.split(',')]
+ except Exception as exc:
+ raise click.BadParameter(u"must match 'n,n,n,…' or 'n^n..n'.")
+ return value
+
+
+ at click.command('overview', short_help="Construct overviews in an existing dataset.")
+ at options.file_in_arg
+ at click.option('--build', callback=build_handler, metavar=u"f1,f2,…|b^min..max",
+ help="A sequence of decimation factors specied as "
+ "comma-separated list of numbers or a base and range of "
+ "exponents.")
+ at click.option('--ls', help="Print the overviews for each band.",
+ is_flag=True, default=False)
+ at click.option('--rebuild', help="Reconstruct existing overviews.",
+ is_flag=True, default=False)
+ at click.option('--resampling', help="Resampling algorithm.",
+ type=click.Choice([item.name for item in Resampling]),
+ default='nearest', show_default=True)
+ at click.pass_context
+def overview(ctx, input, build, ls, rebuild, resampling):
+ """Construct overviews in an existing dataset.
+
+ A pyramid of overviews computed once and stored in the dataset can
+ improve performance in some applications.
+
+ The decimation levels at which to build overviews can be specified as
+ a comma separated list
+
+ rio pyramid --build 2,4,8,16
+
+ or a base and range of exponents.
+
+ rio pyramid --build 2^1..4
+
+ Note that overviews can not currently be removed and are not
+ automatically updated when the dataset's primary bands are
+ modified.
+
+ Information about existing overviews can be printed using the --ls
+ option.
+
+ rio pyramid --ls
+
+ """
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+
+ with rasterio.drivers(CPL_DEBUG=(verbosity > 2)) as env:
+ with rasterio.open(input, 'r+') as dst:
+
+ if ls:
+ resampling_method = dst.tags(
+ ns='rio_overview').get('resampling') or 'unknown'
+
+ click.echo("Overview factors:")
+ for idx in dst.indexes:
+ click.echo(" Band %d: %s (method: '%s')" % (
+ idx, dst.overviews(idx) or 'None', resampling_method))
+
+ elif rebuild:
+ # Build the same overviews for all bands.
+ factors = reduce(
+ operator.or_,
+ [set(dst.overviews(i)) for i in dst.indexes])
+
+ # Attempt to recover the resampling method from dataset tags.
+ resampling_method = dst.tags(
+ ns='rio_overview').get('resampling') or resampling
+
+ dst.build_overviews(
+ list(factors), Resampling[resampling_method])
+
+ elif build:
+ dst.build_overviews(build, Resampling[resampling])
+
+ # Save the resampling method to a tag.
+ dst.update_tags(ns='rio_overview', resampling=resampling)
diff --git a/rasterio/rio/rio.py b/rasterio/rio/rio.py
deleted file mode 100644
index b8cd2b9..0000000
--- a/rasterio/rio/rio.py
+++ /dev/null
@@ -1,203 +0,0 @@
-"""Rasterio command line interface"""
-
-import functools
-import json
-import logging
-import os.path
-import pprint
-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, file_in_arg
-
-
-warnings.simplefilter('default')
-
-
-# Commands are below.
-#
-# Command bodies less than ~20 lines, e.g. info() below, can go in this
-# module. Longer ones, e.g. insp() shall call functions imported from
-# rasterio.tool.
-
-# Insp command.
- at cli.command(short_help="Open a data file and start an interpreter.")
- at file_in_arg
- at click.option(
- '-m',
- '--mode',
- type=click.Choice(['r', 'r+']),
- default='r',
- help="File mode (default 'r').")
- at click.pass_context
-def insp(ctx, input, mode):
- import rasterio.tool
- verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
- logger = logging.getLogger('rio')
- try:
- with rasterio.drivers(CPL_DEBUG=verbosity>2):
- with rasterio.open(input, mode) as src:
- rasterio.tool.main(
- "Rasterio %s Interactive Inspector (Python %s)\n"
- 'Type "src.meta", "src.read_band(1)", or "help(src)" '
- 'for more information.' % (
- rasterio.__version__,
- '.'.join(map(str, sys.version_info[:3]))),
- src)
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
-
-
-# Bounds command.
- at cli.command(short_help="Write bounding boxes to stdout as GeoJSON.")
-# One or more files, the bounds of each are a feature in the collection
-# object or feature sequence.
- at click.argument('INPUT', nargs=-1, type=click.Path(exists=True))
- 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)
- at click.pass_context
-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
-
- $ rio bounds *.tif | geojsonio
-
- """
- import rasterio.warp
- verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
- logger = logging.getLogger('rio')
- dump_kwds = {'sort_keys': True}
- if indent:
- dump_kwds['indent'] = indent
- if compact:
- dump_kwds['separators'] = (',', ':')
- stdout = click.get_text_stream('stdout')
-
- # This is the generator for (feature, bbox) pairs.
- class Collection(object):
-
- def __init__(self):
- self._xs = []
- self._ys = []
-
- @property
- def bbox(self):
- return min(self._xs), min(self._ys), max(self._xs), max(self._ys)
-
- def __call__(self):
- for i, path in enumerate(input):
- with rasterio.open(path) as src:
- bounds = src.bounds
- xs = [bounds[0], bounds[2]]
- ys = [bounds[1], bounds[3]]
- if projection == 'geographic':
- xs, ys = rasterio.warp.transform(
- src.crs, {'init': 'epsg:4326'}, xs, ys)
- if projection == 'mercator':
- xs, ys = rasterio.warp.transform(
- src.crs, {'init': 'epsg:3857'}, xs, ys)
- if precision >= 0:
- xs = [round(v, precision) for v in xs]
- ys = [round(v, precision) for v in ys]
- bbox = [min(xs), min(ys), max(xs), max(ys)]
-
- yield {
- 'type': 'Feature',
- 'bbox': bbox,
- 'geometry': {
- 'type': 'Polygon',
- 'coordinates': [[
- [xs[0], ys[0]],
- [xs[1], ys[0]],
- [xs[1], ys[1]],
- [xs[0], ys[1]],
- [xs[0], ys[0]] ]]},
- 'properties': {
- 'id': str(i),
- 'title': path,
- 'filename': os.path.basename(path)} }
-
- self._xs.extend(bbox[::2])
- self._ys.extend(bbox[1::2])
-
- 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, col, sequence=sequence,
- geojson_type=geojson_type, use_rs=use_rs,
- **dump_kwds)
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
-
-
-# Transform command.
- at cli.command(short_help="Transform coordinates.")
- at click.argument('INPUT', default='-', required=False)
- at click.option('--src-crs', '--src_crs', default='EPSG:4326', help="Source CRS.")
- at click.option('--dst-crs', '--dst_crs', default='EPSG:4326', help="Destination CRS.")
- at precision_opt
- at click.pass_context
-def transform(ctx, input, src_crs, dst_crs, precision):
- import rasterio.warp
-
- verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
- logger = logging.getLogger('rio')
-
- # Handle the case of file, stream, or string input.
- try:
- src = click.open_file(input).readlines()
- except IOError:
- src = [input]
-
- try:
- with rasterio.drivers(CPL_DEBUG=verbosity>2):
- if src_crs.startswith('EPSG'):
- src_crs = {'init': src_crs}
- elif os.path.exists(src_crs):
- with rasterio.open(src_crs) as f:
- src_crs = f.crs
- if dst_crs.startswith('EPSG'):
- dst_crs = {'init': dst_crs}
- elif os.path.exists(dst_crs):
- with rasterio.open(dst_crs) as f:
- dst_crs = f.crs
- for line in src:
- coords = json.loads(line)
- xs = coords[::2]
- ys = coords[1::2]
- xs, ys = rasterio.warp.transform(src_crs, dst_crs, xs, ys)
- if precision >= 0:
- xs = [round(v, precision) for v in xs]
- ys = [round(v, precision) for v in ys]
- result = [0]*len(coords)
- result[::2] = xs
- result[1::2] = ys
- print(json.dumps(result))
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
diff --git a/rasterio/rio/sample.py b/rasterio/rio/sample.py
index 96eef67..8054bec 100644
--- a/rasterio/rio/sample.py
+++ b/rasterio/rio/sample.py
@@ -1,18 +1,16 @@
import json
import logging
-import sys
import warnings
import click
import rasterio
-from rasterio.rio.cli import cli
warnings.simplefilter('default')
- at cli.command(short_help="Sample a dataset.")
+ at click.command(short_help="Sample a dataset.")
@click.argument('files', nargs=-1, required=True, metavar='FILE "[x, y]"')
@click.option('-b', '--bidx', default=None, help="Indexes of input file bands.")
@click.pass_context
diff --git a/rasterio/rio/warp.py b/rasterio/rio/warp.py
new file mode 100644
index 0000000..80a060d
--- /dev/null
+++ b/rasterio/rio/warp.py
@@ -0,0 +1,208 @@
+import logging
+from math import ceil
+import click
+from cligj import format_opt
+
+from . import options
+import rasterio
+from rasterio import crs
+from rasterio.transform import Affine
+from rasterio.warp import (reproject, RESAMPLING, calculate_default_transform,
+ transform_bounds)
+
+
+logger = logging.getLogger('rio')
+
+
+ at click.command(short_help='Warp a raster dataset.')
+ at options.file_in_arg
+ at options.file_out_arg
+ at format_opt
+ at options.like_file_opt
+ at click.option('--dst-crs', default=None,
+ help='Target coordinate reference system.')
+ at options.dimensions_opt
+ at options.bounds_opt
+ at options.resolution_opt
+ at click.option('--resampling', type=click.Choice(['nearest', 'bilinear', 'cubic',
+ 'cubic_spline','lanczos', 'average', 'mode']),
+ default='nearest', help='Resampling method (default: nearest).')
+ at click.option('--threads', type=int, default=1,
+ help='Number of processing threads.')
+ at options.creation_options
+ at click.pass_context
+# TODO: add NODATA options and support for existing output rasters
+def warp(
+ ctx,
+ input,
+ output,
+ driver,
+ like,
+ dst_crs,
+ dimensions,
+ bounds,
+ res,
+ resampling,
+ threads,
+ creation_options):
+ """
+ Warp a raster dataset.
+
+ Currently, the output is always overwritten. This will be changed in a
+ later version.
+
+ If a template raster is provided using the --like option, the coordinate
+ reference system, affine transform, and dimensions of that raster will
+ be used for the output. In this case --dst-crs, --bounds, --res, and
+ --dimensions options are ignored.
+
+ \b
+ $ rio warp input.tif output.tif --like template.tif
+
+ The output coordinate reference system may be either a PROJ.4 or EPSG:nnnn
+ string,
+
+ \b
+ --dst-crs EPSG:4326
+ --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84'
+
+ or a JSON text-encoded PROJ.4 object.
+
+ \b
+ --dst-crs '{"proj": "utm", "zone": 18, ...}'
+
+ If --dimensions are provided, --res and --bounds are ignored. Resolution
+ is calculated based on the relationship between the raster bounds in the
+ target coordinate system and the dimensions, and may produce rectangular
+ rather than square pixels.
+
+ \b
+ $ rio warp input.tif output.tif --dimensions 100 200 --dst-crs EPSG:4326
+
+ If --bounds are provided, --res is required if --dst-crs is provided
+ (defaults to source raster resolution otherwise). Bounds are in the source
+ coordinate reference system.
+
+ \b
+ $ rio warp input.tif output.tif --bounds -78 22 -76 24 --dst-crs \\
+ EPSG:4326 --res 0.1
+ """
+
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ resampling = getattr(RESAMPLING, resampling) # get integer code for method
+
+ if not len(res):
+ # Click sets this as an empty tuple if not provided
+ res = None
+ else:
+ # Expand one value to two if needed
+ res = (res[0], res[0]) if len(res) == 1 else res
+
+ with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+ with rasterio.open(input) as src:
+ l, b, r, t = src.bounds
+ out_kwargs = src.meta.copy()
+ out_kwargs['driver'] = driver
+
+ if like:
+ with rasterio.open(like) as template_ds:
+ dst_crs = template_ds.crs
+ dst_transform = template_ds.affine
+ dst_height = template_ds.height
+ dst_width = template_ds.width
+
+ elif dst_crs:
+ try:
+ dst_crs = crs.from_string(dst_crs)
+ except ValueError:
+ raise click.BadParameter('invalid crs format',
+ param=dst_crs, param_hint=dst_crs)
+
+ if dimensions:
+ # Calculate resolution appropriate for dimensions in target
+ dst_width, dst_height = dimensions
+ xmin, ymin, xmax, ymax = transform_bounds(src.crs, dst_crs,
+ *src.bounds)
+ dst_transform = Affine(
+ (xmax - xmin) / float(dst_width),
+ 0, xmin, 0,
+ (ymin - ymax) / float(dst_height),
+ ymax
+ )
+
+ elif bounds:
+ if not res:
+ raise click.BadParameter('Required when using --bounds',
+ param='res', param_hint='res')
+
+ xmin, ymin, xmax, ymax = transform_bounds(src.crs, dst_crs,
+ *bounds)
+ dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax)
+ dst_width = max(int(ceil((xmax - xmin) / res[0])), 1)
+ dst_height = max(int(ceil((ymax - ymin) / res[1])), 1)
+
+ else:
+ dst_transform, dst_width, dst_height = calculate_default_transform(
+ src.crs, dst_crs, src.width, src.height, *src.bounds,
+ resolution=res)
+
+ elif dimensions:
+ # Same projection, different dimensions, calculate resolution
+ dst_crs = src.crs
+ dst_width, dst_height = dimensions
+ dst_transform = Affine(
+ (r - l) / float(dst_width),
+ 0, l, 0,
+ (b - t) / float(dst_height),
+ t
+ )
+
+ elif bounds:
+ # Same projection, different dimensions and possibly different
+ # resolution
+ if not res:
+ res = (src.affine.a, -src.affine.e)
+
+ dst_crs = src.crs
+ xmin, ymin, xmax, ymax = bounds
+ dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax)
+ dst_width = max(int(ceil((xmax - xmin) / res[0])), 1)
+ dst_height = max(int(ceil((ymax - ymin) / res[1])), 1)
+
+ elif res:
+ # Same projection, different resolution
+ dst_crs = src.crs
+ dst_transform = Affine(res[0], 0, l, 0, -res[1], t)
+ dst_width = max(int(ceil((r - l) / res[0])), 1)
+ dst_height = max(int(ceil((t - b) / res[1])), 1)
+
+ else:
+ dst_crs = src.crs
+ dst_transform = src.affine
+ dst_width = src.width
+ dst_height = src.height
+
+ out_kwargs.update({
+ 'crs': dst_crs,
+ 'transform': dst_transform,
+ 'affine': dst_transform,
+ 'width': dst_width,
+ 'height': dst_height
+ })
+
+ out_kwargs.update(**creation_options)
+
+ with rasterio.open(output, 'w', **out_kwargs) as dst:
+ for i in range(1, src.count + 1):
+
+ reproject(
+ source=rasterio.band(src, i),
+ destination=rasterio.band(dst, i),
+ src_transform=src.affine,
+ src_crs=src.crs,
+ # src_nodata=#TODO
+ dst_transform=out_kwargs['transform'],
+ dst_crs=out_kwargs['crs'],
+ # dst_nodata=#TODO
+ resampling=resampling,
+ num_threads=threads)
\ No newline at end of file
diff --git a/rasterio/sample.py b/rasterio/sample.py
new file mode 100644
index 0000000..aad74ad
--- /dev/null
+++ b/rasterio/sample.py
@@ -0,0 +1,10 @@
+# Workaround for issue #378. A pure Python generator.
+
+def sample_gen(dataset, xy, indexes=None):
+ index = dataset.index
+ read = dataset.read
+ for x, y in xy:
+ r, c = index(x, y)
+ window = ((r, r+1), (c, c+1))
+ data = read(indexes, window=window, masked=False, boundless=True)
+ yield data[:,0,0]
diff --git a/rasterio/tool.py b/rasterio/tool.py
index 6601cfc..cb6364f 100644
--- a/rasterio/tool.py
+++ b/rasterio/tool.py
@@ -2,7 +2,6 @@
import code
import collections
import logging
-import sys
try:
import matplotlib.pyplot as plt
@@ -49,10 +48,16 @@ def stats(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(funcs, src=dataset, np=numpy, rio=rasterio, plt=plt))
+def main(banner, dataset, alt_interpreter=None):
+ """ Main entry point for use with python interpreter """
+ local = dict(funcs, src=dataset, np=numpy, rio=rasterio, plt=plt)
+ if not alt_interpreter:
+ code.interact(banner, local=local)
+ elif alt_interpreter == 'ipython':
+ import IPython
+ IPython.InteractiveShell.banner1 = banner
+ IPython.start_ipython(argv=[], user_ns=local)
+ else:
+ raise ValueError("Unsupported interpreter '%s'" % alt_interpreter)
return 0
diff --git a/rasterio/warp.py b/rasterio/warp.py
index 219eac7..34d384f 100644
--- a/rasterio/warp.py
+++ b/rasterio/warp.py
@@ -81,7 +81,7 @@ def transform_geom(
precision)
-def transform_bounds(left, bottom, right, top, src_crs, dst_crs, densify_pts=21):
+def transform_bounds(src_crs, dst_crs, left, bottom, right, top, densify_pts=21):
"""
Transforms bounds from src_crs to dst_crs, optionally densifying the edges
(to account for nonlinear transformations along these edges) and extracting
@@ -91,13 +91,13 @@ def transform_bounds(left, bottom, right, top, src_crs, dst_crs, densify_pts=21)
Parameters
----------
- left, bottom, right, top: float
- Bounding coordinates in src_crs, from the bounds property of a raster.
src_crs: dict
Source coordinate reference system, in rasterio dict format.
Example: {'init': 'EPSG:4326'}
dst_crs: dict
Target coordinate reference system.
+ left, bottom, right, top: float
+ Bounding coordinates in src_crs, from the bounds property of a raster.
densify_pts: uint, optional
Number of points to add to each edge to account for nonlinear
edges produced by the transform process. Large numbers will produce
@@ -230,14 +230,14 @@ def reproject(
def calculate_default_transform(
+ src_crs,
+ dst_crs,
+ width,
+ height,
left,
bottom,
right,
top,
- width,
- height,
- src_crs,
- dst_crs,
resolution=None,
densify_pts=21):
"""
@@ -257,13 +257,17 @@ def calculate_default_transform(
Parameters
----------
- left, bottom, right, top: float
- Bounding coordinates in src_crs, from the bounds property of a raster.
src_crs: dict
Source coordinate reference system, in rasterio dict format.
Example: {'init': 'EPSG:4326'}
dst_crs: dict
Target coordinate reference system.
+ width: int
+ Source raster width.
+ height: int
+ Source raster height.
+ left, bottom, right, top: float
+ Bounding coordinates in src_crs, from the bounds property of a raster.
resolution: tuple (x resolution, y resolution) or float, optional
Target resolution, in units of target coordinate reference system.
densify_pts: uint, optional
@@ -277,7 +281,7 @@ def calculate_default_transform(
"""
xmin, ymin, xmax, ymax = transform_bounds(
- left, bottom, right, top, src_crs, dst_crs, densify_pts)
+ src_crs, dst_crs, left, bottom, right, top, densify_pts)
x_dif = xmax - xmin
y_dif = ymax - ymin
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 69196b1..5b7ff43 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,7 +1,7 @@
affine
cligj
coveralls>=0.4
-cython==0.21.2
+cython>=0.21.2
delocate
enum34
numpy>=1.8.0
diff --git a/setup.py b/setup.py
index 7bed441..a45f8b4 100755
--- a/setup.py
+++ b/setup.py
@@ -130,6 +130,11 @@ ext_options = dict(
libraries=libraries,
extra_link_args=extra_link_args)
+if not os.name == "nt":
+ # These options fail on Windows if using Visual Studio
+ ext_options['extra_compile_args'] = ['-Wno-unused-parameter',
+ '-Wno-unused-function']
+
log.debug('ext_options:\n%s', pprint.pformat(ext_options))
# When building from a repo, Cython is required.
@@ -190,7 +195,7 @@ with open('README.rst') as f:
# Runtime requirements.
inst_reqs = [
'affine>=1.0',
- 'cligj',
+ 'cligj>=0.2.0',
'Numpy>=1.7',
'snuggs>=1.3.1']
@@ -224,27 +229,32 @@ setup_args = dict(
packages=['rasterio', 'rasterio.rio'],
entry_points='''
[console_scripts]
- rio=rasterio.rio.main:cli
-
+ rio=rasterio.rio.main:main_group
+
[rasterio.rio_commands]
- bounds=rasterio.rio.rio:bounds
+ bounds=rasterio.rio.features:bounds
calc=rasterio.rio.calc:calc
+ convert=rasterio.rio.convert:convert
edit-info=rasterio.rio.info:edit
env=rasterio.rio.info:env
info=rasterio.rio.info:info
- insp=rasterio.rio.rio:insp
+ insp=rasterio.rio.info:insp
mask=rasterio.rio.features:mask
merge=rasterio.rio.merge:merge
+ overview=rasterio.rio.overview:overview
rasterize=rasterio.rio.features:rasterize
sample=rasterio.rio.sample:sample
shapes=rasterio.rio.features:shapes
stack=rasterio.rio.bands:stack
- transform=rasterio.rio.rio:transform
+ warp=rasterio.rio.warp:warp
+ transform=rasterio.rio.info:transform
''',
include_package_data=True,
ext_modules=ext_modules,
zip_safe=False,
- install_requires=inst_reqs)
+ install_requires=inst_reqs,
+ extras_require={
+ 'ipython': ['ipython>=2.0']})
if os.environ.get('PACKAGE_DATA'):
setup_args['package_data'] = {'rasterio': ['gdal_data/*', 'proj_data/*']}
diff --git a/tests/data/RGB.byte.tif b/tests/data/RGB.byte.tif
index 1efaf4a..9396ba2 100644
Binary files a/tests/data/RGB.byte.tif and b/tests/data/RGB.byte.tif differ
diff --git a/tests/test_cli_main.py b/tests/test_cli_main.py
new file mode 100644
index 0000000..c98ba15
--- /dev/null
+++ b/tests/test_cli_main.py
@@ -0,0 +1,21 @@
+from pkg_resources import iter_entry_points
+
+from click.testing import CliRunner
+
+import rasterio
+from rasterio.rio.main import main_group
+
+
+def test_version():
+ runner = CliRunner()
+ result = runner.invoke(main_group, ['--version'])
+ assert result.exit_code == 0
+ assert rasterio.__version__ in result.output
+
+
+def test_all_registered():
+ # This test makes sure that all of the subcommands defined in the
+ # rasterio.rio_commands entry-point are actually registered to the main
+ # cli group.
+ for ep in iter_entry_points('rasterio.rio_commands'):
+ assert ep.name in main_group.commands
diff --git a/tests/test_colormap.py b/tests/test_colormap.py
index e5899fb..112ebfa 100644
--- a/tests/test_colormap.py
+++ b/tests/test_colormap.py
@@ -7,6 +7,21 @@ import rasterio
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+def test_write_colormap_err(tmpdir):
+
+ with rasterio.drivers():
+
+ with rasterio.open('tests/data/shade.tif') as src:
+ meta = src.meta
+
+ tiffname = str(tmpdir.join('foo.tif'))
+
+ with rasterio.open(tiffname, 'w', **meta) as dst:
+ with pytest.raises(ValueError):
+ dst.write_colormap(1, {0: (255, 0, 0, 255), 255: (0, 0, 0, 0)})
+
+
def test_write_colormap(tmpdir):
with rasterio.drivers():
@@ -15,19 +30,20 @@ def test_write_colormap(tmpdir):
shade = src.read_band(1)
meta = src.meta
- tiffname = str(tmpdir.join('foo.tif'))
-
+ tiffname = str(tmpdir.join('foo.png'))
+ meta['driver'] = 'PNG'
+
with rasterio.open(tiffname, 'w', **meta) as dst:
dst.write_band(1, shade)
- dst.write_colormap(1, {0: (255, 0, 0, 255), 255: (0, 0, 255, 255)})
+ dst.write_colormap(1, {0: (255, 0, 0, 255), 255: (0, 0, 0, 0)})
cmap = dst.colormap(1)
assert cmap[0] == (255, 0, 0, 255)
- assert cmap[255] == (0, 0, 255, 255)
+ assert cmap[255] == (0, 0, 0, 0)
with rasterio.open(tiffname) as src:
cmap = src.colormap(1)
assert cmap[0] == (255, 0, 0, 255)
- assert cmap[255] == (0, 0, 255, 255)
+ assert cmap[255] == (0, 0, 0, 0)
# subprocess.call(['open', tiffname])
diff --git a/tests/test_crs.py b/tests/test_crs.py
index eb6da86..300b5cd 100644
--- a/tests/test_crs.py
+++ b/tests/test_crs.py
@@ -2,13 +2,17 @@ import logging
import pytest
import subprocess
import sys
+import json
import rasterio
from rasterio import crs
-from rasterio._base import is_geographic_crs, is_projected_crs, is_same_crs
+from rasterio.crs import (
+ is_geographic_crs, is_projected_crs, is_same_crs, is_valid_crs)
+
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
# When possible, Rasterio gives you the CRS in the form of an EPSG code.
def test_read_epsg(tmpdir):
with rasterio.drivers():
@@ -57,6 +61,16 @@ def test_write_3857(tmpdir):
AUTHORITY["EPSG","3857"]]""" in info.decode('utf-8')
+def test_from_proj4_json():
+ json_str = '{"proj": "longlat", "ellps": "WGS84", "datum": "WGS84"}'
+ crs_dict = crs.from_string(json_str)
+ assert crs_dict == json.loads(json_str)
+
+ # Test with invalid JSON code
+ with pytest.raises(ValueError):
+ assert crs.from_string('{foo: bar}')
+
+
def test_from_epsg():
crs_dict = crs.from_epsg(4326)
assert crs_dict['init'].lower() == 'epsg:4326'
@@ -66,6 +80,15 @@ def test_from_epsg():
assert crs.from_epsg(0)
+def test_from_epsg_string():
+ crs_dict = crs.from_string('epsg:4326')
+ assert crs_dict['init'].lower() == 'epsg:4326'
+
+ # Test with invalid EPSG code
+ with pytest.raises(ValueError):
+ assert crs.from_string('epsg:xyz')
+
+
def test_bare_parameters():
""" Make sure that bare parameters (e.g., no_defs) are handled properly,
even if they come in with key=True. This covers interaction with pyproj,
@@ -117,4 +140,16 @@ def test_is_same_crs():
# Make sure that same projection with different parameter are not equal
lcc_crs1 = crs.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0')
lcc_crs2 = crs.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=45 +lat_0=0')
- assert is_same_crs(lcc_crs1, lcc_crs2) is False
\ No newline at end of file
+ assert is_same_crs(lcc_crs1, lcc_crs2) is False
+
+
+def test_to_string():
+ assert crs.to_string({'init': 'EPSG:4326'}) == "+init=EPSG:4326"
+
+
+def test_is_valid_false():
+ assert not is_valid_crs('EPSG:432600')
+
+
+def test_is_valid():
+ assert is_valid_crs('EPSG:4326')
diff --git a/tests/test_indexing.py b/tests/test_indexing.py
index f536302..c1c5f73 100644
--- a/tests/test_indexing.py
+++ b/tests/test_indexing.py
@@ -36,9 +36,16 @@ def test_window():
left, bottom, right, top = src.bounds
dx, dy = src.res
eps = 1.0e-8
- assert src.window(left+eps, bottom+eps, right-eps, top-eps) == ((0, src.height-1),
- (0, src.width-1))
+ assert src.window(
+ left+eps, bottom+eps, right-eps, top-eps) == ((0, src.height),
+ (0, src.width))
assert src.index(left+400, top-400) == (1, 1)
assert src.index(left+dx+eps, top-dy-eps) == (1, 1)
assert src.window(left, top-400, left+400, top) == ((0, 1), (0, 1))
assert src.window(left, top-2*dy-eps, left+2*dx+eps, top) == ((0, 2), (0, 2))
+
+
+def test_window_bounds_roundtrip():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert ((100, 200), (100, 200)) == src.window(
+ *src.window_bounds(((100, 200), (100, 200))))
diff --git a/tests/test_meta.py b/tests/test_meta.py
index 5e59c39..68070d1 100644
--- a/tests/test_meta.py
+++ b/tests/test_meta.py
@@ -3,26 +3,23 @@
import rasterio
-def test_blocksize_rgb(tmpdir):
+def test_copy_meta(tmpdir):
with rasterio.open('tests/data/RGB.byte.tif') as src:
kwds = src.meta
- assert kwds['blockxsize'] == 791
- assert kwds['blockysize'] == 3
- assert kwds['tiled'] is False
+ with rasterio.open(
+ str(tmpdir.join('test_copy_meta.tif')), 'w', **kwds) as dst:
+ assert dst.meta['count'] == 3
-def test_blocksize_shade(tmpdir):
- with rasterio.open('tests/data/shade.tif') as src:
- kwds = src.meta
- assert kwds['blockxsize'] == 1024
- assert kwds['blockysize'] == 8
- assert kwds['tiled'] is False
-def test_copy_meta(tmpdir):
+def test_blacklisted_keys(tmpdir):
+ # Some keys were removed from .meta when they were found to clash with
+ # creation options.
+ # https://github.com/mapbox/rasterio/issues/402
with rasterio.open('tests/data/RGB.byte.tif') as src:
kwds = src.meta
with rasterio.open(
str(tmpdir.join('test_copy_meta.tif')), 'w', **kwds) as dst:
- assert dst.meta['count'] == 3
- assert dst.meta['blockxsize'] == 791
- assert dst.meta['blockysize'] == 3
- assert dst.meta['tiled'] is False
+ keys = map(lambda x: x.lower(), dst.meta.keys())
+ assert 'blockxsize' not in keys
+ assert 'blockysize' not in keys
+ assert 'tiled' not in keys
diff --git a/tests/test_options.py b/tests/test_options.py
new file mode 100644
index 0000000..38ee698
--- /dev/null
+++ b/tests/test_options.py
@@ -0,0 +1,20 @@
+import click
+import pytest
+from rasterio.rio import options
+
+
+def test_cb_key_val():
+
+ pairs = ['KEY=val', '1==']
+ expected = {
+ 'KEY': 'val',
+ '1': '=',
+ }
+ assert options._cb_key_val(None, None, pairs) == expected
+
+ # Make sure None or an empty list returns an empty dict
+ assert options._cb_key_val(None, None, None) == {}
+ assert options._cb_key_val(None, None, ()) == {}
+
+ with pytest.raises(click.BadParameter):
+ options._cb_key_val(None, None, 'bad_val')
diff --git a/tests/test_overviews.py b/tests/test_overviews.py
new file mode 100644
index 0000000..381a06e
--- /dev/null
+++ b/tests/test_overviews.py
@@ -0,0 +1,40 @@
+"""Tests of overview counting and creation."""
+
+import logging
+import sys
+
+from click.testing import CliRunner
+
+import rasterio
+from rasterio.enums import Resampling
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_count_overviews_zero(data):
+ inputfile = str(data.join('RGB.byte.tif'))
+ with rasterio.open(inputfile) as src:
+ assert src.overviews(1) == []
+ assert src.overviews(2) == []
+ assert src.overviews(3) == []
+
+
+def test_build_overviews_one(data):
+ inputfile = str(data.join('RGB.byte.tif'))
+ with rasterio.open(inputfile, 'r+') as src:
+ overview_factors = [2]
+ src.build_overviews(overview_factors, resampling=Resampling.nearest)
+ assert src.overviews(1) == [2]
+ assert src.overviews(2) == [2]
+ assert src.overviews(3) == [2]
+
+
+def test_build_overviews_two(data):
+ inputfile = str(data.join('RGB.byte.tif'))
+ with rasterio.open(inputfile, 'r+') as src:
+ overview_factors = [2, 4]
+ src.build_overviews(overview_factors, resampling=Resampling.nearest)
+ assert src.overviews(1) == [2, 4]
+ assert src.overviews(2) == [2, 4]
+ assert src.overviews(3) == [2, 4]
diff --git a/tests/test_profile.py b/tests/test_profile.py
index c277da5..32979c4 100644
--- a/tests/test_profile.py
+++ b/tests/test_profile.py
@@ -77,3 +77,13 @@ def test_profile_overlay():
assert kwds['tiled']
assert kwds['compress'] == 'lzw'
assert kwds['count'] == 3
+
+
+def test_dataset_profile_property(data):
+ tiffile = str(data.join('RGB.byte.tif'))
+ with rasterio.open(tiffile, 'r+') as src:
+ src.update_tags(ns='rio_creation_kwds', foo='bar')
+ assert src.profile['blockxsize'] == 791
+ assert src.profile['blockysize'] == 3
+ assert src.profile['tiled'] == False
+ assert src.profile['foo'] == 'bar'
diff --git a/tests/test_rio_cli.py b/tests/test_rio_cli.py
deleted file mode 100644
index 875e3fa..0000000
--- a/tests/test_rio_cli.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from rasterio.rio import cli
-
-
-def test_resolve_files_inout__output():
- assert cli.resolve_inout(input='in', output='out') == ('out', ['in'])
-
-
-def test_resolve_files_inout__input():
- assert cli.resolve_inout(input='in') == (None, ['in'])
-
-
-def test_resolve_files_inout__inout_files():
- assert cli.resolve_inout(files=('a', 'b', 'c')) == ('c', ['a', 'b'])
-
-
-def test_resolve_files_inout__inout_files_output_o():
- assert cli.resolve_inout(
- files=('a', 'b', 'c'), output='out') == ('out', ['a', 'b', 'c'])
diff --git a/tests/test_rio_convert.py b/tests/test_rio_convert.py
new file mode 100644
index 0000000..91590a2
--- /dev/null
+++ b/tests/test_rio_convert.py
@@ -0,0 +1,120 @@
+import sys
+import os
+import logging
+import click
+from click.testing import CliRunner
+
+import rasterio
+from rasterio.rio.convert import convert
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+# Tests: format and type conversion, --format and --dtype
+
+def test_format(tmpdir):
+ outputname = str(tmpdir.join('test.jpg'))
+ runner = CliRunner()
+ result = runner.invoke(
+ convert,
+ ['tests/data/RGB.byte.tif', outputname, '--format', 'JPEG'])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as src:
+ assert src.driver == 'JPEG'
+
+
+def test_format_short(tmpdir):
+ outputname = str(tmpdir.join('test.jpg'))
+ runner = CliRunner()
+ result = runner.invoke(
+ convert,
+ ['tests/data/RGB.byte.tif', outputname, '-f', 'JPEG'])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as src:
+ assert src.driver == 'JPEG'
+
+
+def test_output_opt(tmpdir):
+ outputname = str(tmpdir.join('test.jpg'))
+ runner = CliRunner()
+ result = runner.invoke(
+ convert,
+ ['tests/data/RGB.byte.tif', '-o', outputname, '-f', 'JPEG'])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as src:
+ assert src.driver == 'JPEG'
+
+
+def test_dtype(tmpdir):
+ outputname = str(tmpdir.join('test.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ convert,
+ ['tests/data/RGB.byte.tif', outputname, '--dtype', 'uint16'])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as src:
+ assert src.dtypes == ['uint16']*3
+
+
+def test_dtype_rescaling_uint8_full(tmpdir):
+ """Rescale uint8 [0, 255] to uint8 [0, 255]"""
+ outputname = str(tmpdir.join('test.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ convert,
+ ['tests/data/RGB.byte.tif', outputname, '--scale-ratio', '1.0'])
+ assert result.exit_code == 0
+
+ src_stats = [
+ {"max": 255.0, "mean": 44.434478650699106, "min": 1.0},
+ {"max": 255.0, "mean": 66.02203484105824, "min": 1.0},
+ {"max": 255.0, "mean": 71.39316199120559, "min": 1.0}]
+
+ with rasterio.open(outputname) as src:
+ for band, expected in zip(src.read(masked=True), src_stats):
+ assert round(band.min() - expected['min'], 6) == 0.0
+ assert round(band.max() - expected['max'], 6) == 0.0
+ assert round(band.mean() - expected['mean'], 6) == 0.0
+
+
+def test_dtype_rescaling_uint8_half(tmpdir):
+ """Rescale uint8 [0, 255] to uint8 [0, 127]"""
+ outputname = str(tmpdir.join('test.tif'))
+ runner = CliRunner()
+ result = runner.invoke(convert, [
+ 'tests/data/RGB.byte.tif', outputname, '--scale-ratio', '0.5'])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as src:
+ for band in src.read():
+ assert round(band.min() - 0, 6) == 0.0
+ assert round(band.max() - 127, 6) == 0.0
+
+
+def test_dtype_rescaling_uint16(tmpdir):
+ """Rescale uint8 [0, 255] to uint16 [0, 4095]"""
+ # NB: 255 * 16 is 4080, we don't actually get to 4095.
+ outputname = str(tmpdir.join('test.tif'))
+ runner = CliRunner()
+ result = runner.invoke(convert, [
+ 'tests/data/RGB.byte.tif', outputname, '--dtype', 'uint16',
+ '--scale-ratio', '16'])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as src:
+ for band in src.read():
+ assert round(band.min() - 0, 6) == 0.0
+ assert round(band.max() - 4080, 6) == 0.0
+
+
+def test_dtype_rescaling_float64(tmpdir):
+ """Rescale uint8 [0, 255] to float64 [-1, 1]"""
+ outputname = str(tmpdir.join('test.tif'))
+ runner = CliRunner()
+ result = runner.invoke(convert, [
+ 'tests/data/RGB.byte.tif', outputname, '--dtype', 'float64',
+ '--scale-ratio', str(2.0/255), '--scale-offset', '-1.0'])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as src:
+ for band in src.read():
+ assert round(band.min() + 1.0, 6) == 0.0
+ assert round(band.max() - 1.0, 6) == 0.0
diff --git a/tests/test_rio_helpers.py b/tests/test_rio_helpers.py
new file mode 100644
index 0000000..5e60740
--- /dev/null
+++ b/tests/test_rio_helpers.py
@@ -0,0 +1,23 @@
+from rasterio.rio import helpers
+
+
+def test_resolve_files_inout__output():
+ assert helpers.resolve_inout(input='in', output='out') == ('out', ['in'])
+
+
+def test_resolve_files_inout__input():
+ assert helpers.resolve_inout(input='in') == (None, ['in'])
+
+
+def test_resolve_files_inout__inout_files():
+ assert helpers.resolve_inout(files=('a', 'b', 'c')) == ('c', ['a', 'b'])
+
+
+def test_resolve_files_inout__inout_files_output_o():
+ assert helpers.resolve_inout(
+ files=('a', 'b', 'c'), output='out') == ('out', ['a', 'b', 'c'])
+
+
+def test_to_lower():
+
+ assert helpers.to_lower(None, None, 'EPSG:3857') == 'epsg:3857'
\ No newline at end of file
diff --git a/tests/test_rio_info.py b/tests/test_rio_info.py
index c1a5d91..79c5184 100644
--- a/tests/test_rio_info.py
+++ b/tests/test_rio_info.py
@@ -3,10 +3,13 @@ import logging
import sys
import click
+from click import Context
from click.testing import CliRunner
+import pytest
import rasterio
-from rasterio.rio import cli, info
+from rasterio.rio import info
+from rasterio.rio.main import main_group
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
@@ -111,9 +114,189 @@ def test_edit_tags(data):
assert src.tags()['lol'] == '1'
assert src.tags()['wut'] == '2'
+
+class MockContext:
+
+ def __init__(self):
+ self.obj = {}
+
+
+class MockOption:
+
+ def __init__(self, name):
+ self.name = name
+
+
+def test_like_dataset_callback(data):
+ ctx = MockContext()
+ info.like_handler(ctx, 'like', str(data.join('RGB.byte.tif')))
+ assert ctx.obj['like']['crs'] == {'init': 'epsg:32618'}
+
+
+def test_all_callback_pass(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'transform': 'foo'}
+ assert info.all_handler(ctx, None, None) == None
+
+
+def test_all_callback(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'transform': 'foo'}
+ assert info.all_handler(ctx, None, True) == {'transform': 'foo'}
+
+
+def test_all_callback_None(data):
+ ctx = MockContext()
+ assert info.all_handler(ctx, None, None) is None
+
+
+def test_transform_callback_pass(data):
+ """Always return None if the value is None"""
+ ctx = MockContext()
+ ctx.obj['like'] = {'transform': 'foo'}
+ assert info.transform_handler(ctx, MockOption('transform'), None) is None
+
+
+def test_transform_callback_err(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'transform': 'foo'}
+ with pytest.raises(click.BadParameter):
+ info.transform_handler(ctx, MockOption('transform'), '?')
+
+
+def test_transform_callback(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'transform': 'foo'}
+ assert info.transform_handler(ctx, MockOption('transform'), 'like') == 'foo'
+
+
+def test_nodata_callback_err(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'nodata': 'lolwut'}
+ with pytest.raises(click.BadParameter):
+ info.nodata_handler(ctx, MockOption('nodata'), 'lolwut')
+
+
+def test_nodata_callback_pass(data):
+ """Always return None if the value is None"""
+ ctx = MockContext()
+ ctx.obj['like'] = {'nodata': -1}
+ assert info.nodata_handler(ctx, MockOption('nodata'), None) is None
+
+
+def test_nodata_callback_0(data):
+ ctx = MockContext()
+ assert info.nodata_handler(ctx, MockOption('nodata'), '0') == 0.0
+
+
+def test_nodata_callback(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'nodata': -1}
+ assert info.nodata_handler(ctx, MockOption('nodata'), 'like') == -1.0
+
+
+def test_crs_callback_pass(data):
+ """Always return None if the value is None"""
+ ctx = MockContext()
+ ctx.obj['like'] = {'crs': 'foo'}
+ assert info.crs_handler(ctx, MockOption('crs'), None) is None
+
+
+def test_crs_callback(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'crs': 'foo'}
+ assert info.crs_handler(ctx, MockOption('crs'), 'like') == 'foo'
+
+
+def test_tags_callback_err(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'tags': {'foo': 'bar'}}
+ with pytest.raises(click.BadParameter):
+ info.tags_handler(ctx, MockOption('tags'), '?') == {'foo': 'bar'}
+
+
+def test_tags_callback(data):
+ ctx = MockContext()
+ ctx.obj['like'] = {'tags': {'foo': 'bar'}}
+ assert info.tags_handler(ctx, MockOption('tags'), 'like') == {'foo': 'bar'}
+
+
+def test_edit_crs_like(data):
+ runner = CliRunner()
+
+ # Set up the file to be edited.
+ inputfile = str(data.join('RGB.byte.tif'))
+ with rasterio.open(inputfile, 'r+') as dst:
+ dst.crs = {'init': 'epsg:32617'}
+ dst.nodata = 1.0
+
+ # Double check.
+ with rasterio.open(inputfile) as src:
+ assert src.crs == {'init': 'epsg:32617'}
+ assert src.nodata == 1.0
+
+ # The test.
+ templatefile = 'tests/data/RGB.byte.tif'
+ result = runner.invoke(info.edit, [
+ inputfile, '--like', templatefile, '--crs', 'like'])
+ assert result.exit_code == 0
+ with rasterio.open(inputfile) as src:
+ assert src.crs == {'init': 'epsg:32618'}
+ assert src.nodata == 1.0
+
+
+def test_edit_nodata_like(data):
+ runner = CliRunner()
+
+ # Set up the file to be edited.
+ inputfile = str(data.join('RGB.byte.tif'))
+ with rasterio.open(inputfile, 'r+') as dst:
+ dst.crs = {'init': 'epsg:32617'}
+ dst.nodata = 1.0
+
+ # Double check.
+ with rasterio.open(inputfile) as src:
+ assert src.crs == {'init': 'epsg:32617'}
+ assert src.nodata == 1.0
+
+ # The test.
+ templatefile = 'tests/data/RGB.byte.tif'
+ result = runner.invoke(info.edit, [
+ inputfile, '--like', templatefile, '--nodata', 'like'])
+ assert result.exit_code == 0
+ with rasterio.open(inputfile) as src:
+ assert src.crs == {'init': 'epsg:32617'}
+ assert src.nodata == 0.0
+
+
+def test_edit_all_like(data):
+ runner = CliRunner()
+
+ inputfile = str(data.join('RGB.byte.tif'))
+ with rasterio.open(inputfile, 'r+') as dst:
+ dst.crs = {'init': 'epsg:32617'}
+ dst.nodata = 1.0
+
+ # Double check.
+ with rasterio.open(inputfile) as src:
+ assert src.crs == {'init': 'epsg:32617'}
+ assert src.nodata == 1.0
+
+ templatefile = 'tests/data/RGB.byte.tif'
+ result = runner.invoke(info.edit, [
+ inputfile, '--like', templatefile, '--all'])
+ assert result.exit_code == 0
+ with rasterio.open(inputfile) as src:
+ assert src.crs == {'init': 'epsg:32618'}
+ assert src.nodata == 0.0
+
+
def test_env():
runner = CliRunner()
- result = runner.invoke(info.env, ['--formats'])
+ result = runner.invoke(main_group, [
+ 'env',
+ '--formats'
+ ])
assert result.exit_code == 0
assert 'GTiff' in result.output
@@ -137,17 +320,21 @@ def test_info():
def test_info_verbose():
runner = CliRunner()
- result = runner.invoke(
- cli.cli,
- ['-v', 'info', 'tests/data/RGB.byte.tif'])
+ result = runner.invoke(main_group, [
+ '-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'])
+ result = runner.invoke(main_group, [
+ '-q',
+ 'info',
+ 'tests/data/RGB.byte.tif'
+ ])
assert result.exit_code == 0
@@ -206,7 +393,8 @@ def test_mo_info():
def test_info_stats():
runner = CliRunner()
- result = runner.invoke(info.info, ['tests/data/RGB.byte.tif', '--tell-me-more'])
+ result = runner.invoke(
+ info.info, ['tests/data/RGB.byte.tif', '--tell-me-more'])
assert result.exit_code == 0
assert '"max": 255.0' in result.output
assert '"min": 1.0' in result.output
@@ -215,6 +403,241 @@ def test_info_stats():
def test_info_stats_only():
runner = CliRunner()
- result = runner.invoke(info.info, ['tests/data/RGB.byte.tif', '--stats', '--bidx', '2'])
+ result = runner.invoke(
+ info.info, ['tests/data/RGB.byte.tif', '--stats', '--bidx', '2'])
assert result.exit_code == 0
assert result.output.startswith('1.000000 255.000000 66.02')
+
+
+def test_transform_err():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'transform'
+ ], "[-78.0]")
+ assert result.exit_code == 1
+
+
+def test_transform_point():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'transform',
+ '--dst-crs', 'EPSG:32618',
+ '--precision', '2'
+ ], "[-78.0, 23.0]", catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == '[192457.13, 2546667.68]'
+
+
+def test_transform_point_dst_file():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'transform',
+ '--dst-crs', 'tests/data/RGB.byte.tif', '--precision', '2'
+ ], "[-78.0, 23.0]")
+ assert result.exit_code == 0
+ assert result.output.strip() == '[192457.13, 2546667.68]'
+
+
+def test_transform_point_src_file():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'transform',
+ '--src-crs',
+ 'tests/data/RGB.byte.tif',
+ '--precision', '2'
+ ], "[192457.13, 2546667.68]")
+ assert result.exit_code == 0
+ assert result.output.strip() == '[-78.0, 23.0]'
+
+
+def test_transform_point_2():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'transform',
+ '[-78.0, 23.0]',
+ '--dst-crs', 'EPSG:32618',
+ '--precision', '2'
+ ])
+ assert result.exit_code == 0
+ assert result.output.strip() == '[192457.13, 2546667.68]'
+
+
+def test_transform_point_multi():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'transform',
+ '--dst-crs', 'EPSG:32618',
+ '--precision', '2'
+ ], "[-78.0, 23.0]\n[-78.0, 23.0]", catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == (
+ '[192457.13, 2546667.68]\n[192457.13, 2546667.68]')
+
+
+def test_bounds_defaults():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif'
+ ])
+ assert result.exit_code == 0
+ assert 'FeatureCollection' in result.output
+
+
+def test_bounds_err():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests'
+ ])
+ assert result.exit_code == 1
+
+
+def test_bounds_feature():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ '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(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ '--bbox',
+ '--precision', '2'
+ ])
+ assert result.exit_code == 0
+ assert result.output.strip() == '[-78.9, 23.56, -76.6, 25.55]'
+
+
+def test_bounds_compact():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ '--bbox',
+ '--precision', '2',
+ '--compact'
+ ])
+ assert result.exit_code == 0
+ assert result.output.strip() == '[-78.9,23.56,-76.6,25.55]'
+
+
+def test_bounds_indent():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ '--bbox',
+ '--indent', '2',
+ '--precision', '2'
+ ])
+ assert result.exit_code == 0
+ assert len(result.output.split('\n')) == 7
+
+
+def test_bounds_obj_bbox_mercator():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ '--bbox',
+ '--mercator',
+ '--precision', '3'
+ ])
+ assert result.exit_code == 0
+ assert result.output.strip() == (
+ '[-8782900.033, 2700489.278, -8527010.472, 2943560.235]')
+
+
+def test_bounds_obj_bbox_projected():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ '--bbox',
+ '--projected',
+ '--precision', '3'
+ ])
+ assert result.exit_code == 0
+ assert result.output.strip() == (
+ '[101985.0, 2611485.0, 339315.0, 2826915.0]')
+
+
+def test_bounds_crs_bbox():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ '--bbox',
+ '--dst-crs', 'EPSG:32618',
+ '--precision', '3'
+ ])
+ assert result.exit_code == 0
+ assert result.output.strip() == (
+ '[101985.0, 2611485.0, 339315.0, 2826915.0]')
+
+
+def test_bounds_seq():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ 'tests/data/RGB.byte.tif',
+ '--sequence'
+ ])
+ assert result.exit_code == 0
+ assert result.output.count('Polygon') == 2
+
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ 'tests/data/RGB.byte.tif',
+ '--sequence',
+ '--bbox',
+ '--precision', '2'
+ ])
+ 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
+
+
+def test_bounds_seq_rs():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'bounds',
+ 'tests/data/RGB.byte.tif',
+ 'tests/data/RGB.byte.tif',
+ '--sequence',
+ '--rs',
+ '--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')
+
+
+def test_insp():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'insp',
+ 'tests/data/RGB.byte.tif'
+ ])
+ assert result.exit_code == 0
+
+
+def test_insp_err():
+ runner = CliRunner()
+ result = runner.invoke(main_group, [
+ 'insp',
+ 'tests'
+ ])
+ assert result.exit_code == 1
diff --git a/tests/test_rio_merge.py b/tests/test_rio_merge.py
index 5fa5f91..d492373 100644
--- a/tests/test_rio_merge.py
+++ b/tests/test_rio_merge.py
@@ -329,3 +329,23 @@ def test_merge_tiny_output_opt(tiffs):
assert (data[0][0:2,3] == 90).all()
assert data[0][2][1] == 60
assert data[0][3][0] == 40
+
+
+def test_merge_tiny_res(tiffs):
+ outputname = str(tiffs.join('merged.tif'))
+ inputs = [str(x) for x in tiffs.listdir()]
+ inputs.sort()
+ runner = CliRunner()
+ result = runner.invoke(merge, inputs + [outputname, '--res', 2])
+ assert result.exit_code == 0
+
+ # Output should be
+ # [[[120 90]
+ # [ 0 0]]]
+
+ with rasterio.open(outputname) as src:
+ data = src.read()
+ print(data)
+ assert data[0, 0, 0] == 120
+ assert data[0, 0, 1] == 90
+ assert (data[0, 1, 0:1] == 0).all()
diff --git a/tests/test_rio_overview.py b/tests/test_rio_overview.py
new file mode 100644
index 0000000..718f6d6
--- /dev/null
+++ b/tests/test_rio_overview.py
@@ -0,0 +1,67 @@
+import logging
+import sys
+
+from click.testing import CliRunner
+
+import rasterio
+from rasterio.rio.main import main_group as cli
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_err(data):
+ runner = CliRunner()
+ inputfile = str(data.join('RGB.byte.tif'))
+ result = runner.invoke(cli, ['overview', inputfile, '--build', 'a^2'])
+ assert result.exit_code == 2
+ assert "must match" in result.output
+
+
+def test_ls_none(data):
+ runner = CliRunner()
+ inputfile = str(data.join('RGB.byte.tif'))
+ result = runner.invoke(cli, ['overview', inputfile, '--ls'])
+ assert result.exit_code == 0
+ expected = "Overview factors:\n Band 1: None (method: 'unknown')\n Band 2: None (method: 'unknown')\n Band 3: None (method: 'unknown')\n"
+ assert result.output == expected
+
+
+def test_build_ls(data):
+ runner = CliRunner()
+ inputfile = str(data.join('RGB.byte.tif'))
+ result = runner.invoke(cli, ['overview', inputfile, '--build', '2,4,8'])
+ assert result.exit_code == 0
+ result = runner.invoke(cli, ['overview', inputfile, '--ls'])
+ assert result.exit_code == 0
+ expected = " Band 1: [2, 4, 8] (method: 'nearest')\n Band 2: [2, 4, 8] (method: 'nearest')\n Band 3: [2, 4, 8] (method: 'nearest')\n"
+ assert result.output.endswith(expected)
+
+
+def test_build_pow_ls(data):
+ runner = CliRunner()
+ inputfile = str(data.join('RGB.byte.tif'))
+ result = runner.invoke(cli, ['overview', inputfile, '--build', '2^1..3'])
+ assert result.exit_code == 0
+ result = runner.invoke(cli, ['overview', inputfile, '--ls'])
+ assert result.exit_code == 0
+ expected = " Band 1: [2, 4, 8] (method: 'nearest')\n Band 2: [2, 4, 8] (method: 'nearest')\n Band 3: [2, 4, 8] (method: 'nearest')\n"
+ assert result.output.endswith(expected)
+
+
+def test_rebuild_ls(data):
+ runner = CliRunner()
+ inputfile = str(data.join('RGB.byte.tif'))
+
+ result = runner.invoke(cli,
+ ['overview', inputfile, '--build', '2,4,8', '--resampling', 'cubic'])
+ assert result.exit_code == 0
+
+ result = runner.invoke(cli, ['overview', inputfile, '--rebuild'])
+ assert result.exit_code == 0
+
+ result = runner.invoke(cli, ['overview', inputfile, '--ls'])
+ assert result.exit_code == 0
+
+ expected = " Band 1: [2, 4, 8] (method: 'cubic')\n Band 2: [2, 4, 8] (method: 'cubic')\n Band 3: [2, 4, 8] (method: 'cubic')\n"
+ assert result.output.endswith(expected)
diff --git a/tests/test_rio_rio.py b/tests/test_rio_rio.py
deleted file mode 100644
index b8e7ef3..0000000
--- a/tests/test_rio_rio.py
+++ /dev/null
@@ -1,182 +0,0 @@
-import click
-from click.testing import CliRunner
-
-
-import rasterio
-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():
- runner = CliRunner()
- result = runner.invoke(
- rio.insp,
- ['tests/data/RGB.byte.tif'])
- assert result.exit_code == 0
-
-
-def test_insp_err():
- runner = CliRunner()
- result = runner.invoke(
- rio.insp,
- ['tests'])
- assert result.exit_code == 1
-
-
-def test_bounds_defaults():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif'])
- assert result.exit_code == 0
- assert 'FeatureCollection' in result.output
-
-
-def test_bounds_err():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests'])
- 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(
- rio.bounds,
- ['tests/data/RGB.byte.tif', '--bbox', '--precision', '2'])
- assert result.exit_code == 0
- assert result.output.strip() == '[-78.9, 23.56, -76.6, 25.55]'
-
-
-def test_bounds_compact():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif', '--bbox', '--precision', '2', '--compact'])
- assert result.exit_code == 0
- assert result.output.strip() == '[-78.9,23.56,-76.6,25.55]'
-
-
-def test_bounds_indent():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif', '--bbox', '--indent', '2', '--precision', '2'])
- assert result.exit_code == 0
- assert len(result.output.split('\n')) == 7
-
-
-def test_bounds_obj_bbox_mercator():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif', '--bbox', '--mercator', '--precision', '3'])
- assert result.exit_code == 0
- assert result.output.strip() == '[-8782900.033, 2700489.278, -8527010.472, 2943560.235]'
-
-
-def test_bounds_obj_bbox_projected():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif', '--bbox', '--projected', '--precision', '3'])
- assert result.exit_code == 0
- assert result.output.strip() == '[101985.0, 2611485.0, 339315.0, 2826915.0]'
-
-
-def test_bounds_seq():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--sequence'])
- assert result.exit_code == 0
- assert result.output.count('Polygon') == 2
-
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--sequence', '--bbox', '--precision', '2'])
- 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
-
-
-def test_bounds_seq_rs():
- runner = CliRunner()
- result = runner.invoke(
- rio.bounds,
- ['tests/data/RGB.byte.tif', 'tests/data/RGB.byte.tif', '--sequence', '--rs', '--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'
-
-
-def test_transform_err():
- runner = CliRunner()
- result = runner.invoke(
- rio.transform,
- [], "[-78.0]")
- assert result.exit_code == 1
-
-
-def test_transform_point():
- runner = CliRunner()
- result = runner.invoke(
- rio.transform,
- ['--dst-crs', 'EPSG:32618', '--precision', '2'],
- "[-78.0, 23.0]", catch_exceptions=False)
- assert result.exit_code == 0
- assert result.output.strip() == '[192457.13, 2546667.68]'
-
-
-def test_transform_point_dst_file():
- runner = CliRunner()
- result = runner.invoke(
- rio.transform,
- ['--dst-crs', 'tests/data/RGB.byte.tif', '--precision', '2'],
- "[-78.0, 23.0]")
- assert result.exit_code == 0
- assert result.output.strip() == '[192457.13, 2546667.68]'
-
-
-def test_transform_point_src_file():
- runner = CliRunner()
- result = runner.invoke(
- rio.transform,
- ['--src-crs', 'tests/data/RGB.byte.tif', '--precision', '2'],
- "[192457.13, 2546667.68]")
- assert result.exit_code == 0
- assert result.output.strip() == '[-78.0, 23.0]'
-
-
-def test_transform_point_2():
- runner = CliRunner()
- result = runner.invoke(
- rio.transform,
- ['[-78.0, 23.0]', '--dst-crs', 'EPSG:32618', '--precision', '2'])
- assert result.exit_code == 0
- assert result.output.strip() == '[192457.13, 2546667.68]'
-
-
-def test_transform_point_multi():
- runner = CliRunner()
- result = runner.invoke(
- rio.transform,
- ['--dst-crs', 'EPSG:32618', '--precision', '2'],
- "[-78.0, 23.0]\n[-78.0, 23.0]", catch_exceptions=False)
- assert result.exit_code == 0
- assert result.output.strip() == '[192457.13, 2546667.68]\n[192457.13, 2546667.68]'
diff --git a/tests/test_rio_warp.py b/tests/test_rio_warp.py
new file mode 100644
index 0000000..b5a57fc
--- /dev/null
+++ b/tests/test_rio_warp.py
@@ -0,0 +1,238 @@
+import logging
+import os
+import re
+import sys
+import numpy
+
+import rasterio
+from rasterio.rio import warp
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_warp_no_reproject(runner, tmpdir):
+ """ When called without parameters, output should be same as source """
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.count == src.count
+ assert output.crs == src.crs
+ assert output.nodata == src.nodata
+ assert numpy.allclose(output.bounds, src.bounds)
+ assert output.affine.almost_equals(src.affine)
+ assert numpy.allclose(output.read(1), src.read(1))
+
+
+def test_warp_no_reproject_dimensions(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dimensions', '100', '100'])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.crs == src.crs
+ assert output.width == 100
+ assert output.height == 100
+ assert numpy.allclose([97.839396, 97.839396],
+ [output.affine.a, -output.affine.e])
+
+
+def test_warp_no_reproject_res(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--res', 30])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.crs == src.crs
+ assert numpy.allclose([30, 30], [output.affine.a, -output.affine.e])
+ assert output.width == 327
+ assert output.height == 327
+
+
+def test_warp_no_reproject_bounds(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ out_bounds = [-11850000, 4810000, -11849000, 4812000]
+ result = runner.invoke(warp.warp,[srcname, outputname,
+ '--bounds'] + out_bounds)
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.crs == src.crs
+ assert numpy.allclose(output.bounds, out_bounds)
+ assert numpy.allclose([src.affine.a, src.affine.e],
+ [output.affine.a, output.affine.e])
+ assert output.width == 105
+ assert output.height == 210
+
+
+def test_warp_no_reproject_bounds_res(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ out_bounds = [-11850000, 4810000, -11849000, 4812000]
+ result = runner.invoke(warp.warp,[srcname, outputname,
+ '--res', 30,
+ '--bounds', ] + out_bounds)
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.crs == src.crs
+ assert numpy.allclose(output.bounds, out_bounds)
+ assert numpy.allclose([30, 30], [output.affine.a, -output.affine.e])
+ assert output.width == 34
+ assert output.height == 67
+
+
+def test_warp_reproject_dst_crs(runner, tmpdir):
+ srcname = 'tests/data/RGB.byte.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dst-crs', 'EPSG:4326'])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.count == src.count
+ assert output.crs == {'init': 'epsg:4326'}
+ assert output.width == 824
+ assert output.height == 686
+ assert numpy.allclose(output.bounds,
+ [-78.95864996545055, 23.564424693996177,
+ -76.57259451863895, 25.550873767433984])
+
+def test_warp_reproject_dst_crs_error(runner, tmpdir):
+ srcname = 'tests/data/RGB.byte.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dst-crs', '{foo: bar}'])
+ assert result.exit_code == 2
+ assert 'invalid crs format' in result.output
+
+
+def test_warp_reproject_dst_crs_proj4(runner, tmpdir):
+ proj4 = '+proj=longlat +ellps=WGS84 +datum=WGS84'
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dst-crs', proj4])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(outputname) as output:
+ assert output.crs == {'init': 'epsg:4326'} # rasterio converts to EPSG
+
+
+def test_warp_reproject_res(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dst-crs', 'EPSG:4326',
+ '--res', 0.01])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(outputname) as output:
+ assert output.crs == {'init': 'epsg:4326'}
+ assert numpy.allclose([0.01, 0.01], [output.affine.a, -output.affine.e])
+ assert output.width == 9
+ assert output.height == 7
+
+
+def test_warp_reproject_dimensions(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dst-crs', 'EPSG:4326',
+ '--dimensions', '100', '100'])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.crs == {'init': 'epsg:4326'}
+ assert output.width == 100
+ assert output.height == 100
+ assert numpy.allclose([0.0008789062498762235, 0.0006771676143921468],
+ [output.affine.a, -output.affine.e])
+
+
+def test_warp_reproject_bounds_no_res(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ out_bounds = [-11850000, 4810000, -11849000, 4812000]
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dst-crs', 'EPSG:4326',
+ '--bounds', ] + out_bounds)
+ assert result.exit_code == 2
+
+
+def test_warp_reproject_bounds_res(runner, tmpdir):
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ out_bounds = [-11850000, 4810000, -11849000, 4812000]
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--dst-crs', 'EPSG:4326',
+ '--res', 0.001, '--bounds', ]
+ + out_bounds)
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(srcname) as src:
+ with rasterio.open(outputname) as output:
+ assert output.crs == {'init': 'epsg:4326'}
+ assert numpy.allclose(output.bounds[:],
+ [-106.45036, 39.6138, -106.44136, 39.6278])
+ assert numpy.allclose([0.001, 0.001],
+ [output.affine.a, -output.affine.e])
+ assert output.width == 9
+ assert output.height == 14
+
+
+def test_warp_reproject_like(runner, tmpdir):
+ likename = str(tmpdir.join('like.tif'))
+ kwargs = {
+ "crs": {'init': 'epsg:4326'},
+ "transform": (-106.523, 0.001, 0, 39.6395, 0, -0.001),
+ "count": 1,
+ "dtype": rasterio.uint8,
+ "driver": "GTiff",
+ "width": 10,
+ "height": 10,
+ "nodata": 0
+ }
+
+ with rasterio.drivers():
+ with rasterio.open(likename, 'w', **kwargs) as dst:
+ data = numpy.zeros((10, 10), dtype=rasterio.uint8)
+ dst.write_band(1, data)
+
+ srcname = 'tests/data/shade.tif'
+ outputname = str(tmpdir.join('test.tif'))
+ result = runner.invoke(warp.warp, [srcname, outputname,
+ '--like', likename])
+ assert result.exit_code == 0
+ assert os.path.exists(outputname)
+
+ with rasterio.open(outputname) as output:
+ assert output.crs == {'init': 'epsg:4326'}
+ assert numpy.allclose([0.001, 0.001], [output.affine.a, -output.affine.e])
+ assert output.width == 10
+ assert output.height == 10
diff --git a/tests/test_sampling.py b/tests/test_sampling.py
index 02eef7e..6d73737 100644
--- a/tests/test_sampling.py
+++ b/tests/test_sampling.py
@@ -6,12 +6,21 @@ def test_sampling():
data = next(src.sample([(220650.0, 2719200.0)]))
assert list(data) == [18, 25, 14]
+
def test_sampling_beyond_bounds():
with rasterio.open('tests/data/RGB.byte.tif') as src:
data = next(src.sample([(-10, 2719200.0)]))
assert list(data) == [0, 0, 0]
+
def test_sampling_indexes():
with rasterio.open('tests/data/RGB.byte.tif') as src:
data = next(src.sample([(220650.0, 2719200.0)], indexes=[2]))
assert list(data) == [25]
+
+
+def test_sampling_type():
+ """See https://github.com/mapbox/rasterio/issues/378."""
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ sampler = src.sample([(220650.0, 2719200.0)], indexes=[2])
+ assert type(sampler)
diff --git a/tests/test_warp.py b/tests/test_warp.py
index 7ba8cb0..d64dfae 100644
--- a/tests/test_warp.py
+++ b/tests/test_warp.py
@@ -30,7 +30,7 @@ class ReprojectParams(object):
with rasterio.drivers():
dt, dw, dh = calculate_default_transform(
- left, bottom, right, top, width, height, src_crs, dst_crs)
+ src_crs, dst_crs, width, height, left, bottom, right, top)
self.dst_transform = dt
self.dst_width = dw
self.dst_height = dh
@@ -68,7 +68,7 @@ def test_transform_bounds():
with rasterio.open('tests/data/RGB.byte.tif') as src:
l, b, r, t = src.bounds
assert numpy.allclose(
- transform_bounds(l, b, r, t, src.crs, {'init': 'EPSG:4326'}),
+ transform_bounds(src.crs, {'init': 'EPSG:4326'}, l, b, r, t),
(
-78.95864996545055, 23.564991210854686,
-76.57492370013823, 25.550873767433984
@@ -83,9 +83,9 @@ def test_transform_bounds_densify():
dst_crs = {'init': 'EPSG:32610'}
assert numpy.allclose(
transform_bounds(
- -120, 40, -80, 64,
src_crs,
dst_crs,
+ -120, 40, -80, 64,
densify_pts=0
),
(
@@ -96,9 +96,9 @@ def test_transform_bounds_densify():
assert numpy.allclose(
transform_bounds(
- -120, 40, -80, 64,
src_crs,
dst_crs,
+ -120, 40, -80, 64,
densify_pts=100
),
(
@@ -114,7 +114,7 @@ def test_transform_bounds_no_change():
with rasterio.open('tests/data/RGB.byte.tif') as src:
l, b, r, t = src.bounds
assert numpy.allclose(
- transform_bounds(l, b, r, t, src.crs, src.crs),
+ transform_bounds(src.crs, src.crs, l, b, r, t),
src.bounds
)
@@ -122,9 +122,9 @@ def test_transform_bounds_no_change():
def test_transform_bounds_densify_out_of_bounds():
with pytest.raises(ValueError):
transform_bounds(
- -120, 40, -80, 64,
{'init': 'EPSG:4326'},
{'init': 'EPSG:32610'},
+ -120, 40, -80, 64,
densify_pts=-10
)
@@ -136,10 +136,9 @@ def test_calculate_default_transform():
)
with rasterio.drivers():
with rasterio.open('tests/data/RGB.byte.tif') as src:
- l, b, r, t = src.bounds
wgs84_crs = {'init': 'EPSG:4326'}
dst_transform, width, height = calculate_default_transform(
- l, b, r, t, src.width, src.height, src.crs, wgs84_crs)
+ src.crs, wgs84_crs, src.width, src.height, *src.bounds)
assert dst_transform.almost_equals(target_transform)
assert width == 824
@@ -156,8 +155,8 @@ def test_calculate_default_transform_single_resolution():
0.0, -target_resolution, 25.550873767433984
)
dst_transform, width, height = calculate_default_transform(
- l, b, r, t, src.width, src.height, src.crs,
- {'init': 'EPSG:4326'}, resolution=target_resolution
+ src.crs, {'init': 'EPSG:4326'}, src.width, src.height,
+ *src.bounds, resolution=target_resolution
)
assert dst_transform.almost_equals(target_transform)
@@ -168,7 +167,6 @@ def test_calculate_default_transform_single_resolution():
def test_calculate_default_transform_multiple_resolutions():
with rasterio.drivers():
with rasterio.open('tests/data/RGB.byte.tif') as src:
- l, b, r, t = src.bounds
target_resolution = (0.2, 0.1)
target_transform = Affine(
target_resolution[0], 0.0, -78.95864996545055,
@@ -176,8 +174,8 @@ def test_calculate_default_transform_multiple_resolutions():
)
dst_transform, width, height = calculate_default_transform(
- l, b, r, t, src.width, src.height, src.crs,
- {'init': 'EPSG:4326'}, resolution=target_resolution
+ src.crs, {'init': 'EPSG:4326'}, src.width, src.height,
+ *src.bounds, resolution=target_resolution
)
assert dst_transform.almost_equals(target_transform)
diff --git a/tests/test_write.py b/tests/test_write.py
index 25f5178..b8a7e20 100644
--- a/tests/test_write.py
+++ b/tests/test_write.py
@@ -148,6 +148,26 @@ def test_write_crs_transform(tmpdir):
# (precision varies slightly by platform)
assert re.search("Pixel Size = \(300.03792\d+,-300.04178\d+\)", info)
+def test_write_crs_transform_affine(tmpdir):
+ name = str(tmpdir.join("test_write_crs_transform.tif"))
+ a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
+ transform = [101985.0, 300.0379266750948, 0.0,
+ 2826915.0, 0.0, -300.041782729805]
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=1,
+ crs={'units': 'm', 'no_defs': True, 'ellps': 'WGS84',
+ 'proj': 'utm', 'zone': 18},
+ affine=transform,
+ dtype=rasterio.ubyte) as s:
+ s.write_band(1, a)
+ assert s.crs == {'init': 'epsg:32618'}
+ info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
+ assert 'PROJCS["UTM Zone 18, Northern Hemisphere",' in info
+ # make sure that pixel size is nearly the same as transform
+ # (precision varies slightly by platform)
+ assert re.search("Pixel Size = \(300.03792\d+,-300.04178\d+\)", info)
+
def test_write_crs_transform_2(tmpdir):
"""Using 'EPSG:32618' as CRS."""
name = str(tmpdir.join("test_write_crs_transform.tif"))
--
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