nsidc-iceflow example

This notebook shows an example of how to use the nsidc-iceflow Python library to do ITRF transformations with real data. We recommend starting with the Applying Coordinate Transformations to Facilitate Data Comparison notebook to learn more about ITRF transformations and why they matter.

For background on airborne altimetry missions supported by nsidc-iceflow, see Altimetry Data at NSIDC: Point Cloud Data Overview.

Lets assume we want to do an analysis using IceBridge ATM L1B Qfit Elevation and Return Strength, Version 1 (ILATM1B) data near Pine Island Glacier in Antarctica.

Finding, downloading, and reading ILATM1B v1 data with nsidc-iceflow is straightforward. ILATM1B data can be very large, so for the purposes of this example we will focus on just a small area near Pine Island Glacier along with a short date range in order to fetch a manageable amount of data.

To learn about how to download and manage larger amounts of data across many datasets with nsidc-iceflow, see the Using nsidc-iceflow with icepyx to Generate an Elevation Timeseries notebook.

Import required packages

We will import several functions from the nsidc-iceflow library along with pathlib to specify a download path, datetime to define our date range, and matplotlib to plot our results.

from __future__ import annotations

import datetime as dt
from pathlib import Path

import matplotlib.pyplot as plt

from nsidc.iceflow import (
    Dataset,
    download_iceflow_results,
    find_iceflow_data,
    read_iceflow_datafiles,
)

# note: use `inline` to save the resulting image as an embedded png (nice for sharing). \
# Use `widget` to obtain interactive controls to explore the data in depth!
%matplotlib inline
# %matplotlib widget

Finding, downloading, and reading data

Next we define our download path, data set of interest, and filter parameters:

# Downloaded data will go here.
data_path = Path("./downloaded-data/")
data_path.mkdir(exist_ok=True)

# Define the dataset that we want to search for.
atm1b_v1_dataset = Dataset(short_name="ILATM1B", version="1")

# Define a bounding box for our area of interest.
BBOX = (
    -103.125559,
    -75.180563,
    -102.677327,
    -74.798063,
)

# We will define a short date range in 2009 to search for data.
date_range = (dt.date(2009, 11, 1), dt.date(2009, 12, 31))

Next, we use the find_iceflow_data function to search for data matching our search parameters.

search_results = find_iceflow_data(
    datasets=[atm1b_v1_dataset],
    bounding_box=BBOX,
    temporal=date_range,
)
len(search_results)
1

Once we have found data matching our area of interest, we can download the results with download_iceflow_results

downloaded_files = download_iceflow_results(search_results, output_dir=data_path)
print(downloaded_files)
2025-06-12 17:43:41.316 | INFO     | nsidc.iceflow.data.fetch:_download_iceflow_search_result:62 - Downloading 1 granules to downloaded-data/ILATM1B_1.
[PosixPath('downloaded-data/ILATM1B_1/ILATM1B_20091109_203148.atm4cT3.qi')]

Finally, we can read the data we just downloaded with read_iceflow_datafiles. The output is a pandas DataFrame containing the matching data.

iceflow_df = read_iceflow_datafiles(downloaded_files)

iceflow_df.head()
rel_time latitude longitude elevation xmt_sigstr rcv_sigstr azimuth pitch roll gps_pdop pulse_width gps_time passive_signal passive_footprint_latitude passive_footprint_longitude passive_footprint_synthesized_elevation ITRF
utc_datetime
2009-11-09 20:31:49.000 0.0 -75.159033 -102.290217 136.427994 1406.0 1393.0 164903.0 1547.0 1253.0 24.0 7.0 203204000.0 NaN NaN NaN NaN ITRF2005
2009-11-09 20:31:49.000 0.0 -75.159036 -102.290130 136.436996 973.0 1064.0 166291.0 1547.0 1253.0 24.0 6.0 203204000.0 NaN NaN NaN NaN ITRF2005
2009-11-09 20:31:49.000 0.0 -75.159041 -102.290045 136.386002 1586.0 1546.0 167679.0 1547.0 1253.0 24.0 7.0 203204000.0 NaN NaN NaN NaN ITRF2005
2009-11-09 20:31:49.000 0.0 -75.159046 -102.289961 136.347000 1101.0 1192.0 169067.0 1547.0 1253.0 24.0 7.0 203204000.0 NaN NaN NaN NaN ITRF2005
2009-11-09 20:31:49.001 1.0 -75.159051 -102.289878 136.298996 1302.0 1296.0 170455.0 1547.0 1253.0 24.0 8.0 203204001.0 NaN NaN NaN NaN ITRF2005

For the purposes of the rest of this example, we will narrow our focus to just the latitude, longitude, elevation, and ITRF values.

iceflow_df = iceflow_df[["latitude", "longitude", "elevation", "ITRF"]]
iceflow_df.head()
latitude longitude elevation ITRF
utc_datetime
2009-11-09 20:31:49.000 -75.159033 -102.290217 136.427994 ITRF2005
2009-11-09 20:31:49.000 -75.159036 -102.290130 136.436996 ITRF2005
2009-11-09 20:31:49.000 -75.159041 -102.290045 136.386002 ITRF2005
2009-11-09 20:31:49.000 -75.159046 -102.289961 136.347000 ITRF2005
2009-11-09 20:31:49.001 -75.159051 -102.289878 136.298996 ITRF2005

