diff --git a/src/xarray_grass/grass_interface.py b/src/xarray_grass/grass_interface.py index f66f8d3..5d5c7b0 100644 --- a/src/xarray_grass/grass_interface.py +++ b/src/xarray_grass/grass_interface.py @@ -314,7 +314,9 @@ def list_grass_objects(self, mapset: str = None) -> dict[list[str]]: @staticmethod def get_raster_info(raster_id: str) -> Info: - return gs.parse_command("r.info", map=raster_id, flags="ge") + result = gs.parse_command("r.info", map=raster_id, flags="ge") + # Strip quotes from string values (r.info returns quoted strings) + return {k: v.strip('"') if isinstance(v, str) else v for k, v in result.items()} @staticmethod def get_raster3d_info(raster3d_id): diff --git a/src/xarray_grass/to_grass.py b/src/xarray_grass/to_grass.py index 96e2c96..8987526 100644 --- a/src/xarray_grass/to_grass.py +++ b/src/xarray_grass/to_grass.py @@ -250,7 +250,7 @@ def _write_stds(self, data: xr.DataArray, dims: Mapping): # 1. Determine the temporal coordinate and type time_coord = data[dims["start_time"]] time_dtype = time_coord.dtype - time_unit = None # Initialize for absolute time case + time_unit = "" # Initialize for absolute time case if isinstance(time_dtype, np.dtypes.DateTime64DType): temporal_type = "absolute" elif np.issubdtype(time_dtype, np.integer): diff --git a/src/xarray_grass/xarray_grass.py b/src/xarray_grass/xarray_grass.py index 44c500f..409db35 100644 --- a/src/xarray_grass/xarray_grass.py +++ b/src/xarray_grass/xarray_grass.py @@ -258,7 +258,8 @@ def _set_cf_coordinates_attributes( for time_dim in time_dims: da[time_dim].attrs["axis"] = "T" da[time_dim].attrs["standard_name"] = "time" - da[time_dim].attrs["units"] = time_unit + if time_unit: # NetCDF does not accept units for absolute time + da[time_dim].attrs["units"] = time_unit da[x_coord].attrs["axis"] = "X" da[y_coord].attrs["axis"] = "Y" if self.grass_interface.is_latlon(): @@ -277,8 +278,8 @@ def _set_cf_coordinates_attributes( else: da[x_coord].attrs["standard_name"] = "projection_x_coordinate" da[y_coord].attrs["standard_name"] = "projection_y_coordinate" - da[x_coord].attrs["units"] = spatial_unit - da[y_coord].attrs["units"] = spatial_unit + da[x_coord].attrs["units"] = str(spatial_unit) + da[y_coord].attrs["units"] = str(spatial_unit) return da def _open_grass_raster(self, raster_name: str) -> xr.DataArray: @@ -304,8 +305,8 @@ def _open_grass_raster(self, raster_name: str) -> xr.DataArray: da_with_attrs.attrs["source"] = ",".join( [r_infos["source1"], r_infos["source2"]] ) - da_with_attrs.attrs["units"] = r_infos.get("units", "") - da_with_attrs.attrs["comment"] = r_infos.get("comments", "") + da_with_attrs.attrs["units"] = str(r_infos.get("units", "")) + da_with_attrs.attrs["comment"] = str(r_infos.get("comments", "")) # CF attributes "institution" and "references" # Do not correspond to a direct GRASS value. return da_with_attrs @@ -352,7 +353,7 @@ def _open_grass_strds(self, strds_name: str) -> xr.DataArray: ).values() strds_infos = self.grass_interface.get_stds_infos(strds_id, stds_type="strds") if strds_infos.temporal_type == "absolute": - time_unit = None + time_unit = "" else: time_unit = strds_infos.time_unit start_time_dim = f"start_time_{strds_name}" @@ -421,7 +422,7 @@ def _open_grass_str3ds(self, str3ds_name: str) -> xr.DataArray: ).values() strds_infos = self.grass_interface.get_stds_infos(str3ds_id, stds_type="str3ds") if strds_infos.temporal_type == "absolute": - time_unit = None + time_unit = "" else: time_unit = strds_infos.time_unit start_time_dim = f"start_time_{str3ds_name}" diff --git a/tests/test_xarray_grass.py b/tests/test_xarray_grass.py index 22deda0..adbade5 100644 --- a/tests/test_xarray_grass.py +++ b/tests/test_xarray_grass.py @@ -272,6 +272,7 @@ def test_attributes_separation(self, grass_i, temp_gisdb) -> None: raster=ACTUAL_RASTER_MAP, raster_3d=ACTUAL_RASTER3D_MAP, strds=ACTUAL_STRDS, + str3ds=RELATIVE_STR3DS, ) # Dataset-level attributes: only these should be present @@ -338,8 +339,8 @@ def test_attributes_separation(self, grass_i, temp_gisdb) -> None: assert test_dataset[time_coord].attrs["standard_name"] == "time" # For relative time coordinates, units should be present - if time_coord.startswith("start_time_") or time_coord.startswith( - "end_time_" + if time_coord.endswith(RELATIVE_STR3DS) or time_coord.endswith( + RELATIVE_STR3DS ): # This is a relative time coordinate, should have units assert "units" in test_dataset[time_coord].attrs