4. Using equivalent sources for cross-over errors#

For some data types, such as gravity and magnetics, the value of the data depends on the the elevation it was collected at. For an intersection, if the two lines were flown at different altitudes, the intersection point is only the intersection in 2D space, not in 3D. To make it a true 3D intersection, we can perform upward continuation on the data so that the values of both lines represent the values that would have been observed if the flights were flown at the same altitude. We don’t actually need to upward continue the entire lines, just the single points which make up the intersection.

[ ]:
# %load_ext autoreload
# %autoreload 2


import logging

import geopandas as gpd
import numpy as np
import pandas as pd
import plotly.io as pio

import airbornegeo

# setup logging to get some additional info from the airbornegeo functions
logging.getLogger("airbornegeo").setLevel("INFO")
logging.basicConfig()
pio.renderers.default = "notebook"
/home/sungw937/airbornegeo/.pixi/envs/default/lib/python3.14/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

4.1. Load data#

This is a subset of the BAS AGAP survey over Antarctica’s Gamburtsev Subglacial Mountains. The file is download and subset in the notebook AGAP_magnetic_survey, and the BAS processing steps are repeated in the notebook processing_AGAP_magnetic_survey.

[20]:
data_df = pd.read_csv("data/AGAP_magnetic_survey_processed_blocked.csv")
data_df = data_df[
    [
        "easting",
        "northing",
        "height",
        "line",
        "unixtime",
        "distance_along_line",
        "mag",
    ]
]

# for testing limit number of lines
data_df = data_df[~data_df.line.between(133, 142)]
data_df = data_df[~data_df.line.between(168, 176)]
data_df = data_df[
    (data_df.line.isin(data_df.line.unique()[::2])) | (data_df.line >= 143)
]

# drop rows with any nans
data_df = data_df.dropna(how="any")

# define flight lines vs tie lines with column 'tie' which is True or False
data_df["tie"] = False
data_df.loc[data_df.line >= 142, "tie"] = True

# convert dataframe into geodataframe
data_df = gpd.GeoDataFrame(
    data_df,
    geometry=gpd.points_from_xy(data_df.easting, data_df.northing),
    crs="EPSG:3031",
)

data_df.head()
[20]:
easting northing height line unixtime distance_along_line mag tie geometry
0 621099.093988 159056.748193 4110.45 1 1.229500e+09 27.181565 -33.385 False POINT (621099.094 159056.748)
1 621206.517953 159071.301512 4114.50 1 1.229500e+09 135.587530 -35.120 False POINT (621206.518 159071.302)
2 621313.794221 159085.113599 4117.90 1 1.229500e+09 243.749367 -36.875 False POINT (621313.794 159085.114)
3 621420.722903 159099.184618 4120.80 1 1.229500e+09 351.600337 -38.585 False POINT (621420.723 159099.185)
4 621527.158177 159114.303863 4123.15 1 1.229500e+09 459.104857 -40.205 False POINT (621527.158 159114.304)

4.2. Find intersections#

[21]:
# calculate theoretical intersection points
inters = airbornegeo.create_intersection_table(data_df)
Line/tie combinations: 100%|██████████| 3630/3630 [00:00<00:00, 4013.77it/s]
Potential intersections: 100%|██████████| 670/670 [00:12<00:00, 53.79it/s]
INFO:airbornegeo:found 662 intersections
_images/using_equivalent_sources_for_cross_over_errors_5_2.png

4.3. Add intersections as rows to the dataframe#

This just performs a cubic interpolation using windows of data on either/both sides of the intersection, we haven’t used equivalent sources yet.

[22]:
data_df, inters = airbornegeo.interpolate_intersections(
    data_df,
    inters,
    to_interp="mag",
    window_width=500,
    method="cubic",
    extrapolate=False,
)
Segments: 100%|██████████| 121/121 [00:09<00:00, 12.66it/s]
[23]:
# re-define flight lines vs tie lines with column 'tie' which is True or False
data_df["tie"] = False
data_df.loc[data_df.line >= 142, "tie"] = True
[24]:
lines_without_inters = airbornegeo.lines_without_intersections(data_df, inters)
lines_without_inters
[24]:
[np.int64(107),
 np.int64(109),
 np.int64(129),
 np.int64(131),
 np.int64(188),
 np.int64(189),
 np.int64(190),
 np.int64(192),
 np.int64(193),
 np.int64(194),
 np.int64(203)]
[25]:
# drop lines without intersections
data_df = data_df[~data_df.line.isin(lines_without_inters)]

4.4. Calculate intersection cross-over errors#

Now we have interpolated magnetic values for each intersecting line at the intersection points, we can calculate the cross-over errors for each intersection

[26]:
inters = airbornegeo.calculate_crossover_errors(
    data_df,
    inters,
    data_col="mag",
    plot_map=True,
)
_images/using_equivalent_sources_for_cross_over_errors_12_1.png

4.5. Inspect flight altitudes#

[27]:
airbornegeo.plotly_points(
    data_df[::50],
    color_col="height",
    hover_cols=["line"],
)

4.6. Inspect flight altitude difference at intersection points#

[ ]:
# perform interpolation again, this time with height column
data_df_height, _inters_height = airbornegeo.interpolate_intersections(
    data_df,
    inters,
    to_interp="height",
    window_width=500,
    method="cubic",
    extrapolate=False,
)

