Source code for xradar.georeference.transforms

#!/usr/bin/env python
# Copyright (c) 2022-2024, openradar developers.
# Distributed under the MIT License. See LICENSE for more info.

"""
Transformations
^^^^^^^^^^^^^^^

.. autosummary::
   :nosignatures:
   :toctree: generated/

   {}
"""

__all__ = ["antenna_to_cartesian", "get_x_y_z", "get_x_y_z_tree"]

__doc__ = __doc__.format("\n   ".join(__all__))


import numpy as np
import xarray as xr

from .projection import add_crs, get_crs, get_earth_radius


[docs] def antenna_to_cartesian( ranges, azimuths, elevations, earth_radius=6371000, effective_radius_fraction=None, site_altitude=0, ): """Return Cartesian coordinates from antenna coordinates. Parameters ---------- ranges : array-like Distances to the center of the radar gates (bins) in meters. azimuths : array-like Azimuth angle of the radar in degrees. elevations : array-like Elevation angle of the radar in degrees. earth_radius: float Radius of the earth (default is 6371000 m). effective_radius_fraction: float Fraction of earth to use for the effective radius (default is 4/3). site_altitude: float Altitude amsl of radar site Returns ------- x, y, z : array Cartesian coordinates in meters from the radar. Notes ----- The calculation for Cartesian coordinate is adapted from equations 2.28(b) and 2.28(c) of Doviak and Zrnić [1]_ assuming a standard atmosphere (4/3 Earth's radius model). .. math:: :nowrap: \\begin{gather*} z = \\sqrt{r^2+R^2+2*r*R*sin(\\theta_e)} - R \\\\ s = R * arcsin(\\frac{r*cos(\\theta_e)}{R+z}) \\\\ x = s * sin(\\theta_a) \\\\ y = s * cos(\\theta_a) \\end{gather*} Where r is the distance from the radar to the center of the gate, :math:`\\theta_a` is the azimuth angle, :math:`\\theta_e` is the elevation angle, s is the arc length, and R is the effective radius of the earth, taken to be 4/3 the mean radius of earth (6371 km). References ---------- .. [1] Doviak and Zrnić, Doppler Radar and Weather Observations, Second Edition, 1993, p. 21. """ if effective_radius_fraction is None: effective_radius_fraction = 4.0 / 3.0 # Convert the elevation angle from degrees to radians theta_e = np.deg2rad(elevations) theta_a = np.deg2rad(azimuths) # azimuth angle in radians. R = earth_radius * effective_radius_fraction # effective radius of earth in meters. r = ranges # distances to gates in meters. # take site altitude into account sr = R + site_altitude z = (2.0 * np.sin(theta_e) * r * sr + r**2 + sr**2) ** 0.5 - R s = R * np.arcsin(r * np.cos(theta_e) / (R + z)) # arc length in m. x = np.sin(theta_a) * s y = np.cos(theta_a) * s return x, y, z
[docs] def get_x_y_z(ds, earth_radius=None, effective_radius_fraction=None): """ Return Cartesian coordinates from antenna coordinates. Parameters ---------- ds: xarray.Dataset Xarray dataset containing range, azimuth, and elevation earth_radius: float Radius of the earth. Defaults to a latitude-dependent radius derived from WGS84 ellipsoid. effective_radius_fraction: float Fraction of earth to use for the effective radius (default is 4/3). Returns ------- ds : xarray.Dataset Dataset including x, y, and z as coordinates. Notes ----- The calculation for Cartesian coordinate is adapted from equations 2.28(b) and 2.28(c) of Doviak and Zrnić [1]_ assuming a standard atmosphere (4/3 Earth's radius model). .. math:: :nowrap: \\begin{gather*} z = \\sqrt{r^2+R^2+2*r*R*sin(\\theta_e)} - R \\\\ s = R * arcsin(\\frac{r*cos(\\theta_e)}{R+z}) \\\\ x = s * sin(\\theta_a) \\\\ y = s * cos(\\theta_a) \\end{gather*} Where r is the distance from the radar to the center of the gate, :math:`\\theta_a` is the azimuth angle, :math:`\\theta_e` is the elevation angle, s is the arc length, and R is the effective radius of the earth, taken to be 4/3 the mean radius of earth (6371 km). References ---------- .. [1] Doviak and Zrnić, Doppler Radar and Weather Observations, Second Edition, 1993, p. 21. """ if earth_radius is None: crs = get_crs(ds) earth_radius = get_earth_radius(crs, ds.latitude.values) ds = ds.pipe(add_crs, crs) # Calculate x, y, and z from the dataset ds["x"], ds["y"], ds["z"] = antenna_to_cartesian( ds.range, ds.azimuth, ds.elevation, earth_radius=earth_radius, effective_radius_fraction=effective_radius_fraction, site_altitude=ds.altitude.values, ) # Set the attributes for the dataset # todo: possible utilize crs.cs_to_cf() for x/y ds.x.attrs = {"standard_name": "east_west_distance_from_radar", "units": "meters"} ds.y.attrs = {"standard_name": "north_south_distance_from_radar", "units": "meters"} ds.z.attrs = {"standard_name": "height_above_ground", "units": "meters"} # Make sure the coordinates are set properly if it is a dataset if isinstance(ds, xr.Dataset): ds = ds.set_coords(["x", "y", "z"]) return ds
[docs] def get_x_y_z_tree(radar, earth_radius=None, effective_radius_fraction=None): """ Applies the georeferencing to a xradar datatree Parameters ---------- radar: xarray.DataTree Xradar datatree object with radar information. earth_radius: float Radius of the earth. Defaults to a latitude-dependent radius derived from WGS84 ellipsoid. effective_radius_fraction: float Fraction of earth to use for the effective radius (default is 4/3). Returns ------- radar: xarray.DataTree Datatree with sweep datasets including georeferenced coordinates """ for key in list(radar.children): if "sweep" in key: radar[key].ds = get_x_y_z( radar[key].to_dataset(), earth_radius=earth_radius, effective_radius_fraction=effective_radius_fraction, ) return radar