Skip to content
This repository was archived by the owner on Aug 29, 2023. It is now read-only.

Commit b201ff7

Browse files
gbrenershoyer
authored andcommitted
Center the coordinates to pixels for rasterio backend (pydata#1468)
* 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. * Add documentation to whats-new.rst + docstring * Minor aesthetic correction * Remove unnecessary line breaks Minor cleanup for @fmaussion
1 parent 6a20f91 commit b201ff7

File tree

3 files changed

+30
-16
lines changed

3 files changed

+30
-16
lines changed

doc/whats-new.rst

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ Enhancements
2828
Bug fixes
2929
~~~~~~~~~
3030

31+
- :py:func:`~xarray.open_rasterio` method now shifts the rasterio
32+
coordinates so that they are centered in each pixel.
33+
By `Greg Brener <https://github.com/gbrener>`_.
34+
3135
.. _whats-new.0.9.6:
3236

3337
v0.9.6 (8 June 2017)

xarray/backends/rasterio_.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ def open_rasterio(filename, chunks=None, cache=None, lock=None):
8787
8888
This should work with any file that rasterio can open (most often:
8989
geoTIFF). The x and y coordinates are generated automatically from the
90-
file's geoinformation.
90+
file's geoinformation, shifted to the center of each pixel (see
91+
`"PixelIsArea" Raster Space
92+
<http://web.archive.org/web/20160326194152/http://remotesensing.org/geotiff/spec/geotiff2.5.html#2.5.2>`_
93+
for more information).
9194
9295
Parameters
9396
----------
@@ -132,8 +135,10 @@ def open_rasterio(filename, chunks=None, cache=None, lock=None):
132135
dx, dy = riods.res[0], -riods.res[1]
133136
x0 = riods.bounds.right if dx < 0 else riods.bounds.left
134137
y0 = riods.bounds.top if dy < 0 else riods.bounds.bottom
135-
coords['y'] = np.linspace(start=y0, num=ny, stop=(y0 + (ny - 1) * dy))
136-
coords['x'] = np.linspace(start=x0, num=nx, stop=(x0 + (nx - 1) * dx))
138+
coords['y'] = np.linspace(start=y0 + dy/2, num=ny,
139+
stop=(y0 + (ny - 1) * dy) + dy/2)
140+
coords['x'] = np.linspace(start=x0 + dx/2, num=nx,
141+
stop=(x0 + (nx - 1) * dx) + dx/2)
137142

138143
# Attributes
139144
attrs = {}

xarray/tests/test_backends.py

+18-13
Original file line numberDiff line numberDiff line change
@@ -1443,7 +1443,6 @@ class TestPyNioAutocloseTrue(TestPyNio):
14431443
class TestRasterio(TestCase):
14441444

14451445
def test_serialization_utm(self):
1446-
14471446
import rasterio
14481447
from rasterio.transform import from_origin
14491448

@@ -1462,13 +1461,15 @@ def test_serialization_utm(self):
14621461
transform=transform,
14631462
dtype=rasterio.float32) as s:
14641463
s.write(data)
1464+
dx, dy = s.res[0], -s.res[1]
14651465

14661466
# Tests
14671467
expected = DataArray(data, dims=('band', 'y', 'x'),
1468-
coords={'band': [1, 2, 3],
1469-
'y': -np.arange(ny) * 2000 + 80000,
1470-
'x': np.arange(nx) * 1000 + 5000,
1471-
})
1468+
coords={
1469+
'band': [1, 2, 3],
1470+
'y': -np.arange(ny) * 2000 + 80000 + dy/2,
1471+
'x': np.arange(nx) * 1000 + 5000 + dx/2,
1472+
})
14721473
with xr.open_rasterio(tmp_file) as rioda:
14731474
assert_allclose(rioda, expected)
14741475
assert 'crs' in rioda.attrs
@@ -1504,13 +1505,14 @@ def test_serialization_platecarree(self):
15041505
transform=transform,
15051506
dtype=rasterio.float32) as s:
15061507
s.write(data, indexes=1)
1508+
dx, dy = s.res[0], -s.res[1]
15071509

15081510
# Tests
15091511
expected = DataArray(data[np.newaxis, ...],
15101512
dims=('band', 'y', 'x'),
15111513
coords={'band': [1],
1512-
'y': -np.arange(ny)*2 + 2,
1513-
'x': np.arange(nx)*0.5 + 1,
1514+
'y': -np.arange(ny)*2 + 2 + dy/2,
1515+
'x': np.arange(nx)*0.5 + 1 + dx/2,
15141516
})
15151517
with xr.open_rasterio(tmp_file) as rioda:
15161518
assert_allclose(rioda, expected)
@@ -1548,11 +1550,12 @@ def test_indexing(self):
15481550
transform=transform,
15491551
dtype=rasterio.float32) as s:
15501552
s.write(data)
1553+
dx, dy = s.res[0], -s.res[1]
15511554

15521555
# ref
15531556
expected = DataArray(data, dims=('band', 'y', 'x'),
1554-
coords={'x': np.arange(nx)*0.5 + 1,
1555-
'y': -np.arange(ny)*2 + 2,
1557+
coords={'x': (np.arange(nx)*0.5 + 1) + dx/2,
1558+
'y': (-np.arange(ny)*2 + 2) + dy/2,
15561559
'band': [1, 2, 3]})
15571560

15581561
with xr.open_rasterio(tmp_file, cache=False) as actual:
@@ -1640,11 +1643,12 @@ def test_caching(self):
16401643
transform=transform,
16411644
dtype=rasterio.float32) as s:
16421645
s.write(data)
1646+
dx, dy = s.res[0], -s.res[1]
16431647

16441648
# ref
16451649
expected = DataArray(data, dims=('band', 'y', 'x'),
1646-
coords={'x': np.arange(nx)*0.5 + 1,
1647-
'y': -np.arange(ny)*2 + 2,
1650+
coords={'x': (np.arange(nx)*0.5 + 1) + dx/2,
1651+
'y': (-np.arange(ny)*2 + 2) + dy/2,
16481652
'band': [1, 2, 3]})
16491653

16501654
# Cache is the default
@@ -1683,6 +1687,7 @@ def test_chunks(self):
16831687
transform=transform,
16841688
dtype=rasterio.float32) as s:
16851689
s.write(data)
1690+
dx, dy = s.res[0], -s.res[1]
16861691

16871692
# Chunk at open time
16881693
with xr.open_rasterio(tmp_file, chunks=(1, 2, 2)) as actual:
@@ -1693,8 +1698,8 @@ def test_chunks(self):
16931698

16941699
# ref
16951700
expected = DataArray(data, dims=('band', 'y', 'x'),
1696-
coords={'x': np.arange(nx)*0.5 + 1,
1697-
'y': -np.arange(ny)*2 + 2,
1701+
coords={'x': np.arange(nx)*0.5 + 1 + dx/2,
1702+
'y': -np.arange(ny)*2 + 2 + dy/2,
16981703
'band': [1, 2, 3]})
16991704

17001705
# do some arithmetic

0 commit comments

Comments
 (0)