[rasterio] 02/07: Imported Upstream version 0.18
Johan Van de Wauw
johanvdw-guest at moszumanska.debian.org
Tue Feb 10 21:19:16 UTC 2015
This is an automated email from the git hooks/post-receive script.
johanvdw-guest pushed a commit to branch master
in repository rasterio.
commit 498df05d2b5509e9d77d3ba48f99a19ff66843e4
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date: Tue Feb 10 21:37:23 2015 +0100
Imported Upstream version 0.18
---
CHANGES.txt | 13 +
build-wheels.sh | 18 -
docs/cli.rst | 191 +++++++--
docs/reproject.rst | 50 ++-
rasterio/__init__.py | 2 +-
rasterio/_base.pxd | 1 +
rasterio/_base.pyx | 119 +++++-
rasterio/_drivers.pyx | 4 +-
rasterio/_err.pyx | 2 +-
rasterio/_example.pyx | 2 +
rasterio/_features.pyx | 64 ++-
rasterio/_fill.pyx | 84 ++++
rasterio/_gdal.pxd | 4 +-
rasterio/_io.pyx | 48 ++-
rasterio/_warp.pyx | 109 +-----
rasterio/features.py | 23 +-
rasterio/fill.py | 47 +++
rasterio/rasterfill.cpp | 886 ++++++++++++++++++++++++++++++++++++++++++
rasterio/rio/features.py | 236 ++++++++++-
rasterio/rio/info.py | 42 +-
rasterio/rio/main.py | 3 +-
rasterio/rio/sample.py | 94 +++++
rasterio/warp.py | 3 +-
requirements-dev.txt | 2 +-
setup.py | 22 +-
tests/conftest.py | 8 +
tests/test_features_bounds.py | 65 ++++
tests/test_fillnodata.py | 45 +++
tests/test_rio_features.py | 282 +++++++++++++-
tests/test_rio_info.py | 42 ++
tests/test_rio_sample.py | 81 ++++
tests/test_sampling.py | 17 +
tests/test_tags.py | 8 +
tests/test_transform.py | 14 +-
34 files changed, 2398 insertions(+), 233 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index fc51402..9dce1f6 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,19 @@
Changes
=======
+0.18.0 (2015-02-10)
+-------------------
+- New rio-rasterize command (#187).
+- New window_transform method (#215).
+- New sample method and rio-sample command (#251, #275).
+- New fillnodata function based on GDAL's rasterfill.cpp (#253).
+- Speedups for _features and _warp modules (#259).
+- Enhancements for rio-info: 'res', 'lnglat', and 'stats' (#269, #270).
+
+0.17.1 (2015-01-20)
+-------------------
+- Properly handle metadata tags with values that contain "=" (#254).
+
0.17.0 (2015-01-15)
-------------------
- Enhancements to rio-merge: relaxation of same-extent and same-resolution
diff --git a/build-wheels.sh b/build-wheels.sh
deleted file mode 100644
index 36e68dc..0000000
--- a/build-wheels.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-# Automation of this is a TODO. For now, it depends on manually built libraries
-# as detailed in https://gist.github.com/sgillies/a8a2fb910a98a8566d0a.
-
-export MACOSX_DEPLOYMENT_TARGET=10.6
-export GDAL_CONFIG="/usr/local/bin/gdal-config"
-export PACKAGE_DATA=1
-
-VERSION=$1
-
-source $HOME/envs/riowhl27/bin/activate
-CFLAGS="`$GDAL_CONFIG --cflags`" LDFLAGS="`$GDAL_CONFIG --libs` `$GDAL_CONFIG --dep-libs`" python setup.py bdist_wheel -d wheels/$VERSION
-source $HOME/envs/riowhl34/bin/activate
-CFLAGS="`$GDAL_CONFIG --cflags`" LDFLAGS="`$GDAL_CONFIG --libs` `$GDAL_CONFIG --dep-libs`" python setup.py bdist_wheel -d wheels/$VERSION
-
-parallel delocate-wheel -w fixed_wheels/$VERSION --require-archs=intel -v {} ::: wheels/$VERSION/*.whl
-parallel cp {} {.}.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl ::: fixed_wheels/$VERSION/*.whl
diff --git a/docs/cli.rst b/docs/cli.rst
index a744c4a..4e6adf2 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -13,19 +13,21 @@ Rasterio's new command line interface is a program named "rio".
Options:
-v, --verbose Increase verbosity.
-q, --quiet Decrease verbosity.
+ --version Show the version and exit.
--help Show this message and exit.
Commands:
bounds Write bounding boxes to stdout as GeoJSON.
+ env Print information about the rio environment.
info Print information about a data file.
insp Open a data file and start an interpreter.
merge Merge a stack of raster datasets.
+ rasterize Rasterize features.
+ sample Sample a dataset.
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.
+It is developed using `Click <http://click.pocoo.org/3/>`__.
bounds
------
@@ -84,26 +86,89 @@ Shoot the GeoJSON into a Leaflet map using geojsonio-cli by typing
info
----
-Rio's info command intends to serve some of the same uses as gdalinfo.
+Rio's info command prints structured information about a dataset.
.. 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}
+ $ rio info tests/data/RGB.byte.tif --indent 2
+ {
+ "count": 3,
+ "crs": "EPSG:32618",
+ "dtype": "uint8",
+ "driver": "GTiff",
+ "bounds": [
+ 101985.0,
+ 2611485.0,
+ 339315.0,
+ 2826915.0
+ ],
+ "lnglat": [
+ -77.75790625255473,
+ 24.561583285327067
+ ],
+ "height": 718,
+ "width": 791,
+ "shape": [
+ 718,
+ 791
+ ],
+ "res": [
+ 300.0379266750948,
+ 300.041782729805
+ ],
+ "nodata": 0.0
+ }
+
+More information, such as band statistics, can be had using the `--verbose`
+option.
+
+.. code-block:: console
+
+ $ rio info tests/data/RGB.byte.tif --indent 2
+ {
+ "count": 3,
+ "crs": "EPSG:32618",
+ "stats": [
+ {
+ "max": 255.0,
+ "mean": 44.434478650699106,
+ "min": 1.0
+ },
+ {
+ "max": 255.0,
+ "mean": 66.02203484105824,
+ "min": 1.0
+ },
+ {
+ "max": 255.0,
+ "mean": 71.39316199120559,
+ "min": 1.0
+ }
+ ],
+ "dtype": "uint8",
+ "driver": "GTiff",
+ "bounds": [
+ 101985.0,
+ 2611485.0,
+ 339315.0,
+ 2826915.0
+ ],
+ "lnglat": [
+ -77.75790625255473,
+ 24.561583285327067
+ ],
+ "height": 718,
+ "width": 791,
+ "shape": [
+ 718,
+ 791
+ ],
+ "res": [
+ 300.0379266750948,
+ 300.041782729805
+ ],
+ "nodata": 0.0
+ }
insp
----
@@ -113,25 +178,12 @@ 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)
+ Rasterio 0.18 Interactive Inspector (Python 2.7.9)
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}
+ >>> print src.name
+ tests/data/RGB.byte.tif
+ >>> print src.bounds
+ BoundingBox(left=101985.0, bottom=2611485.0, right=339315.0, top=2826915.0)
merge
-----
@@ -143,6 +195,69 @@ datasets.
$ rio merge rasterio/tests/data/R*.tif merged.tif
+rasterize
+---------
+
+New in 0.18.
+
+The rasterize command rasterizes GeoJSON features into a new or existing
+raster.
+
+.. code-block:: console
+
+ $ rio rasterize test.tif --res 0.0167 < input.geojson
+
+The resulting file will have an upper left coordinate determined by the bounds
+of the GeoJSON (in EPSG:4326, which is the default), with a
+pixel size of approximately 30 arc seconds. Pixels whose center is within the
+polygon or that are selected by brezenhams line algorithm will be burned in
+with a default value of 1.
+
+It is possible to rasterize into an existing raster and use an alternative
+default value:
+
+.. code-block:: console
+
+ $ rio rasterize existing.tif --default_value 10 < input.geojson
+
+It is also possible to rasterize using a template raster, which will be used
+to determine the transform, dimensions, and coordinate reference system of the
+output raster:
+
+.. code-block:: console
+
+ $ rio rasterize test.tif --like tests/data/shade.tif < input.geojson
+
+GeoJSON features may be provided using stdin or specified directly as first
+argument, and dimensions may be provided in place of pixel resolution:
+
+.. code-block:: console
+
+ $ rio rasterize input.geojson test.tif --dimensions 1024 1024
+
+Other options are available, see:
+
+.. code-block:: console
+
+ $ rio rasterize --help
+
+sample
+------
+
+New in 0.18.
+
+The sample command reads ``x, y`` positions from stdin and writes the dataset
+values at that position to stdout.
+
+.. code-block:: console
+
+ $ cat << EOF | rio sample tests/data/RGB.byte.tif
+ > [220649.99999832606, 2719199.999999095]
+ > EOF
+ [18, 25, 14]
+
+The output of the transform command (see below) makes good input for sample.
+
shapes
------
diff --git a/docs/reproject.rst b/docs/reproject.rst
index ad290b4..0b467f4 100644
--- a/docs/reproject.rst
+++ b/docs/reproject.rst
@@ -53,9 +53,57 @@ transform.
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
+correct: https://a.tiles.mapbox.com/v3/sgillies.hfek2oko/page.html?secure=1#6/0.000/0.033.
+
+Reprojecting a GeoTIFF dataset
+------------------------------
+
+Here's a more real-world example of using ``reproject()`` to make an output dataset zoomed out by a factor of 2.
+Methods of the ``rasterio.Affine`` class help us generate the output dataset's transform matrix and, thereby, its
+spatial extent.
+
+.. code-block:: python
+
+ import numpy
+ import rasterio
+ from rasterio import Affine as A
+ from rasterio.warp import reproject, RESAMPLING
+
+ with rasterio.open('rasterio/tests/data/RGB.byte.tif') as src:
+ src_transform = src.affine
+
+ # Zoom out by a factor of 2 from the center of the source
+ # dataset. The destination transform is the product of the
+ # source transform, a translation down and to the right, and
+ # a scaling.
+ dst_transform = src_transform*A.translation(
+ -src.width/2.0, -src.height/2.0)*A.scale(2.0)
+
+ data = src.read()
+
+ kwargs = src.meta
+ kwargs['transform'] = dst_transform
+
+ with rasterio.open('/tmp/zoomed-out.tif', 'w', **kwargs) as dst:
+
+ for i, band in enumerate(data, 1):
+ dest = numpy.zeros_like(band)
+
+ reproject(
+ band,
+ dest,
+ src_transform=src_transform,
+ src_crs=src.crs,
+ dst_transform=dst_transform,
+ dst_crs=src.crs,
+ resampling=RESAMPLING.nearest)
+
+ dst.write_band(i, dest)
+
+.. image:: https://farm8.staticflickr.com/7399/16390100651_54f01b8601_b_d.jpg)
References
----------
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
index 531dae2..0ea3dd2 100644
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -18,7 +18,7 @@ from rasterio.transform import Affine, guard_transform
__all__ = [
'band', 'open', 'drivers', 'copy', 'pad']
-__version__ = "0.17"
+__version__ = "0.18"
log = logging.getLogger('rasterio')
class NullHandler(logging.Handler):
diff --git a/rasterio/_base.pxd b/rasterio/_base.pxd
index ad5440b..ca070b8 100644
--- a/rasterio/_base.pxd
+++ b/rasterio/_base.pxd
@@ -23,3 +23,4 @@ cdef class DatasetReader:
cdef void *band(self, int bidx)
+cdef void *_osr_from_crs(object crs)
diff --git a/rasterio/_base.pyx b/rasterio/_base.pyx
index 660d112..96c7540 100644
--- a/rasterio/_base.pyx
+++ b/rasterio/_base.pyx
@@ -99,13 +99,13 @@ cdef class DatasetReader(object):
def read_crs(self):
cdef char *proj_c = NULL
- cdef char *auth_key = NULL
- cdef char *auth_val = NULL
+ cdef const char * auth_key = NULL
+ cdef const char * auth_val = NULL
cdef void *osr = NULL
if self._hds == NULL:
raise ValueError("Null dataset")
crs = {}
- cdef char *wkt = _gdal.GDALGetProjectionRef(self._hds)
+ cdef const char * wkt = _gdal.GDALGetProjectionRef(self._hds)
if wkt is NULL:
raise ValueError("Unexpected NULL spatial reference")
wkt_b = wkt
@@ -166,7 +166,7 @@ cdef class DatasetReader(object):
cdef char *proj_c = NULL
cdef char *key_c = NULL
cdef void *osr = NULL
- cdef char *wkt = NULL
+ cdef const char * wkt = NULL
if self._hds == NULL:
raise ValueError("Null dataset")
wkt = _gdal.GDALGetProjectionRef(self._hds)
@@ -379,6 +379,11 @@ cdef class DatasetReader(object):
lr = self.index(right, bottom)
return tuple(zip(ul, lr))
+ def window_transform(self, window):
+ """Returns the affine transform for a dataset window."""
+ (r, _), (c, _) = window
+ return self.affine * Affine.translation(c or 0, r or 0)
+
@property
def meta(self):
m = {
@@ -394,7 +399,14 @@ cdef class DatasetReader(object):
self._read = True
return m
-
+ def lnglat(self):
+ w, s, e, n = self.bounds
+ cx = (w + e)/2.0
+ cy = (s + n)/2.0
+ lng, lat = _transform(
+ self.crs, {'init': 'epsg:4326'}, [cx], [cy], None)
+ return lng.pop(), lat.pop()
+
def get_crs(self):
# _read tells us that the CRS was read before and really is
# None.
@@ -498,7 +510,7 @@ cdef class DatasetReader(object):
item_c = papszStrList[i]
item_b = item_c
item = item_b.decode('utf-8')
- key, value = item.split('=')
+ key, value = item.split('=', 1)
retval[key] = value
return retval
@@ -521,7 +533,7 @@ cdef class DatasetReader(object):
cdef void *hBand
cdef void *hTable
cdef int i
- cdef _gdal.GDALColorEntry *color
+ cdef const _gdal.GDALColorEntry * color
if self._hds == NULL:
raise ValueError("can't read closed raster file")
if bidx not in self.indexes:
@@ -590,6 +602,7 @@ cpdef eval_window(object window, int height, int width):
"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.
@@ -599,8 +612,100 @@ def window_shape(window, height=-1, width=-1):
(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
+
+
+cdef void *_osr_from_crs(object crs):
+ cdef char *proj_c = NULL
+ cdef void *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, zs):
+ cdef double *x = NULL
+ cdef double *y = NULL
+ cdef double *z = NULL
+ cdef char *proj_c = NULL
+ cdef void *src = NULL
+ cdef void *dst = NULL
+ cdef void *transform = NULL
+ cdef int i
+
+ assert len(xs) == len(ys)
+ assert zs is None or len(xs) == len(zs)
+
+ 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]
+
+ if zs is not None:
+ z = <double *>_gdal.CPLMalloc(n*sizeof(double))
+ for i in range(n):
+ z[i] = zs[i]
+
+ transform = _gdal.OCTNewCoordinateTransformation(src, dst)
+ res = _gdal.OCTTransform(transform, n, x, y, z)
+ #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]
+
+ if zs is not None:
+ res_zs = [0]*n
+ for i in range(n):
+ res_zs[i] = z[i]
+ _gdal.CPLFree(z)
+
+ retval = (res_xs, res_ys, res_zs)
+ else:
+ retval = (res_xs, res_ys)
+
+ _gdal.CPLFree(x)
+ _gdal.CPLFree(y)
+ _gdal.OCTDestroyCoordinateTransformation(transform)
+ _gdal.OSRDestroySpatialReference(src)
+ _gdal.OSRDestroySpatialReference(dst)
+ return retval
diff --git a/rasterio/_drivers.pyx b/rasterio/_drivers.pyx
index b8080da..40b8271 100644
--- a/rasterio/_drivers.pyx
+++ b/rasterio/_drivers.pyx
@@ -129,8 +129,8 @@ cdef class GDALEnv(object):
def drivers(self):
cdef void *drv = NULL
- cdef char *key = NULL
- cdef char *val = NULL
+ cdef const char *key = NULL
+ cdef const char *val = NULL
cdef int i
result = {}
for i in range(GDALGetDriverCount()):
diff --git a/rasterio/_err.pyx b/rasterio/_err.pyx
index 44bb201..b59ce44 100644
--- a/rasterio/_err.pyx
+++ b/rasterio/_err.pyx
@@ -61,7 +61,7 @@ cdef class GDALErrCtxManager:
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()
+ cdef const char *msg = CPLGetLastErrorMsg()
# TODO: warn for err_type 2?
if err_type >= 3:
raise exception_map[err_no](msg)
diff --git a/rasterio/_example.pyx b/rasterio/_example.pyx
index c203847..e1b4396 100644
--- a/rasterio/_example.pyx
+++ b/rasterio/_example.pyx
@@ -1,3 +1,5 @@
+# cython: boundscheck=False
+
import numpy
cimport numpy
diff --git a/rasterio/_features.pyx b/rasterio/_features.pyx
index 3858af4..c7c63c6 100644
--- a/rasterio/_features.pyx
+++ b/rasterio/_features.pyx
@@ -1,4 +1,3 @@
-# cython: profile=True
import logging
import json
@@ -50,12 +49,12 @@ def _shapes(image, mask, connectivity, transform):
"""
cdef int retval, rows, cols
- cdef void *hband
- cdef void *hmaskband
- cdef void *hfdriver
- cdef void *hfs
- cdef void *hlayer
- cdef void *fielddefn
+ cdef void *hband = NULL
+ cdef void *hmaskband = NULL
+ cdef void *hfdriver = NULL
+ cdef void *hfs = NULL
+ cdef void *hlayer = NULL
+ cdef void *fielddefn = NULL
cdef _io.RasterReader rdr
cdef _io.RasterReader mrdr
cdef char **options = NULL
@@ -162,9 +161,9 @@ def _sieve(image, size, output, mask, connectivity):
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 void *in_band = NULL
+ cdef void *out_band = NULL
+ cdef void *mask_band = NULL
cdef _io.RasterReader rdr
cdef _io.RasterUpdater udr
cdef _io.RasterReader mask_reader
@@ -291,6 +290,45 @@ def _rasterize(shapes, image, transform, all_touched):
_gdal.CSLDestroy(options)
+def _explode(coords):
+ """Explode a GeoJSON geometry's coordinates object and yield
+ coordinate tuples. As long as the input is conforming, the type of
+ the geometry doesn't matter. From Fiona 1.4.8"""
+ for e in coords:
+ if isinstance(e, (float, int)):
+ yield coords
+ break
+ else:
+ for f in _explode(e):
+ yield f
+
+
+def _bounds(geometry):
+ """Bounding box of a GeoJSON geometry.
+ From Fiona 1.4.8 with updates here to handle feature collections.
+ TODO: add to Fiona.
+ """
+
+ try:
+ if 'features' in geometry:
+ xmins = []
+ ymins = []
+ xmaxs = []
+ ymaxs = []
+ for feature in geometry['features']:
+ xmin, ymin, xmax, ymax = _bounds(feature['geometry'])
+ xmins.append(xmin)
+ ymins.append(ymin)
+ xmaxs.append(xmax)
+ ymaxs.append(ymax)
+ return min(xmins), min(ymins), max(xmaxs), max(ymaxs)
+ else:
+ xyz = tuple(zip(*list(_explode(geometry['coordinates']))))
+ return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])
+ except (KeyError, TypeError):
+ return None
+
+
# Mapping of OGR integer geometry types to GeoJSON type names.
GEOMETRY_TYPES = {
0: 'Unknown',
@@ -416,12 +454,6 @@ cdef class GeomBuilder:
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.
diff --git a/rasterio/_fill.pyx b/rasterio/_fill.pyx
new file mode 100644
index 0000000..8d7c998
--- /dev/null
+++ b/rasterio/_fill.pyx
@@ -0,0 +1,84 @@
+# distutils: language = c++
+# cython: profile=True
+#
+
+import numpy as np
+cimport numpy as np
+
+from rasterio import dtypes
+from rasterio._err import cpl_errs
+from rasterio cimport _gdal, _io
+
+from rasterio._io cimport InMemoryRaster
+
+
+def _fillnodata(image, mask, double max_search_distance=100.0,
+ int smoothing_iterations=0):
+ cdef void *memdriver = _gdal.GDALGetDriverByName("MEM")
+ cdef void *image_dataset
+ cdef void *image_band
+ cdef void *mask_dataset
+ cdef void *mask_band
+ cdef char **alg_options = NULL
+
+ if isinstance(image, np.ndarray):
+ # copy numpy ndarray into an in-memory dataset
+ image_dataset = _gdal.GDALCreate(
+ memdriver,
+ "image",
+ image.shape[1],
+ image.shape[0],
+ 1,
+ <_gdal.GDALDataType>dtypes.dtype_rev[image.dtype.name],
+ NULL)
+ image_band = _gdal.GDALGetRasterBand(image_dataset, 1)
+ _io.io_auto(image, image_band, True)
+ elif isinstance(image, tuple):
+ # TODO
+ raise NotImplementedError()
+ else:
+ raise ValueError("Invalid source image")
+
+ if isinstance(mask, np.ndarray):
+ mask_cast = mask.astype('uint8')
+ mask_dataset = _gdal.GDALCreate(
+ memdriver,
+ "mask",
+ mask.shape[1],
+ mask.shape[0],
+ 1,
+ <_gdal.GDALDataType>dtypes.dtype_rev['uint8'],
+ NULL)
+ mask_band = _gdal.GDALGetRasterBand(mask_dataset, 1)
+ _io.io_auto(mask_cast, mask_band, True)
+ elif isinstance(mask, tuple):
+ # TODO
+ raise NotImplementedError()
+ elif mask is None:
+ mask_band = NULL
+ else:
+ raise ValueError("Invalid source image mask")
+
+ with cpl_errs:
+ alg_options = _gdal.CSLSetNameValue(
+ alg_options, "TEMP_FILE_DRIVER", "MEM")
+
+ _gdal.GDALFillNodata(
+ image_band,
+ mask_band,
+ max_search_distance,
+ 0,
+ smoothing_iterations,
+ alg_options,
+ NULL,
+ NULL)
+
+ # read the result into a numpy ndarray
+ result = np.empty(image.shape, dtype=image.dtype)
+ _io.io_auto(result, image_band, False)
+
+ _gdal.GDALClose(image_dataset)
+ _gdal.GDALClose(mask_dataset)
+ _gdal.CSLDestroy(alg_options)
+
+ return result
diff --git a/rasterio/_gdal.pxd b/rasterio/_gdal.pxd
index 1214678..0ae46f6 100644
--- a/rasterio/_gdal.pxd
+++ b/rasterio/_gdal.pxd
@@ -101,7 +101,7 @@ cdef extern from "gdal.h" nogil:
short c3
short c4
- const GDALColorEntry *GDALGetColorEntry (void *hTable, int)
+ 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)
@@ -211,5 +211,5 @@ cdef extern from "gdal_alg.h":
int GDALApproxTransform(void *pTransformArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
void GDALDestroyApproxTransformer( void * )
-
+ int GDALFillNodata(void *dst_band, void *mask_band, double max_search_distance, int deprecated, int smoothing_iterations, char **options, void *progress_func, void *progress_data)
diff --git a/rasterio/_io.pyx b/rasterio/_io.pyx
index 98e18e1..e9339a5 100644
--- a/rasterio/_io.pyx
+++ b/rasterio/_io.pyx
@@ -559,8 +559,13 @@ cdef class RasterReader(_base.DatasetReader):
a 2D array if it is a band index number.
out: numpy ndarray, optional
- An optional reference to an output array with the same
- dimensions and shape.
+ As with Numpy ufuncs, this is an optional reference to an
+ output array with the same dimensions and shape into which
+ data will be placed.
+
+ *Note*: the method's return value may be a view on this
+ array. In other words, `out` is likely to be an
+ incomplete representation of the method's results.
window : a pair (tuple) of pairs of ints, optional
The optional `window` argument is a 2 item tuple. The first
@@ -585,8 +590,11 @@ cdef class RasterReader(_base.DatasetReader):
Returns
-------
- Numpy ndarray
+ Numpy ndarray or a view on a Numpy ndarray
+ Note: as with Numpy ufuncs, an object is returned even if you
+ use the optional `out` argument and the return value shall be
+ preferentially used by callers.
"""
return2d = False
@@ -855,6 +863,29 @@ cdef class RasterReader(_base.DatasetReader):
hmask, 0, xoff, yoff, width, height, out)
return out
+ def sample(self, xy, indexes=None):
+ """Get the values of a dataset at certain positions
+
+ Parameters
+ ----------
+ xy : iterable, pairs of floats
+ A sequence or generator of (x, y) pairs.
+
+ indexes : list of ints or a single int, optional
+ If `indexes` is a list, the result is a 3D array, but is
+ a 2D array if it is a band index number.
+
+ Returns
+ -------
+ Iterable, yielding dataset values for the specified `indexes`
+ as an ndarray.
+ """
+ for x, y in xy:
+ r, c = self.index(x, y)
+ window = ((r, r+1), (c, c+1))
+ data = self.read(
+ indexes, window=window, masked=False, boundless=True)
+ yield data[:,0,0]
cdef class RasterUpdater(RasterReader):
# Read-write access to raster data and metadata.
@@ -911,7 +942,8 @@ cdef class RasterUpdater(RasterReader):
def start(self):
cdef const char *drv_name = NULL
cdef char **options = NULL
- cdef char *key_c, *val_c = NULL
+ cdef char *key_c = NULL
+ cdef char *val_c = NULL
cdef void *drv = NULL
cdef void *hband = NULL
cdef int success
@@ -1308,7 +1340,7 @@ cdef class RasterUpdater(RasterReader):
def write_colormap(self, bidx, colormap):
"""Write a colormap for a band to the dataset."""
- cdef void *hBand
+ cdef void *hBand = NULL
cdef void *hTable
cdef _gdal.GDALColorEntry color
if self._hds == NULL:
@@ -1412,6 +1444,7 @@ cdef class InMemoryRaster:
self.dataset = NULL
cdef void *memdriver = _gdal.GDALGetDriverByName("MEM")
+ cdef int i = 0 # avoids Cython warning in for loop below
# Several GDAL operations require the array of band IDs as input
self.band_ids[0] = 1
@@ -1548,7 +1581,8 @@ cdef class IndirectRasterUpdater(RasterUpdater):
def close(self):
cdef const char *drv_name = NULL
cdef char **options = NULL
- cdef char *key_c, *val_c = NULL
+ cdef char *key_c = NULL
+ cdef char *val_c = NULL
cdef void *drv = NULL
cdef void *temp = NULL
cdef int success
@@ -1598,7 +1632,7 @@ def writer(path, mode, **kwargs):
# format driver's capabilities.
cdef void *hds = NULL
cdef void *drv = NULL
- cdef char *drv_name = NULL
+ cdef const char *drv_name = NULL
cdef const char *fname = NULL
if mode == 'w' and 'driver' in kwargs:
diff --git a/rasterio/_warp.pyx b/rasterio/_warp.pyx
index d9faaed..b84470a 100644
--- a/rasterio/_warp.pyx
+++ b/rasterio/_warp.pyx
@@ -1,6 +1,4 @@
# distutils: language = c++
-# cython: profile=True
-#
from collections import namedtuple
import logging
@@ -8,7 +6,7 @@ import logging
import numpy as np
cimport numpy as np
-from rasterio cimport _gdal, _ogr, _io, _features
+from rasterio cimport _base, _gdal, _ogr, _io, _features
from rasterio import dtypes
@@ -78,94 +76,6 @@ 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, zs):
- cdef double *x, *y, *z = NULL
- cdef char *proj_c = NULL
- cdef void *src, *dst
- cdef void *transform
- cdef int i
-
- assert len(xs) == len(ys)
- assert zs is None or len(xs) == len(zs)
-
- 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]
-
- if zs is not None:
- z = <double *>_gdal.CPLMalloc(n*sizeof(double))
- for i in range(n):
- z[i] = zs[i]
-
- transform = _gdal.OCTNewCoordinateTransformation(src, dst)
- res = _gdal.OCTTransform(transform, n, x, y, z)
- #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]
-
- if zs is not None:
- res_zs = [0]*n
- for i in range(n):
- res_zs[i] = z[i]
- _gdal.CPLFree(z)
-
- retval = (res_xs, res_ys, res_zs)
- else:
- retval = (res_xs, res_ys)
-
- _gdal.CPLFree(x)
- _gdal.CPLFree(y)
- _gdal.OCTDestroyCoordinateTransformation(transform)
- _gdal.OSRDestroySpatialReference(src)
- _gdal.OSRDestroySpatialReference(dst)
- return retval
-
-
def _transform_geom(
src_crs, dst_crs, geom, antimeridian_cutting, antimeridian_offset,
precision):
@@ -174,15 +84,16 @@ def _transform_geom(
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 void *src = NULL
+ cdef void *dst = NULL
+ cdef void *transform = NULL
+ cdef OGRGeometryFactory *factory = NULL
+ cdef void *src_ogr_geom = NULL
+ cdef void *dst_ogr_geom = NULL
cdef int i
- src = _osr_from_crs(src_crs)
- dst = _osr_from_crs(dst_crs)
+ src = _base._osr_from_crs(src_crs)
+ dst = _base._osr_from_crs(dst_crs)
transform = _gdal.OCTNewCoordinateTransformation(src, dst)
# Transform options.
@@ -259,7 +170,7 @@ def _reproject(
bands of datasets on disk, the coordinate reference systems and
transforms will be read from the appropriate datasets.
"""
- cdef int retval, rows, cols
+ cdef int retval=0, rows, cols
cdef void *hrdriver
cdef void *hdsin
cdef void *hdsout
diff --git a/rasterio/features.py b/rasterio/features.py
index ea5884a..3673c3a 100644
--- a/rasterio/features.py
+++ b/rasterio/features.py
@@ -8,7 +8,7 @@ import warnings
import numpy as np
import rasterio
-from rasterio._features import _shapes, _sieve, _rasterize
+from rasterio._features import _shapes, _sieve, _rasterize, _bounds
from rasterio.transform import IDENTITY, guard_transform
from rasterio.dtypes import get_minimum_int_dtype
@@ -321,3 +321,24 @@ def rasterize(
_rasterize(valid_shapes, out, transform.to_gdal(), all_touched)
return out
+
+
+def bounds(geometry):
+ """Returns a (minx, miny, maxx, maxy) bounding box. From Fiona 1.4.8.
+ Modified to return bbox from geometry if available.
+
+ Parameters
+ ----------
+ geometry: GeoJSON-like feature, feature collection, or geometry.
+
+ Returns
+ -------
+ tuple
+ Bounding box: (minx, miny, maxx, maxy)
+ """
+
+ if 'bbox' in geometry:
+ return tuple(geometry['bbox'])
+
+ geom = geometry.get('geometry') or geometry
+ return _bounds(geom)
diff --git a/rasterio/fill.py b/rasterio/fill.py
new file mode 100644
index 0000000..37ec58e
--- /dev/null
+++ b/rasterio/fill.py
@@ -0,0 +1,47 @@
+import rasterio
+from rasterio._fill import _fillnodata
+
+def fillnodata(
+ image,
+ mask=None,
+ max_search_distance=100.0,
+ smoothing_iterations=0):
+ """
+ Fill selected raster regions by interpolation from the edges.
+
+ This algorithm will interpolate values for all designated nodata pixels
+ (marked by zeros in `mask`). For each pixel a four direction conic search
+ is done to find values to interpolate from (using inverse distance
+ weighting). Once all values are interpolated, zero or more smoothing
+ iterations (3x3 average filters on interpolated pixels) are applied to
+ smooth out artifacts.
+
+ This algorithm is generally suitable for interpolating missing regions of
+ fairly continuously varying rasters (such as elevation models for
+ instance). It is also suitable for filling small holes and cracks in more
+ irregularly varying images (like aerial photos). It is generally not so
+ great for interpolating a raster from sparse point data.
+
+ Parameters
+ ----------
+ image : numpy ndarray
+ The source containing nodata holes.
+ mask : numpy ndarray
+ A mask band indicating which pixels to interpolate. Pixels to
+ interpolate into are indicated by the value 0. Values of 1 indicate
+ areas to use during interpolation. Must be same shape as image.
+ max_search_distance : float, optional
+ The maxmimum number of pixels to search in all directions to find
+ values to interpolate from. The default is 100.
+ smoothing_iterations : integer, optional
+ The number of 3x3 smoothing filter passes to run. The default is 0.
+
+ Returns
+ -------
+ out : numpy ndarray
+ The filled raster array.
+ """
+ max_search_distance = float(max_search_distance)
+ smoothing_iterations = int(smoothing_iterations)
+ with rasterio.drivers():
+ return _fillnodata(image, mask, max_search_distance, smoothing_iterations)
diff --git a/rasterio/rasterfill.cpp b/rasterio/rasterfill.cpp
new file mode 100644
index 0000000..df2297b
--- /dev/null
+++ b/rasterio/rasterfill.cpp
@@ -0,0 +1,886 @@
+/******************************************************************************
+ * $Id$
+ *
+ * Project: GDAL
+ * Purpose: Interpolate in nodata areas.
+ * Author: Frank Warmerdam, warmerdam at pobox.com
+ *
+ ******************************************************************************
+ * Copyright (c) 2008, Frank Warmerdam
+ * Copyright (c) 2015, Sean Gillies <sean at mapbox.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ***************************************************************************/
+
+#include "gdal_alg.h"
+#include "cpl_conv.h"
+#include "cpl_string.h"
+
+CPL_CVSID("$Id$");
+
+/************************************************************************/
+/* GDALFilterLine() */
+/* */
+/* Apply 3x3 filtering one one scanline with masking for which */
+/* pixels are to be interpolated (ThisFMask) and which window */
+/* pixels are valid to include in the interpolation (TMask). */
+/************************************************************************/
+
+static void
+GDALFilterLine( float *pafLastLine, float *pafThisLine, float *pafNextLine,
+ float *pafOutLine,
+ GByte *pabyLastTMask, GByte *pabyThisTMask, GByte*pabyNextTMask,
+ GByte *pabyThisFMask, int nXSize )
+
+{
+ int iX;
+
+ for( iX = 0; iX < nXSize; iX++ )
+ {
+ if( !pabyThisFMask[iX] )
+ {
+ pafOutLine[iX] = pafThisLine[iX];
+ continue;
+ }
+
+ CPLAssert( pabyThisTMask[iX] );
+
+ double dfValSum = 0.0;
+ double dfWeightSum = 0.0;
+
+ // Previous line
+ if( pafLastLine != NULL )
+ {
+ if( iX > 0 && pabyLastTMask[iX-1] )
+ {
+ dfValSum += pafLastLine[iX-1];
+ dfWeightSum += 1.0;
+ }
+ if( pabyLastTMask[iX] )
+ {
+ dfValSum += pafLastLine[iX];
+ dfWeightSum += 1.0;
+ }
+ if( iX < nXSize-1 && pabyLastTMask[iX+1] )
+ {
+ dfValSum += pafLastLine[iX+1];
+ dfWeightSum += 1.0;
+ }
+ }
+
+ // Current Line
+ if( iX > 0 && pabyThisTMask[iX-1] )
+ {
+ dfValSum += pafThisLine[iX-1];
+ dfWeightSum += 1.0;
+ }
+ if( pabyThisTMask[iX] )
+ {
+ dfValSum += pafThisLine[iX];
+ dfWeightSum += 1.0;
+ }
+ if( iX < nXSize-1 && pabyThisTMask[iX+1] )
+ {
+ dfValSum += pafThisLine[iX+1];
+ dfWeightSum += 1.0;
+ }
+
+ // Next line
+ if( pafNextLine != NULL )
+ {
+ if( iX > 0 && pabyNextTMask[iX-1] )
+ {
+ dfValSum += pafNextLine[iX-1];
+ dfWeightSum += 1.0;
+ }
+ if( pabyNextTMask[iX] )
+ {
+ dfValSum += pafNextLine[iX];
+ dfWeightSum += 1.0;
+ }
+ if( iX < nXSize-1 && pabyNextTMask[iX+1] )
+ {
+ dfValSum += pafNextLine[iX+1];
+ dfWeightSum += 1.0;
+ }
+ }
+
+ pafOutLine[iX] = (float) (dfValSum / dfWeightSum);
+ }
+}
+
+/************************************************************************/
+/* GDALMultiFilter() */
+/* */
+/* Apply multiple iterations of a 3x3 smoothing filter over a */
+/* band with masking controlling what pixels should be */
+/* filtered (FiltMaskBand non zero) and which pixels can be */
+/* considered valid contributors to the filter */
+/* (TargetMaskBand non zero). */
+/* */
+/* This implementation attempts to apply many iterations in */
+/* one IO pass by managing the filtering over a rolling buffer */
+/* of nIternations+2 scanlines. While possibly clever this */
+/* makes the algorithm implementation largely */
+/* incomprehensible. */
+/************************************************************************/
+
+static CPLErr
+GDALMultiFilter( GDALRasterBandH hTargetBand,
+ GDALRasterBandH hTargetMaskBand,
+ GDALRasterBandH hFiltMaskBand,
+ int nIterations,
+ GDALProgressFunc pfnProgress,
+ void * pProgressArg )
+
+{
+ float *paf3PassLineBuf;
+ GByte *pabyTMaskBuf;
+ GByte *pabyFMaskBuf;
+ float *pafThisPass, *pafLastPass, *pafSLastPass;
+
+ int nBufLines = nIterations + 2;
+ int iPassCounter = 0;
+ int nNewLine; // the line being loaded this time (zero based scanline)
+ int nXSize = GDALGetRasterBandXSize( hTargetBand );
+ int nYSize = GDALGetRasterBandYSize( hTargetBand );
+ CPLErr eErr = CE_None;
+
+/* -------------------------------------------------------------------- */
+/* Report starting progress value. */
+/* -------------------------------------------------------------------- */
+ if( !pfnProgress( 0.0, "Smoothing Filter...", pProgressArg ) )
+ {
+ CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
+ return CE_Failure;
+ }
+
+/* -------------------------------------------------------------------- */
+/* Allocate rotating buffers. */
+/* -------------------------------------------------------------------- */
+ pabyTMaskBuf = (GByte *) VSIMalloc2(nXSize, nBufLines);
+ pabyFMaskBuf = (GByte *) VSIMalloc2(nXSize, nBufLines);
+
+ paf3PassLineBuf = (float *) VSIMalloc3(nXSize, nBufLines, 3 * sizeof(float));
+ if (pabyTMaskBuf == NULL || pabyFMaskBuf == NULL || paf3PassLineBuf == NULL)
+ {
+ CPLError(CE_Failure, CPLE_OutOfMemory,
+ "Could not allocate enough memory for temporary buffers");
+ eErr = CE_Failure;
+ goto end;
+ }
+
+/* -------------------------------------------------------------------- */
+/* Process rotating buffers. */
+/* -------------------------------------------------------------------- */
+ for( nNewLine = 0;
+ eErr == CE_None && nNewLine < nYSize+nIterations;
+ nNewLine++ )
+ {
+/* -------------------------------------------------------------------- */
+/* Rotate pass buffers. */
+/* -------------------------------------------------------------------- */
+ iPassCounter = (iPassCounter + 1) % 3;
+
+ pafSLastPass = paf3PassLineBuf
+ + ((iPassCounter+0)%3) * nXSize*nBufLines;
+ pafLastPass = paf3PassLineBuf
+ + ((iPassCounter+1)%3) * nXSize*nBufLines;
+ pafThisPass = paf3PassLineBuf
+ + ((iPassCounter+2)%3) * nXSize*nBufLines;
+
+/* -------------------------------------------------------------------- */
+/* Where does the new line go in the rotating buffer? */
+/* -------------------------------------------------------------------- */
+ int iBufOffset = nNewLine % nBufLines;
+
+/* -------------------------------------------------------------------- */
+/* Read the new data line if it is't off the bottom of the */
+/* image. */
+/* -------------------------------------------------------------------- */
+ if( nNewLine < nYSize )
+ {
+ eErr =
+ GDALRasterIO( hTargetMaskBand, GF_Read,
+ 0, nNewLine, nXSize, 1,
+ pabyTMaskBuf + nXSize * iBufOffset, nXSize, 1,
+ GDT_Byte, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+ eErr =
+ GDALRasterIO( hFiltMaskBand, GF_Read,
+ 0, nNewLine, nXSize, 1,
+ pabyFMaskBuf + nXSize * iBufOffset, nXSize, 1,
+ GDT_Byte, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+ eErr =
+ GDALRasterIO( hTargetBand, GF_Read,
+ 0, nNewLine, nXSize, 1,
+ pafThisPass + nXSize * iBufOffset, nXSize, 1,
+ GDT_Float32, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+ }
+
+/* -------------------------------------------------------------------- */
+/* Loop over the loaded data, applying the filter to all loaded */
+/* lines with neighbours. */
+/* -------------------------------------------------------------------- */
+ int iFLine;
+
+ for( iFLine = nNewLine-1;
+ eErr == CE_None && iFLine >= nNewLine-nIterations;
+ iFLine-- )
+ {
+ int iLastOffset, iThisOffset, iNextOffset;
+
+ iLastOffset = (iFLine-1) % nBufLines;
+ iThisOffset = (iFLine ) % nBufLines;
+ iNextOffset = (iFLine+1) % nBufLines;
+
+ // default to preserving the old value.
+ if( iFLine >= 0 )
+ memcpy( pafThisPass + iThisOffset * nXSize,
+ pafLastPass + iThisOffset * nXSize,
+ sizeof(float) * nXSize );
+
+ // currently this skips the first and last line. Eventually
+ // we will enable these too. TODO
+ if( iFLine < 1 || iFLine >= nYSize-1 )
+ {
+ continue;
+ }
+
+ GDALFilterLine(
+ pafSLastPass + iLastOffset * nXSize,
+ pafLastPass + iThisOffset * nXSize,
+ pafThisPass + iNextOffset * nXSize,
+ pafThisPass + iThisOffset * nXSize,
+ pabyTMaskBuf + iLastOffset * nXSize,
+ pabyTMaskBuf + iThisOffset * nXSize,
+ pabyTMaskBuf + iNextOffset * nXSize,
+ pabyFMaskBuf + iThisOffset * nXSize,
+ nXSize );
+ }
+
+/* -------------------------------------------------------------------- */
+/* Write out the top data line that will be rolling out of our */
+/* buffer. */
+/* -------------------------------------------------------------------- */
+ int iLineToSave = nNewLine - nIterations;
+
+ if( iLineToSave >= 0 && eErr == CE_None )
+ {
+ iBufOffset = iLineToSave % nBufLines;
+
+ eErr =
+ GDALRasterIO( hTargetBand, GF_Write,
+ 0, iLineToSave, nXSize, 1,
+ pafThisPass + nXSize * iBufOffset, nXSize, 1,
+ GDT_Float32, 0, 0 );
+ }
+
+/* -------------------------------------------------------------------- */
+/* Report progress. */
+/* -------------------------------------------------------------------- */
+ if( eErr == CE_None
+ && !pfnProgress( (nNewLine+1) / (double) (nYSize+nIterations),
+ "Smoothing Filter...", pProgressArg ) )
+ {
+ CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
+ eErr = CE_Failure;
+ }
+ }
+
+/* -------------------------------------------------------------------- */
+/* Cleanup */
+/* -------------------------------------------------------------------- */
+end:
+ CPLFree( pabyTMaskBuf );
+ CPLFree( pabyFMaskBuf );
+ CPLFree( paf3PassLineBuf );
+
+ return eErr;
+}
+
+/************************************************************************/
+/* QUAD_CHECK() */
+/* */
+/* macro for checking whether a point is nearer than the */
+/* existing closest point. */
+/************************************************************************/
+#define QUAD_CHECK(quad_dist, quad_value, \
+target_x, target_y, origin_x, origin_y, target_value ) \
+ \
+if( quad_value != nNoDataVal ) \
+{ \
+ double dfDx = (double)target_x - (double)origin_x; \
+ double dfDy = (double)target_y - (double)origin_y; \
+ double dfDistSq = dfDx * dfDx + dfDy * dfDy; \
+ \
+ if( dfDistSq < quad_dist*quad_dist ) \
+ { \
+ CPLAssert( dfDistSq > 0.0 ); \
+ quad_dist = sqrt(dfDistSq); \
+ quad_value = target_value; \
+ } \
+}
+
+/************************************************************************/
+/* GDALFillNodata() */
+/************************************************************************/
+
+/**
+ * Fill selected raster regions by interpolation from the edges.
+ *
+ * This algorithm will interpolate values for all designated
+ * nodata pixels (marked by zeros in hMaskBand). For each pixel
+ * a four direction conic search is done to find values to interpolate
+ * from (using inverse distance weighting). Once all values are
+ * interpolated, zero or more smoothing iterations (3x3 average
+ * filters on interpolated pixels) are applied to smooth out
+ * artifacts.
+ *
+ * This algorithm is generally suitable for interpolating missing
+ * regions of fairly continuously varying rasters (such as elevation
+ * models for instance). It is also suitable for filling small holes
+ * and cracks in more irregularly varying images (like airphotos). It
+ * is generally not so great for interpolating a raster from sparse
+ * point data - see the algorithms defined in gdal_grid.h for that case.
+ *
+ * @param hTargetBand the raster band to be modified in place.
+ * @param hMaskBand a mask band indicating pixels to be interpolated (zero valued
+ * @param dfMaxSearchDist the maximum number of pixels to search in all
+ * directions to find values to interpolate from.
+ * @param bDeprecatedOption unused argument, should be zero.
+ * @param nSmoothingIterations the number of 3x3 smoothing filter passes to
+ * run (0 or more).
+ * @param papszOptions additional name=value options in a string list (the
+ * temporary file driver can be specified like TEMP_FILE_DRIVER=MEM).
+ * @param pfnProgress the progress function to report completion.
+ * @param pProgressArg callback data for progress function.
+ *
+ * @return CE_None on success or CE_Failure if something goes wrong.
+ */
+
+CPLErr CPL_STDCALL
+GDALFillNodata( GDALRasterBandH hTargetBand,
+ GDALRasterBandH hMaskBand,
+ double dfMaxSearchDist,
+ int bDeprecatedOption,
+ int nSmoothingIterations,
+ char **papszOptions,
+ GDALProgressFunc pfnProgress,
+ void * pProgressArg )
+
+{
+ VALIDATE_POINTER1( hTargetBand, "GDALFillNodata", CE_Failure );
+
+ int nXSize = GDALGetRasterBandXSize( hTargetBand );
+ int nYSize = GDALGetRasterBandYSize( hTargetBand );
+ CPLErr eErr = CE_None;
+
+ // Special "x" pixel values identifying pixels as special.
+ GUInt32 nNoDataVal;
+ GDALDataType eType;
+
+ if( dfMaxSearchDist == 0.0 )
+ dfMaxSearchDist = MAX(nXSize,nYSize) + 1;
+
+ int nMaxSearchDist = (int) floor(dfMaxSearchDist);
+
+ if( nXSize > 65533 || nYSize > 65533 )
+ {
+ eType = GDT_UInt32;
+ nNoDataVal = 4000002;
+ }
+ else
+ {
+ eType = GDT_UInt16;
+ nNoDataVal = 65535;
+ }
+
+ if( hMaskBand == NULL )
+ hMaskBand = GDALGetMaskBand( hTargetBand );
+
+ /* If there are smoothing iterations, reserve 10% of the progress for them */
+ double dfProgressRatio = (nSmoothingIterations > 0) ? 0.9 : 1.0;
+
+/* -------------------------------------------------------------------- */
+/* Initialize progress counter. */
+/* -------------------------------------------------------------------- */
+ if( pfnProgress == NULL )
+ pfnProgress = GDALDummyProgress;
+
+ if( !pfnProgress( 0.0, "Filling...", pProgressArg ) )
+ {
+ CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
+ return CE_Failure;
+ }
+
+/* -------------------------------------------------------------------- */
+/* Determine format driver for temp work files. */
+/* -------------------------------------------------------------------- */
+ CPLString osTmpFileDriver = CSLFetchNameValueDef(
+ papszOptions, "TEMP_FILE_DRIVER", "MEM");
+ GDALDriverH hDriver = GDALGetDriverByName((const char *) osTmpFileDriver);
+
+ if (hDriver == NULL)
+ {
+ CPLError(CE_Failure, CPLE_AppDefined,
+ "Given driver is not registered");
+ return CE_Failure;
+ }
+
+ if (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, NULL) == NULL)
+ {
+ CPLError(CE_Failure, CPLE_AppDefined,
+ "Given driver is incapable of creating temp work files");
+ return CE_Failure;
+ }
+
+ char **papszWorkFileOptions = NULL;
+ if (osTmpFileDriver == "GTiff") {
+ papszWorkFileOptions = CSLSetNameValue(
+ papszWorkFileOptions, "COMPRESS", "LZW");
+ papszWorkFileOptions = CSLSetNameValue(
+ papszWorkFileOptions, "BIGTIFF", "IF_SAFER");
+ }
+
+/* -------------------------------------------------------------------- */
+/* Create a work file to hold the Y "last value" indices. */
+/* -------------------------------------------------------------------- */
+ GDALDatasetH hYDS;
+ GDALRasterBandH hYBand;
+
+ CPLString osTmpFile = CPLGenerateTempFilename("");
+ CPLString osYTmpFile = osTmpFile + "fill_y_work.tif";
+
+ hYDS = GDALCreate( hDriver, osYTmpFile, nXSize, nYSize, 1,
+ eType, (char **) papszWorkFileOptions );
+
+ if ( hYDS == NULL )
+ {
+ CPLError(CE_Failure, CPLE_AppDefined,
+ "Could not create Y index work file. Check driver capabilities.");
+ return CE_Failure;
+ }
+
+ hYBand = GDALGetRasterBand( hYDS, 1 );
+
+/* -------------------------------------------------------------------- */
+/* Create a work file to hold the pixel value associated with */
+/* the "last xy value" pixel. */
+/* -------------------------------------------------------------------- */
+ GDALDatasetH hValDS;
+ GDALRasterBandH hValBand;
+ CPLString osValTmpFile = osTmpFile + "fill_val_work.tif";
+
+ hValDS = GDALCreate( hDriver, osValTmpFile, nXSize, nYSize, 1,
+ GDALGetRasterDataType( hTargetBand ),
+ (char **) papszWorkFileOptions );
+
+ if ( hValDS == NULL )
+ {
+ CPLError(CE_Failure, CPLE_AppDefined,
+ "Could not create XY value work file. Check driver capabilities.");
+ return CE_Failure;
+ }
+
+ hValBand = GDALGetRasterBand( hValDS, 1 );
+
+/* -------------------------------------------------------------------- */
+/* Create a mask file to make it clear what pixels can be filtered */
+/* on the filtering pass. */
+/* -------------------------------------------------------------------- */
+ GDALDatasetH hFiltMaskDS;
+ GDALRasterBandH hFiltMaskBand;
+ CPLString osFiltMaskTmpFile = osTmpFile + "fill_filtmask_work.tif";
+
+ hFiltMaskDS =
+ GDALCreate( hDriver, osFiltMaskTmpFile, nXSize, nYSize, 1,
+ GDT_Byte, (char **) papszWorkFileOptions );
+
+ if ( hFiltMaskDS == NULL )
+ {
+ CPLError(CE_Failure, CPLE_AppDefined,
+ "Could not create mask work file. Check driver capabilities.");
+ return CE_Failure;
+ }
+
+ hFiltMaskBand = GDALGetRasterBand( hFiltMaskDS, 1 );
+
+/* -------------------------------------------------------------------- */
+/* Allocate buffers for last scanline and this scanline. */
+/* -------------------------------------------------------------------- */
+ GUInt32 *panLastY, *panThisY, *panTopDownY;
+ float *pafLastValue, *pafThisValue, *pafScanline, *pafTopDownValue;
+ GByte *pabyMask, *pabyFiltMask;
+ int iX;
+ int iY;
+
+ panLastY = (GUInt32 *) VSICalloc(nXSize,sizeof(GUInt32));
+ panThisY = (GUInt32 *) VSICalloc(nXSize,sizeof(GUInt32));
+ panTopDownY = (GUInt32 *) VSICalloc(nXSize,sizeof(GUInt32));
+ pafLastValue = (float *) VSICalloc(nXSize,sizeof(float));
+ pafThisValue = (float *) VSICalloc(nXSize,sizeof(float));
+ pafTopDownValue = (float *) VSICalloc(nXSize,sizeof(float));
+ pafScanline = (float *) VSICalloc(nXSize,sizeof(float));
+ pabyMask = (GByte *) VSICalloc(nXSize,1);
+ pabyFiltMask = (GByte *) VSICalloc(nXSize,1);
+ if (panLastY == NULL || panThisY == NULL || panTopDownY == NULL ||
+ pafLastValue == NULL || pafThisValue == NULL || pafTopDownValue == NULL ||
+ pafScanline == NULL || pabyMask == NULL || pabyFiltMask == NULL)
+ {
+ CPLError(CE_Failure, CPLE_OutOfMemory,
+ "Could not allocate enough memory for temporary buffers");
+
+ eErr = CE_Failure;
+ goto end;
+ }
+
+ for( iX = 0; iX < nXSize; iX++ )
+ {
+ panLastY[iX] = nNoDataVal;
+ }
+
+/* ==================================================================== */
+/* Make first pass from top to bottom collecting the "last */
+/* known value" for each column and writing it out to the work */
+/* files. */
+/* ==================================================================== */
+
+ for( iY = 0; iY < nYSize && eErr == CE_None; iY++ )
+ {
+/* -------------------------------------------------------------------- */
+/* Read data and mask for this line. */
+/* -------------------------------------------------------------------- */
+ eErr =
+ GDALRasterIO( hMaskBand, GF_Read, 0, iY, nXSize, 1,
+ pabyMask, nXSize, 1, GDT_Byte, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+ eErr =
+ GDALRasterIO( hTargetBand, GF_Read, 0, iY, nXSize, 1,
+ pafScanline, nXSize, 1, GDT_Float32, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+/* -------------------------------------------------------------------- */
+/* Figure out the most recent pixel for each column. */
+/* -------------------------------------------------------------------- */
+
+ for( iX = 0; iX < nXSize; iX++ )
+ {
+ if( pabyMask[iX] )
+ {
+ pafThisValue[iX] = pafScanline[iX];
+ panThisY[iX] = iY;
+ }
+ else if( iY <= dfMaxSearchDist + panLastY[iX] )
+ {
+ pafThisValue[iX] = pafLastValue[iX];
+ panThisY[iX] = panLastY[iX];
+ }
+ else
+ {
+ panThisY[iX] = nNoDataVal;
+ }
+ }
+
+/* -------------------------------------------------------------------- */
+/* Write out best index/value to working files. */
+/* -------------------------------------------------------------------- */
+ eErr = GDALRasterIO( hYBand, GF_Write, 0, iY, nXSize, 1,
+ panThisY, nXSize, 1, GDT_UInt32, 0, 0 );
+ if( eErr != CE_None )
+ break;
+
+ eErr = GDALRasterIO( hValBand, GF_Write, 0, iY, nXSize, 1,
+ pafThisValue, nXSize, 1, GDT_Float32, 0, 0 );
+ if( eErr != CE_None )
+ break;
+
+/* -------------------------------------------------------------------- */
+/* Flip this/last buffers. */
+/* -------------------------------------------------------------------- */
+ {
+ float *pafTmp = pafThisValue;
+ pafThisValue = pafLastValue;
+ pafLastValue = pafTmp;
+
+ GUInt32 *panTmp = panThisY;
+ panThisY = panLastY;
+ panLastY = panTmp;
+ }
+
+/* -------------------------------------------------------------------- */
+/* report progress. */
+/* -------------------------------------------------------------------- */
+ if( eErr == CE_None
+ && !pfnProgress( dfProgressRatio * (0.5*(iY+1) / (double)nYSize),
+ "Filling...", pProgressArg ) )
+ {
+ CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
+ eErr = CE_Failure;
+ }
+ }
+
+/* ==================================================================== */
+/* Now we will do collect similar this/last information from */
+/* bottom to top and use it in combination with the top to */
+/* bottom search info to interpolate. */
+/* ==================================================================== */
+ for( iY = nYSize-1; iY >= 0 && eErr == CE_None; iY-- )
+ {
+ eErr =
+ GDALRasterIO( hMaskBand, GF_Read, 0, iY, nXSize, 1,
+ pabyMask, nXSize, 1, GDT_Byte, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+ eErr =
+ GDALRasterIO( hTargetBand, GF_Read, 0, iY, nXSize, 1,
+ pafScanline, nXSize, 1, GDT_Float32, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+/* -------------------------------------------------------------------- */
+/* Figure out the most recent pixel for each column. */
+/* -------------------------------------------------------------------- */
+
+ for( iX = 0; iX < nXSize; iX++ )
+ {
+ if( pabyMask[iX] )
+ {
+ pafThisValue[iX] = pafScanline[iX];
+ panThisY[iX] = iY;
+ }
+ else if( panLastY[iX] - iY <= dfMaxSearchDist )
+ {
+ pafThisValue[iX] = pafLastValue[iX];
+ panThisY[iX] = panLastY[iX];
+ }
+ else
+ {
+ panThisY[iX] = nNoDataVal;
+ }
+ }
+
+/* -------------------------------------------------------------------- */
+/* Load the last y and corresponding value from the top down pass. */
+/* -------------------------------------------------------------------- */
+ eErr =
+ GDALRasterIO( hYBand, GF_Read, 0, iY, nXSize, 1,
+ panTopDownY, nXSize, 1, GDT_UInt32, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+ eErr =
+ GDALRasterIO( hValBand, GF_Read, 0, iY, nXSize, 1,
+ pafTopDownValue, nXSize, 1, GDT_Float32, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+/* -------------------------------------------------------------------- */
+/* Attempt to interpolate any pixels that are nodata. */
+/* -------------------------------------------------------------------- */
+ memset( pabyFiltMask, 0, nXSize );
+ for( iX = 0; iX < nXSize; iX++ )
+ {
+ int iStep, iQuad;
+ int nThisMaxSearchDist = nMaxSearchDist;
+
+ // If this was a valid target - no change.
+ if( pabyMask[iX] )
+ continue;
+
+ // Quadrants 0:topleft, 1:bottomleft, 2:topright, 3:bottomright
+ double adfQuadDist[4];
+ double adfQuadValue[4];
+
+ for( iQuad = 0; iQuad < 4; iQuad++ )
+ {
+ adfQuadDist[iQuad] = dfMaxSearchDist + 1.0;
+ adfQuadValue[iQuad] = 0.0;
+ }
+
+ // Step left and right by one pixel searching for the closest
+ // target value for each quadrant.
+ for( iStep = 0; iStep < nThisMaxSearchDist; iStep++ )
+ {
+ int iLeftX = MAX(0,iX - iStep);
+ int iRightX = MIN(nXSize-1,iX + iStep);
+
+ // top left includes current line
+ QUAD_CHECK(adfQuadDist[0],adfQuadValue[0],
+ iLeftX, panTopDownY[iLeftX], iX, iY,
+ pafTopDownValue[iLeftX] );
+
+ // bottom left
+ QUAD_CHECK(adfQuadDist[1],adfQuadValue[1],
+ iLeftX, panLastY[iLeftX], iX, iY,
+ pafLastValue[iLeftX] );
+
+ // top right and bottom right do no include center pixel.
+ if( iStep == 0 )
+ continue;
+
+ // top right includes current line
+ QUAD_CHECK(adfQuadDist[2],adfQuadValue[2],
+ iRightX, panTopDownY[iRightX], iX, iY,
+ pafTopDownValue[iRightX] );
+
+ // bottom right
+ QUAD_CHECK(adfQuadDist[3],adfQuadValue[3],
+ iRightX, panLastY[iRightX], iX, iY,
+ pafLastValue[iRightX] );
+
+ // every four steps, recompute maximum distance.
+ if( (iStep & 0x3) == 0 )
+ nThisMaxSearchDist = (int) floor(
+ MAX(MAX(adfQuadDist[0],adfQuadDist[1]),
+ MAX(adfQuadDist[2],adfQuadDist[3])) );
+ }
+
+ double dfWeightSum = 0.0;
+ double dfValueSum = 0.0;
+
+ for( iQuad = 0; iQuad < 4; iQuad++ )
+ {
+ if( adfQuadDist[iQuad] <= dfMaxSearchDist )
+ {
+ double dfWeight = 1.0 / adfQuadDist[iQuad];
+
+ dfWeightSum += dfWeight;
+ dfValueSum += adfQuadValue[iQuad] * dfWeight;
+ }
+ }
+
+ if( dfWeightSum > 0.0 )
+ {
+ pabyMask[iX] = 255;
+ pabyFiltMask[iX] = 255;
+ pafScanline[iX] = (float) (dfValueSum / dfWeightSum);
+ }
+
+ }
+
+/* -------------------------------------------------------------------- */
+/* Write out the updated data and mask information. */
+/* -------------------------------------------------------------------- */
+ eErr =
+ GDALRasterIO( hTargetBand, GF_Write, 0, iY, nXSize, 1,
+ pafScanline, nXSize, 1, GDT_Float32, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+ eErr =
+ GDALRasterIO( hFiltMaskBand, GF_Write, 0, iY, nXSize, 1,
+ pabyFiltMask, nXSize, 1, GDT_Byte, 0, 0 );
+
+ if( eErr != CE_None )
+ break;
+
+/* -------------------------------------------------------------------- */
+/* Flip this/last buffers. */
+/* -------------------------------------------------------------------- */
+ {
+ float *pafTmp = pafThisValue;
+ pafThisValue = pafLastValue;
+ pafLastValue = pafTmp;
+
+ GUInt32 *panTmp = panThisY;
+ panThisY = panLastY;
+ panLastY = panTmp;
+ }
+
+/* -------------------------------------------------------------------- */
+/* report progress. */
+/* -------------------------------------------------------------------- */
+ if( eErr == CE_None
+ && !pfnProgress( dfProgressRatio*(0.5+0.5*(nYSize-iY) / (double)nYSize),
+ "Filling...", pProgressArg ) )
+ {
+ CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
+ eErr = CE_Failure;
+ }
+ }
+
+/* ==================================================================== */
+/* Now we will do iterative average filters over the */
+/* interpolated values to smooth things out and make linear */
+/* artifacts less obvious. */
+/* ==================================================================== */
+ if( eErr == CE_None && nSmoothingIterations > 0 )
+ {
+ // force masks to be to flushed and recomputed.
+ GDALFlushRasterCache( hMaskBand );
+
+ void *pScaledProgress;
+ pScaledProgress =
+ GDALCreateScaledProgress( dfProgressRatio, 1.0, pfnProgress, NULL );
+
+ eErr = GDALMultiFilter( hTargetBand, hMaskBand, hFiltMaskBand,
+ nSmoothingIterations,
+ GDALScaledProgress, pScaledProgress );
+
+ GDALDestroyScaledProgress( pScaledProgress );
+ }
+
+/* -------------------------------------------------------------------- */
+/* Close and clean up temporary files. Free working buffers */
+/* -------------------------------------------------------------------- */
+end:
+ CPLFree(panLastY);
+ CPLFree(panThisY);
+ CPLFree(panTopDownY);
+ CPLFree(pafLastValue);
+ CPLFree(pafThisValue);
+ CPLFree(pafTopDownValue);
+ CPLFree(pafScanline);
+ CPLFree(pabyMask);
+ CPLFree(pabyFiltMask);
+
+ GDALClose( hYDS );
+ GDALClose( hValDS );
+ GDALClose( hFiltMaskDS );
+
+ CSLDestroy(papszWorkFileOptions);
+
+ GDALDeleteDataset( hDriver, osYTmpFile );
+ GDALDeleteDataset( hDriver, osValTmpFile );
+ GDALDeleteDataset( hDriver, osFiltMaskTmpFile );
+
+ return eErr;
+}
diff --git a/rasterio/rio/features.py b/rasterio/rio/features.py
index f6d4c24..ade9b31 100644
--- a/rasterio/rio/features.py
+++ b/rasterio/rio/features.py
@@ -1,6 +1,8 @@
import functools
import json
import logging
+from math import ceil, floor
+import os
import sys
import warnings
@@ -8,13 +10,15 @@ import click
from cligj import (
precision_opt, indent_opt, compact_opt, projection_geographic_opt,
projection_projected_opt, sequence_opt, use_rs_opt,
- geojson_type_feature_opt, geojson_type_bbox_opt)
+ geojson_type_feature_opt, geojson_type_bbox_opt, files_inout_arg,
+ format_opt)
import rasterio
from rasterio.transform import Affine
from rasterio.rio.cli import cli, coords, write_features
+logger = logging.getLogger('rio')
warnings.simplefilter('default')
@@ -142,3 +146,233 @@ def shapes(
except Exception:
logger.exception("Failed. Exception caught")
sys.exit(1)
+
+
+# Rasterize command.
+ at cli.command(short_help='Rasterize features.')
+ at files_inout_arg
+ at format_opt
+ at click.option('--like', type=click.Path(exists=True),
+ help='Raster dataset to use as a template for obtaining affine '
+ 'transform (bounds and resolution), crs, data type, and driver '
+ 'used to create the output. Only a single band will be output.')
+ at click.option('--bounds', nargs=4, type=float, default=None,
+ help='Output bounds: left, bottom, right, top.')
+ at click.option('--dimensions', nargs=2, type=int, default=None,
+ help='Output dataset width, height in number of pixels.')
+ at click.option('--res', multiple=True, type=float, default=None,
+ help='Output dataset resolution in units of coordinate '
+ 'reference system. Pixels assumed to be square if this option is '
+ 'used once, otherwise use: '
+ '--res pixel_width --res pixel_height')
+ at click.option('--src_crs', default='EPSG:4326',
+ help='Source coordinate reference system. Limited to EPSG '
+ 'codes for now. Used as output coordinate system if output does '
+ 'not exist or --like option is not used. Default: EPSG:4326')
+ at click.option('--all_touched', is_flag=True, default=False)
+ at click.option('--default_value', type=float, default=1, help='Default value '
+ 'for rasterized pixels')
+ at click.option('--fill', type=float, default=0, help='Fill value for all pixels '
+ 'not overlapping features. Will be evaluated as NoData pixels '
+ 'for output. Default: 0')
+ at click.option('--property', type=str, default=None, help='Property in '
+ 'GeoJSON features to use for rasterized values. Any features '
+ 'that lack this property will be given --default_value instead.')
+ at click.pass_context
+def rasterize(
+ ctx,
+ files,
+ driver,
+ like,
+ bounds,
+ dimensions,
+ res,
+ src_crs,
+ all_touched,
+ default_value,
+ fill,
+ property):
+
+ """Rasterize GeoJSON into a new or existing raster.
+
+ If the output raster exists, rio-rasterize will rasterize feature values
+ into all bands of that raster. The GeoJSON is assumed to be in the same
+ coordinate reference system as the output.
+
+ --default_value or property values when using --property must be using a
+ data type valid for the data type of that raster.
+
+
+ If a template raster is provided using the --like option, the affine
+ transform, coordinate reference system, and data type from that raster will
+ be used to create the output.
+
+ --default_value or property values when using --property must be using a
+ data type valid for the data type of that raster.
+
+ --driver, --bounds, --dimensions, --res, and --src_crs are ignored when
+ output exists or --like raster is provided
+
+ The GeoJSON must be within the bounds of the existing output or --like
+ raster, or an error will be raised.
+
+
+ If the output does not exist and --like raster is not provided, the input
+ GeoJSON will be used to determine the bounds of the output unless
+ provided using --bounds.
+
+ --dimensions or --res are required in this case.
+
+ If --res is provided, the bottom and right coordinates of bounds are
+ ignored.
+
+
+ Note:
+ The GeoJSON is not projected to match the coordinate reference system
+ of the output or --like rasters at this time. This functionality may be
+ added in the future.
+ """
+
+ import numpy as np
+ from rasterio.features import rasterize
+ from rasterio.features import bounds as calculate_bounds
+
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+
+ files = list(files)
+ output = files.pop()
+ input = click.open_file(files.pop(0) if files else '-')
+
+ # If values are actually meant to be integers, we need to cast them
+ # as such or rasterize creates floating point outputs
+ if default_value == int(default_value):
+ default_value = int(default_value)
+ if fill == int(fill):
+ fill = int(fill)
+
+ with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+
+ def feature_value(feature):
+ if property and 'properties' in feature:
+ return feature['properties'].get(property, default_value)
+ return default_value
+
+ def disjoint_bounds(bounds1, bounds2):
+ return (bounds1[0] > bounds2[2] or
+ bounds1[2] < bounds2[0] or
+ bounds1[1] > bounds2[3] or
+ bounds1[3] < bounds2[1])
+
+ geojson = json.loads(input.read())
+ if 'features' in geojson:
+ geometries = []
+ for f in geojson['features']:
+ geometries.append((f['geometry'], feature_value(f)))
+ elif 'geometry' in geojson:
+ geometries = ((geojson['geometry'], feature_value(geojson)), )
+ else:
+ raise click.BadParameter('Invalid GeoJSON', param=input,
+ param_hint='input')
+
+ geojson_bounds = geojson.get('bbox', calculate_bounds(geojson))
+
+ if os.path.exists(output):
+ with rasterio.open(output, 'r+') as out:
+ if disjoint_bounds(geojson_bounds, out.bounds):
+ raise click.BadParameter('GeoJSON outside bounds of '
+ 'existing output raster',
+ param=input, param_hint='input')
+
+ meta = out.meta.copy()
+
+ result = rasterize(
+ geometries,
+ out_shape=(meta['height'], meta['width']),
+ transform=meta.get('affine', meta['transform']),
+ all_touched=all_touched,
+ dtype=meta.get('dtype', None),
+ default_value=default_value,
+ fill = fill)
+
+ for bidx in range(1, meta['count'] + 1):
+ data = out.read_band(bidx, masked=True)
+ # Burn in any non-fill pixels, and update mask accordingly
+ ne = result != fill
+ data[ne] = result[ne]
+ data.mask[ne] = False
+ out.write_band(bidx, data)
+
+ else:
+ if like is not None:
+ template_ds = rasterio.open(like)
+ if disjoint_bounds(geojson_bounds, template_ds.bounds):
+ raise click.BadParameter('GeoJSON outside bounds of '
+ '--like raster', param=input,
+ param_hint='input')
+
+ kwargs = template_ds.meta.copy()
+ kwargs['count'] = 1
+ template_ds.close()
+
+ else:
+ bounds = bounds or geojson_bounds
+
+ if src_crs == 'EPSG:4326':
+ if (bounds[0] < -180 or bounds[2] > 180 or
+ bounds[1] < -80 or bounds[3] > 80):
+ raise click.BadParameter(
+ 'Bounds are beyond the valid extent for EPSG:4326.',
+ ctx, param=bounds, param_hint='--bounds')
+
+ if dimensions:
+ width, height = dimensions
+ res = (
+ (bounds[2] - bounds[0]) / float(width),
+ (bounds[3] - bounds[1]) / float(height)
+ )
+
+ else:
+ if not res:
+ raise click.BadParameter(
+ 'pixel dimensions are required',
+ ctx, param=res, param_hint='--res')
+ elif len(res) == 1:
+ res = (res[0], res[0])
+
+ width = max(int(ceil((bounds[2] - bounds[0]) /
+ float(res[0]))), 1)
+ height = max(int(ceil((bounds[3] - bounds[1]) /
+ float(res[1]))), 1)
+
+ src_crs = src_crs.upper()
+ if not src_crs.count('EPSG:'):
+ raise click.BadParameter(
+ 'invalid CRS. Must be an EPSG code.',
+ ctx, param=src_crs, param_hint='--src_crs')
+
+ kwargs = {
+ 'count': 1,
+ 'crs': src_crs,
+ 'width': width,
+ 'height': height,
+ 'transform': Affine(res[0], 0, bounds[0], 0, -res[1],
+ bounds[3]),
+ 'driver': driver
+ }
+
+ result = rasterize(
+ geometries,
+ out_shape=(kwargs['height'], kwargs['width']),
+ transform=kwargs.get('affine', kwargs['transform']),
+ all_touched=all_touched,
+ dtype=kwargs.get('dtype', None),
+ default_value=default_value,
+ fill = fill)
+
+ if 'dtype' not in kwargs:
+ kwargs['dtype'] = result.dtype
+
+ kwargs['nodata'] = fill
+
+ with rasterio.open(output, 'w', **kwargs) as out:
+ out.write_band(1, result)
diff --git a/rasterio/rio/info.py b/rasterio/rio/info.py
index 9c34a9d..427f672 100644
--- a/rasterio/rio/info.py
+++ b/rasterio/rio/info.py
@@ -61,18 +61,29 @@ def env(ctx, key):
@click.option('--bounds', 'meta_member', flag_value='bounds',
help="Print the boundary coordinates "
"(left, bottom, right, top).")
+ at click.option('--res', 'meta_member', flag_value='res',
+ help="Print pixel width and height.")
+ at click.option('--lnglat', 'meta_member', flag_value='lnglat',
+ help="Print longitude and latitude at center.")
+ at click.option('--stats', 'meta_member', flag_value='stats',
+ help="Print statistics (min, max, mean) of a single band "
+ "(use --bidx).")
+ at click.option('-v', '--tell-me-more', '--verbose', is_flag=True,
+ help="Output extra information.")
+ at click.option('--bidx', type=int, default=1,
+ help="Input file band index (default: 1).")
@click.pass_context
-def info(ctx, input, aspect, indent, namespace, meta_member):
+def info(ctx, input, aspect, indent, namespace, meta_member, verbose, bidx):
"""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')
+ mode = 'r' if (verbose or meta_member == 'stats') else 'r-'
try:
with rasterio.drivers(CPL_DEBUG=(verbosity > 2)):
- with rasterio.open(input, 'r-') as src:
+ with rasterio.open(input, mode) as src:
info = src.meta
del info['affine']
del info['transform']
@@ -82,19 +93,30 @@ def info(ctx, input, aspect, indent, namespace, meta_member):
if proj4.startswith('+init=epsg'):
proj4 = proj4.split('=')[1].upper()
info['crs'] = proj4
+ info['res'] = src.res
+ info['lnglat'] = src.lnglat()
+ if verbose:
+ stats = [{'min': float(b.min()),
+ 'max': float(b.max()),
+ 'mean': float(b.mean())} for b in src.read()]
+ info['stats'] = stats
if aspect == 'meta':
- if meta_member:
+ if meta_member == 'stats':
+ band = src.read(bidx)
+ click.echo('%f %f %f' % (
+ float(band.min()),
+ float(band.max()),
+ float(band.mean())))
+ elif meta_member:
if isinstance(info[meta_member], (list, tuple)):
- print(" ".join(map(str, info[meta_member])))
+ click.echo(" ".join(map(str, info[meta_member])))
else:
- print(info[meta_member])
+ click.echo(info[meta_member])
else:
- stdout.write(json.dumps(info, indent=indent))
- stdout.write("\n")
+ click.echo(json.dumps(info, indent=indent))
elif aspect == 'tags':
- stdout.write(json.dumps(src.tags(ns=namespace),
+ click.echo(json.dumps(src.tags(ns=namespace),
indent=indent))
- stdout.write("\n")
sys.exit(0)
except Exception:
logger.exception("Failed. Exception caught")
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
index 1bccb93..bd17c85 100644
--- a/rasterio/rio/main.py
+++ b/rasterio/rio/main.py
@@ -2,7 +2,8 @@
from rasterio.rio.cli import cli
from rasterio.rio.bands import stack
-from rasterio.rio.features import shapes
+from rasterio.rio.features import shapes, rasterize
from rasterio.rio.info import env, info
from rasterio.rio.merge import merge
from rasterio.rio.rio import bounds, insp, transform
+from rasterio.rio.sample import sample
diff --git a/rasterio/rio/sample.py b/rasterio/rio/sample.py
new file mode 100644
index 0000000..fd4684a
--- /dev/null
+++ b/rasterio/rio/sample.py
@@ -0,0 +1,94 @@
+import json
+import logging
+import sys
+import warnings
+
+import click
+
+import rasterio
+from rasterio.rio.cli import cli
+
+
+warnings.simplefilter('default')
+
+
+ at cli.command(short_help="Sample a dataset.")
+ at click.argument('files', nargs=-1, required=True, metavar='FILE "[x, y]"')
+ at click.option('--bidx', default=None, help="Indexes of input file bands.")
+ at click.pass_context
+def sample(ctx, files, bidx):
+ """Sample a dataset at one or more points
+
+ Sampling points (x, y) encoded as JSON arrays, in the coordinate
+ reference system of the dataset, are read from the second
+ positional argument or stdin. Values of the dataset's bands
+ are also encoded as JSON arrays and are written to stdout.
+
+ Example:
+
+ \b
+ $ cat << EOF | rio sample tests/data/RGB.byte.tif
+ > [220650, 2719200]
+ > [219650, 2718200]
+ > EOF
+ [28, 29, 27]
+ [25, 29, 19]
+
+ By default, rio-sample will sample all bands. Optionally, bands
+ may be specified using a simple syntax:
+
+ --bidx N samples the Nth band (first band is 1).
+
+ --bidx M,N,0 samples bands M, N, and O.
+
+ --bidx M..O samples bands M-O, inclusive.
+
+ --bidx ..N samples all bands up to and including N.
+
+ --bidx N.. samples all bands from N to the end.
+
+ Example:
+
+ \b
+ $ cat << EOF | rio sample tests/data/RGB.byte.tif --bidx ..2
+ > [220650, 2719200]
+ > [219650, 2718200]
+ > EOF
+ [28, 29]
+ [25, 29]
+
+ """
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('rio')
+
+ files = list(files)
+ source = files.pop(0)
+ input = files.pop(0) if files else '-'
+
+ # Handle the case of file, stream, or string input.
+ try:
+ points = click.open_file(input).readlines()
+ except IOError:
+ points = [input]
+
+ try:
+ with rasterio.drivers(CPL_DEBUG=verbosity>2):
+ with rasterio.open(source) as src:
+ if bidx is None:
+ indexes = src.indexes
+ elif '..' in bidx:
+ start, stop = map(
+ lambda x: int(x) if x else None, bidx.split('..'))
+ if start is None:
+ start = 1
+ indexes = src.indexes[slice(start-1, stop)]
+ else:
+ indexes = list(map(int, bidx.split(',')))
+ for vals in src.sample(
+ (json.loads(line) for line in points),
+ indexes=indexes):
+ click.echo(json.dumps(vals.tolist()))
+ sys.exit(0)
+ except Exception:
+ logger.exception("Failed. Exception caught")
+ sys.exit(1)
diff --git a/rasterio/warp.py b/rasterio/warp.py
index 25fd5a7..dd971d8 100644
--- a/rasterio/warp.py
+++ b/rasterio/warp.py
@@ -1,6 +1,7 @@
"""Raster warping and reprojection"""
-from rasterio._warp import _reproject, _transform, _transform_geom, RESAMPLING
+from rasterio._base import _transform
+from rasterio._warp import _transform_geom, _reproject, RESAMPLING
from rasterio.transform import guard_transform
diff --git a/requirements-dev.txt b/requirements-dev.txt
index fb826ea..4d9ff53 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -6,5 +6,5 @@ delocate
enum34
numpy>=1.8.0
pytest
-setuptools
+setuptools>=0.9.8
wheel
diff --git a/setup.py b/setup.py
index cd491cf..1788e52 100755
--- a/setup.py
+++ b/setup.py
@@ -11,16 +11,21 @@
import logging
import os
+import pprint
import shutil
import subprocess
import sys
-from setuptools import setup
-from distutils.extension import Extension
+from setuptools import setup
+from setuptools.extension import Extension
logging.basicConfig()
log = logging.getLogger()
+# python -W all setup.py ...
+if 'all' in sys.warnoptions:
+ log.level = logging.DEBUG
+
# Parse the version from the fiona module.
with open('rasterio/__init__.py') as f:
for line in f:
@@ -87,10 +92,9 @@ try:
except Exception as e:
log.warning("Failed to get options via gdal-config: %s", str(e))
-# Conditionally copy PROJ.4 data. Presumes PROJ.4 is installed locally
-# with --prefix=/usr/local.
+# Conditionally copy PROJ.4 data.
if os.environ.get('PACKAGE_DATA'):
- projdatadir = '/usr/local/share/proj'
+ projdatadir = os.environ.get('PROJ_LIB', '/usr/local/share/proj')
if os.path.exists(projdatadir):
try:
shutil.rmtree('rasterio/proj_data')
@@ -104,6 +108,8 @@ ext_options = dict(
libraries=libraries,
extra_link_args=extra_link_args)
+log.debug('ext_options:\n%s', pprint.pformat(ext_options))
+
# 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...")
@@ -126,10 +132,12 @@ if os.path.exists("MANIFEST.in") and "clean" not in sys.argv:
Extension(
'rasterio._warp', ['rasterio/_warp.pyx'], **ext_options),
Extension(
+ 'rasterio._fill', ['rasterio/_fill.pyx', 'rasterio/rasterfill.cpp'], **ext_options),
+ Extension(
'rasterio._err', ['rasterio/_err.pyx'], **ext_options),
Extension(
'rasterio._example', ['rasterio/_example.pyx'], **ext_options),
- ])
+ ], quiet=True)
# If there's no manifest template, as in an sdist, we just specify .c files.
else:
@@ -147,6 +155,8 @@ else:
Extension(
'rasterio._warp', ['rasterio/_warp.cpp'], **ext_options),
Extension(
+ 'rasterio._fill', ['rasterio/_fill.cpp', 'rasterio/rasterfill.cpp'], **ext_options),
+ Extension(
'rasterio._err', ['rasterio/_err.c'], **ext_options),
Extension(
'rasterio._example', ['rasterio/_example.c'], **ext_options),
diff --git a/tests/conftest.py b/tests/conftest.py
index e55a25e..c5a9933 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,6 +4,8 @@ import os
import sys
import pytest
+from click.testing import CliRunner
+
if sys.version_info > (3,):
reduce = functools.reduce
@@ -11,6 +13,7 @@ if sys.version_info > (3,):
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.
@@ -19,3 +22,8 @@ def pytest_cmdline_main(config):
else:
print("Test data not present. See download directions in tests/README.txt")
sys.exit(1)
+
+
+ at pytest.fixture(scope='function')
+def runner():
+ return CliRunner()
diff --git a/tests/test_features_bounds.py b/tests/test_features_bounds.py
new file mode 100644
index 0000000..a7015cd
--- /dev/null
+++ b/tests/test_features_bounds.py
@@ -0,0 +1,65 @@
+from rasterio.features import bounds
+
+
+# Tests copied from Fiona 1.4.1
+
+def test_bounds_point():
+ g = {'type': 'Point', 'coordinates': [10, 10]}
+ assert bounds(g) == (10, 10, 10, 10)
+
+
+def test_bounds_line():
+ g = {'type': 'LineString', 'coordinates': [[0, 0], [10, 10]]}
+ assert bounds(g) == (0, 0, 10, 10)
+
+
+def test_bounds_polygon():
+ g = {'type': 'Polygon', 'coordinates': [[[0, 0], [10, 10], [10, 0]]]}
+ assert bounds(g) == (0, 0, 10, 10)
+
+
+def test_bounds_z():
+ g = {'type': 'Point', 'coordinates': [10, 10, 10]}
+ assert bounds(g) == (10, 10, 10, 10)
+
+
+# TODO: add these to Fiona with update to bounds
+def test_bounds_existing_bbox():
+ """ Test with existing bbox in geojson, similar to that produced by
+ rasterio. Values specifically modified here for testing, bboxes are not
+ valid as written.
+ """
+
+ fc = {
+ 'bbox': [-107, 40, -105, 41],
+ 'features': [{
+ 'bbox': [-107, 40, -104, 42],
+ 'geometry': {
+ 'coordinates': [
+ [[-107, 40], [-106, 40], [-106, 41], [-107, 41], [-107, 40]]
+ ],
+ 'type': 'Polygon'
+ },
+ 'type': 'Feature'
+ }],
+ 'type': 'FeatureCollection'
+ }
+ assert bounds(fc['features'][0]) == (-107, 40, -104, 42)
+ assert bounds(fc) == (-107, 40, -105, 41)
+
+
+def test_feature_collection():
+ fc = {
+ 'features': [{
+ 'geometry': {
+ 'coordinates': [
+ [[-107, 40], [-106, 40], [-106, 41], [-107, 41], [-107, 40]]
+ ],
+ 'type': 'Polygon'
+ },
+ 'type': 'Feature'
+ }],
+ 'type': 'FeatureCollection'
+ }
+ assert bounds(fc['features'][0]) == (-107, 40, -106, 41)
+ assert bounds(fc) == (-107, 40, -106, 41)
diff --git a/tests/test_fillnodata.py b/tests/test_fillnodata.py
new file mode 100644
index 0000000..2ccee1e
--- /dev/null
+++ b/tests/test_fillnodata.py
@@ -0,0 +1,45 @@
+import logging
+import sys
+import numpy
+import pytest
+
+import rasterio
+from rasterio.fill import fillnodata
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+def test_fillnodata():
+ """Test filling nodata values in an ndarray"""
+ # create a 5x5 array, with some missing data
+ a = numpy.ones([3, 3]) * 42
+ a[1][1] = 0
+ # find the missing data
+ mask = ~(a == 0)
+ # fill the missing data using interpolation from the edges
+ result = fillnodata(a, mask)
+ assert(numpy.all((numpy.ones([3, 3]) * 42) == result))
+
+def test_fillnodata_invalid_types():
+ a = numpy.ones([3, 3])
+ with pytest.raises(ValueError):
+ fillnodata(None, a)
+ with pytest.raises(ValueError):
+ fillnodata(a, 42)
+
+def test_fillnodata_mask_ones():
+ # when mask is all ones, image should be unmodified
+ a = numpy.ones([3, 3]) * 42
+ a[1][1] = 0
+ mask = numpy.ones([3, 3])
+ result = fillnodata(a, mask)
+ assert(numpy.all(a == result))
+
+'''
+def test_fillnodata_smooth():
+ a = numpy.array([[1,3,3,1],[2,0,0,2],[2,0,0,2],[1,3,3,1]], dtype=numpy.float64)
+ mask = ~(a == 0)
+ result = fillnodata(a, mask, max_search_distance=1, smoothing_iterations=0)
+ assert(result[1][1] == 3)
+ result = fillnodata(a, mask, max_search_distance=1, smoothing_iterations=1)
+ assert(round(result[1][1], 1) == 2.2)
+'''
diff --git a/tests/test_rio_features.py b/tests/test_rio_features.py
index c886515..e524cd7 100644
--- a/tests/test_rio_features.py
+++ b/tests/test_rio_features.py
@@ -1,14 +1,81 @@
import logging
+import os
import re
import sys
import click
from click.testing import CliRunner
+import rasterio
from rasterio.rio import features
-logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+# logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+TEST_FEATURES = """{
+ "geometry": {
+ "coordinates": [
+ [
+ [-110, 40],
+ [-100, 40],
+ [-100, 45],
+ [-105, 45],
+ [-110, 40]
+ ]
+ ],
+ "type": "Polygon"
+ },
+ "properties": {
+ "val": 15
+ },
+ "type": "Feature"
+}"""
+
+
+# > rio shapes tests/data/shade.tif --mask --sampling 500 --projected --precision 0
+TEST_MERC_FEATURECOLLECTION = """{
+ "bbox": [-11858135.0, 4803914.0, -11848351.0, 4813698.0],
+ "features": [{
+ "bbox": [-11853357.504145855, 4808920.97837715,
+ -11848580.189878704, 4813698.2926443005],
+ "geometry": {
+ "coordinates": [
+ [
+ [-11853357.504145855, 4813698.2926443005],
+ [-11853357.504145855, 4808920.97837715],
+ [-11848580.189878704, 4808920.97837715],
+ [-11848580.189878704, 4813698.2926443005],
+ [-11853357.504145855, 4813698.2926443005]
+ ]
+ ],
+ "type": "Polygon"
+ },
+ "properties": {
+ "val": 2
+ },
+ "type": "Feature"
+ }, {
+ "bbox": [-11858134.818413004, 4804143.66411,
+ -11853357.504145855, 4808920.97837715],
+ "geometry": {
+ "coordinates": [
+ [
+ [-11858134.818413004, 4808920.97837715],
+ [-11858134.818413004, 4804143.66411],
+ [-11853357.504145855, 4804143.66411],
+ [-11853357.504145855, 4808920.97837715],
+ [-11858134.818413004, 4808920.97837715]
+ ]
+ ],
+ "type": "Polygon"
+ },
+ "properties": {
+ "val": 3
+ },
+ "type": "Feature"
+ }],
+ "type": "FeatureCollection"
+}"""
def test_err():
@@ -18,24 +85,21 @@ def test_err():
assert result.exit_code == 1
-def test_shapes():
- runner = CliRunner()
+def test_shapes(runner):
result = runner.invoke(features.shapes, ['tests/data/shade.tif'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 232
-def test_shapes_sequence():
- runner = CliRunner()
+def test_shapes_sequence(runner):
result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--sequence'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 0
assert result.output.count('"Feature"') == 232
-def test_shapes_sequence_rs():
- runner = CliRunner()
+def test_shapes_sequence_rs(runner):
result = runner.invoke(
features.shapes, [
'tests/data/shade.tif',
@@ -47,24 +111,21 @@ def test_shapes_sequence_rs():
assert result.output.count(u'\u001e') == 232
-def test_shapes_with_nodata():
- runner = CliRunner()
+def test_shapes_with_nodata(runner):
result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--with-nodata'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 288
-def test_shapes_indent():
- runner = CliRunner()
+def test_shapes_indent(runner):
result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--indent', '2'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('\n') == 70139
-def test_shapes_compact():
- runner = CliRunner()
+def test_shapes_compact(runner):
result = runner.invoke(features.shapes, ['tests/data/shade.tif', '--compact'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
@@ -72,8 +133,7 @@ def test_shapes_compact():
assert result.output.count(': ') == 0
-def test_shapes_sampling():
- runner = CliRunner()
+def test_shapes_sampling(runner):
result = runner.invoke(
features.shapes, ['tests/data/shade.tif', '--sampling', '10'])
assert result.exit_code == 0
@@ -81,8 +141,7 @@ def test_shapes_sampling():
assert result.output.count('"Feature"') == 124
-def test_shapes_precision():
- runner = CliRunner()
+def test_shapes_precision(runner):
result = runner.invoke(
features.shapes, ['tests/data/shade.tif', '--precision', '1'])
assert result.exit_code == 0
@@ -91,9 +150,194 @@ def test_shapes_precision():
assert re.search(r'\d*\.\d{2,}', result.output) is None
-def test_shapes_mask():
- runner = CliRunner()
+def test_shapes_mask(runner):
result = runner.invoke(features.shapes, ['tests/data/RGB.byte.tif', '--mask'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 9
+
+
+def test_rasterize_err(tmpdir, runner):
+ output = str(tmpdir.join('test.tif'))
+ # Test invalid stdin
+ result = runner.invoke(features.rasterize, [output], input='BOGUS')
+ assert result.exit_code == -1
+
+ # Test invalid GeoJSON
+ result = runner.invoke(features.rasterize, [output],
+ input='{"foo": "bar"}')
+ assert result.exit_code == 2
+
+ # Test invalid res
+ result = runner.invoke(features.rasterize, [output], input=TEST_FEATURES)
+ assert result.exit_code == 2
+
+ # Test invalid CRS for bounds
+ result = runner.invoke(features.rasterize, [output, '--res', 1],
+ input=TEST_MERC_FEATURECOLLECTION)
+ assert result.exit_code == 2
+
+ # Test invalid CRS value
+ result = runner.invoke(features.rasterize, [output,
+ '--res', 1,
+ '--src_crs', 'BOGUS'],
+ input=TEST_MERC_FEATURECOLLECTION)
+ assert result.exit_code == 2
+
+
+def test_rasterize(tmpdir, runner):
+ # Test dimensions
+ output = str(tmpdir.join('test.tif'))
+ result = runner.invoke(features.rasterize,
+ [output, '--dimensions', 20, 10],
+ input=TEST_FEATURES)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+ with rasterio.open(output) as out:
+ assert out.count == 1
+ assert out.meta['width'] == 20
+ assert out.meta['height'] == 10
+ data = out.read_band(1, masked=False)
+ assert (data == 0).sum() == 55
+ assert (data == 1).sum() == 145
+
+ # Test dimensions and bounds
+ output = str(tmpdir.join('test2.tif'))
+ result = runner.invoke(features.rasterize,
+ [output,
+ '--dimensions', 40, 20,
+ '--bounds', -120, 30, -90, 50
+ ], input=TEST_FEATURES)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+ with rasterio.open(output) as out:
+ assert out.count == 1
+ assert out.meta['width'] == 40
+ assert out.meta['height'] == 20
+ data = out.read_band(1, masked=False)
+ assert (data == 0).sum() == 748
+ assert (data == 1).sum() == 52
+
+ # Test resolution
+ output = str(tmpdir.join('test3.tif'))
+ result = runner.invoke(features.rasterize,
+ [output, '--res', 0.5], input=TEST_FEATURES)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+ with rasterio.open(output) as out:
+ assert out.count == 1
+ assert out.meta['width'] == 20
+ assert out.meta['height'] == 10
+ data = out.read_band(1, masked=False)
+ assert (data == 0).sum() == 55
+ assert (data == 1).sum() == 145
+
+
+def test_rasterize_existing_output(tmpdir, runner):
+ output = str(tmpdir.join('test.tif'))
+ result = runner.invoke(features.rasterize,
+ [output, '--res', 0.5], input=TEST_FEATURES)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+
+ geojson = """{
+ "geometry": {
+ "coordinates": [
+ [
+ [-102, 40],
+ [-98, 40],
+ [-98, 45],
+ [-100, 45],
+ [-102, 40]
+ ]
+ ],
+ "type": "Polygon"
+ },
+ "type": "Feature"
+ }"""
+
+ result = runner.invoke(features.rasterize, [output, '--default_value', 2],
+ input=geojson)
+
+ with rasterio.open(output) as out:
+ assert out.count == 1
+ data = out.read_band(1, masked=False)
+ assert (data == 0).sum() == 55
+ assert (data == 1).sum() == 125
+ assert (data == 2).sum() == 20
+
+
+def test_rasterize_like(tmpdir, runner):
+ output = str(tmpdir.join('test.tif'))
+ result = runner.invoke(features.rasterize,
+ [output, '--like', 'tests/data/shade.tif'],
+ input=TEST_MERC_FEATURECOLLECTION)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+ with rasterio.open(output) as out:
+ assert out.count == 1
+ data = out.read_band(1, masked=False)
+ assert (data == 0).sum() == 548576
+ assert (data == 1).sum() == 500000
+
+ # Test invalid like raster
+ output = str(tmpdir.join('test2.tif'))
+ result = runner.invoke(features.rasterize,
+ [output, '--like', 'foo.tif'], input=TEST_FEATURES)
+ assert result.exit_code == 2
+
+
+def test_rasterize_property_value(tmpdir, runner):
+ # Test feature collection property values
+ output = str(tmpdir.join('test.tif'))
+ result = runner.invoke(features.rasterize,
+ [output,
+ '--res', 1000,
+ '--property', 'val',
+ '--src_crs', 'EPSG:3857'
+ ],
+ input=TEST_MERC_FEATURECOLLECTION)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+ with rasterio.open(output) as out:
+ assert out.count == 1
+ data = out.read_band(1, masked=False)
+ assert (data == 0).sum() == 50
+ assert (data == 2).sum() == 25
+ assert (data == 3).sum() == 25
+
+ # Test feature property values
+ output = str(tmpdir.join('test2.tif'))
+ result = runner.invoke(features.rasterize,
+ [output, '--res', 0.5, '--property', 'val'],
+ input=TEST_FEATURES)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+ with rasterio.open(output) as out:
+ assert out.count == 1
+ data = out.read_band(1, masked=False)
+ assert (data == 0).sum() == 55
+ assert (data == 15).sum() == 145
+
+def test_rasterize_out_of_bounds(tmpdir, runner):
+ output = str(tmpdir.join('test.tif'))
+
+ # Test out of bounds of --like raster
+ result = runner.invoke(features.rasterize,
+ [output, '--like', 'tests/data/shade.tif'],
+ input=TEST_FEATURES)
+ assert result.exit_code == 2
+
+ # Test out of bounds of existing output raster (first have to create one)
+ result = runner.invoke(features.rasterize,
+ [output,
+ '--res', 1000,
+ '--property', 'val',
+ '--src_crs', 'EPSG:3857'
+ ],
+ input=TEST_MERC_FEATURECOLLECTION)
+ assert result.exit_code == 0
+ assert os.path.exists(output)
+
+ result = runner.invoke(features.rasterize, [output], input=TEST_FEATURES)
+ assert result.exit_code == 2
diff --git a/tests/test_rio_info.py b/tests/test_rio_info.py
index fce04eb..0dd5c65 100644
--- a/tests/test_rio_info.py
+++ b/tests/test_rio_info.py
@@ -71,3 +71,45 @@ def test_info_tags():
['tests/data/RGB.byte.tif', '--tags'])
assert result.exit_code == 0
assert result.output == '{"AREA_OR_POINT": "Area"}\n'
+
+
+def test_info_res():
+ runner = CliRunner()
+ result = runner.invoke(
+ info.info,
+ ['tests/data/RGB.byte.tif', '--res'])
+ assert result.exit_code == 0
+ assert result.output.startswith('300.037')
+
+
+def test_info_lnglat():
+ runner = CliRunner()
+ result = runner.invoke(
+ info.info,
+ ['tests/data/RGB.byte.tif', '--lnglat'])
+ assert result.exit_code == 0
+ assert result.output.startswith('-77.757')
+
+
+def test_mo_info():
+ runner = CliRunner()
+ result = runner.invoke(info.info, ['tests/data/RGB.byte.tif'])
+ assert result.exit_code == 0
+ assert '"res": [300.037' in result.output
+ assert '"lnglat": [-77.757' in result.output
+
+
+def test_info_stats():
+ runner = CliRunner()
+ result = runner.invoke(info.info, ['tests/data/RGB.byte.tif', '--tell-me-more'])
+ assert result.exit_code == 0
+ assert '"max": 255.0' in result.output
+ assert '"min": 1.0' in result.output
+ assert '"mean": 44.4344' in result.output
+
+
+def test_info_stats_only():
+ runner = CliRunner()
+ result = runner.invoke(info.info, ['tests/data/RGB.byte.tif', '--stats', '--bidx', '2'])
+ assert result.exit_code == 0
+ assert result.output.startswith('1.000000 255.000000 66.02')
diff --git a/tests/test_rio_sample.py b/tests/test_rio_sample.py
new file mode 100644
index 0000000..2c6a329
--- /dev/null
+++ b/tests/test_rio_sample.py
@@ -0,0 +1,81 @@
+import logging
+import sys
+
+import click
+from click.testing import CliRunner
+
+import rasterio
+from rasterio.rio import sample
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+
+def test_sample_err():
+ runner = CliRunner()
+ result = runner.invoke(
+ sample.sample,
+ ['bogus.tif'],
+ "[220650.0, 2719200.0]")
+ assert result.exit_code == 1
+
+
+def test_sample_stdin():
+ runner = CliRunner()
+ result = runner.invoke(
+ sample.sample,
+ ['tests/data/RGB.byte.tif'],
+ "[220650.0, 2719200.0]\n[220650.0, 2719200.0]",
+ catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == '[28, 29, 27]\n[28, 29, 27]'
+
+
+def test_sample_arg():
+ runner = CliRunner()
+ result = runner.invoke(
+ sample.sample,
+ ['tests/data/RGB.byte.tif', "[220650.0, 2719200.0]"],
+ catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == '[28, 29, 27]'
+
+
+def test_sample_bidx():
+ runner = CliRunner()
+ result = runner.invoke(
+ sample.sample,
+ ['tests/data/RGB.byte.tif', '--bidx', '1,2', "[220650.0, 2719200.0]"],
+ catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == '[28, 29]'
+
+
+def test_sample_bidx2():
+ runner = CliRunner()
+ result = runner.invoke(
+ sample.sample,
+ ['tests/data/RGB.byte.tif', '--bidx', '1..2', "[220650.0, 2719200.0]"],
+ catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == '[28, 29]'
+
+
+def test_sample_bidx3():
+ runner = CliRunner()
+ result = runner.invoke(
+ sample.sample,
+ ['tests/data/RGB.byte.tif', '--bidx', '..2', "[220650.0, 2719200.0]"],
+ catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == '[28, 29]'
+
+
+def test_sample_bidx4():
+ runner = CliRunner()
+ result = runner.invoke(
+ sample.sample,
+ ['tests/data/RGB.byte.tif', '--bidx', '3', "[220650.0, 2719200.0]"],
+ catch_exceptions=False)
+ assert result.exit_code == 0
+ assert result.output.strip() == '[27]'
diff --git a/tests/test_sampling.py b/tests/test_sampling.py
new file mode 100644
index 0000000..c4030cc
--- /dev/null
+++ b/tests/test_sampling.py
@@ -0,0 +1,17 @@
+import rasterio
+
+
+def test_sampling():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ data = next(src.sample([(220650.0, 2719200.0)]))
+ assert list(data) == [28, 29, 27]
+
+def test_sampling_beyond_bounds():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ data = next(src.sample([(-10, 2719200.0)]))
+ assert list(data) == [0, 0, 0]
+
+def test_sampling_indexes():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ data = next(src.sample([(220650.0, 2719200.0)], indexes=[2]))
+ assert list(data) == [29]
diff --git a/tests/test_tags.py b/tests/test_tags.py
index 4431055..514d068 100644
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -54,3 +54,11 @@ def test_tags_update_twice():
assert dst.tags() == {'a': '1', 'b': '2'}
dst.update_tags(c=3)
assert dst.tags() == {'a': '1', 'b': '2', 'c': '3'}
+
+
+def test_tags_eq():
+ with rasterio.open(
+ 'test.tif', 'w',
+ 'GTiff', 3, 4, 1, dtype=rasterio.ubyte) as dst:
+ dst.update_tags(a="foo=bar")
+ assert dst.tags() == {'a': "foo=bar"}
diff --git a/tests/test_transform.py b/tests/test_transform.py
index b17feab..acd0d54 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -1,4 +1,3 @@
-
import rasterio
def test_window():
@@ -9,3 +8,16 @@ def test_window():
assert src.window(left, top-src.res[1], left+src.res[0], top) == (
(0, 1), (0, 1))
+
+def test_window_transform():
+ with rasterio.open('tests/data/RGB.byte.tif') as src:
+ assert src.window_transform(((0, None), (0, None))) == src.affine
+ assert src.window_transform(((None, None), (None, None))) == src.affine
+ assert src.window_transform(
+ ((1, None), (1, None))).c == src.bounds.left + src.res[0]
+ assert src.window_transform(
+ ((1, None), (1, None))).f == src.bounds.top - src.res[1]
+ assert src.window_transform(
+ ((-1, None), (-1, None))).c == src.bounds.left - src.res[0]
+ assert src.window_transform(
+ ((-1, None), (-1, None))).f == src.bounds.top + src.res[1]
--
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