Coordinate Convertion

All Boundary classes have methods to convert between magnetic, geodetic, or geographic and normalised boundary coordinates. When geodetic or geographic coordinates are provided, aacgmv2 is used to perform the conversion at a specified height.

Convert between AACGM and OCB coordinates

We’ll start by visualising the location of the OCB using the first good OCB in the default IMAGE FUV file.

def set_up_polar_plot(ax, hemi=1):
    """Set the formatting for a polar plot.

    Parameters
    ----------
    ax : Axes object
        Subplot Axes object to be formatted
    hemi : int
        Hemisphere, 1 is North and -1 is South (default=1)

    """
    ss = "" if hemi == 1 else "-"
    ax.set_theta_zero_location("S")
    ax.xaxis.set_ticks([0, 0.5 * np.pi, np.pi, 1.5 * np.pi])
    ax.xaxis.set_ticklabels(["00:00", "06:00", "12:00 MLT", "18:00"])
    ax.set_rlim(0, 25)
    ax.set_rticks([5, 10, 15, 20])
    ax.yaxis.set_ticklabels([ss + "85$^\circ$", ss + "80$^\circ$",
                             ss + "75$^\circ$", ss + "70$^\circ$"])
    return

fig = plt.figure()
ax = fig.add_subplot(111, projection="polar")
set_up_polar_plot(ax)

Mark the location of the circle centre in AACGM coordinates

# Use the OCB of the first good, paired OCB loaded in the `dual` example
ocb.rec_ind = dual.ocb_ind[0]
ax.plot(np.radians(ocb.phi_cent[ocb.rec_ind]), ocb.r_cent[ocb.rec_ind],
        "mx", ms=10, label="OCB Pole")

Calculate and plot the location of the OCB in AACGM coordinates

mlt = np.linspace(0.0, 24.0, num=64)
ocb.get_aacgm_boundary_lat(mlt, rec_ind=ocb.rec_ind)
theta = ocbpy.ocb_time.hr2rad(mlt)
rad = 90.0 - ocb.aacgm_boundary_lat[ocb.rec_ind]
ax.plot(theta, rad, "m-", linewidth=2, label="OCB")
ax.text(theta[40], rad[40] + 2, "{:.0f}$^\circ$".format(ocb.boundary_lat),
        fontsize="medium", color="m")

Add more reference labels for OCB coordinates. Since we know the location that we want to place these labels in OCB coordinates, the revert_coord() method can be used to get the location in AACGM coordinates.

# Define a latitude grid for the OCB coordinates
grid_ocb = 0.5 * (90 - abs(ocb.boundary_lat)) + ocb.boundary_lat
grid_lat, grid_mlt = ocb.revert_coord(grid_ocb, mlt)
grid_lat = 90.0 - grid_lat
grid_theta = grid_mlt * np.pi / 12.0

# Define the MLT grid in OCB coordinates
lon_clock = list()
lat_clock = list()
for ocb_mlt in np.arange(0.0, 24.0, 6.0):
    aa, oo = ocb.revert_coord(ocb.boundary_lat, ocb_mlt)
    lon_clock.append(oo * np.pi / 12.0)
    lat_clock.append(90.0 - aa)

# Plot the OCB latitude and MLT grid
ax.plot(lon_clock, lat_clock, "m+")
ax.plot([lon_clock[0], lon_clock[2]], [lat_clock[0], lat_clock[2]], "-",
        color="lightpink", zorder=1)
ax.plot([lon_clock[1], lon_clock[3]], [lat_clock[1], lat_clock[3]], "-",
        color="lightpink", zorder=1)
ax.plot(grid_theta, grid_lat, "-", color="lightpink", zorder=1)
ax.text(lon_clock[2] + .2, lat_clock[2] + 1.0, "12:00",fontsize="medium",
        color="m")
ax.text(grid_theta[40], grid_lat[40] + 2, "{:.0f}$^\circ$".format(grid_ocb),
        fontsize="medium", color="m")

Now add the location of a point in AACGM coordinates, calculate the location relative to the OCB, and output both coordinates in the legend

aacgm_lat = 85.0
aacgm_theta = np.pi
ocb_lat, ocb_mlt, _ = ocb.normal_coord(aacgm_lat, aacgm_theta * 12.0 / np.pi)

plabel = "\n".join(["Point (MLT, lat)", "AACGM (12:00, 85.0$^\circ$)",
                    "OCB ({:02.0f}:{:02.0f}, {:.1f}$^\circ$)".format(
                        np.floor(ocb_mlt),
                        (ocb_mlt - np.floor(ocb_mlt)) * 60.0, ocb_lat)])
ax.plot([aacgm_theta], [90.0 - aacgm_lat], "ko", ms=5, label=plabel)

Add a legend to finish the figure.

ax.legend(loc=2, fontsize="small", title="{:}".format(
    ocb.dtime[ocb.rec_ind]), bbox_to_anchor=(-0.4, 1.15))