# add the interpolated height values to the orginal dataframe's intersection rows
for ind, row in data_df[data_df.is_intersection].iterrows():
    height = data_df_height[
        (data_df_height.line == row.line)
        & (data_df_height.intersecting_line == row.intersecting_line)
    ].height.to_numpy()
    assert len(height) == 1
    data_df.loc[ind, "height"] = height

data_df[data_df.is_intersection].head()
Segments: 100%|██████████| 110/110 [00:09<00:00, 11.63it/s]
easting northing height line unixtime distance_along_line mag tie geometry is_intersection intersecting_line mag_interpolation_type
7453 637640.0 161826.0 4122.332685 1 NaN 16811.103180 -53.846042 False POINT (637640 161826) True 150.0 interpolated
7454 670007.0 167673.0 4132.634362 1 NaN 49821.820318 -80.387990 False POINT (670007 167673) True 151.0 interpolated
7455 702402.0 173414.0 4156.031337 1 NaN 82726.336899 -44.550155 False POINT (702402 173414) True 152.0 interpolated
7456 702430.0 173419.0 4156.517464 1 NaN 82767.019948 -44.908585 False POINT (702430 173419) True 153.0 interpolated
7457 734809.0 179103.0 4121.060700 1 NaN 115638.011785 -120.195968 False POINT (734809 179103) True 154.0 interpolated
[69]:
# add flight and tie heights to intersections table
inters["flight_height"] = np.nan
inters["tie_height"] = np.nan
for ind, row in inters.iterrows():
    # search data for values at intersecting lines
    flight_values = data_df[
        (data_df.line == row.line) & (data_df.intersecting_line == row.tie)
    ]
    tie_values = data_df[
        (data_df.line == row.tie) & (data_df.intersecting_line == row.line)
    ]

    # get heights
    flight_height = flight_values.height.to_numpy()
    tie_height = tie_values.height.to_numpy()

    # add to intersection table
    inters.loc[ind, "flight_height"] = flight_height
    inters.loc[ind, "tie_height"] = tie_height
inters.head()
[69]:
line tie geometry max_dist easting northing dist_along_flight_line dist_along_flight_tie flight_interpolation_type tie_interpolation_type mistie_0 flight_height tie_height inter_height_diff
0 1 143 POINT (1158153 254083) 16.441255 1158153.0 254083.0 545768.072695 138071.419338 interpolated interpolated 100.458661 4168.038983 4008.397691 938.813163
1 1 144 POINT (1190820 259865) 24.124887 1190820.0 259865.0 578952.394781 131585.125284 interpolated interpolated 32.151483 4174.500813 4010.840823 941.256295
2 1 145 POINT (1223524 265689) 17.269650 1223524.0 265689.0 612181.972720 147253.496664 interpolated interpolated 72.936553 3544.149925 3995.961216 926.376688
3 1 146 POINT (1256193 271497) 31.608179 1256193.0 271497.0 645372.035087 136211.666619 interpolated interpolated 84.125118 3564.579994 3578.174277 508.589749
4 1 147 POINT (1288901 277249) 50.174377 1288901.0 277249.0 678598.170740 155115.224990 interpolated interpolated -7.731396 3536.160719 3545.947314 476.362786
[70]:
inters["inter_height_diff"] = np.abs(inters["flight_height"] - inters["tie_height"])
airbornegeo.plotly_points(
    inters,
    color_col="inter_height_diff",
    hover_cols=["line", "tie"],
    robust=False,
    size=6,
)
[71]:
airbornegeo.plot_line_and_crosses(
    data_df,
    line=148,
    x="distance_along_line",
    y=["height", "mag"],
    y_axes=[1, 2],
    plot_inters=True,
)

From the above plot showing line 148, we can see many of the cross lines were flown several hundred meters higher or lower.

4.7. Fit equivalent sources to each line#

[72]:
eqs = airbornegeo.eq_sources_1d(
    data_df,
    data_column="mag",
    depth="default",
    damping=None,
    block_size=500,
    groupby_column="line",
)
Groups: 100%|██████████| 110/110 [01:29<00:00,  1.23it/s]

4.8. Upward continue the intersection points to common altitudes#

For each intersection point, we find the highest of the two flight elevations, and upward continue the lower data point to this height. This only changes the individual intersection point, not any of the rest of the line. You can see in the below profile of line 148 that the line data is the same between mag and the new column mag_eqs, but if you look at the intersection points (diamonds), you will see some of the have shifted slightly.

[73]:
data_df["mag_eqs"] = airbornegeo.update_intersections_with_eq_sources(
    data_df,
    fitted_equivalent_sources=eqs,
    data_column="mag",
)
Segments: 100%|██████████| 110/110 [00:05<00:00, 19.78it/s]
[74]:
airbornegeo.plot_line_and_crosses(
    data_df,
    line=148,
    y=["height", "mag", "mag_eqs"],
    y_axes=[1, 2, 2],
    plot_inters=[True, True, True],
)

4.9. Recalculate cross-over errors with the updated intersection values#

[75]:
inters = airbornegeo.calculate_crossover_errors(
    data_df,
    inters,
    data_col="mag_eqs",
    plot_map=True,
)
_images/using_equivalent_sources_for_cross_over_errors_27_1.png
[76]:
airbornegeo.plot_levelling_convergence(inters)
_images/using_equivalent_sources_for_cross_over_errors_28_0.png

We can see that by just upward continuing the intersection points, we have slightly lowered the cross-over errors.