Using nsidc-iceflow with icepyx to Generate an Elevation Timeseries

This notebook shows how to produce a harmonized elevation timeseries across all iceflow-supported datasets along with ICESat-2 data using icepyx.

If this is your first time using nsidc-iceflow, we recommend starting with the NSIDC Iceflow example Jupyter Notebook first.

Similarly, if you are new to icepyx, we suggest reviewing the icepyx documentation for more information about how to use icepyx.

Scenario: assessing ice surface elevation change near Sermeq Kujalleq (Jakobshavn Isbrae)

In this notebook, we will focus on a small area near Sermeq Kujalleq (Jakobshavn Isbrae).

This notebook will demonstrate how to:

  • Search for and download all iceflow-supported data for our area of interest and timeframe.

  • Search for and download ICESat-2 data using icepyx.

  • Average elevation data over our area of interest at a weekly resolution.

  • Plot the results as a timeseries.

Import required packages

We will import several packages needed to perform our analysis:

# Imports
from __future__ import annotations

import datetime as dt
from pathlib import Path

import dask.dataframe as dd
import icepyx as ipx
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import xarray as xr

from nsidc.iceflow import (
    IceflowDataFrame,
    download_iceflow_results,
    find_iceflow_data,
    make_iceflow_parquet,
)

Search for and download iceflow-supported data for our area of interest and timeframe.

We first need to define some constants, including our download path, ITRF, and filter parameters. These will be used throughout the rest of the notebook.

Note that the data supported by nsidc-iceflow and icepyx can be very large! This tutorial will download ~2GB worth of data to local disk.

# Constants

# All of our data will be downloaded to this location.
OUTPUT_DIR = Path("./downloaded-data/")
OUTPUT_DIR.mkdir(exist_ok=True)

# ICESat2 data products use ITRF2014 (e.g., see https://nsidc.org/data/atl06/versions/6):
# > WGS 84 ellipsoid, ITRF2014 reference frame
# NOTE/TODO: This is expected to change in the near future! ICESat2 release
# 7, scheduled for spring 2025, is expected to be referenced to ITRF2020.
ICESAT2_ITRF = "ITRF2014"

# This bounding box covers an area near Sermeq Kujalleq (Jakobshavn Isbrae)
BBOX = (
    -49.149,
    69.186,
    -48.949,
    69.238,
)

# Range of dates we want to evaluate
DATE_RANGE = (dt.date(2007, 1, 1), dt.date(2024, 10, 28))

Next we will use the find_iceflow_data function from the nsidc-iceflow API to find data matching our area of interest.

By default, find_iceflow_data will include all nsidc-iceflow supported datasets, unless one or more are specified as a filter with the datasets kwarg. There may be warnings raised about there not being search results for specific datasets supported by nsidc-iceflow.

search_results = find_iceflow_data(
    bounding_box=tuple(BBOX),
    temporal=DATE_RANGE,
)
len(search_results)
2025-06-16 10:23:28.163 | WARNING  | nsidc.iceflow.data.fetch:_find_iceflow_data_for_dataset:43 - Found no results for dataset.short_name='ILVIS2' dataset.version='1' with search_kwargs={'bounding_box': (-49.149, 69.186, -48.949, 69.238), 'temporal': (datetime.date(2007, 1, 1), datetime.date(2024, 10, 28))}
6

Now download the matching files to disk using download_iceflow_results.

Note: This next step may take a while, and will download data to your local disk.

downloaded_files = download_iceflow_results(search_results, output_dir=OUTPUT_DIR)
2025-06-16 10:23:34.292 | INFO     | nsidc.iceflow.data.fetch:_download_iceflow_search_result:62 - Downloading 4 granules to downloaded-data/ILATM1B_1.
2025-06-16 10:23:53.408 | INFO     | nsidc.iceflow.data.fetch:_download_iceflow_search_result:62 - Downloading 14 granules to downloaded-data/ILATM1B_2.
2025-06-16 10:24:08.263 | INFO     | nsidc.iceflow.data.fetch:_download_iceflow_search_result:62 - Downloading 13 granules to downloaded-data/BLATM1B_1.
2025-06-16 10:24:57.489 | INFO     | nsidc.iceflow.data.fetch:_download_iceflow_search_result:62 - Downloading 2 granules to downloaded-data/ILVIS2_2.
2025-06-16 10:25:13.304 | INFO     | nsidc.iceflow.data.fetch:_download_iceflow_search_result:62 - Downloading 8 granules to downloaded-data/GLAH06_034.

