Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Accessing Antarctica Cube Data

ESA

This notebook is a runnable remote-access demo for the Antarctica data cubes.

The example uses a small Palmer Land / George VI Ice Shelf ROI. Change ROI_CENTER_XY_M or ROI_HALF_WIDTH_CELLS to explore another area. Remote reads still happen at the Zarr chunk level, so keep the ROI inside one chunk-sized area for quick laptop runs.

Setup

The constants below define the remote Zarr stores, the example ROI, and the sampling used for velocity arrows. All following sections use named variables rather than passing dictionaries around.

import numpy as np
import warnings
import xarray as xr
from dask.array.core import PerformanceWarning
from dask.diagnostics import ProgressBar
from matplotlib.colors import BoundaryNorm, ListedColormap
from matplotlib.patches import Rectangle

import matplotlib.pyplot as plt
import cartopy.crs as ccrs

warnings.filterwarnings("ignore", message="In a future version of xarray the default value for join.*", category=FutureWarning)
warnings.filterwarnings("ignore", message="Increasing number of chunks.*", category=PerformanceWarning)

# Approximate EPSG:3031 location for Palmer Land / George VI Ice Shelf.
ROI_CENTER_XY_M = (-2_000_000.0, 800_000.0)
ROI_HALF_WIDTH_CELLS = 3000
VELOCITY_VECTOR_STRIDE = 90
TEMPERATURE_DEPTH_TARGET_M = 1000

antarctic_crs = antarctic_crs = ccrs.SouthPolarStereo(
    central_longitude=0,
    true_scale_latitude=-71,
)
# Convert ROI center from meters to the appropriate scale and calculate half-width
cx, cy = ROI_CENTER_XY_M
half = ROI_HALF_WIDTH_CELLS * 100 

fig, ax = plt.subplots(figsize=(7, 7), subplot_kw={"projection": ccrs.SouthPolarStereo()})
ax.set_extent([-180, 180, -90, -58], crs=ccrs.PlateCarree())
ax.coastlines()
ax.add_patch(Rectangle((cx - half, cy - half), 2 * half, 2 * half, fill=False, edgecolor="red", linewidth=2, transform=ax.projection))
plt.show()
<Figure size 700x700 with 1 Axes>
cube_paths = [
    "https://s3.waw4-1.cloudferro.com/EarthCODE/OSCAssets/antarctica_cube/icetemp.zarr",
    "https://s3.waw4-1.cloudferro.com/EarthCODE/OSCAssets/antarctica_cube/sec.zarr",
    "https://s3.waw4-1.cloudferro.com/EarthCODE/OSCAssets/antarctica_cube/antarctica-combined.zarr",
    "https://s3.waw4-1.cloudferro.com/EarthCODE/OSCAssets/antarctica_cube/icemask_composite.zarr/",
    "https://s3.waw4-1.cloudferro.com/EarthCODE/OSCAssets/antarctica_cube/ice_velocity.zarr",
]

ds = xr.open_mfdataset(cube_paths, engine="zarr", chunks={}, compat="no_conflicts")

ds
Loading...

Select A Small Region Of Interest

The land/ice cubes share the same y, x grid. The helper below snaps the requested EPSG:3031 point to the nearest cube cell and builds one integer ROI slice that is reused for every cube.

x_index = int(np.abs(ds["x"].values - ROI_CENTER_XY_M[0]).argmin())
y_index = int(np.abs(ds["y"].values - ROI_CENTER_XY_M[1]).argmin())
x_slice = slice(x_index - ROI_HALF_WIDTH_CELLS, x_index + ROI_HALF_WIDTH_CELLS + 1)
y_slice = slice(y_index - ROI_HALF_WIDTH_CELLS, y_index + ROI_HALF_WIDTH_CELLS + 1)

small = ds.isel(x=x_slice, y=y_slice).chunk({"x": -1, "y": -1})

small
Loading...

Plot 1: Bedrock Topography And Thickness Above Flotation

