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();