Skip to content

Commit

Permalink
Merge pull request #284 from snowman2/grid_encode
Browse files Browse the repository at this point in the history
- REF: Store grid_mapping in encoding instead of attrs
- DEP: Python 3+ and xarray 0.17+
  • Loading branch information
snowman2 authored Apr 6, 2021
2 parents 0b32bd0 + 596be53 commit 74c2c0f
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 67 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ jobs:
rasterio-version: '*'
xarray-version: '*'
- os: ubuntu-latest
python-version: 3.6
python-version: 3.7
rasterio-version: 1.1
xarray-version: 0.16
xarray-version: '*'
steps:
- uses: actions/checkout@v2

Expand Down
3 changes: 3 additions & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ History

Latest
------
- DEP: Python 3.6+ (issue #215)
- DEP: xarray 0.17+ (needed for issue #282)
- REF: Store `grid_mapping` in `encoding` instead of `attrs` (issue #282)

0.3.2
-----
Expand Down
7 changes: 1 addition & 6 deletions rioxarray/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@

import rioxarray.raster_array # noqa
import rioxarray.raster_dataset # noqa
from rioxarray._io import open_rasterio # noqa
from rioxarray._options import set_options # noqa
from rioxarray._show_versions import show_versions # noqa
from rioxarray._version import __version__ # noqa

try:
# This requires xarray >= 0.12.3
from rioxarray._io import open_rasterio # noqa
except ImportError:
from xarray import open_rasterio # noqa
9 changes: 3 additions & 6 deletions rioxarray/_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,8 @@ def _handle_encoding(result, mask_and_scale, masked, da_name):
"""
Make sure encoding handled properly
"""
if "grid_mapping" in result.attrs:
variables.pop_to(result.attrs, result.encoding, "grid_mapping", name=da_name)
if mask_and_scale:
if "scale_factor" in result.attrs:
variables.pop_to(
Expand Down Expand Up @@ -851,7 +853,6 @@ def open_rasterio(

# handle encoding
_handle_encoding(result, mask_and_scale, masked, da_name)

# Affine transformation matrix (always available)
# This describes coefficients mapping pixel coordinates to CRS
# For serialization store as tuple of 6 floats, the last row being
Expand All @@ -865,11 +866,7 @@ def open_rasterio(
result = _prepare_dask(result, riods, filename, chunks)

# Make the file closeable
try:
# xarray 0.17 +
result.set_close(manager.close)
except AttributeError:
result._file_obj = manager
result.set_close(manager.close)
result.rio._manager = manager
# add file path to encoding
result.encoding["source"] = riods.name
Expand Down
4 changes: 1 addition & 3 deletions rioxarray/raster_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ def _generate_attrs(src_data_array, dst_nodata):
if src_data_array.rio.encoded_nodata is None and fill_value is not None:
new_attrs["_FillValue"] = fill_value

# add raster spatial information
new_attrs["grid_mapping"] = src_data_array.rio.grid_mapping

return new_attrs


Expand All @@ -75,6 +72,7 @@ def _add_attrs_proj(new_data_array, src_data_array):
new_data_array.rio.set_attrs(new_attrs, inplace=True)

# make sure projection added
new_data_array.rio.write_grid_mapping(src_data_array.rio.grid_mapping, inplace=True)
new_data_array.rio.write_crs(src_data_array.rio.crs, inplace=True)
new_data_array.rio.write_coordinate_system(inplace=True)
new_data_array.rio.write_transform(inplace=True)
Expand Down
87 changes: 75 additions & 12 deletions rioxarray/rioxarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,11 @@ def grid_mapping(self):
"""
str: The CF grid_mapping attribute. 'spatial_ref' is the default.
"""
try:
return self._obj.attrs["grid_mapping"]
except KeyError:
pass
grid_mapping = self._obj.encoding.get(
"grid_mapping", self._obj.attrs.get("grid_mapping")
)
if grid_mapping is not None:
return grid_mapping
grid_mapping = DEFAULT_GRID_MAP
# search the dataset for the grid mapping name
if hasattr(self._obj, "data_vars"):
Expand All @@ -245,11 +246,12 @@ def grid_mapping(self):
self._obj[var].rio.y_dim
except DimensionError:
continue
try:
grid_mapping = self._obj[var].attrs["grid_mapping"]
var_grid_mapping = self._obj[var].encoding.get(
"grid_mapping", self._obj[var].attrs.get("grid_mapping")
)
if var_grid_mapping is not None:
grid_mapping = var_grid_mapping
grid_mappings.add(grid_mapping)
except KeyError:
pass
if len(grid_mappings) > 1:
raise RioXarrayError("Multiple grid mappings exist.")
return grid_mapping
Expand Down Expand Up @@ -279,12 +281,22 @@ def write_grid_mapping(self, grid_mapping_name=DEFAULT_GRID_MAP, inplace=False):
except DimensionError:
continue

data_obj[var].rio.update_attrs(
# remove grid_mapping from attributes if it exists
# and update the grid_mapping in encoding
new_attrs = dict(data_obj[var].attrs)
new_attrs.pop("grid_mapping", None)
data_obj[var].rio.update_encoding(
dict(grid_mapping=grid_mapping_name), inplace=True
).rio.set_spatial_dims(x_dim=x_dim, y_dim=y_dim, inplace=True)
return data_obj.rio.update_attrs(
).rio.update_attrs(new_attrs, inplace=True).rio.set_spatial_dims(
x_dim=x_dim, y_dim=y_dim, inplace=True
)
# remove grid_mapping from attributes if it exists
# and update the grid_mapping in encoding
new_attrs = dict(data_obj.attrs)
new_attrs.pop("grid_mapping", None)
return data_obj.rio.update_encoding(
dict(grid_mapping=grid_mapping_name), inplace=True
)
).rio.update_attrs(new_attrs, inplace=True)

def write_crs(self, input_crs=None, grid_mapping_name=None, inplace=False):
"""
Expand Down Expand Up @@ -591,6 +603,57 @@ def update_attrs(self, new_attrs, inplace=False):
data_attrs.update(**new_attrs)
return self.set_attrs(data_attrs, inplace=inplace)

def set_encoding(self, new_encoding, inplace=False):
"""
Set the encoding of the dataset/dataarray and reset
rioxarray properties to re-search for them.
.. versionadded:: 0.4
Parameters
----------
new_encoding: dict
A dictionary for encoding.
inplace: bool, optional
If True, it will write to the existing dataset. Default is False.
Returns
-------
:obj:`xarray.Dataset` | :obj:`xarray.DataArray`:
Modified dataset with new attributes.
"""
data_obj = self._get_obj(inplace=inplace)
# set the attributes
data_obj.encoding = new_encoding
# reset rioxarray properties depending
# on attributes to be generated
data_obj.rio._nodata = None
data_obj.rio._crs = None
return data_obj

def update_encoding(self, new_encoding, inplace=False):
"""
Update the encoding of the dataset/dataarray and reset
rioxarray properties to re-search for them.
.. versionadded:: 0.4
Parameters
----------
new_encoding: dict
A dictionary with encoding values to update with.
inplace: bool, optional
If True, it will write to the existing dataset. Default is False.
Returns
-------
:obj:`xarray.Dataset` | :obj:`xarray.DataArray`:
Modified dataset with updated attributes.
"""
data_encoding = dict(self._obj.encoding)
data_encoding.update(**new_encoding)
return self.set_encoding(data_encoding, inplace=inplace)

def set_spatial_dims(self, x_dim, y_dim, inplace=True):
"""
This sets the spatial dimensions of the dataset.
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def get_version():
with open("README.rst") as readme_file:
readme = readme_file.read()

requirements = ["rasterio", "scipy", "xarray", "pyproj>=2.2"]
requirements = ["rasterio", "scipy", "xarray>=0.17", "pyproj>=2.2"]

test_requirements = ["pytest>=3.6", "pytest-cov", "dask"]
doc_requirements = ["sphinx-click==1.1.0", "nbsphinx", "sphinx_rtd_theme"]
Expand Down Expand Up @@ -52,7 +52,6 @@ def get_version():
"Natural Language :: English",
"Topic :: Scientific/Engineering :: GIS",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
Expand All @@ -71,5 +70,5 @@ def get_version():
url="https://github.com/corteva/rioxarray",
version=get_version(),
zip_safe=False,
python_requires=">=3.6",
python_requires=">=3.7",
)
7 changes: 2 additions & 5 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def _assert_attrs_equal(input_xr, compare_xr, decimal_precision):
attr != "_FillValue"
and attr not in UNWANTED_RIO_ATTRS
and attr != "creation_date"
and attr != "grid_mapping"
):
try:
assert_almost_equal(
Expand Down Expand Up @@ -80,10 +81,6 @@ def _assert_xarrays_equal(
"_FillValue", input_xarray.encoding.get("_FillValue")
)
assert_array_equal([input_fill_value], [compare_fill_value])
assert "grid_mapping" in compare_xarray.attrs
assert (
input_xarray[input_xarray.attrs["grid_mapping"]]
== compare_xarray[compare_xarray.attrs["grid_mapping"]]
)
assert input_xarray.rio.grid_mapping == compare_xarray.rio.grid_mapping
for unwanted_attr in UNWANTED_RIO_ATTRS:
assert unwanted_attr not in input_xarray.attrs
14 changes: 6 additions & 8 deletions test/integration/test_integration__io.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,14 @@ def test_open_group_load_attrs():
assert sorted(attrs) == [
"_FillValue",
"add_offset",
"grid_mapping",
"long_name",
"scale_factor",
"units",
]
assert attrs["long_name"] == "500m Surface Reflectance Band 5 - first layer"
assert attrs["units"] == "reflectance"
assert attrs["_FillValue"] == -28672.0
assert attrs["grid_mapping"] == "spatial_ref"
assert rds["sur_refl_b05_1"].encoding["grid_mapping"] == "spatial_ref"


def test_open_rasterio_mask_chunk_clip():
Expand All @@ -267,14 +266,13 @@ def test_open_rasterio_mask_chunk_clip():
assert np.isnan(xdi.values).sum() == 52119
test_encoding = dict(xdi.encoding)
assert test_encoding.pop("source").endswith("small_dem_3m_merged.tif")
assert test_encoding == {"_FillValue": 0.0}
assert test_encoding == {"_FillValue": 0.0, "grid_mapping": "spatial_ref"}
attrs = dict(xdi.attrs)
assert_almost_equal(
tuple(xdi.rio._cached_transform())[:6],
(3.0, 0.0, 425047.68381405267, 0.0, -3.0, 4615780.040546387),
)
assert attrs == {
"grid_mapping": "spatial_ref",
"add_offset": 0.0,
"scale_factor": 1.0,
}
Expand Down Expand Up @@ -305,7 +303,7 @@ def test_open_rasterio_mask_chunk_clip():
_assert_xarrays_equal(clipped, comp_subset)
test_encoding = dict(clipped.encoding)
assert test_encoding.pop("source").endswith("small_dem_3m_merged.tif")
assert test_encoding == {"_FillValue": 0.0}
assert test_encoding == {"_FillValue": 0.0, "grid_mapping": "spatial_ref"}

# test dataset
clipped_ds = xdi.to_dataset(name="test_data").rio.clip(
Expand All @@ -315,7 +313,7 @@ def test_open_rasterio_mask_chunk_clip():
_assert_xarrays_equal(clipped_ds, comp_subset_ds)
test_encoding = dict(clipped.encoding)
assert test_encoding.pop("source").endswith("small_dem_3m_merged.tif")
assert test_encoding == {"_FillValue": 0.0}
assert test_encoding == {"_FillValue": 0.0, "grid_mapping": "spatial_ref"}


##############################################################################
Expand Down Expand Up @@ -895,14 +893,14 @@ def test_mask_and_scale():
"scale_factor": 0.1,
"_FillValue": 32767.0,
"missing_value": 32767,
"grid_mapping": "crs",
}
attrs = rds.air_temperature.attrs
assert attrs == {
"coordinates": "day",
"coordinate_system": "WGS84,EPSG:4326",
"description": "Daily Maximum Temperature",
"dimensions": "lon lat time",
"grid_mapping": "crs",
"long_name": "tmmx",
"standard_name": "tmmx",
"units": "K",
Expand All @@ -923,6 +921,7 @@ def test_no_mask_and_scale():
assert test_encoding == {
"_FillValue": 32767.0,
"missing_value": 32767,
"grid_mapping": "crs",
}
attrs = rds.air_temperature.attrs
assert attrs == {
Expand All @@ -932,7 +931,6 @@ def test_no_mask_and_scale():
"coordinate_system": "WGS84,EPSG:4326",
"description": "Daily Maximum Temperature",
"dimensions": "lon lat time",
"grid_mapping": "crs",
"long_name": "tmmx",
"scale_factor": 0.1,
"standard_name": "tmmx",
Expand Down
Loading

0 comments on commit 74c2c0f

Please sign in to comment.