Skip to content

Commit e4fcc0f

Browse files
authored
Merge pull request #38 from wx4stg/lmainterceptrhi
Add LMA Intercept RHI functionality originally by @cssjessica; ported by @wx4stg from TRACER-PAWS-NEXRAD-LMA
2 parents e9fc3ba + 4c242be commit e4fcc0f

2 files changed

Lines changed: 316 additions & 0 deletions

File tree

pyxlma/lmalib/lma_intercept_rhi.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
from pyxlma.coords import RadarCoordinateSystem, TangentPlaneCartesianSystem, GeographicSystem
2+
import numpy as np
3+
4+
5+
def rcs_to_tps(radar_latitude, radar_longitude, radar_altitude, radar_azimuth):
6+
"""
7+
Find the unit vector coordinates (east, north, up) of the plane of a radar RHI scan.
8+
9+
Creates a azimuth, elevation, range and tangent plane cartesian system at the radar's latitude and longitude,
10+
and converts the RHI azimuth direction to the tangent plane coordinate system.
11+
12+
Parameters
13+
----------
14+
radar_latitude : `float`
15+
Latitude of the radar in degrees.
16+
radar_longitude : `float`
17+
Longitude of the radar in degrees.
18+
radar_altitude : `float`
19+
Altitude of the radar in meters.
20+
radar_azimuth : `float`
21+
Azimuth of the RHI scan in degrees.
22+
23+
Returns
24+
----------
25+
X : `numpy.ndarray`
26+
A 1x2 array representing the start and end points eastward component of the RHI scan.
27+
Y : `numpy.ndarray`
28+
A 1x2 array representing the start and end points northward component of the RHI scan.
29+
Z : `numpy.ndarray`
30+
A 1x2 array representing the start and end points upward component of the RHI scan.
31+
"""
32+
33+
# Coordinates Systems
34+
rcs = RadarCoordinateSystem(radar_latitude, radar_longitude, radar_altitude)
35+
tps = TangentPlaneCartesianSystem(radar_latitude, radar_longitude, radar_altitude)
36+
37+
# - Elevations, azimuth, range
38+
r = np.array([0, 1])
39+
40+
els = np.array([0])
41+
els = np.tensordot(els, np.ones_like(r), axes=0)
42+
43+
azi = np.array([radar_azimuth])
44+
az = np.tensordot(azi, np.ones_like(r), axes=0)
45+
46+
a, b, c = rcs.toECEF(r,az,els)
47+
abc = np.vstack((a,b,c))
48+
# ECEF to TPS
49+
n = tps.toLocal(abc)
50+
X = n[0,:]
51+
Y = n[1,:]
52+
Z = n[2,:]
53+
54+
X = np.reshape(X, (1, 2))
55+
Y = np.reshape(Y, (1, 2))
56+
Z = np.reshape(Z, (1, 2))
57+
58+
return X, Y, Z
59+
60+
61+
def geo_to_tps(lma_file, tps_latitude, tps_longitude, tps_altitude):
62+
"""
63+
Convert the latitude, longitude, and altitude of LMA VHF sources to x/y/z distances (in meters) from an arbitrary latitude, longitude, and altitude.
64+
65+
Creates a tangent plane cartesian system at the latitude, longitude, and altitude provided, and converts the LMA VHF sources to the tangent plane coordinate system.
66+
67+
Parameters
68+
----------
69+
lma_file : `xarray.Dataset`
70+
A pyxlma dataset containing latitude, longitude, and altitude of LMA VHF sources.
71+
tps_latitude : `float`
72+
Latitude of the tangent plane in degrees.
73+
tps_longitude : `float`
74+
Longitude of the tangent plane in degrees.
75+
tps_altitude : `float`
76+
Altitude of the tangent plane in meters.
77+
78+
Returns
79+
----------
80+
Xlma : `numpy.ndarray`
81+
A 1xN array representing the eastward distance (in meters) of the tangent plane center to the LMA VHF sources.
82+
Ylma : `numpy.ndarray`
83+
A 1xN array representing the northward distance (in meters) of the tangent plane center to the LMA VHF sources.
84+
Zlma : `numpy.ndarray`
85+
A 1xN array representing the upward distance (in meters) of the tangent plane center to the LMA VHF sources.
86+
"""
87+
# GeographicSystem GEO - Lat, lon, alt
88+
geo = GeographicSystem()
89+
# Tangent Plane Cartesian System TPS -
90+
tps = TangentPlaneCartesianSystem(tps_latitude, tps_longitude, tps_altitude)
91+
92+
# GEO to TPS
93+
94+
d, e, h = geo.toECEF(lma_file.event_longitude.data, lma_file.event_latitude.data, lma_file.event_altitude.data)
95+
96+
97+
deh = np.vstack((d,e,h))
98+
m = tps.toLocal(deh)
99+
100+
Xlma = m[0]
101+
Ylma = m[1]
102+
Zlma = m[2]
103+
104+
return Xlma,Ylma,Zlma
105+
106+
107+
def ortho_proj_lma(lma_file, radar_latitude, radar_longitude, radar_altitude, radar_azimuth):
108+
"""
109+
Convert the latitude, longitude, and altitude of LMA VHF sources to distance along, distance from, and height above the ground of a radar RHI scan.
110+
111+
Creates tangent plane at the radar's location and converts the LMA VHF sources to the tangent plane coordinate system, then rotates the tangent plane in the direction of the scan azimuth.
112+
113+
114+
Parameters
115+
----------
116+
lma_file : `xarray.Dataset`
117+
A pyxlma dataset containing latitude, longitude, and altitude of N number of LMA VHF sources.
118+
radar_latitude : `float`
119+
Latitude of the radar in degrees.
120+
radar_longitude : `float`
121+
Longitude of the radar in degrees.
122+
radar_altitude : `float`
123+
Altitude of the radar in meters.
124+
radar_azimuth : `float`
125+
Azimuth of the RHI scan in degrees.
126+
127+
Returns
128+
----------
129+
lma_file_loc : `numpy.ndarray`
130+
A Nx3 array representing the distance along, distance from, and height above the ground (in m) of the LMA VHF sources.
131+
132+
"""
133+
Xlma,Ylma,Zlma = geo_to_tps(lma_file, radar_latitude, radar_longitude, radar_altitude)
134+
X, Y, Z = rcs_to_tps(radar_latitude, radar_longitude, radar_altitude, radar_azimuth)
135+
136+
lon_ini1 = X[0,0]
137+
lat_ini1 = Y[0,0]
138+
lon_fin1 = X[0,-1]
139+
lat_fin1 = Y[0,-1]
140+
141+
142+
dlon1 = lon_fin1 - lon_ini1 # dx
143+
dlat1 = lat_fin1 - lat_ini1 # dy
144+
ds1 = np.array((dlon1,dlat1))
145+
norm_ds1 = np.linalg.norm(ds1)
146+
cross_ds1 = np.tensordot(ds1, ds1, (0,0))
147+
148+
# LMA
149+
lma_file_n = np.column_stack((Xlma, Ylma))
150+
151+
lma_file_loc_par = np.zeros(shape=(len(lma_file_n), 2))
152+
lma_file_loc_perp = np.zeros(shape=(len(lma_file_n), 2))
153+
lma_file_loc = np.zeros(shape=(len(lma_file_n), 3))
154+
155+
#
156+
# ##################################
157+
#
158+
# (Xlma[i],Ylma[i]).ds1 .ds1
159+
# ----------------------
160+
# ds1 . ds1
161+
#
162+
# ##################################
163+
#
164+
lma_file_loc_tensor_x = np.tensordot(ds1,lma_file_n,(0,1))
165+
lma_file_loc_par = np.tensordot((lma_file_loc_tensor_x / cross_ds1 ),ds1,0)
166+
167+
#
168+
# #######################################################################
169+
#
170+
# (Xlma[i],Ylma[i]) _ (Xlma[i],Ylma[i]).ds1 .ds1
171+
# ----------------------
172+
# ds1 . ds1
173+
#
174+
##########################################################################
175+
#
176+
lma_file_loc_perp = lma_file_n - lma_file_loc_par
177+
178+
#
179+
lma_file_loc[:,0] = np.sqrt(lma_file_loc_par[:,0]**2 + lma_file_loc_par[:,1]**2)
180+
if radar_azimuth <= 90 or radar_azimuth >= 270:
181+
lma_file_loc[:,0][lma_file_loc_par[:,1] < 0] = -lma_file_loc[:,0][lma_file_loc_par[:,1] < 0]
182+
elif radar_azimuth > 180 and radar_azimuth < 270:
183+
lma_file_loc[:,0][lma_file_loc_par[:,0] > 0] = -lma_file_loc[:,0][lma_file_loc_par[:,0] > 0]
184+
else:
185+
lma_file_loc[:,0][lma_file_loc_par[:,0] < 0] = -lma_file_loc[:,0][lma_file_loc_par[:,0] < 0]
186+
lma_file_loc[:,1] = np.sqrt(lma_file_loc_perp[:,0]**2 + lma_file_loc_perp[:,1]**2)
187+
lma_file_loc[:,2] = Zlma
188+
189+
return lma_file_loc
190+
191+
192+
def find_points_near_rhi(lma_file, radar_latitude, radar_longitude, radar_altitude, radar_azimuth, radar_scan_time, distance_threshold=1000, time_threshold=30):
193+
"""
194+
Find the LMA VHF sources near a radar RHI scan.
195+
196+
Creates tangent plane at the radar's location and converts the LMA VHF sources to the tangent plane coordinate system, then rotates the tangent plane in the direction of the scan azimuth.
197+
Filters RHI scan points based on distance and time thresholds.
198+
199+
200+
Parameters
201+
----------
202+
lma_file : `xarray.Dataset`
203+
A pyxlma dataset containing latitude, longitude, and altitude, and event_id of N number of LMA VHF sources.
204+
radar_latitude : `float`
205+
Latitude of the radar in degrees.
206+
radar_longitude : `float`
207+
Longitude of the radar in degrees.
208+
radar_altitude : `float`
209+
Altitude of the radar in meters.
210+
radar_azimuth : `float`
211+
Azimuth of the RHI scan in degrees.
212+
radar_scan_time : `datetime.datetime` or `numpy.datetime64` or `pandas.Timestamp`
213+
Time of the RHI scan.
214+
distance_threshold : `float`
215+
Maximum distance from the radar to the LMA VHF sources in meters. Default is 1000.
216+
time_threshold : `float`
217+
Number of seconds before and after the RHI scan time to include LMA VHF sources. Default is 30. (total length: 1 minute)
218+
219+
220+
Returns
221+
----------
222+
lma_range : `numpy.ndarray`
223+
A 1D array representing the distance along the tangent plane in the direction of the RHI scan.
224+
lma_dist : `numpy.ndarray`
225+
A 1D array representing the distance from the radar RHI scan plane to each filtered LMA point.
226+
lma_alt : `numpy.ndarray`
227+
A 1D array representing the height above the tangent plane centered at radar level of each filtered LMA point.
228+
point_mask : `numpy.ndarray`
229+
A 1D array of booleans representing the VHF points that were included in the return.
230+
"""
231+
232+
radar_azimuth = radar_azimuth % 360
233+
234+
radar_scan_time = np.array([radar_scan_time]).astype('datetime64[s]')
235+
236+
projected_lma = ortho_proj_lma(lma_file, radar_latitude, radar_longitude, radar_altitude, radar_azimuth)
237+
lma_range = projected_lma[:,0]
238+
lma_dist = projected_lma[:,1]
239+
lma_alt = projected_lma[:,2]
240+
241+
lma_times = lma_file.event_time.data.astype('datetime64[s]')
242+
point_mask = (lma_dist < distance_threshold) & (np.abs(lma_times - radar_scan_time).astype(float) < time_threshold)
243+
lma_range = lma_range[point_mask]
244+
lma_dist = lma_dist[point_mask]
245+
lma_alt = lma_alt[point_mask]
246+
247+
return lma_range, lma_dist, lma_alt, point_mask

