.. _xradar_integration: ================================= Reading Data: xradar and Py-ART ================================= This page answers the question new users ask first: **which IO path do I use, and what works on the object it gives me?** Reading data in Py-ART today ============================= The recommended path for reading radar data is **xradar-first**: open the file with `xradar `_ into an xarray ``DataTree``, then attach Py-ART's ``pyart`` accessor to get a :class:`pyart.xradar.Xradar` object that behaves like a :class:`pyart.core.Radar` for the purposes of running Py-ART algorithms, correction routines, and displays. .. code-block:: python import xradar as xd import pyart # Access sample cfradial1 data from Py-ART and read using xradar filename = pyart.testing.get_test_data("swx_20120520_0641.nc") tree = xd.io.open_cfradial1_datatree(filename) # Attach the Py-ART accessor -- ``radar`` now exposes Radar-like # attributes/methods (fields, azimuth, elevation, get_elevation, ...) radar = tree.pyart.to_radar() xradar ships ``open_*_datatree`` readers for CfRadial1, CfRadial2/FM301, ODIM_H5, Furuno, Iris/Sigmet, NEXRAD Level 2, and others -- see the `xradar documentation `_ for the full list. Any algorithm in Py-ART that accepts a ``radar`` argument can be handed the resulting :class:`pyart.xradar.Xradar` object directly, in place of a :class:`pyart.core.Radar`. Code that needs to accept either interchangeably (for example, a helper function) can normalize the input with :func:`pyart.xradar.to_pyart_radar`, which returns a :class:`~pyart.core.Radar` or :class:`~pyart.xradar.Xradar` unchanged and wraps a bare ``xarray.DataTree`` for you: .. code-block:: python import pyart radar = pyart.xradar.to_pyart_radar(tree) # Radar, Xradar, or DataTree in When to use ``pyart.io.read`` instead -------------------------------------- Py-ART's built-in readers (:func:`pyart.io.read` and friends, e.g. :func:`pyart.io.read_cfradial`, :func:`pyart.io.read_nexrad_archive`) remain available and return a classic :class:`pyart.core.Radar`. Reach for them when: - You need a format xradar does not (yet) read, or a Py-ART-specific reader such as MDV, UF, CHL, or RSL-backed files (see :mod:`pyart.io` and :mod:`pyart.aux_io`). - You depend on behavior specific to :class:`pyart.core.Radar` that is not (yet) mirrored on :class:`pyart.xradar.Xradar` -- check the :ref:`supported-API table ` below first. - You are maintaining existing code and are not ready to migrate. .. code-block:: python import pyart radar = pyart.io.read(filename) # returns a pyart.core.Radar Reader migration: the roadmap direction ========================================= New radar-format IO support is expected to land in `xradar `_ going forward rather than as new readers in :mod:`pyart.io`. Py-ART's own legacy readers will be deprecated over time as xradar's format coverage grows, matching "Moderate #1" of the rc3.0 roadmap (``roadmaps/pyart-roadmap-rc3.0.pdf``). No legacy reader is being removed today -- this is a direction to plan for, not an immediate break. .. _xradar-supported-api: Supported-API table ===================== :class:`pyart.xradar.Xradar` wraps an xradar ``DataTree`` and duck-types :class:`pyart.core.Radar`: most attributes and methods used by Py-ART's correction, retrieval, mapping, and graphing subpackages work unchanged. The table below lists what has been verified to work, sourced from ``pyart/xradar/accessor.py`` and the test suite in ``tests/xradar/`` (notably ``tests/xradar/test_compat_matrix.py``, which runs the same algorithm against a :class:`~pyart.core.Radar` and the equivalent :class:`~pyart.xradar.Xradar` and asserts numerically equal results). ``Xradar`` (radar-level) -------------------------- .. list-table:: :header-rows: 1 :widths: 35 65 * - Member - Notes * - ``fields`` - ``numpy.ma.MaskedArray`` per field, masked where the source variable is NaN (or was already masked) * - ``azimuth``, ``elevation``, ``range``, ``time``, ``fixed_angle`` - Standard Radar-style ``dict(data=..., **attrs)`` * - ``latitude``, ``longitude``, ``altitude`` - Populated from the DataTree root * - ``sweep_number``, ``sweep_mode``, ``sweep_start_ray_index``, ``sweep_end_ray_index`` - Derived from the combined per-sweep data * - ``rays_per_sweep`` - Lazily derived from the sweep start/end indices * - ``gate_x``/``y``/``z``, ``gate_longitude``/``latitude``, ``gate_altitude`` - Computed the same way as on ``Radar`` * - ``get_elevation(sweep)`` - Per-sweep elevation slice * - ``get_azimuth(sweep)`` - Per-sweep azimuth slice * - ``get_gate_area(sweep)`` - Per-gate area, same formula as ``Radar`` * - ``get_gate_x_y_z``, ``get_gate_lat_lon_alt`` - Per-sweep gate locations, computed via ``xradar.georeference()`` under the hood * - ``get_nyquist_vel`` - From ``instrument_parameters`` when present * - ``info(level=...)`` - Same three levels as ``Radar.info`` * - ``add_field``, ``add_field_like``, ``add_filter`` - Mutates both ``self.fields`` and the underlying ``DataTree`` sweep datasets * - ``extract_sweeps`` - Returns a new ``Xradar`` * - ``instrument_parameters`` - Populated from a ``radar_parameters`` subgroup and/or root-level variables when present, else ``{}`` * - ``radar_calibration`` - Populated from a ``radar_calibration`` subgroup when present in the DataTree, else ``None`` * - ``antenna_transition``, ``rays_are_indexed``, ``ray_angle_res``, ``target_scan_rate``, ``scan_rate``, ``altitude_agl``, ``rotation``, ``tilt``, ``roll``, ``drift``, ``heading``, ``pitch``, ``georefs_applied`` - ``None`` when the source file has no corresponding variable/group -- **never fabricated**. Populated when present. * - ``scan_type`` - ``"ppi"`` by default, or pass ``scan_type="rhi"`` to ``tree.pyart.to_radar(scan_type="rhi")`` -- RHI sweeps are combined on elevation rather than azimuth ``Xgrid`` (grid-level) ------------------------ .. list-table:: :header-rows: 1 :widths: 35 65 * - Member - Notes * - ``fields``, ``x``/``y``/``z``, ``origin_latitude`` etc. - Same shape and layout as ``pyart.core.Grid`` * - ``get_point_longitude_latitude``, ``add_field``, ``to_xarray`` - Same as ``Grid`` * - ``projection_proj`` - ``pyproj.Proj`` built from the ``projection`` attrs; raises ``ValueError`` for the ``pyart_aeqd`` projection (no ``pyproj.Proj`` equivalent) and :class:`~pyart.exceptions.MissingOptionalDependency` if ``pyproj`` is not installed * - ``write(filename)`` - Writes the grid via :func:`pyart.io.grid_io.write_grid` Known caveats ============== - **Fields are masked, not filled.** ``Xradar.fields[name]["data"]`` is a ``numpy.ma.MaskedArray``; any NaN in the source variable (xarray's decoded ``_FillValue``) is masked, mirroring how :class:`pyart.core.Radar` masks missing gates. - **RHI sweeps are supported** by passing ``scan_type="rhi"`` to ``tree.pyart.to_radar()``; internally rays are combined per-sweep on elevation (which varies per ray in an RHI) instead of azimuth (which is ~constant across an RHI sweep). - **``radar_calibration``** is populated only when the source DataTree has a ``radar_calibration`` child group (mirroring how ``instrument_parameters`` is sourced from a ``radar_parameters`` subgroup); otherwise it is ``None``, matching :class:`~pyart.core.Radar`'s handling of files that lack this information. - **Comparing a file read both ways is not bit-for-bit.** ``pyart.io.read`` and xradar's ``open_*_datatree`` readers can disagree on ray bookkeeping for messy real-world files (e.g. which ray straddling the 0/360 azimuth wrap belongs to which sweep); this is a property of two independent parsers, not an ``Xradar`` accessor defect. See ``tests/xradar/test_compat_matrix.py`` for how the compatibility test suite works around this to get an apples-to-apples numerical comparison. Worked example ================ .. code-block:: python import xradar as xd import pyart filename = pyart.testing.get_test_data("swx_20120520_0641.nc") tree = xd.io.open_cfradial1_datatree(filename) radar = tree.pyart.to_radar() # Grid using 11 vertical levels, and 101 horizontal grid cells at a # resolution of 1 km -- pyart.map works directly on the Xradar object grid = pyart.map.grid_from_radars( (radar,), grid_shape=(11, 101, 101), grid_limits=( (0.0, 10_000), (-50_000.0, 50_000.0), (-50_000, 50_000.0), ), ) More end-to-end examples are in the example gallery: - :ref:`sphx_glr_examples_xradar_plot_xradar.py` - :ref:`sphx_glr_examples_xradar_plot_grid_xradar.py` - :ref:`sphx_glr_examples_xradar_plot_dealias_xradar.py` - :ref:`sphx_glr_examples_xradar_plot_rhi_xradar.py` See also :ref:`pyart_2_0` for the rest of the Py-ART 2.0 API changes, and :ref:`overview` for how the ``Radar``/xradar data models compare.