[rasterio] 01/03: Imported Upstream version 0.15
Johan Van de Wauw
johanvdw-guest at moszumanska.debian.org
Mon Oct 27 21:06:28 UTC 2014
This is an automated email from the git hooks/post-receive script.
johanvdw-guest pushed a commit to branch master
in repository rasterio.
commit 341a0acc1f8ab5fab0d7eeef6edb8a6da61e3322
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date: Sun Oct 26 19:00:12 2014 +0100
Imported Upstream version 0.15
---
.travis.yml | 19 +
AUTHORS.txt | 13 +
CHANGES.txt | 164 ++++
LICENSE.txt | 27 +
MANIFEST.in | 8 +
README.rst | 260 +++++++
benchmarks/ndarray.py | 73 ++
docs/cli.rst | 221 ++++++
docs/colormaps.rst | 47 ++
docs/concurrency.rst | 162 ++++
docs/datasets.rst | 186 +++++
docs/features.rst | 91 +++
docs/georeferencing.rst | 81 ++
docs/masks.rst | 122 +++
docs/options.rst | 22 +
docs/reproject.rst | 64 ++
docs/tags.rst | 90 +++
docs/windowed-rw.rst | 231 ++++++
examples/async-rasterio.py | 100 +++
examples/concurrent-cpu-bound.py | 95 +++
examples/decimate.py | 31 +
examples/features.ipynb | 187 +++++
examples/introduction.ipynb | 393 ++++++++++
examples/polygonize.py | 10 +
examples/rasterio_polygonize.py | 74 ++
examples/rasterize_geometry.py | 27 +
examples/reproject.py | 62 ++
examples/sieve.py | 38 +
examples/total.py | 38 +
rasterio/__init__.py | 164 ++++
rasterio/_base.pxd | 25 +
rasterio/_base.pyx | 611 +++++++++++++++
rasterio/_copy.pyx | 55 ++
rasterio/_drivers.pyx | 120 +++
rasterio/_err.pyx | 70 ++
rasterio/_example.pyx | 24 +
rasterio/_features.pxd | 29 +
rasterio/_features.pyx | 586 +++++++++++++++
rasterio/_gdal.pxd | 215 ++++++
rasterio/_io.pxd | 248 +++++++
rasterio/_io.pyx | 1522 ++++++++++++++++++++++++++++++++++++++
rasterio/_ogr.pxd | 99 +++
rasterio/_warp.pyx | 532 +++++++++++++
rasterio/coords.py | 5 +
rasterio/crs.py | 186 +++++
rasterio/dtypes.py | 98 +++
rasterio/enums.py | 19 +
rasterio/features.py | 318 ++++++++
rasterio/five.py | 15 +
rasterio/rio/__init__.py | 1 +
rasterio/rio/bands.py | 129 ++++
rasterio/rio/cli.py | 83 +++
rasterio/rio/features.py | 156 ++++
rasterio/rio/info.py | 100 +++
rasterio/rio/main.py | 8 +
rasterio/rio/merge.py | 75 ++
rasterio/rio/options.py | 21 +
rasterio/rio/rio.py | 220 ++++++
rasterio/tool.py | 53 ++
rasterio/transform.py | 29 +
rasterio/warp.py | 46 ++
requirements-dev.txt | 7 +
requirements.txt | 5 +
setup.cfg | 8 +
setup.py | 167 +++++
tests/__init__.py | 1 +
tests/conftest.py | 21 +
tests/data/README.rst | 8 +
tests/data/RGB.byte.tif | Bin 0 -> 1713704 bytes
tests/data/float.tif | Bin 0 -> 334 bytes
tests/data/float_nan.tif | Bin 0 -> 600 bytes
tests/data/shade.tif | Bin 0 -> 1050093 bytes
tests/test_band.py | 10 +
tests/test_blocks.py | 123 +++
tests/test_cli.py | 115 +++
tests/test_colorinterp.py | 25 +
tests/test_colormap.py | 33 +
tests/test_coords.py | 34 +
tests/test_copy.py | 26 +
tests/test_crs.py | 66 ++
tests/test_driver_management.py | 47 ++
tests/test_dtypes.py | 14 +
tests/test_features_rasterize.py | 157 ++++
tests/test_features_shapes.py | 110 +++
tests/test_features_sieve.py | 141 ++++
tests/test_indexing.py | 25 +
tests/test_nodata.py | 46 ++
tests/test_pad.py | 15 +
tests/test_png.py | 20 +
tests/test_read.py | 242 ++++++
tests/test_revolvingdoor.py | 37 +
tests/test_rio_bands.py | 79 ++
tests/test_rio_info.py | 57 ++
tests/test_rio_options.py | 15 +
tests/test_rio_rio.py | 170 +++++
tests/test_tags.py | 56 ++
tests/test_transform.py | 11 +
tests/test_update.py | 61 ++
tests/test_warp.py | 184 +++++
tests/test_write.py | 244 ++++++
100 files changed, 11178 insertions(+)
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..7180aed
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+language: python
+python:
+ - "2.7"
+ - "3.3"
+ - "3.4"
+before_install:
+ - sudo add-apt-repository -y ppa:ubuntugis/ppa
+ - sudo apt-get update -qq
+ - sudo apt-get install -y libgdal1h gdal-bin libgdal-dev
+install:
+ - "pip install -r requirements-dev.txt"
+ - "pip install pytest"
+ - "pip install coveralls"
+ - "pip install -e ."
+script:
+ - py.test
+ - coverage run --source=rasterio --omit='*.pxd,*.pyx,*/tests/*,*/docs/*,*/examples/*,*/benchmarks/*' -m py.test
+after_success:
+ - coveralls
diff --git a/AUTHORS.txt b/AUTHORS.txt
new file mode 100644
index 0000000..54f3fca
--- /dev/null
+++ b/AUTHORS.txt
@@ -0,0 +1,13 @@
+Authors
+=======
+
+Sean Gillies <sean at mapbox.com>
+Brendan Ward https://github.com/brendan-ward
+Asger Skovbo Petersen https://github.com/AsgerPetersen
+James Seppi https://github.com/jseppi
+Chrisophe Gohlke https://github.com/cgohlke
+Robin Wilson https://github.com/robintw
+Mike Toews https://github.com/mwtoews
+Amit Kapadia https://github.com/kapadia
+
+See also https://github.com/mapbox/rasterio/graphs/contributors.
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..0482830
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,164 @@
+Changes
+=======
+
+0.15
+----
+- Support for more data types in seive() (#159).
+- Handle unexpected PROJ.4 values like "+no_defs=True" (#173).
+- Support for writing PNG, JPEG, etc using GDALCreateCopy (#177).
+- New rio-stack command (#180).
+- Moved rio CLI main entry point to rasterio/rio/main:cli.
+- Add rio-env command and --version option to rio.
+- Make -f and --format aliases for --driver in CLI options (#183).
+- Remove older rio_* scripts (#184).
+- `out` keyword arg supercedes `output` in rasterio.features (#179).
+
+0.14.1 (2014-10-02)
+-------------------
+- Allow update of nodata values in r+ mode (#167).
+
+0.14 (2014-10-01)
+-----------------
+- Fixed tag update crasher (#145).
+- Add --mask and --bidx options to rio shapes (#150).
+- Faster geometry transforms and antimeridian cutting (#163).
+- Support for more data types in shapes() and rasterize() (#155, #158).
+- Switch to Cython 0.20+ for development (#151).
+
+0.13.2 (2014-09-23)
+-------------------
+- Add enum34 to requirements (#149).
+- Make rasterize() more robust (#146).
+- Pin Cython>=0.20 and Numpy>=1.8 (#151).
+
+0.13.1 (2014-09-13)
+-------------------
+- Read unprojected images with less flailing (#117).
+
+0.13 (2014-09-09)
+-----------------
+- Add single value options to rio info command (#139, #143).
+- Switch to console scripts entry points for rio, &c (#137).
+- Avoid unnecessary imports of Numpy in info command, elsewhere (#140).
+
+0.12.1 (2014-09-02)
+-------------------
+- Add missing rasterio.rio package (#135).
+
+0.12 (2014-09-02)
+-----------------
+- Add --mercator option for rio bounds (#126).
+- Add option for RS as a JSON text sequence separator (#127).
+- Add rio merge command (#131).
+- Change layout of tests (#134).
+
+0.11.1 (2014-08-19)
+-------------------
+- Add --bbox option for rio bounds (#124).
+
+0.11 (2014-08-06)
+-----------------
+- Add rio shapes command (#115).
+- Accept CRS strings like 'EPSG:3857' (#116).
+- Write multiple bands at a time (#95).
+
+0.10.1 (2014-07-21)
+-------------------
+- Numpy.require C-contiguous data when writing bands (#108).
+
+0.10 (2014-07-18)
+-----------------
+- Add rio bounds command (#111).
+- Add rio transform command (#112).
+
+0.9 (2014-07-16)
+----------------
+- Add meta and tag dumping options to rio_insp.
+- Leave GDAL finalization to the DLL's destructor (#91).
+- Add pad() function (#84).
+- New read() method, returns 3D arrays (#83).
+- New affine attribute and AffineMatrix object (#80, #86).
+- Removal of rasterio.insp script (#51).
+- Read_band() is now a special case of read() (#96).
+- Add support for multi-band reprojection (#98).
+- Support for GDAL CInt16 datasets (#97).
+- Fix loss of projection information (#102).
+- Fix for loss of nodata values (#109).
+- Permit other than C-contiguous arrays (#108).
+
+0.8 (2014-03-31)
+----------------
+- Add rasterize(), the inverse of shapes() (#45, #62).
+- Change the sense of mask for shapes(). Masks are always positive in
+ rasterio, so we extract shapes only where mask is True.
+
+0.7.3 (2014-03-22)
+------------------
+- Fix sieve() bug (#57).
+
+0.7.2 (2014-03-20)
+------------------
+- Add rio_insp, deprecation warning in rasterio.insp (#50, #52).
+- Fix transform bug in shapes() (#54).
+
+0.7.1 (2014-03-15)
+------------------
+- Source distribution bug fix (#48).
+
+0.7 (2014-03-14)
+----------------
+- Add a Band object, providing a shortcut for shapes() and sieve() functions
+ (#34).
+- Reprojection of rasters (#12).
+- Enhancements to the rasterio.insp console: module aliases, shortcut for
+ show().
+- Add index() method.
+- Reading and writing of GDAL mask bands (#41).
+- Add rio_cp program.
+- Enable r+ mode for GeoTIFFs (#46).
+
+0.6 (2014-02-10)
+----------------
+- Add support for dataset and band tags (#32).
+- Add testing dependence on pytest (#33).
+- Add support for simple RGBA colormaps (#34).
+- Fix for a crasher that occurs when a file is sent through a write-read
+ revolving door.
+- New docs for tags and colormaps.
+
+0.5.1 (2014-02-02)
+------------------
+- Add mask option to shapes() function (#26).
+- Add rasterio.insp interactive interpreter.
+
+0.5 (2014-01-22)
+----------------
+- Access to shapes of raster features via GDALPolygonize (#20).
+- Raster feature sieving (#21).
+- Registration and de-registration of drivers via context managers (#22).
+
+0.4 (2013-12-19)
+----------------
+- Add nodatavals property (#13).
+- Allow nodata to be set when opening file to write (#17).
+
+0.3 (2013-12-15)
+----------------
+- Drop six dependency (#9)
+- Add crs_wkt attribute (#10).
+- Add bounds attribute and ul() method (#11).
+- Add block_windows property (#7).
+- Enable windowed reads and writes (#6).
+- Use row,column ordering in window tuples as in Numpy (#13).
+- Add documentation on windowed reading and writing.
+
+0.2 (2013-11-24)
+----------------
+- Band indexes start at 1 (#2).
+- Decimation or replication of pixels on read and write (#3).
+- Add rasterio.copy() (#5).
+
+0.1 (2013-11-07)
+----------------
+- Reading and writing of GeoTIFFs, with examples.
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..89d3493
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2013, MapBox
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the <organization> nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..5fa402e
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+exclude *.rst *.txt *.py
+include CHANGES.txt AUTHORS.txt LICENSE.txt VERSION.txt README.rst setup.py
+recursive-include examples *.py
+recursive-include tests *.py *.rst
+recursive-exclude tests/data *.tif
+recursive-include tests/data *.txt
+recursive-include docs *.rst
+exclude MANIFEST.in
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..067dc11
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,260 @@
+========
+Rasterio
+========
+
+Rasterio reads and writes geospatial raster datasets.
+
+.. image:: https://travis-ci.org/mapbox/rasterio.png?branch=master
+ :target: https://travis-ci.org/mapbox/rasterio
+
+.. image:: https://coveralls.io/repos/mapbox/rasterio/badge.png
+ :target: https://coveralls.io/r/mapbox/rasterio
+
+Rasterio employs GDAL under the hood for file I/O and raster formatting. Its
+functions typically accept and return Numpy ndarrays. Rasterio is designed to
+make working with geospatial raster data more productive and more fun.
+
+Rasterio is pronounced raw-STIER-ee-oh.
+
+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.
+
+.. 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):
+
+ # 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.
+
+ total = numpy.zeros(r.shape, dtype=rasterio.uint16)
+ 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
+ # dtype to uint8, and specify LZW compression.
+
+ kwargs = src.meta
+ kwargs.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))
+
+ # 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'])
+
+.. 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.open('tests/data/RGB.byte.tif') as src:
+ print(src.width, src.height)
+ print(src.crs)
+ print(src.affine)
+ print(src.count)
+ print(src.indexes)
+
+ # Output:
+ # (791, 718)
+ # {u'units': u'm', u'no_defs': True, u'ellps': u'WGS84', u'proj': u'utm', u'zone': 18}
+ # Affine(300.0379266750948, 0.0, 101985.0,
+ # 0.0, -300.041782729805, 2826915.0)
+ # 3
+ # [1, 2, 3]
+
+Rasterio also affords conversion of GeoTIFFs to other formats.
+
+.. code-block:: python
+
+ with rasterio.drivers():
+
+ rasterio.copy(
+ 'example-total.tif',
+ 'example-total.jpg',
+ driver='JPEG')
+
+ subprocess.call(['open', 'example-total.jpg'])
+
+Rasterio CLI
+============
+
+Rasterio's command line interface, named "rio", is documented at `cli.rst
+<https://github.com/mapbox/rasterio/blob/master/docs/cli.rst>`__. Its ``rio
+insp`` command opens the hood of any raster dataset so you can poke around
+using Python.
+
+.. code-block:: pycon
+
+ $ rio insp tests/data/RGB.byte.tif
+ Rasterio 0.10 Interactive Inspector (Python 3.4.1)
+ Type "src.meta", "src.read_band(1)", or "help(src)" for more information.
+ >>> src.name
+ 'tests/data/RGB.byte.tif'
+ >>> src.closed
+ False
+ >>> src.shape
+ (718, 791)
+ >>> src.crs
+ {'init': 'epsg:32618'}
+ >>> b, g, r = src.read()
+ >>> b
+ masked_array(data =
+ [[-- -- -- ..., -- -- --]
+ [-- -- -- ..., -- -- --]
+ [-- -- -- ..., -- -- --]
+ ...,
+ [-- -- -- ..., -- -- --]
+ [-- -- -- ..., -- -- --]
+ [-- -- -- ..., -- -- --]],
+ mask =
+ [[ True True True ..., True True True]
+ [ True True True ..., True True True]
+ [ True True True ..., True True True]
+ ...,
+ [ True True True ..., True True True]
+ [ True True True ..., True True True]
+ [ True True True ..., True True True]],
+ fill_value = 0)
+
+ >>> b.min(), b.max(), b.mean()
+ (1, 255, 44.434478650699106)
+
+Dependencies
+============
+
+C library dependecies:
+
+- GDAL 1.9+
+
+Python package dependencies (see also requirements.txt):
+
+- affine
+- Numpy
+- setuptools
+
+Development also requires (see requirements-dev.txt)
+
+- Cython
+- pytest
+
+Installation
+============
+
+Rasterio is a C extension and to install on Linux or OS X you'll need a working
+compiler (XCode on OS X etc). You'll also need Numpy preinstalled; the Numpy
+headers are required to run the rasterio setup script. Numpy has to be
+installed (via the indicated requirements file) before rasterio can be
+installed. See rasterio's Travis `configuration
+<https://github.com/mapbox/rasterio/blob/master/.travis.yml>`__ for more
+guidance.
+
+
+Linux
+-----
+
+The following commands are adapted from Rasterio's Travis-CI configuration.
+
+.. code-block:: console
+
+ $ sudo add-apt-repository ppa:ubuntugis/ppa
+ $ sudo apt-get update -qq
+ $ sudo apt-get install python-numpy libgdal1h gdal-bin libgdal-dev
+ $ pip install -r https://raw.githubusercontent.com/mapbox/rasterio/master/requirements.txt
+ $ pip install rasterio
+
+Adapt them as necessary for your Linux system.
+
+OS X
+----
+
+Wheels are available on PyPI for Homebrew based Python environments.
+
+.. code-block:: console
+
+ $ brew install gdal
+ $ pip install -r https://raw.githubusercontent.com/mapbox/rasterio/master/requirements.txt
+ $ pip install rasterio
+
+The wheels are incompatible with MacPorts. MacPorts users will need to specify
+a source installation instead: ``pip install --no-use-wheel``.
+
+Windows
+-------
+
+Windows binary packages created by Christoph Gohlke are available `here
+<http://www.lfd.uci.edu/~gohlke/pythonlibs/#rasterio>`_.
+
+Testing
+-------
+
+From the repo directory, run py.test
+
+.. code-block:: console
+
+ $ py.test
+
+Documentation
+-------------
+
+See https://github.com/mapbox/rasterio/tree/master/docs.
+
+License
+-------
+
+See LICENSE.txt
+
+Authors
+-------
+
+See AUTHORS.txt
+
+Changes
+-------
+
+See CHANGES.txt
+
diff --git a/benchmarks/ndarray.py b/benchmarks/ndarray.py
new file mode 100644
index 0000000..b898138
--- /dev/null
+++ b/benchmarks/ndarray.py
@@ -0,0 +1,73 @@
+# Benchmark for read of raster data to ndarray
+
+import timeit
+
+import rasterio
+from osgeo import gdal
+
+# GDAL
+s = """
+src = gdal.Open('rasterio/tests/data/RGB.byte.tif')
+arr = src.GetRasterBand(1).ReadAsArray()
+src = None
+"""
+
+n = 100
+
+t = timeit.timeit(s, setup='from osgeo import gdal', number=n)
+print("GDAL:")
+print("%f msec\n" % (1000*t/n))
+
+# Rasterio
+s = """
+with rasterio.open('rasterio/tests/data/RGB.byte.tif') as src:
+ arr = src.read_band(1)
+"""
+
+t = timeit.timeit(s, setup='import rasterio', number=n)
+print("Rasterio:")
+print("%f msec\n" % (1000*t/n))
+
+# GDAL Extras
+s = """
+src = gdal.Open('rasterio/tests/data/RGB.byte.tif')
+transform = src.GetGeoTransform()
+srs = osr.SpatialReference()
+srs.ImportFromWkt(src.GetProjectionRef())
+wkt = srs.ExportToWkt()
+proj = srs.ExportToProj4()
+arr = src.GetRasterBand(1).ReadAsArray()
+src = None
+"""
+
+n = 1000
+
+t = timeit.timeit(s, setup='from osgeo import gdal; from osgeo import osr', number=n)
+print("GDAL + Extras:\n")
+print("%f usec\n" % (t/n))
+
+# Rasterio
+s = """
+with rasterio.open('rasterio/tests/data/RGB.byte.tif') as src:
+ transform = src.transform
+ proj = src.crs
+ wkt = src.crs_wkt
+ arr = src.read_band(1)
+"""
+
+t = timeit.timeit(s, setup='import rasterio', number=n)
+print("Rasterio:\n")
+print("%f usec\n" % (t/n))
+
+
+import pstats, cProfile
+
+s = """
+with rasterio.open('rasterio/tests/data/RGB.byte.tif') as src:
+ arr = src.read_band(1, window=(10, 10, 10, 10))
+"""
+
+cProfile.runctx(s, globals(), locals(), "Profile.prof")
+
+s = pstats.Stats("Profile.prof")
+s.strip_dirs().sort_stats("time").print_stats()
diff --git a/docs/cli.rst b/docs/cli.rst
new file mode 100644
index 0000000..a31e0ff
--- /dev/null
+++ b/docs/cli.rst
@@ -0,0 +1,221 @@
+Command Line Interface
+======================
+
+Rasterio's new command line interface is a program named "rio".
+
+.. code-block:: console
+
+ $ rio
+ Usage: rio [OPTIONS] COMMAND [ARGS]...
+
+ Rasterio command line interface.
+
+ Options:
+ -v, --verbose Increase verbosity.
+ -q, --quiet Decrease verbosity.
+ --help Show this message and exit.
+
+ Commands:
+ bounds Write bounding boxes to stdout as GeoJSON.
+ info Print information about a data file.
+ insp Open a data file and start an interpreter.
+ merge Merge a stack of raster datasets.
+ shapes Write the shapes of features.
+ stack Stack a number of bands into a multiband dataset.
+ transform Transform coordinates.
+
+It is developed using the ``click`` package.
+
+
+bounds
+------
+
+New in 0.10.
+
+The bounds command writes the bounding boxes of raster datasets to GeoJSON for
+use with, e.g., `geojsonio-cli <https://github.com/mapbox/geojsonio-cli>`__.
+
+.. code-block:: console
+
+ $ rio bounds tests/data/RGB.byte.tif --indent 2
+ {
+ "features": [
+ {
+ "geometry": {
+ "coordinates": [
+ [
+ [
+ -78.898133,
+ 23.564991
+ ],
+ [
+ -76.599438,
+ 23.564991
+ ],
+ [
+ -76.599438,
+ 25.550874
+ ],
+ [
+ -78.898133,
+ 25.550874
+ ],
+ [
+ -78.898133,
+ 23.564991
+ ]
+ ]
+ ],
+ "type": "Polygon"
+ },
+ "properties": {
+ "id": "0",
+ "title": "tests/data/RGB.byte.tif"
+ },
+ "type": "Feature"
+ }
+ ],
+ "type": "FeatureCollection"
+ }
+
+Shoot the GeoJSON into a Leaflet map using geojsonio-cli by typing
+``rio bounds tests/data/RGB.byte.tif | geojsonio``.
+
+info
+----
+
+Rio's info command intends to serve some of the same uses as gdalinfo.
+
+.. code-block:: console
+
+ $ rio info tests/data/RGB.byte.tif
+ { 'affine': Affine(300.0379266750948, 0.0, 101985.0,
+ 0.0, -300.041782729805, 2826915.0),
+ 'count': 3,
+ 'crs': { 'init': u'epsg:32618'},
+ 'driver': u'GTiff',
+ 'dtype': <type 'numpy.uint8'>,
+ 'height': 718,
+ 'nodata': 0.0,
+ 'transform': ( 101985.0,
+ 300.0379266750948,
+ 0.0,
+ 2826915.0,
+ 0.0,
+ -300.041782729805),
+ 'width': 791}
+
+insp
+----
+
+The insp command opens a dataset and an interpreter.
+
+.. code-block:: console
+
+ $ rio insp tests/data/RGB.byte.tif
+ Rasterio 0.9 Interactive Inspector (Python 2.7.5)
+ Type "src.meta", "src.read_band(1)", or "help(src)" for more information.
+ >>> import pprint
+ >>> pprint.pprint(src.meta)
+ {'affine': Affine(300.0379266750948, 0.0, 101985.0,
+ 0.0, -300.041782729805, 2826915.0),
+ 'count': 3,
+ 'crs': {'init': u'epsg:32618'},
+ 'driver': u'GTiff',
+ 'dtype': <type 'numpy.uint8'>,
+ 'height': 718,
+ 'nodata': 0.0,
+ 'transform': (101985.0,
+ 300.0379266750948,
+ 0.0,
+ 2826915.0,
+ 0.0,
+ -300.041782729805),
+ 'width': 791}
+
+merge
+-----
+
+The merge command can be used to flatten a stack of identically layed out
+datasets.
+
+.. code-block:: console
+
+ $ rio merge rasterio/tests/data/R*.tif -o result.tif
+
+shapes
+------
+
+New in 0.11.
+
+The shapes command extracts and writes features of a specified dataset band out
+as GeoJSON.
+
+.. code-block:: console
+
+ $ rio shapes tests/data/shade.tif --bidx 1 --precision 6 > shade.geojson
+
+The resulting file, uploaded to Mapbox, looks like this: `sgillies.j1ho338j <https://a.tiles.mapbox.com/v4/sgillies.j1ho338j/page.html?access_token=pk.eyJ1Ijoic2dpbGxpZXMiLCJhIjoiWUE2VlZVcyJ9.OITHkb1GHNh9nvzIfUc9QQ#13/39.6079/-106.4822>`__.
+
+Using the ``--mask`` option you can write out the shapes of a dataset's valid
+data region.
+
+.. code-block:: console
+
+ $ rio shapes --mask --precision 6 tests/data/RGB.byte.tif | geojsonio
+
+See http://bl.ocks.org/anonymous/raw/ef244954b719dba97926/.
+
+stack
+-----
+
+New in 0.15.
+
+The rio-stack command stack a number of bands from one or more input files into
+a multiband dataset. Input datasets must be of a kind: same data type,
+dimensions, etc. The output is cloned from the first input. By default,
+rio-stack will take all bands from each input and write them in same order to
+the output. Optionally, bands for each input may be specified using a simple
+syntax:
+
+- ``--bidx N`` takes the Nth band from the input (first band is 1).
+- ``--bidx M,N,O`` takes bands M, N, and O.
+- ``--bidx M..O`` takes bands M-O, inclusive.
+- ``--bidx ..N`` takes all bands up to and including N.
+- ``--bidx N..`` takes all bands from N to the end.
+
+Examples using the Rasterio testing dataset that produce a copy of it.
+
+.. code-block:: console
+
+ $ rio stack RGB.byte.tif -o stacked.tif
+ $ rio stack RGB.byte.tif --bidx 1,2,3 -o stacked.tif
+ $ rio stack RGB.byte.tif --bidx 1..3 -o stacked.tif
+ $ rio stack RGB.byte.tif --bidx ..2 RGB.byte.tif --bidx 3.. -o stacked.tif
+
+transform
+---------
+
+New in 0.10.
+
+The transform command reads a JSON array of coordinates, interleaved, and
+writes another array of transformed coordinates to stdout.
+
+To transform a longitude, latitude point (EPSG:4326 is the default) to
+another coordinate system with 2 decimal places of output precision, do the
+following.
+
+.. code-block:: console
+
+ $ echo "[-78.0, 23.0]" | rio transform - --dst_crs EPSG:32618 --precision 2
+ [192457.13, 2546667.68]
+
+To transform a longitude, latitude bounding box to the coordinate system of
+a raster dataset, do the following.
+
+.. code-block:: console
+
+ $ 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]
+
+Suggestions for other commands are welcome!
diff --git a/docs/colormaps.rst b/docs/colormaps.rst
new file mode 100644
index 0000000..45af887
--- /dev/null
+++ b/docs/colormaps.rst
@@ -0,0 +1,47 @@
+Colormaps
+=========
+
+Writing colormaps
+-----------------
+
+Mappings from 8-bit (rasterio.uint8) pixel values to RGBA values can be attached
+to bands using the ``write_colormap()`` method.
+
+.. code-block:: python
+
+ import rasterio
+
+ with rasterio.drivers():
+
+ with rasterio.open('tests/data/shade.tif') as src:
+ shade = src.read_band(1)
+ meta = src.meta
+
+ with rasterio.open('/tmp/colormap.tif', 'w', **meta) as dst:
+ dst.write_band(1, shade)
+ dst.write_colormap(
+ 1, {
+ 0: (255, 0, 0, 255),
+ 255: (0, 0, 255, 255) })
+ cmap = dst.colormap(1)
+ # True
+ assert cmap[0] == (255, 0, 0, 255)
+ # True
+ assert cmap[255] == (0, 0, 255, 255)
+
+ subprocess.call(['open', '/tmp/colormap.tif'])
+
+The program above (on OS X, another viewer is needed with a different OS)
+yields the image below:
+
+.. image:: http://farm8.staticflickr.com/7391/12443115173_80ecca89db_d.jpg
+ :width: 500
+ :height: 500
+
+Reading colormaps
+-----------------
+
+As shown above, the ``colormap()`` returns a dict holding the colormap for the
+given band index. For TIFF format files, the colormap will have 256 items, and
+all but two of those would map to (0, 0, 0, 0) in the example above.
+
diff --git a/docs/concurrency.rst b/docs/concurrency.rst
new file mode 100644
index 0000000..3d43859
--- /dev/null
+++ b/docs/concurrency.rst
@@ -0,0 +1,162 @@
+Concurrent processing
+=====================
+
+Rasterio affords concurrent processing of raster data. The Python GIL is
+released when calling GDAL's ``GDALRasterIO()`` function, which means that
+datasets can read and write concurrently with other threads.
+
+The Numpy library also releases the GIL as much as it can, e.g., in applying
+universal functions to arrays, and this makes it possible to distribute
+processing of an array across cores of a processor. The Cython function below,
+included in Rasterio's ``_example`` module, simulates such a GIL-releasing
+raster processing function.
+
+.. code-block:: python
+
+ import numpy
+ cimport numpy
+
+ def compute(
+ unsigned char[:, :, :] input,
+ unsigned char[:, :, :] output):
+ # Given input and output uint8 arrays, fakes an CPU-intensive
+ # computation.
+ cdef int I, J, K
+ cdef int i, j, k, l
+ cdef double val
+ I = input.shape[0]
+ J = input.shape[1]
+ K = input.shape[2]
+ with nogil:
+ for i in range(I):
+ for j in range(J):
+ for k in range(K):
+ val = <double>input[i, j, k]
+ for l in range(2000):
+ val += 1.0
+ val -= 2000.0
+ output[~i, j, k] = <unsigned char>val
+
+
+Here is the program in examples/concurrent-cpu-bound.py.
+
+.. code-block:: python
+
+ """concurrent-cpu-bound.py
+
+ Operate on a raster dataset window-by-window using a ThreadPoolExecutor.
+
+ Simulates a CPU-bound thread situation where multiple threads can improve performance.
+
+ With -j 4, the program returns in about 1/4 the time as with -j 1.
+ """
+
+ import concurrent.futures
+ import multiprocessing
+ import time
+
+ import numpy
+ import rasterio
+ from rasterio._example import compute
+
+ def main(infile, outfile, num_workers=4):
+
+ with rasterio.drivers():
+
+ # Open the source dataset.
+ with rasterio.open(infile) as src:
+
+ # Create a destination dataset based on source params.
+ # The destination will be tiled, and we'll "process" the tiles
+ # concurrently.
+ meta = src.meta
+ del meta['transform']
+ meta.update(affine=src.affine)
+ meta.update(blockxsize=256, blockysize=256, tiled='yes')
+ with rasterio.open(outfile, 'w', **meta) as dst:
+
+ # Define a generator for data, window pairs.
+ # We use the new read() method here to a 3D array with all
+ # bands, but could also use read_band().
+ def jobs():
+ for ij, window in dst.block_windows():
+ data = src.read(window=window)
+ result = numpy.zeros(data.shape, dtype=data.dtype)
+ yield data, result, window
+
+ # Submit the jobs to the thread pool executor.
+ with concurrent.futures.ThreadPoolExecutor(
+ max_workers=num_workers) as executor:
+
+ # Map the futures returned from executor.submit()
+ # to their destination windows.
+ #
+ # The _example.compute function modifies no Python
+ # objects and releases the GIL. It can execute
+ # concurrently.
+ future_to_window = {
+ executor.submit(compute, data, res): (res, window)
+ for data, res, window in jobs()}
+
+ # As the processing jobs are completed, get the
+ # results and write the data to the appropriate
+ # destination window.
+ for future in concurrent.futures.as_completed(
+ future_to_window):
+
+ result, window = future_to_window[future]
+
+ # Since there's no multiband write() method yet in
+ # Rasterio, we use write_band for each part of the
+ # 3D data array.
+ for i, arr in enumerate(result, 1):
+ dst.write_band(i, arr, window=window)
+
+
+ if __name__ == '__main__':
+
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="Concurrent raster processing demo")
+ parser.add_argument(
+ 'input',
+ metavar='INPUT',
+ help="Input file name")
+ parser.add_argument(
+ 'output',
+ metavar='OUTPUT',
+ help="Output file name")
+ parser.add_argument(
+ '-j',
+ metavar='NUM_JOBS',
+ type=int,
+ default=multiprocessing.cpu_count(),
+ help="Number of concurrent jobs")
+ args = parser.parse_args()
+
+ main(args.input, args.output, args.j)
+
+The code above simulates a fairly CPU-intensive process that runs faster when
+spread over multiple cores using the ``ThreadPoolExecutor`` from Python 3's
+``concurrent.futures`` module. Compared to the case of one concurrent job
+(``-j 1``)
+
+.. code-block:: console
+
+ $ time python examples/concurrent-cpu-bound.py tests/data/RGB.byte.tif /tmp/threads.tif -j 1
+
+ real 0m3.474s
+ user 0m3.426s
+ sys 0m0.043s
+
+we get an almost 3x speed up with four concurrent jobs.
+
+.. code-block:: console
+
+ $ time python examples/concurrent-cpu-bound.py tests/data/RGB.byte.tif /tmp/threads.tif -j 4
+
+ real 0m1.335s
+ user 0m3.400s
+ sys 0m0.043s
+
diff --git a/docs/datasets.rst b/docs/datasets.rst
new file mode 100644
index 0000000..e9d097a
--- /dev/null
+++ b/docs/datasets.rst
@@ -0,0 +1,186 @@
+Datasets and ndarrays
+=====================
+
+Dataset objects provide read, read-write, and write access to raster data files
+and are obtained by calling ``rasterio.open()``. That function mimics Python's
+built-in ``open()`` and the dataset objects it returns mimic Python ``file``
+objects.
+
+.. code-block:: pycon
+
+ >>> import rasterio
+ >>> dataset = rasterio.open('tests/data/RGB.byte.tif')
+ >>> dataset
+ <open RasterReader name='tests/data/RGB.byte.tif' mode='r'>
+ >>> dataset.name
+ 'tests/data/RGB.byte.tif'
+ >>> dataset.mode
+ r
+ >>> dataset.closed
+ False
+
+If you attempt to access a nonexistent path, ``rasterio.open()`` does the same
+thing as ``open()``, raising an exception immediately.
+
+.. code-block:: pycon
+
+ >>> open('/lol/wut.tif')
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ IOError: [Errno 2] No such file or directory: '/lol/wut.tif'
+ >>> rasterio.open('/lol/wut.tif')
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ IOError: no such file or directory: '/lol/wut.tif'
+
+Attributes
+----------
+
+In addition to the file-like attributes shown above, a dataset has a number
+of other read-only attributes that help explain its role in spatial information
+systems. The ``driver`` attribute gives you the name of the GDAL format
+driver used. The ``height`` and ``width`` are the number of rows and columns of
+the raster dataset and ``shape`` is a ``height, width`` tuple as used by
+Numpy. The ``count`` attribute tells you the number of bands in the dataset.
+
+.. code-block:: pycon
+
+ >>> dataset.driver
+ u'GTiff'
+ >>> dataset.height, dataset.width
+ (718, 791)
+ >>> dataset.shape
+ (718, 791)
+ >>> dataset.count
+ 3
+
+What makes geospatial raster datasets different from other raster files is
+that their pixels map to regions of the Earth. A dataset has a coordinate
+reference system and an affine transformation matrix that maps pixel
+coordinates to coordinates in that reference system.
+
+.. code-block:: pycon
+
+ >>> dataset.crs
+ {u'units': u'm', u'no_defs': True, u'ellps': u'WGS84', u'proj': u'utm', u'zone': 18}
+ >>> dataset.affine
+ Affine(300.0379266750948, 0.0, 101985.0,
+ 0.0, -300.041782729805, 2826915.0)
+
+To get the ``x, y`` world coordinates for the upper left corner of any pixel,
+take the product of the affine transformation matrix and the tuple ``(col,
+row)``.
+
+.. code-block:: pycon
+
+ >>> col, row = 0, 0
+ >>> src.affine * (col, row)
+ (101985.0, 2826915.0)
+ >>> col, row = src.width, src.height
+ >>> src.affine * (col, row)
+ (339315.0, 2611485.0)
+
+Reading data
+------------
+
+Datasets generally have one or more bands (or layers). Following the GDAL
+convention, these are indexed starting with the number 1. The first band of
+a file can be read like this:
+
+.. code-block:: pycon
+
+ >>> dataset.read_band(1)
+ array([[0, 0, 0, ..., 0, 0, 0],
+ [0, 0, 0, ..., 0, 0, 0],
+ [0, 0, 0, ..., 0, 0, 0],
+ ...,
+ [0, 0, 0, ..., 0, 0, 0],
+ [0, 0, 0, ..., 0, 0, 0],
+ [0, 0, 0, ..., 0, 0, 0]], dtype=uint8)
+
+The returned object is a 2-dimensional Numpy ndarray. The representation of
+that array at the Python prompt is just a summary; the GeoTIFF file that
+Rasterio uses for testing has 0 values in the corners, but has nonzero values
+elsewhere.
+
+.. code-block::
+
+ >>> from matplotlib import pyplot
+ >>> pyplot.imshow(dataset.read_band(1), cmap='pink')
+ <matplotlib.image.AxesImage object at 0x111195c10>
+ >>> pyplot.show()
+
+.. image:: http://farm6.staticflickr.com/5032/13938576006_b99b23271b_o_d.png
+
+The indexes, Numpy data types, and nodata values of all a dataset's bands can
+be had from its ``indexes``, ``dtypes``, and ``nodatavals`` attributes.
+
+.. code-block:: pycon
+
+ >>> for i, dtype, ndval in zip(src.indexes, src.dtypes, src.nodatavals):
+ ... print i, dtype, nodataval
+ ...
+ 1 <type 'numpy.uint8'> 0.0
+ 2 <type 'numpy.uint8'> 0.0
+ 3 <type 'numpy.uint8'> 0.0
+
+To close a dataset, call its ``close()`` method.
+
+.. code-block:: pycon
+
+ >>> dataset.close()
+ >>> dataset
+ <closed RasterReader name='tests/data/RGB.byte.tif' mode='r'>
+
+After it's closed, data can no longer be read.
+
+.. code-block:: pycon
+
+ >>> dataset.read_band(1)
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ ValueError: can't read closed raster file
+
+This is the same behavior as Python's ``file``.
+
+.. code-block:: pycon
+
+ >>> f = open('README.rst')
+ >>> f.close()
+ >>> f.read()
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ ValueError: I/O operation on closed file
+
+As Python ``file`` objects can, Rasterio datasets can manage the entry into
+and exit from runtime contexts created using a ``with`` statement. This
+ensures that files are closed no matter what exceptions may be raised within
+the the block.
+
+.. code-block:: pycon
+
+ >>> with rasterio.open('tests/data/RGB.byte.tif', 'r') as one:
+ ... with rasterio.open('tests/data/RGB.byte.tif', 'r') as two:
+ print two
+ ... print one
+ ... print two
+ >>> print one
+ <open RasterReader name='tests/data/RGB.byte.tif' mode='r'>
+ <open RasterReader name='tests/data/RGB.byte.tif' mode='r'>
+ <closed RasterReader name='tests/data/RGB.byte.tif' mode='r'>
+ <closed RasterReader name='tests/data/RGB.byte.tif' mode='r'>
+
+Writing data
+------------
+
+Opening a file in writing mode is a little more complicated than opening
+a text file in Python. The dimensions of the raster dataset, the
+data types, and the specific format must be specified.
+
+.. code-block:: pycon
+
+ >>> with rasterio.oepn
+
+Writing data mostly works as with a Python file. There are a few format-
+specific differences. TODO: details.
+
diff --git a/docs/features.rst b/docs/features.rst
new file mode 100644
index 0000000..ef51d65
--- /dev/null
+++ b/docs/features.rst
@@ -0,0 +1,91 @@
+Features
+========
+
+Rasterio's ``features`` module provides functions to extract shapes of raster
+features and to create new features by "burning" shapes into rasters:
+``shapes()`` and ``rasterize()``. These functions expose GDAL functions in
+a very general way, using iterators over GeoJSON-like Python objects instead of
+GIS layers.
+
+Extracting shapes of raster features
+------------------------------------
+
+Consider the Python logo.
+
+.. image:: https://farm8.staticflickr.com/7018/13547682814_f2e459f7a5_o_d.png
+
+The shapes of the foreground features can be extracted like this:
+
+.. code-block:: python
+
+ import pprint
+ import rasterio
+ from rasterio import features
+
+ with rasterio.open('13547682814_f2e459f7a5_o_d.png') as src:
+ blue = src.read_band(3)
+
+ mask = blue != 255
+ shapes = features.shapes(blue, mask=mask)
+ pprint.pprint(next(shapes))
+
+ # Output
+ # pprint.pprint(next(shapes))
+ # ({'coordinates': [[(71.0, 6.0),
+ # (71.0, 7.0),
+ # (72.0, 7.0),
+ # (72.0, 6.0),
+ # (71.0, 6.0)]],
+ # 'type': 'Polygon'},
+ # 253)
+
+The shapes iterator yields ``geometry, value`` pairs. The second item is the
+value of the raster feature corresponding to the shape and the first is its
+geometry. The coordinates of the geometries in this case are in pixel units
+with origin at the upper left of the image. If the source dataset was
+georeferenced, you would get similarly georeferenced geometries like this:
+
+.. code-block:: python
+
+ shapes = features.shapes(blue, mask=mask, transform=src.transform)
+
+Burning shapes into a raster
+----------------------------
+
+To go the other direction, use ``rasterize()`` to burn values into the pixels
+intersecting with geometries.
+
+.. code-block:: python
+
+ image = features.rasterize(
+ ((g, 255) for g, v in shapes),
+ out_shape=src.shape)
+
+Again, to burn in georeferenced shapes, pass an appropriate transform for the
+image to be created.
+
+.. code-block:: python
+
+ image = features.rasterize(
+ ((g, 255) for g, v in shapes),
+ out_shape=src.shape,
+ transform=src.transform)
+
+The values for the input shapes are replaced with ``255`` in a generator
+expression. The resulting image, written to disk like this,
+
+.. code-block:: python
+
+ with rasterio.open(
+ '/tmp/rasterized-results.tif', 'w',
+ driver='GTiff',
+ dtype=rasterio.uint8,
+ count=1,
+ width=src.width,
+ height=src.height) as dst:
+ dst.write_band(1, image)
+
+has a black background and white foreground features.
+
+.. image:: https://farm4.staticflickr.com/3728/13547425455_79bdb5eaeb_o_d.png
+
diff --git a/docs/georeferencing.rst b/docs/georeferencing.rst
new file mode 100644
index 0000000..f8c2321
--- /dev/null
+++ b/docs/georeferencing.rst
@@ -0,0 +1,81 @@
+Georeferencing
+==============
+
+There are two parts to the georeferencing of raster datasets: the definition
+of the local, regional, or global system in which a raster's pixels are
+located; and the parameters by which pixel coordinates are transformed into
+coordinates in that system.
+
+Coordinate Reference System
+---------------------------
+
+The coordinate reference system of a dataset is accessed from its ``crs``
+attribute. Type ``rio insp tests/data/RGB.byte.tif`` from the
+Rasterio distribution root to see.
+
+.. code-block:: pycon
+
+ Rasterio 0.9 Interactive Inspector (Python 3.4.1)
+ Type "src.meta", "src.read_band(1)", or "help(src)" for more information.
+ >>> src
+ <open RasterReader name='tests/data/RGB.byte.tif' mode='r'>
+ >>> src.crs
+ {'init': 'epsg:32618'}
+
+Rasterio follows pyproj and uses PROJ.4 syntax in dict form as its native
+CRS syntax. If you want a WKT representation of the CRS, see the ``crs_wkt``
+attribute.
+
+.. code-block:: pycon
+
+ >>> src.crs_wkt
+ 'PROJCS["UTM Zone 18, Northern Hemisphere",GEOGCS["Unknown datum based upon the WGS 84 ellipsoid",DATUM["Not_specified_based_on_WGS_84_spheroid",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-75],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing [...]
+
+When opening a new file for writing, you may also use a CRS string as an
+argument.
+
+.. code-block:: pycon
+
+ >>> with rasterio.open('/tmp/foo.tif', 'w', crs='EPSG:3857', ...) as dst:
+ ... # write data to this Web Mercator projection dataset.
+
+Coordinate Transformation
+-------------------------
+
+A dataset's pixel coordinate system has its orgin at the "upper left" (imagine
+it displayed on your screen). Column index increases to the right, and row
+index increases downward. The mapping of these coordinates to "world"
+coordinates in the dataset's reference system is done with an affine
+transformation matrix.
+
+.. code-block:: pycon
+
+ >>> src.affine
+ Affine(300.0379266750948, 0.0, 101985.0,
+ 0.0, -300.041782729805, 2826915.0)
+
+The ``Affine`` object is a named tuple with elements ``a, b, c, d, e, f``
+corresponding to the elements in the matrix equation below, in which
+a pixel's image coordinates are ``x, y`` and its world coordinates are
+``x', y'``.::
+
+ | x' | | a b c | | x |
+ | y' | = | d e f | | y |
+ | 1 | | 0 0 1 | | 1 |
+
+The ``Affine`` class has a number of useful properties and methods
+described at https://github.com/sgillies/affine.
+
+The ``affine`` attribute is new. Previous versions of Rasterio had only a
+``transform`` attribute. As explained in the warning below, Rasterio is in
+a transitional phase.
+
+.. code-block:: pycon
+
+ >>> src.transform
+ /usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/code.py:90: FutureWarning: The value of this property will change in version 1.0. Please see https://github.com/mapbox/rasterio/issues/86 for details.
+ [101985.0, 300.0379266750948, 0.0, 2826915.0, 0.0, -300.041782729805]
+
+In Rasterio 1.0, the value of a ``transform`` attribute will be an instance
+of ``Affine`` and the ``affine`` attribute will remain as an alias.
+
diff --git a/docs/masks.rst b/docs/masks.rst
new file mode 100644
index 0000000..10a6239
--- /dev/null
+++ b/docs/masks.rst
@@ -0,0 +1,122 @@
+Masks
+=====
+
+Reading masks
+-------------
+
+There are a few different ways for raster datasets to carry valid data masks.
+Rasterio subscribes to GDAL's abstract mask band interface, so although the
+module's main test dataset has no mask band, GDAL will build one based upon
+its declared nodata value of 0.
+
+.. code-block:: python
+
+ import rasterio
+
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ mask = src.read_mask()
+ print mask.any()
+ count = mask.shape[0] * mask.shape[1]
+ print float((mask > 0).sum())/count
+ print float((mask == 0).sum())/count
+
+Some of the elements of the mask evaluate to ``True``, meaning that there is some
+valid data. Just over 2/3 of the dataset's pixels (use of sum being a neat trick for
+computing the number of pixels in a selection) have valid data.
+
+.. code-block:: console
+
+ True
+ 0.673974976142
+ 0.326025023858
+
+Writing masks
+-------------
+
+Writing a mask is just as straightforward: pass an ndarray with ``True`` (or values
+that evaluate to ``True`` to indicate valid data and ``False`` to indicate no data
+to ``write_mask()``.
+
+.. code-block:: python
+
+ import os
+ import shutil
+ import tempfile
+
+ import numpy
+ import rasterio
+
+ tempdir = tempfile.mkdtemp()
+
+ with rasterio.open(
+ os.path.join(tempdir, 'example.tif'),
+ 'w',
+ driver='GTiff',
+ count=1,
+ dtype=rasterio.uint8,
+ width=10,
+ height=10) as dst:
+
+ dst.write_band(1, numpy.ones(dst.shape, dtype=rasterio.uint8))
+
+ mask = numpy.zeros(dst.shape, rasterio.uint8)
+ mask[5:,5:] = 255
+ dst.write_mask(mask)
+
+ print os.listdir(tempdir)
+ shutil.rmtree(tempdir)
+
+The code above masks out all of the file except the lower right quadrant and
+writes a file with a sidecar TIFF to hold the mask.
+
+.. code-block:: console
+
+ ['example.tif', 'example.tif.msk']
+
+To use an internal TIFF mask, use the ``drivers()`` option shown below:
+
+.. code-block:: python
+
+ tempdir = tempfile.mkdtemp()
+ tiffname = os.path.join(tempdir, 'example.tif')
+
+ with rasterio.drivers(GDAL_TIFF_INTERNAL_MASK=True):
+
+ with rasterio.open(
+ tiffname,
+ 'w',
+ driver='GTiff',
+ count=1,
+ dtype=rasterio.uint8,
+ width=10,
+ height=10) as dst:
+
+ dst.write_band(1, numpy.ones(dst.shape, dtype=rasterio.uint8))
+
+ mask = numpy.zeros(dst.shape, rasterio.uint8)
+ mask[5:,5:] = 255
+ dst.write_mask(mask)
+
+ print os.listdir(tempdir)
+ print subprocess.check_output(['gdalinfo', tiffname])
+
+The output:
+
+.. code-block:: console
+
+ ['example.tif']
+ Driver: GTiff/GeoTIFF
+ Files: /var/folders/jh/w0mgrfqd1t37n0bcqzt16bnc0000gn/T/tmpcnGV_r/example.tif
+ Size is 10, 10
+ Coordinate System is `'
+ Image Structure Metadata:
+ INTERLEAVE=BAND
+ Corner Coordinates:
+ Upper Left ( 0.0, 0.0)
+ Lower Left ( 0.0, 10.0)
+ Upper Right ( 10.0, 0.0)
+ Lower Right ( 10.0, 10.0)
+ Center ( 5.0, 5.0)
+ Band 1 Block=10x10 Type=Byte, ColorInterp=Gray
+ Mask Flags: PER_DATASET
+
diff --git a/docs/options.rst b/docs/options.rst
new file mode 100644
index 0000000..8d0c270
--- /dev/null
+++ b/docs/options.rst
@@ -0,0 +1,22 @@
+Options
+=======
+
+GDAL's format drivers have many [configuration
+options](https://trac.osgeo.org/gdal/wiki/ConfigOptions) The way to set options
+for rasterio is via ``rasterio.drivers()``. Options set on entering are deleted
+on exit.
+
+.. code-block:: python
+
+ import rasterio
+
+ with rasterio.drivers(GDAL_TIFF_INTERNAL_MASK=True):
+ # GeoTIFFs written here will have internal masks, not the
+ # .msk sidecars.
+ ...
+
+ # Option is gone and the default (False) returns.
+
+Use native Python forms (``True`` and ``False``) for boolean options. Rasterio
+will convert them GDAL's internal forms.
+
diff --git a/docs/reproject.rst b/docs/reproject.rst
new file mode 100644
index 0000000..ad290b4
--- /dev/null
+++ b/docs/reproject.rst
@@ -0,0 +1,64 @@
+Reprojection
+============
+
+Rasterio can map the pixels of a destination raster with an associated
+coordinate reference system and transform to the pixels of a source image with
+a different coordinate reference system and transform. This process is known as
+reprojection.
+
+Rasterio's ``rasterio.warp.reproject()`` is a very geospatial-specific analog
+to SciPy's ``scipy.ndimage.interpolation.geometric_transform()`` [1]_.
+
+The code below reprojects between two arrays, using no pre-existing GIS
+datasets. ``rasterio.warp.reproject()`` has two positional arguments: source
+and destination. The remaining keyword arguments parameterize the reprojection
+transform.
+
+.. code-block:: python
+
+ import numpy
+ import rasterio
+ from rasterio import Affine as A
+ from rasterio.warp import reproject, RESAMPLING
+
+ with rasterio.drivers():
+
+ # As source: a 512 x 512 raster centered on 0 degrees E and 0
+ # degrees N, each pixel covering 15".
+ rows, cols = src_shape = (512, 512)
+ d = 1.0/240 # decimal degrees per pixel
+ # The following is equivalent to
+ # A(d, 0, -cols*d/2, 0, -d, rows*d/2).
+ src_transform = A.translation(-cols*d/2, rows*d/2) * A.scale(d, -d)
+ src_crs = {'init': 'EPSG:4326'}
+ source = numpy.ones(src_shape, numpy.uint8)*255
+
+ # Destination: a 1024 x 1024 dataset in Web Mercator (EPSG:3857)
+ # with origin at 0.0, 0.0.
+ dst_shape = (1024, 1024)
+ dst_transform = [-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0]
+ dst_crs = {'init': 'EPSG:3857'}
+ destination = numpy.zeros(dst_shape, numpy.uint8)
+
+ reproject(
+ source,
+ destination,
+ src_transform=src_transform,
+ src_crs=src_crs,
+ dst_transform=dst_transform,
+ dst_crs=dst_crs,
+ resampling=RESAMPLING.nearest)
+
+ # Assert that the destination is only partly filled.
+ assert destination.any()
+ assert not destination.all()
+
+See `examples/reproject.py <https://github.com/mapbox/rasterio/blob/master/examples/reproject.py>`__ for code that writes the destination array to a GeoTIFF file. I've
+uploaded the resulting file to a Mapbox map to demonstrate that the reprojection is
+correct: https://a.tiles.mapbox.com/v3/sgillies.hfek2oko/page.html?secure=1#6/0.000/0.033
+
+References
+----------
+
+.. [1] http://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.interpolation.geometric_transform.html#scipy.ndimage.interpolation.geometric_transform
+
diff --git a/docs/tags.rst b/docs/tags.rst
new file mode 100644
index 0000000..e08f6cd
--- /dev/null
+++ b/docs/tags.rst
@@ -0,0 +1,90 @@
+Tagging datasets and bands
+==========================
+
+GDAL's `data model <http://www.gdal.org/gdal_datamodel.html>`__ includes
+collections of key, value pairs for major classes. In that model, these are
+"metadata", but since they don't have to be just for metadata, these key, value
+pairs are called "tags" in rasterio.
+
+Reading tags
+------------
+
+I'm going to use the rasterio interactive inspector in these examples below.
+
+.. code-block:: console
+
+ $ rasterio.insp tests/data/RGB.byte.tif
+ Rasterio 0.6 Interactive Inspector (Python 2.7.5)
+ Type "src.name", "src.read_band(1)", or "help(src)" for more information.
+ >>>
+
+Tags belong to namespaces. To get a copy of a dataset's tags from the default
+namespace, just call ``tags()`` with no arguments.
+
+.. code-block:: pycon
+
+ >>>src.tags()
+ {u'AREA_OR_POINT': u'Area'}
+
+A dataset's bands may have tags, too. Here are the tags from the default namespace
+for the first band, accessed using the positional band index argument of ``tags()``.
+
+.. code-block:: pycon
+
+ >>> src.tags(1)
+ {u'STATISTICS_MEAN': u'29.947726688477', u'STATISTICS_MINIMUM': u'0', u'STATISTICS_MAXIMUM': u'255', u'STATISTICS_STDDEV': u'52.340921626611'}
+
+These are the tags that came with the sample data I'm using to test rasterio. In
+practice, maintaining stats in the tags can be unreliable as there is no automatic
+update of the tags when the band's image data changes.
+
+The 3 standard, non-default GDAL tag namespaces are 'SUBDATASETS', 'IMAGE_STRUCTURE',
+and 'RPC'. You can get the tags from these namespaces using the `ns` keyword of
+``tags()``.
+
+.. code-block:: pycon
+
+ >>> src.tags(ns='IMAGE_STRUCTURE')
+ {u'INTERLEAVE': u'PIXEL'}
+ >>> src.tags(ns='SUBDATASETS')
+ {}
+ >>> src.tags(ns='RPC')
+ {}
+
+Writing tags
+------------
+
+You can add new tags to a dataset or band, in the default or another namespace,
+using the ``update_tags()`` method. Unicode tag values, too, at least for TIFF
+files.
+
+.. code-block:: python
+
+ import rasterio
+
+ with rasterio.open(
+ '/tmp/test.tif',
+ 'w',
+ driver='GTiff',
+ count=1,
+ dtype=rasterio.uint8,
+ width=10,
+ height=10) as dst:
+
+ dst.update_tags(a='1', b='2')
+ dst.update_tags(1, c=3)
+ with pytest.raises(ValueError):
+ dst.update_tags(4, d=4)
+
+ # True
+ assert dst.tags() == {'a': '1', 'b': '2'}
+ # True
+ assert dst.tags(1) == {'c': '3' }
+
+ dst.update_tags(ns='rasterio_testing', rus=u'другая строка')
+ # True
+ assert dst.tags(ns='rasterio_testing') == {'rus': u'другая строка'}
+
+As with image data, tags aren't written to the file on disk until the dataset
+is closed.
+
diff --git a/docs/windowed-rw.rst b/docs/windowed-rw.rst
new file mode 100644
index 0000000..d91f5a9
--- /dev/null
+++ b/docs/windowed-rw.rst
@@ -0,0 +1,231 @@
+Windowed reading and writing
+============================
+
+Beginning in rasterio 0.3, you can read and write "windows" of raster files.
+This feature allows you to operate on rasters that are larger than your
+computers RAM or process chunks of very large rasters in parallel.
+
+Windows
+-------
+
+A window is a view onto a rectangular subset of a raster dataset and is
+described in rasterio by a pair of range tuples.
+
+.. code-block:: python
+
+ ((row_start, row_stop), (col_start, col_stop))
+
+The first pair contains the indexes of the raster rows at which the window
+starts and stops. The second contains the indexes of the raster columns at
+which the window starts and stops. For example,
+
+.. code-block:: python
+
+ ((0, 4), (0, 4))
+
+Specifies a 4 x 4 window at the upper left corner of a raster dataset and
+
+.. code-block:: python
+
+ ((10, 20), (10, 20))
+
+specifies a 10 x 10 window with origin at row 10 and column 10. Use of `None`
+for a range value indicates either 0 (in the start position) or the full raster
+height or width (in the stop position). The window tuple
+
+.. code-block:: python
+
+ ((None, 4), (None, 4))
+
+also specifies a 4 x 4 window at the upper left corner of the raster and
+
+.. code-block:: python
+
+ ((4, None), (4, None))
+
+specifies a rectangular subset with upper left at row 4, column 4 and
+extending to the lower right corner of the raster dataset.
+
+Using window tuples should feel like using Python's range() and slice()
+functions. Range() selects a range of numbers from the sequence of all integers
+and slice() produces a object that can be used in slicing expressions.
+
+.. code-block:: pycon
+
+ >>> range(10, 20)
+ [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
+ >>> range(10, 20)[slice(4, None)]
+ [14, 15, 16, 17, 18, 19]
+
+Reading
+-------
+
+Here is an example of reading a 100 row x 100 column subset of the rasterio
+test file.
+
+.. code-block:: pycon
+
+ >>> import rasterio
+ >>> with rasterio.open('tests/data/RGB.byte.tif') as src:
+ ... w = src.read_band(1, window=((0, 100), (0, 100)))
+ ...
+ >>> print(w.shape)
+ (100, 100)
+
+Writing
+-------
+
+Writing works similarly. The following creates a blank 500 column x 300 row
+GeoTIFF and plops 37500 pixels with value 127 into a window 30 pixels down from
+and 50 pixels to the right of the upper left corner of the GeoTIFF.
+
+.. code-block:: python
+
+ image = numpy.ones((150, 250), dtype=rasterio.ubyte) * 127
+
+ with rasterio.open(
+ '/tmp/example.tif', 'w',
+ driver='GTiff', width=500, height=300, count=1,
+ dtype=image.dtype) as dst:
+ dst.write_band(1, image, window=((30, 180), (50, 300)))
+
+The result:
+
+.. image:: http://farm6.staticflickr.com/5503/11378078386_cbe2fde02e_o_d.png
+ :width: 500
+ :height: 300
+
+Decimation
+----------
+
+If the write window is smaller than the data, the data will be decimated.
+Below, the window is scaled to one third of the source image.
+
+.. code-block:: python
+
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ b, g, r = (src.read_band(k) for k in (1, 2, 3))
+
+ write_window = (30, 269), (50, 313)
+
+ with rasterio.open(
+ '/tmp/example.tif', 'w',
+ driver='GTiff', width=500, height=300, count=3,
+ dtype=r.dtype) as dst:
+ for k, arr in [(1, b), (2, g), (3, r)]:
+ dst.write_band(k, arr, window=write_window)
+
+And the result:
+
+.. image:: http://farm4.staticflickr.com/3804/11378361126_c034743079_o_d.png
+ :width: 500
+ :height: 300
+
+Advanced windows
+----------------
+
+Since windows are like slices, you can also use negative numbers in rasterio
+windows.
+
+.. code-block:: python
+
+ ((-4, None), (-4, None))
+
+specifies a 4 x 4 rectangular subset with upper left at 4 rows to the left of
+and 4 columns above the lower right corner of the dataset and extending to the
+lower right corner of the dataset.
+
+Below is an example of reading a raster subset and then writing it into a
+larger subset that is defined relative to the lower right corner of the
+destination dataset.
+
+.. code-block:: python
+
+ read_window = (350, 410), (350, 450)
+
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ b, g, r = (src.read_band(k, window=read_window) for k in (1, 2, 3))
+
+ write_window = (-240, None), (-400, None)
+
+ with rasterio.open(
+ '/tmp/example2.tif', 'w',
+ driver='GTiff', width=500, height=300, count=3,
+ dtype=r.dtype) as dst:
+ for k, arr in [(1, b), (2, g), (3, r)]:
+ dst.write_band(k, arr, window=write_window)
+
+This example also demonstrates decimation.
+
+.. image:: http://farm3.staticflickr.com/2827/11378772013_c8ab540f21_o_d.png
+ :width: 500
+ :height: 300
+
+Blocks
+------
+
+Raster datasets are generally composed of multiple blocks of data and
+windowed reads and writes are most efficient when the windows match the
+dataset's own block structure. When a file is opened to read, the shape
+of blocks for any band can be had from the block_shapes property.
+
+.. code-block:: pycon
+
+ >>> with rasterio.open('tests/data/RGB.byte.tif') as src:
+ ... for i, shape in enumerate(src.block_shapes, 1):
+ ... print(i, shape)
+ ...
+ (1, (3, 791))
+ (2, (3, 791))
+ (3, (3, 791))
+
+
+The block windows themselves can be had from the block_windows function.
+
+.. code-block:: pycon
+
+ >>> with rasterio.open('tests/data/RGB.byte.tif') as src:
+ ... for ji, window in src.block_windows(1):
+ ... print(ji, window)
+ ...
+ ((0, 0), ((0, 3), (0, 791)))
+ ((1, 0), ((3, 6), (0, 791)))
+ ...
+
+This function returns an iterator that yields a pair of values. The second is
+a window tuple that can be used in calls to read_band or write_band. The first
+is the pair of row and column indexes of this block within all blocks of the
+dataset.
+
+You may read windows of data from a file block-by-block like this.
+
+.. code-block:: pycon
+
+ >>> with rasterio.open('tests/data/RGB.byte.tif') as src:
+ ... for ji, window in src.block_windows(1):
+ ... r = src.read_band(1, window=window)
+ ... print(r.shape)
+ ... break
+ ...
+ (3, 791)
+
+Well-bred files have identically blocked bands, but GDAL allows otherwise and
+it's a good idea to test this assumption in your code.
+
+.. code-block:: pycon
+
+ >>> with rasterio.open('tests/data/RGB.byte.tif') as src:
+ ... assert len(set(src.block_shapes)) == 1
+ ... for ji, window in src.block_windows(1):
+ ... b, g, r = (src.read_band(k, window=window) for k in (1, 2, 3))
+ ... print(ji, r.shape, g.shape, b.shape)
+ ... break
+ ...
+ ((0, 0), (3, 791), (3, 791), (3, 791))
+
+The block_shapes property is a band-ordered list of block shapes and
+`set(src.block_shapes)` gives you the set of unique shapes. Asserting that
+there is only one item in the set is effectively the same as asserting that all
+bands have the same block structure. If they do, you can use the same windows
+for each.
+
diff --git a/examples/async-rasterio.py b/examples/async-rasterio.py
new file mode 100644
index 0000000..4e50b84
--- /dev/null
+++ b/examples/async-rasterio.py
@@ -0,0 +1,100 @@
+"""async-rasterio.py
+
+Operate on a raster dataset window-by-window using asyncio's event loop
+and thread executor.
+
+Simulates a CPU-bound thread situation where multiple threads can improve
+performance.
+"""
+
+import asyncio
+import time
+
+import numpy
+import rasterio
+
+from rasterio._example import compute
+
+def main(infile, outfile, with_threads=False):
+
+ with rasterio.drivers():
+
+ # Open the source dataset.
+ with rasterio.open(infile) as src:
+
+ # Create a destination dataset based on source params. The
+ # destination will be tiled, and we'll "process" the tiles
+ # concurrently.
+
+ meta = src.meta
+ del meta['transform']
+ meta.update(affine=src.affine)
+ meta.update(blockxsize=256, blockysize=256, tiled='yes')
+ with rasterio.open(outfile, 'w', **meta) as dst:
+
+ loop = asyncio.get_event_loop()
+
+ # With the exception of the ``yield from`` statement,
+ # process_window() looks like callback-free synchronous
+ # code. With a coroutine, we can keep the read, compute,
+ # and write statements close together for
+ # maintainability. As in the concurrent-cpu-bound.py
+ # example, all of the speedup is provided by
+ # distributing raster computation across multiple
+ # threads. The difference here is that we're submitting
+ # jobs to the thread pool asynchronously.
+
+ @asyncio.coroutine
+ def process_window(window):
+
+ # Read a window of data.
+ data = src.read(window=window)
+
+ # We run the raster computation in a separate thread
+ # and pause until the computation finishes, letting
+ # other coroutines advance.
+ #
+ # The _example.compute function modifies no Python
+ # objects and releases the GIL. It can execute
+ # concurrently.
+ result = numpy.zeros(data.shape, dtype=data.dtype)
+ if with_threads:
+ yield from loop.run_in_executor(
+ None, compute, data, result)
+ else:
+ compute(data, result)
+
+ # Write the result.
+ for i, arr in enumerate(result, 1):
+ dst.write_band(i, arr, window=window)
+
+ # Queue up the loop's tasks.
+ tasks = [asyncio.Task(process_window(window))
+ for ij, window in dst.block_windows(1)]
+
+ # Wait for all the tasks to finish, and close.
+ loop.run_until_complete(asyncio.wait(tasks))
+ loop.close()
+
+if __name__ == '__main__':
+
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="Concurrent raster processing demo")
+ parser.add_argument(
+ 'input',
+ metavar='INPUT',
+ help="Input file name")
+ parser.add_argument(
+ 'output',
+ metavar='OUTPUT',
+ help="Output file name")
+ parser.add_argument(
+ '--with-workers',
+ action='store_true',
+ help="Run with a pool of worker threads")
+ args = parser.parse_args()
+
+ main(args.input, args.output, args.with_workers)
+
diff --git a/examples/concurrent-cpu-bound.py b/examples/concurrent-cpu-bound.py
new file mode 100644
index 0000000..1ceeb7c
--- /dev/null
+++ b/examples/concurrent-cpu-bound.py
@@ -0,0 +1,95 @@
+"""concurrent-cpu-bound.py
+
+Operate on a raster dataset window-by-window using a ThreadPoolExecutor.
+
+Simulates a CPU-bound thread situation where multiple threads can improve performance.
+
+With -j 4, the program returns in about 1/4 the time as with -j 1.
+"""
+
+import concurrent.futures
+import multiprocessing
+import time
+
+import numpy
+import rasterio
+from rasterio._example import compute
+
+def main(infile, outfile, num_workers=4):
+
+ with rasterio.drivers():
+
+ # Open the source dataset.
+ with rasterio.open(infile) as src:
+
+ # Create a destination dataset based on source params.
+ # The destination will be tiled, and we'll "process" the tiles
+ # concurrently.
+ meta = src.meta
+ del meta['transform']
+ meta.update(affine=src.affine)
+ meta.update(blockxsize=256, blockysize=256, tiled='yes')
+ with rasterio.open(outfile, 'w', **meta) as dst:
+
+ # Define a generator for data, window pairs.
+ # We use the new read() method here to a 3D array with all
+ # bands, but could also use read_band().
+ def jobs():
+ for ij, window in dst.block_windows():
+ data = src.read(window=window)
+ result = numpy.zeros(data.shape, dtype=data.dtype)
+ yield data, result, window
+
+ # Submit the jobs to the thread pool executor.
+ with concurrent.futures.ThreadPoolExecutor(
+ max_workers=num_workers) as executor:
+
+ # Map the futures returned from executor.submit()
+ # to their destination windows.
+ #
+ # The _example.compute function modifies no Python
+ # objects and releases the GIL. It can execute
+ # concurrently.
+ future_to_window = {
+ executor.submit(compute, data, res): (res, window)
+ for data, res, window in jobs()}
+
+ # As the processing jobs are completed, get the
+ # results and write the data to the appropriate
+ # destination window.
+ for future in concurrent.futures.as_completed(
+ future_to_window):
+
+ result, window = future_to_window[future]
+
+ # Since there's no multiband write() method yet in
+ # Rasterio, we use write_band for each part of the
+ # 3D data array.
+ for i, arr in enumerate(result, 1):
+ dst.write_band(i, arr, window=window)
+
+
+if __name__ == '__main__':
+
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="Concurrent raster processing demo")
+ parser.add_argument(
+ 'input',
+ metavar='INPUT',
+ help="Input file name")
+ parser.add_argument(
+ 'output',
+ metavar='OUTPUT',
+ help="Output file name")
+ parser.add_argument(
+ '-j',
+ metavar='NUM_JOBS',
+ type=int,
+ default=multiprocessing.cpu_count(),
+ help="Number of concurrent jobs")
+ args = parser.parse_args()
+
+ main(args.input, args.output, args.j)
+
diff --git a/examples/decimate.py b/examples/decimate.py
new file mode 100644
index 0000000..12b2d1a
--- /dev/null
+++ b/examples/decimate.py
@@ -0,0 +1,31 @@
+import os.path
+import subprocess
+import tempfile
+
+import rasterio
+
+with rasterio.drivers():
+
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ b, g, r = (src.read_band(k) for k in (1, 2, 3))
+ meta = src.meta
+
+ tmpfilename = os.path.join(tempfile.mkdtemp(), 'decimate.tif')
+
+ meta.update(
+ width=src.width/2,
+ height=src.height/2)
+
+ with rasterio.open(
+ tmpfilename, 'w',
+ **meta
+ ) as dst:
+ for k, a in [(1, b), (2, g), (3, r)]:
+ dst.write_band(k, a)
+
+ outfilename = os.path.join(tempfile.mkdtemp(), 'decimate.jpg')
+
+ rasterio.copy(tmpfilename, outfilename, driver='JPEG', quality='30')
+
+info = subprocess.call(['open', outfilename])
+
diff --git a/examples/features.ipynb b/examples/features.ipynb
new file mode 100644
index 0000000..4c0a8aa
--- /dev/null
+++ b/examples/features.ipynb
@@ -0,0 +1,187 @@
+{
+ "metadata": {
+ "name": "",
+ "signature": "sha256:226eb42f053d4da563e4614eb832e56a383e6b4911e98a702ffb7155200d3c9d"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+ {
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Interacting with raster features\n",
+ "\n",
+ "A raster feature is a continguous region of like pixels. Rasterio permits extraction of features into a vector data representation and the reverse operation, \"burning\" vector data into a raster or image."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Extracting features"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Rasterizing features\n",
+ "\n",
+ "Given a source of GeoJSON-like geometry objects or objects that provide the Python Geo Interface, you can \"burn\" these into a raster dataset."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "from rasterio.transform import Affine"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 14
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "def transform_from_corner(ulx, uly, dx, dy):\n",
+ " return Affine.translation(ulx, uly)*Affine.scale(dx, -dy)\n",
+ "\n",
+ "print transform_from_corner(bounds[0], bounds[3], 1.0/3600, 1.0/3600).to_gdal()"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "output_type": "stream",
+ "stream": "stdout",
+ "text": [
+ "(119.52, 0.0002777777777777778, 0.0, -20.5, 0.0, -0.0002777777777777778)\n"
+ ]
+ }
+ ],
+ "prompt_number": 15
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "from rasterio.features import rasterize\n",
+ "from shapely.geometry import Polygon, mapping\n",
+ "\n",
+ "# image transform\n",
+ "bounds = (119.52, -21.6, 120.90, -20.5)\n",
+ "transform = transform_from_corner(bounds[0], bounds[3], 1.0/3600, 1.0/3600)\n",
+ "\n",
+ "# Make raster image, burn in vector data which lies completely inside the bounding box\n",
+ "poly = Polygon(((120, -21), (120.5, -21), (120.5, -21.2), (120, -21.2)))\n",
+ "output = rasterize([poly], transform=transform, out_shape=(3961, 4969))\n",
+ "print output"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "output_type": "stream",
+ "stream": "stdout",
+ "text": [
+ "[[0 0 0 ..., 0 0 0]\n",
+ " [0 0 0 ..., 0 0 0]\n",
+ " [0 0 0 ..., 0 0 0]\n",
+ " ..., \n",
+ " [0 0 0 ..., 0 0 0]\n",
+ " [0 0 0 ..., 0 0 0]\n",
+ " [0 0 0 ..., 0 0 0]]\n"
+ ]
+ }
+ ],
+ "prompt_number": 16
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "%matplotlib inline"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 17
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "import matplotlib.pyplot as plt"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 18
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "plt.imshow(output)"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 19,
+ "text": [
+ "<matplotlib.image.AxesImage at 0x1245e5550>"
+ ]
+ },
+ {
+ "metadata": {},
+ "output_type": "display_data",
+ "png": "iVBORw0KGgoAAAANSUhEUgAAAUIAAAEACAYAAADGPX/7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAF+5JREFUeJzt3W1oW+fh9/Gvgg2ltCl5McveOQGlsRxXiWqLpcperJDg\nOg/u4rlkt++6W+y0KQwHNncpZe2rNoPV7sYobTdBGR6YFBLnflE7jEW4pXH6tKrUsymr9ieCySDJ\nsmmWZTitW6fx9X+R9NxxE9uJn+T6+n3ggH1JR7ouSL45Rw85PmOMQUTEYmsKPQERkUJTCEXEegqh\niFhPIRQR6ymEImI9hVBErLesIYzH41RWVhIMBnnhhReW86lFRGbkW67PEV6+fJlNmzbx5ptv4jgO\n9913H8eOHeOee+5ZjqcXEZnRsh0Rfvjhh5SXlxMIBCguLubhhx+mt7d3uZ5eRGRGyxbCXC7H+vXr\nvd9d1yWXy [...]
+ "text": [
+ "<matplotlib.figure.Figure at 0x10f2f9990>"
+ ]
+ }
+ ],
+ "prompt_number": 19
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "import json\n",
+ "\n",
+ "output = rasterize([json.dumps(mapping(poly))], transform=transform, out_shape=(3961, 4969))\n",
+ "print output"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "output_type": "stream",
+ "stream": "stderr",
+ "text": [
+ "ERROR:rasterio:Geometry '{\"type\": \"Polygon\", \"coordinates\": [[[120.0, -21.0], [120.5, -21.0], [120.5, -21.2], [120.0, -21.2], [120.0, -21.0]]]}' at index 0 with value 255 skipped\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [],
+ "language": "python",
+ "metadata": {},
+ "outputs": []
+ }
+ ],
+ "metadata": {}
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb
new file mode 100644
index 0000000..4b5bc8b
--- /dev/null
+++ b/examples/introduction.ipynb
@@ -0,0 +1,393 @@
+{
+ "metadata": {
+ "name": "",
+ "signature": "sha256:5a6908bb26106597e34dd231b1a5f453aaa6e8a3e4c9298d8c3baaf3c3e0c4a1"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+ {
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# An introduction to Rasterio\n",
+ "\n",
+ "The smallest interesting problems [1] addressed by Rasterio are reading raster data from files as [Numpy](http://www.numpy.org/) arrays and writing such arrays back to files. In between, you can use the world of scientific python software to analyze and process the data. Rasterio also provides a few operations that are described in the next notebooks in this series.\n",
+ "\n",
+ "This notebook demonstrates the basics of reading and writing raster data with Rasterio.\n",
+ "\n",
+ "## Overview of a dataset\n",
+ "\n",
+ "A raster dataset consists of one or more dense (as opposed to sparse) 2-D arrays of scalar values. An RGB TIFF image file is a good example of a raster dataset. It has 3 bands (or channels \u2013 we'll call them bands here) and each has a number of rows (its `height`) and columns (its `width`) and a uniform data type (unsigned 8-bit integers, 64-bit floats, etc). Geospatially referenced datasets will also possess a mapping from image to world coordinates (a `transform`) in a speci [...]
+ "\n",
+ "The Scientific Python community often imports numpy as `np`. Do this and also import rasterio."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "import numpy as np\n",
+ "\n",
+ "import rasterio"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 9
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Rasterio uses for many of its tests a small 3-band GeoTIFF file named \"RGB.byte.tif\". Open it using the function `rasterio.open()`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src = rasterio.open('../tests/data/RGB.byte.tif')"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [],
+ "prompt_number": 10
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This function returns a dataset object. It has many of the same properties as a Python file object."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.name"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 11,
+ "text": [
+ "'../tests/data/RGB.byte.tif'"
+ ]
+ }
+ ],
+ "prompt_number": 11
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.mode"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 12,
+ "text": [
+ "'r'"
+ ]
+ }
+ ],
+ "prompt_number": 12
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.closed"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 13,
+ "text": [
+ "False"
+ ]
+ }
+ ],
+ "prompt_number": 13
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Raster datasets have additional structure and a description can be had from its `meta` property or individually."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.meta"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 14,
+ "text": [
+ "{'affine': Affine(300.0379266750948, 0.0, 101985.0,\n",
+ " 0.0, -300.041782729805, 2826915.0),\n",
+ " 'count': 3,\n",
+ " 'crs': {'init': u'epsg:32618'},\n",
+ " 'driver': u'GTiff',\n",
+ " 'dtype': 'uint8',\n",
+ " 'height': 718,\n",
+ " 'nodata': 0.0,\n",
+ " 'transform': (101985.0,\n",
+ " 300.0379266750948,\n",
+ " 0.0,\n",
+ " 2826915.0,\n",
+ " 0.0,\n",
+ " -300.041782729805),\n",
+ " 'width': 791}"
+ ]
+ }
+ ],
+ "prompt_number": 14
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.crs"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 15,
+ "text": [
+ "{'init': u'epsg:32618'}"
+ ]
+ }
+ ],
+ "prompt_number": 15
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To close an opened dataset, use its `close()` method."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.close()\n",
+ "src.closed"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 16,
+ "text": [
+ "True"
+ ]
+ }
+ ],
+ "prompt_number": 16
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You can't read from or write to a closed dataset, but you can continue access its properties."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.driver"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 23,
+ "text": [
+ "u'GTiff'"
+ ]
+ }
+ ],
+ "prompt_number": 23
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Dataset layout\n",
+ "\n",
+ "Three properties of a Rasterio dataset tell you a lot about it in Numpy terms. The `shape` of a dataset is a `height, width` tuple and is exactly the shape of Numpy arrays that would be read from it. The testing dataset has 718 rows and 791 columns."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.shape"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 26,
+ "text": [
+ "(718, 791)"
+ ]
+ }
+ ],
+ "prompt_number": 26
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `count` of bands in the dataset is 3."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.count"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 27,
+ "text": [
+ "3"
+ ]
+ }
+ ],
+ "prompt_number": 27
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "All three of its bands contain 8-bit unsigned integers."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "src.dtypes"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 28,
+ "text": [
+ "['uint8', 'uint8', 'uint8']"
+ ]
+ }
+ ],
+ "prompt_number": 28
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Numpy concepts are the model here. If you wanted to create a 3-D Numpy array into which the testing data file's bands would fit without any resampling, you would use the following Python code."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [
+ "dest = np.empty((src.count,) + src.shape, dtype='uint8')\n",
+ "dest"
+ ],
+ "language": "python",
+ "metadata": {},
+ "outputs": [
+ {
+ "metadata": {},
+ "output_type": "pyout",
+ "prompt_number": 25,
+ "text": [
+ "array([[[0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " ..., \n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0]],\n",
+ "\n",
+ " [[0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " ..., \n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0]],\n",
+ "\n",
+ " [[0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " ..., \n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0],\n",
+ " [0, 0, 0, ..., 0, 0, 0]]], dtype=uint8)"
+ ]
+ }
+ ],
+ "prompt_number": 25
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## References"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[1]: Mike Bostock's words from his FOSS4G keynote, 2014-09-10"
+ ]
+ }
+ ],
+ "metadata": {}
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/polygonize.py b/examples/polygonize.py
new file mode 100644
index 0000000..ae6e000
--- /dev/null
+++ b/examples/polygonize.py
@@ -0,0 +1,10 @@
+import pprint
+
+import rasterio
+import rasterio._features as ftrz
+
+with rasterio.open('box.png') as src:
+ image = src.read_band(1)
+
+pprint.pprint(
+ list(ftrz.polygonize(image)))
diff --git a/examples/rasterio_polygonize.py b/examples/rasterio_polygonize.py
new file mode 100644
index 0000000..54dd19a
--- /dev/null
+++ b/examples/rasterio_polygonize.py
@@ -0,0 +1,74 @@
+# Emulates GDAL's gdal_polygonize.py
+
+import argparse
+import logging
+import subprocess
+import sys
+
+import fiona
+import numpy as np
+import rasterio
+from rasterio.features import shapes
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.INFO)
+logger = logging.getLogger('rasterio_polygonize')
+
+
+def main(raster_file, vector_file, driver, mask_value):
+
+ with rasterio.drivers():
+
+ with rasterio.open(raster_file) as src:
+ image = src.read_band(1)
+
+ if mask_value is not None:
+ mask = image == mask_value
+ else:
+ mask = None
+
+ results = (
+ {'properties': {'raster_val': v}, 'geometry': s}
+ for i, (s, v)
+ in enumerate(
+ shapes(image, mask=mask, transform=src.affine)))
+
+ with fiona.open(
+ vector_file, 'w',
+ driver=driver,
+ crs=src.crs,
+ schema={'properties': [('raster_val', 'int')],
+ 'geometry': 'Polygon'}) as dst:
+ dst.writerecords(results)
+
+ return dst.name
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser(
+ description="Writes shapes of raster features to a vector file")
+ parser.add_argument(
+ 'input',
+ metavar='INPUT',
+ help="Input file name")
+ parser.add_argument(
+ 'output',
+ metavar='OUTPUT',
+ help="Output file name")
+ parser.add_argument(
+ '--output-driver',
+ metavar='OUTPUT DRIVER',
+ help="Output vector driver name")
+ parser.add_argument(
+ '--mask-value',
+ default=None,
+ type=int,
+ metavar='MASK VALUE',
+ help="Value to mask")
+ args = parser.parse_args()
+
+ name = main(args.input, args.output, args.output_driver, args.mask_value)
+
+ print subprocess.check_output(
+ ['ogrinfo', '-so', args.output, name])
+
diff --git a/examples/rasterize_geometry.py b/examples/rasterize_geometry.py
new file mode 100644
index 0000000..bfbc43e
--- /dev/null
+++ b/examples/rasterize_geometry.py
@@ -0,0 +1,27 @@
+import logging
+import numpy
+import sys
+import rasterio
+from rasterio.features import rasterize
+from rasterio.transform import IDENTITY
+
+logging.basicConfig(stream=sys.stderr, level=logging.INFO)
+logger = logging.getLogger('rasterize_geometry')
+
+
+rows = cols = 10
+geometry = {'type':'Polygon','coordinates':[[(2,2),(2,4.25),(4.25,4.25),(4.25,2),(2,2)]]}
+with rasterio.drivers():
+ result = rasterize([geometry], out_shape=(rows, cols))
+ with rasterio.open(
+ "test.tif",
+ 'w',
+ driver='GTiff',
+ width=cols,
+ height=rows,
+ count=1,
+ dtype=numpy.uint8,
+ nodata=0,
+ transform=IDENTITY,
+ crs={'init': "EPSG:4326"}) as out:
+ out.write_band(1, result.astype(numpy.uint8))
diff --git a/examples/reproject.py b/examples/reproject.py
new file mode 100644
index 0000000..9a8a348
--- /dev/null
+++ b/examples/reproject.py
@@ -0,0 +1,62 @@
+import os
+import shutil
+import subprocess
+import tempfile
+
+import numpy
+import rasterio
+from rasterio import Affine as A
+from rasterio.warp import reproject, RESAMPLING
+
+tempdir = '/tmp'
+tiffname = os.path.join(tempdir, 'example.tif')
+
+with rasterio.drivers():
+
+ # Consider a 512 x 512 raster centered on 0 degrees E and 0 degrees N
+ # with each pixel covering 15".
+ rows, cols = src_shape = (512, 512)
+ dpp = 1.0/240 # decimal degrees per pixel
+ # The following is equivalent to
+ # A(dpp, 0, -cols*dpp/2, 0, -dpp, rows*dpp/2).
+ src_transform = A.translation(-cols*dpp/2, rows*dpp/2) * A.scale(dpp, -dpp)
+ src_crs = {'init': 'EPSG:4326'}
+ source = numpy.ones(src_shape, numpy.uint8)*255
+
+ # Prepare to reproject this rasters to a 1024 x 1024 dataset in
+ # Web Mercator (EPSG:3857) with origin at -8928592, 2999585.
+ dst_shape = (1024, 1024)
+ dst_transform = A.from_gdal(-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0)
+ dst_transform = dst_transform.to_gdal()
+ dst_crs = {'init': 'EPSG:3857'}
+ destination = numpy.zeros(dst_shape, numpy.uint8)
+
+ reproject(
+ source,
+ destination,
+ src_transform=src_transform,
+ src_crs=src_crs,
+ dst_transform=dst_transform,
+ dst_crs=dst_crs,
+ resampling=RESAMPLING.nearest)
+
+ # Assert that the destination is only partly filled.
+ assert destination.any()
+ assert not destination.all()
+
+ # Write it out to a file.
+ with rasterio.open(
+ tiffname,
+ 'w',
+ driver='GTiff',
+ width=dst_shape[1],
+ height=dst_shape[0],
+ count=1,
+ dtype=numpy.uint8,
+ nodata=0,
+ transform=dst_transform,
+ crs=dst_crs) as dst:
+ dst.write_band(1, destination)
+
+info = subprocess.call(['open', tiffname])
+
diff --git a/examples/sieve.py b/examples/sieve.py
new file mode 100644
index 0000000..b3aab7f
--- /dev/null
+++ b/examples/sieve.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+#
+# sieve: demonstrate sieving and polygonizing of raster features.
+
+import subprocess
+
+import numpy
+import rasterio
+from rasterio.features import sieve, shapes
+
+
+# Register GDAL and OGR drivers.
+with rasterio.drivers():
+
+ # Read a raster to be sieved.
+ with rasterio.open('tests/data/shade.tif') as src:
+ shade = src.read_band(1)
+
+ # Print the number of shapes in the source raster.
+ print("Slope shapes: %d" % len(list(shapes(shade))))
+
+ # Sieve out features 13 pixels or smaller.
+ sieved = sieve(shade, 13, out=numpy.zeros(src.shape, src.dtypes[0]))
+
+ # Print the number of shapes in the sieved raster.
+ print("Sieved (13) shapes: %d" % len(list(shapes(sieved))))
+
+ # Write out the sieved raster.
+ kwargs = src.meta
+ kwargs['transform'] = kwargs.pop('affine')
+ with rasterio.open('example-sieved.tif', 'w', **kwargs) as dst:
+ dst.write_band(1, sieved)
+
+# Dump out gdalinfo's report card and open (or "eog") the TIFF.
+print(subprocess.check_output(
+ ['gdalinfo', '-stats', 'example-sieved.tif']))
+subprocess.call(['open', 'example-sieved.tif'])
+
diff --git a/examples/total.py b/examples/total.py
new file mode 100644
index 0000000..d95a0fa
--- /dev/null
+++ b/examples/total.py
@@ -0,0 +1,38 @@
+import numpy
+import rasterio
+import subprocess
+
+with rasterio.drivers(CPL_DEBUG=True):
+
+ # Read raster bands directly to Numpy arrays.
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ r, g, b = src.read()
+
+ # Combine arrays using the 'iadd' ufunc. Expecting that the sum will
+ # 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.
+ total = numpy.zeros(r.shape, dtype=rasterio.uint16)
+ 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 dtype to uint8, and specify
+ # LZW compression.
+ kwargs = src.meta
+ kwargs.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))
+
+# 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'])
+
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
new file mode 100644
index 0000000..ea03bb6
--- /dev/null
+++ b/rasterio/__init__.py
@@ -0,0 +1,164 @@
+# rasterio
+
+from collections import namedtuple
+import logging
+import os
+import warnings
+
+from rasterio._base import eval_window, window_shape, window_index
+from rasterio._drivers import driver_count, GDALEnv
+import rasterio.dtypes
+from rasterio.dtypes import (
+ bool_, ubyte, uint8, uint16, int16, uint32, int32, float32, float64,
+ complex_)
+from rasterio.five import string_types
+from rasterio.transform import Affine, guard_transform
+
+# Classes in rasterio._io are imported below just before we need them.
+
+__all__ = [
+ 'band', 'open', 'drivers', 'copy', 'pad']
+__version__ = "0.15"
+
+log = logging.getLogger('rasterio')
+class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+log.addHandler(NullHandler())
+
+
+def open(
+ path, mode='r',
+ driver=None,
+ width=None, height=None,
+ count=None,
+ crs=None, transform=None,
+ dtype=None,
+ nodata=None,
+ **kwargs):
+ """Open file at ``path`` in ``mode`` "r" (read), "r+" (read/write),
+ or "w" (write) and return a ``Reader`` or ``Updater`` object.
+
+ In write mode, a driver name such as "GTiff" or "JPEG" (see GDAL
+ docs or ``gdal_translate --help`` on the command line), ``width``
+ (number of pixels per line) and ``height`` (number of lines), the
+ ``count`` number of bands in the new file must be specified.
+ Additionally, the data type for bands such as ``rasterio.ubyte`` for
+ 8-bit bands or ``rasterio.uint16`` for 16-bit bands must be
+ specified using the ``dtype`` argument.
+
+ A coordinate reference system for raster datasets in write mode can
+ be defined by the ``crs`` argument. It takes Proj4 style mappings
+ like
+
+ {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84',
+ 'no_defs': True}
+
+ An affine transformation that maps ``col,row`` pixel coordinates to
+ ``x,y`` coordinates in the coordinate reference system can be
+ specified using the ``transform`` argument. The value may be either
+ an instance of ``affine.Affine`` or a 6-element sequence of the
+ affine transformation matrix coefficients ``a, b, c, d, e, f``.
+ These coefficients are shown in the figure below.
+
+ | x | | a b c | | c |
+ | y | = | d e f | | r |
+ | 1 | | 0 0 1 | | 1 |
+
+ a: rate of change of X with respect to increasing column, i.e.
+ pixel width
+ b: rotation, 0 if the raster is oriented "north up"
+ c: X coordinate of the top left corner of the top left pixel
+ f: Y coordinate of the top left corner of the top left pixel
+ d: rotation, 0 if the raster is oriented "north up"
+ e: rate of change of Y with respect to increasing row, usually
+ a negative number i.e. -1 * pixel height
+ f: Y coordinate of the top left corner of the top left pixel
+
+ Finally, additional kwargs are passed to GDAL as driver-specific
+ dataset creation parameters.
+ """
+ if not isinstance(path, string_types):
+ raise TypeError("invalid path: %r" % path)
+ if mode and not isinstance(mode, string_types):
+ raise TypeError("invalid mode: %r" % mode)
+ if driver and not isinstance(driver, string_types):
+ raise TypeError("invalid driver: %r" % driver)
+ if mode in ('r', 'r+'):
+ if not os.path.exists(path):
+ raise IOError("no such file or directory: %r" % path)
+ if transform:
+ transform = guard_transform(transform)
+
+ if mode == 'r':
+ from rasterio._io import RasterReader
+ s = RasterReader(path)
+ elif mode == 'r+':
+ from rasterio._io import writer
+ s = writer(path, mode)
+ elif mode == 'r-':
+ from rasterio._base import DatasetReader
+ s = DatasetReader(path)
+ elif mode == 'w':
+ from rasterio._io import writer
+ s = writer(path, mode, driver=driver,
+ width=width, height=height, count=count,
+ crs=crs, transform=transform, dtype=dtype,
+ nodata=nodata,
+ **kwargs)
+ else:
+ raise ValueError(
+ "mode string must be one of 'r', 'r+', or 'w', not %s" % mode)
+ s.start()
+ return s
+
+
+def copy(src, dst, **kw):
+ """Copy a source dataset to a new destination with driver specific
+ creation options.
+
+ ``src`` must be an existing file and ``dst`` a valid output file.
+
+ A ``driver`` keyword argument with value like 'GTiff' or 'JPEG' is
+ used to control the output format.
+
+ This is the one way to create write-once files like JPEGs.
+ """
+ from rasterio._copy import RasterCopier
+ with drivers():
+ return RasterCopier()(src, dst, **kw)
+
+
+def drivers(**kwargs):
+ """Returns a gdal environment with registered drivers."""
+ if driver_count() == 0:
+ log.debug("Creating a chief GDALEnv in drivers()")
+ return GDALEnv(True, **kwargs)
+ else:
+ log.debug("Creating a not-responsible GDALEnv in drivers()")
+ return GDALEnv(False, **kwargs)
+
+
+Band = namedtuple('Band', ['ds', 'bidx', 'dtype', 'shape'])
+
+def band(ds, bidx):
+ """Wraps a dataset and a band index up as a 'Band'"""
+ return Band(
+ ds,
+ bidx,
+ set(ds.dtypes).pop(),
+ ds.shape)
+
+
+def pad(array, transform, pad_width, mode=None, **kwargs):
+ """Returns a padded array and shifted affine transform matrix.
+
+ Array is padded using `numpy.pad()`."""
+ import numpy
+ transform = guard_transform(transform)
+ padded_array = numpy.pad(array, pad_width, mode, **kwargs)
+ padded_trans = list(transform)
+ padded_trans[2] -= pad_width*padded_trans[0]
+ padded_trans[5] -= pad_width*padded_trans[4]
+ return padded_array, Affine(*padded_trans[:6])
+
diff --git a/rasterio/_base.pxd b/rasterio/_base.pxd
new file mode 100644
index 0000000..ad5440b
--- /dev/null
+++ b/rasterio/_base.pxd
@@ -0,0 +1,25 @@
+# Base class.
+
+cdef class DatasetReader:
+ # Read-only access to dataset metadata. No IO!
+
+ cdef void *_hds
+
+ cdef readonly object name
+ cdef readonly object mode
+ cdef readonly object width, height
+ cdef readonly object shape
+ cdef public object driver
+ cdef public object _count
+ cdef public object _dtypes
+ cdef public object _closed
+ cdef public object _crs
+ cdef public object _crs_wkt
+ cdef public object _transform
+ cdef public object _block_shapes
+ cdef public object _nodatavals
+ cdef public object _read
+ cdef object env
+
+ cdef void *band(self, int bidx)
+
diff --git a/rasterio/_base.pyx b/rasterio/_base.pyx
new file mode 100644
index 0000000..87f696e
--- /dev/null
+++ b/rasterio/_base.pyx
@@ -0,0 +1,611 @@
+# The Numpy-free base classes.
+
+# cython: boundscheck=False
+
+import logging
+import math
+import sys
+import warnings
+
+from libc.stdlib cimport malloc, free
+
+from rasterio cimport _gdal, _ogr
+from rasterio._drivers import driver_count, GDALEnv
+from rasterio._err import cpl_errs
+from rasterio import dtypes
+from rasterio.coords import BoundingBox
+from rasterio.transform import Affine
+from rasterio.enums import ColorInterp
+
+
+log = logging.getLogger('rasterio')
+if 'all' in sys.warnoptions:
+ # show messages in console with: python -W all
+ logging.basicConfig()
+else:
+ # no handler messages shown
+ class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+ log.addHandler(NullHandler())
+
+
+cdef class DatasetReader(object):
+
+ def __init__(self, path):
+ self.name = path
+ self.mode = 'r'
+ self._hds = NULL
+ self._count = 0
+ self._closed = True
+ self._dtypes = []
+ self._block_shapes = None
+ self._nodatavals = []
+ self._crs = None
+ self._crs_wkt = None
+ self._read = False
+ self.env = None
+
+ def __repr__(self):
+ return "<%s RasterReader name='%s' mode='%s'>" % (
+ self.closed and 'closed' or 'open',
+ self.name,
+ self.mode)
+
+ def start(self):
+ # Is there not a driver manager already?
+ if driver_count() == 0 and not self.env:
+ # create a local manager and enter
+ self.env = GDALEnv(True)
+ else:
+ # create a local manager and enter
+ self.env = GDALEnv(False)
+ self.env.start()
+
+ name_b = self.name.encode('utf-8')
+ cdef const char *fname = name_b
+ with cpl_errs:
+ self._hds = _gdal.GDALOpen(fname, 0)
+ if self._hds == NULL:
+ raise ValueError("Null dataset")
+
+ cdef void *drv
+ cdef const char *drv_name
+ drv = _gdal.GDALGetDatasetDriver(self._hds)
+ drv_name = _gdal.GDALGetDriverShortName(drv)
+ self.driver = drv_name.decode('utf-8')
+
+ self._count = _gdal.GDALGetRasterCount(self._hds)
+ self.width = _gdal.GDALGetRasterXSize(self._hds)
+ self.height = _gdal.GDALGetRasterYSize(self._hds)
+ self.shape = (self.height, self.width)
+
+ self._transform = self.read_transform()
+ self._crs = self.read_crs()
+ self._crs_wkt = self.read_crs_wkt()
+
+ # touch self.meta
+ _ = self.meta
+
+ self._closed = False
+
+ cdef void *band(self, int bidx):
+ if self._hds == NULL:
+ raise ValueError("Null dataset")
+ cdef void *hband = _gdal.GDALGetRasterBand(self._hds, bidx)
+ if hband == NULL:
+ raise ValueError("Null band")
+ return hband
+
+ def read_crs(self):
+ cdef char *proj_c = NULL
+ cdef char *auth_key = NULL
+ cdef char *auth_val = NULL
+ cdef void *osr = NULL
+ if self._hds == NULL:
+ raise ValueError("Null dataset")
+ crs = {}
+ cdef char *wkt = _gdal.GDALGetProjectionRef(self._hds)
+ if wkt is NULL:
+ raise ValueError("Unexpected NULL spatial reference")
+ wkt_b = wkt
+ if len(wkt_b) > 0:
+ osr = _gdal.OSRNewSpatialReference(wkt)
+ if osr == NULL:
+ raise ValueError("Unexpected NULL spatial reference")
+ log.debug("Got coordinate system")
+
+ retval = _gdal.OSRAutoIdentifyEPSG(osr)
+ if retval > 0:
+ log.info("Failed to auto identify EPSG: %d", retval)
+
+ auth_key = _gdal.OSRGetAuthorityName(osr, NULL)
+ auth_val = _gdal.OSRGetAuthorityCode(osr, NULL)
+
+ if auth_key != NULL and auth_val != NULL:
+ key_b = auth_key
+ key = key_b.decode('utf-8')
+ if key == 'EPSG':
+ val_b = auth_val
+ val = val_b.decode('utf-8')
+ crs['init'] = "epsg:" + val
+ else:
+ _gdal.OSRExportToProj4(osr, &proj_c)
+ if proj_c == NULL:
+ raise ValueError("Unexpected Null spatial reference")
+ proj_b = proj_c
+ log.debug("Params: %s", proj_b)
+ value = proj_b.decode()
+ value = value.strip()
+ for param in value.split():
+ kv = param.split("=")
+ if len(kv) == 2:
+ k, v = kv
+ try:
+ v = float(v)
+ if v % 1 == 0:
+ v = int(v)
+ except ValueError:
+ # Leave v as a string
+ pass
+ elif len(kv) == 1:
+ k, v = kv[0], True
+ else:
+ raise ValueError(
+ "Unexpected proj parameter %s" % param)
+ k = k.lstrip("+")
+ crs[k] = v
+
+ _gdal.CPLFree(proj_c)
+ _gdal.OSRDestroySpatialReference(osr)
+ else:
+ log.debug("GDAL dataset has no projection.")
+ return crs
+
+ def read_crs_wkt(self):
+ cdef char *proj_c = NULL
+ cdef char *key_c = NULL
+ cdef void *osr = NULL
+ cdef char *wkt = NULL
+ if self._hds == NULL:
+ raise ValueError("Null dataset")
+ wkt = _gdal.GDALGetProjectionRef(self._hds)
+ if wkt is NULL:
+ raise ValueError("Unexpected NULL spatial reference")
+ wkt_b = wkt
+ if len(wkt_b) > 0:
+ osr = _gdal.OSRNewSpatialReference(wkt)
+ log.debug("Got coordinate system")
+ if osr != NULL:
+ retval = _gdal.OSRAutoIdentifyEPSG(osr)
+ if retval > 0:
+ log.info("Failed to auto identify EPSG: %d", retval)
+ _gdal.OSRExportToWkt(osr, &proj_c)
+ if proj_c == NULL:
+ raise ValueError("Null projection")
+ proj_b = proj_c
+ crs_wkt = proj_b.decode('utf-8')
+ _gdal.CPLFree(proj_c)
+ _gdal.OSRDestroySpatialReference(osr)
+ else:
+ log.debug("GDAL dataset has no projection.")
+ crs_wkt = None
+ return crs_wkt
+
+ def read_transform(self):
+ if self._hds == NULL:
+ raise ValueError("Null dataset")
+ cdef double gt[6]
+ _gdal.GDALGetGeoTransform(self._hds, gt)
+ transform = [0]*6
+ for i in range(6):
+ transform[i] = gt[i]
+ return transform
+
+ def stop(self):
+ if self._hds != NULL:
+ _gdal.GDALFlushCache(self._hds)
+ _gdal.GDALClose(self._hds)
+ if self.env:
+ self.env.stop()
+ self._hds = NULL
+
+ def close(self):
+ self.stop()
+ self._closed = True
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
+ def __dealloc__(self):
+ if self._hds != NULL:
+ _gdal.GDALClose(self._hds)
+
+ @property
+ def closed(self):
+ return self._closed
+
+ @property
+ def count(self):
+ if not self._count:
+ if self._hds == NULL:
+ raise ValueError("Can't read closed raster file")
+ self._count = _gdal.GDALGetRasterCount(self._hds)
+ return self._count
+
+ @property
+ def indexes(self):
+ return list(range(1, self.count+1))
+
+ @property
+ def dtypes(self):
+ """Returns an ordered list of all band data types."""
+ cdef void *hband = NULL
+ if not self._dtypes:
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ for i in range(self._count):
+ hband = _gdal.GDALGetRasterBand(self._hds, i+1)
+ self._dtypes.append(
+ dtypes.dtype_fwd[_gdal.GDALGetRasterDataType(hband)])
+ return self._dtypes
+
+ @property
+ def block_shapes(self):
+ """Returns an ordered list of block shapes for all bands.
+
+ Shapes are tuples and have the same ordering as the dataset's
+ shape: (count of image rows, count of image columns).
+ """
+ cdef void *hband = NULL
+ cdef int xsize, ysize
+ if self._block_shapes is None:
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ self._block_shapes = []
+ for i in range(self._count):
+ hband = _gdal.GDALGetRasterBand(self._hds, i+1)
+ if hband == NULL:
+ raise ValueError("Null band")
+ _gdal.GDALGetBlockSize(hband, &xsize, &ysize)
+ self._block_shapes.append((ysize, xsize))
+ return self._block_shapes
+
+ def get_nodatavals(self):
+ cdef void *hband = NULL
+ cdef object val
+ cdef int success
+ if not self._nodatavals:
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ for i in range(self._count):
+ hband = _gdal.GDALGetRasterBand(self._hds, i+1)
+ if hband == NULL:
+ raise ValueError("Null band")
+ val = _gdal.GDALGetRasterNoDataValue(hband, &success)
+ if not success:
+ val = None
+ self._nodatavals.append(val)
+ return self._nodatavals
+
+ property nodatavals:
+ def __get__(self):
+ return self.get_nodatavals()
+
+ def block_windows(self, bidx=0):
+ """Returns an iterator over a band's block windows and their
+ indexes.
+
+ The positional parameter `bidx` takes the index (starting at 1)
+ of the desired band. Block windows are tuples
+
+ ((row_start, row_stop), (col_start, col_stop))
+
+ For example, ((0, 2), (0, 2)) defines a 2 x 2 block at the upper
+ left corner of the raster dataset.
+
+ This iterator yields blocks "left to right" and "top to bottom"
+ and is similar to Python's enumerate() in that it also returns
+ indexes.
+
+ The primary use of this function is to obtain windows to pass to
+ read_band() for highly efficient access to raster block data.
+ """
+ cdef int i, j
+ block_shapes = self.block_shapes
+ if bidx < 1:
+ if len(set(block_shapes)) > 1:
+ raise ValueError(
+ "A band index must be provided when band block shapes"
+ "are inhomogeneous")
+ bidx = 1
+ h, w = block_shapes[bidx-1]
+ d, m = divmod(self.height, h)
+ nrows = d + int(m>0)
+ d, m = divmod(self.width, w)
+ ncols = d + int(m>0)
+ for j in range(nrows):
+ row = j * h
+ height = min(h, self.height - row)
+ for i in range(ncols):
+ col = i * w
+ width = min(w, self.width - col)
+ yield (j, i), ((row, row+height), (col, col+width))
+
+ property bounds:
+ """Returns the lower left and upper right bounds of the dataset
+ in the units of its coordinate reference system.
+
+ The returned value is a tuple:
+ (lower left x, lower left y, upper right x, upper right y)
+ """
+ def __get__(self):
+ a, b, c, d, e, f, _, _, _ = self.affine
+ return BoundingBox(c, f+e*self.height, c+a*self.width, f)
+
+ property res:
+ """Returns the (width, height) of pixels in the units of its
+ coordinate reference system."""
+ def __get__(self):
+ a, b, c, d, e, f, _, _, _ = self.affine
+ if b == d == 0:
+ return a, -e
+ else:
+ return math.sqrt(a*a+d*d), math.sqrt(b*b+e*e)
+
+ def ul(self, row, col):
+ """Returns the coordinates (x, y) of the upper left corner of a
+ pixel at `row` and `col` in the units of the dataset's
+ coordinate reference system.
+ """
+ a, b, c, d, e, f, _, _, _ = self.affine
+ if col < 0:
+ col += self.width
+ if row < 0:
+ row += self.height
+ return c+a*col, f+e*row
+
+ def index(self, x, y):
+ """Returns the (row, col) index of the pixel containing (x, y)."""
+ a, b, c, d, e, f, _, _, _ = self.affine
+ return int(round((y-f)/e)), int(round((x-c)/a))
+
+ def window(self, left, bottom, right, top):
+ """Returns the window corresponding to the world bounding box."""
+ ul = self.index(left, top)
+ lr = self.index(right, bottom)
+ if ul[0] < 0 or ul[1] < 0 or lr[0] > self.height or lr[1] > self.width:
+ raise ValueError("Bounding box overflows the dataset extents")
+ else:
+ return tuple(zip(ul, lr))
+
+ @property
+ def meta(self):
+ m = {
+ 'driver': self.driver,
+ 'dtype': self.dtypes[0],
+ 'nodata': self.nodatavals[0],
+ 'width': self.width,
+ 'height': self.height,
+ 'count': self.count,
+ 'crs': self.crs,
+ 'transform': self.affine.to_gdal(),
+ 'affine': self.affine }
+ self._read = True
+ return m
+
+
+ def get_crs(self):
+ # _read tells us that the CRS was read before and really is
+ # None.
+ if not self._read and self._crs is None:
+ self._crs = self.read_crs()
+ return self._crs
+
+ property crs:
+ """A mapping of PROJ.4 coordinate reference system params.
+ """
+ def __get__(self):
+ return self.get_crs()
+
+ property crs_wkt:
+ """An OGC WKT string representation of the coordinate reference
+ system.
+ """
+ def __get__(self):
+ if not self._read and self._crs_wkt is None:
+ self._crs = self.read_crs_wkt()
+ return self._crs_wkt
+
+ def get_transform(self):
+ """Returns a GDAL geotransform in its native form."""
+ if not self._read and self._transform is None:
+ self._transform = self.read_transform()
+ return self._transform
+
+ property transform:
+ """Coefficients of the affine transformation that maps col,row
+ pixel coordinates to x,y coordinates in the specified crs. The
+ coefficients of the augmented matrix are shown below.
+
+ | x | | a b c | | r |
+ | y | = | d e f | | c |
+ | 1 | | 0 0 1 | | 1 |
+
+ In Rasterio versions before 1.0 the value of this property
+ is a list of coefficients ``[c, a, b, f, d, e]``. This form
+ is *deprecated* beginning in 0.9 and in version 1.0 this
+ property will be replaced by an instance of ``affine.Affine``,
+ which is a namedtuple with coefficients in the order
+ ``(a, b, c, d, e, f)``.
+
+ Please see https://github.com/mapbox/rasterio/issues/86
+ for more details.
+ """
+ def __get__(self):
+ warnings.warn(
+ "The value of this property will change in version 1.0. "
+ "Please see https://github.com/mapbox/rasterio/issues/86 "
+ "for details.",
+ FutureWarning,
+ stacklevel=2)
+ return self.get_transform()
+
+ property affine:
+ """An instance of ``affine.Affine``. This property is a
+ transitional feature: see the docstring of ``transform``
+ (above) for more details.
+ """
+ def __get__(self):
+ return Affine.from_gdal(*self.get_transform())
+
+ def tags(self, bidx=0, ns=None):
+ """Returns a dict containing copies of the dataset or band's
+ tags.
+
+ Tags are pairs of key and value strings. Tags belong to
+ namespaces. The standard namespaces are: default (None) and
+ 'IMAGE_STRUCTURE'. Applications can create their own additional
+ namespaces.
+
+ The optional bidx argument can be used to select the tags of
+ a specific band. The optional ns argument can be used to select
+ a namespace other than the default.
+ """
+ cdef char *item_c
+ cdef void *hobj
+ cdef const char *domain_c
+ cdef char **papszStrList
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ if bidx > 0:
+ if bidx not in self.indexes:
+ raise ValueError("Invalid band index")
+ hobj = _gdal.GDALGetRasterBand(self._hds, bidx)
+ if hobj == NULL:
+ raise ValueError("NULL band")
+ else:
+ hobj = self._hds
+ if ns:
+ domain_b = ns.encode('utf-8')
+ domain_c = domain_b
+ else:
+ domain_c = NULL
+ papszStrList = _gdal.GDALGetMetadata(hobj, domain_c)
+ num_items = _gdal.CSLCount(papszStrList)
+ retval = {}
+ for i in range(num_items):
+ item_c = papszStrList[i]
+ item_b = item_c
+ item = item_b.decode('utf-8')
+ key, value = item.split('=')
+ retval[key] = value
+ return retval
+
+ def colorinterp(self, bidx):
+ """Returns the color interpretation for a band or None."""
+ cdef void *hBand
+
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ if bidx > 0:
+ if bidx not in self.indexes:
+ raise ValueError("Invalid band index")
+ hBand = _gdal.GDALGetRasterBand(self._hds, bidx)
+ if hBand == NULL:
+ raise ValueError("NULL band")
+ value = _gdal.GDALGetRasterColorInterpretation(hBand)
+ return ColorInterp(value)
+
+ def colormap(self, bidx):
+ """Returns a dict containing the colormap for a band or None."""
+ cdef void *hBand
+ cdef void *hTable
+ cdef int i
+ cdef _gdal.GDALColorEntry *color
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ if bidx > 0:
+ if bidx not in self.indexes:
+ raise ValueError("Invalid band index")
+ hBand = _gdal.GDALGetRasterBand(self._hds, bidx)
+ if hBand == NULL:
+ raise ValueError("NULL band")
+ hTable = _gdal.GDALGetRasterColorTable(hBand)
+ if hTable == NULL:
+ raise ValueError("NULL color table")
+ retval = {}
+
+ for i in range(_gdal.GDALGetColorEntryCount(hTable)):
+ color = _gdal.GDALGetColorEntry(hTable, i)
+ if color == NULL:
+ log.warn("NULL color at %d, skipping", i)
+ continue
+ log.info("Color: (%d, %d, %d, %d)", color.c1, color.c2, color.c3, color.c4)
+ retval[i] = (color.c1, color.c2, color.c3, color.c4)
+ return retval
+
+ @property
+ def kwds(self):
+ return self.tags(ns='rio_creation_kwds')
+
+# Window utils
+# A window is a 2D ndarray indexer in the form of a tuple:
+# ((row_start, row_stop), (col_start, col_stop))
+
+cpdef eval_window(object window, int height, int width):
+ """Evaluates a window tuple that might contain negative values
+ in the context of a raster height and width."""
+ cdef int r_start, r_stop, c_start, c_stop
+ try:
+ r, c = window
+ assert len(r) == 2
+ assert len(c) == 2
+ except (ValueError, TypeError, AssertionError):
+ raise ValueError("invalid window structure; expecting "
+ "((row_start, row_stop), (col_start, col_stop))")
+ r_start = r[0] or 0
+ if r_start < 0:
+ if height < 0:
+ raise ValueError("invalid height: %d" % height)
+ r_start += height
+ r_stop = r[1] or height
+ if r_stop < 0:
+ if height < 0:
+ raise ValueError("invalid height: %d" % height)
+ r_stop += height
+ if not r_stop >= r_start:
+ raise ValueError(
+ "invalid window: row range (%d, %d)" % (r_start, r_stop))
+ c_start = c[0] or 0
+ if c_start < 0:
+ if width < 0:
+ raise ValueError("invalid width: %d" % width)
+ c_start += width
+ c_stop = c[1] or width
+ if c_stop < 0:
+ if width < 0:
+ raise ValueError("invalid width: %d" % width)
+ c_stop += width
+ if not c_stop >= c_start:
+ raise ValueError(
+ "invalid window: col range (%d, %d)" % (c_start, c_stop))
+ return (r_start, r_stop), (c_start, c_stop)
+
+def window_shape(window, height=-1, width=-1):
+ """Returns shape of a window.
+
+ height and width arguments are optional if there are no negative
+ values in the window.
+ """
+ (a, b), (c, d) = eval_window(window, height, width)
+ return b-a, d-c
+
+def window_index(window):
+ return tuple(slice(*w) for w in window)
+
+def tastes_like_gdal(t):
+ return t[2] == t[4] == 0.0 and t[1] > 0 and t[5] < 0
diff --git a/rasterio/_copy.pyx b/rasterio/_copy.pyx
new file mode 100644
index 0000000..fab8593
--- /dev/null
+++ b/rasterio/_copy.pyx
@@ -0,0 +1,55 @@
+import logging
+import os
+import os.path
+
+from rasterio cimport _gdal
+
+
+log = logging.getLogger('rasterio')
+class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+log.addHandler(NullHandler())
+
+
+cdef class RasterCopier:
+
+ def __call__(self, src, dst, **kw):
+ cdef char **options = NULL
+ src_b = src.encode('utf-8')
+ cdef const char *src_c = src_b
+ dst_b = dst.encode('utf-8')
+ cdef const char *dst_c = dst_b
+ cdef void *src_ds = _gdal.GDALOpen(src_c, 0)
+ if src_ds == NULL:
+ raise ValueError("NULL source dataset")
+ driver = kw.pop('driver', 'GTiff')
+ driver_b = driver.encode('utf-8')
+ cdef const char *driver_c = driver_b
+ cdef void *drv = _gdal.GDALGetDriverByName(driver_c)
+ if drv == NULL:
+ raise ValueError("NULL driver")
+ strictness = 0
+ if kw.pop('strict', None):
+ strictness = 1
+
+ # Creation options
+ for k, v in kw.items():
+ k, v = k.upper(), v.upper()
+ key_b = k.encode('utf-8')
+ val_b = v.encode('utf-8')
+ key_c = key_b
+ val_c = val_b
+ options = _gdal.CSLSetNameValue(options, key_c, val_c)
+ log.debug("Option: %r\n", (k, v))
+
+ cdef void *dst_ds = _gdal.GDALCreateCopy(
+ drv, dst_c, src_ds, strictness, NULL, NULL, NULL)
+ if dst_ds == NULL:
+ raise ValueError("NULL destination dataset")
+ _gdal.GDALClose(src_ds)
+ _gdal.GDALClose(dst_ds)
+
+ if options:
+ _gdal.CSLDestroy(options)
+
diff --git a/rasterio/_drivers.pyx b/rasterio/_drivers.pyx
new file mode 100644
index 0000000..26f6689
--- /dev/null
+++ b/rasterio/_drivers.pyx
@@ -0,0 +1,120 @@
+# The GDAL and OGR driver registry.
+# GDAL driver management.
+
+import logging
+
+from rasterio.five import string_types
+
+cdef extern from "cpl_conv.h":
+ void CPLFree (void *ptr)
+ void CPLSetThreadLocalConfigOption (char *key, char *val)
+ const char * CPLGetConfigOption ( const char *key, const char *default)
+
+cdef extern from "cpl_error.h":
+ void CPLSetErrorHandler (void *handler)
+
+cdef extern from "gdal.h":
+ void GDALAllRegister()
+ void GDALDestroyDriverManager()
+ int GDALGetDriverCount()
+ void * GDALGetDriver(int i)
+ const char * GDALGetDriverShortName(void *driver)
+ const char * GDALGetDriverLongName(void *driver)
+
+cdef extern from "ogr_api.h":
+ void OGRRegisterAll()
+ void OGRCleanupAll()
+ int OGRGetDriverCount()
+
+log = logging.getLogger('GDAL')
+class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+log.addHandler(NullHandler())
+
+level_map = {
+ 0: 0,
+ 1: logging.DEBUG,
+ 2: logging.WARNING,
+ 3: logging.ERROR,
+ 4: logging.CRITICAL }
+
+code_map = {
+ 0: 'CPLE_None',
+ 1: 'CPLE_AppDefined',
+ 2: 'CPLE_OutOfMemory',
+ 3: 'CPLE_FileIO',
+ 4: 'CPLE_OpenFailed',
+ 5: 'CPLE_IllegalArg',
+ 6: 'CPLE_NotSupported',
+ 7: 'CPLE_AssertionFailed',
+ 8: 'CPLE_NoWriteAccess',
+ 9: 'CPLE_UserInterrupt',
+ 10: 'CPLE_ObjectNull'
+}
+
+cdef void * errorHandler(int eErrClass, int err_no, char *msg):
+ log.log(level_map[eErrClass], "%s in %s", code_map[err_no], msg)
+
+def driver_count():
+ return GDALGetDriverCount() + OGRGetDriverCount()
+
+
+cdef class GDALEnv(object):
+
+ cdef object is_chef
+ cdef public object options
+
+ def __init__(self, is_chef=True, **options):
+ self.is_chef = is_chef
+ self.options = options.copy()
+
+ def __enter__(self):
+ self.start()
+ return self
+
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
+ self.stop()
+
+ def start(self):
+ cdef const char *key_c
+ cdef const char *val_c
+ GDALAllRegister()
+ OGRRegisterAll()
+ CPLSetErrorHandler(<void *>errorHandler)
+ if driver_count() == 0:
+ raise ValueError("Drivers not registered")
+ for key, val in self.options.items():
+ key_b = key.upper().encode('utf-8')
+ key_c = key_b
+ if isinstance(val, string_types):
+ val_b = val.encode('utf-8')
+ else:
+ val_b = ('ON' if val else 'OFF').encode('utf-8')
+ val_c = val_b
+ CPLSetThreadLocalConfigOption(key_c, val_c)
+ log.debug("Option %s=%s", key, CPLGetConfigOption(key_c, NULL))
+ return self
+
+ def stop(self):
+ cdef const char *key_c
+ for key in self.options:
+ key_b = key.upper().encode('utf-8')
+ key_c = key_b
+ CPLSetThreadLocalConfigOption(key_c, NULL)
+ CPLSetErrorHandler(NULL)
+
+ def drivers(self):
+ cdef void *drv = NULL
+ cdef char *key = NULL
+ cdef char *val = NULL
+ cdef int i
+ result = {}
+ for i in range(GDALGetDriverCount()):
+ drv = GDALGetDriver(i)
+ key = GDALGetDriverShortName(drv)
+ key_b = key
+ val = GDALGetDriverLongName(drv)
+ val_b = val
+ result[key_b.decode('utf-8')] = val_b.decode('utf-8')
+ return result
diff --git a/rasterio/_err.pyx b/rasterio/_err.pyx
new file mode 100644
index 0000000..44bb201
--- /dev/null
+++ b/rasterio/_err.pyx
@@ -0,0 +1,70 @@
+"""rasterio._err
+
+Transformation of GDAL C API errors to Python exceptions using Python's
+``with`` statement and an error-handling context manager class.
+
+The ``cpl_errs`` error-handling context manager is intended for use in
+Rasterio's Cython code. When entering the body of a ``with`` statement,
+the context manager clears GDAL's error stack. On exit, the context
+manager pops the last error off the stack and raises an appropriate
+Python exception. It's otherwise pretty difficult to do this kind of
+thing. I couldn't make it work with a CPL error handler, Cython's
+C code swallows exceptions raised from C callbacks.
+
+When used to wrap a call to open a PNG in update mode
+
+ with cpl_errs:
+ cdef void *hds = GDALOpen('file.png', 1)
+ if hds == NULL:
+ raise ValueError("NULL dataset")
+
+the ValueError of last resort never gets raised because the context
+manager raises a more useful and informative error:
+
+ Traceback (most recent call last):
+ File "/Users/sean/code/rasterio/scripts/rio_insp", line 65, in <module>
+ with rasterio.open(args.src, args.mode) as src:
+ File "/Users/sean/code/rasterio/rasterio/__init__.py", line 111, in open
+ s.start()
+ ValueError: The PNG driver does not support update access to existing datasets.
+"""
+
+# CPL function declarations.
+cdef extern from "cpl_error.h":
+ int CPLGetLastErrorNo()
+ const char* CPLGetLastErrorMsg()
+ int CPLGetLastErrorType()
+ void CPLErrorReset()
+
+# Map GDAL error numbers to Python exceptions.
+exception_map = {
+ 1: RuntimeError, # CPLE_AppDefined
+ 2: MemoryError, # CPLE_OutOfMemory
+ 3: IOError, # CPLE_FileIO
+ 4: IOError, # CPLE_OpenFailed
+ 5: TypeError, # CPLE_IllegalArg
+ 6: ValueError, # CPLE_NotSupported
+ 7: AssertionError, # CPLE_AssertionFailed
+ 8: IOError, # CPLE_NoWriteAccess
+ 9: KeyboardInterrupt, # CPLE_UserInterrupt
+ 10: ValueError # ObjectNull
+ }
+
+
+cdef class GDALErrCtxManager:
+ """A manager for GDAL error handling contexts."""
+
+ def __enter__(self):
+ CPLErrorReset()
+ return self
+
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
+ cdef int err_type = CPLGetLastErrorType()
+ cdef int err_no = CPLGetLastErrorNo()
+ cdef char *msg = CPLGetLastErrorMsg()
+ # TODO: warn for err_type 2?
+ if err_type >= 3:
+ raise exception_map[err_no](msg)
+
+cpl_errs = GDALErrCtxManager()
+
diff --git a/rasterio/_example.pyx b/rasterio/_example.pyx
new file mode 100644
index 0000000..c203847
--- /dev/null
+++ b/rasterio/_example.pyx
@@ -0,0 +1,24 @@
+import numpy
+cimport numpy
+
+def compute(
+ unsigned char[:, :, :] input,
+ unsigned char[:, :, :] output):
+ # Given input and output uint8 arrays, fakes an CPU-intensive
+ # computation.
+ cdef int I, J, K
+ cdef int i, j, k, l
+ cdef double val
+ I = input.shape[0]
+ J = input.shape[1]
+ K = input.shape[2]
+ with nogil:
+ for i in range(I):
+ for j in range(J):
+ for k in range(K):
+ val = <double>input[i, j, k]
+ for l in range(2000):
+ val += 1.0
+ val -= 2000.0
+ output[~i, j, k] = <unsigned char>val
+
diff --git a/rasterio/_features.pxd b/rasterio/_features.pxd
new file mode 100644
index 0000000..9ffcacd
--- /dev/null
+++ b/rasterio/_features.pxd
@@ -0,0 +1,29 @@
+
+cdef class GeomBuilder:
+ cdef void *geom
+ cdef object code
+ cdef object geomtypename
+ cdef object ndims
+ cdef _buildCoords(self, void *geom)
+ cpdef _buildPoint(self)
+ cpdef _buildLineString(self)
+ cpdef _buildLinearRing(self)
+ cdef _buildParts(self, void *geom)
+ cpdef _buildPolygon(self)
+ cpdef _buildMultiPolygon(self)
+ cdef build(self, void *geom)
+ cpdef build_wkb(self, object wkb)
+
+
+cdef class OGRGeomBuilder:
+ cdef void * _createOgrGeometry(self, int geom_type)
+ 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)
diff --git a/rasterio/_features.pyx b/rasterio/_features.pyx
new file mode 100644
index 0000000..2fc5ec6
--- /dev/null
+++ b/rasterio/_features.pyx
@@ -0,0 +1,586 @@
+# cython: profile=True
+
+import logging
+import json
+import numpy as np
+cimport numpy as np
+from rasterio._io cimport InMemoryRaster
+from rasterio cimport _gdal, _ogr, _io
+from rasterio import dtypes
+
+
+log = logging.getLogger('rasterio')
+
+
+class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+log.addHandler(NullHandler())
+
+
+def _shapes(image, mask, connectivity, transform):
+ """
+ Return a generator of (polygon, value) for each each set of adjacent pixels
+ of the same value.
+
+ Parameters
+ ----------
+ image : numpy ndarray or rasterio Band object
+ (RasterReader, bidx namedtuple).
+ Data type must be one of rasterio.int16, rasterio.int32,
+ rasterio.uint8, rasterio.uint16, or rasterio.float32.
+ mask : numpy ndarray or rasterio Band object
+ Values of False will be excluded from feature generation
+ Must be of type rasterio.bool_
+ connectivity : int
+ Use 4 or 8 pixel connectivity for grouping pixels into features
+ transform : Affine transformation
+ If not provided, feature coordinates will be generated based on pixel
+ coordinates
+
+ Returns
+ -------
+ Generator of (polygon, value)
+ Yields a pair of (polygon, value) for each feature found in the image.
+ Polygons are GeoJSON-like dicts and the values are the associated value
+ from the image, in the data type of the image.
+ Note: due to floating point precision issues, values returned from a
+ floating point image may not exactly match the original values.
+
+ """
+
+ cdef int retval, rows, cols
+ cdef void *hband
+ cdef void *hmaskband
+ cdef void *hfdriver
+ cdef void *hfs
+ cdef void *hlayer
+ cdef void *fielddefn
+ cdef _io.RasterReader rdr
+ cdef _io.RasterReader mrdr
+ cdef char **options = NULL
+
+ cdef InMemoryRaster mem_ds = None
+ cdef InMemoryRaster mask_ds = None
+ cdef bint is_float = np.dtype(image.dtype).kind == 'f'
+ cdef int fieldtp = 0
+
+ if is_float:
+ fieldtp = 2
+
+ if isinstance(image, np.ndarray):
+ mem_ds = InMemoryRaster(image, transform)
+ hband = mem_ds.band
+ elif isinstance(image, tuple):
+ rdr = image.ds
+ hband = rdr.band(image.bidx)
+ else:
+ raise ValueError("Invalid source image")
+
+ if isinstance(mask, np.ndarray):
+ # A boolean mask must be converted to uint8 for GDAL
+ mask_ds = InMemoryRaster(mask.astype('uint8'), transform)
+ hmaskband = mask_ds.band
+ elif isinstance(mask, tuple):
+ if mask.shape != image.shape:
+ raise ValueError("Mask must have same shape as image")
+ mrdr = mask.ds
+ hmaskband = mrdr.band(mask.bidx)
+ else:
+ hmaskband = NULL
+
+ # Create an in-memory feature store.
+ hfdriver = _ogr.OGRGetDriverByName("Memory")
+ if hfdriver == NULL:
+ raise ValueError("NULL driver")
+ hfs = _ogr.OGR_Dr_CreateDataSource(hfdriver, "temp", NULL)
+ if hfs == NULL:
+ raise ValueError("NULL feature dataset")
+
+ # And a layer.
+ hlayer = _ogr.OGR_DS_CreateLayer(hfs, "polygons", NULL, 3, NULL)
+ if hlayer == NULL:
+ raise ValueError("NULL layer")
+
+ fielddefn = _ogr.OGR_Fld_Create("image_value", fieldtp)
+ if fielddefn == NULL:
+ raise ValueError("NULL field definition")
+ _ogr.OGR_L_CreateField(hlayer, fielddefn, 1)
+ _ogr.OGR_Fld_Destroy(fielddefn)
+
+ if connectivity == 8:
+ options = _gdal.CSLSetNameValue(options, "8CONNECTED", "8")
+
+ if is_float:
+ _gdal.GDALFPolygonize(hband, hmaskband, hlayer, 0, options, NULL, NULL)
+ else:
+ _gdal.GDALPolygonize(hband, hmaskband, hlayer, 0, options, NULL, NULL)
+
+ # Yield Fiona-style features
+ cdef ShapeIterator shape_iter = ShapeIterator()
+ shape_iter.hfs = hfs
+ shape_iter.hlayer = hlayer
+ shape_iter.fieldtp = fieldtp
+ for s, v in shape_iter:
+ yield s, v
+
+ if mem_ds is not None:
+ mem_ds.close()
+ if mask_ds is not None:
+ mask_ds.close()
+ if hfs != NULL:
+ _ogr.OGR_DS_Destroy(hfs)
+ if options:
+ _gdal.CSLDestroy(options)
+
+
+def _sieve(image, size, output, mask, connectivity):
+ """
+ Replaces small polygons in `image` with the value of their largest
+ neighbor. Polygons are found for each set of neighboring pixels of the
+ same value.
+
+ Parameters
+ ----------
+ image : numpy ndarray or rasterio Band object
+ (RasterReader, bidx namedtuple)
+ Must be of type rasterio.int16, rasterio.int32, rasterio.uint8,
+ rasterio.uint16, or rasterio.float32.
+ size : int
+ minimum polygon size (number of pixels) to retain.
+ output : numpy ndarray
+ Array of same shape and data type as `image` in which to store results.
+ mask : numpy ndarray or rasterio Band object
+ Values of False will be excluded from feature generation.
+ Must be of type rasterio.bool_.
+ connectivity : int
+ Use 4 or 8 pixel connectivity for grouping pixels into features.
+
+ """
+
+ cdef int retval, rows, cols
+ cdef InMemoryRaster in_mem_ds = None
+ cdef InMemoryRaster out_mem_ds = None
+ cdef InMemoryRaster mask_mem_ds = None
+ cdef void *in_band
+ cdef void *out_band
+ cdef void *mask_band
+ cdef _io.RasterReader rdr
+ cdef _io.RasterUpdater udr
+ cdef _io.RasterReader mask_reader
+
+ if isinstance(image, np.ndarray):
+ in_mem_ds = InMemoryRaster(image)
+ in_band = in_mem_ds.band
+ elif isinstance(image, tuple):
+ rdr = image.ds
+ hband = rdr.band(image.bidx)
+ else:
+ raise ValueError("Invalid source image")
+
+ if isinstance(output, np.ndarray):
+ out_mem_ds = InMemoryRaster(output)
+ out_band = out_mem_ds.band
+ elif isinstance(output, tuple):
+ udr = output.ds
+ out_band = udr.band(output.bidx)
+ else:
+ raise ValueError("Invalid output image")
+
+ if isinstance(mask, np.ndarray):
+ # A boolean mask must be converted to uint8 for GDAL
+ mask_mem_ds = InMemoryRaster(mask.astype('uint8'))
+ mask_band = mask_mem_ds.band
+ elif isinstance(mask, tuple):
+ if mask.shape != image.shape:
+ raise ValueError("Mask must have same shape as image")
+ mask_reader = mask.ds
+ mask_band = mask_reader.band(mask.bidx)
+ else:
+ mask_band = NULL
+
+ _gdal.GDALSieveFilter(
+ in_band,
+ mask_band,
+ out_band,
+ size,
+ connectivity,
+ NULL,
+ NULL,
+ NULL
+ )
+
+ # Read from out_band into output
+ _io.io_auto(output, out_band, False)
+
+ if in_mem_ds is not None:
+ in_mem_ds.close()
+ if out_mem_ds is not None:
+ out_mem_ds.close()
+ if mask_mem_ds is not None:
+ mask_mem_ds.close()
+
+
+def _rasterize(shapes, image, transform, all_touched):
+ """
+ Burns input geometries into `image`.
+
+ Parameters
+ ----------
+ shapes : iterable of (geometry, value) pairs
+ `geometry` is a GeoJSON-like object.
+ image : numpy ndarray
+ Array in which to store results.
+ transform : Affine transformation object, optional
+ Transformation applied to shape geometries into pixel coordinates.
+ all_touched : boolean, optional
+ If True, all pixels touched by geometries will be burned in.
+ If false, only pixels whose center is within the polygon or that are
+ selected by brezenhams line algorithm will be burned in.
+
+ """
+
+ cdef int retval
+ cdef size_t i
+ cdef size_t num_geometries = 0
+ cdef void **ogr_geoms = NULL
+ cdef char **options = NULL
+ cdef double *pixel_values = NULL # requires one value per geometry
+ cdef InMemoryRaster mem
+
+ try:
+ if all_touched:
+ options = _gdal.CSLSetNameValue(options, "ALL_TOUCHED", "TRUE")
+
+ # GDAL needs an array of geometries.
+ # For now, we'll build a Python list on the way to building that
+ # C array. TODO: make this more efficient.
+ all_shapes = list(shapes)
+ num_geometries = len(all_shapes)
+
+ ogr_geoms = <void **>_gdal.CPLMalloc(num_geometries * sizeof(void*))
+ pixel_values = <double *>_gdal.CPLMalloc(
+ num_geometries * sizeof(double))
+
+ for i, (geometry, value) in enumerate(all_shapes):
+ try:
+ ogr_geoms[i] = OGRGeomBuilder().build(geometry)
+ pixel_values[i] = <double>value
+ except:
+ log.error("Geometry %r at index %d with value %d skipped",
+ geometry, i, value)
+
+ with InMemoryRaster(image, transform) as mem:
+ _gdal.GDALRasterizeGeometries(
+ mem.dataset, 1, mem.band_ids,
+ num_geometries, ogr_geoms,
+ NULL, mem.transform, pixel_values,
+ options, NULL, NULL)
+
+ # Read in-memory data back into image
+ image = mem.read()
+
+ finally:
+ for i in range(num_geometries):
+ _deleteOgrGeom(ogr_geoms[i])
+ _gdal.CPLFree(ogr_geoms)
+ _gdal.CPLFree(pixel_values)
+ if options:
+ _gdal.CSLDestroy(options)
+
+
+# Mapping of OGR integer geometry types to GeoJSON type names.
+GEOMETRY_TYPES = {
+ 0: 'Unknown',
+ 1: 'Point',
+ 2: 'LineString',
+ 3: 'Polygon',
+ 4: 'MultiPoint',
+ 5: 'MultiLineString',
+ 6: 'MultiPolygon',
+ 7: 'GeometryCollection',
+ 100: 'None',
+ 101: 'LinearRing',
+ 0x80000001: '3D Point',
+ 0x80000002: '3D LineString',
+ 0x80000003: '3D Polygon',
+ 0x80000004: '3D MultiPoint',
+ 0x80000005: '3D MultiLineString',
+ 0x80000006: '3D MultiPolygon',
+ 0x80000007: '3D GeometryCollection'
+}
+
+# Mapping of GeoJSON type names to OGR integer geometry types
+GEOJSON2OGR_GEOMETRY_TYPES = dict(
+ (v, k) for k, v in GEOMETRY_TYPES.iteritems()
+)
+
+
+# Geometry related functions and classes follow.
+
+cdef void * _createOgrGeomFromWKB(object wkb) except NULL:
+ """Make an OGR geometry from a WKB string"""
+
+ geom_type = bytearray(wkb)[1]
+ cdef unsigned char *buffer = wkb
+ cdef void *cogr_geometry = _ogr.OGR_G_CreateGeometry(geom_type)
+ if cogr_geometry != NULL:
+ _ogr.OGR_G_ImportFromWkb(cogr_geometry, buffer, len(wkb))
+ return cogr_geometry
+
+
+cdef _deleteOgrGeom(void *cogr_geometry):
+ """Delete an OGR geometry"""
+
+ if cogr_geometry != NULL:
+ _ogr.OGR_G_DestroyGeometry(cogr_geometry)
+ cogr_geometry = NULL
+
+
+cdef class GeomBuilder:
+ """Builds a GeoJSON (Fiona-style) geometry from an OGR geometry."""
+
+ cdef _buildCoords(self, void *geom):
+ # Build a coordinate sequence
+ cdef int i
+ if geom == NULL:
+ raise ValueError("Null geom")
+ npoints = _ogr.OGR_G_GetPointCount(geom)
+ coords = []
+ for i in range(npoints):
+ values = [_ogr.OGR_G_GetX(geom, i), _ogr.OGR_G_GetY(geom, i)]
+ if self.ndims > 2:
+ values.append(_ogr.OGR_G_GetZ(geom, i))
+ coords.append(tuple(values))
+ return coords
+
+ cpdef _buildPoint(self):
+ return {
+ 'type': 'Point',
+ 'coordinates': self._buildCoords(self.geom)[0]
+ }
+
+ cpdef _buildLineString(self):
+ return {
+ 'type': 'LineString',
+ 'coordinates': self._buildCoords(self.geom)
+ }
+
+ cpdef _buildLinearRing(self):
+ return {
+ 'type': 'LinearRing',
+ 'coordinates': self._buildCoords(self.geom)
+ }
+
+ cdef _buildParts(self, void *geom):
+ cdef int j
+ cdef void *part
+ if geom == NULL:
+ raise ValueError("Null geom")
+ parts = []
+ for j in range(_ogr.OGR_G_GetGeometryCount(geom)):
+ part = _ogr.OGR_G_GetGeometryRef(geom, j)
+ parts.append(GeomBuilder().build(part))
+ return parts
+
+ cpdef _buildPolygon(self):
+ coordinates = [p['coordinates'] for p in self._buildParts(self.geom)]
+ return {'type': 'Polygon', 'coordinates': coordinates}
+
+ cpdef _buildMultiPolygon(self):
+ coordinates = [p['coordinates'] for p in self._buildParts(self.geom)]
+ return {'type': 'MultiPolygon', 'coordinates': coordinates}
+
+ cdef build(self, void *geom):
+ """Builds a GeoJSON object from an OGR geometry object."""
+
+ if geom == NULL:
+ raise ValueError("Null geom")
+
+ cdef unsigned int etype = _ogr.OGR_G_GetGeometryType(geom)
+ self.code = etype
+ self.geomtypename = GEOMETRY_TYPES[self.code & (~0x80000000)]
+ self.ndims = _ogr.OGR_G_GetCoordinateDimension(geom)
+ self.geom = geom
+ return getattr(self, '_build' + self.geomtypename)()
+
+ cpdef build_wkb(self, object wkb):
+ """Builds a GeoJSON object from a Well-Known Binary format (WKB)."""
+ # The only other method anyone needs to call
+ cdef object data = wkb
+ cdef void *cogr_geometry = _createOgrGeomFromWKB(data)
+ result = self.build(cogr_geometry)
+ _deleteOgrGeom(cogr_geometry)
+ return result
+
+
+cdef geometry(void *geom):
+ """Returns a GeoJSON object from an OGR geometry object."""
+
+ return GeomBuilder().build(geom)
+
+
+cdef class OGRGeomBuilder:
+ """
+ Builds an OGR geometry from GeoJSON geometry.
+ From Fiona: https://github.com/Toblerity/Fiona/blob/master/src/fiona/ogrext.pyx
+ """
+
+ cdef void * _createOgrGeometry(self, int geom_type) except NULL:
+ cdef void *cogr_geometry = _ogr.OGR_G_CreateGeometry(geom_type)
+ if cogr_geometry is NULL:
+ raise Exception(
+ "Could not create OGR Geometry of type: %i" % geom_type
+ )
+ return cogr_geometry
+
+ cdef _addPointToGeometry(self, void *cogr_geometry, object coordinate):
+ if len(coordinate) == 2:
+ x, y = coordinate
+ _ogr.OGR_G_AddPoint_2D(cogr_geometry, x, y)
+ else:
+ x, y, z = coordinate[:3]
+ _ogr.OGR_G_AddPoint(cogr_geometry, x, y, z)
+
+ cdef void * _buildPoint(self, object coordinates) except NULL:
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['Point']
+ )
+ self._addPointToGeometry(cogr_geometry, coordinates)
+ return cogr_geometry
+
+ cdef void * _buildLineString(self, object coordinates) except NULL:
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['LineString']
+ )
+ for coordinate in coordinates:
+ self._addPointToGeometry(cogr_geometry, coordinate)
+ return cogr_geometry
+
+ cdef void * _buildLinearRing(self, object coordinates) except NULL:
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['LinearRing']
+ )
+ for coordinate in coordinates:
+ self._addPointToGeometry(cogr_geometry, coordinate)
+ _ogr.OGR_G_CloseRings(cogr_geometry)
+ return cogr_geometry
+
+ cdef void * _buildPolygon(self, object coordinates) except NULL:
+ cdef void *cogr_ring
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['Polygon']
+ )
+ for ring in coordinates:
+ cogr_ring = self._buildLinearRing(ring)
+ _ogr.OGR_G_AddGeometryDirectly(cogr_geometry, cogr_ring)
+ return cogr_geometry
+
+ cdef void * _buildMultiPoint(self, object coordinates) except NULL:
+ cdef void *cogr_part
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['MultiPoint']
+ )
+ for coordinate in coordinates:
+ cogr_part = self._buildPoint(coordinate)
+ _ogr.OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)
+ return cogr_geometry
+
+ cdef void * _buildMultiLineString(self, object coordinates) except NULL:
+ cdef void *cogr_part
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['MultiLineString']
+ )
+ for line in coordinates:
+ cogr_part = self._buildLineString(line)
+ _ogr.OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)
+ return cogr_geometry
+
+ cdef void * _buildMultiPolygon(self, object coordinates) except NULL:
+ cdef void *cogr_part
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['MultiPolygon']
+ )
+ for part in coordinates:
+ cogr_part = self._buildPolygon(part)
+ _ogr.OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)
+ return cogr_geometry
+
+ cdef void * _buildGeometryCollection(self, object coordinates) except NULL:
+ cdef void *cogr_part
+ cdef void *cogr_geometry = self._createOgrGeometry(
+ GEOJSON2OGR_GEOMETRY_TYPES['GeometryCollection']
+ )
+ for part in coordinates:
+ cogr_part = OGRGeomBuilder().build(part)
+ _ogr.OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part)
+ return cogr_geometry
+
+ cdef void * build(self, object geometry) except NULL:
+ """Builds an OGR geometry from GeoJSON geometry."""
+
+ cdef object typename = geometry['type']
+ cdef object coordinates = geometry.get('coordinates')
+ if not typename or not coordinates:
+ raise ValueError("Input is not a valid geometry object")
+ if typename == 'Point':
+ return self._buildPoint(coordinates)
+ elif typename == 'LineString':
+ return self._buildLineString(coordinates)
+ elif typename == 'LinearRing':
+ return self._buildLinearRing(coordinates)
+ elif typename == 'Polygon':
+ return self._buildPolygon(coordinates)
+ elif typename == 'MultiPoint':
+ return self._buildMultiPoint(coordinates)
+ elif typename == 'MultiLineString':
+ return self._buildMultiLineString(coordinates)
+ elif typename == 'MultiPolygon':
+ return self._buildMultiPolygon(coordinates)
+ elif typename == 'GeometryCollection':
+ coordinates = geometry.get('geometries')
+ return self._buildGeometryCollection(coordinates)
+ else:
+ raise ValueError("Unsupported geometry type %s" % typename)
+
+
+# Feature extension classes and functions follow.
+
+cdef _deleteOgrFeature(void *cogr_feature):
+ """Delete an OGR feature"""
+ if cogr_feature != NULL:
+ _ogr.OGR_F_Destroy(cogr_feature)
+ cogr_feature = NULL
+
+
+cdef class ShapeIterator:
+ """Provides an iterator over shapes in an OGR feature layer."""
+
+ # Reference to its Collection
+ cdef void *hfs
+ cdef void *hlayer
+
+ cdef int fieldtp # OGR Field Type: 0=int, 2=double
+
+ def __iter__(self):
+ _ogr.OGR_L_ResetReading(self.hlayer)
+ return self
+
+ def __next__(self):
+ cdef long fid
+ cdef void *ftr
+ cdef void *geom
+ ftr = _ogr.OGR_L_GetNextFeature(self.hlayer)
+ if ftr == NULL:
+ raise StopIteration
+ if self.fieldtp == 0:
+ image_value = _ogr.OGR_F_GetFieldAsInteger(ftr, 0)
+ else:
+ image_value = _ogr.OGR_F_GetFieldAsDouble(ftr, 0)
+ geom = _ogr.OGR_F_GetGeometryRef(ftr)
+ if geom != NULL:
+ shape = GeomBuilder().build(geom)
+ else:
+ shape = None
+ _deleteOgrFeature(ftr)
+ return shape, image_value
diff --git a/rasterio/_gdal.pxd b/rasterio/_gdal.pxd
new file mode 100644
index 0000000..1214678
--- /dev/null
+++ b/rasterio/_gdal.pxd
@@ -0,0 +1,215 @@
+# GDAL function definitions.
+#
+
+cdef extern from "cpl_conv.h" nogil:
+ void * CPLMalloc (size_t)
+ void CPLFree (void *ptr)
+ void CPLSetThreadLocalConfigOption (char *key, char *val)
+ const char *CPLGetConfigOption (char *, char *)
+
+cdef extern from "cpl_string.h":
+ int CSLCount (char **papszStrList)
+ char ** CSLAddNameValue (char **papszStrList, const char *pszName, const char *pszValue)
+ char ** CSLDuplicate (char ** papszStrList)
+
+ int CSLFindName (char **papszStrList, const char *pszName)
+ const char * CSLFetchNameValue (char **papszStrList, const char *pszName)
+ char ** CSLSetNameValue (char **list, char *name, char *val)
+ void CSLDestroy (char **list)
+
+cdef extern from "ogr_srs_api.h":
+ void OSRCleanup ()
+ void * OSRClone (void *srs)
+ void OSRDestroySpatialReference (void *srs)
+ int OSRExportToProj4 (void *srs, char **params)
+ int OSRExportToWkt (void *srs, char **params)
+ int OSRImportFromEPSG (void *srs, int code)
+ int OSRImportFromProj4 (void *srs, char *proj)
+ int OSRSetFromUserInput (void *srs, char *input)
+ int OSRAutoIdentifyEPSG (void *srs)
+ int OSRFixup(void *srs)
+ const char * OSRGetAuthorityName (void *srs, const char *key)
+ const char * OSRGetAuthorityCode (void *srs, const char *key)
+ void * OSRNewSpatialReference (char *wkt)
+ void OSRRelease (void *srs)
+ void * OCTNewCoordinateTransformation (void *source, void *dest)
+ void OCTDestroyCoordinateTransformation (void *source)
+ int OCTTransform (void *ct, int nCount, double *x, double *y, double *z)
+
+cdef extern from "gdal.h" nogil:
+ void GDALAllRegister()
+ int GDALGetDriverCount()
+ void * GDALGetDriver(int)
+ const char* GDALGetDescription (void *)
+ void GDALSetDescription (void *, const char *)
+
+ void * GDALGetDriverByName(const char *name)
+ void * GDALOpen(const char *filename, int access) # except -1
+ void GDALFlushCache (void *ds)
+ void GDALClose(void *ds)
+ void * GDALGetDatasetDriver(void *ds)
+ int GDALGetGeoTransform (void *ds, double *transform)
+ const char * GDALGetProjectionRef(void *ds)
+ int GDALGetRasterXSize(void *ds)
+ int GDALGetRasterYSize(void *ds)
+ int GDALGetRasterCount(void *ds)
+ void * GDALGetRasterBand(void *ds, int num)
+ int GDALSetGeoTransform (void *ds, double *transform)
+ int GDALSetProjection(void *ds, const char *wkt)
+
+ ctypedef enum GDALDataType:
+ GDT_Unknown
+ GDT_Byte
+ GDT_UInt16
+ GDT_Int16
+ GDT_UInt32
+ GDT_Int32
+ GDT_Float32
+ GDT_Float64
+ GDT_CInt16
+ GDT_CInt32
+ GDT_CFloat32
+ GDT_CFloat64
+ GDT_TypeCount
+
+ ctypedef enum GDALRWFlag:
+ GF_Read
+ GF_Write
+
+ void GDALGetBlockSize(void *band, int *xsize, int *ysize)
+ int GDALGetRasterDataType(void *band)
+ double GDALGetRasterNoDataValue(void *band, int *success)
+ int GDALSetRasterNoDataValue(void *band, double value)
+ int GDALDatasetRasterIO(void *band, int, int xoff, int yoff, int xsize, int ysize, void *buffer, int width, int height, int, int count, int *bmap, int poff, int loff, int boff)
+ int GDALRasterIO(void *band, int, int xoff, int yoff, int xsize, int ysize, void *buffer, int width, int height, int, int poff, int loff)
+
+ int GDALSetRasterNoDataValue(void *band, double value)
+
+ void * GDALCreate(void *driver, const char *filename, int width, int height, int nbands, GDALDataType dtype, const char **options)
+ void * GDALCreateCopy(void *driver, const char *filename, void *ds, int strict, char **options, void *progress_func, void *progress_data)
+ const char * GDALGetDriverShortName(void *driver)
+ const char * GDALGetDriverLongName(void *driver)
+
+ char** GDALGetMetadata (void *hObject, const char *pszDomain)
+ int GDALSetMetadata (void *hObject, char **papszMD, const char *pszDomain)
+ const char* GDALGetMetadataItem(void *hObject, const char *pszName, const char *pszDomain)
+ int GDALSetMetadataItem (void *hObject, const char *pszName, const char *pszValue, const char *pszDomain)
+
+ ctypedef struct GDALColorEntry:
+ short c1
+ short c2
+ short c3
+ short c4
+
+ const GDALColorEntry *GDALGetColorEntry (void *hTable, int)
+ void GDALSetColorEntry (void *hTable, int i, const GDALColorEntry *poEntry)
+ int GDALSetRasterColorTable (void *hBand, void *hTable)
+ void *GDALGetRasterColorTable (void *hBand)
+ void *GDALCreateColorTable (int)
+ void GDALDestroyColorTable (void *hTable)
+ int GDALGetColorEntryCount (void *hTable)
+ int GDALGetRasterColorInterpretation (void *hBand)
+ int GDALSetRasterColorInterpretation (void *hBand, int)
+
+ void *GDALGetMaskBand (void *hBand)
+ int GDALCreateMaskBand (void *hDS, int flags)
+
+cdef extern from "gdalwarper.h":
+
+ ctypedef enum GDALResampleAlg:
+ GRA_NearestNeighbour
+ GRA_Bilinear
+ GRA_Cubic
+ GRA_CubicSpline
+ GRA_Lanczos
+ GRA_Average
+ GRA_Mode
+
+ ctypedef int (*GDALMaskFunc)( void *pMaskFuncArg,
+ int nBandCount, int eType,
+ int nXOff, int nYOff,
+ int nXSize, int nYSize,
+ unsigned char **papabyImageData,
+ int bMaskIsFloat, void *pMask )
+
+ ctypedef int (*GDALTransformerFunc)( void *pTransformerArg,
+ int bDstToSrc, int nPointCount,
+ double *x, double *y, double *z, int *panSuccess )
+
+ ctypedef struct GDALWarpOptions:
+ char **papszWarpOptions
+ double dfWarpMemoryLimit
+ GDALResampleAlg eResampleAlg
+ GDALDataType eWorkingDataType
+ void *hSrcDS
+ void *hDstDS
+ # 0 for all bands
+ int nBandCount
+ # List of source band indexes
+ int *panSrcBands
+ # List of destination band indexes
+ int *panDstBands
+ # The source band so use as an alpha (transparency) value, 0=disabled
+ int nSrcAlphaBand
+ # The dest. band so use as an alpha (transparency) value, 0=disabled
+ int nDstAlphaBand
+ # The "nodata" value real component for each input band, if NULL there isn't one */
+ double *padfSrcNoDataReal
+ # The "nodata" value imaginary component - may be NULL even if real component is provided. */
+ double *padfSrcNoDataImag
+ # The "nodata" value real component for each output band, if NULL there isn't one */
+ double *padfDstNoDataReal
+ # The "nodata" value imaginary component - may be NULL even if real component is provided. */
+ double *padfDstNoDataImag
+ # GDALProgressFunc() compatible progress reporting function, or NULL if there isn't one. */
+ void *pfnProgress
+ # Callback argument to be passed to pfnProgress. */
+ void *pProgressArg
+ # Type of spatial point transformer function */
+ GDALTransformerFunc pfnTransformer
+ # Handle to image transformer setup structure */
+ void *pTransformerArg
+ GDALMaskFunc *papfnSrcPerBandValidityMaskFunc
+ void **papSrcPerBandValidityMaskFuncArg
+ GDALMaskFunc pfnSrcValidityMaskFunc
+ void *pSrcValidityMaskFuncArg
+ GDALMaskFunc pfnSrcDensityMaskFunc
+ void *pSrcDensityMaskFuncArg
+ GDALMaskFunc pfnDstDensityMaskFunc
+ void *pDstDensityMaskFuncArg
+ GDALMaskFunc pfnDstValidityMaskFunc
+ void *pDstValidityMaskFuncArg
+ int (*pfnPreWarpChunkProcessor)( void *pKern, void *pArg )
+ void *pPreWarpProcessorArg
+ int (*pfnPostWarpChunkProcessor)( void *pKern, void *pArg)
+ void *pPostWarpProcessorArg
+ # Optional OGRPolygonH for a masking cutline. */
+ void *hCutline
+ # Optional blending distance to apply across cutline in pixels, default is 0
+ double dfCutlineBlendDist
+
+ GDALWarpOptions *GDALCreateWarpOptions()
+ void GDALDestroyWarpOptions(GDALWarpOptions *)
+
+cdef extern from "gdal_alg.h":
+
+ int GDALPolygonize(void *src_band, void *mask_band, void *layer, int fidx, char **options, void *progress_func, void *progress_data)
+ int GDALFPolygonize(void *src_band, void *mask_band, void *layer, int fidx, char **options, void *progress_func, void *progress_data)
+ int GDALSieveFilter(void *src_band, void *mask_band, void *dst_band, int size, int connectivity, char **options, void *progress_func, void *progress_data)
+ int GDALRasterizeGeometries(void *out_ds, int band_count, int *dst_bands, int geom_count, void **geometries,
+ GDALTransformerFunc transform_func, void *transform, double *pixel_values, char **options,
+ void *progress_func, void *progress_data)
+
+ void *GDALCreateGenImgProjTransformer(void* hSrcDS, const char *pszSrcWKT,
+ void* hDstDS, const char *pszDstWKT,
+ int bGCPUseOK, double dfGCPErrorThreshold,
+ int nOrder )
+ int GDALGenImgProjTransform(void *pTransformArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
+ void GDALDestroyGenImgProjTransformer( void * )
+
+ void *GDALCreateApproxTransformer( GDALTransformerFunc pfnRawTransformer, void *pRawTransformerArg, double dfMaxError )
+ int GDALApproxTransform(void *pTransformArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
+ void GDALDestroyApproxTransformer( void * )
+
+
+
diff --git a/rasterio/_io.pxd b/rasterio/_io.pxd
new file mode 100644
index 0000000..bc866ea
--- /dev/null
+++ b/rasterio/_io.pxd
@@ -0,0 +1,248 @@
+cimport numpy as np
+
+from rasterio cimport _base
+
+
+cdef extern from "gdal.h":
+
+ ctypedef enum GDALDataType:
+ GDT_Unknown
+ GDT_Byte
+ GDT_UInt16
+ GDT_Int16
+ GDT_UInt32
+ GDT_Int32
+ GDT_Float32
+ GDT_Float64
+ GDT_CInt16
+ GDT_CInt32
+ GDT_CFloat32
+ GDT_CFloat64
+ GDT_TypeCount
+
+ ctypedef enum GDALAccess:
+ GA_ReadOnly
+ GA_Update
+
+ ctypedef enum GDALRWFlag:
+ GF_Read
+ GF_Write
+
+
+cdef class RasterReader(_base.DatasetReader):
+ # Read-only access to raster data and metadata.
+ pass
+
+
+cdef class RasterUpdater(RasterReader):
+ # Read-write access to raster data and metadata.
+ cdef readonly object _init_dtype
+ cdef readonly object _init_nodata
+ cdef readonly object _options
+
+
+cdef class IndirectRasterUpdater(RasterUpdater):
+ pass
+
+
+cdef class InMemoryRaster:
+ cdef void *dataset
+ cdef void *band
+ cdef double transform[6]
+ cdef int band_ids[1]
+ cdef np.ndarray _image
+
+
+ctypedef np.uint8_t DTYPE_UBYTE_t
+ctypedef np.uint16_t DTYPE_UINT16_t
+ctypedef np.int16_t DTYPE_INT16_t
+ctypedef np.uint32_t DTYPE_UINT32_t
+ctypedef np.int32_t DTYPE_INT32_t
+ctypedef np.float32_t DTYPE_FLOAT32_t
+ctypedef np.float64_t DTYPE_FLOAT64_t
+
+cdef int io_ubyte(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint8_t[:, :] buffer)
+
+cdef int io_uint16(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint16_t[:, :] buffer)
+
+cdef int io_int16(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int16_t[:, :] buffer)
+
+cdef int io_uint32(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint32_t[:, :] buffer)
+
+cdef int io_int32(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int32_t[:, :] buffer)
+
+cdef int io_float32(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float32_t[:, :] buffer)
+
+cdef int io_float64(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float64_t[:, :] buffer)
+
+cdef int io_multi_ubyte(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint8_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil
+
+cdef int io_multi_uint16(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint16_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil
+
+cdef int io_multi_int16(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int16_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil
+
+cdef int io_multi_uint32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint32_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil
+
+cdef int io_multi_int32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int32_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil
+
+cdef int io_multi_float32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float32_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil
+
+cdef int io_multi_float64(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float64_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil
+
+cdef int io_multi_cint16(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex_t[:, :, :] out,
+ long[:] indexes,
+ int count)
+
+cdef int io_multi_cint32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex_t[:, :, :] out,
+ long[:] indexes,
+ int count)
+
+cdef int io_multi_cfloat32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex64_t[:, :, :] out,
+ long[:] indexes,
+ int count)
+
+cdef int io_multi_cfloat64(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex128_t[:, :, :] out,
+ long[:] indexes,
+ int count)
+
+cdef int io_auto(image, void *hband, bint write)
diff --git a/rasterio/_io.pyx b/rasterio/_io.pyx
new file mode 100644
index 0000000..9ae404d
--- /dev/null
+++ b/rasterio/_io.pyx
@@ -0,0 +1,1522 @@
+# cython: boundscheck=False
+
+import logging
+import math
+import os
+import os.path
+import sys
+import warnings
+
+from libc.stdlib cimport malloc, free
+import numpy as np
+cimport numpy as np
+
+from rasterio cimport _base, _gdal, _ogr, _io
+from rasterio._base import (
+ eval_window, window_shape, window_index, tastes_like_gdal)
+from rasterio._drivers import driver_count, GDALEnv
+from rasterio._err import cpl_errs
+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
+
+log = logging.getLogger('rasterio')
+if 'all' in sys.warnoptions:
+ # show messages in console with: python -W all
+ logging.basicConfig()
+else:
+ # no handler messages shown
+ class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+
+ log.addHandler(NullHandler())
+
+# Single band IO functions.
+
+cdef int io_ubyte(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint8_t[:, :] buffer):
+ with nogil:
+ return _gdal.GDALRasterIO(
+ hband, mode, xoff, yoff, width, height,
+ &buffer[0, 0], buffer.shape[1], buffer.shape[0], 1, 0, 0)
+
+cdef int io_uint16(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint16_t[:, :] buffer):
+ with nogil:
+ return _gdal.GDALRasterIO(
+ hband, mode, xoff, yoff, width, height,
+ &buffer[0, 0], buffer.shape[1], buffer.shape[0], 2, 0, 0)
+
+cdef int io_int16(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int16_t[:, :] buffer):
+ with nogil:
+ return _gdal.GDALRasterIO(
+ hband, mode, xoff, yoff, width, height,
+ &buffer[0, 0], buffer.shape[1], buffer.shape[0], 3, 0, 0)
+
+cdef int io_uint32(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint32_t[:, :] buffer):
+ with nogil:
+ return _gdal.GDALRasterIO(
+ hband, mode, xoff, yoff, width, height,
+ &buffer[0, 0], buffer.shape[1], buffer.shape[0], 4, 0, 0)
+
+cdef int io_int32(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int32_t[:, :] buffer):
+ with nogil:
+ return _gdal.GDALRasterIO(
+ hband, mode, xoff, yoff, width, height,
+ &buffer[0, 0], buffer.shape[1], buffer.shape[0], 5, 0, 0)
+
+cdef int io_float32(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float32_t[:, :] buffer):
+ with nogil:
+ return _gdal.GDALRasterIO(
+ hband, mode, xoff, yoff, width, height,
+ &buffer[0, 0], buffer.shape[1], buffer.shape[0], 6, 0, 0)
+
+cdef int io_float64(
+ void *hband,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float64_t[:, :] buffer):
+ with nogil:
+ return _gdal.GDALRasterIO(
+ hband, mode, xoff, yoff, width, height,
+ &buffer[0, 0], buffer.shape[1], buffer.shape[0], 7, 0, 0)
+
+# The multi-band IO functions.
+
+cdef int io_multi_ubyte(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint8_t[:, :, :] buffer,
+ long[:] indexes,
+ int count) nogil:
+ cdef int i, retval=0
+ cdef void *hband
+ cdef int *bandmap
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buffer[0, 0, 0], buffer.shape[2], buffer.shape[1],
+ 1, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+ return retval
+
+cdef int io_multi_uint16(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint16_t[:, :, :] buf,
+ long[:] indexes,
+ int count) nogil:
+ cdef int i, retval=0
+ cdef void *hband = NULL
+ cdef int *bandmap
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf[0, 0, 0], buf.shape[2], buf.shape[1],
+ 2, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+ return retval
+
+cdef int io_multi_int16(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int16_t[:, :, :] buf,
+ long[:] indexes,
+ int count) nogil:
+ cdef int i, retval=0
+ cdef void *hband = NULL
+ cdef int *bandmap
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf[0, 0, 0], buf.shape[2], buf.shape[1],
+ 3, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+ return retval
+
+cdef int io_multi_uint32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.uint32_t[:, :, :] buf,
+ long[:] indexes,
+ int count) nogil:
+ cdef int i, retval=0
+ cdef void *hband = NULL
+ cdef int *bandmap
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf[0, 0, 0], buf.shape[2], buf.shape[1],
+ 4, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+ return retval
+
+cdef int io_multi_int32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.int32_t[:, :, :] buf,
+ long[:] indexes,
+ int count) nogil:
+ cdef int i, retval=0
+ cdef void *hband = NULL
+ cdef int *bandmap
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf[0, 0, 0], buf.shape[2], buf.shape[1],
+ 5, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+ return retval
+
+
+cdef int io_multi_float32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float32_t[:, :, :] buf,
+ long[:] indexes,
+ int count) nogil:
+ cdef int i, retval=0
+ cdef void *hband = NULL
+ cdef int *bandmap
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf[0, 0, 0], buf.shape[2], buf.shape[1],
+ 6, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+ return retval
+
+cdef int io_multi_float64(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.float64_t[:, :, :] buf,
+ long[:] indexes,
+ int count) nogil:
+ cdef int i, retval=0
+ cdef void *hband = NULL
+ cdef int *bandmap
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf[0, 0, 0], buf.shape[2], buf.shape[1],
+ 7, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+ return retval
+
+cdef int io_multi_cint16(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex_t[:, :, :] out,
+ long[:] indexes,
+ int count):
+
+ cdef int retval=0
+ cdef int *bandmap
+ cdef int I, J, K
+ cdef int i, j, k
+ cdef np.int16_t real, imag
+
+ buf = np.empty(
+ (out.shape[0], 2*out.shape[2]*out.shape[1]),
+ dtype=np.int16)
+ cdef np.int16_t[:, :] buf_view = buf
+
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf_view[0, 0], out.shape[2], out.shape[1],
+ 8, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+
+ if retval > 0:
+ return retval
+
+ I = out.shape[0]
+ J = out.shape[1]
+ K = out.shape[2]
+ for i in range(I):
+ for j in range(J):
+ for k in range(K):
+ real = buf_view[i, 2*(j*K+k)]
+ imag = buf_view[i, 2*(j*K+k)+1]
+ out[i,j,k].real = real
+ out[i,j,k].imag = imag
+
+ return retval
+
+cdef int io_multi_cint32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex_t[:, :, :] out,
+ long[:] indexes,
+ int count):
+
+ cdef int retval=0
+ cdef int *bandmap
+ cdef int I, J, K
+ cdef int i, j, k
+ cdef np.int32_t real, imag
+
+ buf = np.empty(
+ (out.shape[0], 2*out.shape[2]*out.shape[1]),
+ dtype=np.int32)
+ cdef np.int32_t[:, :] buf_view = buf
+
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf_view[0, 0], out.shape[2], out.shape[1],
+ 9, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+
+ if retval > 0:
+ return retval
+
+ I = out.shape[0]
+ J = out.shape[1]
+ K = out.shape[2]
+ for i in range(I):
+ for j in range(J):
+ for k in range(K):
+ real = buf_view[i, 2*(j*K+k)]
+ imag = buf_view[i, 2*(j*K+k)+1]
+ out[i,j,k].real = real
+ out[i,j,k].imag = imag
+
+ return retval
+
+cdef int io_multi_cfloat32(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex64_t[:, :, :] out,
+ long[:] indexes,
+ int count):
+
+ cdef int retval=0
+ cdef int *bandmap
+ cdef int I, J, K
+ cdef int i, j, k
+ cdef np.float32_t real, imag
+
+ buf = np.empty(
+ (out.shape[0], 2*out.shape[2]*out.shape[1]),
+ dtype=np.float32)
+ cdef np.float32_t[:, :] buf_view = buf
+
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf_view[0, 0], out.shape[2], out.shape[1],
+ 10, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+
+ if retval > 0:
+ return retval
+
+ I = out.shape[0]
+ J = out.shape[1]
+ K = out.shape[2]
+ for i in range(I):
+ for j in range(J):
+ for k in range(K):
+ real = buf_view[i, 2*(j*K+k)]
+ imag = buf_view[i, 2*(j*K+k)+1]
+ out[i,j,k].real = real
+ out[i,j,k].imag = imag
+
+ return retval
+
+cdef int io_multi_cfloat64(
+ void *hds,
+ int mode,
+ int xoff,
+ int yoff,
+ int width,
+ int height,
+ np.complex128_t[:, :, :] out,
+ long[:] indexes,
+ int count):
+
+ cdef int retval=0
+ cdef int *bandmap
+ cdef int I, J, K
+ cdef int i, j, k
+ cdef np.float64_t real, imag
+
+ buf = np.empty(
+ (out.shape[0], 2*out.shape[2]*out.shape[1]),
+ dtype=np.float64)
+ cdef np.float64_t[:, :] buf_view = buf
+
+ with nogil:
+ bandmap = <int *>_gdal.CPLMalloc(count*sizeof(int))
+ for i in range(count):
+ bandmap[i] = indexes[i]
+ retval = _gdal.GDALDatasetRasterIO(
+ hds, mode, xoff, yoff, width, height,
+ &buf_view[0, 0], out.shape[2], out.shape[1],
+ 11, count, bandmap, 0, 0, 0)
+ _gdal.CPLFree(bandmap)
+
+ if retval > 0:
+ return retval
+
+ I = out.shape[0]
+ J = out.shape[1]
+ K = out.shape[2]
+ for i in range(I):
+ for j in range(J):
+ for k in range(K):
+ real = buf_view[i, 2*(j*K+k)]
+ imag = buf_view[i, 2*(j*K+k)+1]
+ out[i,j,k].real = real
+ out[i,j,k].imag = imag
+
+ return retval
+
+
+cdef int io_auto(image, void *hband, bint write):
+ """
+ Convenience function to handle IO with a GDAL band and a 2D numpy image
+
+ :param image: a numpy 2D image
+ :param hband: an instance of GDALGetRasterBand
+ :param write: 1 (True) uses write mode, 0 (False) uses read
+ :return: the return value from the data-type specific IO function
+ """
+
+ if not len(image.shape) == 2:
+ raise ValueError("Specified image must have 2 dimensions")
+
+ cdef int width = image.shape[1]
+ cdef int height = image.shape[0]
+
+ dtype_name = image.dtype.name
+
+ if dtype_name == "float32":
+ return io_float32(hband, write, 0, 0, width, height, image)
+ elif dtype_name == "float64":
+ return io_float64(hband, write, 0, 0, width, height, image)
+ elif dtype_name == "uint8":
+ return io_ubyte(hband, write, 0, 0, width, height, image)
+ elif dtype_name == "int16":
+ return io_int16(hband, write, 0, 0, width, height, image)
+ elif dtype_name == "int32":
+ return io_int32(hband, write, 0, 0, width, height, image)
+ elif dtype_name == "uint16":
+ return io_uint16(hband, write, 0, 0, width, height, image)
+ elif dtype_name == "uint32":
+ return io_uint32(hband, write, 0, 0, width, height, image)
+ else:
+ raise ValueError("Image dtype is not supported for this function."
+ "Must be float32, float64, int16, int32, uint8, "
+ "uint16, or uint32")
+
+
+cdef class RasterReader(_base.DatasetReader):
+
+ def read_band(self, bidx, out=None, window=None, masked=None):
+ """Read the `bidx` band into an `out` array if provided,
+ otherwise return a new array.
+
+ Band indexes begin with 1: read_band(1) returns the first band.
+
+ The optional `window` argument is a 2 item tuple. The first item
+ is a tuple containing the indexes of the rows at which the
+ window starts and stops and the second is a tuple containing the
+ indexes of the columns at which the window starts and stops. For
+ example, ((0, 2), (0, 2)) defines a 2x2 window at the upper left
+ of the raster dataset.
+ """
+ return self.read(bidx, out=out, window=window, masked=masked)
+
+ def read(self, indexes=None, out=None, window=None, masked=None):
+ """Read raster bands as a multidimensional array
+
+ If `indexes` is a list, the result is a 3D array, but
+ is a 2D array if it is a band index number.
+
+ Optional `out` argument is a reference to an output array with the
+ same dimensions and shape.
+
+ See `read_band` for usage of the optional `window` argument.
+
+ The return type will be either a regular NumPy array, or a masked
+ NumPy array depending on the `masked` argument. The return type is
+ forced if either `True` or `False`, but will be chosen if `None`.
+ For `masked=None` (default), the array will be the same type as
+ `out` (if used), or will be masked if any of the nodatavals are
+ not `None`.
+ """
+ cdef int height, width, xoff, yoff, aix, bidx, indexes_count
+ cdef int retval = 0
+ return2d = False
+
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ if indexes is None: # Default: read all bands
+ indexes = self.indexes
+ elif isinstance(indexes, int):
+ indexes = [indexes]
+ return2d = True
+ if out is not None and out.ndim == 2:
+ out.shape = (1,) + out.shape
+ if not indexes:
+ raise ValueError("No indexes to read")
+ check_dtypes = set()
+ nodatavals = []
+ # Check each index before processing 3D array
+ for bidx in indexes:
+ if bidx not in self.indexes:
+ raise IndexError("band index out of range")
+ idx = self.indexes.index(bidx)
+ check_dtypes.add(self.dtypes[idx])
+ nodatavals.append(self._nodatavals[idx])
+ if len(check_dtypes) > 1:
+ raise ValueError("more than one 'dtype' found")
+ elif len(check_dtypes) == 0:
+ dtype = self.dtypes[0]
+ else: # unique dtype; normal case
+ dtype = check_dtypes.pop()
+ out_shape = (len(indexes),) + (
+ window
+ and window_shape(window, self.height, self.width)
+ or self.shape)
+ if out is not None:
+ if out.dtype != dtype:
+ raise ValueError(
+ "the array's dtype '%s' does not match "
+ "the file's dtype '%s'" % (out.dtype, dtype))
+ if out.shape[0] != out_shape[0]:
+ raise ValueError(
+ "'out' shape %s does not mach raster slice shape %s" %
+ (out.shape, out_shape))
+ if masked is None:
+ masked = hasattr(out, 'mask')
+ if masked is None:
+ masked = any([x is not None for x in nodatavals])
+ if out is None:
+ if masked:
+ out = np.ma.empty(out_shape, dtype)
+ else:
+ out = np.empty(out_shape, dtype)
+
+ # Prepare the IO window.
+ if window:
+ window = eval_window(window, self.height, self.width)
+ yoff = <int>window[0][0]
+ xoff = <int>window[1][0]
+ height = <int>window[0][1] - yoff
+ width = <int>window[1][1] - xoff
+ else:
+ xoff = yoff = <int>0
+ width = <int>self.width
+ height = <int>self.height
+
+ # Call io_multi* functions with C type args so that they
+ # can release the GIL.
+ indexes_arr = np.array(indexes, dtype=int)
+ indexes_count = <int>indexes_arr.shape[0]
+ gdt = dtypes.dtype_rev[dtype]
+
+ if gdt == 1:
+ retval = io_multi_ubyte(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 2:
+ retval = io_multi_uint16(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 3:
+ retval = io_multi_int16(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 4:
+ retval = io_multi_uint32(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 5:
+ retval = io_multi_int32(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 6:
+ retval = io_multi_float32(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 7:
+ retval = io_multi_float64(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 8:
+ retval = io_multi_cint16(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 9:
+ retval = io_multi_cint32(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 10:
+ retval = io_multi_cfloat32(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+ elif gdt == 11:
+ retval = io_multi_cfloat64(
+ self._hds, 0, xoff, yoff, width, height,
+ out, indexes_arr, indexes_count)
+
+ if retval in (1, 2, 3):
+ raise IOError("Read or write failed")
+ elif retval == 4:
+ raise ValueError("NULL band")
+
+ # Masking the output. TODO: explain the logic better.
+ if masked:
+ test1nodata = set(nodatavals)
+ if len(test1nodata) == 1:
+ if nodatavals[0] is None:
+ out = np.ma.masked_array(out, copy=False)
+ elif np.isnan(nodatavals[0]):
+ out = np.ma.masked_where(np.isnan(out), out, copy=False)
+ else:
+ out = np.ma.masked_equal(out, nodatavals[0], copy=False)
+ else:
+ out = np.ma.masked_array(out, copy=False)
+ for aix in range(len(indexes)):
+ if nodatavals[aix] is None:
+ band_mask = False
+ elif np.isnan(nodatavals[aix]):
+ band_mask = np.isnan(out[aix])
+ else:
+ band_mask = out[aix] == nodatavals[aix]
+ out[aix].mask = band_mask
+
+ if return2d:
+ out.shape = out.shape[1:]
+ return out
+
+ def read_mask(self, out=None, window=None):
+ """Read the mask band into an `out` array if provided,
+ otherwise return a new array containing the dataset's
+ valid data 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
+ cdef void *hmask
+ if self._hds == NULL:
+ raise ValueError("can't write closed raster file")
+ hband = _gdal.GDALGetRasterBand(self._hds, 1)
+ if hband == NULL:
+ raise ValueError("NULL band mask")
+ hmask = _gdal.GDALGetMaskBand(hband)
+ if hmask == NULL:
+ return None
+ if out is None:
+ out_shape = (
+ window
+ and window_shape(window, self.height, self.width)
+ or self.shape)
+ out = np.empty(out_shape, np.uint8)
+ if window:
+ window = eval_window(window, self.height, self.width)
+ yoff = window[0][0]
+ xoff = window[1][0]
+ height = window[0][1] - yoff
+ width = window[1][1] - xoff
+ else:
+ xoff = yoff = 0
+ width = self.width
+ height = self.height
+ retval = io_ubyte(
+ hmask, 0, xoff, yoff, width, height, out)
+ return out
+
+
+cdef class RasterUpdater(RasterReader):
+ # Read-write access to raster data and metadata.
+ # TODO: the r+ mode.
+
+ def __init__(
+ self, path, mode, driver=None,
+ width=None, height=None, count=None,
+ crs=None, transform=None, dtype=None,
+ nodata=None,
+ **kwargs):
+ # Validate write mode arguments.
+ if mode == 'w':
+ if not isinstance(driver, string_types):
+ raise TypeError("A driver name string is required.")
+ try:
+ width = int(width)
+ height = int(height)
+ except:
+ raise TypeError("Integer width and height are required.")
+ try:
+ count = int(count)
+ except:
+ raise TypeError("Integer band count is required.")
+ try:
+ assert dtype is not None
+ _ = np.dtype(dtype)
+ except:
+ raise TypeError("A valid dtype is required.")
+ self.name = path
+ self.mode = mode
+ self.driver = driver
+ self.width = width
+ self.height = height
+ self._count = count
+ self._init_dtype = np.dtype(dtype).name
+ self._init_nodata = nodata
+ self._hds = NULL
+ self._count = count
+ self._crs = crs
+ if transform is not None:
+ self._transform = transform.to_gdal()
+ self._closed = True
+ self._dtypes = []
+ self._nodatavals = []
+ self._options = kwargs.copy()
+
+ def __repr__(self):
+ return "<%s RasterUpdater name='%s' mode='%s'>" % (
+ self.closed and 'closed' or 'open',
+ self.name,
+ self.mode)
+
+ def start(self):
+ cdef const char *drv_name = NULL
+ cdef char **options = NULL
+ cdef char *key_c, *val_c = NULL
+ cdef void *drv = NULL
+ cdef void *hband = NULL
+ cdef int success
+ name_b = self.name.encode('utf-8')
+ cdef const char *fname = name_b
+
+ # Is there not a driver manager already?
+ if driver_count() == 0 and not self.env:
+ # create a local manager and enter
+ self.env = GDALEnv(True)
+ else:
+ self.env = GDALEnv(False)
+ self.env.start()
+
+ kwds = []
+
+ if self.mode == 'w':
+ # GDAL can Create() GTiffs. Many other formats only support
+ # CreateCopy(). Rasterio lets you write GTiffs *only* for now.
+ if self.driver not in ['GTiff']:
+ raise ValueError("only GTiffs can be opened in 'w' mode")
+
+ # Delete existing file, create.
+ if os.path.exists(self.name):
+ os.unlink(self.name)
+
+ driver_b = self.driver.encode('utf-8')
+ drv_name = driver_b
+ drv = _gdal.GDALGetDriverByName(drv_name)
+ if drv == NULL:
+ raise ValueError("NULL driver for %s", self.driver)
+
+ # Find the equivalent GDAL data type or raise an exception
+ # We've mapped numpy scalar types to GDAL types so see
+ # if we can crosswalk those.
+ if hasattr(self._init_dtype, 'type'):
+ tp = self._init_dtype.type
+ if tp not in dtypes.dtype_rev:
+ raise ValueError(
+ "Unsupported dtype: %s" % self._init_dtype)
+ else:
+ gdal_dtype = dtypes.dtype_rev.get(tp)
+ else:
+ gdal_dtype = dtypes.dtype_rev.get(self._init_dtype)
+
+ # Creation options
+ for k, v in self._options.items():
+ # Skip items that are definitely *not* valid driver options.
+ if k.lower() in ['affine']:
+ continue
+ kwds.append((k.lower(), v))
+ k, v = k.upper(), str(v).upper()
+ key_b = k.encode('utf-8')
+ val_b = v.encode('utf-8')
+ key_c = key_b
+ val_c = val_b
+ options = _gdal.CSLSetNameValue(options, key_c, val_c)
+ log.debug(
+ "Option: %r\n",
+ (k, _gdal.CSLFetchNameValue(options, key_c)))
+
+ self._hds = _gdal.GDALCreate(
+ drv, fname, self.width, self.height, self._count,
+ gdal_dtype, options)
+ if self._hds == NULL:
+ raise ValueError("NULL dataset")
+
+ if self._init_nodata is not None:
+ for i in range(self._count):
+ hband = _gdal.GDALGetRasterBand(self._hds, i+1)
+ success = _gdal.GDALSetRasterNoDataValue(
+ hband, self._init_nodata)
+
+ if self._transform:
+ self.write_transform(self._transform)
+ if self._crs:
+ self.set_crs(self._crs)
+
+ elif self.mode == 'r+':
+ with cpl_errs:
+ self._hds = _gdal.GDALOpen(fname, 1)
+ if self._hds == NULL:
+ raise ValueError("NULL dataset")
+
+ drv = _gdal.GDALGetDatasetDriver(self._hds)
+ drv_name = _gdal.GDALGetDriverShortName(drv)
+ self.driver = drv_name.decode('utf-8')
+
+ self._count = _gdal.GDALGetRasterCount(self._hds)
+ self.width = _gdal.GDALGetRasterXSize(self._hds)
+ self.height = _gdal.GDALGetRasterYSize(self._hds)
+ self.shape = (self.height, self.width)
+
+ self._transform = self.read_transform()
+ self._crs = self.read_crs()
+ self._crs_wkt = self.read_crs_wkt()
+
+ if options != NULL:
+ _gdal.CSLDestroy(options)
+
+ # touch self.meta
+ _ = self.meta
+
+ self.update_tags(ns='rio_creation_kwds', **kwds)
+ self._closed = False
+
+ def set_crs(self, crs):
+ """Writes a coordinate reference system to the dataset."""
+ cdef char *proj_c = NULL
+ cdef char *wkt = NULL
+ if self._hds == NULL:
+ raise ValueError("Can't read closed raster file")
+ cdef void *osr = _gdal.OSRNewSpatialReference(NULL)
+ if osr == NULL:
+ raise ValueError("Null spatial reference")
+ params = []
+
+ log.debug("Input CRS: %r", crs)
+
+ # Normally, we expect a CRS dict.
+ if isinstance(crs, dict):
+ # EPSG is a special case.
+ init = crs.get('init')
+ if init:
+ auth, val = init.split(':')
+ if auth.upper() == 'EPSG':
+ _gdal.OSRImportFromEPSG(osr, int(val))
+ else:
+ crs['wktext'] = True
+ for k, v in crs.items():
+ if v is True or (k in ('no_defs', 'wktext') and v):
+ params.append("+%s" % k)
+ else:
+ params.append("+%s=%s" % (k, v))
+ proj = " ".join(params)
+ log.debug("PROJ.4 to be imported: %r", proj)
+ proj_b = proj.encode('utf-8')
+ proj_c = proj_b
+ _gdal.OSRImportFromProj4(osr, proj_c)
+ # Fall back for CRS strings like "EPSG:3857."
+ else:
+ proj_b = crs.encode('utf-8')
+ proj_c = proj_b
+ _gdal.OSRSetFromUserInput(osr, proj_c)
+
+ # Fixup, export to WKT, and set the GDAL dataset's projection.
+ _gdal.OSRFixup(osr)
+ _gdal.OSRExportToWkt(osr, &wkt)
+ wkt_b = wkt
+ log.debug("Exported WKT: %s", wkt_b.decode('utf-8'))
+ _gdal.GDALSetProjection(self._hds, wkt)
+
+ _gdal.CPLFree(wkt)
+ _gdal.OSRDestroySpatialReference(osr)
+ self._crs = crs
+ log.debug("Self CRS: %r", self._crs)
+
+ property crs:
+ """A mapping of PROJ.4 coordinate reference system params.
+ """
+
+ def __get__(self):
+ return self.get_crs()
+
+ def __set__(self, value):
+ self.set_crs(value)
+
+ def write_transform(self, transform):
+ if self._hds == NULL:
+ raise ValueError("Can't read closed raster file")
+ cdef double gt[6]
+ for i in range(6):
+ gt[i] = transform[i]
+ retval = _gdal.GDALSetGeoTransform(self._hds, gt)
+ self._transform = transform
+
+ property transform:
+ """An affine transformation that maps pixel row/column
+ coordinates to coordinates in the specified crs. The affine
+ transformation is represented by a six-element sequence.
+ Reference system coordinates can be calculated by the
+ following formula
+
+ X = Item 0 + Column * Item 1 + Row * Item 2
+ Y = Item 3 + Column * Item 4 + Row * Item 5
+
+ See also this class's ul() method.
+ """
+
+ def __get__(self):
+ return Affine.from_gdal(*self.get_transform())
+
+ def __set__(self, value):
+ self.write_transform(value.to_gdal())
+
+ def set_nodatavals(self, vals):
+ cdef void *hband = NULL
+ cdef double val
+ for i, val in zip(self.indexes, vals):
+ hband = _gdal.GDALGetRasterBand(self._hds, i)
+ success = _gdal.GDALSetRasterNoDataValue(hband, val)
+ if success:
+ raise ValueError("Invalid nodata values")
+ self._nodatavals = vals
+
+ property nodatavals:
+ """A list by band of a dataset's nodata values.
+ """
+
+ def __get__(self):
+ return self.get_nodatavals()
+
+ def __set__(self, value):
+ self.set_nodatavals(value)
+
+
+ def write(self, src, indexes=None, window=None):
+ """Write the src array into indexed bands of the dataset.
+
+ If `indexes` is a list, the src must be a 3D array of
+ matching shape. If an int, the src must be a 2D array.
+
+ See `read()` for usage of the optional `window` argument.
+ """
+ cdef int height, width, xoff, yoff, indexes_count
+ cdef int retval = 0
+
+ if self._hds == NULL:
+ raise ValueError("can't write to closed raster file")
+
+ if indexes is None:
+ indexes = self.indexes
+ elif isinstance(indexes, int):
+ indexes = [indexes]
+ src = np.array([src])
+ if len(src.shape) != 3 or src.shape[0] != len(indexes):
+ raise ValueError(
+ "Source shape is inconsistent with given indexes")
+
+ check_dtypes = set()
+ # Check each index before processing 3D array
+ for bidx in indexes:
+ if bidx not in self.indexes:
+ raise IndexError("band index out of range")
+ idx = self.indexes.index(bidx)
+ check_dtypes.add(self.dtypes[idx])
+ if len(check_dtypes) > 1:
+ raise ValueError("more than one 'dtype' found")
+ elif len(check_dtypes) == 0:
+ dtype = self.dtypes[0]
+ else: # unique dtype; normal case
+ dtype = check_dtypes.pop()
+
+ if src is not None and src.dtype != dtype:
+ raise ValueError(
+ "the array's dtype '%s' does not match "
+ "the file's dtype '%s'" % (src.dtype, dtype))
+
+ # Require C-continguous arrays (see #108).
+ src = np.require(src, dtype=dtype, requirements='C')
+
+ # Prepare the IO window.
+ if window:
+ window = eval_window(window, self.height, self.width)
+ yoff = <int>window[0][0]
+ xoff = <int>window[1][0]
+ height = <int>window[0][1] - yoff
+ width = <int>window[1][1] - xoff
+ else:
+ xoff = yoff = <int>0
+ width = <int>self.width
+ height = <int>self.height
+
+ # Call io_multi* functions with C type args so that they
+ # can release the GIL.
+ indexes_arr = np.array(indexes, dtype=int)
+ indexes_count = <int>indexes_arr.shape[0]
+ gdt = dtypes.dtype_rev[dtype]
+ if gdt == 1:
+ retval = io_multi_ubyte(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 2:
+ retval = io_multi_uint16(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 3:
+ retval = io_multi_int16(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 4:
+ retval = io_multi_uint32(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 5:
+ retval = io_multi_int32(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 6:
+ retval = io_multi_float32(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 7:
+ retval = io_multi_float64(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 8:
+ retval = io_multi_cint16(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 9:
+ retval = io_multi_cint32(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 10:
+ retval = io_multi_cfloat32(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+ elif gdt == 11:
+ retval = io_multi_cfloat64(
+ self._hds, 1, xoff, yoff, width, height,
+ src, indexes_arr, indexes_count)
+
+ if retval in (1, 2, 3):
+ raise IOError("Read or write failed")
+ elif retval == 4:
+ raise ValueError("NULL band")
+
+ def write_band(self, bidx, src, window=None):
+ """Write the src array into the `bidx` band.
+
+ Band indexes begin with 1: read_band(1) returns the first band.
+
+ The optional `window` argument takes a tuple like:
+
+ ((row_start, row_stop), (col_start, col_stop))
+
+ specifying a raster subset to write into.
+ """
+ self.write(src, bidx, window=window)
+
+ def update_tags(self, bidx=0, ns=None, **kwargs):
+ """Updates the tags of a dataset or one of its bands.
+
+ Tags are pairs of key and value strings. Tags belong to
+ namespaces. The standard namespaces are: default (None) and
+ 'IMAGE_STRUCTURE'. Applications can create their own additional
+ namespaces.
+
+ The optional bidx argument can be used to select the dataset
+ band. The optional ns argument can be used to select a namespace
+ other than the default.
+ """
+ cdef char *key_c = NULL
+ cdef char *value_c = NULL
+ cdef void *hobj = NULL
+ cdef const char *domain_c = NULL
+ cdef char **papszStrList = NULL
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ if bidx > 0:
+ if bidx not in self.indexes:
+ raise ValueError("Invalid band index")
+ hobj = _gdal.GDALGetRasterBand(self._hds, bidx)
+ if hobj == NULL:
+ raise ValueError("NULL band")
+ else:
+ hobj = self._hds
+ if ns:
+ domain_b = ns.encode('utf-8')
+ domain_c = domain_b
+ else:
+ domain_c = NULL
+
+ papszStrList = _gdal.CSLDuplicate(
+ _gdal.GDALGetMetadata(hobj, domain_c))
+
+ for key, value in kwargs.items():
+ key_b = text_type(key).encode('utf-8')
+ value_b = text_type(value).encode('utf-8')
+ key_c = key_b
+ value_c = value_b
+ papszStrList = _gdal.CSLSetNameValue(
+ papszStrList, key_c, value_c)
+
+ retval = _gdal.GDALSetMetadata(hobj, papszStrList, domain_c)
+ if papszStrList != NULL:
+ _gdal.CSLDestroy(papszStrList)
+
+ if retval == 2:
+ log.warn("Tags accepted but may not be persisted.")
+ elif retval == 3:
+ raise RuntimeError("Tag update failed.")
+
+ def write_colormap(self, bidx, colormap):
+ """Write a colormap for a band to the dataset."""
+ cdef void *hBand
+ cdef void *hTable
+ cdef _gdal.GDALColorEntry color
+ if self._hds == NULL:
+ raise ValueError("can't read closed raster file")
+ if bidx > 0:
+ if bidx not in self.indexes:
+ raise ValueError("Invalid band index")
+ hBand = _gdal.GDALGetRasterBand(self._hds, bidx)
+ if hBand == NULL:
+ raise ValueError("NULL band")
+ # RGB only for now. TODO: the other types.
+ # 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 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.GDALSetRasterColorTable(hBand, hTable)
+ _gdal.GDALDestroyColorTable(hTable)
+
+ def write_mask(self, src, window=None):
+ """Write the valid data mask src array into the dataset's band
+ 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
+ cdef void *hmask
+ if self._hds == NULL:
+ raise ValueError("can't write closed raster file")
+ hband = _gdal.GDALGetRasterBand(self._hds, 1)
+ if hband == NULL:
+ raise ValueError("NULL band mask")
+ if _gdal.GDALCreateMaskBand(hband, 0x02) != 0:
+ raise RuntimeError("Failed to create mask")
+ hmask = _gdal.GDALGetMaskBand(hband)
+ if hmask == NULL:
+ raise ValueError("NULL band mask")
+ log.debug("Created mask band")
+ if window:
+ window = eval_window(window, self.height, self.width)
+ yoff = window[0][0]
+ xoff = window[1][0]
+ height = window[0][1] - yoff
+ width = window[1][1] - xoff
+ else:
+ xoff = yoff = 0
+ width = self.width
+ height = self.height
+ if src.dtype == np.bool:
+ array = 255 * src.astype(np.uint8)
+ retval = io_ubyte(
+ hmask, 1, xoff, yoff, width, height, array)
+ else:
+ retval = io_ubyte(
+ hmask, 1, xoff, yoff, width, height, src)
+
+
+cdef class InMemoryRaster:
+ """
+ Class that manages a single-band in memory GDAL raster dataset. Data type
+ is determined from the data type of the input numpy 2D array (image), and
+ must be one of the data types supported by GDAL
+ (see rasterio.dtypes.dtype_rev). Data are populated at create time from
+ the 2D array passed in.
+
+ Use the 'with' pattern to instantiate this class for automatic closing
+ of the memory dataset.
+
+ This class includes attributes that are intended to be passed into GDAL
+ functions:
+ self.dataset
+ self.band
+ self.band_ids (single element array with band ID of this dataset's band)
+ self.transform (GDAL compatible transform array)
+
+ This class is only intended for internal use within rasterio to support
+ IO with GDAL. Other memory based operations should use numpy arrays.
+ """
+
+ def __cinit__(self, image, transform=None):
+ """
+ Create in-memory raster dataset, and populate its initial values with
+ the values in image.
+
+ :param image: 2D numpy array. Must be of supported data type
+ (see rasterio.dtypes.dtype_rev)
+ :param transform: GDAL compatible transform array
+ """
+
+ self._image = image
+ self.dataset = NULL
+
+ cdef void *memdriver = _gdal.GDALGetDriverByName("MEM")
+
+ # Several GDAL operations require the array of band IDs as input
+ self.band_ids[0] = 1
+
+ self.dataset = _gdal.GDALCreate(
+ memdriver,
+ "output",
+ image.shape[1],
+ image.shape[0],
+ 1,
+ <_gdal.GDALDataType>dtypes.dtype_rev[image.dtype.name],
+ NULL
+ )
+
+ if self.dataset == NULL:
+ raise ValueError("NULL output datasource")
+
+ if transform is not None:
+ for i in range(6):
+ self.transform[i] = transform[i]
+ err = _gdal.GDALSetGeoTransform(self.dataset, self.transform)
+ if err:
+ raise ValueError("transform not set: %s" % transform)
+
+ self.band = _gdal.GDALGetRasterBand(self.dataset, 1)
+ if self.band == NULL:
+ raise ValueError("NULL output band: {0}".format(i))
+
+ self.write(image)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args, **kwargs):
+ self.close()
+
+ def close(self):
+ if self.dataset != NULL:
+ _gdal.GDALClose(self.dataset)
+ self.dataset = NULL
+
+ def read(self):
+ io_auto(self._image, self.band, False)
+ return self._image
+
+ def write(self, image):
+ io_auto(image, self.band, True)
+
+
+cdef class IndirectRasterUpdater(RasterUpdater):
+
+ def __repr__(self):
+ return "<%s IndirectRasterUpdater name='%s' mode='%s'>" % (
+ self.closed and 'closed' or 'open',
+ self.name,
+ self.mode)
+
+ def start(self):
+ cdef const char *drv_name = NULL
+ cdef void *drv = NULL
+ cdef void *memdrv = NULL
+ cdef void *hband = NULL
+ cdef void *temp = NULL
+ cdef int success
+ name_b = self.name.encode('utf-8')
+ cdef const char *fname = name_b
+
+ memdrv = _gdal.GDALGetDriverByName("MEM")
+
+ # Is there not a driver manager already?
+ if driver_count() == 0 and not self.env:
+ # create a local manager and enter
+ self.env = GDALEnv(True)
+ else:
+ self.env = GDALEnv(False)
+ self.env.start()
+
+ if self.mode == 'w':
+ # Find the equivalent GDAL data type or raise an exception
+ # We've mapped numpy scalar types to GDAL types so see
+ # if we can crosswalk those.
+ if hasattr(self._init_dtype, 'type'):
+ tp = self._init_dtype.type
+ if tp not in dtypes.dtype_rev:
+ raise ValueError(
+ "Unsupported dtype: %s" % self._init_dtype)
+ else:
+ gdal_dtype = dtypes.dtype_rev.get(tp)
+ else:
+ gdal_dtype = dtypes.dtype_rev.get(self._init_dtype)
+ self._hds = _gdal.GDALCreate(
+ memdrv, "temp", self.width, self.height, self._count,
+ gdal_dtype, NULL)
+ if self._hds == NULL:
+ raise ValueError("NULL dataset")
+ if self._init_nodata is not None:
+ for i in range(self._count):
+ hband = _gdal.GDALGetRasterBand(self._hds, i+1)
+ success = _gdal.GDALSetRasterNoDataValue(
+ hband, self._init_nodata)
+ if self._transform:
+ self.write_transform(self._transform)
+ if self._crs:
+ self.set_crs(self._crs)
+
+ elif self.mode == 'r+':
+ with cpl_errs:
+ temp = _gdal.GDALOpen(fname, 0)
+ if temp == NULL:
+ raise ValueError("Null dataset")
+ self._hds = _gdal.GDALCreateCopy(
+ memdrv, "temp", temp, 1, NULL, NULL, NULL)
+ if self._hds == NULL:
+ raise ValueError("NULL dataset")
+ drv = _gdal.GDALGetDatasetDriver(temp)
+ drv_name = _gdal.GDALGetDriverShortName(drv)
+ self.driver = drv_name.decode('utf-8')
+ _gdal.GDALClose(temp)
+
+ self._count = _gdal.GDALGetRasterCount(self._hds)
+ self.width = _gdal.GDALGetRasterXSize(self._hds)
+ self.height = _gdal.GDALGetRasterYSize(self._hds)
+ self.shape = (self.height, self.width)
+
+ self._transform = self.read_transform()
+ self._crs = self.read_crs()
+ self._crs_wkt = self.read_crs_wkt()
+
+ # touch self.meta
+ _ = self.meta
+
+ self._closed = False
+
+ def close(self):
+ cdef const char *drv_name = NULL
+ cdef char **options = NULL
+ cdef char *key_c, *val_c = NULL
+ cdef void *drv = NULL
+ cdef void *temp = NULL
+ cdef int success
+ name_b = self.name.encode('utf-8')
+ cdef const char *fname = name_b
+
+ # Delete existing file, create.
+ if os.path.exists(self.name):
+ os.unlink(self.name)
+
+ driver_b = self.driver.encode('utf-8')
+ drv_name = driver_b
+ drv = _gdal.GDALGetDriverByName(drv_name)
+ if drv == NULL:
+ raise ValueError("NULL driver for %s", self.driver)
+
+ kwds = []
+ # Creation options
+ for k, v in self._options.items():
+ # Skip items that are definitely *not* valid driver options.
+ if k.lower() in ['affine']:
+ continue
+ kwds.append((k.lower(), v))
+ k, v = k.upper(), str(v).upper()
+ key_b = k.encode('utf-8')
+ val_b = v.encode('utf-8')
+ key_c = key_b
+ val_c = val_b
+ options = _gdal.CSLSetNameValue(options, key_c, val_c)
+ log.debug(
+ "Option: %r\n",
+ (k, _gdal.CSLFetchNameValue(options, key_c)))
+
+ #self.update_tags(ns='rio_creation_kwds', **kwds)
+ temp = _gdal.GDALCreateCopy(
+ drv, fname, self._hds, 1, options, NULL, NULL)
+
+ if options != NULL:
+ _gdal.CSLDestroy(options)
+
+ if temp != NULL:
+ _gdal.GDALClose(temp)
+
+
+def writer(path, mode, **kwargs):
+ # Dispatch to direct or indirect writer/updater according to the
+ # format driver's capabilities.
+ cdef void *hds = NULL
+ cdef void *drv = NULL
+ cdef char *drv_name = NULL
+ cdef const char *fname = NULL
+
+ if mode == 'w' and 'driver' in kwargs:
+ if kwargs['driver'] == 'GTiff':
+ return RasterUpdater(path, mode, **kwargs)
+ else:
+ return IndirectRasterUpdater(path, mode, **kwargs)
+ else:
+ # Peek into the dataset at path to determine it's format
+ # driver.
+ name_b = path.encode('utf-8')
+ fname = name_b
+ with cpl_errs:
+ hds = _gdal.GDALOpen(fname, 0)
+ if hds == NULL:
+ raise ValueError("NULL dataset")
+ drv = _gdal.GDALGetDatasetDriver(hds)
+ drv_name = _gdal.GDALGetDriverShortName(drv)
+ drv_name_b = drv_name
+ driver = drv_name_b.decode('utf-8')
+ _gdal.GDALClose(hds)
+ if driver == 'GTiff':
+ return RasterUpdater(path, mode)
+ else:
+ return IndirectRasterUpdater(path, mode)
diff --git a/rasterio/_ogr.pxd b/rasterio/_ogr.pxd
new file mode 100644
index 0000000..9fb3796
--- /dev/null
+++ b/rasterio/_ogr.pxd
@@ -0,0 +1,99 @@
+
+ctypedef int OGRErr
+ctypedef struct OGREnvelope:
+ double MinX
+ double MaxX
+ double MinY
+ double MaxY
+
+cdef extern from "ogr_core.h":
+ char * OGRGeometryTypeToName(int)
+
+cdef extern from "ogr_api.h":
+ char * OGR_Dr_GetName (void *driver)
+ void * OGR_Dr_CreateDataSource (void *driver, char *path, char **options)
+ int OGR_Dr_DeleteDataSource (void *driver, char *)
+ int OGR_DS_DeleteLayer (void *datasource, int n)
+ void * OGR_DS_CreateLayer (void *datasource, char *name, void *crs, int geomType, char **options)
+ void * OGR_DS_ExecuteSQL (void *datasource, char *name, void *filter, char *dialext)
+ void OGR_DS_Destroy (void *datasource)
+ void * OGR_DS_GetDriver (void *layer_defn)
+ void * OGR_DS_GetLayerByName (void *datasource, char *name)
+ int OGR_DS_GetLayerCount (void *datasource)
+ void * OGR_DS_GetLayer (void *datasource, int n)
+ void OGR_DS_ReleaseResultSet (void *datasource, void *results)
+ int OGR_DS_SyncToDisk (void *datasource)
+ void * OGR_F_Create (void *featuredefn)
+ void OGR_F_Destroy (void *feature)
+ long OGR_F_GetFID (void *feature)
+ int OGR_F_IsFieldSet (void *feature, int n)
+ int OGR_F_GetFieldAsDateTime (void *feature, int n, int *y, int *m, int *d, int *h, int *m, int *s, int *z)
+ double OGR_F_GetFieldAsDouble (void *feature, int n)
+ int OGR_F_GetFieldAsInteger (void *feature, int n)
+ char * OGR_F_GetFieldAsString (void *feature, int n)
+ int OGR_F_GetFieldCount (void *feature)
+ void * OGR_F_GetFieldDefnRef (void *feature, int n)
+ int OGR_F_GetFieldIndex (void *feature, char *name)
+ void * OGR_F_GetGeometryRef (void *feature)
+ void OGR_F_SetFieldDateTime (void *feature, int n, int y, int m, int d, int hh, int mm, int ss, int tz)
+ void OGR_F_SetFieldDouble (void *feature, int n, double value)
+ void OGR_F_SetFieldInteger (void *feature, int n, int value)
+ void OGR_F_SetFieldString (void *feature, int n, char *value)
+ int OGR_F_SetGeometryDirectly (void *feature, void *geometry)
+ void * OGR_FD_Create (char *name)
+ int OGR_FD_GetFieldCount (void *featuredefn)
+ void * OGR_FD_GetFieldDefn (void *featuredefn, int n)
+ int OGR_FD_GetGeomType (void *featuredefn)
+ char * OGR_FD_GetName (void *featuredefn)
+ void * OGR_Fld_Create (char *name, int fieldtype)
+ void OGR_Fld_Destroy (void *fielddefn)
+ char * OGR_Fld_GetNameRef (void *fielddefn)
+ int OGR_Fld_GetPrecision (void *fielddefn)
+ int OGR_Fld_GetType (void *fielddefn)
+ int OGR_Fld_GetWidth (void *fielddefn)
+ void OGR_Fld_Set (void *fielddefn, char *name, int fieldtype, int width, int precision, int justification)
+ void OGR_Fld_SetPrecision (void *fielddefn, int n)
+ void OGR_Fld_SetWidth (void *fielddefn, int n)
+ OGRErr OGR_G_AddGeometryDirectly (void *geometry, void *part)
+ void OGR_G_AddPoint (void *geometry, double x, double y, double z)
+ void OGR_G_AddPoint_2D (void *geometry, double x, double y)
+ void OGR_G_CloseRings (void *geometry)
+ void * OGR_G_CreateGeometry (int wkbtypecode)
+ void * OGR_G_CreateGeometryFromJson(char *json)
+ void OGR_G_DestroyGeometry (void *geometry)
+ unsigned char * OGR_G_ExportToJson (void *geometry)
+ void OGR_G_ExportToWkb (void *geometry, int endianness, char *buffer)
+ int OGR_G_GetCoordinateDimension (void *geometry)
+ int OGR_G_GetGeometryCount (void *geometry)
+ unsigned char * OGR_G_GetGeometryName (void *geometry)
+ int OGR_G_GetGeometryType (void *geometry)
+ void * OGR_G_GetGeometryRef (void *geometry, int n)
+ int OGR_G_GetPointCount (void *geometry)
+ double OGR_G_GetX (void *geometry, int n)
+ double OGR_G_GetY (void *geometry, int n)
+ double OGR_G_GetZ (void *geometry, int n)
+ void OGR_G_ImportFromWkb (void *geometry, unsigned char *bytes, int nbytes)
+ int OGR_G_WkbSize (void *geometry)
+ OGRErr OGR_L_CreateFeature (void *layer, void *feature)
+ int OGR_L_CreateField (void *layer, void *fielddefn, int flexible)
+ OGRErr OGR_L_GetExtent (void *layer, void *extent, int force)
+ void * OGR_L_GetFeature (void *layer, int n)
+ int OGR_L_GetFeatureCount (void *layer, int m)
+ void * OGR_L_GetLayerDefn (void *layer)
+ char * OGR_L_GetName (void *layer)
+ void * OGR_L_GetNextFeature (void *layer)
+ void * OGR_L_GetSpatialFilter (void *layer)
+ void * OGR_L_GetSpatialRef (void *layer)
+ void OGR_L_ResetReading (void *layer)
+ void OGR_L_SetSpatialFilter (void *layer, void *geometry)
+ void OGR_L_SetSpatialFilterRect (
+ void *layer, double minx, double miny, double maxx, double maxy
+ )
+ int OGR_L_TestCapability (void *layer, char *name)
+ void * OGRGetDriverByName (char *)
+ void * OGROpen (char *path, int mode, void *x)
+ void * OGROpenShared (char *path, int mode, void *x)
+ int OGRReleaseDataSource (void *datasource)
+ void OGRRegisterAll()
+ void OGRCleanupAll()
+
diff --git a/rasterio/_warp.pyx b/rasterio/_warp.pyx
new file mode 100644
index 0000000..3137bf2
--- /dev/null
+++ b/rasterio/_warp.pyx
@@ -0,0 +1,532 @@
+# distutils: language = c++
+# cython: profile=True
+#
+
+from collections import namedtuple
+import logging
+
+import numpy as np
+cimport numpy as np
+
+from rasterio cimport _gdal, _ogr, _io, _features
+from rasterio import dtypes
+
+
+cdef extern from "gdalwarper.h" nogil:
+
+ ctypedef struct GDALWarpOptions
+
+ cdef cppclass GDALWarpOperation:
+ GDALWarpOperation() except +
+ int Initialize(const GDALWarpOptions *psNewOptions)
+ const GDALWarpOptions *GetOptions()
+ int ChunkAndWarpImage(
+ int nDstXOff, int nDstYOff, int nDstXSize, int nDstYSize )
+ int ChunkAndWarpMulti(
+ int nDstXOff, int nDstYOff, int nDstXSize, int nDstYSize )
+ int WarpRegion( int nDstXOff, int nDstYOff,
+ int nDstXSize, int nDstYSize,
+ int nSrcXOff=0, int nSrcYOff=0,
+ int nSrcXSize=0, int nSrcYSize=0,
+ double dfProgressBase=0.0, double dfProgressScale=1.0)
+ int WarpRegionToBuffer( int nDstXOff, int nDstYOff,
+ int nDstXSize, int nDstYSize,
+ void *pDataBuf,
+ int eBufDataType,
+ int nSrcXOff=0, int nSrcYOff=0,
+ int nSrcXSize=0, int nSrcYSize=0,
+ double dfProgressBase=0.0,
+ double dfProgressScale=1.0)
+
+
+RESAMPLING = namedtuple('RESAMPLING', [
+ 'nearest',
+ 'bilinear',
+ 'cubic',
+ 'cubic_spline',
+ 'lanczos',
+ 'average',
+ 'mode'] )(*list(range(7)))
+
+
+cdef extern from "ogr_geometry.h" nogil:
+
+ cdef cppclass OGRGeometry:
+ pass
+
+ cdef cppclass OGRGeometryFactory:
+ void * transformWithOptions(void *geom, void *ct, char **options)
+# const OGRGeometry* poSrcGeom,
+# OGRCoordinateTransformation *poCT,
+# char** papszOptions
+
+
+cdef extern from "ogr_spatialref.h":
+
+ cdef cppclass OGRCoordinateTransformation:
+ pass
+
+
+log = logging.getLogger('rasterio')
+class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+log.addHandler(NullHandler())
+
+
+def tastes_like_gdal(t):
+ return t[2] == t[4] == 0.0 and t[1] > 0 and t[5] < 0
+
+
+cdef void *_osr_from_crs(object crs):
+ cdef char *proj_c = NULL
+ cdef void *osr
+ osr = _gdal.OSRNewSpatialReference(NULL)
+ params = []
+ # Normally, we expect a CRS dict.
+ if isinstance(crs, dict):
+ # EPSG is a special case.
+ init = crs.get('init')
+ if init:
+ auth, val = init.split(':')
+ if auth.upper() == 'EPSG':
+ _gdal.OSRImportFromEPSG(osr, int(val))
+ else:
+ crs['wktext'] = True
+ for k, v in crs.items():
+ if v is True or (k in ('no_defs', 'wktext') and v):
+ params.append("+%s" % k)
+ else:
+ params.append("+%s=%s" % (k, v))
+ proj = " ".join(params)
+ log.debug("PROJ.4 to be imported: %r", proj)
+ proj_b = proj.encode('utf-8')
+ proj_c = proj_b
+ _gdal.OSRImportFromProj4(osr, proj_c)
+ # Fall back for CRS strings like "EPSG:3857."
+ else:
+ proj_b = crs.encode('utf-8')
+ proj_c = proj_b
+ _gdal.OSRSetFromUserInput(osr, proj_c)
+ return osr
+
+
+def _transform(src_crs, dst_crs, xs, ys):
+ cdef double *x, *y
+ cdef char *proj_c = NULL
+ cdef void *src, *dst
+ cdef void *transform
+ cdef int i
+
+ assert len(xs) == len(ys)
+
+ src = _osr_from_crs(src_crs)
+ dst = _osr_from_crs(dst_crs)
+
+ n = len(xs)
+ x = <double *>_gdal.CPLMalloc(n*sizeof(double))
+ y = <double *>_gdal.CPLMalloc(n*sizeof(double))
+ for i in range(n):
+ x[i] = xs[i]
+ y[i] = ys[i]
+
+ transform = _gdal.OCTNewCoordinateTransformation(src, dst)
+ res = _gdal.OCTTransform(transform, n, x, y, NULL)
+ #if res:
+ # raise ValueError("Failed coordinate transformation")
+
+ res_xs = [0]*n
+ res_ys = [0]*n
+
+ for i in range(n):
+ res_xs[i] = x[i]
+ res_ys[i] = y[i]
+
+ _gdal.CPLFree(x)
+ _gdal.CPLFree(y)
+ _gdal.OCTDestroyCoordinateTransformation(transform)
+ _gdal.OSRDestroySpatialReference(src)
+ _gdal.OSRDestroySpatialReference(dst)
+ return res_xs, res_ys
+
+
+def _transform_geom(
+ src_crs, dst_crs, geom, antimeridian_cutting, antimeridian_offset,
+ precision):
+ """Return a transformed geometry."""
+ cdef char *proj_c = NULL
+ cdef char *key_c = NULL
+ cdef char *val_c = NULL
+ cdef char **options = NULL
+ cdef void *src, *dst
+ cdef void *transform
+ cdef OGRGeometryFactory *factory
+ cdef void *src_ogr_geom
+ cdef void *dst_ogr_geom
+ cdef int i
+
+ src = _osr_from_crs(src_crs)
+ dst = _osr_from_crs(dst_crs)
+ transform = _gdal.OCTNewCoordinateTransformation(src, dst)
+
+ # Transform options.
+ val_b = str(antimeridian_offset).encode('utf-8')
+ val_c = val_b
+ options = _gdal.CSLSetNameValue(
+ options, "DATELINEOFFSET", val_c)
+ if antimeridian_cutting:
+ options = _gdal.CSLSetNameValue(options, "WRAPDATELINE", "YES")
+
+ factory = new OGRGeometryFactory()
+ src_ogr_geom = _features.OGRGeomBuilder().build(geom)
+ dst_ogr_geom = factory.transformWithOptions(
+ <const OGRGeometry *>src_ogr_geom,
+ <OGRCoordinateTransformation *>transform,
+ options)
+ g = _features.GeomBuilder().build(dst_ogr_geom)
+
+ _ogr.OGR_G_DestroyGeometry(dst_ogr_geom)
+ _ogr.OGR_G_DestroyGeometry(src_ogr_geom)
+ _gdal.OCTDestroyCoordinateTransformation(transform)
+ if options != NULL:
+ _gdal.CSLDestroy(options)
+ _gdal.OSRDestroySpatialReference(src)
+ _gdal.OSRDestroySpatialReference(dst)
+
+ if precision >= 0:
+ if g['type'] == 'Point':
+ x, y = g['coordinates']
+ x = round(x, precision)
+ y = round(y, precision)
+ new_coords = [x, y]
+ elif g['type'] in ['LineString', 'MultiPoint']:
+ xp, yp = zip(*g['coordinates'])
+ xp = [round(v, precision) for v in xp]
+ yp = [round(v, precision) for v in yp]
+ new_coords = list(zip(xp, yp))
+ elif g['type'] in ['Polygon', 'MultiLineString']:
+ new_coords = []
+ for piece in g['coordinates']:
+ xp, yp = zip(*piece)
+ xp = [round(v, precision) for v in xp]
+ yp = [round(v, precision) for v in yp]
+ new_coords.append(list(zip(xp, yp)))
+ elif g['type'] == 'MultiPolygon':
+ parts = g['coordinates']
+ new_coords = []
+ for part in parts:
+ inner_coords = []
+ for ring in part:
+ xp, yp = zip(*ring)
+ xp = [round(v, precision) for v in xp]
+ yp = [round(v, precision) for v in yp]
+ inner_coords.append(list(zip(xp, yp)))
+ new_coords.append(inner_coords)
+ g['coordinates'] = new_coords
+
+ return g
+
+
+def _reproject(
+ source, destination,
+ src_transform=None, src_crs=None,
+ dst_transform=None, dst_crs=None,
+ resampling=RESAMPLING.nearest,
+ **kwargs):
+ """Reproject a source raster to a destination.
+
+ If the source and destination are ndarrays, coordinate reference
+ system definitions and affine transformation parameters are required
+ for reprojection.
+
+ If the source and destination are rasterio Bands, shorthand for
+ bands of datasets on disk, the coordinate reference systems and
+ transforms will be read from the appropriate datasets.
+ """
+ cdef int retval, rows, cols
+ cdef void *hrdriver
+ cdef void *hdsin
+ cdef void *hdsout
+ cdef void *hbandin
+ cdef void *hbandout
+ cdef _io.RasterReader rdr
+ cdef _io.RasterUpdater udr
+ cdef _io.GDALAccess GA
+ cdef double gt[6]
+ cdef char *srcwkt = NULL
+ cdef char *dstwkt= NULL
+ cdef const char *proj_c
+ cdef void *osr
+ cdef char **warp_extras
+ cdef char *key_c
+ cdef char *val_c
+ cdef const char* pszWarpThreads
+
+ # If the source is an ndarray, we copy to a MEM dataset.
+ # We need a src_transform and src_dst in this case. These will
+ # be copied to the MEM dataset.
+ if isinstance(source, np.ndarray):
+ # Convert 2D single-band arrays to 3D multi-band.
+ if len(source.shape) == 2:
+ source = source.reshape(1, *source.shape)
+ src_count = source.shape[0]
+ rows = source.shape[1]
+ cols = source.shape[2]
+ dtype = np.dtype(source.dtype).name
+ hrdriver = _gdal.GDALGetDriverByName("MEM")
+ if hrdriver == NULL:
+ raise ValueError("NULL driver for 'MEM'")
+
+ hdsin = _gdal.GDALCreate(
+ hrdriver, "input", cols, rows,
+ src_count, dtypes.dtype_rev[dtype], NULL)
+ if hdsin == NULL:
+ raise ValueError("NULL input datasource")
+ _gdal.GDALSetDescription(
+ hdsin, "Temporary source dataset for _reproject()")
+ log.debug("Created temp source dataset")
+ for i in range(6):
+ gt[i] = src_transform[i]
+ retval = _gdal.GDALSetGeoTransform(hdsin, gt)
+ log.debug("Set transform on temp source dataset: %d", retval)
+ osr = _gdal.OSRNewSpatialReference(NULL)
+ if osr == NULL:
+ raise ValueError("Null spatial reference")
+ params = []
+ for k, v in src_crs.items():
+ if v is True or (k == 'no_defs' and v):
+ params.append("+%s" % k)
+ else:
+ params.append("+%s=%s" % (k, v))
+ proj = " ".join(params)
+ proj_b = proj.encode()
+ proj_c = proj_b
+ _gdal.OSRImportFromProj4(osr, proj_c)
+ _gdal.OSRExportToWkt(osr, &srcwkt)
+ _gdal.GDALSetProjection(hdsin, srcwkt)
+ _gdal.CPLFree(srcwkt)
+ _gdal.OSRDestroySpatialReference(osr)
+ log.debug("Set CRS on temp source dataset: %s", srcwkt)
+
+ # Copy arrays to the dataset.
+ #hbandin = _gdal.GDALGetRasterBand(hdsin, 1)
+ #if hbandin == NULL:
+ # raise ValueError("NULL input band")
+ #log.debug("Got temp source band")
+ indexes = np.array(range(1, src_count+1))
+ if dtype == dtypes.ubyte:
+ retval = _io.io_multi_ubyte(
+ hdsin, 1, 0, 0, cols, rows, source, indexes, src_count)
+ elif dtype == dtypes.uint16:
+ retval = _io.io_multi_uint16(
+ hdsin, 1, 0, 0, cols, rows, source, indexes, src_count)
+ elif dtype == dtypes.int16:
+ retval = _io.io_multi_int16(
+ hdsin, 1, 0, 0, cols, rows, source, indexes, src_count)
+ elif dtype == dtypes.uint32:
+ retval = _io.io_multi_uint32(
+ hdsin, 1, 0, 0, cols, rows, source, indexes, src_count)
+ elif dtype == dtypes.int32:
+ retval = _io.io_multi_int32(
+ hdsin, 1, 0, 0, cols, rows, source, indexes, src_count)
+ elif dtype == dtypes.float32:
+ retval = _io.io_multi_float32(
+ hdsin, 1, 0, 0, cols, rows, source, indexes, src_count)
+ elif dtype == dtypes.float64:
+ retval = _io.io_multi_float64(
+ hdsin, 1, 0, 0, cols, rows, source, indexes, src_count)
+ else:
+ raise ValueError("Invalid dtype")
+ # TODO: handle errors (by retval).
+ log.debug("Wrote array to temp source dataset")
+
+ # If the source is a rasterio Band, no copy necessary.
+ elif isinstance(source, tuple):
+ rdr = source.ds
+ hdsin = rdr._hds
+ src_count = 1
+ else:
+ raise ValueError("Invalid source")
+
+ # Next, do the same for the destination raster.
+ if isinstance(destination, np.ndarray):
+ if len(destination.shape) == 2:
+ 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'")
+ _, rows, cols = destination.shape
+ hdsout = _gdal.GDALCreate(
+ hrdriver, "output", cols, rows, src_count,
+ dtypes.dtype_rev[np.dtype(destination.dtype).name], NULL)
+ if hdsout == NULL:
+ raise ValueError("NULL output datasource")
+ _gdal.GDALSetDescription(
+ hdsout, "Temporary destination dataset for _reproject()")
+ log.debug("Created temp destination dataset")
+ for i in range(6):
+ gt[i] = dst_transform[i]
+ retval = _gdal.GDALSetGeoTransform(hdsout, gt)
+ log.debug("Set transform on temp destination dataset: %d", retval)
+ osr = _gdal.OSRNewSpatialReference(NULL)
+ if osr == NULL:
+ raise ValueError("Null spatial reference")
+ params = []
+ for k, v in dst_crs.items():
+ if v is True or (k == 'no_defs' and v):
+ params.append("+%s" % k)
+ else:
+ params.append("+%s=%s" % (k, v))
+ proj = " ".join(params)
+ log.debug("Proj4 string: %s", proj)
+ proj_b = proj.encode()
+ proj_c = proj_b
+ _gdal.OSRImportFromProj4(osr, proj_c)
+ _gdal.OSRExportToWkt(osr, &dstwkt)
+ retval = _gdal.GDALSetProjection(hdsout, dstwkt)
+ log.debug("Setting Projection: %d", retval)
+ _gdal.CPLFree(dstwkt)
+ _gdal.OSRDestroySpatialReference(osr)
+ log.debug("Set CRS on temp destination dataset: %s", dstwkt)
+
+ elif isinstance(destination, tuple):
+ udr = destination.ds
+ hdsout = udr._hds
+ else:
+ raise ValueError("Invalid destination")
+
+ cdef void *hTransformArg = NULL
+ cdef _gdal.GDALWarpOptions *psWOptions = NULL
+ cdef GDALWarpOperation *oWarper = new GDALWarpOperation()
+ reprojected = False
+
+ try:
+ hTransformArg = _gdal.GDALCreateGenImgProjTransformer(
+ hdsin, NULL, hdsout, NULL,
+ 1, 1000.0, 0)
+ if hTransformArg == NULL:
+ raise ValueError("NULL transformer")
+ log.debug("Created transformer")
+
+ psWOptions = _gdal.GDALCreateWarpOptions()
+
+ warp_extras = psWOptions.papszWarpOptions
+ for k, v in kwargs.items():
+ k, v = k.upper(), str(v).upper()
+ key_b = k.encode('utf-8')
+ val_b = v.encode('utf-8')
+ key_c = key_b
+ val_c = val_b
+ warp_extras = _gdal.CSLSetNameValue(warp_extras, key_c, val_c)
+
+ pszWarpThreads = _gdal.CSLFetchNameValue(warp_extras, "NUM_THREADS")
+ if pszWarpThreads == NULL:
+ pszWarpThreads = _gdal.CPLGetConfigOption(
+ "GDAL_NUM_THREADS", "1")
+ warp_extras = _gdal.CSLSetNameValue(
+ warp_extras, "NUM_THREADS", pszWarpThreads)
+
+ log.debug("Created warp options")
+
+ psWOptions.eResampleAlg = <_gdal.GDALResampleAlg>resampling
+ # TODO: Approximate transformations.
+ #if maxerror > 0.0:
+ # psWOptions.pTransformerArg = _gdal.GDALCreateApproxTransformer(
+ # _gdal.GDALGenImgProjTransform,
+ # hTransformArg,
+ # maxerror )
+ # psWOptions.pfnTransformer = _gdal.GDALApproxTransform
+ #else:
+ psWOptions.pfnTransformer = _gdal.GDALGenImgProjTransform
+ psWOptions.pTransformerArg = hTransformArg
+ psWOptions.hSrcDS = hdsin
+ psWOptions.hDstDS = hdsout
+ psWOptions.nBandCount = src_count
+ psWOptions.panSrcBands = <int *>_gdal.CPLMalloc(src_count*sizeof(int))
+ psWOptions.panDstBands = <int *>_gdal.CPLMalloc(src_count*sizeof(int))
+ if isinstance(source, tuple):
+ psWOptions.panSrcBands[0] = source.bidx
+ else:
+ for i in range(src_count):
+ psWOptions.panSrcBands[i] = i+1
+ if isinstance(destination, tuple):
+ psWOptions.panDstBands[0] = destination.bidx
+ else:
+ for i in range(src_count):
+ psWOptions.panDstBands[i] = i+1
+ log.debug("Set transformer options")
+
+ # TODO: Src nodata and alpha band.
+
+ eErr = oWarper.Initialize(psWOptions)
+ if eErr == 0:
+ _, rows, cols = destination.shape
+ log.debug(
+ "Chunk and warp window: %d, %d, %d, %d",
+ 0, 0, cols, rows)
+ with nogil:
+ eErr = oWarper.ChunkAndWarpMulti(0, 0, cols, rows)
+ log.debug("Chunked and warped: %d", retval)
+
+ except Exception:
+ log.exception(
+ "Caught exception in warping. Source not reprojected.")
+
+ else:
+ reprojected = True
+
+ finally:
+ if hTransformArg != NULL:
+ _gdal.GDALDestroyGenImgProjTransformer(hTransformArg)
+ #if maxerror > 0.0:
+ # _gdal.GDALDestroyApproxTransformer(psWOptions.pTransformerArg)
+ if psWOptions != NULL:
+ _gdal.GDALDestroyWarpOptions(psWOptions)
+ if isinstance(source, np.ndarray):
+ if hdsin != NULL:
+ _gdal.GDALClose(hdsin)
+
+ if reprojected and isinstance(destination, np.ndarray):
+ try:
+ dtype = np.dtype(destination.dtype).name
+ _, rows, cols = destination.shape
+ indexes = np.array(range(1, src_count+1))
+ if dtype == dtypes.ubyte:
+ retval = _io.io_multi_ubyte(
+ hdsout, 0, 0, 0, cols, rows,
+ destination, indexes, src_count)
+ elif dtype == dtypes.uint16:
+ retval = _io.io_multi_uint16(
+ hdsout, 0, 0, 0, cols, rows,
+ destination, indexes, src_count)
+ elif dtype == dtypes.int16:
+ retval = _io.io_multi_int16(
+ hdsout, 0, 0, 0, cols, rows,
+ destination, indexes, src_count)
+ elif dtype == dtypes.uint32:
+ retval = _io.io_multi_uint32(
+ hdsout, 0, 0, 0, cols, rows,
+ destination, indexes, src_count)
+ elif dtype == dtypes.int32:
+ retval = _io.io_multi_int32(
+ hdsout, 0, 0, 0, cols, rows,
+ destination, indexes, src_count)
+ elif dtype == dtypes.float32:
+ retval = _io.io_multi_float32(
+ hdsout, 0, 0, 0, cols, rows,
+ destination, indexes, src_count)
+ elif dtype == dtypes.float64:
+ retval = _io.io_multi_float64(
+ hdsout, 0, 0, 0, cols, rows,
+ destination, indexes, src_count)
+ else:
+ raise ValueError("Invalid dtype")
+ # TODO: handle errors (by retval).
+ except Exception:
+ raise
+ finally:
+ if hdsout != NULL:
+ _gdal.GDALClose(hdsout)
+
diff --git a/rasterio/coords.py b/rasterio/coords.py
new file mode 100644
index 0000000..1d89156
--- /dev/null
+++ b/rasterio/coords.py
@@ -0,0 +1,5 @@
+
+from collections import namedtuple
+
+BoundingBox = namedtuple('BoundingBox', ('left', 'bottom', 'right', 'top'))
+
diff --git a/rasterio/crs.py b/rasterio/crs.py
new file mode 100644
index 0000000..eef744c
--- /dev/null
+++ b/rasterio/crs.py
@@ -0,0 +1,186 @@
+# Coordinate reference systems and functions.
+#
+# PROJ.4 is the law of this land: http://proj.osgeo.org/. But whereas PROJ.4
+# coordinate reference systems are described by strings of parameters such as
+#
+# +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs
+#
+# here we use mappings:
+#
+# {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True}
+#
+
+from rasterio.five import string_types
+
+def to_string(crs):
+ """Turn a parameter mapping into a more conventional PROJ.4 string.
+
+ Mapping keys are tested against the ``all_proj_keys`` list. Values of
+ ``True`` are omitted, leaving the key bare: {'no_defs': True} -> "+no_defs"
+ and items where the value is otherwise not a str, int, or float are
+ omitted.
+ """
+ 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], string_types)),
+ crs.items() )):
+ items.append(
+ "+" + "=".join(
+ map(str, filter(
+ 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.
+ """
+ parts = [o.lstrip('+') for o in prjs.strip().split()]
+ def parse(v):
+ if v in ('True', 'true'):
+ return True
+ elif v in ('False', 'false'):
+ return False
+ else:
+ try:
+ return int(v)
+ except ValueError:
+ pass
+ try:
+ 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)
+
+def from_epsg(code):
+ """Given an integer code, returns an EPSG-like mapping.
+
+ Note: the input code is not validated against an EPSG database.
+ """
+ if int(code) <= 0:
+ raise ValueError("EPSG codes are positive integers")
+ return {'init': "epsg:%s" % code, 'no_defs': True}
+
+
+# Below is the big list of PROJ4 parameters from
+# http://trac.osgeo.org/proj/wiki/GenParms.
+# It is parsed into a list of paramter keys ``all_proj_keys``.
+
+_param_data = """
++a Semimajor radius of the ellipsoid axis
++alpha ? Used with Oblique Mercator and possibly a few others
++axis Axis orientation (new in 4.8.0)
++b Semiminor radius of the ellipsoid axis
++datum Datum name (see `proj -ld`)
++ellps Ellipsoid name (see `proj -le`)
++init Initialize from a named CRS
++k Scaling factor (old name)
++k_0 Scaling factor (new name)
++lat_0 Latitude of origin
++lat_1 Latitude of first standard parallel
++lat_2 Latitude of second standard parallel
++lat_ts Latitude of true scale
++lon_0 Central meridian
++lonc ? Longitude used with Oblique Mercator and possibly a few others
++lon_wrap Center longitude to use for wrapping (see below)
++nadgrids Filename of NTv2 grid file to use for datum transforms (see below)
++no_defs Don't use the /usr/share/proj/proj_def.dat defaults file
++over Allow longitude output outside -180 to 180 range, disables wrapping (see below)
++pm Alternate prime meridian (typically a city name, see below)
++proj Projection name (see `proj -l`)
++south Denotes southern hemisphere UTM zone
++to_meter Multiplier to convert map units to 1.0m
++towgs84 3 or 7 term datum transform parameters (see below)
++units meters, US survey feet, etc.
++vto_meter vertical conversion to meters.
++vunits vertical units.
++x_0 False easting
++y_0 False northing
++zone UTM zone
++a Semimajor radius of the ellipsoid axis
++alpha ? Used with Oblique Mercator and possibly a few others
++azi
++b Semiminor radius of the ellipsoid axis
++belgium
++beta
++czech
++e Eccentricity of the ellipsoid = sqrt(1 - b^2/a^2) = sqrt( f*(2-f) )
++ellps Ellipsoid name (see `proj -le`)
++es Eccentricity of the ellipsoid squared
++f Flattening of the ellipsoid (often presented as an inverse, e.g. 1/298)
++gamma
++geoc
++guam
++h
++k Scaling factor (old name)
++K
++k_0 Scaling factor (new name)
++lat_0 Latitude of origin
++lat_1 Latitude of first standard parallel
++lat_2 Latitude of second standard parallel
++lat_b
++lat_t
++lat_ts Latitude of true scale
++lon_0 Central meridian
++lon_1
++lon_2
++lonc ? Longitude used with Oblique Mercator and possibly a few others
++lsat
++m
++M
++n
++no_cut
++no_off
++no_rot
++ns
++o_alpha
++o_lat_1
++o_lat_2
++o_lat_c
++o_lat_p
++o_lon_1
++o_lon_2
++o_lon_c
++o_lon_p
++o_proj
++over
++p
++path
++proj Projection name (see `proj -l`)
++q
++R
++R_a
++R_A Compute radius such that the area of the sphere is the same as the area of the ellipsoid
++rf Reciprocal of the ellipsoid flattening term (e.g. 298)
++R_g
++R_h
++R_lat_a
++R_lat_g
++rot
++R_V
++s
++south Denotes southern hemisphere UTM zone
++sym
++t
++theta
++tilt
++to_meter Multiplier to convert map units to 1.0m
++units meters, US survey feet, etc.
++vopt
++W
++westo
++x_0 False easting
++y_0 False northing
++zone UTM zone
+"""
+
+_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)
+ ) + ['no_mayo']
diff --git a/rasterio/dtypes.py b/rasterio/dtypes.py
new file mode 100644
index 0000000..e08f14e
--- /dev/null
+++ b/rasterio/dtypes.py
@@ -0,0 +1,98 @@
+# Mapping of GDAL to Numpy data types.
+#
+# Since 0.13 we are not importing numpy here and data types are strings.
+# Happily strings can be used throughout Numpy and so existing code will
+# break.
+#
+# Within Rasterio, to test data types, we use Numpy's dtype() factory to
+# do something like this:
+#
+# if np.dtype(destination.dtype) == np.dtype(rasterio.uint8): ...
+#
+
+bool_ = 'bool'
+ubyte = uint8 = 'uint8'
+uint16 = 'uint16'
+int16 = 'int16'
+uint32 = 'uint32'
+int32 = 'int32'
+float32 = 'float32'
+float64 = 'float64'
+complex_ = 'complex'
+complex64 = 'complex64'
+complex128 = 'complex128'
+
+# Not supported:
+# GDT_CInt16 = 8, GDT_CInt32 = 9, GDT_CFloat32 = 10, GDT_CFloat64 = 11
+
+dtype_fwd = {
+ 0: None, # GDT_Unknown
+ 1: ubyte, # GDT_Byte
+ 2: uint16, # GDT_UInt16
+ 3: int16, # GDT_Int16
+ 4: uint32, # GDT_UInt32
+ 5: int32, # GDT_Int32
+ 6: float32, # GDT_Float32
+ 7: float64, # GDT_Float64
+ 8: complex_, # GDT_CInt16
+ 9: complex_, # GDT_CInt32
+ 10: complex64, # GDT_CFloat32
+ 11: complex128 } # GDT_CFloat64
+
+dtype_rev = dict((v, k) for k, v in dtype_fwd.items())
+dtype_rev['uint8'] = 1
+
+typename_fwd = {
+ 0: 'Unknown',
+ 1: 'Byte',
+ 2: 'UInt16',
+ 3: 'Int16',
+ 4: 'UInt32',
+ 5: 'Int32',
+ 6: 'Float32',
+ 7: 'Float64',
+ 8: 'CInt16',
+ 9: 'CInt32',
+ 10: 'CFloat32',
+ 11: 'CFloat64' }
+
+typename_rev = dict((v, k) for k, v in typename_fwd.items())
+
+def _gdal_typename(dt):
+ try:
+ return typename_fwd[dtype_rev[dt]]
+ except KeyError:
+ return typename_fwd[dtype_rev[dt().dtype.name]]
+
+def check_dtype(dt):
+ if dt not in dtype_rev:
+ try:
+ return dt().dtype.name in dtype_rev
+ except:
+ return False
+ return True
+
+
+def get_minimum_int_dtype(values):
+ """
+ Uses range checking to determine the minimum integer data type required
+ to represent values.
+
+ :param values: numpy array
+ :return: named data type that can be later used to create a numpy dtype
+ """
+
+ min_value = values.min()
+ max_value = values.max()
+
+ if min_value >= 0:
+ if max_value <= 255:
+ return uint8
+ elif max_value <= 65535:
+ return uint16
+ elif max_value <= 4294967295:
+ return uint32
+ elif min_value >= -32768 and max_value <= 32767:
+ return int16
+ elif min_value >= -2147483648 and max_value <= 2147483647:
+ return int32
diff --git a/rasterio/enums.py b/rasterio/enums.py
new file mode 100644
index 0000000..3926680
--- /dev/null
+++ b/rasterio/enums.py
@@ -0,0 +1,19 @@
+
+from enum import IntEnum
+
+class ColorInterp(IntEnum):
+ undefined=0
+ grey=1
+ gray=1
+ palette=2
+ red=3
+ green=4
+ blue=5
+ alpha=6
+ hue=7
+ saturation=8
+ lightness=9
+ cyan=10
+ magenta=11
+ yellow=12
+ black=13
\ No newline at end of file
diff --git a/rasterio/features.py b/rasterio/features.py
new file mode 100644
index 0000000..9ea14db
--- /dev/null
+++ b/rasterio/features.py
@@ -0,0 +1,318 @@
+"""Functions for working with features in a raster dataset."""
+
+import json
+import logging
+import time
+import warnings
+
+import numpy as np
+
+import rasterio
+from rasterio._features import _shapes, _sieve, _rasterize
+from rasterio.transform import IDENTITY, guard_transform
+from rasterio.dtypes import get_minimum_int_dtype
+
+
+log = logging.getLogger('rasterio')
+
+
+class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+log.addHandler(NullHandler())
+
+
+def shapes(image, mask=None, connectivity=4, transform=IDENTITY):
+ """
+ Return a generator of (polygon, value) for each each set of adjacent pixels
+ of the same value.
+
+ Parameters
+ ----------
+ image : numpy ndarray or rasterio Band object
+ (RasterReader, bidx namedtuple).
+ Data type must be one of rasterio.int16, rasterio.int32,
+ rasterio.uint8, rasterio.uint16, or rasterio.float32.
+ mask : numpy ndarray or rasterio Band object, optional
+ Values of False will be excluded from feature generation
+ Must be of type rasterio.bool_
+ connectivity : int, optional
+ Use 4 or 8 pixel connectivity for grouping pixels into features
+ transform : Affine transformation, optional
+ If not provided, feature coordinates will be generated based on pixel
+ coordinates
+
+ Returns
+ -------
+ Generator of (polygon, value)
+ Yields a pair of (polygon, value) for each feature found in the image.
+ Polygons are GeoJSON-like dicts and the values are the associated value
+ from the image, in the data type of the image.
+ Note: due to floating point precision issues, values returned from a
+ floating point image may not exactly match the original values.
+
+ Notes
+ -----
+ The amount of memory used by this algorithm is proportional to the number
+ and complexity of polygons produced. This algorithm is most appropriate
+ for simple thematic data. Data with high pixel-to-pixel variability, such
+ as imagery, may produce one polygon per pixel and consume large amounts of
+ memory.
+
+ """
+
+ valid_dtypes = ('int16', 'int32', 'uint8', 'uint16', 'float32')
+
+ if np.dtype(image.dtype).name not in valid_dtypes:
+ raise ValueError('image dtype must be one of: %s'
+ % (', '.join(valid_dtypes)))
+
+ if mask is not None and np.dtype(mask.dtype) != np.dtype(rasterio.bool_):
+ raise ValueError("Mask must be dtype rasterio.bool_")
+
+ if connectivity not in (4, 8):
+ raise ValueError("Connectivity Option must be 4 or 8")
+
+ transform = guard_transform(transform)
+
+ with rasterio.drivers():
+ for s, v in _shapes(image, mask, connectivity, transform.to_gdal()):
+ yield s, v
+
+
+def sieve(image, size, out=None, output=None, mask=None, connectivity=4):
+ """
+ Replaces small polygons in `image` with the value of their largest
+ neighbor. Polygons are found for each set of neighboring pixels of the
+ same value.
+
+ Parameters
+ ----------
+ image : numpy ndarray or rasterio Band object
+ (RasterReader, bidx namedtuple)
+ Must be of type rasterio.int16, rasterio.int32, rasterio.uint8,
+ rasterio.uint16, or rasterio.float32
+ size : int
+ minimum polygon size (number of pixels) to retain.
+ out : numpy ndarray, optional
+ Array of same shape and data type as `image` in which to store results.
+ output : older alias for `out`, will be removed before 1.0.
+ output : numpy ndarray, optional
+ mask : numpy ndarray or rasterio Band object, optional
+ Values of False will be excluded from feature generation
+ Must be of type rasterio.bool_
+ connectivity : int, optional
+ Use 4 or 8 pixel connectivity for grouping pixels into features
+
+ Returns
+ -------
+ out : numpy ndarray
+ Result
+
+ Notes
+ -----
+ GDAL only supports values that can be cast to 32-bit integers for this
+ operation.
+
+ The amount of memory used by this algorithm is proportional to the number
+ and complexity of polygons found in the image. This algorithm is most
+ appropriate for simple thematic data. Data with high pixel-to-pixel
+ variability, such as imagery, may produce one polygon per pixel and consume
+ large amounts of memory.
+
+ """
+
+ valid_dtypes = ('int16', 'int32', 'uint8', 'uint16')
+
+ if np.dtype(image.dtype).name not in valid_dtypes:
+ valid_types_str = ', '.join(('rasterio.{0}'.format(t) for t
+ in valid_dtypes))
+ raise ValueError('image dtype must be one of: %' % valid_types_str)
+
+ if size <= 0:
+ raise ValueError('size must be greater than 0')
+ elif type(size) == float:
+ raise ValueError('size must be an integer number of pixels')
+ elif size > (image.shape[0] * image.shape[1]):
+ raise ValueError('size must be smaller than size of image')
+
+ if connectivity not in (4, 8):
+ raise ValueError('connectivity must be 4 or 8')
+
+ if mask is not None:
+ if np.dtype(mask.dtype) != np.dtype(rasterio.bool_):
+ raise ValueError('Mask must be dtype rasterio.bool_')
+ elif mask.shape != image.shape:
+ raise ValueError('mask shape must be same as image shape')
+
+ # Start moving users over to 'out'.
+ if output is not None:
+ warnings.warn(
+ "The 'output' keyword arg has been superceded by 'out' "
+ "and will be removed before Rasterio 1.0.",
+ FutureWarning,
+ stacklevel=2)
+
+ out = out if out is not None else output
+ if out is None:
+ out = np.zeros_like(image)
+ else:
+ if np.dtype(image.dtype).name != np.dtype(out.dtype).name:
+ raise ValueError('output must match dtype of image')
+ elif out.shape != image.shape:
+ raise ValueError('mask shape must be same as image shape')
+
+ with rasterio.drivers():
+ _sieve(image, size, out, mask, connectivity)
+ return out
+
+
+def rasterize(
+ shapes,
+ out_shape=None,
+ fill=0,
+ out=None,
+ output=None,
+ transform=IDENTITY,
+ all_touched=False,
+ default_value=1,
+ dtype=None):
+ """
+ Returns an image array with input geometries burned in.
+
+ Parameters
+ ----------
+ shapes : iterable of (geometry, value) pairs or iterable over geometries
+ `geometry` can either be an object that implements the geo interface or
+ GeoJSON-like object.
+ out_shape : tuple or list
+ Shape of output numpy ndarray
+ fill : int or float, optional
+ Used as fill value for all areas not covered by input geometries
+ out : numpy ndarray, optional
+ Array of same shape and data type as `image` in which to store results.
+ output : older alias for `out`, will be removed before 1.0.
+ transform : Affine transformation object, optional
+ transformation applied to shape geometries into pixel coordinates
+ all_touched : boolean, optional
+ If True, all pixels touched by geometries will be burned in.
+ If false, only pixels whose center is within the polygon or that are
+ selected by brezenhams line algorithm will be burned in.
+ default_value : int or float, optional
+ Used as value for all geometries, if not provided in `shapes`
+ dtype : rasterio or numpy data type, optional
+ Used as data type for results, if `output` is not provided
+
+ Returns
+ -------
+ out : numpy ndarray
+ Results
+
+ Notes
+ -----
+ Valid data types for `fill`, `default_value`, `out`, `dtype` and
+ shape values are rasterio.int16, rasterio.int32, rasterio.uint8,
+ rasterio.uint16, rasterio.uint32, rasterio.float32, rasterio.float64
+
+ """
+
+ valid_dtypes = ('int16', 'int32', 'uint8', 'uint16', 'uint32', 'float32',
+ 'float64')
+
+ def get_valid_dtype(values):
+ values_dtype = values.dtype
+ if values_dtype.kind == 'i':
+ values_dtype = np.dtype(get_minimum_int_dtype(values))
+ if values_dtype.name in valid_dtypes:
+ return values_dtype
+ return None
+
+ def can_cast_dtype(values, dtype):
+ if values.dtype.name == np.dtype(dtype).name:
+ return True
+ elif values.dtype.kind == 'f':
+ return np.allclose(values, values.astype(dtype))
+ else:
+ return np.array_equal(values, values.astype(dtype))
+
+ if fill != 0:
+ fill_array = np.array([fill])
+ if get_valid_dtype(fill_array) is None:
+ raise ValueError('fill must be one of these types: %s'
+ % (', '.join(valid_dtypes)))
+ elif dtype is not None and not can_cast_dtype(fill_array, dtype):
+ raise ValueError('fill value cannot be cast to specified dtype')
+
+ if default_value != 1:
+ default_value_array = np.array([default_value])
+ if get_valid_dtype(default_value_array) is None:
+ raise ValueError('default_value must be one of these types: %s'
+ % (', '.join(valid_dtypes)))
+ elif dtype is not None and not can_cast_dtype(default_value_array,
+ dtype):
+ raise ValueError('default_value cannot be cast to specified dtype')
+
+ valid_shapes = []
+ shape_values = []
+ for index, item in enumerate(shapes):
+ try:
+ if isinstance(item, (tuple, list)):
+ geom, value = item
+ else:
+ geom = item
+ value = default_value
+ geom = getattr(geom, '__geo_interface__', None) or geom
+ if (not isinstance(geom, dict) or
+ 'type' not in geom or 'coordinates' not in geom):
+ raise ValueError(
+ 'Object %r at index %d is not a geometry object' %
+ (geom, index))
+ valid_shapes.append((geom, value))
+ shape_values.append(value)
+ except Exception:
+ log.exception('Exception caught, skipping shape %d', index)
+
+ if not valid_shapes:
+ raise ValueError('No valid shapes found for rasterize. Shapes must be '
+ 'valid geometry objects')
+
+ shape_values = np.array(shape_values)
+ values_dtype = get_valid_dtype(shape_values)
+ if values_dtype is None:
+ raise ValueError('shape values must be one of these dtypes: %s' %
+ (', '.join(valid_dtypes)))
+
+ if dtype is None:
+ dtype = values_dtype
+ elif np.dtype(dtype).name not in valid_dtypes:
+ raise ValueError('dtype must be one of: %s' % (', '.join(valid_dtypes)))
+ elif not can_cast_dtype(shape_values, dtype):
+ raise ValueError('shape values could not be cast to specified dtype')
+
+ if output is not None:
+ warnings.warn(
+ "The 'output' keyword arg has been superceded by 'out' "
+ "and will be removed before Rasterio 1.0.",
+ FutureWarning,
+ stacklevel=2)
+ out = out if out is not None else output
+ if out is not None:
+ if np.dtype(output.dtype).name not in valid_dtypes:
+ raise ValueError('Output image dtype must be one of: %s'
+ % (', '.join(valid_dtypes)))
+ if not can_cast_dtype(shape_values, output.dtype):
+ raise ValueError('shape values cannot be cast to dtype of output '
+ 'image')
+
+ elif out_shape is not None:
+ out = np.empty(out_shape, dtype=dtype)
+ out.fill(fill)
+ else:
+ raise ValueError('Either an output shape or image must be provided')
+
+ transform = guard_transform(transform)
+
+ with rasterio.drivers():
+ _rasterize(valid_shapes, out, transform.to_gdal(), all_touched)
+
+ return out
diff --git a/rasterio/five.py b/rasterio/five.py
new file mode 100644
index 0000000..ec38a12
--- /dev/null
+++ b/rasterio/five.py
@@ -0,0 +1,15 @@
+# Python 2-3 compatibility
+
+import itertools
+import sys
+
+if sys.version_info[0] >= 3:
+ string_types = str,
+ text_type = str
+ integer_types = int,
+ zip_longest = itertools.zip_longest
+else:
+ string_types = basestring,
+ text_type = unicode
+ integer_types = int, long
+ zip_longest = itertools.izip_longest
diff --git a/rasterio/rio/__init__.py b/rasterio/rio/__init__.py
new file mode 100644
index 0000000..e736ca1
--- /dev/null
+++ b/rasterio/rio/__init__.py
@@ -0,0 +1 @@
+# module of CLI commands.
diff --git a/rasterio/rio/bands.py b/rasterio/rio/bands.py
new file mode 100644
index 0000000..cab2c7b
--- /dev/null
+++ b/rasterio/rio/bands.py
@@ -0,0 +1,129 @@
+import logging
+import os.path
+import sys
+
+import click
+
+import rasterio
+
+from rasterio.five import zip_longest
+from rasterio.rio.cli import cli
+
+
+PHOTOMETRIC_CHOICES = [val.lower() for val in [
+ 'MINISBLACK',
+ 'MINISWHITE',
+ 'RGB',
+ 'CMYK',
+ 'YCBCR',
+ 'CIELAB',
+ 'ICCLAB',
+ 'ITULAB']]
+
+
+# Stack command.
+ at cli.command(short_help="Stack a number of bands into a multiband dataset.")
+ at click.argument('input', nargs=-1,
+ type=click.Path(exists=True, resolve_path=True), required=True)
+ at click.option('--bidx', multiple=True,
+ help="Indexes of input file bands.")
+ at click.option('--photometric', default=None,
+ type=click.Choice(PHOTOMETRIC_CHOICES),
+ help="Photometric interpretation")
+ at click.option('-o','--output',
+ type=click.Path(exists=False, resolve_path=True), required=True,
+ help="Path to output file.")
+ at click.option('-f', '--format', '--driver', default='GTiff',
+ help="Output format driver")
+ at click.pass_context
+def stack(ctx, input, bidx, photometric, output, driver):
+ """Stack a number of bands from one or more input files into a
+ multiband dataset.
+
+ Input datasets must be of a kind: same data type, dimensions, etc. The
+ output is cloned from the first input.
+
+ By default, rio-stack will take all bands from each input and write them
+ in same order to the output. Optionally, bands for each input may be
+ specified using a simple syntax:
+
+ --bidx N takes the Nth band from the input (first band is 1).
+
+ --bidx M,N,0 takes bands M, N, and O.
+
+ --bidx M..O takes bands M-O, inclusive.
+
+ --bidx ..N takes all bands up to and including N.
+
+ --bidx N.. takes all bands from N to the end.
+
+ Examples, using the Rasterio testing dataset, which produce a copy.
+
+ rio stack RGB.byte.tif -o stacked.tif
+
+ rio stack RGB.byte.tif --bidx 1,2,3 -o stacked.tif
+
+ rio stack RGB.byte.tif --bidx 1..3 -o stacked.tif
+
+ rio stack RGB.byte.tif --bidx ..2 RGB.byte.tif --bidx 3.. -o stacked.tif
+
+ """
+ import numpy as np
+
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 2
+ logger = logging.getLogger('rio')
+ try:
+ with rasterio.drivers(CPL_DEBUG=verbosity>2):
+ output_count = 0
+ indexes = []
+ for path, item in zip_longest(input, bidx, fillvalue=None):
+ with rasterio.open(path) as src:
+ src_indexes = src.indexes
+ if item is None:
+ indexes.append(src_indexes)
+ output_count += len(src_indexes)
+ elif '..' in item:
+ start, stop = map(
+ lambda x: int(x) if x else None, item.split('..'))
+ if start is None:
+ start = 1
+ indexes.append(src_indexes[slice(start-1, stop)])
+ output_count += len(src_indexes[slice(start-1, stop)])
+ else:
+ parts = list(map(int, item.split(',')))
+ if len(parts) == 1:
+ indexes.append(parts[0])
+ output_count += 1
+ else:
+ parts = list(parts)
+ indexes.append(parts)
+ output_count += len(parts)
+
+ with rasterio.open(input[0]) as first:
+ kwargs = first.meta
+ kwargs['transform'] = kwargs.pop('affine')
+
+ kwargs.update(
+ driver=driver,
+ count=output_count)
+
+ if photometric:
+ kwargs['photometric'] = photometric
+
+ with rasterio.open(output, 'w', **kwargs) as dst:
+ dst_idx = 1
+ for path, index in zip(input, indexes):
+ with rasterio.open(path) as src:
+ if isinstance(index, int):
+ data = src.read(index)
+ dst.write(data, dst_idx)
+ dst_idx += 1
+ elif isinstance(index, list):
+ data = src.read(index)
+ dst.write(data, range(dst_idx, dst_idx+len(index)))
+ dst_idx += len(index)
+
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
diff --git a/rasterio/rio/cli.py b/rasterio/rio/cli.py
new file mode 100644
index 0000000..cf54fda
--- /dev/null
+++ b/rasterio/rio/cli.py
@@ -0,0 +1,83 @@
+import json
+import logging
+import sys
+
+import click
+
+import rasterio
+from rasterio.rio import options
+
+def configure_logging(verbosity):
+ log_level = max(10, 30 - 10*verbosity)
+ logging.basicConfig(stream=sys.stderr, level=log_level)
+
+
+# The CLI command group.
+ at click.group(help="Rasterio command line interface.")
+ at options.verbose
+ at options.quiet
+ at options.version
+ at click.pass_context
+def cli(ctx, verbose, quiet):
+ verbosity = verbose - quiet
+ configure_logging(verbosity)
+ ctx.obj = {}
+ ctx.obj['verbosity'] = verbosity
+
+
+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(file, collection,
+ agg_mode='obj', expression='feature', use_rs=False,
+ **dump_kwds):
+ """Read an iterator of (feat, bbox) pairs and write to file using
+ the selected modes."""
+ # Sequence of features expressed as bbox, feature, or collection.
+ if agg_mode == 'seq':
+ for feat in collection():
+ xs, ys = zip(*coords(feat))
+ bbox = (min(xs), min(ys), max(xs), max(ys))
+ if use_rs:
+ file.write(u'\u001e')
+ if expression == 'feature':
+ file.write(json.dumps(feat, **dump_kwds))
+ elif expression == 'bbox':
+ file.write(json.dumps(bbox, **dump_kwds))
+ else:
+ file.write(
+ json.dumps({
+ 'type': 'FeatureCollection',
+ 'bbox': bbox,
+ 'features': [feat]}, **dump_kwds))
+ file.write('\n')
+ # Aggregate all features into a single object expressed as
+ # bbox or collection.
+ else:
+ features = list(collection())
+ if expression == 'bbox':
+ file.write(json.dumps(collection.bbox, **dump_kwds))
+ elif expression == 'feature':
+ file.write(json.dumps(features[0], **dump_kwds))
+ else:
+ file.write(json.dumps({
+ 'bbox': collection.bbox,
+ 'type': 'FeatureCollection',
+ 'features': features},
+ **dump_kwds))
+ file.write('\n')
diff --git a/rasterio/rio/features.py b/rasterio/rio/features.py
new file mode 100644
index 0000000..ede426f
--- /dev/null
+++ b/rasterio/rio/features.py
@@ -0,0 +1,156 @@
+import functools
+import json
+import logging
+import sys
+import warnings
+
+import click
+
+import rasterio
+from rasterio.transform import Affine
+from rasterio.rio.cli import cli, coords, write_features
+
+
+warnings.simplefilter('default')
+
+
+# Shapes command.
+ at cli.command(short_help="Write the shapes of features.")
+ at click.argument('input', type=click.Path(exists=True))
+# Coordinate precision option.
+ at click.option('--precision', type=int, default=-1,
+ help="Decimal precision of coordinates.")
+# JSON formatting options.
+ at click.option('--indent', default=None, type=int,
+ help="Indentation level for JSON output")
+ at click.option('--compact/--no-compact', default=False,
+ help="Use compact separators (',', ':').")
+# Geographic (default) or Mercator switch.
+ at click.option('--geographic', 'projected', flag_value='geographic',
+ default=True,
+ help="Output in geographic coordinates (the default).")
+ at click.option('--projected', 'projected', flag_value='projected',
+ help="Output in projected coordinates.")
+# JSON object (default) or sequence (experimental) switch.
+ at click.option('--json-obj', 'json_mode', flag_value='obj', default=True,
+ help="Write a single JSON object (the default).")
+ at click.option('--x-json-seq', 'json_mode', flag_value='seq',
+ help="Write a JSON sequence. Experimental.")
+# Use ASCII RS control code to signal a sequence item (False is default).
+# See http://tools.ietf.org/html/draft-ietf-json-text-sequence-05.
+# Experimental.
+ at click.option('--x-json-seq-rs/--x-json-seq-no-rs', default=False,
+ help="Use RS as text separator. Experimental.")
+# GeoJSON feature (default), bbox, or collection switch. Meaningful only
+# when --x-json-seq is used.
+ at click.option('--collection', 'output_mode', flag_value='collection',
+ default=True,
+ help="Output as a GeoJSON feature collection (the default).")
+ at click.option('--feature', 'output_mode', flag_value='feature',
+ help="Output as sequence of GeoJSON features.")
+ at click.option('--bbox', 'output_mode', flag_value='bbox',
+ help="Output as a GeoJSON bounding box array.")
+ at click.option('--bands/--mask', default=True,
+ help="Extract shapes from one of the dataset bands or from "
+ "its nodata mask")
+ at click.option('--bidx', type=int, default=1,
+ help="Index of the source band")
+ at click.option('--sampling', type=int, default=1,
+ help="Inverse of the sampling fraction")
+ at click.option('--with-nodata/--without-nodata', default=False,
+ help="Include or do not include (the default) nodata regions.")
+ at click.pass_context
+def shapes(
+ ctx, input, precision, indent, compact, projected, json_mode,
+ x_json_seq_rs, output_mode, bands, bidx, sampling, with_nodata):
+ """Writes features of a dataset out as GeoJSON. It's intended for
+ use with single-band rasters and reads from the first band.
+ """
+ # These import numpy, which we don't want to do unless its needed.
+ import numpy
+ import rasterio.features
+ import rasterio.warp
+
+ verbosity = ctx.obj['verbosity']
+ 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):
+ with rasterio.open(input) as src:
+ if bands:
+ if sampling == 1:
+ img = src.read_band(bidx)
+ transform = src.transform
+ # Decimate the band.
+ else:
+ img = numpy.zeros(
+ (src.height//sampling, src.width//sampling),
+ dtype=src.dtypes[src.indexes.index(bidx)])
+ img = src.read_band(bidx, img)
+ transform = src.affine * Affine.scale(float(sampling))
+ else:
+ if sampling == 1:
+ img = src.read_mask()
+ transform = src.transform
+ # Decimate the mask.
+ else:
+ img = numpy.zeros(
+ (src.height//sampling, src.width//sampling),
+ dtype=numpy.uint8)
+ img = src.read_mask(img)
+ transform = src.affine * Affine.scale(float(sampling))
+
+ bounds = src.bounds
+ xs = [bounds[0], bounds[2]]
+ ys = [bounds[1], bounds[3]]
+ if projected == '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]
+ self._xs = xs
+ self._ys = ys
+
+ kwargs = {'transform': transform}
+ if not bands and not with_nodata:
+ kwargs['mask'] = (img==255)
+ for g, i in rasterio.features.shapes(img, **kwargs):
+ if projected == 'geographic':
+ g = rasterio.warp.transform_geom(
+ src.crs, 'EPSG:4326', g,
+ antimeridian_cutting=True, precision=precision)
+ xs, ys = zip(*coords(g))
+ yield {
+ 'type': 'Feature',
+ 'id': str(i),
+ 'properties': {'val': i},
+ 'bbox': [min(xs), min(ys), max(xs), max(ys)],
+ 'geometry': g }
+
+ try:
+ with rasterio.drivers(CPL_DEBUG=verbosity>2):
+ write_features(
+ stdout, Collection(), agg_mode=json_mode,
+ expression=output_mode, use_rs=x_json_seq_rs,
+ **dump_kwds)
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
diff --git a/rasterio/rio/info.py b/rasterio/rio/info.py
new file mode 100644
index 0000000..a3e79e0
--- /dev/null
+++ b/rasterio/rio/info.py
@@ -0,0 +1,100 @@
+# Info command.
+
+import json
+import logging
+import os.path
+import pprint
+import sys
+
+import click
+
+import rasterio
+import rasterio.crs
+from rasterio.rio.cli import cli
+
+
+ at cli.command(short_help="Print information about the rio environment.")
+ at click.option('--formats', 'key', flag_value='formats', default=True,
+ help="Enumerate the available formats.")
+ at click.pass_context
+def env(ctx, key):
+ """Print information about the Rasterio environment: available
+ formats, etc.
+ """
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+ stdout = click.get_text_stream('stdout')
+ with rasterio.drivers(CPL_DEBUG=(verbosity > 2)) as env:
+ if key == 'formats':
+ for k, v in sorted(env.drivers().items()):
+ stdout.write("%s: %s\n" % (k, v))
+ stdout.write('\n')
+
+
+ at cli.command(short_help="Print information about a data file.")
+ at click.argument('input', type=click.Path(exists=True))
+ at click.option('--meta', 'aspect', flag_value='meta', default=True,
+ help="Show data file structure (default).")
+ at click.option('--tags', 'aspect', flag_value='tags',
+ help="Show data file tags.")
+ at click.option('--namespace', help="Select a tag namespace.")
+ at click.option('--indent', default=None, type=int,
+ help="Indentation level for pretty printed output")
+# Options to pick out a single metadata item and print it as
+# a string.
+ at click.option('--count', 'meta_member', flag_value='count',
+ help="Print the count of bands.")
+ at click.option('--dtype', 'meta_member', flag_value='dtype',
+ help="Print the dtype name.")
+ at click.option('--nodata', 'meta_member', flag_value='nodata',
+ help="Print the nodata value.")
+ at click.option('-f', '--format', '--driver', 'meta_member', flag_value='driver',
+ help="Print the format driver.")
+ at click.option('--shape', 'meta_member', flag_value='shape',
+ help="Print the (height, width) shape.")
+ at click.option('--height', 'meta_member', flag_value='height',
+ help="Print the height (number of rows).")
+ at click.option('--width', 'meta_member', flag_value='width',
+ help="Print the width (number of columns).")
+ at click.option('--crs', 'meta_member', flag_value='crs',
+ help="Print the CRS as a PROJ.4 string.")
+ at click.option('--bounds', 'meta_member', flag_value='bounds',
+ help="Print the nodata value.")
+ at click.pass_context
+def info(ctx, input, aspect, indent, namespace, meta_member):
+ """Print metadata about the dataset as JSON.
+
+ Optionally print a single metadata item as a string.
+ """
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+ stdout = click.get_text_stream('stdout')
+ try:
+ with rasterio.drivers(CPL_DEBUG=(verbosity > 2)):
+ with rasterio.open(input, 'r-') as src:
+ info = src.meta
+ del info['affine']
+ del info['transform']
+ info['shape'] = info['height'], info['width']
+ info['bounds'] = src.bounds
+ proj4 = rasterio.crs.to_string(src.crs)
+ if proj4.startswith('+init=epsg'):
+ proj4 = proj4.split('=')[1].upper()
+ info['crs'] = proj4
+ if aspect == 'meta':
+ if meta_member:
+ if isinstance(info[meta_member], (list, tuple)):
+ print(" ".join(map(str, info[meta_member])))
+ else:
+ print(info[meta_member])
+ else:
+ stdout.write(json.dumps(info, indent=indent))
+ stdout.write("\n")
+ elif aspect == 'tags':
+ stdout.write(json.dumps(src.tags(ns=namespace),
+ indent=indent))
+ stdout.write("\n")
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
new file mode 100644
index 0000000..1bccb93
--- /dev/null
+++ b/rasterio/rio/main.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+
+from rasterio.rio.cli import cli
+from rasterio.rio.bands import stack
+from rasterio.rio.features import shapes
+from rasterio.rio.info import env, info
+from rasterio.rio.merge import merge
+from rasterio.rio.rio import bounds, insp, transform
diff --git a/rasterio/rio/merge.py b/rasterio/rio/merge.py
new file mode 100644
index 0000000..a2a92f5
--- /dev/null
+++ b/rasterio/rio/merge.py
@@ -0,0 +1,75 @@
+# Merge command.
+
+import logging
+import os.path
+import sys
+
+import click
+
+import rasterio
+
+from rasterio.rio.cli import cli
+
+
+ at cli.command(short_help="Merge a stack of raster datasets.")
+ at click.argument('input', nargs=-1,
+ type=click.Path(exists=True, resolve_path=True),
+ required=True)
+ at click.option('-o','--output',
+ type=click.Path(exists=False, resolve_path=True),
+ required=True,
+ help="Path to output file.")
+ at click.option('-f', '--format', '--driver', default='GTiff',
+ help="Output format driver")
+ at click.pass_context
+def merge(ctx, input, output, driver):
+ """Copy valid pixels from input files to the output file.
+
+ All files must have the same shape, number of bands, and data type.
+
+ Input files are merged in their listed order using a reverse
+ painter's algorithm.
+ """
+ import numpy as np
+
+ verbosity = ctx.obj['verbosity']
+ logger = logging.getLogger('rio')
+ try:
+ with rasterio.drivers(CPL_DEBUG=verbosity>2):
+
+ with rasterio.open(input[0]) as first:
+ kwargs = first.meta
+ kwargs['transform'] = kwargs.pop('affine')
+ dest = np.empty((3,) + first.shape, dtype=first.dtypes[0])
+
+ if os.path.exists(output):
+ dst = rasterio.open(output, 'r+')
+ nodataval = dst.nodatavals[0]
+ else:
+ kwargs['driver'] == driver
+ dst = rasterio.open(output, 'w', **kwargs)
+ nodataval = first.nodatavals[0]
+
+ dest.fill(nodataval)
+
+ for fname in reversed(input):
+ with rasterio.open(fname) as src:
+ data = src.read()
+ np.copyto(dest, data,
+ where=np.logical_and(
+ dest==nodataval, data.mask==False))
+
+ if dst.mode == 'r+':
+ data = dst.read()
+ np.copyto(dest, data,
+ where=np.logical_and(
+ dest==nodataval, data.mask==False))
+
+ dst.write(dest)
+ dst.close()
+
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
+
diff --git a/rasterio/rio/options.py b/rasterio/rio/options.py
new file mode 100644
index 0000000..7f8340f
--- /dev/null
+++ b/rasterio/rio/options.py
@@ -0,0 +1,21 @@
+import click
+
+from rasterio import __version__ as rio_version
+
+
+def print_version(ctx, param, value):
+ if not value or ctx.resilient_parsing:
+ return
+ click.echo(rio_version)
+ ctx.exit()
+
+
+verbose = click.option('--verbose', '-v', count=True,
+ help="Increase verbosity.")
+
+quiet = click.option('--quiet', '-q', count=True,
+ help="Decrease verbosity.")
+
+version = click.option('--version', is_flag=True, callback=print_version,
+ expose_value=False, is_eager=True,
+ help="Print Rasterio version.")
diff --git a/rasterio/rio/rio.py b/rasterio/rio/rio.py
new file mode 100644
index 0000000..10df923
--- /dev/null
+++ b/rasterio/rio/rio.py
@@ -0,0 +1,220 @@
+"""Rasterio command line interface"""
+
+import functools
+import json
+import logging
+import os.path
+import pprint
+import sys
+import warnings
+
+import click
+
+import rasterio
+
+from rasterio.rio.cli import cli, write_features
+
+
+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 click.argument('input', type=click.Path(exists=True))
+ at click.option('--mode', type=click.Choice(['r', 'r+']), default='r', help="File mode (default 'r').")
+ at click.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)
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
+
+
+# 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))
+# Coordinate precision option.
+ at click.option('--precision', type=int, default=-1,
+ help="Decimal precision of coordinates.")
+# JSON formatting options.
+ at click.option('--indent', default=None, type=int,
+ help="Indentation level for JSON output")
+ at click.option('--compact/--no-compact', default=False,
+ help="Use compact separators (',', ':').")
+# Geographic (default) or Mercator switch.
+ at click.option('--geographic', 'projected', flag_value='geographic',
+ default=True,
+ help="Output in geographic coordinates (the default).")
+ at click.option('--projected', 'projected', flag_value='projected',
+ help="Output in projected coordinates.")
+ at click.option('--mercator', 'projected', flag_value='mercator',
+ help="Output in Web Mercator coordinates.")
+# JSON object (default) or sequence (experimental) switch.
+ at click.option('--json-obj', 'json_mode', flag_value='obj', default=True,
+ help="Write a single JSON object (the default).")
+ at click.option('--x-json-seq', 'json_mode', flag_value='seq',
+ help="Write a JSON sequence. Experimental.")
+# Use ASCII RS control code to signal a sequence item (False is default).
+# See http://tools.ietf.org/html/draft-ietf-json-text-sequence-05.
+# Experimental.
+ at click.option('--x-json-seq-rs/--x-json-seq-no-rs', default=False,
+ help="Use RS as text separator. Experimental.")
+# GeoJSON feature (default), bbox, or collection switch. Meaningful only
+# when --x-json-seq is used.
+ at click.option('--collection', 'output_mode', flag_value='collection',
+ default=True,
+ help="Output as a GeoJSON feature collection (the default).")
+ at click.option('--feature', 'output_mode', flag_value='feature',
+ help="Output as sequence of GeoJSON features.")
+ at click.option('--bbox', 'output_mode', flag_value='bbox',
+ help="Output as a GeoJSON bounding box array.")
+ at click.pass_context
+def bounds(ctx, input, precision, indent, compact, projected, json_mode,
+ x_json_seq_rs, output_mode):
+ """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 projected == 'geographic':
+ xs, ys = rasterio.warp.transform(
+ src.crs, {'init': 'epsg:4326'}, xs, ys)
+ if projected == '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} }
+
+ self._xs.extend(bbox[::2])
+ self._ys.extend(bbox[1::2])
+
+ collection = Collection()
+
+ # Use the generator defined above as input to the generic output
+ # writing function.
+ try:
+ with rasterio.drivers(CPL_DEBUG=verbosity>2):
+ write_features(
+ stdout, collection, agg_mode=json_mode,
+ expression=output_mode, use_rs=x_json_seq_rs,
+ **dump_kwds)
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
+
+
+# Transform command.
+ at cli.command(short_help="Transform coordinates.")
+ at click.argument('input', default='-', required=False)
+ at click.option('--src_crs', default='EPSG:4326', help="Source CRS.")
+ at click.option('--dst_crs', default='EPSG:4326', help="Destination CRS.")
+ at click.option('--precision', type=int, default=-1,
+ help="Decimal precision of coordinates.")
+ 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))
+
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
diff --git a/rasterio/tool.py b/rasterio/tool.py
new file mode 100644
index 0000000..d0f368a
--- /dev/null
+++ b/rasterio/tool.py
@@ -0,0 +1,53 @@
+
+import code
+import collections
+import logging
+import sys
+
+try:
+ import matplotlib.pyplot as plt
+except ImportError:
+ plt = None
+
+import numpy
+
+import rasterio
+
+
+logger = logging.getLogger('rasterio')
+
+Stats = collections.namedtuple('Stats', ['min', 'max', 'mean'])
+
+def main(banner, dataset):
+
+ def show(source, cmap='gray'):
+ """Show a raster using matplotlib.
+
+ The raster may be either an ndarray or a (dataset, bidx)
+ tuple.
+ """
+ if isinstance(source, tuple):
+ arr = source[0].read_band(source[1])
+ else:
+ arr = source
+ if plt is not None:
+ plt.imshow(arr, cmap=cmap)
+ plt.show()
+ else:
+ raise ImportError("matplotlib could not be imported")
+
+ def stats(source):
+ """Return a tuple with raster min, max, and mean.
+ """
+ if isinstance(source, tuple):
+ arr = source[0].read_band(source[1])
+ else:
+ arr = source
+ return Stats(numpy.min(arr), numpy.max(arr), numpy.mean(arr))
+
+ code.interact(
+ banner,
+ local=dict(
+ locals(), src=dataset, np=numpy, rio=rasterio, plt=plt))
+
+ return 0
diff --git a/rasterio/transform.py b/rasterio/transform.py
new file mode 100644
index 0000000..4b3ed93
--- /dev/null
+++ b/rasterio/transform.py
@@ -0,0 +1,29 @@
+
+import warnings
+
+from affine import Affine
+
+IDENTITY = Affine.identity()
+
+def tastes_like_gdal(t):
+ return t[2] == t[4] == 0.0 and t[1] > 0 and t[5] < 0
+
+def guard_transform(transform):
+ """Return an Affine transformation instance"""
+ if not isinstance(transform, Affine):
+ if tastes_like_gdal(transform):
+ warnings.warn(
+ "GDAL-style transforms are deprecated and will not "
+ "be supported in Rasterio 1.0.",
+ FutureWarning,
+ stacklevel=2)
+ transform = Affine.from_gdal(*transform)
+ else:
+ transform = Affine(*transform)
+ a, e = transform.a, transform.e
+ if a == 0.0 or e == 0.0:
+ raise ValueError(
+ "Transform has invalid coefficients a, e: (%f, %f)" % (
+ transform.a, transform.e))
+ return transform
+
diff --git a/rasterio/warp.py b/rasterio/warp.py
new file mode 100644
index 0000000..b01f841
--- /dev/null
+++ b/rasterio/warp.py
@@ -0,0 +1,46 @@
+"""Raster warping and reprojection"""
+
+from rasterio._warp import _reproject, _transform, _transform_geom, RESAMPLING
+from rasterio.transform import guard_transform
+
+
+def transform(src_crs, dst_crs, xs, ys):
+ """Return transformed vectors of x and y."""
+ return _transform(src_crs, dst_crs, xs, ys)
+
+
+def transform_geom(
+ src_crs, dst_crs, geom,
+ antimeridian_cutting=False, antimeridian_offset=10.0, precision=-1):
+ """Return transformed geometry."""
+ return _transform_geom(
+ src_crs, dst_crs, geom,
+ antimeridian_cutting, antimeridian_offset, precision)
+
+
+def reproject(
+ source, destination,
+ src_transform=None, src_crs=None,
+ dst_transform=None, dst_crs=None,
+ resampling=RESAMPLING.nearest,
+ **kwargs):
+ """Reproject a source raster to a destination.
+
+ If the source and destination are ndarrays, coordinate reference
+ system definitions and affine transformation parameters are required
+ for reprojection.
+
+ If the source and destination are rasterio Bands, shorthand for
+ bands of datasets on disk, the coordinate reference systems and
+ transforms will be read from the appropriate datasets.
+ """
+ if src_transform:
+ src_transform = guard_transform(src_transform).to_gdal()
+ if dst_transform:
+ dst_transform = guard_transform(dst_transform).to_gdal()
+
+ _reproject(
+ source, destination,
+ src_transform, src_crs,
+ dst_transform, dst_crs,
+ resampling, **kwargs)
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..41a8d9f
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,7 @@
+git+https://github.com/sgillies/affine.git#egg=affine
+Cython>=0.20
+Numpy>=1.8.0
+pytest
+coveralls>=0.4
+setuptools
+six
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e095453
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+affine>=1.0
+click
+enum34
+numpy>=1.8
+setuptools
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..5eea52d
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,8 @@
+[nosetests]
+tests=rasterio/tests
+nocapture=True
+verbosity=3
+logging-filter=rasterio
+logging-level=DEBUG
+with-coverage=1
+cover-package=rasterio
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..98fbc4c
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+import logging
+import os
+import subprocess
+import sys
+from setuptools import setup
+
+from distutils.extension import Extension
+
+logging.basicConfig()
+log = logging.getLogger()
+
+# Parse the version from the fiona module.
+with open('rasterio/__init__.py') as f:
+ for line in f:
+ if line.find("__version__") >= 0:
+ version = line.split("=")[1].strip()
+ version = version.strip('"')
+ version = version.strip("'")
+ continue
+
+with open('VERSION.txt', 'w') as f:
+ f.write(version)
+
+# Use Cython if available.
+try:
+ from Cython.Build import cythonize
+except ImportError:
+ cythonize = None
+
+# By default we'll try to get options via gdal-config. On systems without,
+# options will need to be set in setup.cfg or on the setup command line.
+include_dirs = []
+library_dirs = []
+libraries = []
+extra_link_args = []
+
+try:
+ import numpy
+ include_dirs.append(numpy.get_include())
+except ImportError:
+ log.critical("Numpy and its headers are required to run setup(). Exiting.")
+ sys.exit(1)
+
+try:
+ gdal_config = "gdal-config"
+ with open("gdal-config.txt", "w") as gcfg:
+ subprocess.call([gdal_config, "--cflags"], stdout=gcfg)
+ subprocess.call([gdal_config, "--libs"], stdout=gcfg)
+ with open("gdal-config.txt", "r") as gcfg:
+ cflags = gcfg.readline().strip()
+ libs = gcfg.readline().strip()
+ for item in cflags.split():
+ if item.startswith("-I"):
+ include_dirs.extend(item[2:].split(":"))
+ for item in libs.split():
+ if item.startswith("-L"):
+ library_dirs.extend(item[2:].split(":"))
+ elif item.startswith("-l"):
+ libraries.append(item[2:])
+ else:
+ # e.g. -framework GDAL
+ extra_link_args.append(item)
+except Exception as e:
+ log.warning("Failed to get options via gdal-config: %s", str(e))
+
+ext_options = dict(
+ include_dirs=include_dirs,
+ library_dirs=library_dirs,
+ libraries=libraries,
+ extra_link_args=extra_link_args)
+
+# When building from a repo, Cython is required.
+if os.path.exists("MANIFEST.in") and "clean" not in sys.argv:
+ log.info("MANIFEST.in found, presume a repo, cythonizing...")
+ if not cythonize:
+ log.critical(
+ "Cython.Build.cythonize not found. "
+ "Cython is required to build from a repo.")
+ sys.exit(1)
+ ext_modules = cythonize([
+ Extension(
+ 'rasterio._base', ['rasterio/_base.pyx'], **ext_options),
+ Extension(
+ 'rasterio._io', ['rasterio/_io.pyx'], **ext_options),
+ Extension(
+ 'rasterio._copy', ['rasterio/_copy.pyx'], **ext_options),
+ Extension(
+ 'rasterio._features', ['rasterio/_features.pyx'], **ext_options),
+ Extension(
+ 'rasterio._drivers', ['rasterio/_drivers.pyx'], **ext_options),
+ Extension(
+ 'rasterio._warp', ['rasterio/_warp.pyx'], **ext_options),
+ Extension(
+ 'rasterio._err', ['rasterio/_err.pyx'], **ext_options),
+ Extension(
+ 'rasterio._example', ['rasterio/_example.pyx'], **ext_options),
+ ])
+
+# If there's no manifest template, as in an sdist, we just specify .c files.
+else:
+ ext_modules = [
+ Extension(
+ 'rasterio._base', ['rasterio/_base.c'], **ext_options),
+ Extension(
+ 'rasterio._io', ['rasterio/_io.c'], **ext_options),
+ Extension(
+ 'rasterio._copy', ['rasterio/_copy.c'], **ext_options),
+ Extension(
+ 'rasterio._features', ['rasterio/_features.c'], **ext_options),
+ Extension(
+ 'rasterio._drivers', ['rasterio/_drivers.c'], **ext_options),
+ Extension(
+ 'rasterio._warp', ['rasterio/_warp.cpp'], **ext_options),
+ Extension(
+ 'rasterio._err', ['rasterio/_err.c'], **ext_options),
+ Extension(
+ 'rasterio._example', ['rasterio/_example.c'], **ext_options),
+ ]
+
+with open('README.rst') as f:
+ readme = f.read()
+
+# Runtime requirements.
+inst_reqs = [
+ 'affine>=1.0',
+ 'click',
+ 'Numpy>=1.7',
+ 'setuptools' ]
+
+if sys.version_info < (3, 4):
+ inst_reqs.append('enum34')
+
+setup(name='rasterio',
+ version=version,
+ description=(
+ "Fast and direct raster I/O for Python programmers who use Numpy"),
+ long_description=readme,
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Information Technology',
+ 'Intended Audience :: Science/Research',
+ 'License :: OSI Approved :: BSD License',
+ 'Programming Language :: C',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Topic :: Multimedia :: Graphics :: Graphics Conversion',
+ 'Topic :: Scientific/Engineering :: GIS'
+ ],
+ keywords='raster gdal',
+ author='Sean Gillies',
+ author_email='sean at mapbox.com',
+ url='https://github.com/mapbox/rasterio',
+ license='BSD',
+ package_dir={'': '.'},
+ packages=['rasterio', 'rasterio.rio'],
+ entry_points='''
+ [console_scripts]
+ rio=rasterio.rio.main:cli
+ ''',
+ include_package_data=True,
+ ext_modules=ext_modules,
+ zip_safe=False,
+ install_requires=inst_reqs )
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..e55a25e
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,21 @@
+import functools
+import operator
+import os
+import sys
+
+import pytest
+
+if sys.version_info > (3,):
+ reduce = functools.reduce
+
+test_files = [os.path.join(os.path.dirname(__file__), p) for p in [
+ 'data/RGB.byte.tif', 'data/float.tif', 'data/float_nan.tif', 'data/shade.tif']]
+
+def pytest_cmdline_main(config):
+ # Bail if the test raster data is not present. Test data is not
+ # distributed with sdists since 0.12.
+ if reduce(operator.and_, map(os.path.exists, test_files)):
+ print("Test data present.")
+ else:
+ print("Test data not present. See download directions in tests/README.txt")
+ sys.exit(1)
diff --git a/tests/data/README.rst b/tests/data/README.rst
new file mode 100644
index 0000000..9c85140
--- /dev/null
+++ b/tests/data/README.rst
@@ -0,0 +1,8 @@
+Testing
+=======
+
+Rasterio's tests require several raster data files. Grab them from
+
+https://github.com/mapbox/rasterio/tree/master/tests/data
+
+and copy them to this directory.
diff --git a/tests/data/RGB.byte.tif b/tests/data/RGB.byte.tif
new file mode 100644
index 0000000..1efaf4a
Binary files /dev/null and b/tests/data/RGB.byte.tif differ
diff --git a/tests/data/float.tif b/tests/data/float.tif
new file mode 100644
index 0000000..96c67bc
Binary files /dev/null and b/tests/data/float.tif differ
diff --git a/tests/data/float_nan.tif b/tests/data/float_nan.tif
new file mode 100644
index 0000000..dec973f
Binary files /dev/null and b/tests/data/float_nan.tif differ
diff --git a/tests/data/shade.tif b/tests/data/shade.tif
new file mode 100644
index 0000000..6a5a226
Binary files /dev/null and b/tests/data/shade.tif differ
diff --git a/tests/test_band.py b/tests/test_band.py
new file mode 100644
index 0000000..71fb830
--- /dev/null
+++ b/tests/test_band.py
@@ -0,0 +1,10 @@
+import rasterio
+
+def test_band():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ b = rasterio.band(src, 1)
+ assert b.ds == src
+ assert b.bidx == 1
+ assert b.dtype in src.dtypes
+ assert b.shape == src.shape
+
diff --git a/tests/test_blocks.py b/tests/test_blocks.py
new file mode 100644
index 0000000..912baf5
--- /dev/null
+++ b/tests/test_blocks.py
@@ -0,0 +1,123 @@
+import logging
+import os.path
+import unittest
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import numpy
+
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+class WindowTest(unittest.TestCase):
+ def test_window_shape_errors(self):
+ # Positive height and width are needed when stop is None.
+ self.assertRaises(
+ ValueError,
+ rasterio.window_shape,
+ (((10, 20),(10, None)),) )
+ self.assertRaises(
+ ValueError,
+ rasterio.window_shape,
+ (((None, 10),(10, 20)),) )
+ def test_window_shape_None_start(self):
+ self.assertEqual(
+ rasterio.window_shape(((None,4),(None,102))),
+ (4, 102))
+ def test_window_shape_None_stop(self):
+ self.assertEqual(
+ rasterio.window_shape(((10, None),(10, None)), 100, 90),
+ (90, 80))
+ def test_window_shape_positive(self):
+ self.assertEqual(
+ rasterio.window_shape(((0,4),(1,102))),
+ (4, 101))
+ def test_window_shape_negative(self):
+ self.assertEqual(
+ rasterio.window_shape(((-10, None),(-10, None)), 100, 90),
+ (10, 10))
+ self.assertEqual(
+ rasterio.window_shape(((~0, None),(~0, None)), 100, 90),
+ (1, 1))
+ self.assertEqual(
+ rasterio.window_shape(((None, ~0),(None, ~0)), 100, 90),
+ (99, 89))
+ def test_eval(self):
+ self.assertEqual(
+ rasterio.eval_window(((-10, None), (-10, None)), 100, 90),
+ ((90, 100), (80, 90)))
+ self.assertEqual(
+ rasterio.eval_window(((None, -10), (None, -10)), 100, 90),
+ ((0, 90), (0, 80)))
+
+def test_window_index():
+ idx = rasterio.window_index(((0,4),(1,12)))
+ assert len(idx) == 2
+ r, c = idx
+ assert r.start == 0
+ assert r.stop == 4
+ assert c.start == 1
+ assert c.stop == 12
+ arr = numpy.ones((20,20))
+ assert arr[idx].shape == (4, 11)
+
+class RasterBlocksTest(unittest.TestCase):
+ def test_blocks(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ self.assertEqual(len(s.block_shapes), 3)
+ self.assertEqual(s.block_shapes, [(3, 791), (3, 791), (3, 791)])
+ windows = s.block_windows(1)
+ (j,i), first = next(windows)
+ self.assertEqual((j,i), (0, 0))
+ self.assertEqual(first, ((0, 3), (0, 791)))
+ windows = s.block_windows()
+ (j,i), first = next(windows)
+ self.assertEqual((j,i), (0, 0))
+ self.assertEqual(first, ((0, 3), (0, 791)))
+ (j, i), second = next(windows)
+ self.assertEqual((j,i), (1, 0))
+ self.assertEqual(second, ((3, 6), (0, 791)))
+ (j, i), last = list(windows)[~0]
+ self.assertEqual((j,i), (239, 0))
+ self.assertEqual(last, ((717, 718), (0, 791)))
+ def test_block_coverage(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ self.assertEqual(
+ s.width*s.height,
+ sum((w[0][1]-w[0][0])*(w[1][1]-w[1][0])
+ for ji, w in s.block_windows(1)))
+
+class WindowReadTest(unittest.TestCase):
+ def test_read_window(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ windows = s.block_windows(1)
+ ji, first_window = next(windows)
+ first_block = s.read_band(1, window=first_window)
+ self.assertEqual(first_block.dtype, rasterio.ubyte)
+ self.assertEqual(
+ first_block.shape,
+ rasterio.window_shape(first_window))
+
+class WindowWriteTest(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+ def tearDown(self):
+ shutil.rmtree(self.tempdir)
+ def test_write_window(self):
+ name = os.path.join(self.tempdir, "test_write_window.tif")
+ a = numpy.ones((50, 50), dtype=rasterio.ubyte) * 127
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=1,
+ dtype=a.dtype) as s:
+ s.write_band(1, a, window=((30, 80), (10, 60)))
+ # subprocess.call(["open", name])
+ info = subprocess.check_output(["gdalinfo", "-stats", name])
+ self.assert_(
+ "Minimum=0.000, Maximum=127.000, "
+ "Mean=31.750, StdDev=54.993" in info.decode('utf-8'),
+ info)
+
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..35cbabb
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,115 @@
+import subprocess
+
+
+def test_cli_bounds_obj_bbox():
+ result = subprocess.check_output(
+ 'rio bounds tests/data/RGB.byte.tif --bbox --precision 6',
+ shell=True)
+ assert result.decode('utf-8').strip() == '[-78.898133, 23.564991, -76.599438, 25.550874]'
+
+
+def test_cli_bounds_obj_bbox_mercator():
+ result = subprocess.check_output(
+ 'rio bounds tests/data/RGB.byte.tif --bbox --mercator --precision 3',
+ shell=True)
+ assert result.decode('utf-8').strip() == '[-8782900.033, 2700489.278, -8527010.472, 2943560.235]'
+
+
+def test_cli_bounds_obj_feature():
+ result = subprocess.check_output(
+ 'rio bounds tests/data/RGB.byte.tif --feature --precision 6',
+ shell=True)
+ assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}'
+
+
+def test_cli_bounds_obj_collection():
+ result = subprocess.check_output(
+ 'rio bounds tests/data/RGB.byte.tif --precision 6',
+ shell=True)
+ assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
+
+
+def test_cli_bounds_seq_feature_rs():
+ result = subprocess.check_output(
+ 'rio bounds tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --feature --precision 6',
+ shell=True)
+ assert result.decode('utf-8').startswith(u'\x1e')
+ assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}'
+
+
+def test_cli_bounds_seq_collection():
+ result = subprocess.check_output(
+ 'rio bounds tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --precision 6',
+ shell=True)
+ assert result.decode('utf-8').startswith(u'\x1e')
+ assert result.decode('utf-8').strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
+
+
+def test_cli_bounds_seq_bbox():
+ result = subprocess.check_output(
+ 'rio bounds tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --bbox --precision 6',
+ shell=True)
+ assert result.decode('utf-8').startswith(u'\x1e')
+ assert result.decode('utf-8').strip() == '[-78.898133, 23.564991, -76.599438, 25.550874]'
+
+
+def test_cli_bounds_seq_collection_multi(tmpdir):
+ filename = str(tmpdir.join("test.json"))
+ tmp = open(filename, 'w')
+
+ subprocess.check_call(
+ 'rio bounds tests/data/RGB.byte.tif tests/data/RGB.byte.tif --x-json-seq --x-json-seq-rs --precision 6',
+ stdout=tmp,
+ shell=True)
+
+ tmp.close()
+ tmp = open(filename, 'r')
+ json_texts = []
+ text = ""
+ for line in tmp:
+ rs_idx = line.find(u'\x1e')
+ if rs_idx >= 0:
+ if text:
+ text += line[:rs_idx]
+ json_texts.append(text)
+ text = line[rs_idx+1:]
+ else:
+ text += line
+ else:
+ json_texts.append(text)
+
+ assert len(json_texts) == 2
+ assert json_texts[0].strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
+ assert json_texts[1].strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "features": [{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "1", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}], "type": "FeatureCollection"}'
+
+
+def test_cli_info_count():
+ result = subprocess.check_output(
+ 'if [ `rio info tests/data/RGB.byte.tif --count` -eq 3 ]; '
+ 'then echo "True"; fi',
+ shell=True)
+ assert result.decode('utf-8').strip() == 'True'
+
+
+def test_cli_info_nodata():
+ result = subprocess.check_output(
+ 'if [ `rio info tests/data/RGB.byte.tif --nodata` = "0.0" ]; '
+ 'then echo "True"; fi',
+ shell=True)
+ assert result.decode('utf-8').strip() == 'True'
+
+
+def test_cli_info_dtype():
+ result = subprocess.check_output(
+ 'if [ `rio info tests/data/RGB.byte.tif --dtype` = "uint8" ]; '
+ 'then echo "True"; fi',
+ shell=True)
+ assert result.decode('utf-8').strip() == 'True'
+
+
+def test_cli_info_shape():
+ result = subprocess.check_output(
+ 'if [[ `rio info tests/data/RGB.byte.tif --shape` == "718 791" ]]; '
+ 'then echo "True"; fi',
+ shell=True, executable='/bin/bash')
+ assert result.decode('utf-8').strip() == 'True'
diff --git a/tests/test_colorinterp.py b/tests/test_colorinterp.py
new file mode 100644
index 0000000..d05e8a2
--- /dev/null
+++ b/tests/test_colorinterp.py
@@ -0,0 +1,25 @@
+
+import rasterio
+from rasterio.enums import ColorInterp
+
+
+def test_colorinterp(tmpdir):
+
+ with rasterio.drivers():
+
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.colorinterp(1) == ColorInterp.red
+ assert src.colorinterp(2) == ColorInterp.green
+ assert src.colorinterp(3) == ColorInterp.blue
+
+ tiffname = str(tmpdir.join('foo.tif'))
+
+ meta = src.meta
+ meta['photometric'] = 'CMYK'
+ meta['count'] = 4
+ with rasterio.open(tiffname, 'w', **meta) as dst:
+ assert dst.colorinterp(1) == ColorInterp.cyan
+ assert dst.colorinterp(2) == ColorInterp.magenta
+ assert dst.colorinterp(3) == ColorInterp.yellow
+ assert dst.colorinterp(4) == ColorInterp.black
+
diff --git a/tests/test_colormap.py b/tests/test_colormap.py
new file mode 100644
index 0000000..e5899fb
--- /dev/null
+++ b/tests/test_colormap.py
@@ -0,0 +1,33 @@
+import logging
+import pytest
+import subprocess
+import sys
+
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+def test_write_colormap(tmpdir):
+
+ with rasterio.drivers():
+
+ with rasterio.open('tests/data/shade.tif') as src:
+ shade = src.read_band(1)
+ meta = src.meta
+
+ tiffname = str(tmpdir.join('foo.tif'))
+
+ 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)})
+ cmap = dst.colormap(1)
+ assert cmap[0] == (255, 0, 0, 255)
+ assert cmap[255] == (0, 0, 255, 255)
+
+ with rasterio.open(tiffname) as src:
+ cmap = src.colormap(1)
+ assert cmap[0] == (255, 0, 0, 255)
+ assert cmap[255] == (0, 0, 255, 255)
+
+ # subprocess.call(['open', tiffname])
+
diff --git a/tests/test_coords.py b/tests/test_coords.py
new file mode 100644
index 0000000..69eafcf
--- /dev/null
+++ b/tests/test_coords.py
@@ -0,0 +1,34 @@
+
+import rasterio
+
+def test_bounds():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.bounds == (101985.0, 2611485.0, 339315.0, 2826915.0)
+
+def test_ul():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.ul(0, 0) == (101985.0, 2826915.0)
+ assert src.ul(1, 0) == (101985.0, 2826614.95821727)
+ assert src.ul(src.height, src.width) == (339315.0, 2611485.0)
+ assert tuple(
+ round(v, 6) for v in src.ul(~0, ~0)
+ ) == (339014.962073, 2611785.041783)
+
+def test_res():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert tuple(round(v, 6) for v in src.res) == (300.037927, 300.041783)
+
+def test_index():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.index(101985.0, 2826915.0) == (0, 0)
+ assert src.index(101985.0+400.0, 2826915.0) == (0, 1)
+ assert src.index(101985.0+400.0, 2826915.0-700.0) == (2, 1)
+
+def test_window():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ left, bottom, right, top = src.bounds
+ assert src.window(left, bottom, right, top) == ((0, src.height),
+ (0, src.width))
+ assert src.window(left, top-400, left+400, top) == ((0, 1), (0, 1))
+ assert src.window(left, top-500, left+500, top) == ((0, 2), (0, 2))
+
diff --git a/tests/test_copy.py b/tests/test_copy.py
new file mode 100644
index 0000000..4d2b4dc
--- /dev/null
+++ b/tests/test_copy.py
@@ -0,0 +1,26 @@
+import logging
+import os.path
+import unittest
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import numpy
+
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+class CopyTest(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+ def tearDown(self):
+ shutil.rmtree(self.tempdir)
+ def test_copy(self):
+ name = os.path.join(self.tempdir, 'test_copy.tif')
+ rasterio.copy(
+ 'tests/data/RGB.byte.tif',
+ name)
+ info = subprocess.check_output(["gdalinfo", name])
+ self.assert_("GTiff" in info.decode('utf-8'))
diff --git a/tests/test_crs.py b/tests/test_crs.py
new file mode 100644
index 0000000..4bb549b
--- /dev/null
+++ b/tests/test_crs.py
@@ -0,0 +1,66 @@
+import logging
+import pytest
+import subprocess
+import sys
+
+import rasterio
+from rasterio import 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():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.crs == {'init': 'epsg:32618'}
+
+def test_read_epsg3857(tmpdir):
+ tiffname = str(tmpdir.join('lol.tif'))
+ subprocess.call([
+ 'gdalwarp', '-t_srs', 'EPSG:3857',
+ 'tests/data/RGB.byte.tif', tiffname])
+ with rasterio.drivers():
+ with rasterio.open(tiffname) as src:
+ assert src.crs == {'init': 'epsg:3857'}
+
+# Ensure that CRS sticks when we write a file.
+def test_write_3857(tmpdir):
+ src_path = str(tmpdir.join('lol.tif'))
+ subprocess.call([
+ 'gdalwarp', '-t_srs', 'EPSG:3857',
+ 'tests/data/RGB.byte.tif', src_path])
+ dst_path = str(tmpdir.join('wut.tif'))
+ with rasterio.drivers():
+ with rasterio.open(src_path) as src:
+ with rasterio.open(dst_path, 'w', **src.meta) as dst:
+ assert dst.crs == {'init': 'epsg:3857'}
+ info = subprocess.check_output([
+ 'gdalinfo', dst_path])
+ assert """PROJCS["WGS 84 / Pseudo-Mercator",
+ GEOGCS["WGS 84",
+ DATUM["WGS_1984",
+ SPHEROID["WGS 84",6378137,298.257223563,
+ AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich",0],
+ UNIT["degree",0.0174532925199433],
+ AUTHORITY["EPSG","4326"]],
+ PROJECTION["Mercator_1SP"],
+ PARAMETER["central_meridian",0],
+ PARAMETER["scale_factor",1],
+ PARAMETER["false_easting",0],
+ PARAMETER["false_northing",0],
+ UNIT["metre",1,
+ AUTHORITY["EPSG","9001"]],
+ EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs"],
+ AUTHORITY["EPSG","3857"]]""" in info.decode('utf-8')
+
+
+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,
+ which makes presents bare parameters as key=<bool>."""
+
+ # Example produced by pyproj
+ crs_dict = crs.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0')
+ assert crs_dict.get('no_defs', False) is True
diff --git a/tests/test_driver_management.py b/tests/test_driver_management.py
new file mode 100644
index 0000000..7368ed3
--- /dev/null
+++ b/tests/test_driver_management.py
@@ -0,0 +1,47 @@
+import logging
+import sys
+
+import rasterio
+from rasterio._drivers import driver_count, GDALEnv
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+def test_drivers():
+ with rasterio.drivers() as m:
+ assert driver_count() > 0
+ assert type(m) == GDALEnv
+
+ n = rasterio.drivers()
+ assert driver_count() > 0
+ assert type(n) == GDALEnv
+
+def test_options(tmpdir):
+ """Test that setting CPL_DEBUG=True results in GDAL debug messages.
+ """
+ logger = logging.getLogger('GDAL')
+ logger.setLevel(logging.DEBUG)
+ logfile1 = str(tmpdir.join('test_options1.log'))
+ fh = logging.FileHandler(logfile1)
+ logger.addHandler(fh)
+
+ # With CPL_DEBUG=True, expect debug messages from GDAL in
+ # logfile1
+ with rasterio.drivers(CPL_DEBUG=True):
+ with rasterio.open("tests/data/RGB.byte.tif") as src:
+ pass
+
+ log = open(logfile1).read()
+ assert "GDAL: GDALOpen(tests/data/RGB.byte.tif" in log
+
+ # The GDAL env above having exited, CPL_DEBUG should be OFF.
+ logfile2 = str(tmpdir.join('test_options2.log'))
+ fh = logging.FileHandler(logfile2)
+ logger.addHandler(fh)
+
+ with rasterio.open("tests/data/RGB.byte.tif") as src:
+ pass
+
+ # Expect no debug messages from GDAL.
+ log = open(logfile2).read()
+ assert "GDAL: GDALOpen(tests/data/RGB.byte.tif" not in log
+
diff --git a/tests/test_dtypes.py b/tests/test_dtypes.py
new file mode 100644
index 0000000..7a41fc7
--- /dev/null
+++ b/tests/test_dtypes.py
@@ -0,0 +1,14 @@
+import numpy as np
+
+import rasterio.dtypes
+
+def test_np_dt_uint8():
+ assert rasterio.dtypes.check_dtype(np.uint8)
+
+def test_dt_ubyte():
+ assert rasterio.dtypes.check_dtype(rasterio.ubyte)
+
+def test_gdal_name():
+ assert rasterio.dtypes._gdal_typename(rasterio.ubyte) == 'Byte'
+ assert rasterio.dtypes._gdal_typename(np.uint8) == 'Byte'
+ assert rasterio.dtypes._gdal_typename(np.uint16) == 'UInt16'
diff --git a/tests/test_features_rasterize.py b/tests/test_features_rasterize.py
new file mode 100644
index 0000000..2d57da7
--- /dev/null
+++ b/tests/test_features_rasterize.py
@@ -0,0 +1,157 @@
+import logging
+import sys
+import numpy
+import pytest
+
+import rasterio
+from rasterio.features import shapes, rasterize
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_rasterize_geometries():
+ """
+ Make sure that geometries are correctly rasterized according to parameters
+ """
+
+ rows = cols = 10
+ transform = (1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
+ geometry = {
+ 'type': 'Polygon',
+ 'coordinates': [[(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]]
+ }
+
+ with rasterio.drivers():
+ # we expect a subset of the pixels using default mode
+ result = rasterize([geometry], out_shape=(rows, cols))
+ truth = numpy.zeros((rows, cols))
+ truth[2:4, 2:4] = 1
+ assert numpy.array_equal(result, truth)
+
+ # we expect all touched pixels
+ result = rasterize(
+ [geometry], out_shape=(rows, cols), all_touched=True
+ )
+ truth = numpy.zeros((rows, cols))
+ truth[2:5, 2:5] = 1
+ assert numpy.array_equal(result, truth)
+
+ # we expect the pixel value to match the one we pass in
+ value = 5
+ result = rasterize([(geometry, value)], out_shape=(rows, cols))
+ truth = numpy.zeros((rows, cols))
+ truth[2:4, 2:4] = value
+ assert numpy.array_equal(result, truth)
+
+ # Check the fill and default transform.
+ # we expect the pixel value to match the one we pass in
+ value = 5
+ result = rasterize(
+ [(geometry, value)],
+ out_shape=(rows, cols),
+ fill=1
+ )
+ truth = numpy.ones((rows, cols))
+ truth[2:4, 2:4] = value
+ assert numpy.array_equal(result, truth)
+
+
+def test_rasterize_dtype():
+ """Make sure that data types are handled correctly"""
+
+ rows = cols = 10
+ transform = (1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
+ geometry = {
+ 'type': 'Polygon',
+ 'coordinates': [[(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]]
+ }
+
+ with rasterio.drivers():
+ # Supported types should all work properly
+ supported_types = (
+ ('int16', -32768),
+ ('int32', -2147483648),
+ ('uint8', 255),
+ ('uint16', 65535),
+ ('uint32', 4294967295),
+ ('float32', 1.434532),
+ ('float64', -98332.133422114)
+ )
+
+ for dtype, default_value in supported_types:
+ truth = numpy.zeros((rows, cols), dtype=dtype)
+ truth[2:4, 2:4] = default_value
+
+ result = rasterize(
+ [geometry],
+ out_shape=(rows, cols),
+ default_value=default_value,
+ dtype=dtype
+ )
+ assert numpy.array_equal(result, truth)
+ assert numpy.dtype(result.dtype) == numpy.dtype(truth.dtype)
+
+ result = rasterize(
+ [(geometry, default_value)],
+ out_shape=(rows, cols)
+ )
+ if numpy.dtype(dtype).kind == 'f':
+ assert numpy.allclose(result, truth)
+ else:
+ assert numpy.array_equal(result, truth)
+ # Since dtype is auto-detected, it may not match due to upcasting
+
+ # Unsupported types should all raise exceptions
+ unsupported_types = (
+ ('int8', -127),
+ ('int64', 20439845334323),
+ ('float16', -9343.232)
+ )
+
+ for dtype, default_value in unsupported_types:
+ with pytest.raises(ValueError):
+ rasterize(
+ [geometry],
+ out_shape=(rows, cols),
+ default_value=default_value,
+ dtype=dtype
+ )
+
+ with pytest.raises(ValueError):
+ rasterize(
+ [(geometry, default_value)],
+ out_shape=(rows, cols),
+ dtype=dtype
+ )
+
+ # Mismatched values and dtypes should raise exceptions
+ mismatched_types = (('uint8', 3.2423), ('uint8', -2147483648))
+ for dtype, default_value in mismatched_types:
+ with pytest.raises(ValueError):
+ rasterize(
+ [geometry],
+ out_shape=(rows, cols),
+ default_value=default_value,
+ dtype=dtype
+ )
+
+ with pytest.raises(ValueError):
+ rasterize(
+ [(geometry, default_value)],
+ out_shape=(rows, cols),
+ dtype=dtype
+ )
+
+
+def test_rasterize_geometries_symmetric():
+ """Make sure that rasterize is symmetric with shapes"""
+
+ rows = cols = 10
+ transform = (1.0, 0.0, 0.0, 0.0, -1.0, 0.0)
+ truth = numpy.zeros((rows, cols), dtype=rasterio.ubyte)
+ truth[2:5, 2:5] = 1
+ with rasterio.drivers():
+ s = shapes(truth, transform=transform)
+ result = rasterize(s, out_shape=(rows, cols), transform=transform)
+ assert numpy.array_equal(result, truth)
diff --git a/tests/test_features_shapes.py b/tests/test_features_shapes.py
new file mode 100644
index 0000000..4ab9d78
--- /dev/null
+++ b/tests/test_features_shapes.py
@@ -0,0 +1,110 @@
+import logging
+import sys
+import numpy
+import pytest
+
+import rasterio
+import rasterio.features as ftrz
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_shapes():
+ """Test creation of shapes from pixel values"""
+
+ image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
+ image[5:15, 5:15] = 127
+ with rasterio.drivers():
+ shapes = ftrz.shapes(image)
+ shape, val = next(shapes)
+ assert shape['type'] == 'Polygon'
+ assert len(shape['coordinates']) == 2 # exterior and hole
+ assert val == 0
+ shape, val = next(shapes)
+ assert shape['type'] == 'Polygon'
+ assert len(shape['coordinates']) == 1 # no hole
+ assert val == 127
+ try:
+ shape, val = next(shapes)
+ except StopIteration:
+ assert True
+ else:
+ assert False
+
+
+def test_shapes_band_shortcut():
+ """Test rasterio bands as input to shapes"""
+
+ with rasterio.drivers():
+ with rasterio.open('tests/data/shade.tif') as src:
+ shapes = ftrz.shapes(rasterio.band(src, 1))
+ shape, val = next(shapes)
+ assert shape['type'] == 'Polygon'
+ assert len(shape['coordinates']) == 1
+ assert val == 255
+
+
+def test_shapes_internal_driver_manager():
+ """Make sure this works if driver is managed outside this test"""
+
+ image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
+ image[5:15, 5:15] = 127
+ shapes = ftrz.shapes(image)
+ shape, val = next(shapes)
+ assert shape['type'] == 'Polygon'
+
+
+def test_shapes_connectivity():
+ """Test connectivity options"""
+
+ image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
+ image[5:11, 5:11] = 1
+ image[11, 11] = 1
+
+ shapes = ftrz.shapes(image, connectivity=8)
+ shape, val = next(shapes)
+ assert len(shape['coordinates'][0]) == 9
+ # Note: geometry is not technically valid at this point, it has a self
+ # intersection at 11,11
+
+
+def test_shapes_dtype():
+ """Test image data type handling"""
+
+ rows = cols = 10
+ with rasterio.drivers():
+ supported_types = (
+ ('int16', -32768),
+ ('int32', -2147483648),
+ ('uint8', 255),
+ ('uint16', 65535),
+ ('float32', 1.434532)
+ )
+
+ for dtype, test_value in supported_types:
+ image = numpy.zeros((rows, cols), dtype=dtype)
+ image[2:5, 2:5] = test_value
+
+ shapes = ftrz.shapes(image)
+ shape, value = next(shapes)
+ if dtype == 'float32':
+ assert round(value, 6) == round(test_value, 6)
+ else:
+ assert value == test_value
+
+ # Unsupported types should all raise exceptions
+ unsupported_types = (
+ ('int8', -127),
+ ('uint32', 4294967295),
+ ('int64', 20439845334323),
+ ('float16', -9343.232),
+ ('float64', -98332.133422114)
+ )
+
+ for dtype, test_value in unsupported_types:
+ with pytest.raises(ValueError):
+ image = numpy.zeros((rows, cols), dtype=dtype)
+ image[2:5, 2:5] = test_value
+ shapes = ftrz.shapes(image)
+ shape, value = next(shapes)
diff --git a/tests/test_features_sieve.py b/tests/test_features_sieve.py
new file mode 100644
index 0000000..baea580
--- /dev/null
+++ b/tests/test_features_sieve.py
@@ -0,0 +1,141 @@
+import logging
+import sys
+import numpy
+import pytest
+
+import rasterio
+import rasterio.features as ftrz
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_sieve():
+ """Test sieving a 10x10 feature from an ndarray."""
+
+ image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
+ image[5:15, 5:15] = 1
+
+ # An attempt to sieve out features smaller than 100 should not change the
+ # image.
+ with rasterio.drivers():
+ sieved_image = ftrz.sieve(image, 100)
+ assert numpy.array_equal(sieved_image, image)
+
+ # Setting the size to 100 should leave us an empty, False image.
+ with rasterio.drivers():
+ sieved_image = ftrz.sieve(image, 101)
+ assert not sieved_image.any()
+
+
+def test_sieve_connectivity():
+ """Test proper behavior of connectivity"""
+
+ image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
+ image[5:15:2, 5:15] = 1
+ image[6, 4] = 1
+ image[8, 15] = 1
+ image[10, 4] = 1
+ image[12, 15] = 1
+
+ # Diagonals not connected, all become small features that will be removed
+ sieved_image = ftrz.sieve(image, 54, connectivity=4)
+ assert not sieved_image.any()
+
+ # Diagonals connected, everything is retained
+ sieved_image = ftrz.sieve(image, 54, connectivity=8)
+ assert numpy.array_equal(sieved_image, image)
+
+
+def test_sieve_output():
+ """Test proper behavior of output image, if passed into sieve"""
+
+ with rasterio.drivers():
+ shape = (20, 20)
+ image = numpy.zeros(shape, dtype=rasterio.ubyte)
+ image[5:15, 5:15] = 1
+
+ # Output should match returned array
+ output = numpy.zeros_like(image)
+ output[1:3, 1:3] = 5
+ sieved_image = ftrz.sieve(image, 100, output=output)
+ assert numpy.array_equal(output, sieved_image)
+
+ # Output of different dtype should fail
+ output = numpy.zeros(shape, dtype=rasterio.int32)
+ with pytest.raises(ValueError):
+ ftrz.sieve(image, 100, output)
+
+
+def test_sieve_mask():
+ """Test proper behavior of mask image, if passed int sieve"""
+
+ with rasterio.drivers():
+ shape = (20, 20)
+ image = numpy.zeros(shape, dtype=rasterio.ubyte)
+ image[5:15, 5:15] = 1
+ image[1:3, 1:3] = 2
+
+ # Blank mask has no effect, only areas smaller than size will be
+ # removed
+ mask = numpy.ones(shape, dtype=rasterio.bool_)
+ sieved_image = ftrz.sieve(image, 100, mask=mask)
+ truth = numpy.zeros_like(image)
+ truth[5:15, 5:15] = 1
+ assert numpy.array_equal(sieved_image, truth)
+
+ # Only areas within the overlap of the mask and values will be kept
+ mask = numpy.ones(shape, dtype=rasterio.bool_)
+ mask[7:10, 7:10] = False
+ sieved_image = ftrz.sieve(image, 100, mask=mask)
+ truth = numpy.zeros_like(image)
+ truth[7:10, 7:10] = 1
+ assert numpy.array_equal(sieved_image, truth)
+
+ # mask of other type than rasterio.bool_ should fail
+ mask = numpy.zeros(shape, dtype=rasterio.uint8)
+ with pytest.raises(ValueError):
+ ftrz.sieve(image, 100, mask=mask)
+
+
+def test_dtypes():
+ """Test data type support for sieve"""
+
+ rows = cols = 10
+ with rasterio.drivers():
+ supported_types = (
+ ('int16', -32768),
+ ('int32', -2147483648),
+ ('uint8', 255),
+ ('uint16', 65535)
+ )
+
+ for dtype, test_value in supported_types:
+ image = numpy.zeros((rows, cols), dtype=dtype)
+ image[2:5, 2:5] = test_value
+
+ # Sieve should return the original image
+ sieved_image = ftrz.sieve(image, 2)
+ assert numpy.array_equal(image, sieved_image)
+ assert numpy.dtype(sieved_image.dtype).name == dtype
+
+ # Sieve should return a blank image
+ sieved_image = ftrz.sieve(image, 10)
+ assert numpy.array_equal(numpy.zeros_like(image), sieved_image)
+ assert numpy.dtype(sieved_image.dtype).name == dtype
+
+ # Unsupported types should all raise exceptions
+ unsupported_types = (
+ ('int8', -127),
+ ('uint32', 4294967295),
+ ('int64', 20439845334323),
+ ('float16', -9343.232),
+ ('float32', 1.434532),
+ ('float64', -98332.133422114)
+ )
+
+ for dtype, test_value in unsupported_types:
+ with pytest.raises(ValueError):
+ image = numpy.zeros((rows, cols), dtype=dtype)
+ image[2:5, 2:5] = test_value
+ sieved_image = ftrz.sieve(image, 2)
diff --git a/tests/test_indexing.py b/tests/test_indexing.py
new file mode 100644
index 0000000..fbc57b4
--- /dev/null
+++ b/tests/test_indexing.py
@@ -0,0 +1,25 @@
+
+import rasterio
+
+def test_index():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ left, bottom, right, top = src.bounds
+ assert src.index(left, top) == (0, 0)
+ assert src.index(right, top) == (0, src.width)
+ assert src.index(right, bottom) == (src.height, src.width)
+ assert src.index(left, bottom) == (src.height, 0)
+
+def test_full_window():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.window(*src.bounds) == tuple(zip((0, 0), src.shape))
+
+def test_window_exception():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ left, bottom, right, top = src.bounds
+ left -= 1000.0
+ try:
+ _ = src.window(left, bottom, right, top)
+ assert False
+ except ValueError:
+ assert True
+
diff --git a/tests/test_nodata.py b/tests/test_nodata.py
new file mode 100644
index 0000000..1fee860
--- /dev/null
+++ b/tests/test_nodata.py
@@ -0,0 +1,46 @@
+import logging
+import pytest
+import re
+import subprocess
+import sys
+
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+def test_nodata(tmpdir):
+ dst_path = str(tmpdir.join('lol.tif'))
+ with rasterio.drivers():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ with rasterio.open(dst_path, 'w', **src.meta) as dst:
+ assert dst.meta['nodata'] == 0.0
+ assert dst.nodatavals == [0.0, 0.0, 0.0]
+ info = subprocess.check_output([
+ 'gdalinfo', dst_path])
+ pattern = b'Band 1.*?NoData Value=0'
+ assert re.search(pattern, info, re.DOTALL) is not None
+ pattern = b'Band 2.*?NoData Value=0'
+ assert re.search(pattern, info, re.DOTALL) is not None
+ pattern = b'Band 2.*?NoData Value=0'
+ assert re.search(pattern, info, re.DOTALL) is not None
+
+def test_set_nodata(tmpdir):
+ dst_path = str(tmpdir.join('lol.tif'))
+ with rasterio.drivers():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ meta = src.meta
+ meta['nodata'] = 42
+ with rasterio.open(dst_path, 'w', **meta) as dst:
+ assert dst.meta['nodata'] == 42
+ assert dst.nodatavals == [42, 42, 42]
+ info = subprocess.check_output([
+ 'gdalinfo', dst_path])
+ pattern = b'Band 1.*?NoData Value=42'
+ assert re.search(pattern, info, re.DOTALL) is not None
+ pattern = b'Band 2.*?NoData Value=42'
+ assert re.search(pattern, info, re.DOTALL) is not None
+ pattern = b'Band 2.*?NoData Value=42'
+ assert re.search(pattern, info, re.DOTALL) is not None
+
+
+
diff --git a/tests/test_pad.py b/tests/test_pad.py
new file mode 100644
index 0000000..8cd894c
--- /dev/null
+++ b/tests/test_pad.py
@@ -0,0 +1,15 @@
+
+import affine
+import numpy
+
+import rasterio
+
+
+def test_pad():
+ arr = numpy.ones((10, 10))
+ trans = affine.Affine(1.0, 0.0, 0.0, 0.0, -1.0, 10.0)
+ arr2, trans2 = rasterio.pad(arr, trans, 2, 'edge')
+ assert arr2.shape == (14, 14)
+ assert trans2.xoff == -2.0
+ assert trans2.yoff == 12.0
+
diff --git a/tests/test_png.py b/tests/test_png.py
new file mode 100644
index 0000000..c9324a0
--- /dev/null
+++ b/tests/test_png.py
@@ -0,0 +1,20 @@
+import logging
+import subprocess
+import sys
+import re
+import numpy
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_write_ubyte(tmpdir):
+ name = str(tmpdir.mkdir("sub").join("test_write_ubyte.png"))
+ a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
+ with rasterio.open(
+ name, 'w',
+ driver='PNG', width=100, height=100, count=1,
+ dtype=a.dtype) as s:
+ s.write_band(1, a)
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
diff --git a/tests/test_read.py b/tests/test_read.py
new file mode 100644
index 0000000..f962138
--- /dev/null
+++ b/tests/test_read.py
@@ -0,0 +1,242 @@
+import unittest
+
+import numpy
+from hashlib import md5
+
+import rasterio
+
+
+class ReaderContextTest(unittest.TestCase):
+
+ def test_context(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ self.assertEqual(s.name, 'tests/data/RGB.byte.tif')
+ self.assertEqual(s.driver, 'GTiff')
+ self.assertEqual(s.closed, False)
+ self.assertEqual(s.count, 3)
+ self.assertEqual(s.width, 791)
+ self.assertEqual(s.height, 718)
+ self.assertEqual(s.shape, (718, 791))
+ self.assertEqual(s.dtypes, [rasterio.ubyte]*3)
+ self.assertEqual(s.nodatavals, [0]*3)
+ self.assertEqual(s.indexes, [1,2,3])
+ self.assertEqual(s.crs['init'], 'epsg:32618')
+ self.assert_(s.crs_wkt.startswith('PROJCS'), s.crs_wkt)
+ for i, v in enumerate((101985.0, 2611485.0, 339315.0, 2826915.0)):
+ self.assertAlmostEqual(s.bounds[i], v)
+ self.assertEqual(
+ s.affine,
+ (300.0379266750948, 0.0, 101985.0,
+ 0.0, -300.041782729805, 2826915.0,
+ 0, 0, 1.0))
+ self.assertEqual(s.meta['crs'], s.crs)
+ self.assertEqual(
+ repr(s),
+ "<open RasterReader name='tests/data/RGB.byte.tif' "
+ "mode='r'>")
+ self.assertEqual(s.closed, True)
+ self.assertEqual(s.count, 3)
+ self.assertEqual(s.width, 791)
+ self.assertEqual(s.height, 718)
+ self.assertEqual(s.shape, (718, 791))
+ self.assertEqual(s.dtypes, [rasterio.ubyte]*3)
+ self.assertEqual(s.nodatavals, [0]*3)
+ self.assertEqual(s.crs['init'], 'epsg:32618')
+ self.assertEqual(
+ s.affine,
+ (300.0379266750948, 0.0, 101985.0,
+ 0.0, -300.041782729805, 2826915.0,
+ 0, 0, 1.0))
+ self.assertEqual(
+ repr(s),
+ "<closed RasterReader name='tests/data/RGB.byte.tif' "
+ "mode='r'>")
+
+ def test_derived_spatial(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ self.assert_(s.crs_wkt.startswith('PROJCS'), s.crs_wkt)
+ for i, v in enumerate((101985.0, 2611485.0, 339315.0, 2826915.0)):
+ self.assertAlmostEqual(s.bounds[i], v)
+ for a, b in zip(s.ul(0, 0), (101985.0, 2826915.0)):
+ self.assertAlmostEqual(a, b)
+
+ def test_read_ubyte(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ a = s.read_band(1)
+ self.assertEqual(a.dtype, rasterio.ubyte)
+
+ def test_read_ubyte_bad_index(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ self.assertRaises(IndexError, s.read_band, 0)
+
+ def test_read_ubyte_out(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ a = numpy.zeros((718, 791), dtype=rasterio.ubyte)
+ a = s.read_band(1, a)
+ self.assertEqual(a.dtype, rasterio.ubyte)
+
+ def test_read_out_dtype_fail(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ a = numpy.zeros((718, 791), dtype=rasterio.float32)
+ try:
+ s.read_band(1, a)
+ except ValueError as e:
+ assert "the array's dtype 'float32' does not match the file's dtype" in str(e)
+ except:
+ assert "failed to catch exception" is False
+
+ def test_read_out_shape_resample(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ a = numpy.zeros((7, 8), dtype=rasterio.ubyte)
+ s.read_band(1, a)
+ self.assert_(
+ repr(a) == """array([[ 0, 8, 5, 7, 0, 0, 0, 0],
+ [ 0, 6, 61, 15, 27, 15, 24, 128],
+ [ 0, 20, 152, 23, 15, 19, 28, 0],
+ [ 0, 17, 255, 25, 255, 22, 32, 0],
+ [ 9, 7, 14, 16, 19, 18, 36, 0],
+ [ 6, 27, 43, 207, 38, 31, 73, 0],
+ [ 0, 0, 0, 0, 74, 23, 0, 0]], dtype=uint8)""", a)
+
+ def test_read_basic(self):
+ with rasterio.open('tests/data/shade.tif') as s:
+ a = s.read() # Gray
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (1, 1024, 1024))
+ self.assertTrue(hasattr(a, 'mask'))
+ self.assertEqual(a.fill_value, 255)
+ self.assertEqual(list(set(s.nodatavals)), [255])
+ self.assertEqual(a.dtype, rasterio.ubyte)
+ self.assertEqual(a.sum((1, 2)).tolist(), [0])
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ a = s.read() # RGB
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (3, 718, 791))
+ self.assertTrue(hasattr(a, 'mask'))
+ self.assertEqual(a.fill_value, 0)
+ self.assertEqual(list(set(s.nodatavals)), [0])
+ self.assertEqual(a.dtype, rasterio.ubyte)
+ a = s.read(masked=False) # no mask
+ self.assertFalse(hasattr(a, 'mask'))
+ self.assertEqual(list(set(s.nodatavals)), [0])
+ self.assertEqual(a.dtype, rasterio.ubyte)
+ with rasterio.open('tests/data/float.tif') as s:
+ a = s.read() # floating point values
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (1, 2, 3))
+ self.assertFalse(hasattr(a, 'mask'))
+ self.assertEqual(list(set(s.nodatavals)), [None])
+ self.assertEqual(a.dtype, rasterio.float64)
+
+ def test_read_indexes(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ a = s.read() # RGB
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (3, 718, 791))
+ self.assertEqual(a.sum((1, 2)).tolist(),
+ [17008452, 25282412, 27325233])
+ # read last index as 2D array
+ a = s.read(s.indexes[-1]) # B
+ self.assertEqual(a.ndim, 2)
+ self.assertEqual(a.shape, (718, 791))
+ self.assertEqual(a.sum(), 27325233)
+ # read last index as 2D array
+ a = s.read(s.indexes[-1:]) # [B]
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (1, 718, 791))
+ self.assertEqual(a.sum((1, 2)).tolist(), [27325233])
+ # out of range indexes
+ self.assertRaises(IndexError, s.read, 0)
+ self.assertRaises(IndexError, s.read, [3, 4])
+ # read slice
+ a = s.read(s.indexes[0:2]) # [RG]
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (2, 718, 791))
+ self.assertEqual(a.sum((1, 2)).tolist(), [17008452, 25282412])
+ # read stride
+ a = s.read(s.indexes[::2]) # [RB]
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (2, 718, 791))
+ self.assertEqual(a.sum((1, 2)).tolist(), [17008452, 27325233])
+ # read zero-length slice
+ try:
+ a = s.read(s.indexes[1:1])
+ except ValueError:
+ pass
+
+ def test_read_window(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ # correct format
+ self.assertRaises(ValueError, s.read, window=(300, 320, 320, 330))
+ # window with 1 nodata on band 3
+ a = s.read(window=((300, 320), (320, 330)))
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (3, 20, 10))
+ self.assertTrue(hasattr(a, 'mask'))
+ self.assertEqual(a.mask.sum((1, 2)).tolist(), [0, 0, 1])
+ self.assertEqual([md5(x.tostring()).hexdigest() for x in a],
+ ['1df719040daa9dfdb3de96d6748345e8',
+ 'ec8fb3659f40c4a209027231bef12bdb',
+ '5a9c12aebc126ec6f27604babd67a4e2'])
+ # window without any missing data, but still is masked result
+ a = s.read(window=((310, 330), (320, 330)))
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (3, 20, 10))
+ self.assertTrue(hasattr(a, 'mask'))
+ self.assertEqual([md5(x.tostring()).hexdigest() for x in a[:]],
+ ['9e3000d60b4b6fb956f10dc57c4dc9b9',
+ '6a675416a32fcb70fbcf601d01aeb6ee',
+ '94fd2733b534376c273a894f36ad4e0b'])
+
+ def test_read_out(self):
+ with rasterio.open('tests/data/RGB.byte.tif') as s:
+ # regular array, without mask
+ a = numpy.empty((3, 718, 791), numpy.ubyte)
+ b = s.read(out=a)
+ self.assertEqual(id(a), id(b))
+ self.assertFalse(hasattr(a, 'mask'))
+ self.assertFalse(hasattr(b, 'mask'))
+ # with masked array
+ a = numpy.ma.empty((3, 718, 791), numpy.ubyte)
+ b = s.read(out=a)
+ self.assertEqual(id(a.data), id(b.data))
+ # TODO: is there a way to id(a.mask)?
+ self.assertTrue(hasattr(a, 'mask'))
+ self.assertTrue(hasattr(b, 'mask'))
+ # use all parameters
+ a = numpy.empty((1, 20, 10), numpy.ubyte)
+ b = s.read([2], a, ((310, 330), (320, 330)), False)
+ self.assertEqual(id(a), id(b))
+ # pass 2D array with index
+ a = numpy.empty((20, 10), numpy.ubyte)
+ b = s.read(2, a, ((310, 330), (320, 330)), False)
+ self.assertEqual(id(a), id(b))
+ self.assertEqual(a.ndim, 2)
+ # different data types
+ a = numpy.empty((3, 718, 791), numpy.float64)
+ self.assertRaises(ValueError, s.read, out=a)
+ # different number of array dimensions
+ a = numpy.empty((20, 10), numpy.ubyte)
+ self.assertRaises(ValueError, s.read, [2], out=a)
+ # different number of array shape in 3D
+ a = numpy.empty((2, 20, 10), numpy.ubyte)
+ self.assertRaises(ValueError, s.read, [2], out=a)
+
+ def test_read_nan_nodata(self):
+ with rasterio.open('tests/data/float_nan.tif') as s:
+ a = s.read()
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (1, 2, 3))
+ self.assertTrue(hasattr(a, 'mask'))
+ self.assertNotEqual(a.fill_value, numpy.nan)
+ self.assertEqual(str(list(set(s.nodatavals))), str([numpy.nan]))
+ self.assertEqual(a.dtype, rasterio.float32)
+ self.assertFalse(numpy.isnan(a).any())
+ a = s.read(masked=False)
+ self.assertFalse(hasattr(a, 'mask'))
+ self.assertTrue(numpy.isnan(a).any())
+ # Window does not contain a nodatavalue, result is still masked
+ a = s.read(window=((0, 2), (0, 2)))
+ self.assertEqual(a.ndim, 3)
+ self.assertEqual(a.shape, (1, 2, 2))
+ self.assertTrue(hasattr(a, 'mask'))
diff --git a/tests/test_revolvingdoor.py b/tests/test_revolvingdoor.py
new file mode 100644
index 0000000..40d0e95
--- /dev/null
+++ b/tests/test_revolvingdoor.py
@@ -0,0 +1,37 @@
+# Test of opening and closing and opening
+
+import logging
+import os.path
+import shutil
+import subprocess
+import sys
+import tempfile
+import unittest
+
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+log = logging.getLogger('rasterio.tests')
+
+class RevolvingDoorTest(unittest.TestCase):
+
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.tempdir)
+
+ def test_write_colormap_revolving_door(self):
+
+ with rasterio.open('tests/data/shade.tif') as src:
+ shade = src.read_band(1)
+ meta = src.meta
+
+ tiffname = os.path.join(self.tempdir, 'foo.tif')
+
+ with rasterio.open(tiffname, 'w', **meta) as dst:
+ dst.write_band(1, shade)
+
+ with rasterio.open(tiffname) as src:
+ pass
+
diff --git a/tests/test_rio_bands.py b/tests/test_rio_bands.py
new file mode 100644
index 0000000..b25b293
--- /dev/null
+++ b/tests/test_rio_bands.py
@@ -0,0 +1,79 @@
+import click
+from click.testing import CliRunner
+
+import rasterio
+from rasterio.rio import bands
+
+
+def test_photometic_choices():
+ assert len(bands.PHOTOMETRIC_CHOICES) == 8
+
+
+def test_stack(tmpdir):
+ outputname = str(tmpdir.join('stacked.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ bands.stack,
+ ['tests/data/RGB.byte.tif', '-o', outputname],
+ catch_exceptions=False)
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as out:
+ assert out.count == 3
+
+
+def test_stack_list(tmpdir):
+ outputname = str(tmpdir.join('stacked.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ bands.stack,
+ ['tests/data/RGB.byte.tif', '--bidx', '1,2,3', '-o', outputname])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as out:
+ assert out.count == 3
+
+
+def test_stack_slice(tmpdir):
+ outputname = str(tmpdir.join('stacked.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ bands.stack,
+ [
+ 'tests/data/RGB.byte.tif', '--bidx', '..2',
+ 'tests/data/RGB.byte.tif', '--bidx', '3..',
+ '-o', outputname])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as out:
+ assert out.count == 3
+
+
+def test_stack_single_slice(tmpdir):
+ outputname = str(tmpdir.join('stacked.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ bands.stack,
+ [
+ 'tests/data/RGB.byte.tif', '--bidx', '1',
+ 'tests/data/RGB.byte.tif', '--bidx', '2..',
+ '--photometric', 'rgb',
+ '-o', outputname])
+ assert result.exit_code == 0
+ with rasterio.open(outputname) as out:
+ assert out.count == 3
+
+
+def test_format_jpeg(tmpdir):
+ outputname = str(tmpdir.join('stacked.jpg'))
+ runner = CliRunner()
+ result = runner.invoke(
+ bands.stack,
+ ['tests/data/RGB.byte.tif', '-o', outputname, '--format', 'JPEG'])
+ assert result.exit_code == 0
+
+
+def test_error(tmpdir):
+ outputname = str(tmpdir.join('stacked.tif'))
+ runner = CliRunner()
+ result = runner.invoke(
+ bands.stack,
+ ['tests/data/RGB.byte.tif', '-o', outputname, '--driver', 'BOGUS'])
+ assert result.exit_code == 1
diff --git a/tests/test_rio_info.py b/tests/test_rio_info.py
new file mode 100644
index 0000000..fd56e20
--- /dev/null
+++ b/tests/test_rio_info.py
@@ -0,0 +1,57 @@
+import click
+from click.testing import CliRunner
+
+
+import rasterio
+from rasterio.rio import info
+
+
+def test_env():
+ runner = CliRunner()
+ result = runner.invoke(info.env, ['--formats'])
+ assert result.exit_code == 0
+ assert 'GTiff' in result.output
+
+
+def test_info_err():
+ runner = CliRunner()
+ result = runner.invoke(
+ info.info,
+ ['tests'])
+ assert result.exit_code == 1
+
+
+def test_info():
+ runner = CliRunner()
+ result = runner.invoke(
+ info.info,
+ ['tests/data/RGB.byte.tif'])
+ assert result.exit_code == 0
+ assert '"count": 3' in result.output
+
+
+def test_info_count():
+ runner = CliRunner()
+ result = runner.invoke(
+ info.info,
+ ['tests/data/RGB.byte.tif', '--count'])
+ assert result.exit_code == 0
+ assert result.output == '3\n'
+
+
+def test_info_nodatavals():
+ runner = CliRunner()
+ result = runner.invoke(
+ info.info,
+ ['tests/data/RGB.byte.tif', '--bounds'])
+ assert result.exit_code == 0
+ assert result.output == '101985.0 2611485.0 339315.0 2826915.0\n'
+
+
+def test_info_tags():
+ runner = CliRunner()
+ result = runner.invoke(
+ info.info,
+ ['tests/data/RGB.byte.tif', '--tags'])
+ assert result.exit_code == 0
+ assert result.output == '{"AREA_OR_POINT": "Area"}\n'
diff --git a/tests/test_rio_options.py b/tests/test_rio_options.py
new file mode 100644
index 0000000..a4b3e31
--- /dev/null
+++ b/tests/test_rio_options.py
@@ -0,0 +1,15 @@
+import click
+from click.testing import CliRunner
+
+
+import rasterio
+from rasterio.rio import rio
+
+
+def test_insp():
+ runner = CliRunner()
+ result = runner.invoke(
+ rio.cli,
+ ['--version'])
+ assert result.exit_code == 0
+ assert result.output.strip() == rasterio.__version__
diff --git a/tests/test_rio_rio.py b/tests/test_rio_rio.py
new file mode 100644
index 0000000..4780e33
--- /dev/null
+++ b/tests/test_rio_rio.py
@@ -0,0 +1,170 @@
+import click
+from click.testing import CliRunner
+
+
+import rasterio
+from rasterio.rio import rio
+
+
+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_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', '--x-json-seq', '--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', '--x-json-seq', '--x-json-seq-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_bounds_obj_feature():
+ runner = CliRunner()
+ result = runner.invoke(
+ rio.bounds,
+ ['tests/data/RGB.byte.tif', '--feature', '--precision', '6'])
+ assert result.exit_code == 0
+ assert result.output.strip() == '{"bbox": [-78.898133, 23.564991, -76.599438, 25.550874], "geometry": {"coordinates": [[[-78.898133, 23.564991], [-76.599438, 23.564991], [-76.599438, 25.550874], [-78.898133, 25.550874], [-78.898133, 23.564991]]], "type": "Polygon"}, "properties": {"id": "0", "title": "tests/data/RGB.byte.tif"}, "type": "Feature"}'
+
+
+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_tags.py b/tests/test_tags.py
new file mode 100644
index 0000000..4431055
--- /dev/null
+++ b/tests/test_tags.py
@@ -0,0 +1,56 @@
+#-*- coding: utf-8 -*-
+import logging
+import sys
+
+import pytest
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+def test_tags_read():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.tags() == {'AREA_OR_POINT': 'Area'}
+ assert src.tags(ns='IMAGE_STRUCTURE') == {'INTERLEAVE': 'PIXEL'}
+ assert src.tags(ns='bogus') == {}
+ assert 'STATISTICS_MAXIMUM' in src.tags(1)
+ with pytest.raises(ValueError):
+ tags = src.tags(4)
+
+def test_tags_update(tmpdir):
+ tiffname = str(tmpdir.join('foo.tif'))
+ with rasterio.open(
+ tiffname,
+ 'w',
+ driver='GTiff',
+ count=1,
+ dtype=rasterio.uint8,
+ width=10,
+ height=10) as dst:
+
+ dst.update_tags(a='1', b='2')
+ dst.update_tags(1, c=3)
+ with pytest.raises(ValueError):
+ dst.update_tags(4, d=4)
+
+ assert dst.tags() == {'a': '1', 'b': '2'}
+ assert dst.tags(1) == {'c': '3' }
+
+ # Assert that unicode tags work.
+ # Russian text appropriated from pytest issue #319
+ # https://bitbucket.org/hpk42/pytest/issue/319/utf-8-output-in-assertion-error-converted
+ dst.update_tags(ns='rasterio_testing', rus=u'другая строка')
+ assert dst.tags(ns='rasterio_testing') == {'rus': u'другая строка'}
+
+ with rasterio.open(tiffname) as src:
+ assert src.tags() == {'a': '1', 'b': '2'}
+ assert src.tags(1) == {'c': '3'}
+ assert src.tags(ns='rasterio_testing') == {'rus': u'другая строка'}
+
+def test_tags_update_twice():
+ with rasterio.open(
+ 'test.tif', 'w',
+ 'GTiff', 3, 4, 1, dtype=rasterio.ubyte) as dst:
+ dst.update_tags(a=1, b=2)
+ assert dst.tags() == {'a': '1', 'b': '2'}
+ dst.update_tags(c=3)
+ assert dst.tags() == {'a': '1', 'b': '2', 'c': '3'}
diff --git a/tests/test_transform.py b/tests/test_transform.py
new file mode 100644
index 0000000..b17feab
--- /dev/null
+++ b/tests/test_transform.py
@@ -0,0 +1,11 @@
+
+import rasterio
+
+def test_window():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ left, bottom, right, top = src.bounds
+ assert src.window(left, bottom, right, top) == ((0, src.height),
+ (0, src.width))
+ assert src.window(left, top-src.res[1], left+src.res[0], top) == (
+ (0, 1), (0, 1))
+
diff --git a/tests/test_update.py b/tests/test_update.py
new file mode 100644
index 0000000..8d84748
--- /dev/null
+++ b/tests/test_update.py
@@ -0,0 +1,61 @@
+
+import shutil
+import subprocess
+import re
+
+import affine
+import numpy
+import pytest
+
+import rasterio
+
+def test_update_tags(tmpdir):
+ tiffname = str(tmpdir.join('foo.tif'))
+ shutil.copy('tests/data/RGB.byte.tif', tiffname)
+ with rasterio.open(tiffname, 'r+') as f:
+ f.update_tags(a='1', b='2')
+ f.update_tags(1, c=3)
+ with pytest.raises(ValueError):
+ f.update_tags(4, d=4)
+ assert f.tags() == {'AREA_OR_POINT': 'Area', 'a': '1', 'b': '2'}
+ assert ('c', '3') in f.tags(1).items()
+ info = subprocess.check_output(["gdalinfo", tiffname]).decode('utf-8')
+ assert re.search("Metadata:\W+a=1\W+AREA_OR_POINT=Area\W+b=2", info)
+
+def test_update_band(tmpdir):
+ tiffname = str(tmpdir.join('foo.tif'))
+ shutil.copy('tests/data/RGB.byte.tif', tiffname)
+ with rasterio.open(tiffname, 'r+') as f:
+ f.write_band(1, numpy.zeros(f.shape, dtype=f.dtypes[0]))
+ with rasterio.open(tiffname) as f:
+ assert not f.read_band(1).any()
+
+def test_update_spatial(tmpdir):
+ tiffname = str(tmpdir.join('foo.tif'))
+ shutil.copy('tests/data/RGB.byte.tif', tiffname)
+ with rasterio.open(tiffname, 'r+') as f:
+ f.transform = affine.Affine.from_gdal(1.0, 1.0, 0.0, 0.0, 0.0, -1.0)
+ f.crs = {'init': 'epsg:4326'}
+ with rasterio.open(tiffname) as f:
+ assert list(f.transform) == [1.0, 1.0, 0.0, 0.0, 0.0, -1.0]
+ assert list(f.affine.to_gdal()) == [1.0, 1.0, 0.0, 0.0, 0.0, -1.0]
+ assert f.crs == {'init': 'epsg:4326'}
+
+def test_update_spatial_epsg(tmpdir):
+ tiffname = str(tmpdir.join('foo.tif'))
+ shutil.copy('tests/data/RGB.byte.tif', tiffname)
+ with rasterio.open(tiffname, 'r+') as f:
+ f.transform = affine.Affine.from_gdal(1.0, 1.0, 0.0, 0.0, 0.0, -1.0)
+ f.crs = 'EPSG:4326'
+ with rasterio.open(tiffname) as f:
+ assert list(f.transform) == [1.0, 1.0, 0.0, 0.0, 0.0, -1.0]
+ assert list(f.affine.to_gdal()) == [1.0, 1.0, 0.0, 0.0, 0.0, -1.0]
+ assert f.crs == {'init': 'epsg:4326'}
+
+def test_update_nodatavals(tmpdir):
+ tiffname = str(tmpdir.join('foo.tif'))
+ shutil.copy('tests/data/RGB.byte.tif', tiffname)
+ with rasterio.open(tiffname, 'r+') as f:
+ f.nodatavals = [-1, -1, -1]
+ with rasterio.open(tiffname) as f:
+ assert f.nodatavals == [-1, -1, -1]
diff --git a/tests/test_warp.py b/tests/test_warp.py
new file mode 100644
index 0000000..d9fef2b
--- /dev/null
+++ b/tests/test_warp.py
@@ -0,0 +1,184 @@
+
+import logging
+import subprocess
+import sys
+
+import affine
+import numpy
+
+import rasterio
+from rasterio.warp import reproject, RESAMPLING, transform_geom
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+def test_reproject():
+ """Ndarry to ndarray"""
+ with rasterio.drivers():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ source = src.read_band(1)
+ dst_transform = affine.Affine.from_gdal(-8789636.708, 300.0, 0.0, 2943560.235, 0.0, -300.0)
+ dst_crs = dict(
+ proj='merc',
+ a=6378137,
+ b=6378137,
+ lat_ts=0.0,
+ lon_0=0.0,
+ x_0=0.0,
+ y_0=0,
+ k=1.0,
+ units='m',
+ nadgrids='@null',
+ wktext=True,
+ no_defs=True)
+ destin = numpy.empty(src.shape, dtype=numpy.uint8)
+ reproject(
+ source,
+ destin,
+ src_transform=src.transform,
+ src_crs=src.crs,
+ dst_transform=dst_transform,
+ dst_crs=dst_crs,
+ resampling=RESAMPLING.nearest )
+ assert destin.any()
+ try:
+ import matplotlib.pyplot as plt
+ plt.imshow(destin)
+ plt.gray()
+ plt.savefig('test_reproject.png')
+ except:
+ pass
+
+def test_reproject_multi():
+ """Ndarry to ndarray"""
+ with rasterio.drivers():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ source = src.read()
+ dst_transform = affine.Affine.from_gdal(
+ -8789636.708, 300.0, 0.0, 2943560.235, 0.0, -300.0)
+ dst_crs = dict(
+ proj='merc',
+ a=6378137,
+ b=6378137,
+ lat_ts=0.0,
+ lon_0=0.0,
+ x_0=0.0,
+ y_0=0,
+ k=1.0,
+ units='m',
+ nadgrids='@null',
+ wktext=True,
+ no_defs=True)
+ destin = numpy.empty(source.shape, dtype=numpy.uint8)
+ reproject(
+ source,
+ destin,
+ src_transform=src.transform,
+ src_crs=src.crs,
+ dst_transform=dst_transform,
+ dst_crs=dst_crs,
+ resampling=RESAMPLING.nearest )
+ assert destin.any()
+ try:
+ import matplotlib.pyplot as plt
+ plt.imshow(destin)
+ plt.gray()
+ plt.savefig('test_reproject.png')
+ except:
+ pass
+
+def test_warp_from_file():
+ """File to ndarray"""
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ dst_transform = affine.Affine.from_gdal(-8789636.708, 300.0, 0.0, 2943560.235, 0.0, -300.0)
+ dst_crs = dict(
+ proj='merc',
+ a=6378137,
+ b=6378137,
+ lat_ts=0.0,
+ lon_0=0.0,
+ x_0=0.0,
+ y_0=0,
+ k=1.0,
+ units='m',
+ nadgrids='@null',
+ wktext=True,
+ no_defs=True)
+ destin = numpy.empty(src.shape, dtype=numpy.uint8)
+ reproject(
+ rasterio.band(src, 1),
+ destin,
+ dst_transform=dst_transform,
+ dst_crs=dst_crs)
+ assert destin.any()
+ try:
+ import matplotlib.pyplot as plt
+ plt.imshow(destin)
+ plt.gray()
+ plt.savefig('test_warp_from_filereproject.png')
+ except:
+ pass
+
+def test_warp_from_to_file(tmpdir):
+ """File to file"""
+ tiffname = str(tmpdir.join('foo.tif'))
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ dst_transform = affine.Affine.from_gdal(-8789636.708, 300.0, 0.0, 2943560.235, 0.0, -300.0)
+ dst_crs = dict(
+ proj='merc',
+ a=6378137,
+ b=6378137,
+ lat_ts=0.0,
+ lon_0=0.0,
+ x_0=0.0,
+ y_0=0,
+ k=1.0,
+ units='m',
+ nadgrids='@null',
+ wktext=True,
+ no_defs=True)
+ kwargs = src.meta.copy()
+ kwargs.update(
+ transform=dst_transform,
+ crs=dst_crs)
+ with rasterio.open(tiffname, 'w', **kwargs) as dst:
+ for i in (1, 2, 3):
+ reproject(rasterio.band(src, i), rasterio.band(dst, i))
+ # subprocess.call(['open', tiffname])
+
+def test_warp_from_to_file_multi(tmpdir):
+ """File to file"""
+ tiffname = str(tmpdir.join('foo.tif'))
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ dst_transform = affine.Affine.from_gdal(-8789636.708, 300.0, 0.0, 2943560.235, 0.0, -300.0)
+ dst_crs = dict(
+ proj='merc',
+ a=6378137,
+ b=6378137,
+ lat_ts=0.0,
+ lon_0=0.0,
+ x_0=0.0,
+ y_0=0,
+ k=1.0,
+ units='m',
+ nadgrids='@null',
+ wktext=True,
+ no_defs=True)
+ kwargs = src.meta.copy()
+ kwargs.update(
+ transform=dst_transform,
+ crs=dst_crs)
+ with rasterio.open(tiffname, 'w', **kwargs) as dst:
+ for i in (1, 2, 3):
+ reproject(
+ rasterio.band(src, i),
+ rasterio.band(dst, i),
+ num_threads=2)
+ # subprocess.call(['open', tiffname])
+
+def test_transform_geom_wrap():
+ geom = {'type': 'Polygon', 'coordinates': (((798842.3090855901, 6569056.500655151), (756688.2826828464, 6412397.888771972), (755571.0617232556, 6408461.009397383), (677605.2284582685, 6425600.39266733), (677605.2284582683, 6425600.392667332), (670873.3791649605, 6427248.603432341), (664882.1106069803, 6407585.48425362), (663675.8662823177, 6403676.990080649), (485120.71963574126, 6449787.167760638), (485065.55660851026, 6449802.826920689), (485957.03982722526, 6452708.625101285), (48 [...]
+ result = transform_geom(
+ 'EPSG:3373', 'EPSG:4326', geom, antimeridian_cutting=True)
+ assert result['type'] == 'MultiPolygon'
+ assert len(result['coordinates']) == 2
diff --git a/tests/test_write.py b/tests/test_write.py
new file mode 100644
index 0000000..121e478
--- /dev/null
+++ b/tests/test_write.py
@@ -0,0 +1,244 @@
+import logging
+import subprocess
+import sys
+import re
+import numpy
+import rasterio
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_validate_dtype_None(tmpdir):
+ name = str(tmpdir.join("lol.tif"))
+ try:
+ ds = rasterio.open(
+ name, 'w', driver='GTiff', width=100, height=100, count=1,
+ # dtype=None
+ )
+ except TypeError:
+ pass
+
+def test_validate_dtype_str(tmpdir):
+ name = str(tmpdir.join("lol.tif"))
+ try:
+ ds = rasterio.open(
+ name, 'w', driver='GTiff', width=100, height=100, count=1,
+ dtype='Int16')
+ except TypeError:
+ pass
+
+def test_validate_count_None(tmpdir):
+ name = str(tmpdir.join("lol.tif"))
+ try:
+ ds = rasterio.open(
+ name, 'w', driver='GTiff', width=100, height=100, #count=None
+ dtype=rasterio.uint8)
+ except TypeError:
+ pass
+
+def test_no_crs(tmpdir):
+ # A dataset without crs is okay.
+ name = str(tmpdir.join("lol.tif"))
+ with rasterio.open(
+ name, 'w', driver='GTiff', width=100, height=100, count=1,
+ dtype=rasterio.uint8) as dst:
+ dst.write_band(1, numpy.ones((100, 100), dtype=rasterio.uint8))
+
+def test_context(tmpdir):
+ name = str(tmpdir.join("test_context.tif"))
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=1,
+ dtype=rasterio.ubyte) as s:
+ assert s.name == name
+ assert s.driver == 'GTiff'
+ assert s.closed == False
+ assert s.count == 1
+ assert s.width == 100
+ assert s.height == 100
+ assert s.shape == (100, 100)
+ assert s.indexes == [1]
+ assert repr(s) == "<open RasterUpdater name='%s' mode='w'>" % name
+ assert s.closed == True
+ assert s.count == 1
+ assert s.width == 100
+ assert s.height == 100
+ assert s.shape == (100, 100)
+ assert repr(s) == "<closed RasterUpdater name='%s' mode='w'>" % name
+ info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
+ assert "GTiff" in info
+ assert "Size is 100, 100" in info
+ assert "Band 1 Block=100x81 Type=Byte, ColorInterp=Gray" in info
+
+def test_write_ubyte(tmpdir):
+ name = str(tmpdir.mkdir("sub").join("test_write_ubyte.tif"))
+ a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=1,
+ dtype=a.dtype) as s:
+ s.write_band(1, a)
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
+def test_write_ubyte_multi(tmpdir):
+ name = str(tmpdir.mkdir("sub").join("test_write_ubyte_multi.tif"))
+ a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=1,
+ dtype=a.dtype) as s:
+ s.write(a, 1)
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
+def test_write_ubyte_multi_list(tmpdir):
+ name = str(tmpdir.mkdir("sub").join("test_write_ubyte_multi_list.tif"))
+ a = numpy.array([numpy.ones((100, 100), dtype=rasterio.ubyte) * 127])
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=1,
+ dtype=a.dtype) as s:
+ s.write(a, [1])
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
+def test_write_ubyte_multi_3(tmpdir):
+ name = str(tmpdir.mkdir("sub").join("test_write_ubyte_multi_list.tif"))
+ arr = numpy.array(3*[numpy.ones((100, 100), dtype=rasterio.ubyte) * 127])
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=3,
+ dtype=arr.dtype) as s:
+ s.write(arr)
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
+
+def test_write_float(tmpdir):
+ name = str(tmpdir.join("test_write_float.tif"))
+ a = numpy.ones((100, 100), dtype=rasterio.float32) * 42.0
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=2,
+ dtype=rasterio.float32) as s:
+ assert s.dtypes == [rasterio.float32]*2
+ s.write_band(1, a)
+ s.write_band(2, a)
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "Minimum=42.000, Maximum=42.000, Mean=42.000, StdDev=0.000" in info
+
+def test_write_crs_transform(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},
+ transform=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"))
+ 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='EPSG:32618',
+ transform=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["WGS 84 / UTM zone 18N",' 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_3(tmpdir):
+ """Using WKT as CRS."""
+ 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]
+ crs_wkt = 'PROJCS["UTM Zone 18, Northern Hemisphere",GEOGCS["WGS 84",DATUM["unknown",SPHEROID["WGS84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-75],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["Meter",1]]'
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=1,
+ crs=crs_wkt,
+ transform=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_meta(tmpdir):
+ name = str(tmpdir.join("test_write_meta.tif"))
+ a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
+ meta = dict(driver='GTiff', width=100, height=100, count=1)
+ with rasterio.open(name, 'w', dtype=a.dtype, **meta) as s:
+ s.write_band(1, a)
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
+
+def test_write_nodata(tmpdir):
+ name = str(tmpdir.join("test_write_nodata.tif"))
+ a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff', width=100, height=100, count=2,
+ dtype=a.dtype, nodata=0) as s:
+ s.write_band(1, a)
+ s.write_band(2, a)
+ info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
+ assert "NoData Value=0" in info
+
+def test_write_lzw(tmpdir):
+ name = str(tmpdir.join("test_write_lzw.tif"))
+ a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
+ with rasterio.open(
+ name, 'w',
+ driver='GTiff',
+ width=100, height=100, count=1,
+ dtype=a.dtype,
+ compress='LZW') as s:
+ assert ('compress', 'LZW') in s.kwds.items()
+ s.write_band(1, a)
+ info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
+ assert "LZW" in info
+
+def test_write_noncontiguous(tmpdir):
+ name = str(tmpdir.join("test_write_nodata.tif"))
+ ROWS = 4
+ COLS = 10
+ BANDS = 6
+ with rasterio.drivers():
+ # Create a 3-D random int array (rows, columns, bands)
+ total = ROWS * COLS * BANDS
+ arr = numpy.random.randint(
+ 0, 10, size=total).reshape(
+ (ROWS, COLS, BANDS), order='F').astype(numpy.int32)
+ kwargs = {
+ 'driver': 'GTiff',
+ 'width': COLS,
+ 'height': ROWS,
+ 'count': BANDS,
+ 'dtype': rasterio.int32
+ }
+ with rasterio.open(name, 'w', **kwargs) as dst:
+ for i in range(BANDS):
+ dst.write_band(i+1, arr[:,:,i])
+
--
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