From fc9d9fc38509ceb5882f11b3f706f96f28d2ad65 Mon Sep 17 00:00:00 2001 From: carleyjmartin Date: Tue, 14 Mar 2023 14:11:17 -0600 Subject: [PATCH 1/5] add option for true velocity --- pydarn/plotting/maps.py | 67 +++++++++++++++++++++++++++++++++++++--- pydarn/utils/plotting.py | 1 + 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/pydarn/plotting/maps.py b/pydarn/plotting/maps.py index 265b5366..076c20e5 100644 --- a/pydarn/plotting/maps.py +++ b/pydarn/plotting/maps.py @@ -110,6 +110,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, MapParams.FITTED_VELOCITY: 'plasma', MapParams.MODEL_VELOCITY: 'plasma', MapParams.RAW_VELOCITY: 'plasma', + MapParams.TRUE_VELOCITY: 'plasma', MapParams.POWER: 'plasma_r', MapParams.SPECTRAL_WIDTH: PyDARNColormaps.PYDARN_VIRIDIS zmin: int @@ -117,6 +118,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, Default: MapParams.FITTED_VELOCITY: [0], MapParams.MODEL_VELOCITY: [0], MapParams.RAW_VELOCITY: [0], + MapParams.TRUE_VELOCITY: [0], MapParams.POWER: [0], MapParams.SPECTRAL_WIDTH: [0] zmax: int @@ -124,6 +126,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, Default: MapParams.FITTED_VELOCITY: [1000], MapParams.MODEL_VELOCITY: [1000], MapParams.RAW_VELOCITY: [1000], + MapParams.TRUE_VELOCITY: [1000], MapParams.POWER: [250], MapParams.SPECTRAL_WIDTH: [250] colorbar: bool @@ -167,6 +170,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, cmap = {MapParams.FITTED_VELOCITY: 'plasma_r', MapParams.MODEL_VELOCITY: 'plasma_r', MapParams.RAW_VELOCITY: 'plasma_r', + MapParams.TRUE_VELOCITY: 'plasma_r', MapParams.POWER: 'plasma', MapParams.SPECTRAL_WIDTH: PyDARNColormaps.PYDARN_VIRIDIS} cmap = plt.cm.get_cmap(cmap[parameter]) @@ -174,6 +178,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, defaultzminmax = {MapParams.FITTED_VELOCITY: [0, 1000], MapParams.MODEL_VELOCITY: [0, 1000], MapParams.RAW_VELOCITY: [0, 1000], + MapParams.TRUE_VELOCITY: [0, 1000], MapParams.POWER: [0, 250], MapParams.SPECTRAL_WIDTH: [0, 250]} if zmin is None: @@ -249,16 +254,34 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, elif parameter == MapParams.RAW_VELOCITY: v_mag = dmap_data[record]['vector.vel.median'] azm_v = np.radians(dmap_data[record]['vector.kvect']) + elif parameter == MapParams.TRUE_VELOCITY: + # Get LOS velocities + v_los = dmap_data[record]['vector.vel.median'] + a_los = np.radians(dmap_data[record]['vector.kvect']) + # Get fitted velocities + v_fit, a_fit = \ + cls.calculated_fitted_velocities(mlats=mlats, + mlons=np.radians( + data_lons), + hemisphere=hemisphere, + fit_coefficient=dmap_data[ + record]['N+2'], + fit_order=dmap_data[ + record]['fit.order'], + lat_min=dmap_data[ + record]['latmin'], + len_factor=len_factor) + v_mag, azm_v = cls.calculated_true_velocities(v_los, a_los, + v_fit, a_fit) elif parameter == MapParams.POWER: v_mag = dmap_data[record]['vector.pwr.median'] azm_v = np.radians(dmap_data[record]['vector.kvect']) - elif parameter == MapParams.SPECTRAL_WIDTH: v_mag = dmap_data[record]['vector.wdt.median'] azm_v = np.radians(dmap_data[record]['vector.kvect']) if parameter in [MapParams.FITTED_VELOCITY, MapParams.MODEL_VELOCITY, - MapParams.RAW_VELOCITY]: + MapParams.RAW_VELOCITY, MapParams.TRUE_VELOCITY]: # Make reference vector and add it to the array to # be calculated too reflat = (np.abs(plt.gca().get_ylim()[1]) - 5) * hemisphere.value @@ -318,7 +341,8 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, if color_vectors is True: if parameter in [MapParams.FITTED_VELOCITY, MapParams.MODEL_VELOCITY, - MapParams.RAW_VELOCITY]: + MapParams.RAW_VELOCITY, + MapParams.TRUE_VELOCITY]: if reference_vector > 0: plt.scatter(mlons[:], mlats[:], c=v_mag[:], s=2.0, vmin=zmin, vmax=zmax, cmap=cmap, zorder=5.0, @@ -340,7 +364,8 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, colorbar = False if parameter in [MapParams.FITTED_VELOCITY, MapParams.MODEL_VELOCITY, - MapParams.RAW_VELOCITY]: + MapParams.RAW_VELOCITY, + MapParams.TRUE_VELOCITY]: if reference_vector > 0: plt.scatter(mlons[:], mlats[:], c='#292929', s=2.0, zorder=5.0, clip_on=False) @@ -374,7 +399,8 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, else: if parameter in [MapParams.FITTED_VELOCITY, MapParams.MODEL_VELOCITY, - MapParams.RAW_VELOCITY]: + MapParams.RAW_VELOCITY, + MapParams.TRUE_VELOCITY]: cb.set_label('Velocity (m s$^{-1}$)') elif parameter is MapParams.SPECTRAL_WIDTH: cb.set_label('Spectral Width (m s$^{-1}$)') @@ -451,6 +477,37 @@ def index_legendre(cls, l: int, m: int): and (m != 0) and l**2 + 2 * m - 1) or 0 + @classmethod + def calculated_true_velocities(cls, v_los: list, a_los: list, + v_fit: list, a_fit: list): + """ + Calculates the true velocities + The True velocity is calculated as the combined LOS vector and the + perpendicular-to-LOS component of the fitted velocity + + Parameters + ---------- + v_los: array + raw magnitude of LOS velocity + a_los: array + angle of direction of raw LOS velocity + v_fit: array + raw magnitude of LOS velocity + a_fit: array + angle of direction of raw LOS velocity + """ + # Get vector component of fitted velocities + # perpendicular to LOS velocity + v_perp = np.sqrt( abs( v_fit**2 - v_los**2 )) + a_perp = (a_los - np.pi/2) + np.arctan2(v_los, v_perp) + + # Calculate the true velocities as the resultatnt of the + # perpendicular and LOS velocities + v_true = np.sqrt( v_perp**2 + v_los**2 ) + a_true = a_perp + np.arctan2(v_los, v_perp) + return v_true, a_true + + @classmethod def calculated_fitted_velocities(cls, mlats: list, mlons: list, fit_coefficient: list, diff --git a/pydarn/utils/plotting.py b/pydarn/utils/plotting.py index 25aff0ce..7e0fe731 100644 --- a/pydarn/utils/plotting.py +++ b/pydarn/utils/plotting.py @@ -29,6 +29,7 @@ class MapParams(enum.Enum): FITTED_VELOCITY = "fitted" MODEL_VELOCITY = "model.vel.median" RAW_VELOCITY = "vector.vel.median" + TRUE_VELOCITY = "true" POWER = "vector.pwr.median" SPECTRAL_WIDTH = "vector.wdt.median" From 3c3b8f7f1c38506ecc3ef6a6675b2d9c435787d8 Mon Sep 17 00:00:00 2001 From: carleyjmartin Date: Tue, 14 Mar 2023 14:17:11 -0600 Subject: [PATCH 2/5] mod line --- pydarn/plotting/maps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydarn/plotting/maps.py b/pydarn/plotting/maps.py index 076c20e5..73ef7653 100644 --- a/pydarn/plotting/maps.py +++ b/pydarn/plotting/maps.py @@ -11,6 +11,7 @@ # vector # 2022-08-15: CJM - Removed plot_FOV call for default uses # 2022-12-13: CJM - Limited reference vectors to only velocity use +# 2023-03-14: CJM - Added true vector option # # Disclaimer: # pyDARN is under the LGPL v3 license found in the root directory LICENSE.md From ecd30f9d460b5b60811eea4e711dffc828c3c8b9 Mon Sep 17 00:00:00 2001 From: carleyjmartin Date: Wed, 26 Apr 2023 15:47:49 -0600 Subject: [PATCH 3/5] correction for true velocity calculations --- pydarn/plotting/maps.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/pydarn/plotting/maps.py b/pydarn/plotting/maps.py index 73ef7653..972f7e54 100644 --- a/pydarn/plotting/maps.py +++ b/pydarn/plotting/maps.py @@ -482,9 +482,11 @@ def index_legendre(cls, l: int, m: int): def calculated_true_velocities(cls, v_los: list, a_los: list, v_fit: list, a_fit: list): """ - Calculates the true velocities + Calculates the true velocities (Lasse Clausen/Ade Grocott Version) The True velocity is calculated as the combined LOS vector and the perpendicular-to-LOS component of the fitted velocity + Be aware many of the vectors in this function are in multi-dimensional + arrays Parameters ---------- @@ -497,15 +499,31 @@ def calculated_true_velocities(cls, v_los: list, a_los: list, a_fit: array angle of direction of raw LOS velocity """ - # Get vector component of fitted velocities - # perpendicular to LOS velocity - v_perp = np.sqrt( abs( v_fit**2 - v_los**2 )) - a_perp = (a_los - np.pi/2) + np.arctan2(v_los, v_perp) - - # Calculate the true velocities as the resultatnt of the - # perpendicular and LOS velocities - v_true = np.sqrt( v_perp**2 + v_los**2 ) - a_true = a_perp + np.arctan2(v_los, v_perp) + # Reduce LOS vector to components normalized + tkvect = np.empty([2, len(a_los)]) + for j in range(0, len(a_los)): + tkvect[:,j] = [-np.cos(a_los[j]), np.sin(a_los[j])] + + # Get vector components + rvect = np.empty([2, len(v_fit)]) + rvect[0,:] = v_fit * np.cos(a_fit) + rvect[1,:] = v_fit * np.sin(a_fit) + + # Get perpendicular vector components + vv = np.empty([2, len(v_fit)]) + vn = np.squeeze(np.sum(rvect * tkvect, axis=0)) + for i in range(0,len(vn)): + vv[:,i] = vn[i] * tkvect[:,i] + tvect = rvect - vv + + # Combine vectors + for k in range(0,len(v_los)): + tvect[:,k] = tvect[:,k] + v_los[k] * tkvect[:,k] + + # Calculate the magnitude and azimuth + v_true = np.sqrt(tvect[0,:]**2 + tvect[1,:]**2) + a_true = np.arctan2(tvect[1,:], -tvect[0,:]) + return v_true, a_true From df900871ce3d40c557d43a33aa60eef2032737ad Mon Sep 17 00:00:00 2001 From: Carley <60905856+carleyjmartin@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:26:33 -0600 Subject: [PATCH 4/5] Apply suggestions from code review --- pydarn/plotting/maps.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pydarn/plotting/maps.py b/pydarn/plotting/maps.py index f11ea8ca..f8240595 100644 --- a/pydarn/plotting/maps.py +++ b/pydarn/plotting/maps.py @@ -637,13 +637,13 @@ def calculated_true_velocities(cls, v_los: list, a_los: list, Parameters ---------- v_los: array - raw magnitude of LOS velocity + magnitude of LOS velocity vector a_los: array - angle of direction of raw LOS velocity + angle of LOS velocity vector from north v_fit: array - raw magnitude of LOS velocity + magnitude of fitted velocity vector a_fit: array - angle of direction of raw LOS velocity + angle of fitted velocity vector from magnetic north """ # Reduce LOS vector to components normalized tkvect = np.empty([2, len(a_los)]) From 49d413fd471e1e1cee1ee05ce9c4b20b9946094a Mon Sep 17 00:00:00 2001 From: Carley Date: Wed, 18 Feb 2026 13:54:24 -0600 Subject: [PATCH 5/5] update to true velocity code --- pydarn/plotting/maps.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/pydarn/plotting/maps.py b/pydarn/plotting/maps.py index f8240595..f914c7d2 100644 --- a/pydarn/plotting/maps.py +++ b/pydarn/plotting/maps.py @@ -265,7 +265,6 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, record]['fit.order'], lat_min=dmap_data[ record]['latmin']) - elif parameter == MapParams.MODEL_VELOCITY: v_mag = dmap_data[record]['model.vel.median'] azm_v = np.radians(dmap_data[record]['model.kvect']) @@ -287,8 +286,7 @@ def plot_mapdata(cls, dmap_data: List[dict], ax=None, fit_order=dmap_data[ record]['fit.order'], lat_min=dmap_data[ - record]['latmin'], - len_factor=len_factor) + record]['latmin']) v_mag, azm_v = cls.calculated_true_velocities(v_los, a_los, v_fit, a_fit) elif parameter == MapParams.POWER: @@ -645,26 +643,26 @@ def calculated_true_velocities(cls, v_los: list, a_los: list, a_fit: array angle of fitted velocity vector from magnetic north """ - # Reduce LOS vector to components normalized + # Reduce LOS vector to components tkvect = np.empty([2, len(a_los)]) - for j in range(0, len(a_los)): - tkvect[:,j] = [-np.cos(a_los[j]), np.sin(a_los[j])] + tkvect[0,:] = - v_los * np.cos(a_los) + tkvect[1,:] = v_los * np.sin(a_los) - # Get vector components - rvect = np.empty([2, len(v_fit)]) - rvect[0,:] = v_fit * np.cos(a_fit) + # Get fitted vector components + rvect = np.empty([2, len(a_fit)]) + rvect[0,:] = - v_fit * np.cos(a_fit) rvect[1,:] = v_fit * np.sin(a_fit) - # Get perpendicular vector components - vv = np.empty([2, len(v_fit)]) - vn = np.squeeze(np.sum(rvect * tkvect, axis=0)) - for i in range(0,len(vn)): - vv[:,i] = vn[i] * tkvect[:,i] - tvect = rvect - vv + # Get fitted component along the los direction + # (a.b / b.b) * b + vn = (np.sum(rvect * tkvect, axis=0) / np.sum(tkvect * tkvect, axis=0)) * tkvect + + # Perp is original vect - parallel + tvect = rvect - vn # Combine vectors for k in range(0,len(v_los)): - tvect[:,k] = tvect[:,k] + v_los[k] * tkvect[:,k] + tvect[:,k] = tvect[:,k] + tkvect[:,k] # Calculate the magnitude and azimuth v_true = np.sqrt(tvect[0,:]**2 + tvect[1,:]**2)