Repository: corteva/rioxarray

Issue:
Saving a RasterArray with a float nodata value encodes it as a string and transform information is corrupted
I'm on the most recent version of rioxarray, .18

The data file I'm using, "NEON-DS-Airborne-Remote-Sensing/HARV/DTM/HARV_dtmCrop.tif" can be downloaded here: https://ndownloader.figshare.com/articles/2009586/versions/10

```python
DTM_UTM = xarray.open_rasterio("data/NEON-DS-Airborne-Remote-Sensing/HARV/DTM/HARV_dtmCrop.tif")

from rasterio.crs import CRS
crs = CRS.from_epsg(4326)

reprojected = DTM_UTM.rio.reproject(dst_crs=crs)
# this is a float
reprojected.rio.nodata
reprojected_path = "data/NEON-DS-Airborne-Remote-Sensing/HARV/DTM/HARV_dtmCrop_WGS84.tif"
reprojected.rio.to_raster(reprojected_path)

DTM_WGS84 = xarray.open_rasterio(reprojected_path)
DTM_WGS84.rio.nodata # this is now a string because it was saved to a raster but it should be a float or int
```
```
'-9999.0'
```
Is this fixable? I tried converting the nodata value to an int first before saving but the issue still came up. I see that there is also a `nodatavals` attribute in my dataset that actually contains a float in a tuple, so I can use that, but I'm curious if it is possible to handle the `nodata` value issue since I'm designing a geospatial python lesson that uses rioxarray. Thanks for the great library!

Discussion:
snowman2: Failures are unrelated to this PR - looks like GDAL 2.4.4 broke some things.

Patch:
diff --git a/docs/history.rst b/docs/history.rst
index 92a22940..f4260842 100644
--- a/docs/history.rst
+++ b/docs/history.rst
@@ -1,6 +1,11 @@
 History
 =======
 