Finally, we will create a parquet dataset using make_iceflow_parquet.Writing data to a parquet dataset allows dask (which we will use later!) to read the chunks of data it needs to do calculations (e.g., mean) without needing to read all of the data into memory at once. This is important because nsidc-iceflow can find many millions of data points for even small areas of interest!

This function transforms all data found in the data_dir to the provided target_itrf, further facilitating analysis across many datasets.

Note: This next step may take a while

parquet_path = make_iceflow_parquet(
    data_dir=OUTPUT_DIR,
    target_itrf=ICESAT2_ITRF,
    overwrite=True,
)

Now we read the data stored in the parquet dataset using dask:

iceflow_df = dd.read_parquet(parquet_path)

# Ensure that our index is set as a datetime object
iceflow_df = iceflow_df.reset_index()
iceflow_df["utc_datetime"] = dd.to_datetime(iceflow_df["utc_datetime"])
iceflow_df = iceflow_df.set_index("utc_datetime")

iceflow_df.head()
latitude longitude elevation dataset
utc_datetime
2007-03-12 06:39:58.970415 50.105011 -41.965750 40.621659 GLAH06v034
2007-03-12 06:39:58.995415 50.106557 -41.966118 39.633659 GLAH06v034
2007-03-12 06:39:59.220415 50.120483 -41.969395 40.284658 GLAH06v034
2007-03-12 06:39:59.320415 50.126654 -41.970829 40.168658 GLAH06v034
2007-03-12 06:40:00.520415 50.200840 -41.988417 39.976656 GLAH06v034

Search for and download ICESat-2 data using icepyx

Next, we will use icepyx to find ATL06 data for the same area of interest and timeframe.

To learn more about icepyx.Query, which is used below, see the documentation.

# We will compare the ILATM1B data to ATL06 data from October 2018.
result = ipx.Query(
    "ATL06",
    list(BBOX),
    DATE_RANGE,
)
result
<icepyx.core.query.Query at 0x7b8227f62f00>

Now we place an order and download the results. Note that this may take a while to download data to local disk.

Note: These next steps may take a while

