diff --git a/pyart/xradar/accessor.py b/pyart/xradar/accessor.py index 34ad3ab1f5..80dd91d047 100644 --- a/pyart/xradar/accessor.py +++ b/pyart/xradar/accessor.py @@ -344,8 +344,10 @@ def __init__(self, xradar, default_sweep="sweep_0", scan_type=None): self.metadata = dict(**self.xradar.attrs) self.ngates = len(self.range["data"]) self.nrays = len(self.azimuth["data"]) + self.projection = {"proj": "pyart_aeqd", "_include_lon_0_lat_0": True} self.instrument_parameters = self.find_instrument_parameters() self.init_gate_x_y_z() + self.init_gate_longitude_latitude() self.init_gate_alt() def __repr__(self): @@ -582,6 +584,62 @@ def get_gate_x_y_z(self, sweep, edges=False, filter_transitions=False): data = self.combined_sweeps.sel(sweep_number=sweep) return data["x"].values, data["y"].values, data["z"].values + def get_gate_lat_lon_alt( + self, sweep, reset_gate_coords=False, filter_transitions=False + ): + """ + Return the longitude, latitude and altitude gate locations. + Longitude and latitude are in degrees and altitude in meters. + + With the default parameter this method returns the same data as + contained in the gate_latitude, gate_longitude and gate_altitude + attributes but this method performs the gate location calculations + only for the specified sweep and therefore is more efficient than + accessing this data through these attribute. If coordinates have + at all, please use the reset_gate_coords parameter. + + Parameters + ---------- + sweep : int + Sweep number to retrieve gate locations from, 0 based. + reset_gate_coords : bool, optional + Optional to reset the gate latitude, gate longitude and gate + altitude attributes before using them in this function. This + is useful when the geographic coordinates have changed and gate + latitude, gate longitude and gate altitude need to be reset. + filter_transitions : bool, optional + True to remove rays where the antenna was in transition between + sweeps. False will include these rays. No rays will be removed + if the antenna_transition attribute is not available (set to None). + + Returns + ------- + lat, lon, alt : 2D array + Array containing the latitude, longitude and altitude, + for all gates in the sweep. + + """ + s = self.get_slice(sweep) + + if reset_gate_coords: + gate_latitude = LazyLoadDict(get_metadata("gate_latitude")) + gate_latitude.set_lazy("data", _gate_lon_lat_data_factory(self, 1)) + self.gate_latitude = gate_latitude + + gate_longitude = LazyLoadDict(get_metadata("gate_longitude")) + gate_longitude.set_lazy("data", _gate_lon_lat_data_factory(self, 0)) + self.gate_longitude = gate_longitude + + gate_altitude = LazyLoadDict(get_metadata("gate_altitude")) + gate_altitude.set_lazy("data", _gate_altitude_data_factory(self)) + self.gate_altitude = gate_altitude + + lat = self.gate_latitude["data"][s] + lon = self.gate_longitude["data"][s] + alt = self.gate_altitude["data"][s] + + return lat, lon, alt + def init_gate_x_y_z(self): """Initialize or reset the gate_{x, y, z} attributes.""" @@ -616,6 +674,18 @@ def init_gate_alt(self): data=np.mean(self.altitude["data"]) + self.gate_z["data"] ) + def init_gate_longitude_latitude(self): + """ + Initialize or reset the gate_longitude and gate_latitude attributes. + """ + gate_longitude = LazyLoadDict(get_metadata("gate_longitude")) + gate_longitude.set_lazy("data", _gate_lon_lat_data_factory(self, 0)) + self.gate_longitude = gate_longitude + + gate_latitude = LazyLoadDict(get_metadata("gate_latitude")) + gate_latitude.set_lazy("data", _gate_lon_lat_data_factory(self, 1)) + self.gate_latitude = gate_latitude + def _combine_sweeps(self): # Loop through and extract the different datasets ds_list = [] @@ -789,6 +859,13 @@ def get_azimuth(self, sweep, copy=False): else: return azimuths + def get_projparams(self): + projparams = self.projection.copy() + if projparams.pop("_include_lon_0_lat_0", False): + projparams["lon_0"] = self.longitude["data"][0] + projparams["lat_0"] = self.latitude["data"][0] + return projparams + def _point_data_factory(grid, coordinate): """Return a function which returns the locations of all points.""" @@ -841,6 +918,41 @@ def _point_altitude_data(): return _point_altitude_data +def _gate_lon_lat_data_factory(radar, coordinate): + """Return a function which returns the geographic locations of gates.""" + + def _gate_lon_lat_data(): + """The function which returns the geographic locations gates.""" + x = radar.gate_x["data"] + y = radar.gate_y["data"] + projparams = radar.get_projparams() + geographic_coords = cartesian_to_geographic(x, y, projparams) + # Set gate_latitude['data'] when gate_longitude['data'] is evaluated + # and vice-versa. This ensures that both attributes contain data from + # the same map projection and that the map projection only needs to be + # evaluated once. + if coordinate == 0: + radar.gate_latitude["data"] = geographic_coords[1] + else: + radar.gate_longitude["data"] = geographic_coords[0] + return geographic_coords[coordinate] + + return _gate_lon_lat_data + + +def _gate_altitude_data_factory(radar): + """Return a function which returns the gate altitudes.""" + + def _gate_altitude_data(): + """The function which returns the gate altitudes.""" + try: + return radar.altitude["data"] + radar.gate_z["data"] + except ValueError: + return np.mean(radar.altitude["data"]) + radar.gate_z["data"] + + return _gate_altitude_data + + @register_datatree_accessor("pyart") class XradarDataTreeAccessor(XradarAccessor): """Adds a number of pyart specific methods to datatree.DataTree objects.""" diff --git a/tests/xradar/test_accessor.py b/tests/xradar/test_accessor.py index 001dbb17c0..4fe0d6b373 100644 --- a/tests/xradar/test_accessor.py +++ b/tests/xradar/test_accessor.py @@ -57,6 +57,22 @@ def test_get_gate_x_y_z(filename=filename): assert z.shape == (480, 996) +def test_get_gate_lat_lon(filename=filename): + dtree = xd.io.open_cfradial1_datatree( + filename, + optional=False, + ) + radar = pyart.xradar.Xradar(dtree) + lat, lon, alt = radar.get_gate_lat_lon_alt(0) + # Check lat, lon, and alt values + assert lat.shape == (480, 996) + assert_allclose(lat.min(), 21.183521) + assert lon.shape == (480, 996) + assert_allclose(lon.min(), 118.97935) + assert alt.shape == (480, 996) + assert_allclose(alt.min(), 45.0000017) + + def test_add_field(filename=filename): dtree = xd.io.open_cfradial1_datatree( filename,