Repository: corteva/rioxarray

Issue:
to_raster() save function changes original data
# to_raster() function modifying original variable values on output

I added a reproducible script below and attached a link to the data.

#### Code Sample


```python
import rioxarray
import xarray as xr
import numpy as np
from pyproj import CRS

# Open data
fpath = "./2020/002/00/OR_ABI-L2-LSTM1-M6_G16_s20200020000291_e20200020000349_c20200020001006.nc"
ds_xarr = xr.open_dataset(fpath, decode_coords="all")

# Lets convert coords to latitude and longitude
cc = CRS.from_cf(ds_xarr.goes_imager_projection.attrs)
ds_xarr.rio.write_crs(cc.to_string(), inplace=True)
sat_height = ds_xarr.goes_imager_projection.attrs["perspective_point_height"]
ds_xarr['x'] = ds_xarr.x.values * sat_height
ds_xarr['y'] = ds_xarr.y.values * sat_height
ds_xarr4326 = ds_xarr.rio.reproject("epsg:4326")

# lets view the min and max values
print("original min value: ", np.nanmin(ds_xarr4326['LST'].data)) # 255.795
print("original max value:" , np.nanmax(ds_xarr4326['LST'].data)) # 287.8375

# Save as raster file
ds_xarr4326['LST'].rio.to_raster("./GOES16_LST.tif",driver="GTiff")

# Lets read in output file and view the min max values (why are these different than original data?)
outds = xr.open_dataset("./GOES16_LST.tif")
print("outds min value: ", np.nanmin(outds['band_data'].data)) # 108.08
print("outds maxvalue: ", np.nanmax(outds['band_data'].data)) # 271.91748

```
__test data location:__
https://github.com/arojas314/data-sharing/blob/main/OR_ABI-L2-LSTM1-M6_G16_s20200020000291_e20200020000349_c20200020001006.nc

#### Problem description

When saving the xarray as a raster (GeoTiff file), the original data values are modified.
See output for min and max values above for more insight.
Originally, min max values were [255.795, 287.8375]. After saving to .tif file, new min max values are [108.08, 271.91748].

#### Expected Output


#### Environment Information

 - rioxarray version: `0.11.1`
 - rasterio version: `1.2.10`
 - GDAL version: (`3.4.3`)
 - Python version: `3.10.5`

#### Installation method
 - conda

#### Conda environment information (if you installed with conda):

<br/>

Environment (<code>conda list</code>):
<details>

