From 1a8c6f1da97221a2c742fe837d3b297791386140 Mon Sep 17 00:00:00 2001 From: gbrener Date: Thu, 29 Jun 2017 17:37:47 -0500 Subject: [PATCH 1/4] Center the coordinates for rasterio backend Rasterio uses edge-based coordinates, which contradict the treatment of coordinates in xarray as being centered over the pixels. This centers them with an offset of half the resolution. --- xarray/backends/rasterio_.py | 8 ++++++-- xarray/tests/test_backends.py | 31 ++++++++++++++++++------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/xarray/backends/rasterio_.py b/xarray/backends/rasterio_.py index bb50a6b0b5e..988e3a3d064 100644 --- a/xarray/backends/rasterio_.py +++ b/xarray/backends/rasterio_.py @@ -132,8 +132,12 @@ def open_rasterio(filename, chunks=None, cache=None, lock=None): dx, dy = riods.res[0], -riods.res[1] x0 = riods.bounds.right if dx < 0 else riods.bounds.left y0 = riods.bounds.top if dy < 0 else riods.bounds.bottom - coords['y'] = np.linspace(start=y0, num=ny, stop=(y0 + (ny - 1) * dy)) - coords['x'] = np.linspace(start=x0, num=nx, stop=(x0 + (nx - 1) * dx)) + coords['y'] = np.linspace(start=y0 + dy/2, + num=ny, + stop=(y0 + (ny - 1) * dy) + dy/2) + coords['x'] = np.linspace(start=x0+dx/2, + num=nx, + stop=(x0 + (nx - 1) * dx) + dx/2) # Attributes attrs = {} diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index dacb191584c..883a56b81ab 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -1443,7 +1443,6 @@ class TestPyNioAutocloseTrue(TestPyNio): class TestRasterio(TestCase): def test_serialization_utm(self): - import rasterio from rasterio.transform import from_origin @@ -1462,13 +1461,15 @@ def test_serialization_utm(self): transform=transform, dtype=rasterio.float32) as s: s.write(data) + dx, dy = s.res[0], -s.res[1] # Tests expected = DataArray(data, dims=('band', 'y', 'x'), - coords={'band': [1, 2, 3], - 'y': -np.arange(ny) * 2000 + 80000, - 'x': np.arange(nx) * 1000 + 5000, - }) + coords={ + 'band': [1, 2, 3], + 'y': -np.arange(ny) * 2000 + 80000 + dy/2, + 'x': np.arange(nx) * 1000 + 5000 + dx/2, + }) with xr.open_rasterio(tmp_file) as rioda: assert_allclose(rioda, expected) assert 'crs' in rioda.attrs @@ -1498,13 +1499,14 @@ def test_serialization_platecarree(self): transform=transform, dtype=rasterio.float32) as s: s.write(data, indexes=1) + dx, dy = s.res[0], -s.res[1] # Tests expected = DataArray(data[np.newaxis, ...], dims=('band', 'y', 'x'), coords={'band': [1], - 'y': -np.arange(ny)*2 + 2, - 'x': np.arange(nx)*0.5 + 1, + 'y': -np.arange(ny)*2 + 2 + dy/2, + 'x': np.arange(nx)*0.5 + 1 + dx/2, }) with xr.open_rasterio(tmp_file) as rioda: assert_allclose(rioda, expected) @@ -1536,11 +1538,12 @@ def test_indexing(self): transform=transform, dtype=rasterio.float32) as s: s.write(data) + dx, dy = s.res[0], -s.res[1] # ref expected = DataArray(data, dims=('band', 'y', 'x'), - coords={'x': np.arange(nx)*0.5 + 1, - 'y': -np.arange(ny)*2 + 2, + coords={'x': (np.arange(nx)*0.5 + 1) + dx/2, + 'y': (-np.arange(ny)*2 + 2) + dy/2, 'band': [1, 2, 3]}) with xr.open_rasterio(tmp_file, cache=False) as actual: @@ -1628,11 +1631,12 @@ def test_caching(self): transform=transform, dtype=rasterio.float32) as s: s.write(data) + dx, dy = s.res[0], -s.res[1] # ref expected = DataArray(data, dims=('band', 'y', 'x'), - coords={'x': np.arange(nx)*0.5 + 1, - 'y': -np.arange(ny)*2 + 2, + coords={'x': (np.arange(nx)*0.5 + 1) + dx/2, + 'y': (-np.arange(ny)*2 + 2) + dy/2, 'band': [1, 2, 3]}) # Cache is the default @@ -1671,6 +1675,7 @@ def test_chunks(self): transform=transform, dtype=rasterio.float32) as s: s.write(data) + dx, dy = s.res[0], -s.res[1] # Chunk at open time with xr.open_rasterio(tmp_file, chunks=(1, 2, 2)) as actual: @@ -1681,8 +1686,8 @@ def test_chunks(self): # ref expected = DataArray(data, dims=('band', 'y', 'x'), - coords={'x': np.arange(nx)*0.5 + 1, - 'y': -np.arange(ny)*2 + 2, + coords={'x': np.arange(nx)*0.5 + 1 + dx/2, + 'y': -np.arange(ny)*2 + 2 + dy/2, 'band': [1, 2, 3]}) # do some arithmetic From 2bc35a8dbeb60678eb4207753aba1e5d13988e14 Mon Sep 17 00:00:00 2001 From: gbrener Date: Fri, 30 Jun 2017 15:54:57 -0500 Subject: [PATCH 2/4] Add documentation to whats-new.rst + docstring --- doc/whats-new.rst | 4 ++++ xarray/backends/rasterio_.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index ac4fc507aa7..fdeb4739dba 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -21,6 +21,10 @@ v0.9.7 (unreleased) Enhancements ~~~~~~~~~~~~ +- :py:func:`~xarray.open_rasterio` method now shifts the rasterio + coordinates so that they are centered in each pixel. + By `Greg Brener `_. + Bug fixes ~~~~~~~~~ diff --git a/xarray/backends/rasterio_.py b/xarray/backends/rasterio_.py index 988e3a3d064..8e0e33338fb 100644 --- a/xarray/backends/rasterio_.py +++ b/xarray/backends/rasterio_.py @@ -87,7 +87,10 @@ def open_rasterio(filename, chunks=None, cache=None, lock=None): This should work with any file that rasterio can open (most often: geoTIFF). The x and y coordinates are generated automatically from the - file's geoinformation. + file's geoinformation, shifted to the center of each pixel (see + `"PixelIsArea" Raster Space + `_ + for more information). Parameters ---------- From 55a2a27d785dd394da78a779264defa713c7f2e9 Mon Sep 17 00:00:00 2001 From: gbrener Date: Fri, 30 Jun 2017 16:36:03 -0500 Subject: [PATCH 3/4] Minor aesthetic correction --- xarray/backends/rasterio_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/backends/rasterio_.py b/xarray/backends/rasterio_.py index 8e0e33338fb..57a501b3086 100644 --- a/xarray/backends/rasterio_.py +++ b/xarray/backends/rasterio_.py @@ -138,7 +138,7 @@ def open_rasterio(filename, chunks=None, cache=None, lock=None): coords['y'] = np.linspace(start=y0 + dy/2, num=ny, stop=(y0 + (ny - 1) * dy) + dy/2) - coords['x'] = np.linspace(start=x0+dx/2, + coords['x'] = np.linspace(start=x0 + dx/2, num=nx, stop=(x0 + (nx - 1) * dx) + dx/2) From aeeef43378997ab40f1b5f1e54930e00ba843946 Mon Sep 17 00:00:00 2001 From: gbrener Date: Wed, 5 Jul 2017 12:41:10 -0500 Subject: [PATCH 4/4] Remove unnecessary line breaks Minor cleanup for @fmaussion --- xarray/backends/rasterio_.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/xarray/backends/rasterio_.py b/xarray/backends/rasterio_.py index cea76eb9e4c..16f5a55fa69 100644 --- a/xarray/backends/rasterio_.py +++ b/xarray/backends/rasterio_.py @@ -135,11 +135,9 @@ def open_rasterio(filename, chunks=None, cache=None, lock=None): dx, dy = riods.res[0], -riods.res[1] x0 = riods.bounds.right if dx < 0 else riods.bounds.left y0 = riods.bounds.top if dy < 0 else riods.bounds.bottom - coords['y'] = np.linspace(start=y0 + dy/2, - num=ny, + coords['y'] = np.linspace(start=y0 + dy/2, num=ny, stop=(y0 + (ny - 1) * dy) + dy/2) - coords['x'] = np.linspace(start=x0 + dx/2, - num=nx, + coords['x'] = np.linspace(start=x0 + dx/2, num=nx, stop=(x0 + (nx - 1) * dx) + dx/2) # Attributes