result.order_granules(subset=True)
Harmony job ID:  1d2cafe2-2691-495c-b00d-692624a72ddb
Initial status of your harmony order request: running
Job ID Type Status Details
1d2cafe2-2691-495c-b00d-692624a72ddb subset running View Details
result.download_granules("downloaded-data/ATL06/")
Your harmony job status is still running. Please continue waiting... this may take a few moments.
................................Downloading results for harmony job 1d2cafe2-2691-495c-b00d-692624a72ddb
downloaded-data/ATL06/102107183_ATL06_20181111054901_06660105_006_02_subsetted.h5
downloaded-data/ATL06/102107184_ATL06_20181217152316_12220103_006_02_subsetted.h5
downloaded-data/ATL06/102107185_ATL06_20190112025232_02240205_006_02_subsetted.h5
downloaded-data/ATL06/102107187_ATL06_20190318110313_12220203_006_02_subsetted.h5
downloaded-data/ATL06/102107186_ATL06_20190115135914_02770203_006_02_subsetted.h5
downloaded-data/ATL06/102107190_ATL06_20190511210825_06660305_006_02_subsetted.h5
downloaded-data/ATL06/102107191_ATL06_20190617064249_12220303_006_02_subsetted.h5
downloaded-data/ATL06/102107193_ATL06_20190810164806_06660405_006_02_subsetted.h5
downloaded-data/ATL06/102107194_ATL06_20190916022239_12220403_006_02_subsetted.h5
downloaded-data/ATL06/102107195_ATL06_20191011135158_02240505_006_02_subsetted.h5
downloaded-data/ATL06/102107199_ATL06_20200110093142_02240605_006_01_subsetted.h5
downloaded-data/ATL06/102107197_ATL06_20191109122800_06660505_006_01_subsetted.h5
downloaded-data/ATL06/102107198_ATL06_20191215220228_12220503_006_01_subsetted.h5
downloaded-data/ATL06/102107201_ATL06_20200208080743_06660605_006_01_subsetted.h5
downloaded-data/ATL06/102107202_ATL06_20200315174217_12220603_006_01_subsetted.h5
downloaded-data/ATL06/102107203_ATL06_20200410051129_02240705_006_02_subsetted.h5
downloaded-data/ATL06/102107206_ATL06_20200614132201_12220703_006_01_subsetted.h5
downloaded-data/ATL06/102107205_ATL06_20200509034734_06660705_006_01_subsetted.h5
downloaded-data/ATL06/102107209_ATL06_20200807232719_06660805_006_01_subsetted.h5
downloaded-data/ATL06/102107211_ATL06_20201008203102_02240905_006_01_subsetted.h5
downloaded-data/ATL06/102107210_ATL06_20200913090149_12220803_006_02_subsetted.h5
downloaded-data/ATL06/102107214_ATL06_20210107161057_02241005_006_01_subsetted.h5
downloaded-data/ATL06/102107213_ATL06_20201213044139_12220903_006_01_subsetted.h5
downloaded-data/ATL06/102107212_ATL06_20201106190706_06660905_006_01_subsetted.h5
downloaded-data/ATL06/102107216_ATL06_20210205144704_06661005_006_01_subsetted.h5
downloaded-data/ATL06/102107217_ATL06_20210314002135_12221003_006_01_subsetted.h5
downloaded-data/ATL06/102107218_ATL06_20210408115050_02241105_006_02_subsetted.h5
downloaded-data/ATL06/102107222_ATL06_20210708073039_02241205_006_01_subsetted.h5
downloaded-data/ATL06/102107220_ATL06_20210507102653_06661105_006_01_subsetted.h5
downloaded-data/ATL06/102107224_ATL06_20210806060645_06661205_006_01_subsetted.h5
downloaded-data/ATL06/102107221_ATL06_20210612200124_12221103_006_01_subsetted.h5
downloaded-data/ATL06/102107225_ATL06_20211007031039_02241305_006_01_subsetted.h5
downloaded-data/ATL06/102107227_ATL06_20211105014647_06661305_006_01_subsetted.h5
downloaded-data/ATL06/102107229_ATL06_20220105225028_02241405_006_01_subsetted.h5
downloaded-data/ATL06/102107228_ATL06_20211211112116_12221303_006_01_subsetted.h5
downloaded-data/ATL06/102107234_ATL06_20220611024058_12221503_006_01_subsetted.h5
downloaded-data/ATL06/102107232_ATL06_20220312070106_12221403_006_01_subsetted.h5
downloaded-data/ATL06/102107231_ATL06_20220203212633_06661405_006_01_subsetted.h5
downloaded-data/ATL06/102107233_ATL06_20220505170624_06661505_006_01_subsetted.h5
downloaded-data/ATL06/102107237_ATL06_20220804124627_06661605_006_01_subsetted.h5
downloaded-data/ATL06/102107238_ATL06_20220909222056_12221603_006_01_subsetted.h5
downloaded-data/ATL06/102107241_ATL06_20221103082608_06661705_006_01_subsetted.h5
downloaded-data/ATL06/102107239_ATL06_20221005095006_02241705_006_01_subsetted.h5
downloaded-data/ATL06/102107235_ATL06_20220706141022_02241605_006_02_subsetted.h5
downloaded-data/ATL06/102107242_ATL06_20221209180033_12221703_006_02_subsetted.h5
downloaded-data/ATL06/102107243_ATL06_20230104052948_02241805_006_02_subsetted.h5
downloaded-data/ATL06/102107248_ATL06_20230503234538_06661905_006_03_subsetted.h5
downloaded-data/ATL06/102107245_ATL06_20230202040603_06661805_006_02_subsetted.h5
downloaded-data/ATL06/102107246_ATL06_20230405010947_02241905_006_02_subsetted.h5
downloaded-data/ATL06/102107253_ATL06_20230908045912_12222003_006_02_subsetted.h5
downloaded-data/ATL06/102107249_ATL06_20230609091949_12221903_006_02_subsetted.h5
downloaded-data/ATL06/102107252_ATL06_20230802192451_06662005_006_02_subsetted.h5
downloaded-data/ATL06/102107255_ATL06_20231101150428_06662105_006_01_subsetted.h5
downloaded-data/ATL06/102107258_ATL06_20240102120809_02242205_006_01_subsetted.h5
downloaded-data/ATL06/102107261_ATL06_20240402074729_02242305_006_01_subsetted.h5
downloaded-data/ATL06/102107263_ATL06_20240501062333_06662305_006_01_subsetted.h5
downloaded-data/ATL06/102107260_ATL06_20240131104358_06662205_006_01_subsetted.h5
downloaded-data/ATL06/102107264_ATL06_20240702032717_02242405_006_01_subsetted.h5
downloaded-data/ATL06/102107257_ATL06_20231208003854_12222103_006_03_subsetted.h5
downloaded-data/ATL06/102107268_ATL06_20240930230647_02242505_006_02_subsetted.h5
downloaded-data/ATL06/102107266_ATL06_20240731020304_06662405_006_01_subsetted.h5
downloaded-data/ATL06/102107267_ATL06_20240905113742_12222403_006_01_subsetted.h5
[PosixPath('downloaded-data/ATL06/102107183_ATL06_20181111054901_06660105_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107184_ATL06_20181217152316_12220103_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107185_ATL06_20190112025232_02240205_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107187_ATL06_20190318110313_12220203_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107186_ATL06_20190115135914_02770203_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107190_ATL06_20190511210825_06660305_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107191_ATL06_20190617064249_12220303_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107193_ATL06_20190810164806_06660405_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107194_ATL06_20190916022239_12220403_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107195_ATL06_20191011135158_02240505_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107199_ATL06_20200110093142_02240605_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107197_ATL06_20191109122800_06660505_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107198_ATL06_20191215220228_12220503_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107201_ATL06_20200208080743_06660605_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107202_ATL06_20200315174217_12220603_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107203_ATL06_20200410051129_02240705_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107206_ATL06_20200614132201_12220703_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107205_ATL06_20200509034734_06660705_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107209_ATL06_20200807232719_06660805_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107211_ATL06_20201008203102_02240905_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107210_ATL06_20200913090149_12220803_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107214_ATL06_20210107161057_02241005_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107213_ATL06_20201213044139_12220903_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107212_ATL06_20201106190706_06660905_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107216_ATL06_20210205144704_06661005_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107217_ATL06_20210314002135_12221003_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107218_ATL06_20210408115050_02241105_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107222_ATL06_20210708073039_02241205_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107220_ATL06_20210507102653_06661105_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107224_ATL06_20210806060645_06661205_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107221_ATL06_20210612200124_12221103_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107225_ATL06_20211007031039_02241305_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107227_ATL06_20211105014647_06661305_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107229_ATL06_20220105225028_02241405_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107228_ATL06_20211211112116_12221303_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107234_ATL06_20220611024058_12221503_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107232_ATL06_20220312070106_12221403_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107231_ATL06_20220203212633_06661405_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107233_ATL06_20220505170624_06661505_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107237_ATL06_20220804124627_06661605_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107238_ATL06_20220909222056_12221603_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107241_ATL06_20221103082608_06661705_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107239_ATL06_20221005095006_02241705_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107235_ATL06_20220706141022_02241605_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107242_ATL06_20221209180033_12221703_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107243_ATL06_20230104052948_02241805_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107248_ATL06_20230503234538_06661905_006_03_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107245_ATL06_20230202040603_06661805_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107246_ATL06_20230405010947_02241905_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107253_ATL06_20230908045912_12222003_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107249_ATL06_20230609091949_12221903_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107252_ATL06_20230802192451_06662005_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107255_ATL06_20231101150428_06662105_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107258_ATL06_20240102120809_02242205_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107261_ATL06_20240402074729_02242305_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107263_ATL06_20240501062333_06662305_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107260_ATL06_20240131104358_06662205_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107264_ATL06_20240702032717_02242405_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107257_ATL06_20231208003854_12222103_006_03_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107268_ATL06_20240930230647_02242505_006_02_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107266_ATL06_20240731020304_06662405_006_01_subsetted.h5'),
 PosixPath('downloaded-data/ATL06/102107267_ATL06_20240905113742_12222403_006_01_subsetted.h5')]