![image](https://user-images.githubusercontent.com/57734585/188782225-288c21e5-a081-4337-9f25-301fe17de82e.png)

</details>


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/corteva/rioxarray/pull/575?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) Report
> Merging [#575](https://codecov.io/gh/corteva/rioxarray/pull/575?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) (ea3ff6c) into [master](https://codecov.io/gh/corteva/rioxarray/commit/f23d3d44928272e560ca2bab65494331ed816139?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) (f23d3d4) will **decrease** coverage by `0.09%`.
> The diff coverage is `92.85%`.

```diff
@@            Coverage Diff             @@
##           master     #575      +/-   ##
==========================================
- Coverage   96.25%   96.15%   -0.10%     
==========================================
  Files          13       13              
  Lines        1603     1640      +37     
==========================================
+ Hits         1543     1577      +34     
- Misses         60       63       +3     
```


| [Impacted Files](https://codecov.io/gh/corteva/rioxarray/pull/575?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) | Coverage Δ | |
|---|---|---|
| [rioxarray/\_io.py](https://codecov.io/gh/corteva/rioxarray/pull/575/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva#diff-cmlveGFycmF5L19pby5weQ==) | `95.51% <92.45%> (-0.35%)` | :arrow_down: |
| [rioxarray/raster\_writer.py](https://codecov.io/gh/corteva/rioxarray/pull/575/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva#diff-cmlveGFycmF5L3Jhc3Rlcl93cml0ZXIucHk=) | `95.00% <100.00%> (+0.10%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva)


Patch:
diff --git a/docs/history.rst b/docs/history.rst
index 5a028e21..a7b459e0 100644
--- a/docs/history.rst
+++ b/docs/history.rst
@@ -3,6 +3,7 @@ History
 
 Latest
 ------
+- BUG: Handle `_Unsigned` and load in all attributes (pull #575)
 
 0.12.0
 -------
diff --git a/rioxarray/_io.py b/rioxarray/_io.py
index 629ba27f..d91df3fe 100644
--- a/rioxarray/_io.py
+++ b/rioxarray/_io.py
@@ -11,10 +11,11 @@ import os
 import re
 import threading
 import warnings
-from typing import Any, Dict, Hashable, List, Optional, Tuple, Union
+from typing import Any, Dict, Hashable, Iterable, List, Optional, Tuple, Union
 
 import numpy as np
 import rasterio
+from numpy.typing import NDArray
 from packaging import version
 from rasterio.errors import NotGeoreferencedWarning
 from rasterio.vrt import WarpedVRT
@@ -39,6 +40,18 @@ NO_LOCK = contextlib.nullcontext()
 RasterioReader = Union[rasterio.io.DatasetReader, rasterio.vrt.WarpedVRT]
 
 
+def _get_unsigned_dtype(unsigned, dtype):
+    """
+    Based on: https://github.com/pydata/xarray/blob/abe1e613a96b000ae603c53d135828df532b952e/xarray/coding/variables.py#L306-L334
+    """
+    dtype = np.dtype(dtype)
+    if unsigned is True and dtype.kind == "i":
+        return np.dtype(f"u{dtype.itemsize}")
+    if unsigned is False and dtype.kind == "u":
+        return np.dtype(f"i{dtype.itemsize}")
+    return None
+
+
 class FileHandleLocal(threading.local):
     """
     This contains the thread local ThreadURIManager
@@ -173,28 +186,31 @@ class RasterioArrayWrapper(BackendArray):
         self._shape = (riods.count, riods.height, riods.width)
 
         self._dtype = None
+        self._unsigned_dtype = None
+        self._fill_value = riods.nodata
         dtypes = riods.dtypes
         if not np.all(np.asarray(dtypes) == dtypes[0]):
             raise ValueError("All bands should have the same dtype")
 
         dtype = _rasterio_to_numpy_dtype(dtypes)
-
-        # handle unsigned case
-        if mask_and_scale and unsigned and dtype.kind == "i":
-            self._dtype = np.dtype(f"u{dtype.itemsize}")
-        elif mask_and_scale and unsigned:
-            warnings.warn(
-                f"variable {name!r} has _Unsigned attribute but is not "
-                "of integer type. Ignoring attribute.",
-                variables.SerializationWarning,
-                stacklevel=3,
+        if mask_and_scale and unsigned is not None:
+            self._unsigned_dtype = _get_unsigned_dtype(
+                unsigned=unsigned,
+                dtype=dtype,
             )
-        self._fill_value = riods.nodata
-        if self._dtype is None:
-            if self.masked:
-                self._dtype, self._fill_value = maybe_promote(dtype)
-            else:
-                self._dtype = dtype
+            if self._unsigned_dtype is not None and self._fill_value is not None:
+                self._fill_value = self._unsigned_dtype.type(self._fill_value)
+            if self._unsigned_dtype is None and dtype.kind not in ("i", "u"):
+                warnings.warn(
+                    f"variable {name!r} has _Unsigned attribute but is not "
+                    "of integer type. Ignoring attribute.",
+                    variables.SerializationWarning,
+                    stacklevel=3,
+                )
+        if self.masked:
+            self._dtype, self._fill_value = maybe_promote(dtype)
+        else:
+            self._dtype = dtype
 
     @property
     def dtype(self):
@@ -288,6 +304,8 @@ class RasterioArrayWrapper(BackendArray):
                 if self.vrt_params is not None:
                     riods = WarpedVRT(riods, **self.vrt_params)
                 out = riods.read(band_key, window=window, masked=self.masked)
+                if self._unsigned_dtype is not None:
+                    out = out.astype(self._unsigned_dtype)
                 if self.masked:
                     out = np.ma.filled(out.astype(self.dtype), self.fill_value)
                 if self.mask_and_scale:
@@ -418,29 +436,44 @@ def _load_netcdf_attrs(tags: Dict, data_array: DataArray) -> None:
             data_array.coords[variable_name].attrs.update({attr_name: value})
 
 
+def _parse_netcdf_attr_array(attr: Union[NDArray, str], dtype=None) -> NDArray:
+    """
+    Expected format: '{2,6}' or '[2. 6.]'
+    """
+    value: Union[NDArray, str, List]
+    if isinstance(attr, str):
+        if attr.startswith("{"):
+            value = attr.strip("{}").split(",")
+        else:
+            value = attr.strip("[]").split()
+    elif not isinstance(attr, Iterable):
+        value = [attr]
+    else:
+        value = attr
+    return np.array(value, dtype=dtype)
+
+
 def _load_netcdf_1d_coords(tags: Dict) -> Dict:
     """
     Dimension information:
         - NETCDF_DIM_EXTRA: '{time}' (comma separated list of dim names)
-        - NETCDF_DIM_time_DEF: '{2,6}' (dim size, dim dtype)
-        - NETCDF_DIM_time_VALUES: '{0,872712.659688}' (comma separated list of data)
+        - NETCDF_DIM_time_DEF: '{2,6}' or '[2. 6.]' (dim size, dim dtype)
+        - NETCDF_DIM_time_VALUES: '{0,872712.659688}' (comma separated list of data) or [     0.       872712.659688]
     """
     dim_names = tags.get("NETCDF_DIM_EXTRA")
     if not dim_names:
         return {}
-    dim_names = dim_names.strip("{}").split(",")
+    dim_names = _parse_netcdf_attr_array(dim_names)
     coords = {}
     for dim_name in dim_names:
         dim_def = tags.get(f"NETCDF_DIM_{dim_name}_DEF")
-        if not dim_def:
+        if dim_def is None:
             continue
         # pylint: disable=unused-variable
-        dim_size, dim_dtype = dim_def.strip("{}").split(",")
-        dim_dtype = NETCDF_DTYPE_MAP.get(int(dim_dtype), object)
-        dim_values = tags[f"NETCDF_DIM_{dim_name}_VALUES"].strip("{}")
-        coords[dim_name] = IndexVariable(
-            dim_name, np.fromstring(dim_values, dtype=dim_dtype, sep=",")
-        )
+        dim_size, dim_dtype = _parse_netcdf_attr_array(dim_def)
+        dim_dtype = NETCDF_DTYPE_MAP.get(int(float(dim_dtype)), object)
+        dim_values = _parse_netcdf_attr_array(tags[f"NETCDF_DIM_{dim_name}_VALUES"])
+        coords[dim_name] = IndexVariable(dim_name, dim_values)
     return coords
 
 
@@ -491,7 +524,7 @@ def _get_rasterio_attrs(riods: RasterioReader):
     """
     # pylint: disable=too-many-branches
     # Add rasterio attributes
-    attrs = _parse_tags(riods.tags(1))
+    attrs = _parse_tags({**riods.tags(), **riods.tags(1)})
     if riods.nodata is not None:
         # The nodata values for the raster bands
         attrs["_FillValue"] = riods.nodata
@@ -591,6 +624,19 @@ def _parse_driver_tags(
                 attrs[key] = value
 
 
+def _pop_global_netcdf_attrs_from_vars(dataset_to_clean: Dataset) -> Dataset:
+    # remove GLOBAL netCDF attributes from dataset variables
+    for coord in dataset_to_clean.coords:
+        for variable in dataset_to_clean.variables:
+            dataset_to_clean[variable].attrs = {
+                attr: value
+                for attr, value in dataset_to_clean[variable].attrs.items()
+                if attr not in dataset_to_clean.attrs
+                and not attr.startswith(f"{coord}#")
+            }
+    return dataset_to_clean
+
+
 def _load_subdatasets(
     riods: RasterioReader,
     group: Optional[Union[str, List[str], Tuple[str, ...]]],
@@ -608,7 +654,7 @@ def _load_subdatasets(
     """
     Load in rasterio subdatasets
     """
-    base_tags = _parse_tags(riods.tags())
+    global_tags = _parse_tags(riods.tags())
     dim_groups = {}
     subdataset_filter = None
     if any((group, variable)):
@@ -638,12 +684,15 @@ def _load_subdatasets(
 
     if len(dim_groups) > 1:
         dataset: Union[Dataset, List[Dataset]] = [
-            Dataset(dim_group, attrs=base_tags) for dim_group in dim_groups.values()
+            _pop_global_netcdf_attrs_from_vars(Dataset(dim_group, attrs=global_tags))
+            for dim_group in dim_groups.values()
         ]
     elif not dim_groups:
-        dataset = Dataset(attrs=base_tags)
+        dataset = Dataset(attrs=global_tags)
     else:
-        dataset = Dataset(list(dim_groups.values())[0], attrs=base_tags)
+        dataset = _pop_global_netcdf_attrs_from_vars(
+            Dataset(list(dim_groups.values())[0], attrs=global_tags)
+        )
     return dataset
 
 
@@ -689,7 +738,11 @@ def _prepare_dask(
 
 
 def _handle_encoding(
-    result: DataArray, mask_and_scale: bool, masked: bool, da_name: Optional[Hashable]
+    result: DataArray,
+    mask_and_scale: bool,
+    masked: bool,
+    da_name: Optional[Hashable],
+    unsigned: Union[bool, None],
 ) -> None:
     """
     Make sure encoding handled properly
@@ -711,6 +764,16 @@ def _handle_encoding(
                 result.attrs, result.encoding, "missing_value", name=da_name
             )
 
+    if mask_and_scale and unsigned is not None and "_FillValue" in result.encoding:
+        unsigned_dtype = _get_unsigned_dtype(
+            unsigned=unsigned,
+            dtype=result.encoding["dtype"],
+        )
+        if unsigned_dtype is not None:
+            result.encoding["_FillValue"] = unsigned_dtype.type(
+                result.encoding["_FillValue"]
+            )
+
 
 def open_rasterio(
     filename: Union[
@@ -879,13 +942,19 @@ def open_rasterio(
 
     # parse tags & load alternate coords
     attrs = _get_rasterio_attrs(riods=riods)
-    coords = _load_netcdf_1d_coords(riods.tags())
+    coords = _load_netcdf_1d_coords(attrs)
     _parse_driver_tags(riods=riods, attrs=attrs, coords=coords)
     for coord in coords:
         if f"NETCDF_DIM_{coord}" in attrs:
             coord_name = coord
             attrs.pop(f"NETCDF_DIM_{coord}")
             break
+        if f"NETCDF_DIM_{coord}_VALUES" in attrs:
+            coord_name = coord
+            attrs.pop(f"NETCDF_DIM_{coord}_VALUES")
+            attrs.pop(f"NETCDF_DIM_{coord}_DEF", None)
+            attrs.pop("NETCDF_DIM_EXTRA", None)
+            break
     else:
         coord_name = "band"
         coords[coord_name] = np.asarray(riods.indexes)
@@ -900,7 +969,7 @@ def open_rasterio(
             _generate_spatial_coords(riods.transform, riods.width, riods.height)
         )
 
-    unsigned = False
+    unsigned = None
     encoding: Dict[Hashable, Any] = {}
     if mask_and_scale and "_Unsigned" in attrs:
         unsigned = variables.pop_to(attrs, encoding, "_Unsigned") == "true"
@@ -938,11 +1007,11 @@ def open_rasterio(
     )
 
     # make sure the _FillValue is correct dtype
-    if "_FillValue" in attrs:
-        attrs["_FillValue"] = result.dtype.type(attrs["_FillValue"])
+    if "_FillValue" in result.attrs:
+        result.attrs["_FillValue"] = result.dtype.type(result.attrs["_FillValue"])
 
     # handle encoding
-    _handle_encoding(result, mask_and_scale, masked, da_name)
+    _handle_encoding(result, mask_and_scale, masked, da_name, unsigned=unsigned)
     # 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
@@ -964,4 +1033,18 @@ def open_rasterio(
     # add file path to encoding
     result.encoding["source"] = riods.name
     result.encoding["rasterio_dtype"] = str(riods.dtypes[0])
+    # remove duplicate coordinate information
+    for coord in result.coords:
+        result.attrs = {
+            attr: value
+            for attr, value in result.attrs.items()
+            if not attr.startswith(f"{coord}#")
+        }
+    # remove duplicate tags
+    if result.name:
+        result.attrs = {
+            attr: value
+            for attr, value in result.attrs.items()
+            if not attr.startswith(f"{result.name}#")
+        }
     return result
diff --git a/rioxarray/raster_writer.py b/rioxarray/raster_writer.py
index c737a03b..7d49d35c 100644
--- a/rioxarray/raster_writer.py
+++ b/rioxarray/raster_writer.py
@@ -15,6 +15,7 @@ import rasterio
 from rasterio.windows import Window
 from xarray.conventions import encode_cf_variable
 
+from rioxarray._io import _get_unsigned_dtype
 from rioxarray.exceptions import RioXarrayError
 
 try:
@@ -39,7 +40,11 @@ def _write_metatata_to_raster(raster_handle, xarray_dataset, tags):
     """
     Write the metadata stored in the xarray object to raster metadata
     """
-    tags = xarray_dataset.attrs if tags is None else {**xarray_dataset.attrs, **tags}
+    tags = (
+        xarray_dataset.attrs.copy()
+        if tags is None
+        else {**xarray_dataset.attrs, **tags}
+    )
 
     # write scales and offsets
     try:
@@ -224,11 +229,25 @@ class RasterioWriter:
         **kwargs
             Keyword arguments to pass into writing the raster.
         """
+        xarray_dataarray = xarray_dataarray.copy()
         kwargs["dtype"], numpy_dtype = _get_dtypes(
             kwargs["dtype"],
             xarray_dataarray.encoding.get("rasterio_dtype"),
             xarray_dataarray.encoding.get("dtype", str(xarray_dataarray.dtype)),
         )
+        # there is no equivalent for netCDF _Unsigned
+        # across output GDAL formats. It is safest to convert beforehand.
+        # https://github.com/OSGeo/gdal/issues/6352#issuecomment-1245981837
+        if "_Unsigned" in xarray_dataarray.encoding:
+            unsigned_dtype = _get_unsigned_dtype(
+                unsigned=xarray_dataarray.encoding["_Unsigned"] == "true",
+                dtype=numpy_dtype,
+            )
+            if unsigned_dtype is not None:
+                numpy_dtype = unsigned_dtype
+                kwargs["dtype"] = unsigned_dtype
+                xarray_dataarray.encoding["rasterio_dtype"] = str(unsigned_dtype)
+                xarray_dataarray.encoding["dtype"] = str(unsigned_dtype)
 
         if kwargs["nodata"] is not None:
             # Ensure dtype of output data matches the expected dtype.


Test patch:
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 5881c483..6a8f4f16 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -136,6 +136,7 @@ jobs:
           conda info
 
       - name: pylint
+        if: matrix.python-version == '3.8'
         shell: bash
         run: |
           source activate test
@@ -143,6 +144,7 @@ jobs:
 
       - name: mypy
         shell: bash
+        if: matrix.python-version == '3.8'
         run: |
           source activate test
           mypy rioxarray/
diff --git a/test/integration/test_integration__io.py b/test/integration/test_integration__io.py
index 8cf7f62c..22ec6bef 100644
--- a/test/integration/test_integration__io.py
+++ b/test/integration/test_integration__io.py
@@ -23,7 +23,6 @@ from rasterio.errors import NotGeoreferencedWarning
 from rasterio.transform import from_origin
 from rasterio.warp import calculate_default_transform
 from xarray import DataArray
-from xarray.coding.variables import SerializationWarning
 from xarray.testing import assert_allclose, assert_equal, assert_identical
 
 import rioxarray
@@ -258,11 +257,16 @@ def test_open_group_load_attrs(open_rasterio):
     ) as rds:
         attrs = rds["sur_refl_b05_1"].attrs
         assert sorted(attrs) == [
+            "Nadir Data Resolution",
             "_FillValue",
             "add_offset",
+            "add_offset_err",
+            "calibrated_nt",
             "long_name",
             "scale_factor",
+            "scale_factor_err",
             "units",
+            "valid_range",
         ]
         assert attrs["long_name"] == "500m Surface Reflectance Band 5 - first layer"
         assert attrs["units"] == "reflectance"
@@ -299,6 +303,7 @@ def test_open_rasterio_mask_chunk_clip():
             (3.0, 0.0, 425047.68381405267, 0.0, -3.0, 4615780.040546387),
         )
         assert attrs == {
+            "AREA_OR_POINT": "Area",
             "add_offset": 0.0,
             "scale_factor": 1.0,
         }
@@ -967,35 +972,33 @@ def test_open_cog(lock):
 
 def test_mask_and_scale(open_rasterio):
     test_file = os.path.join(TEST_INPUT_DATA_DIR, "tmmx_20190121.nc")
-    with pytest.warns(SerializationWarning):
-        with open_rasterio(test_file, mask_and_scale=True) as rds:
-            assert np.nanmin(rds.air_temperature.values) == np.float32(248.7)
-            assert np.nanmax(rds.air_temperature.values) == np.float32(302.1)
-            test_encoding = dict(rds.air_temperature.encoding)
-            source = test_encoding.pop("source")
-            assert source.startswith("netcdf:") and source.endswith(
-                "tmmx_20190121.nc:air_temperature"
-            )
-            assert test_encoding == {
-                "_Unsigned": "true",
-                "add_offset": 220.0,
-                "scale_factor": 0.1,
-                "_FillValue": 32767.0,
-                "missing_value": 32767,
-                "grid_mapping": "crs",
-                "dtype": "uint16",
-                "rasterio_dtype": "uint16",
-            }
-            attrs = rds.air_temperature.attrs
-            assert attrs == {
-                "coordinates": "day",
-                "coordinate_system": "WGS84,EPSG:4326",
-                "description": "Daily Maximum Temperature",
-                "dimensions": "lon lat time",
-                "long_name": "tmmx",
-                "standard_name": "tmmx",
-                "units": "K",
-            }
+    with open_rasterio(test_file, mask_and_scale=True) as rds:
+        assert np.nanmin(rds.air_temperature.values) == np.float32(248.7)
+        assert np.nanmax(rds.air_temperature.values) == np.float32(302.1)
+        test_encoding = dict(rds.air_temperature.encoding)
+        source = test_encoding.pop("source")
+        assert source.startswith("netcdf:") and source.endswith(
+            "tmmx_20190121.nc:air_temperature"
+        )
+        assert test_encoding == {
+            "_Unsigned": "true",
+            "add_offset": 220.0,
+            "scale_factor": 0.1,
+            "_FillValue": 32767.0,
+            "missing_value": 32767,
+            "grid_mapping": "crs",
+            "dtype": "uint16",
+            "rasterio_dtype": "uint16",
+        }
+        attrs = rds.air_temperature.attrs
+        assert attrs == {
+            "coordinates": "day",
+            "description": "Daily Maximum Temperature",
+            "dimensions": "lon lat time",
+            "long_name": "tmmx",
+            "standard_name": "tmmx",
+            "units": "K",
+        }
 
 
 def test_no_mask_and_scale(open_rasterio):
@@ -1023,7 +1026,6 @@ def test_no_mask_and_scale(open_rasterio):
             "_Unsigned": "true",
             "add_offset": 220.0,
             "coordinates": "day",
-            "coordinate_system": "WGS84,EPSG:4326",
             "description": "Daily Maximum Temperature",
             "dimensions": "lon lat time",
             "long_name": "tmmx",
@@ -1036,16 +1038,44 @@ def test_no_mask_and_scale(open_rasterio):
 def test_mask_and_scale__to_raster(open_rasterio, tmp_path):
     test_file = os.path.join(TEST_INPUT_DATA_DIR, "tmmx_20190121.nc")
     tmp_output = tmp_path / "tmmx_20190121.tif"
-    with pytest.warns(SerializationWarning):
-        with open_rasterio(test_file, mask_and_scale=True) as rds:
-            rds.air_temperature.rio.to_raster(str(tmp_output))
-            with rasterio.open(str(tmp_output)) as riofh:
-                assert riofh.scales == (0.1,)
-                assert riofh.offsets == (220.0,)
-                assert riofh.nodata == 32767.0
-                data = riofh.read(1, masked=True)
-                assert data.min() == 287
-                assert data.max() == 821
+    with open_rasterio(test_file, mask_and_scale=True) as rds:
+        rds.air_temperature.rio.to_raster(str(tmp_output))
+        with rasterio.open(str(tmp_output)) as riofh:
+            assert riofh.scales == (0.1,)
+            assert riofh.offsets == (220.0,)
+            assert riofh.nodata == 32767.0
+            data = riofh.read(1, masked=True)
+            assert data.min() == 287
+            assert data.max() == 821
+
+
+def test_mask_and_scale__unicode(open_rasterio):
+    test_file = os.path.join(TEST_INPUT_DATA_DIR, "unicode.nc")
+    with open_rasterio(test_file, mask_and_scale=True) as rds:
+        assert np.nanmin(rds.LST.values) == np.float32(270.4925)
+        assert np.nanmax(rds.LST.values) == np.float32(276.6025)
+        test_encoding = dict(rds.LST.encoding)
+        assert test_encoding["_Unsigned"] == "true"
+        assert test_encoding["add_offset"] == 190
+        assert test_encoding["scale_factor"] == pytest.approx(0.0025)
+        assert test_encoding["_FillValue"] == 65535
+        assert test_encoding["dtype"] == "int16"
+        assert test_encoding["rasterio_dtype"] == "int16"
+
+
+def test_mask_and_scale__unicode__to_raster(open_rasterio, tmp_path):
+    tmp_output = tmp_path / "unicode.tif"
+    test_file = os.path.join(TEST_INPUT_DATA_DIR, "unicode.nc")
+    with open_rasterio(test_file, mask_and_scale=True) as rds:
+        rds.LST.rio.to_raster(str(tmp_output))
+        with rasterio.open(str(tmp_output)) as riofh:
+            assert riofh.scales == (pytest.approx(0.0025),)
+            assert riofh.offsets == (190,)
+            assert riofh.nodata == 65535
+            data = riofh.read(1, masked=True)
+            assert data.min() == 32197
+            assert data.max() == 34641
+            assert riofh.dtypes == ("uint16",)
 
 
 def test_notgeoreferenced_warning(open_rasterio):
diff --git a/test/integration/test_integration_rioxarray.py b/test/integration/test_integration_rioxarray.py
index c62d3ba8..ba099d88 100644
--- a/test/integration/test_integration_rioxarray.py
+++ b/test/integration/test_integration_rioxarray.py
@@ -1558,6 +1558,7 @@ def test_to_raster(
 def test_to_raster_3d(open_method, windowed, write_lock, compute, tmpdir):
     tmp_raster = tmpdir.join("planet_3d_raster.tif")
     with open_method(os.path.join(TEST_INPUT_DATA_DIR, "PLANET_SCOPE_3D.nc")) as mda:
+        assert sorted(mda.coords) == ["spatial_ref", "time", "x", "y"]
         xds = mda.green.fillna(mda.green.rio.encoded_nodata)
         xds.rio._nodata = mda.green.rio.encoded_nodata
         delayed = xds.rio.to_raster(