Skip to content

Commit

Permalink
WIP: furuno
Browse files Browse the repository at this point in the history
  • Loading branch information
kmuehlbauer committed Sep 27, 2022
1 parent b896589 commit f8a5a6c
Show file tree
Hide file tree
Showing 5 changed files with 975 additions and 55 deletions.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"xarray.backends": [
"cfradial1 = xradar.io.backends:CfRadial1BackendEntrypoint",
"odim = xradar.io.backends:OdimBackendEntrypoint",
"furuno = xradar.io.backends:FurunoBackendEntrypoint",
]
},
test_suite="tests",
Expand Down
3 changes: 3 additions & 0 deletions xradar/io/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
:maxdepth: 4
.. automodule:: xradar.io.backends.cfradial1
.. automodule:: xradar.io.backends.furuno
.. automodule:: xradar.io.backends.odim
"""

from .cfradial1 import * # noqa
from .odim import * # noqa
from .furuno import * # noqa

__all__ = [s for s in dir() if not s.startswith("_")]
168 changes: 168 additions & 0 deletions xradar/io/backends/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import xarray as xr
from datatree import DataTree

import struct
from collections import OrderedDict


def _maybe_decode(attr):
try:
Expand All @@ -41,6 +44,18 @@ def _remove_duplicate_rays(ds, store=None):
return ds


def _calculate_angle_res(dim):
# need to sort dim first
angle_diff = np.diff(sorted(dim))
angle_diff2 = np.abs(np.diff(angle_diff))

# only select angle_diff, where angle_diff2 is less than 0.1 deg
# Todo: currently 0.05 is working in most cases
# make this robust or parameterisable
angle_diff_wanted = angle_diff[:-1][angle_diff2 < 0.05]
return np.round(np.nanmean(angle_diff_wanted), decimals=2)


def _fix_angle(da):
# fix elevation outliers
if len(set(da.values)) > 1:
Expand Down Expand Up @@ -126,3 +141,156 @@ def _attach_sweep_groups(dtree, sweeps):
for i, sw in enumerate(sweeps):
DataTree(sw, name=f"sweep_{i}", parent=dtree)
return dtree


def _assign_root(sweeps):
"""(Re-)Create root object according CfRadial2 standard"""
# extract time coverage
times = np.array(
[[ts.time.values.min(), ts.time.values.max()] for ts in sweeps[1:]]
).flatten()
time_coverage_start = min(times)
time_coverage_end = max(times)

time_coverage_start_str = str(time_coverage_start)[:19] + "Z"
time_coverage_end_str = str(time_coverage_end)[:19] + "Z"

# create root group from scratch
root = xr.Dataset() # data_vars=wrl.io.xarray.global_variables,
# attrs=wrl.io.xarray.global_attrs)

# take first dataset/file for retrieval of location
# site = self.site

# assign root variables
root = root.assign(
{
"volume_number": 0,
"platform_type": str("fixed"),
"instrument_type": "radar",
"time_coverage_start": time_coverage_start_str,
"time_coverage_end": time_coverage_end_str,
"latitude": sweeps[1]["latitude"].data,
"longitude": sweeps[1]["longitude"].data,
"altitude": sweeps[1]["altitude"].data,
}
)

# assign root attributes
attrs = {}
attrs["Conventions"] = sweeps[0].attrs["Conventions"]
attrs.update(
{
"version": "None",
"title": "None",
"institution": "None",
"references": "None",
"source": "None",
"history": "None",
"comment": "im/exported using xradar",
"instrument_name": "None",
}
)
root = root.assign_attrs(attrs)
# todo: pull in only CF attributes
root = root.assign_attrs(sweeps[1].attrs)
return root



def _get_fmt_string(dictionary, retsub=False, byte_order="<"):
"""Get Format String from given dictionary.
Parameters
----------
dictionary : dict
Dictionary containing data structure with fmt-strings.
retsub : bool
If True, return sub structures.
Returns
-------
fmt : str
struct format string
sub : dict
Dictionary containing substructure
"""
fmt = f"{byte_order}"
if retsub:
sub = OrderedDict()
for k, v in dictionary.items():
try:
fmt += v["fmt"]
except KeyError:
# remember sub-structures
if retsub:
sub[k] = v
if "size" in v:
fmt += v["size"]
else:
fmt += f"{struct.calcsize(_get_fmt_string(v))}s"
if retsub:
return fmt, sub
else:
return fmt


def _unpack_dictionary(buffer, dictionary, rawdata=False):
"""Unpacks binary data using the given dictionary structure.
Parameters
----------
buffer : array-like
dictionary : dict
data structure in dictionary, keys are names and values are structure formats
Returns
-------
data : dict
Ordered Dictionary with unpacked data
"""
# get format and substructures of dictionary
fmt, sub = _get_fmt_string(dictionary, retsub=True)

# unpack into OrderedDict
data = OrderedDict(zip(dictionary, struct.unpack(fmt, buffer)))

# remove spares
if not rawdata:
keys_to_remove = [k for k in data.keys() if k.startswith("spare")]
keys_to_remove.extend([k for k in data.keys() if k.startswith("reserved")])
for k in keys_to_remove:
data.pop(k, None)

# iterate over sub dictionary and unpack/read/decode
for k, v in sub.items():
if not rawdata:
# read/decode data
for k1 in ["read", "func"]:
try:
# print("K/V:", k, v)
data[k] = v[k1](data[k], **v[k1[0] + "kw"])
except KeyError:
pass
except UnicodeDecodeError:
pass
# unpack sub dictionary
try:
data[k] = _unpack_dictionary(data[k], v, rawdata=rawdata)
except TypeError:
pass

return data


# IRIS Data Types and corresponding python struct format characters
# 4.2 Scalar Definitions, Page 23
# https://docs.python.org/3/library/struct.html#format-characters
SINT2 = {"fmt": "h", "dtype": "int16"}
SINT4 = {"fmt": "i", "dtype": "int32"}
UINT1 = {"fmt": "B", "dtype": "unit8"}
UINT2 = {"fmt": "H", "dtype": "uint16"}
UINT4 = {"fmt": "I", "dtype": "unint32"}



0 comments on commit f8a5a6c

Please sign in to comment.