Read ICESat-2 data into xarray and pandas

Next, we will use icepyx.Read to read the data into an xarray Dataset.

Note that the code below wraps the reading of each file in a try/except block because of a known issue with subsetting. See https://github.com/icesat2py/icepyx/issues/576 for more information.

datasets = []
for file in Path("downloaded-data/ATL06/").glob("*.h5"):
    try:
        reader = ipx.Read(str(file))
        reader.variables.append(var_list=["h_li", "latitude", "longitude"])
        ds = reader.load()
        datasets.append(ds)
    except Exception:
        print(f"{file=} contains an error and will not be read")
        continue

len(datasets)
file=PosixPath('downloaded-data/ATL06/102107201_ATL06_20200208080743_06660605_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107263_ATL06_20240501062333_06662305_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107238_ATL06_20220909222056_12221603_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107186_ATL06_20190115135914_02770203_006_02_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107264_ATL06_20240702032717_02242405_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107214_ATL06_20210107161057_02241005_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107248_ATL06_20230503234538_06661905_006_03_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107224_ATL06_20210806060645_06661205_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107234_ATL06_20220611024058_12221503_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107213_ATL06_20201213044139_12220903_006_01_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107268_ATL06_20240930230647_02242505_006_02_subsetted.h5') contains an error and will not be read
file=PosixPath('downloaded-data/ATL06/102107253_ATL06_20230908045912_12222003_006_02_subsetted.h5') contains an error and will not be read
50
ds = xr.concat(datasets, dim="gran_idx")
ds.head()
<xarray.Dataset> Size: 5kB
Dimensions:              (gran_idx: 5, spot: 5, photon_idx: 5)
Coordinates:
  * photon_idx           (photon_idx) int64 40B 0 1 2 3 4
  * spot                 (spot) uint8 5B 1 2 3 4 5
  * gran_idx             (gran_idx) uint64 40B 22412 66614 122213 22401 66610
    source_file          (gran_idx) <U81 2kB 'downloaded-data/ATL06/102107225...
    delta_time           (gran_idx, photon_idx) datetime64[ns] 200B 2021-10-0...
