EarthCARE Validation during ORCESTRA

EarthCARE Validation during ORCESTRA#

EarthCARE regularly flew over the measurement area, allowing each of the aircraft to fly underneath. The data from the underflights are used to validate the EarthCARE measurements. This page shows how to compare EarthCARE and HALO tracks.

Note

In this example, we use the EarthCARE orbit predictions as used for flight planning during ORCESTRA. With EarthCARE data being public, other ways to obtain the actual (past) orbits should be available at some point.

First, we load the track data. EarthCARE tracks are available for different regions of interest (roi) and forecast days. You can check sattracks.orcestra-campaign.org for an overview of all available forecasts.

from datetime import date

import cartopy.crs as ccrs
import cartopy.feature as cf
import matplotlib.pyplot as plt
import xarray as xr
from orcestra import bco, sat


# Get EartchCare track
date = date(2024, 8, 25)
ec_track = sat.SattrackLoader(
    "EARTHCARE",
    forecast_day=date,
    kind="PRE",
    roi="CAPE_VERDE",
).get_track_for_day(date)

EarthCare tracks include the ascending and descending orbits. For the plot, we select only the afternoon overpass that coincided with our flight.

ec_track = ec_track.sel(time=slice(f"{date} 12:00", f"{date} 23:59"))

Next, we will select the HALO position/attidue data for the corresponding flight day. For plotting reasons, the coarsen the 100Hz data into 1min-averages.

# root = "ipns://latest.orcestra-campaign.org"
root = "ipfs://Qmb1wKeNLkqichCfPwsjk8f2vBHU1dxkgLRVmHwk7rdvi2"
halo_track = xr.open_dataset(f"{root}/products/HALO/position_attitude.zarr", engine="zarr")
halo_track = halo_track.sel(time=str(date)).coarsen(time=6000, boundary="pad").mean("time")  # 1min-average
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[3], line 4
      1 # root = "ipns://latest.orcestra-campaign.org"
      2 root = "ipfs://Qmb1wKeNLkqichCfPwsjk8f2vBHU1dxkgLRVmHwk7rdvi2"
      3 halo_track = xr.open_dataset(f"{root}/products/HALO/position_attitude.zarr", engine="zarr")
----> 4 halo_track = halo_track.sel(time=str(date)).coarsen(time=6000, boundary="pad").mean("time")  # 1min-average

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/computation/rolling.py:1339, in DatasetCoarsen._reduce_method.<locals>.wrapped_func(self, keep_attrs, **kwargs)
   1337 reduced = {}
   1338 for key, da in self.obj.data_vars.items():
-> 1339     reduced[key] = da.variable.coarsen(
   1340         self.windows,
   1341         func,
   1342         self.boundary,
   1343         self.side,
   1344         keep_attrs=keep_attrs,
   1345         **kwargs,
   1346     )
   1348 coords = {}
   1349 for c, v in self.obj.coords.items():
   1350     # variable.coarsen returns variables not containing the window dims
   1351     # unchanged (maybe removes attrs)

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/variable.py:2268, in Variable.coarsen(self, windows, func, boundary, side, keep_attrs, **kwargs)
   2265 if not windows:
   2266     return self._replace(attrs=_attrs)
-> 2268 reshaped, axes = self.coarsen_reshape(windows, boundary, side)
   2269 if isinstance(func, str):
   2270     name = func

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/variable.py:2328, in Variable.coarsen_reshape(self, windows, boundary, side)
   2323         raise TypeError(
   2324             f"{boundary[d]} is invalid for boundary. Valid option is 'exact', "
   2325             "'trim' and 'pad'"
   2326         )
   2327 if pad_widths:
-> 2328     variable = variable.pad(pad_widths, mode="constant")
   2330 shape = []
   2331 axes = []

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/variable.py:1337, in Variable.pad(self, pad_width, mode, stat_length, constant_values, end_values, reflect_type, keep_attrs, **pad_width_kwargs)
   1333 if reflect_type is not None:
   1334     pad_option_kwargs["reflect_type"] = reflect_type
   1336 array = duck_array_ops.pad(
-> 1337     duck_array_ops.astype(self.data, dtype, copy=False),
   1338     pad_width_by_index,
   1339     mode=mode,
   1340     **pad_option_kwargs,
   1341 )
   1343 if keep_attrs is None:
   1344     keep_attrs = _get_keep_attrs(default=True)

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/variable.py:455, in Variable.data(self)
    453     duck_array = self._data.array
    454 elif isinstance(self._data, indexing.ExplicitlyIndexed):
--> 455     duck_array = self._data.get_duck_array()
    456 elif is_duck_array(self._data):
    457     duck_array = self._data

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/indexing.py:976, in MemoryCachedArray.get_duck_array(self)
    975 def get_duck_array(self):