tests/test_intercept_rhi.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from pyxlma.lmalib import lma_intercept_rhi
2+
from pyxlma.lmalib.io import read as lma_read
3+
from datetime import timedelta
4+
from os import listdir
5+
import xarray as xr
6+
import numpy as np
7+
8+
9+
def test_intercept_rhi():
10+
"""Test functionality of pyxlma.plot.lma_intercept_rhi"""
11+
files_to_read = listdir('examples/data/')
12+
files_to_read = ['examples/data/'+file for file in files_to_read]
13+
dataset, start_date = lma_read.dataset(files_to_read)
14+
lma_radar_range, lma_plane_distance, lma_arl, event_mask = lma_intercept_rhi.find_points_near_rhi(dataset, 33.56, -101.81, 1600.0, 225, start_date+timedelta(seconds=30), distance_threshold=500, time_threshold=15)
15+
lma_indices = np.flatnonzero(event_mask)
16+
true_radar_range = np.array([-24314.95714263, -24255.42134556, -23947.00309296, -24201.07999466,
17+
-24216.22494555, -24209.0779143 , -24252.54770026, -24196.90100367,
18+
-24261.77984816, -24002.07311165, -23977.02349918, -23922.33835871,
19+
-24061.79138268, -24063.22463418, -24095.4865483 , -23629.07408446,
20+
-23711.71702149, -23815.42007446, -23801.22424582, -23811.93829348,
21+
-23880.1053328 , -23787.41575292, -23714.05514112, -23753.40801102,
22+
-23697.50367439, -23808.79529204, -23702.79936852, -23733.26491339,
23+
-23654.19223296, -23639.08173578, -23684.40998865, -22516.2312817 ,
24+
-24956.31925763, -25087.31684706, -25278.91196761, -25284.01320286,
25+
-25287.53041298, -25404.67473694, -25398.64205462, -25328.81651605,
26+
-25326.06791898, -25236.99678994, -25662.94030104, -25703.02511737,
27+
-25733.48865903, -25548.97709685, -25840.7709326 , -26074.465209 ,
28+
-26135.94920909, -25718.73533927, -26070.13443815, -25742.36675196,
29+
-26088.72774852, -26150.28911721, -22628.68886763])
30+
true_plane_distance = np.array([476.82409578, 471.16428085, 324.56896919, 464.99685801,
31+
443.51557595, 420.92058199, 399.02641209, 300.20108514,
32+
330.2928808 , 284.48486544, 277.49727128, 228.24504655,
33+
101.86785646, 46.09758876, 159.49307647, 422.34413364,
34+
462.82523707, 479.04669236, 450.06522663, 495.67782222,
35+
458.62470004, 397.71230587, 322.04159886, 358.54206568,
36+
263.69453322, 215.27970084, 234.5031954 , 274.60201124,
37+
184.85941046, 153.78011609, 203.25043091, 141.91307406,
38+
291.47165215, 274.21197041, 378.27769326, 296.90514221,
39+
280.02141178, 354.41770628, 327.55116845, 385.75338378,
40+
177.16633758, 35.34312855, 287.23074474, 220.35513643,
41+
134.09140466, 13.21239026, 82.51552308, 6.20537471,
42+
46.62220121, 275.7219309 , 67.74536475, 254.6569667 ,
43+
175.35763511, 190.40871913, 100.09991281])
44+
true_radar_arl = np.array([2427.10820635, 2622.80703538, 2642.20087632, 2740.2745904 ,
45+
2930.63971071, 2752.31680521, 2927.29418501, 2859.50970951,
46+
3625.92759368, 2942.6473481 , 2931.8120999 , 2970.00909331,
47+
3111.63730722, 3062.02183736, 2859.75475096, 1394.13388557,
48+
3537.06953818, 3000.85787932, 2971.55268989, 3552.81355203,
49+
2836.86606787, 3226.92886031, 2460.49052586, 3200.61752493,
50+
2790.06678895, 2551.85152503, 2572.98642808, 3500.51837957,
51+
3063.97138001, 3031.9081397 , 3487.58191853, 6265.1033319 ,
52+
2473.16902922, 2203.98364055, 2856.44738566, 2345.22649489,
53+
2515.22474203, 2635.02689176, 2923.88449116, 2959.58956895,
54+
2263.4423628 , 2731.91960772, 2772.38586661, 2878.88741945,
55+
2746.43494287, 2584.35605103, 2341.75645168, 2613.90859694,
56+
2919.30959546, 2614.69497102, 2473.25507194, 2782.82223582,
57+
2461.79799166, 2377.74468257, 7332.69246886])
58+
true_lma_ids = np.array([12731, 12733, 12734, 12736, 12741, 12743, 12745, 12752, 12756,
59+
12768, 12769, 12775, 12787, 12788, 12797, 12991, 13002, 13005,
60+
13006, 13007, 13008, 13009, 13010, 13011, 13012, 13013, 13015,
61+
13016, 13023, 13029, 13030, 13201, 13388, 13390, 13395, 13396,
62+
13397, 13399, 13402, 13405, 13408, 13409, 13411, 13412, 13415,
63+
13419, 13428, 13437, 13439, 13442, 13443, 13444, 13448, 13451,
64+
13556], dtype='uint64')
65+
assert np.allclose(lma_radar_range, true_radar_range, atol=1e-3)
66+
assert np.allclose(lma_plane_distance, true_plane_distance, atol=1e-3)
67+
assert np.allclose(lma_arl, true_radar_arl, atol=1e-3)
68+
assert np.all(lma_indices == true_lma_ids)
69+

0 commit comments

Comments
 (0)