Data variables:
    sc_orient            (gran_idx) int8 5B 0 0 0 0 1
    cycle_number         (gran_idx) int8 5B 13 15 14 2 11
    rgt                  (gran_idx) int16 10B 224 666 1222 224 666
    atlas_sdp_gps_epoch  (gran_idx) datetime64[ns] 40B 2018-01-01T00:00:18 .....
    data_start_utc       (gran_idx) datetime64[ns] 40B 2021-10-07T03:10:39.42...
    data_end_utc         (gran_idx) datetime64[ns] 40B 2021-10-07T03:15:15.96...
    h_li                 (spot, gran_idx, photon_idx) float32 500B 793.6 ... nan
    latitude             (spot, gran_idx, photon_idx) float64 1kB 69.24 ... nan
    longitude            (spot, gran_idx, photon_idx) float64 1kB -49.15 ... nan
    gt                   (gran_idx, spot) object 200B 'gt1l' nan ... 'gt2l' nan
Attributes:
    data_product:  ATL06
    Description:   The land_ice_height group contains the primary set of deri...
    data_rate:     Data within this group are sparse.  Data values are provid...

icepyx reads ICESat-2 data as an xarray dataset. xarray is a powerful tool and the data is ready to use in this format, but to simplify things for this notebook and make the data more compatible with nsidc-iceflow, the next step will convert the data into an nsidc-iceflow-compatible pandas DataFrame.

The first step is to rename the variables to be consistent with how nsidc-iceflow identifies the elevation and time fields:

ds = ds.rename({"h_li": "elevation", "delta_time": "utc_datetime"})

The following code block converts the xarray dataset into an iceflow dataframe:

spot_dfs = []
for spot in ds.spot.data.flatten():
    df = pd.DataFrame.from_dict(
        {
            "elevation": ds.sel(spot=spot).elevation.data.flatten(),
            "latitude": ds.sel(spot=spot).latitude.data.flatten(),
            "longitude": ds.sel(spot=spot).longitude.data.flatten(),
            "utc_datetime": ds.sel(spot=spot).utc_datetime.data.flatten(),
            "spot": [spot] * len(ds.sel(spot=spot).elevation.data.flatten()),
            "ITRF": ICESAT2_ITRF,
        },
    )
    spot_dfs.append(df)


df = pd.concat(spot_dfs, sort=True)
# Drop rows where lat, lon, or elev are missing.
df = df.dropna(subset=["latitude", "longitude", "elevation"], how="any")
df = df.set_index("utc_datetime")
# Cast the df as an IceflowDataFrame.
# The `atl06_df` can now be used with e.g., `nsidc-iceflow`'s ITRF conversion function
# to perform plate motion model adjustments if desired.
atl06_df = IceflowDataFrame(df)
atl06_df.head()
ITRF elevation latitude longitude spot
utc_datetime
2021-10-07 03:13:30.711221536 ITRF2014 793.648804 69.237833 -49.146681 1
2021-10-07 03:13:30.714035872 ITRF2014 793.453491 69.237655 -49.146743 1
2021-10-07 03:13:30.716850784 ITRF2014 793.404785 69.237477 -49.146805 1
2021-10-07 03:13:30.719666512 ITRF2014 793.342651 69.237299 -49.146866 1
2021-10-07 03:13:30.722483664 ITRF2014 793.249939 69.237121 -49.146928 1