This reproduces the bedrock-topography use case on the remote cube. It combines BedMachine-style bed, thickness, and mask variables to estimate thickness above flotation for grounded marine ice. Low values identify ice that is closer to flotation.

rho_ice = 917.0
rho_water = 1027.0

bed = small["bedrock_topography_bed"]
thickness = small["bedrock_topography_thickness"]
mask = small["bedrock_topography_mask"]

flotation_thickness = xr.where(bed < 0, -(rho_water / rho_ice) * bed, 0)
taf = (thickness - flotation_thickness).where((mask == 2) & (bed < 0)).clip(0, 500)

with ProgressBar():
    taf = taf.compute()

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": antarctic_crs})
taf.plot(ax=ax, cmap="viridis_r", vmin=0, vmax=500, transform=antarctic_crs)
ax.coastlines()
[########################################] | 100% Completed | 19.14 s
<cartopy.mpl.feature_artist.FeatureArtist at 0x33bb60cd0>
<Figure size 800x800 with 2 Axes>

Plot 2: Ice-Shelf Basal Melt Rate

The basal-melt workflow is available as a raster layer in the combined cube. This plot shows the melt-rate field for the same ROI. The original shelf-name polygon time series is not part of these raster cubes, but the gridded melt-rate field is ready for spatial overlay and small-area statistics.

basal_melt = small["ice_shelf_basal_melt_rate"]

with ProgressBar():
    basal_melt = basal_melt.compute()

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": antarctic_crs})
basal_melt.plot(ax=ax, cmap="RdBu_r", robust=True, transform=antarctic_crs)
ax.coastlines()

[########################################] | 100% Completed | 1.04 sms
<cartopy.mpl.feature_artist.FeatureArtist at 0x33bbae210>
<Figure size 800x800 with 2 Axes>

Plot 3: Surface Elevation Change

The SEC cube stores gridded surface-elevation-change rates by time period. This example takes the latest period, masks to grounded ice, converts metres to millimetres, and plots the spatial fingerprint of elevation gain or loss.

sec_rate = small["surface_elevation_change_rate"].isel(time_period=-1) * 1000
sec_grounded = small["surface_elevation_change_surface_type"] == 2
sec_rate = sec_rate.where(sec_grounded)

with ProgressBar():
    sec_rate = sec_rate.compute()

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": antarctic_crs})
sec_rate.plot(ax=ax, cmap="RdBu_r", vmin=-500, vmax=500, transform=antarctic_crs)
ax.coastlines()
[########################################] | 100% Completed | 1.66 sms
<cartopy.mpl.feature_artist.FeatureArtist at 0x30c90e710>
<Figure size 800x800 with 2 Axes>

Plot 4: Englacial Temperature At A Selected Depth

The old temperature notebook reprojected a non-regular source product. In the remote cube, ice temperature is already on the shared EPSG:3031 grid. This plot selects the depth nearest TEMPERATURE_DEPTH_TARGET_M.

ice_temp_c = small["englacial_temp_profile_tice"].isel(depth=0)

with ProgressBar():
    ice_temp_c = ice_temp_c.compute()

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": antarctic_crs})
ice_temp_c.plot(ax=ax, cmap="coolwarm", robust=True, transform=antarctic_crs)
ax.coastlines()
[########################################] | 100% Completed | 524.44 ms
/opt/miniconda3/envs/pangeo/lib/python3.13/site-packages/numpy/lib/_function_base_impl.py:107: RuntimeWarning: overflow encountered in cast
  get_virtual_index=lambda n, quantiles: (n - 1) * quantiles,
/opt/miniconda3/envs/pangeo/lib/python3.13/site-packages/numpy/lib/_function_base_impl.py:4750: RuntimeWarning: overflow encountered in cast
  indexes_above_bounds = virtual_indexes >= valid_values_count - 1
/opt/miniconda3/envs/pangeo/lib/python3.13/site-packages/numpy/lib/_function_base_impl.py:4655: RuntimeWarning: invalid value encountered in multiply
  lerp_interpolation = asanyarray(add(a, diff_b_a * t, out=out))
/opt/miniconda3/envs/pangeo/lib/python3.13/site-packages/numpy/lib/_function_base_impl.py:4656: RuntimeWarning: invalid value encountered in scalar multiply
  subtract(b, diff_b_a * (1 - t), out=lerp_interpolation, where=t >= 0.5,
<cartopy.mpl.feature_artist.FeatureArtist at 0x33bbdb390>
<Figure size 800x800 with 2 Axes>

Plot 5: Calving-Front Mask Change

The calving-front product is transformed from time-evolving vector polygons into a raster mask stack. This plot compares the first and last available masks in the ROI, highlighting where the binary mask changed through time.

calving = small["calving_fronts"]

with ProgressBar():
    calving_time = calving.notnull().any(("x", "y")).compute()

calving = calving.isel(time=calving_time.values)
calving_change = calving.isel(time=-1) - calving.isel(time=0)

with ProgressBar():
    calving_change = calving_change.compute()

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": antarctic_crs})
calving_change.plot(ax=ax, cmap="RdBu_r", robust=True, transform=antarctic_crs)
ax.coastlines()
[########################################] | 100% Completed | 30.58 s
[########################################] | 100% Completed | 2.41 ss
<cartopy.mpl.feature_artist.FeatureArtist at 0x38e5107d0>
<Figure size 800x800 with 2 Axes>

Plot 6: Grounding Lines And Lake Masks

The groundline, subglacial-lake, and supraglacial-lake workflows are available as raster masks in the combined cube. This plot combines them into one class-code image for a compact overview: 1 marks supraglacial lakes/channels, 2 marks subglacial lakes, and 4 marks grounding-line pixels. Sums indicate overlap.

mask_code = (
    (small["supra_glacial_lakes_mask"] > 0).astype("uint8")
    + 2 * (small["subglacial_lakes_mask"] > 0).astype("uint8")
    + 4 * (small["groundlines_mask"] > 0).astype("uint8")
)

with ProgressBar():
    mask_code = mask_code.compute()

mask_cmap = ListedColormap(["white", "#4f9cf9", "#8e44ad", "#1f9d55", "#111111", "#f59e0b", "#d946ef", "#ef4444"])
mask_norm = BoundaryNorm(np.arange(-0.5, 8.5), mask_cmap.N)

# mask_code.plot(cmap=mask_cmap, norm=mask_norm, cbar_kwargs={"ticks": range(8)})

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": antarctic_crs})
mask_code.plot(ax=ax, cmap=mask_cmap, norm=mask_norm, cbar_kwargs={"ticks": range(8)}, transform=antarctic_crs)
ax.coastlines()
[########################################] | 100% Completed | 530.53 ms
<cartopy.mpl.feature_artist.FeatureArtist at 0x34bfa4e10>
<Figure size 800x800 with 2 Axes>

Plot 7: Ice Velocity Vectors Over Basal Melt

The velocity cube provides easting, northing, and magnitude variables on the same remote grid. This section samples the latest time step coarsely for arrows and plots those vectors over the basal-melt raster computed earlier.

stride = VELOCITY_VECTOR_STRIDE

background = small["ice_shelf_basal_melt_rate"]
u = small["ice_sheet_surface_velocity_easting"].isel(time=-1, y=slice(None, None, stride), x=slice(None, None, stride))
v = small["ice_sheet_surface_velocity_northing"].isel(time=-1, y=slice(None, None, stride), x=slice(None, None, stride))
with ProgressBar():
    background = background.compute()
    u = u.compute()
    v = v.compute()

X, Y = np.meshgrid(u["x"].values, u["y"].values)

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={"projection": antarctic_crs})

background.plot(ax=ax, cmap="RdBu_r", robust=True, transform=antarctic_crs)
ax.quiver(X, Y, u.values, v.values, width=0.0025, transform=antarctic_crs)
ax.coastlines()

plt.show()
[########################################] | 100% Completed | 1.47 sms
[########################################] | 100% Completed | 14.39 ss
[########################################] | 100% Completed | 11.77 ss
<Figure size 800x800 with 2 Axes>