diff --git a/CHANGES.txt b/CHANGES.txt index 64897efb..118dbbe2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,9 +1,12 @@ Changes ======= -1.1.1 (2019-11-12) +1.1.1 (2019-11-13) ------------------ +- Calling a dataset's sample method with coordinates outside the extent of a + dataset with a nodata value of None has raised a TypeError (#1822). Now, it + gives the values we would get from a boundless read of the dataset's values. - Use new set_proj_search_path() function to set the PROJ data search path. For GDAL versions before 3.0 this sets the PROJ_LIB environment variable. For GDAL version 3.0 this calls OSRSetPROJSearchPaths(), which overrides diff --git a/rasterio/_io.pyx b/rasterio/_io.pyx index 994e0f32..a60522f3 100644 --- a/rasterio/_io.pyx +++ b/rasterio/_io.pyx @@ -762,30 +762,35 @@ cdef class DatasetReaderBase(DatasetBase): mask = mask | self.read_masks(i, **kwargs) return mask - def sample(self, xy, indexes=None): + def sample(self, xy, indexes=None, masked=False): """Get the values of a dataset at certain positions Values are from the nearest pixel. They are not interpolated. 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. + xy : iterable + Pairs of x, y coordinates (floats) in the dataset's + reference system. + indexes : int or list of int + Indexes of dataset bands to sample. + masked : bool, default: False + Whether to mask samples that fall outside the extent of the + dataset. Returns - ------- - Iterable, yielding dataset values for the specified `indexes` - as an ndarray. + ------ + iterable + Arrays of length equal to the number of specified indexes + containing the dataset values for the bands corresponding to + those indexes. + """ # In https://github.com/mapbox/rasterio/issues/378 a user has # found what looks to be a Cython generator bug. Until that can # be confirmed and fixed, the workaround is a pure Python # generator implemented in sample.py. - return sample_gen(self, xy, indexes) + return sample_gen(self, xy, indexes=indexes, masked=masked) @contextmanager diff --git a/rasterio/sample.py b/rasterio/sample.py index e2ca734b..a4cce286 100644 --- a/rasterio/sample.py +++ b/rasterio/sample.py @@ -5,8 +5,29 @@ import numpy from rasterio.windows import Window -def sample_gen(dataset, xy, indexes=None): - """Generator for sampled pixels""" +def sample_gen(dataset, xy, indexes=None, masked=False): + """Sample pixels from a dataset + + Parameters + ---------- + dataset : rasterio Dataset + Opened in "r" mode. + xy : iterable + Pairs of x, y coordinates in the dataset's reference system. + indexes : int or list of int + Indexes of dataset bands to sample. + masked : bool, default: False + Whether to mask samples that fall outside the extent of the + dataset. + + Yields + ------ + array + A array of length equal to the number of specified indexes + containing the dataset values for the bands corresponding to + those indexes. + + """ index = dataset.index read = dataset.read @@ -15,9 +36,9 @@ def sample_gen(dataset, xy, indexes=None): for x, y in xy: row_off, col_off = index(x, y) - if row_off < 0 or col_off < 0: - yield numpy.ones((dataset.count,), dtype=dataset.dtypes[0]) * dataset.nodata - else: - window = Window(col_off, row_off, 1, 1) - data = read(indexes, window=window, masked=False) - yield data[:, 0, 0] +# if row_off < 0 or col_off < 0: +# yield numpy.ones((dataset.count,), dtype=dataset.dtypes[0]) * dataset.nodata +# else: + window = Window(col_off, row_off, 1, 1) + data = read(indexes, window=window, masked=masked, boundless=True) + yield data[:, 0, 0] diff --git a/tests/test_sampling.py b/tests/test_sampling.py index 47011974..17cee040 100644 --- a/tests/test_sampling.py +++ b/tests/test_sampling.py @@ -1,3 +1,5 @@ +import numpy + import rasterio @@ -13,6 +15,24 @@ def test_sampling_beyond_bounds(): assert list(data) == [0, 0, 0] +def test_sampling_beyond_bounds_no_nodata(): + with rasterio.open('tests/data/RGB2.byte.tif') as src: + data = next(src.sample([(-10, 2719200.0)])) + assert list(data) == [0, 0, 0] + + +def test_sampling_beyond_bounds_masked(): + with rasterio.open('tests/data/RGBA.byte.tif') as src: + data = next(src.sample([(-10, 2719200.0)], masked=True)) + assert list(data.mask) == [True, True, True, False] + + +def test_sampling_beyond_bounds_nan(): + with rasterio.open('tests/data/float_nan.tif') as src: + data = next(src.sample([(-10, 0.0)])) + assert numpy.isnan(data) + + def test_sampling_indexes(): with rasterio.open('tests/data/RGB.byte.tif') as src: data = next(src.sample([(220650.0, 2719200.0)], indexes=[2]))