Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read wavelength ranges from netcdf #1544

Merged
merged 4 commits into from
Feb 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions satpy/dataset/dataid.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,37 @@ def convert(cls, wl):
return cls(*wl)
return wl

def to_cf(self):
"""Serialize for cf export."""
return str(self)

@classmethod
def from_cf(cls, blob):
"""Return a WavelengthRange from a cf blob."""
try:
obj = cls._read_cf_from_string_export(blob)
except TypeError:
obj = cls._read_cf_from_string_list(blob)
return obj

@classmethod
def _read_cf_from_string_export(cls, blob):
"""Read blob as a string created by `to_cf`."""
pattern = "{central:f} {unit:s} ({min:f}-{max:f} {unit2:s})"
from trollsift import Parser
parser = Parser(pattern)
res_dict = parser.parse(blob)
res_dict.pop('unit2')
obj = cls(**res_dict)
return obj

@classmethod
def _read_cf_from_string_list(cls, blob):
"""Read blob as a list of strings (legacy formatting)."""
min_wl, central_wl, max_wl, unit = blob
obj = cls(float(min_wl), float(central_wl), float(max_wl), unit)
return obj


class ModifierTuple(tuple):
"""A tuple holder for modifiers."""
Expand Down
9 changes: 6 additions & 3 deletions satpy/readers/satpy_cf_nc.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,14 @@
ancillary_variables: []

"""
from satpy.readers.file_handlers import BaseFileHandler
import logging
import itertools
import logging

import xarray as xr

from satpy import CHUNK_SIZE
from satpy.dataset.dataid import WavelengthRange
from satpy.readers.file_handlers import BaseFileHandler

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -251,7 +254,7 @@ def _dynamic_datasets(self):
ds_info['file_type'] = self.filetype_info['file_type']
ds_info['name'] = var_name
try:
ds_info['wavelength'] = tuple([float(wlength) for wlength in ds_info['wavelength'][0:3]])
ds_info['wavelength'] = WavelengthRange.from_cf(ds_info['wavelength'])
except KeyError:
pass
self.fix_modifier_attr(ds_info)
Expand Down
7 changes: 6 additions & 1 deletion satpy/tests/reader_tests/test_satpy_cf_nc.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import xarray as xr

from satpy import Scene
from satpy.dataset.dataid import WavelengthRange
from satpy.readers.satpy_cf_nc import SatpyCFFileHandler


Expand All @@ -52,7 +53,10 @@ def setUp(self):
vis006 = xr.DataArray(data_visir,
dims=('y', 'x'),
coords={'y': y_visir, 'x': x_visir, 'acq_time': ('y', time_vis006)},
attrs={'name': 'image0', 'id_tag': 'ch_r06', 'coordinates': 'lat lon'})
attrs={'name': 'image0', 'id_tag': 'ch_r06',
'coordinates': 'lat lon', 'resolution': 1000, 'calibration': 'reflectance',
'wavelength': WavelengthRange(min=0.58, central=0.63, max=0.68, unit='µm')
})

ir_108 = xr.DataArray(data_visir,
dims=('y', 'x'),
Expand Down Expand Up @@ -103,6 +107,7 @@ def test_write_and_read(self):
self.assertTrue(np.all(scn_['image0'].data == self.scene['image0'].data))
self.assertTrue(np.all(scn_['lat'].data == self.scene['lat'].data)) # lat loaded as dataset
self.assertTrue(np.all(scn_['image0'].coords['lon'] == self.scene['lon'].data)) # lon loded as coord
assert isinstance(scn_['image0'].attrs['wavelength'], WavelengthRange)
finally:
with suppress(PermissionError):
os.remove(filename)
Expand Down
10 changes: 10 additions & 0 deletions satpy/tests/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,3 +538,13 @@ def test_wavelength_range():
# Check __str__
assert str(wr) == "2 µm (1-3 µm)"
assert str(wr2) == "2 nm (1-3 nm)"


def test_wavelength_range_cf_roundtrip():
"""Test the wavelength range object roundtrip to cf."""
from satpy.dataset.dataid import WavelengthRange

wr = WavelengthRange(1, 2, 3)

assert WavelengthRange.from_cf(wr.to_cf()) == wr
assert WavelengthRange.from_cf([str(item) for item in wr]) == wr
34 changes: 20 additions & 14 deletions satpy/writers/cf_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,25 +98,23 @@
.. _CF-compliant: http://cfconventions.org/
"""

from collections import OrderedDict, defaultdict
import copy
import logging
from datetime import datetime
import json
import logging
import warnings
from collections import OrderedDict, defaultdict
from datetime import datetime
from distutils.version import LooseVersion

from dask.base import tokenize
import numpy as np
import xarray as xr
from dask.base import tokenize
from pyresample.geometry import AreaDefinition, SwathDefinition
from xarray.coding.times import CFDatetimeCoder
import numpy as np

from pyresample.geometry import AreaDefinition, SwathDefinition
from satpy.writers import Writer
from satpy.writers.utils import flatten_dict

from distutils.version import LooseVersion


logger = logging.getLogger(__name__)

EPOCH = u"seconds since 1970-01-01 00:00:00"
Expand Down Expand Up @@ -381,22 +379,30 @@ def _encode_nc(obj):


def encode_nc(obj):
"""Encode the given object as a netcdf compatible datatype.
"""Encode the given object as a netcdf compatible datatype."""
try:
return obj.to_cf()
except AttributeError:
return _encode_python_objects(obj)


def _encode_python_objects(obj):
"""Try to find the datatype which most closely resembles the object's nature.

Try to find the datatype which most closely resembles the object's nature. If that fails, encode as a string.
Plain lists are encoded recursively.
If on failure, encode as a string. Plain lists are encoded recursively.
"""
if isinstance(obj, (list, tuple)) and all([not isinstance(item, (list, tuple)) for item in obj]):
return [encode_nc(item) for item in obj]
try:
return _encode_nc(obj)
dump = _encode_nc(obj)
except ValueError:
try:
# Decode byte-strings
decoded = obj.decode()
except AttributeError:
decoded = obj
return json.dumps(decoded, cls=AttributeEncoder).strip('"')
dump = json.dumps(decoded, cls=AttributeEncoder).strip('"')
return dump


def encode_attrs_nc(attrs):
Expand Down