../_images/example_ocb_location.png

Scaling of values dependent on the electric potential can be found in the ocbpy.ocb_scaling module.

Convert between AACGM and dual-boundary coordinates

Now let us perform the same visualisation using a paired EAB and OCB. The prior example ensured that this was a time with a good dual-boundary for dual from the prior examples. Continue by adding the EAB to the existing figure.

dual.rec_ind = 0
ax.plot(np.radians(dual.eab.phi_cent[dual.eab.rec_ind]),
                   dual.eab.r_cent[dual.eab.rec_ind],
                   "+", color='purple', ms=10, label="EAB Pole")

Calculate and plot the location of the EAB in AACGM coordinates, expanding the radial boundaries of the figure as needed.

dual.get_aacgm_boundary_lats(mlt, rec_ind=dual.rec_ind, overwrite=True)
rad = 90.0 - dual.eab.aacgm_boundary_lat[dual.eab.rec_ind]
ax.plot(theta, rad, "-", color='purple', linewidth=2, label="EAB")
ax.text(theta[40], rad[40] + 2,
        "{:.0f}$^\circ$".format(dual.eab.boundary_lat), fontsize="medium",
        color="m")
ax.set_rmax(np.ceil(max(rad) / 10.0) * 10.0)

Add more reference labels for the dual-boundary coordinates. This is harder to do, because there is no direct conversion beween the dual-boundary coordinates and AACGM coordinates without already knowing the AACGM MLT. To allow forward and backward transformations, normal_coord() also returns the OCB coordinates, which can be reverted using revert_coord(). Without this knowledge, you must provide the AACGM MLT. This is only a barrier for locations equatorward of the OCB.

# Define a latitude grid midway between the EAB and OCB. Since no locations
# at or poleward of the OCB are provided, the reversion will only use the
# shape of the `mlt` arg input.
grid_eab = 0.5 * (dual.ocb.boundary_lat
                  - dual.eab.boundary_lat) + dual.eab.boundary_lat
grid_lat, grid_mlt = dual.revert_coord(grid_eab, mlt, aacgm_mlt=mlt,
                                       is_ocb=False)
grid_lat = 90.0 - grid_lat
grid_theta = grid_mlt * np.pi / 12.0

ax.plot(grid_theta, grid_lat, "-", color="lightpink", zorder=1)
ax.text(grid_theta[40], grid_lat[40] + 2, "{:.0f}$^\circ$".format(grid_eab),
        fontsize="medium", color="m")

# Extend the MLT grid in dual-boundary coordinates
fine_mlt = np.linspace(0, 24.0, num=500)
dual.get_aacgm_boundary_lats(fine_mlt, rec_ind=dual.rec_ind, overwrite=True)
for i, ocb_mlt in enumerate(np.arange(0.0, 24.0, 6.0)):
    ocb_lat, omlt, _ = dual.ocb.normal_coord(
        dual.eab.aacgm_boundary_lat[dual.eab.rec_ind] - 10.0, fine_mlt)
    j = abs(omlt - ocb_mlt).argmin()
    if ocb_mlt == 0:
        j2 = abs(omlt - 24).argmin()
        if abs(omlt[j2] - ocb_mlt) < abs(omlt[j] - ocb_mlt):
            j = j2

    aa, oo = dual.revert_coord(ocb_lat[j], ocb_mlt)
    lon_outer = oo * np.pi / 12.0
    lat_outer = 90.0 - aa

    ax.plot([lon_clock[i], lon_outer], [lat_clock[i], lat_outer], "-",
            color="lightpink", zorder=1)

Now add the location of two more points in AACGM coordinates, calculating the dual-boundary location, and output both coordinates in the legend

aacgm_lat = [70.0, 61.0]
aacgm_mlt = 0.0
bound_lat, bound_mlt, _, _ = dual.normal_coord(aacgm_lat, aacgm_mlt)

markers = ['s', '*']
for i, lat in enumerate(aacgm_lat):
    plabel = "\n".join(["Point (MLT, lat)",
                        "AACGM (00:00, {:.1f}$^\circ$)".format(lat),
                        "Dual ({:02.0f}:{:02.0f}, {:.1f}$^\circ$)".format(
                            np.floor(bound_mlt[i]),
                            (bound_mlt[i] - np.floor(bound_mlt[i])) * 60.0,
                            bound_lat[i])])
    fmt = 'k{:s}'.format(markers[i])
    ax.plot([aacgm_mlt * np.pi / 12.0], [90.0 - lat], fmt, ms=5,
            label=plabel)

Update the legend to finish the figure.

fig.subplots_adjust(left=.3, right=.99)
ax.legend(loc=2, fontsize="small", title="{:}".format(
    dual.dtime[dual.rec_ind]), bbox_to_anchor=(-0.6, 1.15))
../_images/example_dual_location.png