The ATL06 data contains some negative elevation values, as we see printed below. We will filter these out, as we expect positive elevations.

print(atl06_df.elevation.min())

# Filter out negative values. We expect positive elevations.
atl06_df = atl06_df[atl06_df.elevation > 0]
-1320.41162109375

Average elevation data over our area of interest at a weekly resolution

In this next step, we will resample the ATL06 and nsidc-iceflow data to a weekly resolution, taking the mean of elevation over our area of interest. This will provide us with one data point per week where data is available, giving us a general idea of how the elevation of our area of interest changes over time. First we resample ATL06 data to a weekly mean:

atl06_avg_df = atl06_df[["elevation"]].resample("W").mean()
atl06_avg_df = atl06_avg_df.dropna(how="any")
atl06_avg_df.head()
elevation
utc_datetime
2018-11-11 876.653491
2018-12-23 887.003014
2019-01-13 836.233506
2019-03-24 865.448348
2019-05-12 877.559574

And now we resample the iceflow data to a weekly mean:

iceflow_df_sampled = iceflow_df.repartition(freq="1W")
iceflow_df_sampled = iceflow_df_sampled.dropna(how="any")

iceflow_df_sampled = iceflow_df_sampled[iceflow_df_sampled.elevation > 0]

iceflow_avg = iceflow_df_sampled.resample("W").agg(
    {
        "elevation": "mean",
        "dataset": lambda x: ", ".join(x.astype("str").unique()),
    }
)
iceflow_avg = iceflow_avg.replace([np.inf, -np.inf], np.nan)
iceflow_avg = iceflow_avg.dropna(how="any")
iceflow_avg = iceflow_avg.compute()

iceflow_avg.head()
elevation dataset
utc_datetime
2007-03-18 657.922769 GLAH06v034
2007-10-07 438.062308 GLAH06v034
2008-02-17 181.279161 GLAH06v034
2008-06-29 888.758989 BLATM1Bv1
2008-07-06 879.285982 BLATM1Bv1

Plot the results as a timeseries

Now we will use matplotlib to plot the results as a timeseries:

%matplotlib inline

# We will use a unique marker for each dataset
iceflow_marker_map = {
    "GLAH06v034": "s",
    "BLATM1Bv1": "D",
    "ILATM1Bv1": "x",
    "ILATM1Bv2": "o",
    "ILVIS2v1": "v",
    "ILVIS2v2": "^",
}

fig = plt.figure(figsize=(10, 8))

for dataset, marker in iceflow_marker_map.items():
    subset = iceflow_avg[iceflow_avg.dataset == dataset]
    if subset.elevation.any():
        plt.scatter(
            subset.index,
            subset.elevation,
            marker=marker,
            label=dataset,
            linestyle="",
            color="black",
        )

plt.scatter(
    atl06_avg_df.index,
    atl06_avg_df.elevation,
    color="black",
    marker="*",
    label="ATL06v6",
    linestyle="",
)

plt.xlabel("Date")
plt.ylabel("Elevation")
plt.legend(title="Dataset")
<matplotlib.legend.Legend at 0x7b822655c500>
../_images/9a7d6cdf19c88190b7c2073fcedb4356007658cd4f8a42b285368d78ee9509e2.png

Conclusions

In this notebook, we found and analyzed laser altimetry data from a variety of datasets using nsidc-iceflow and icepyx.

In the timeseries plot above, we can see how the surface elevation of a small area near Sermeq Kujalleq (Jakobshavn Isbrae) changes over time.

Note that this analysis was relatively simple. Although the data and plot above give us an idea of surface elevation changes, it should be noted that there are still a number of things a researcher should consider when doing an analysis across many datasets over time. To further this analysis, we may want to consider doing one or more of the following:

  • Outlier detection and filtering

  • Cross-calibration of data between sensors/datasets

  • Plate motion model coordinate propagation (see the nsidc-iceflow example Jupyter Notebook for an example of how to do this)

  • Account for spatial variability within our region of interest