+0.0.19
+-------
+- Add support for writing scales & offsets to raster (pull #79)
+- Don't write standard raster metadata to raster tags (issue #78)
+
 0.0.18
 ------
 - Fixed windowed writing to require tiled output raster (pull #66)
diff --git a/rioxarray/rioxarray.py b/rioxarray/rioxarray.py
index f234fe86..68d84244 100644
--- a/rioxarray/rioxarray.py
+++ b/rioxarray/rioxarray.py
@@ -209,9 +209,32 @@ def _write_metatata_to_raster(raster_handle, xarray_dataset, tags):
     """
     Write the metadata stored in the xarray object to raster metadata
     """
-    # write metadata to raster
-    tags = {} if tags is None else tags
-    raster_handle.update_tags(**xarray_dataset.attrs, **tags)
+    tags = xarray_dataset.attrs if tags is None else {**xarray_dataset.attrs, **tags}
+
+    # write scales and offsets
+    try:
+        raster_handle.scales = tags["scales"]
+    except KeyError:
+        try:
+            raster_handle.scales = (tags["scale_factor"],) * raster_handle.count
+        except KeyError:
+            pass
+    try:
+        raster_handle.offsets = tags["offsets"]
+    except KeyError:
+        try:
+            raster_handle.offsets = (tags["add_offset"],) * raster_handle.count
+        except KeyError:
+            pass
+
+    # filter out attributes that should be written in a different location
+    skip_tags = (
+        UNWANTED_RIO_ATTRS
+        + FILL_VALUE_NAMES
+        + ("transform", "scales", "scale_factor", "add_offset", "offsets")
+    )
+    tags = {key: value for key, value in tags.items() if key not in skip_tags}
+    raster_handle.update_tags(**tags)
 
     # write band name information
     long_name = xarray_dataset.attrs.get("long_name")


Test patch:
diff --git a/test/integration/test_integration_rioxarray.py b/test/integration/test_integration_rioxarray.py
index 2a48acdb..149dbe1f 100644
--- a/test/integration/test_integration_rioxarray.py
+++ b/test/integration/test_integration_rioxarray.py
@@ -741,7 +741,20 @@ def test_to_raster(open_method, windowed, recalc_transform, tmpdir):
             tags=test_tags,
         )
         xds = mda.copy().squeeze()
-        xds_attrs = {key: str(value) for key, value in mda.attrs.items()}
+        xds_attrs = {
+            key: str(value)
+            for key, value in mda.attrs.items()
+            if key
+            not in (
+                "add_offset",
+                "crs",
+                "is_tiled",
+                "nodata",
+                "res",
+                "scale_factor",
+                "transform",
+            )
+        }
 
     with rasterio.open(str(tmp_raster)) as rds:
         assert rds.count == 1
@@ -768,7 +781,11 @@ def test_to_raster_3d(open_method, windowed, tmpdir):
         xds = mda.green.fillna(mda.green.rio.encoded_nodata)
         xds.rio._nodata = mda.green.rio.encoded_nodata
         xds.rio.to_raster(str(tmp_raster), windowed=windowed)
-        xds_attrs = {key: str(value) for key, value in xds.attrs.items()}
+        xds_attrs = {
+            key: str(value)
+            for key, value in xds.attrs.items()
+            if key not in ("add_offset", "nodata", "scale_factor", "transform")
+        }
 
     with rasterio.open(str(tmp_raster)) as rds:
         assert rds.crs == xds.rio.crs
@@ -781,6 +798,7 @@ def test_to_raster_3d(open_method, windowed, tmpdir):
     # test roundtrip
     with rioxarray.open_rasterio(str(tmp_raster)) as rds:
         assert rds.attrs["long_name"] == "green"
+        assert numpy.isnan(rds.rio.nodata)
 
 
 def test_to_raster__custom_description(tmpdir):
@@ -791,7 +809,9 @@ def test_to_raster__custom_description(tmpdir):
         xds = mda.green.fillna(mda.green.rio.encoded_nodata)
         xds.attrs["long_name"] = ("one", "two")
         xds.rio.to_raster(str(tmp_raster))
-        xds_attrs = {key: str(value) for key, value in xds.attrs.items()}
+        xds_attrs = {
+            key: str(value) for key, value in xds.attrs.items() if key != "nodata"
+        }
 
     with rasterio.open(str(tmp_raster)) as rds:
         assert rds.tags() == {"AREA_OR_POINT": "Area", **xds_attrs}
@@ -800,6 +820,55 @@ def test_to_raster__custom_description(tmpdir):
     # test roundtrip
     with rioxarray.open_rasterio(str(tmp_raster)) as rds:
         assert rds.attrs["long_name"] == ("one", "two")
+        assert rds.rio.nodata == 0.0
+
+
+def test_to_raster__scale_factor_and_add_offset(tmpdir):
+    tmp_raster = tmpdir.join("air_temp_offset.tif")
+
+    with rioxarray.open_rasterio(
+        os.path.join(TEST_INPUT_DATA_DIR, "tmmx_20190121.nc")
+    ) as rds:
+        assert rds.air_temperature.scale_factor == 0.1
+        assert rds.air_temperature.add_offset == 220.0
+        rds.air_temperature.rio.to_raster(str(tmp_raster))
+
+    with rasterio.open(str(tmp_raster)) as rds:
+        assert rds.scales == (0.1,)
+        assert rds.offsets == (220.0,)
+
+    # test roundtrip
+    with rioxarray.open_rasterio(str(tmp_raster)) as rds:
+        assert rds.scale_factor == 0.1
+        assert rds.add_offset == 220.0
+        assert rds.rio.nodata == 32767.0
+
+
+def test_to_raster__offsets_and_scales(tmpdir):
+    tmp_raster = tmpdir.join("air_temp_offset.tif")
+
+    with rioxarray.open_rasterio(
+        os.path.join(TEST_INPUT_DATA_DIR, "tmmx_20190121.nc")
+    ) as rds:
+        attrs = dict(rds.air_temperature.attrs)
+        attrs["scales"] = [0.1]
+        attrs["offsets"] = [220.0]
+        attrs.pop("scale_factor")
+        attrs.pop("add_offset")
+        rds.air_temperature.attrs = attrs
+        assert rds.air_temperature.scales == [0.1]
+        assert rds.air_temperature.offsets == [220.0]
+        rds.air_temperature.rio.to_raster(str(tmp_raster))
+
+    with rasterio.open(str(tmp_raster)) as rds:
+        assert rds.scales == (0.1,)
+        assert rds.offsets == (220.0,)
+
+    # test roundtrip
+    with rioxarray.open_rasterio(str(tmp_raster)) as rds:
+        assert rds.scale_factor == 0.1
+        assert rds.add_offset == 220.0
+        assert rds.rio.nodata == 32767.0
 
 
 def test_to_raster__custom_description__wrong(tmpdir):