#!/usr/bin/env python
# Copyright (c) 2021-2023, wradlib developers.
# Distributed under the MIT License. See LICENSE.txt for more info.
"""
Xarray backends
^^^^^^^^^^^^^^^
Reading radar data into xarray Datasets using ``xarray.open_dataset``
and ``xarray.open_mfdataset``.
Former available backends have been ported to `xradar <https://xradar.rtfd.io>`__-package.
.. autosummary::
:nosignatures:
:toctree: generated/
{}
"""
__all__ = [
"RadolanBackendEntrypoint",
]
__doc__ = __doc__.format("\n ".join(__all__))
import io
import numpy as np
from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint
from xarray.backends.file_manager import CachingFileManager, DummyFileManager
from xarray.backends.locks import SerializableLock, ensure_lock
from xarray.backends.store import StoreBackendEntrypoint
from xarray.core import indexing
from xarray.core.utils import Frozen, FrozenDict, close_on_error
from xarray.core.variable import Variable
from wradlib.io.radolan import _radolan_file
RADOLAN_LOCK = SerializableLock()
class RadolanArrayWrapper(BackendArray):
"""Wraps array of RADOLAN data."""
def __init__(self, datastore, name, array):
self.datastore = datastore
self.name = name
self.shape = array.shape
self.dtype = array.dtype
def _getitem(self, key):
return self.datastore.ds.data[self.name][key]
def __getitem__(self, key):
return indexing.explicit_indexing_adapter(
key,
self.shape,
indexing.IndexingSupport.BASIC,
self._getitem,
)
class RadolanDataStore(AbstractDataStore):
"""Implements ``xarray.AbstractDataStore`` read-only API for a RADOLAN files."""
def __init__(
self, filename_or_obj, lock=None, fillmissing=False, copy=False, ancillary=False
):
if lock is None:
lock = RADOLAN_LOCK
self.lock = ensure_lock(lock)
if isinstance(filename_or_obj, str):
manager = CachingFileManager(
_radolan_file,
filename_or_obj,
lock=lock,
kwargs={
"fillmissing": fillmissing,
"copy": copy,
"ancillary": ancillary,
},
)
else:
if isinstance(filename_or_obj, bytes):
filename_or_obj = io.BytesIO(filename_or_obj)
dataset = _radolan_file(
filename_or_obj, fillmissing=fillmissing, copy=copy, ancillary=ancillary
)
manager = DummyFileManager(dataset)
self._manager = manager
self._filename = self.ds.filename
def _acquire(self, needs_lock=True):
with self._manager.acquire_context(needs_lock) as ds:
return ds
@property
def ds(self):
return self._acquire()
def open_store_variable(self, name, var):
encoding = {"source": self._filename}
vdata = var.data
if isinstance(vdata, np.ndarray):
data = vdata
else:
data = indexing.LazilyOuterIndexedArray(
RadolanArrayWrapper(self, name, vdata)
)
return Variable(var.dimensions, data, var.attributes, encoding)
def get_variables(self):
return FrozenDict(
(k, self.open_store_variable(k, v)) for k, v in self.ds.variables.items()
)
def get_attrs(self):
return Frozen(self.ds.attributes)
def get_dimensions(self):
return Frozen(self.ds.dimensions)
def get_encoding(self):
dims = self.get_dimensions()
encoding = {"unlimited_dims": {k for k, v in dims.items() if v is None}}
return encoding
def close(self, **kwargs):
self._manager.close(**kwargs)
[docs]class RadolanBackendEntrypoint(BackendEntrypoint):
"""Xarray BackendEntrypoint for RADOLAN data."""
description = "Open RADOLAN in Xarray"
url = "https://docs.wradlib.org/en/stable/notebooks/fileio/backends/radolan_backend.html"
[docs] def open_dataset(
self,
filename_or_obj,
*,
mask_and_scale=True,
decode_times=True,
concat_characters=True,
decode_coords=True,
drop_variables=None,
use_cftime=None,
decode_timedelta=None,
fillmissing=False,
copy=False,
ancillary=False,
):
store = RadolanDataStore(
filename_or_obj,
fillmissing=fillmissing,
copy=copy,
ancillary=ancillary,
)
store_entrypoint = StoreBackendEntrypoint()
with close_on_error(store):
ds = store_entrypoint.open_dataset(
store,
mask_and_scale=mask_and_scale,
decode_times=decode_times,
concat_characters=concat_characters,
decode_coords=decode_coords,
drop_variables=drop_variables,
use_cftime=use_cftime,
decode_timedelta=decode_timedelta,
)
return ds