--> 976     duck_array = self.array.get_duck_array()
    977     # ensure the array object is cached in-memory
    978     self.array = as_indexable(duck_array)

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/indexing.py:930, in CopyOnWriteArray.get_duck_array(self)
    929 def get_duck_array(self):
--> 930     return self.array.get_duck_array()

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/coding/common.py:80, in _ElementwiseFunctionArray.get_duck_array(self)
     79 def get_duck_array(self):
---> 80     return self.func(self.array.get_duck_array())

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/indexing.py:770, in LazilyIndexedArray.get_duck_array(self)
    767 from xarray.backends.common import BackendArray
    769 if isinstance(self.array, BackendArray):
--> 770     array = self.array[self.key]
    771 else:
    772     array = apply_indexer(self.array, self.key)

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/backends/zarr.py:316, in ZarrArrayWrapper.__getitem__(self, key)
    314 elif isinstance(key, indexing.OuterIndexer):
    315     method = self._oindex
--> 316 return indexing.explicit_indexing_adapter(
    317     key, array.shape, indexing.IndexingSupport.VECTORIZED, method
    318 )

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/core/indexing.py:1162, in explicit_indexing_adapter(key, shape, indexing_support, raw_indexing_method)
   1140 """Support explicit indexing by delegating to a raw indexing method.
   1141 
   1142 Outer and/or vectorized indexers are supported by indexing a second time
   (...)   1159 Indexing result, in the form of a duck numpy-array.
   1160 """
   1161 raw_key, numpy_indices = decompose_indexer(key, shape, indexing_support)
-> 1162 result = raw_indexing_method(raw_key.tuple)
   1163 if numpy_indices.tuple:
   1164     # index the loaded duck array
   1165     indexable = as_indexable(result)

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/xarray/backends/zarr.py:279, in ZarrArrayWrapper._getitem(self, key)
    278 def _getitem(self, key):
--> 279     return self._array[key]

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/zarr/core/array.py:2639, in Array.__getitem__(self, selection)
   2637     return self.vindex[cast("CoordinateSelection | MaskSelection", selection)]
   2638 elif is_pure_orthogonal_indexing(pure_selection, self.ndim):
-> 2639     return self.get_orthogonal_selection(pure_selection, fields=fields)
   2640 else:
   2641     return self.get_basic_selection(cast("BasicSelection", pure_selection), fields=fields)

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/zarr/core/array.py:3116, in Array.get_orthogonal_selection(self, selection, out, fields, prototype)
   3114     prototype = default_buffer_prototype()
   3115 indexer = OrthogonalIndexer(selection, self.shape, self._chunk_grid)
-> 3116 return sync(
   3117     self.async_array._get_selection(
   3118         indexer=indexer, out=out, fields=fields, prototype=prototype
   3119     )
   3120 )

File ~/miniconda3/envs/orcestra_book/lib/python3.12/site-packages/zarr/core/sync.py:147, in sync(coro, loop, timeout)
    143     pass
    145 future = asyncio.run_coroutine_threadsafe(_runner(coro), loop)
--> 147 finished, unfinished = wait([future], return_when=asyncio.ALL_COMPLETED, timeout=timeout)
    148 if len(unfinished) > 0:
    149     raise TimeoutError(f"Coroutine {coro} failed to finish within {timeout} s")

File ~/miniconda3/envs/orcestra_book/lib/python3.12/concurrent/futures/_base.py:305, in wait(fs, timeout, return_when)
    301         return DoneAndNotDoneFutures(done, not_done)
    303     waiter = _create_and_install_waiters(fs, return_when)
--> 305 waiter.event.wait(timeout)
    306 for f in fs:
    307     with f._condition:

File ~/miniconda3/envs/orcestra_book/lib/python3.12/threading.py:634, in Event.wait(self, timeout)
    632 signaled = self._flag
    633 if not signaled:
--> 634     signaled = self._cond.wait(timeout)
    635 return signaled

File ~/miniconda3/envs/orcestra_book/lib/python3.12/threading.py:334, in Condition.wait(self, timeout)
    332 try:    # restore state no matter what (e.g., KeyboardInterrupt)
    333     if timeout is None:
--> 334         waiter.acquire()
    335         gotit = True
    336     else:

KeyboardInterrupt: 

Now we can plot the tracks:

fig, ax = plt.subplots(figsize=(10, 8), subplot_kw={"projection": ccrs.PlateCarree()})
ax.set_extent([-50, -10, -5, 25])
ax.add_feature(cf.COASTLINE)
ax.add_feature(cf.LAND)
ax.add_feature(cf.OCEAN)
ax.plot(halo_track.lon, halo_track.lat, label="HALO", lw=3, c="tab:orange", transform=ccrs.Geodetic())
ax.plot(ec_track.lon, ec_track.lat, label="EarthCare", lw=1.5, c="tab:green", transform=ccrs.Geodetic())
ax.legend();