ITRF transformations

We can see that ILATM1B v1 uses ITRF2005. Lets say we have other data that are in ITRF2014, and we want to compare that to the ILATM1B v1 data we just fetched. nsidc-iceflow provides a transform_itrf function that allows the user to transform latitude, longitude, and elevation data from an nsidc-iceflow dataframe into a target ITRF.

from nsidc.iceflow import transform_itrf

itrf2014_df = transform_itrf(data=iceflow_df, target_itrf="ITRF2014")
itrf2014_df.head()
latitude longitude elevation ITRF
utc_datetime
2009-11-09 20:31:49.000 -75.159033 -102.290217 136.420352 ITRF2014
2009-11-09 20:31:49.000 -75.159036 -102.290130 136.429354 ITRF2014
2009-11-09 20:31:49.000 -75.159041 -102.290045 136.378359 ITRF2014
2009-11-09 20:31:49.000 -75.159046 -102.289961 136.339358 ITRF2014
2009-11-09 20:31:49.001 -75.159051 -102.289878 136.291354 ITRF2014

Let’s take a look at the differences between the original data (ITRF2005) and the data that has been transformed to ITRF2014.

for variable in ["latitude", "longitude", "elevation"]:
    print(
        f"Max difference in {variable}: {abs(itrf2014_df[variable] - iceflow_df[variable]).max()}"
    )
Max difference in latitude: 1.8509084043216717e-08
Max difference in longitude: 7.992288431069028e-08
Max difference in elevation: 0.00764224398881197

We can see here that the differences are very small! The largest elevation difference is 0.007 - 7mm!

Propagating data to a new epoch

Now lets assume we need to propagate the data to a target epoch of 2019.7, which corresponds to September 13, 2019. This accounts for continental plate motion.

The transform_itrf function optionally takes a target_epoch in order to do this transformation. We’ll use the original data as input.

itrf2014_epoch_2019_7_df = transform_itrf(
    data=iceflow_df,
    target_itrf="ITRF2014",
    target_epoch="2019.7",
)

itrf2014_epoch_2019_7_df.head()
latitude longitude elevation ITRF
utc_datetime
2009-11-09 20:31:49.000 -75.159033 -102.290211 136.420264 ITRF2014
2009-11-09 20:31:49.000 -75.159036 -102.290124 136.429267 ITRF2014
2009-11-09 20:31:49.000 -75.159041 -102.290039 136.378272 ITRF2014
2009-11-09 20:31:49.000 -75.159046 -102.289955 136.339270 ITRF2014
2009-11-09 20:31:49.001 -75.159051 -102.289872 136.291266 ITRF2014

The maximum difference is still small:

for variable in ["latitude", "longitude", "elevation"]:
    print(
        f"Max difference in {variable}: {abs(itrf2014_epoch_2019_7_df[variable] - iceflow_df[variable]).max()}"
    )
Max difference in latitude: 4.895740346455568e-07
Max difference in longitude: 5.5522629338611296e-06
Max difference in elevation: 0.007729958742856979

Visualizing the differences

To visualize the differences, which are very small, we need to zoom in on just a small subset of points. First we set a filter condition and apply it to each DataFrame.

filter_condition = (iceflow_df.reset_index().index > 50) & (
    iceflow_df.reset_index().index < 60
)
sampled_iceflow_df = iceflow_df[filter_condition]
sampled_itrf2014_df = itrf2014_df[filter_condition]
sampled_itrf2014_epoch_2019_7_df = itrf2014_epoch_2019_7_df[filter_condition]

Now we create a 3D plot using matplotlib to visualize the differences across latitude, longitude, and elevation.

fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection="3d")
ax.scatter(
    sampled_iceflow_df.longitude.values,
    sampled_iceflow_df.latitude.values,
    sampled_iceflow_df.elevation.values,
    color="blue",
    marker="o",
    label="original",
)

ax.scatter(
    sampled_itrf2014_df.longitude.values,
    sampled_itrf2014_df.latitude.values,
    sampled_itrf2014_df.elevation.values,
    color="green",
    marker="x",
    label="ITRF2014",
)

ax.scatter(
    sampled_itrf2014_epoch_2019_7_df.longitude.values,
    sampled_itrf2014_epoch_2019_7_df.latitude.values,
    sampled_itrf2014_epoch_2019_7_df.elevation.values,
    color="red",
    marker="v",
    label="ITRF2014 epoch 2019.7",
)

ax.set_xlabel("longitude (degrees)", labelpad=10)
ax.set_ylabel("latitude (degrees)", labelpad=10)
ax.set_zlabel("elevation (m)", labelpad=10)
plt.legend(loc="upper left")
plt.show()
../_images/faa17d03a131219b1a7b356aef63373b42fce498ad267f815931b5bd65ef32b7.png