diff --git a/satpy/_scene_converters.py b/satpy/_scene_converters.py
index ba4432a58f..c5c0b1c896 100644
--- a/satpy/_scene_converters.py
+++ b/satpy/_scene_converters.py
@@ -66,7 +66,7 @@ def to_xarray(scn,
epoch (str):
Reference time for encoding the time coordinates (if available).
Example format: "seconds since 1970-01-01 00:00:00".
- If None, the default reference time is retrieved using "from satpy.cf_writer import EPOCH"
+ If None, the default reference time is retrieved using "from satpy.writers.cf import EPOCH"
flatten_attrs (bool):
If True, flatten dict-type attributes.
exclude_attrs (list):
@@ -90,8 +90,8 @@ def to_xarray(scn,
A CF-compliant xr.Dataset
"""
+ from satpy.writers.cf import EPOCH
from satpy.writers.cf.datasets import collect_cf_datasets
- from satpy.writers.cf.time import EPOCH
if epoch is None:
epoch = EPOCH
diff --git a/satpy/scene.py b/satpy/scene.py
index e3e71811e9..52580d14e8 100644
--- a/satpy/scene.py
+++ b/satpy/scene.py
@@ -1128,7 +1128,7 @@ def to_xarray(self,
epoch (str):
Reference time for encoding the time coordinates (if available).
Example format: "seconds since 1970-01-01 00:00:00".
- If None, the default reference time is retrieved using "from satpy.cf_writer import EPOCH"
+ If None, the default reference time is retrieved using "from satpy.writers.cf import EPOCH"
flatten_attrs (bool):
If True, flatten dict-type attributes.
exclude_attrs (list):
diff --git a/satpy/tests/utils.py b/satpy/tests/utils.py
index c87cd1055c..155916aca1 100644
--- a/satpy/tests/utils.py
+++ b/satpy/tests/utils.py
@@ -407,3 +407,18 @@ def assert_attrs_equal(attrs, attrs_exp, tolerance=0):
)
except TypeError:
assert attrs[key] == attrs_exp[key], err_msg
+
+
+def assert_dict_array_equality(d1, d2):
+ """Check that dicts containing arrays are equal."""
+ assert set(d1.keys()) == set(d2.keys())
+ for key, val1 in d1.items():
+ val2 = d2[key]
+ if isinstance(val1, np.ndarray):
+ np.testing.assert_array_equal(val1, val2)
+ assert val1.dtype == val2.dtype
+ else:
+ assert val1 == val2
+ if isinstance(val1, (np.floating, np.integer, np.bool_)):
+ assert isinstance(val2, np.generic)
+ assert val1.dtype == val2.dtype
diff --git a/satpy/tests/writer_tests/cf_tests/test_area.py b/satpy/tests/writer_tests/cf_tests/test_area.py
index 92088f6d68..5b7dd86d38 100644
--- a/satpy/tests/writer_tests/cf_tests/test_area.py
+++ b/satpy/tests/writer_tests/cf_tests/test_area.py
@@ -16,18 +16,12 @@
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see .
"""Tests for the CF Area."""
-import logging
-
import dask.array as da
import numpy as np
import pytest
import xarray as xr
from pyresample import AreaDefinition, SwathDefinition
-# NOTE:
-# The following fixtures are not defined in this file, but are used and injected by Pytest:
-# - caplog
-
class TestCFArea:
"""Test case for CF Area."""
@@ -406,32 +400,6 @@ def test__add_lonlat_coords(self):
assert {'name': 'latitude', 'standard_name': 'latitude', 'units': 'degrees_north'}.items() <= lat.attrs.items()
assert {'name': 'longitude', 'standard_name': 'longitude', 'units': 'degrees_east'}.items() <= lon.attrs.items()
- def test_is_projected(self, caplog):
- """Tests for private _is_projected function."""
- from satpy.writers.cf.crs import _is_projected
-
- # test case with units but no area
- da = xr.DataArray(
- np.arange(25).reshape(5, 5),
- dims=("y", "x"),
- coords={"x": xr.DataArray(np.arange(5), dims=("x",), attrs={"units": "m"}),
- "y": xr.DataArray(np.arange(5), dims=("y",), attrs={"units": "m"})})
- assert _is_projected(da)
-
- da = xr.DataArray(
- np.arange(25).reshape(5, 5),
- dims=("y", "x"),
- coords={"x": xr.DataArray(np.arange(5), dims=("x",), attrs={"units": "degrees_east"}),
- "y": xr.DataArray(np.arange(5), dims=("y",), attrs={"units": "degrees_north"})})
- assert not _is_projected(da)
-
- da = xr.DataArray(
- np.arange(25).reshape(5, 5),
- dims=("y", "x"))
- with caplog.at_level(logging.WARNING):
- assert _is_projected(da)
- assert "Failed to tell if data are projected." in caplog.text
-
@pytest.fixture
def datasets(self):
"""Create test dataset."""
diff --git a/satpy/tests/writer_tests/cf_tests/test_attrs.py b/satpy/tests/writer_tests/cf_tests/test_attrs.py
index a969765181..6988e761ee 100644
--- a/satpy/tests/writer_tests/cf_tests/test_attrs.py
+++ b/satpy/tests/writer_tests/cf_tests/test_attrs.py
@@ -109,30 +109,16 @@ def get_test_attrs(self):
'raw_metadata_dict_b': np.array([1, 2, 3], dtype='uint8')}
return attrs, encoded, encoded_flat
- def assertDictWithArraysEqual(self, d1, d2):
- """Check that dicts containing arrays are equal."""
- # TODO: this is also used by test_da2cf
- assert set(d1.keys()) == set(d2.keys())
- for key, val1 in d1.items():
- val2 = d2[key]
- if isinstance(val1, np.ndarray):
- np.testing.assert_array_equal(val1, val2)
- assert val1.dtype == val2.dtype
- else:
- assert val1 == val2
- if isinstance(val1, (np.floating, np.integer, np.bool_)):
- assert isinstance(val2, np.generic)
- assert val1.dtype == val2.dtype
-
def test__encode_attrs_nc(self):
"""Test attributes encoding."""
+ from satpy.tests.utils import assert_dict_array_equality
from satpy.writers.cf.attrs import _encode_attrs_nc
attrs, expected, _ = self.get_test_attrs()
# Test encoding
encoded = _encode_attrs_nc(attrs)
- self.assertDictWithArraysEqual(expected, encoded)
+ assert_dict_array_equality(expected, encoded)
# Test decoding of json-encoded attributes
raw_md_roundtrip = {'recarray': [[0, 0], [0, 0], [0, 0]],
diff --git a/satpy/tests/writer_tests/cf_tests/test_time_coords.py b/satpy/tests/writer_tests/cf_tests/test_coords.py
similarity index 56%
rename from satpy/tests/writer_tests/cf_tests/test_time_coords.py
rename to satpy/tests/writer_tests/cf_tests/test_coords.py
index ce7845dcca..9e9d8c4607 100644
--- a/satpy/tests/writer_tests/cf_tests/test_time_coords.py
+++ b/satpy/tests/writer_tests/cf_tests/test_coords.py
@@ -16,16 +16,22 @@
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see .
"""CF processing of time information (coordinates and dimensions)."""
+import logging
+
import numpy as np
import xarray as xr
+# NOTE:
+# The following fixtures are not defined in this file, but are used and injected by Pytest:
+# - caplog
+
class TestCFtime:
"""Test cases for CF time dimension and coordinates."""
def test_add_time_bounds_dimension(self):
"""Test addition of CF-compliant time attributes."""
- from satpy.writers.cf.time import add_time_bounds_dimension
+ from satpy.writers.cf.coords import add_time_bounds_dimension
test_array = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
times = np.array(['2018-05-30T10:05:00', '2018-05-30T10:05:01',
@@ -42,3 +48,36 @@ def test_add_time_bounds_dimension(self):
assert "time_bnds" in list(ds.data_vars)
assert "bounds" in ds["time"].attrs
assert "standard_name" in ds["time"].attrs
+
+
+class TestCFcoords:
+ """Test cases for CF spatial dimension and coordinates."""
+
+ def test_is_projected(self, caplog):
+ """Tests for private _is_projected function."""
+ from satpy.writers.cf.coords import _is_projected
+
+ # test case with units but no area
+ da = xr.DataArray(
+ np.arange(25).reshape(5, 5),
+ dims=("y", "x"),
+ coords={"x": xr.DataArray(np.arange(5), dims=("x",), attrs={"units": "m"}),
+ "y": xr.DataArray(np.arange(5), dims=("y",), attrs={"units": "m"})})
+ assert _is_projected(da)
+
+ da = xr.DataArray(
+ np.arange(25).reshape(5, 5),
+ dims=("y", "x"),
+ coords={"x": xr.DataArray(np.arange(5), dims=("x",), attrs={"units": "degrees_east"}),
+ "y": xr.DataArray(np.arange(5), dims=("y",), attrs={"units": "degrees_north"})})
+ assert not _is_projected(da)
+
+ da = xr.DataArray(
+ np.arange(25).reshape(5, 5),
+ dims=("y", "x"))
+ with caplog.at_level(logging.WARNING):
+ assert _is_projected(da)
+ assert "Failed to tell if data are projected." in caplog.text
+
+ # add_xy_coords_attrs
+ # process_time_coord
diff --git a/satpy/tests/writer_tests/cf_tests/test_dataaarray.py b/satpy/tests/writer_tests/cf_tests/test_dataaarray.py
index a67cae9ca2..43b87cfc20 100644
--- a/satpy/tests/writer_tests/cf_tests/test_dataaarray.py
+++ b/satpy/tests/writer_tests/cf_tests/test_dataaarray.py
@@ -154,23 +154,9 @@ def get_test_attrs(self):
'raw_metadata_dict_b': np.array([1, 2, 3], dtype='uint8')}
return attrs, encoded, encoded_flat
- def assertDictWithArraysEqual(self, d1, d2):
- """Check that dicts containing arrays are equal."""
- # TODO: also used by cf/test_attrs.py
- assert set(d1.keys()) == set(d2.keys())
- for key, val1 in d1.items():
- val2 = d2[key]
- if isinstance(val1, np.ndarray):
- np.testing.assert_array_equal(val1, val2)
- assert val1.dtype == val2.dtype
- else:
- assert val1 == val2
- if isinstance(val1, (np.floating, np.integer, np.bool_)):
- assert isinstance(val2, np.generic)
- assert val1.dtype == val2.dtype
-
def test_make_cf_dataarray(self):
"""Test the conversion of a DataArray to a CF-compatible DataArray."""
+ from satpy.tests.utils import assert_dict_array_equality
from satpy.writers.cf.dataarray import make_cf_dataarray
# Create set of test attributes
@@ -200,12 +186,12 @@ def test_make_cf_dataarray(self):
np.testing.assert_array_equal(res['acq_time'], arr['acq_time'])
assert res['x'].attrs == {'units': 'm', 'standard_name': 'projection_x_coordinate'}
assert res['y'].attrs == {'units': 'm', 'standard_name': 'projection_y_coordinate'}
- self.assertDictWithArraysEqual(res.attrs, attrs_expected)
+ assert_dict_array_equality(res.attrs, attrs_expected)
# Test attribute kwargs
res_flat = make_cf_dataarray(arr, flatten_attrs=True, exclude_attrs=['int'])
attrs_expected_flat.pop('int')
- self.assertDictWithArraysEqual(res_flat.attrs, attrs_expected_flat)
+ assert_dict_array_equality(res_flat.attrs, attrs_expected_flat)
def test_make_cf_dataarray_one_dimensional_array(self):
"""Test the conversion of an 1d DataArray to a CF-compatible DataArray."""
@@ -214,3 +200,5 @@ def test_make_cf_dataarray_one_dimensional_array(self):
arr = xr.DataArray(np.array([1, 2, 3, 4]), attrs={}, dims=('y',),
coords={'y': [0, 1, 2, 3], 'acq_time': ('y', [0, 1, 2, 3])})
_ = make_cf_dataarray(arr)
+
+ # _handle_dataarray_name
diff --git a/satpy/tests/writer_tests/cf_tests/test_datasets.py b/satpy/tests/writer_tests/cf_tests/test_datasets.py
index b094feecbc..d92099e869 100644
--- a/satpy/tests/writer_tests/cf_tests/test_datasets.py
+++ b/satpy/tests/writer_tests/cf_tests/test_datasets.py
@@ -15,7 +15,7 @@
#
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see .
-"""Tests CF-compliant DataArray creation."""
+"""Tests CF-compliant Dataset(s) creation."""
import datetime
import numpy as np
@@ -24,18 +24,10 @@
from pyresample import AreaDefinition, create_area_def
-def test_empty_collect_cf_datasets():
- """Test that if no DataArrays, collect_cf_datasets raise error."""
- from satpy.writers.cf.datasets import collect_cf_datasets
-
- with pytest.raises(RuntimeError):
- collect_cf_datasets(list_dataarrays=[])
-
-
-class TestCollectCfDatasets:
+class TestCollectCfDataset:
"""Test case for collect_cf_dataset."""
- def test_collect_cf_dataarrays(self):
+ def test_collect_cf_dataset(self):
"""Test collecting CF datasets from a DataArray objects."""
from satpy.writers.cf.datasets import _collect_cf_dataset
@@ -75,7 +67,7 @@ def test_collect_cf_dataarrays(self):
assert 'grid_mapping' not in da_var2.attrs
assert da_var2.attrs['long_name'] == 'variable 2'
- def test_collect_cf_dataarrays_with_latitude_named_lat(self):
+ def test_collect_cf_dataset_with_latitude_named_lat(self):
"""Test collecting CF datasets with latitude named lat."""
from satpy.writers.cf.datasets import _collect_cf_dataset
@@ -148,3 +140,14 @@ def test_geographic_area_coords_attrs(self):
assert ds["mavas"].attrs["longitude_of_prime_meridian"] == 0.0
np.testing.assert_allclose(ds["mavas"].attrs["semi_major_axis"], 6378137.0)
np.testing.assert_allclose(ds["mavas"].attrs["inverse_flattening"], 298.257223563)
+
+
+class TestCollectCfDatasets:
+ """Test case for collect_cf_datasets."""
+
+ def test_empty_collect_cf_datasets(self):
+ """Test that if no DataArrays, collect_cf_datasets raise error."""
+ from satpy.writers.cf.datasets import collect_cf_datasets
+
+ with pytest.raises(RuntimeError):
+ collect_cf_datasets(list_dataarrays=[])
diff --git a/satpy/tests/writer_tests/cf_tests/test_encoding.py b/satpy/tests/writer_tests/cf_tests/test_encoding.py
index 66f7c72a48..125c7eec94 100644
--- a/satpy/tests/writer_tests/cf_tests/test_encoding.py
+++ b/satpy/tests/writer_tests/cf_tests/test_encoding.py
@@ -22,8 +22,8 @@
import xarray as xr
-class TestUpdateDatasetEncodings:
- """Test update of Dataset encodings."""
+class TestUpdateEncoding:
+ """Test update of dataset encodings."""
@pytest.fixture
def fake_ds(self):
diff --git a/satpy/writers/cf/__init__.py b/satpy/writers/cf/__init__.py
index f597a9264c..c48acebcf9 100644
--- a/satpy/writers/cf/__init__.py
+++ b/satpy/writers/cf/__init__.py
@@ -1,3 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Code for generation of CF-compliant datasets."""
+
+EPOCH = u"seconds since 1970-01-01 00:00:00"
diff --git a/satpy/writers/cf/coords.py b/satpy/writers/cf/coords.py
new file mode 100644
index 0000000000..dee28952b5
--- /dev/null
+++ b/satpy/writers/cf/coords.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""Set CF-compliant spatial and temporal coordinates."""
+
+import logging
+from contextlib import suppress
+
+import numpy as np
+import xarray as xr
+from pyresample.geometry import AreaDefinition, SwathDefinition
+
+logger = logging.getLogger(__name__)
+
+
+def add_xy_coords_attrs(dataarray):
+ """Add relevant attributes to x, y coordinates."""
+ # If there are no coords, return dataarray
+ if not dataarray.coords.keys() & {"x", "y", "crs"}:
+ return dataarray
+ # If projected area
+ if _is_projected(dataarray):
+ dataarray = _add_xy_projected_coords_attrs(dataarray)
+ else:
+ dataarray = _add_xy_geographic_coords_attrs(dataarray)
+ if 'crs' in dataarray.coords:
+ dataarray = dataarray.drop_vars('crs')
+ return dataarray
+
+
+def _is_projected(dataarray):
+ """Guess whether data are projected or not."""
+ crs = _try_to_get_crs(dataarray)
+ if crs:
+ return crs.is_projected
+ units = _try_get_units_from_coords(dataarray)
+ if units:
+ if units.endswith("m"):
+ return True
+ if units.startswith("degrees"):
+ return False
+ logger.warning("Failed to tell if data are projected. Assuming yes.")
+ return True
+
+
+def _try_to_get_crs(dataarray):
+ """Try to get a CRS from attributes."""
+ if "area" in dataarray.attrs:
+ if isinstance(dataarray.attrs["area"], AreaDefinition):
+ return dataarray.attrs["area"].crs
+ if not isinstance(dataarray.attrs["area"], SwathDefinition):
+ logger.warning(
+ f"Could not tell CRS from area of type {type(dataarray.attrs['area']).__name__:s}. "
+ "Assuming projected CRS.")
+ if "crs" in dataarray.coords:
+ return dataarray.coords["crs"].item()
+
+
+def _try_get_units_from_coords(dataarray):
+ """Try to retrieve coordinate x/y units."""
+ for c in ["x", "y"]:
+ with suppress(KeyError):
+ # If the data has only 1 dimension, it has only one of x or y coords
+ if "units" in dataarray.coords[c].attrs:
+ return dataarray.coords[c].attrs["units"]
+
+
+def _add_xy_projected_coords_attrs(dataarray, x='x', y='y'):
+ """Add relevant attributes to x, y coordinates of a projected CRS."""
+ if x in dataarray.coords:
+ dataarray[x].attrs['standard_name'] = 'projection_x_coordinate'
+ dataarray[x].attrs['units'] = 'm'
+ if y in dataarray.coords:
+ dataarray[y].attrs['standard_name'] = 'projection_y_coordinate'
+ dataarray[y].attrs['units'] = 'm'
+ return dataarray
+
+
+def _add_xy_geographic_coords_attrs(dataarray, x='x', y='y'):
+ """Add relevant attributes to x, y coordinates of a geographic CRS."""
+ if x in dataarray.coords:
+ dataarray[x].attrs['standard_name'] = 'longitude'
+ dataarray[x].attrs['units'] = 'degrees_east'
+ if y in dataarray.coords:
+ dataarray[y].attrs['standard_name'] = 'latitude'
+ dataarray[y].attrs['units'] = 'degrees_north'
+ return dataarray
+
+
+def add_time_bounds_dimension(ds, time="time"):
+ """Add time bound dimension to xr.Dataset."""
+ start_times = []
+ end_times = []
+ for _var_name, data_array in ds.items():
+ start_times.append(data_array.attrs.get("start_time", None))
+ end_times.append(data_array.attrs.get("end_time", None))
+
+ start_time = min(start_time for start_time in start_times
+ if start_time is not None)
+ end_time = min(end_time for end_time in end_times
+ if end_time is not None)
+ ds['time_bnds'] = xr.DataArray([[np.datetime64(start_time),
+ np.datetime64(end_time)]],
+ dims=['time', 'bnds_1d'])
+ ds[time].attrs['bounds'] = "time_bnds"
+ ds[time].attrs['standard_name'] = "time"
+ return ds
+
+
+def process_time_coord(dataarray, epoch):
+ """Process the 'time' coordinate, if existing.
+
+ It expand the DataArray with a time dimension if does not yet exists.
+
+ The function assumes
+
+ - that x and y dimensions have at least shape > 1
+ - the time coordinate has size 1
+
+ """
+ if 'time' in dataarray.coords:
+ dataarray['time'].encoding['units'] = epoch
+ dataarray['time'].attrs['standard_name'] = 'time'
+ dataarray['time'].attrs.pop('bounds', None)
+
+ if 'time' not in dataarray.dims and dataarray["time"].size not in dataarray.shape:
+ dataarray = dataarray.expand_dims('time')
+
+ return dataarray
diff --git a/satpy/writers/cf/coords_attrs.py b/satpy/writers/cf/coords_attrs.py
deleted file mode 100644
index c7e559adc2..0000000000
--- a/satpy/writers/cf/coords_attrs.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""Set CF-compliant attributes to x and y spatial dimensions."""
-
-import logging
-
-from satpy.writers.cf.crs import _is_projected
-
-logger = logging.getLogger(__name__)
-
-
-def add_xy_coords_attrs(dataarray):
- """Add relevant attributes to x, y coordinates."""
- # If there are no coords, return dataarray
- if not dataarray.coords.keys() & {"x", "y", "crs"}:
- return dataarray
- # If projected area
- if _is_projected(dataarray):
- dataarray = _add_xy_projected_coords_attrs(dataarray)
- else:
- dataarray = _add_xy_geographic_coords_attrs(dataarray)
- if 'crs' in dataarray.coords:
- dataarray = dataarray.drop_vars('crs')
- return dataarray
-
-
-def _add_xy_projected_coords_attrs(dataarray, x='x', y='y'):
- """Add relevant attributes to x, y coordinates of a projected CRS."""
- if x in dataarray.coords:
- dataarray[x].attrs['standard_name'] = 'projection_x_coordinate'
- dataarray[x].attrs['units'] = 'm'
- if y in dataarray.coords:
- dataarray[y].attrs['standard_name'] = 'projection_y_coordinate'
- dataarray[y].attrs['units'] = 'm'
- return dataarray
-
-
-def _add_xy_geographic_coords_attrs(dataarray, x='x', y='y'):
- """Add relevant attributes to x, y coordinates of a geographic CRS."""
- if x in dataarray.coords:
- dataarray[x].attrs['standard_name'] = 'longitude'
- dataarray[x].attrs['units'] = 'degrees_east'
- if y in dataarray.coords:
- dataarray[y].attrs['standard_name'] = 'latitude'
- dataarray[y].attrs['units'] = 'degrees_north'
- return dataarray
diff --git a/satpy/writers/cf/crs.py b/satpy/writers/cf/crs.py
deleted file mode 100644
index e6952a484f..0000000000
--- a/satpy/writers/cf/crs.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""CRS utility."""
-
-import logging
-from contextlib import suppress
-
-from pyresample.geometry import AreaDefinition, SwathDefinition
-
-logger = logging.getLogger(__name__)
-
-
-def _is_projected(dataarray):
- """Guess whether data are projected or not."""
- crs = _try_to_get_crs(dataarray)
- if crs:
- return crs.is_projected
- units = _try_get_units_from_coords(dataarray)
- if units:
- if units.endswith("m"):
- return True
- if units.startswith("degrees"):
- return False
- logger.warning("Failed to tell if data are projected. Assuming yes.")
- return True
-
-
-def _try_to_get_crs(dataarray):
- """Try to get a CRS from attributes."""
- if "area" in dataarray.attrs:
- if isinstance(dataarray.attrs["area"], AreaDefinition):
- return dataarray.attrs["area"].crs
- if not isinstance(dataarray.attrs["area"], SwathDefinition):
- logger.warning(
- f"Could not tell CRS from area of type {type(dataarray.attrs['area']).__name__:s}. "
- "Assuming projected CRS.")
- if "crs" in dataarray.coords:
- return dataarray.coords["crs"].item()
-
-
-def _try_get_units_from_coords(dataarray):
- """Try to retrieve coordinate x/y units."""
- for c in ["x", "y"]:
- with suppress(KeyError):
- # If the data has only 1 dimension, it has only one of x or y coords
- if "units" in dataarray.coords[c].attrs:
- return dataarray.coords[c].attrs["units"]
diff --git a/satpy/writers/cf/dataarray.py b/satpy/writers/cf/dataarray.py
index a5322cfee4..df52406f96 100644
--- a/satpy/writers/cf/dataarray.py
+++ b/satpy/writers/cf/dataarray.py
@@ -19,9 +19,9 @@
import logging
import warnings
+from satpy.writers.cf import EPOCH
from satpy.writers.cf.attrs import preprocess_datarray_attrs
-from satpy.writers.cf.coords_attrs import add_xy_coords_attrs
-from satpy.writers.cf.time import EPOCH, process_time_coord
+from satpy.writers.cf.coords import add_xy_coords_attrs, process_time_coord
logger = logging.getLogger(__name__)
diff --git a/satpy/writers/cf/datasets.py b/satpy/writers/cf/datasets.py
index c87e6673d4..0cdf2b8210 100644
--- a/satpy/writers/cf/datasets.py
+++ b/satpy/writers/cf/datasets.py
@@ -22,7 +22,7 @@
import xarray as xr
-from satpy.writers.cf.time import EPOCH
+from satpy.writers.cf import EPOCH
from satpy.writers.cf_writer import CF_DTYPES, CF_VERSION
logger = logging.getLogger(__name__)
@@ -77,7 +77,7 @@ def _collect_cf_dataset(list_dataarrays,
epoch : str
Reference time for encoding the time coordinates (if available).
Example format: "seconds since 1970-01-01 00:00:00".
- If None, the default reference time is retrieved using `from satpy.cf_writer import EPOCH`
+ If None, the default reference time is retrieved using `from satpy.writers.cf import EPOCH`
flatten_attrs : bool, optional
If True, flatten dict-type attributes.
exclude_attrs : list, optional
@@ -197,7 +197,7 @@ def collect_cf_datasets(list_dataarrays,
epoch (str):
Reference time for encoding the time coordinates (if available).
Example format: "seconds since 1970-01-01 00:00:00".
- If None, the default reference time is retrieved using `from satpy.cf_writer import EPOCH`
+ If None, the default reference time is retrieved using `from satpy.writers.cf import EPOCH`
flatten_attrs (bool):
If True, flatten dict-type attributes.
exclude_attrs (list):
@@ -228,7 +228,7 @@ def collect_cf_datasets(list_dataarrays,
Global attributes to be attached to the xr.Dataset / netCDF4.
"""
from satpy.writers.cf.attrs import preprocess_header_attrs
- from satpy.writers.cf.time import add_time_bounds_dimension
+ from satpy.writers.cf.coords import add_time_bounds_dimension
if not list_dataarrays:
raise RuntimeError("None of the requested datasets have been "
diff --git a/satpy/writers/cf/time.py b/satpy/writers/cf/time.py
deleted file mode 100644
index 4c5cbf5bc9..0000000000
--- a/satpy/writers/cf/time.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Copyright (c) 2017-2023 Satpy developers
-#
-# This file is part of satpy.
-#
-# satpy is free software: you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation, either version 3 of the License, or (at your option) any later
-# version.
-#
-# satpy is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# satpy. If not, see .
-"""CF processing of time dimension and coordinates."""
-import logging
-
-import numpy as np
-import xarray as xr
-
-logger = logging.getLogger(__name__)
-
-
-EPOCH = u"seconds since 1970-01-01 00:00:00"
-
-
-def add_time_bounds_dimension(ds, time="time"):
- """Add time bound dimension to xr.Dataset."""
- start_times = []
- end_times = []
- for _var_name, data_array in ds.items():
- start_times.append(data_array.attrs.get("start_time", None))
- end_times.append(data_array.attrs.get("end_time", None))
-
- start_time = min(start_time for start_time in start_times
- if start_time is not None)
- end_time = min(end_time for end_time in end_times
- if end_time is not None)
- ds['time_bnds'] = xr.DataArray([[np.datetime64(start_time),
- np.datetime64(end_time)]],
- dims=['time', 'bnds_1d'])
- ds[time].attrs['bounds'] = "time_bnds"
- ds[time].attrs['standard_name'] = "time"
- return ds
-
-
-def process_time_coord(dataarray, epoch):
- """Process the 'time' coordinate, if existing.
-
- It expand the DataArray with a time dimension if does not yet exists.
-
- The function assumes
-
- - that x and y dimensions have at least shape > 1
- - the time coordinate has size 1
-
- """
- if 'time' in dataarray.coords:
- dataarray['time'].encoding['units'] = epoch
- dataarray['time'].attrs['standard_name'] = 'time'
- dataarray['time'].attrs.pop('bounds', None)
-
- if 'time' not in dataarray.dims and dataarray["time"].size not in dataarray.shape:
- dataarray = dataarray.expand_dims('time')
-
- return dataarray
diff --git a/satpy/writers/cf_writer.py b/satpy/writers/cf_writer.py
index 30ca7e784e..096293e2b7 100644
--- a/satpy/writers/cf_writer.py
+++ b/satpy/writers/cf_writer.py
@@ -163,7 +163,7 @@
from packaging.version import Version
from satpy.writers import Writer
-from satpy.writers.cf.time import EPOCH
+from satpy.writers.cf import EPOCH
logger = logging.getLogger(__name__)