Vantor Stereo Data#

This notebooks illustrates basic search and visualization of Vantor Stereo Imagery.

Coincident uses the Vantor ‘discovery’ STAC API to find high resolution stereo data.

import coincident
import geopandas as gpd
import matplotlib.pyplot as plt

%matplotlib inline
/home/docs/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/io/download.py:25: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)
  from tqdm.autonotebook import tqdm

Load browse image#

Vantor provides monochrome uint8 or RGB browse images at ~30m resolution that we can visualize before ordering the full-resolution scenes.

# We first convert from geopandas metadata to a pystac items
items = coincident.search.to_pystac_items(gf_stereo)
da = coincident.io.xarray.open_vantor_browse(items[0]).squeeze()
da
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/file_manager.py:219, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    218 try:
--> 219     file = self._cache[self._key]
    220 except KeyError:

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/lru_cache.py:56, in LRUCache.__getitem__(self, key)
     55 with self._lock:
---> 56     value = self._cache[key]
     57     self._cache.move_to_end(key)

KeyError: [<function open at 0x7f8457e483b0>, ('https://api.maxar.com/discovery/v1/catalogs/imagery/collections/wv01/items/10200100A7501F00/assets/collections/dg-archive/assets/browse/10200100A7501F00.browse.tif',), 'r', (('overview_level', 0), ('sharing', False)), '32195ac6-02fe-4091-97ab-5730290add1a']

During handling of the above exception, another exception occurred:

CPLE_HttpResponseError                    Traceback (most recent call last)
File rasterio/_base.pyx:320, in rasterio._base.DatasetBase.__init__()
--> 320 'Could not get source, probably due dynamically evaluated source code.'

File rasterio/_base.pyx:232, in rasterio._base.open_dataset()
--> 232 'Could not get source, probably due dynamically evaluated source code.'

File rasterio/_err.pyx:359, in rasterio._err.exc_wrap_pointer()
--> 359 'Could not get source, probably due dynamically evaluated source code.'

CPLE_HttpResponseError: HTTP response code: 401

During handling of the above exception, another exception occurred:

RasterioIOError                           Traceback (most recent call last)
Cell In[6], line 1
----> 1 da = coincident.io.xarray.open_vantor_browse(items[0]).squeeze()
      2 da

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/io/xarray.py:180, in open_vantor_browse(item, overview_level)
    175 env = rasterio.Env(
    176     GDAL_DISABLE_READDIR_ON_OPEN="EMPTY_DIR",
    177     GDAL_HTTP_HEADERS=f"VANTOR-API-KEY:{os.environ['VANTOR_API_KEY']}",
    178 )
    179 with env:
--> 180     return xr.open_dataarray(
    181         href,
    182         engine="rasterio",
    183         mask_and_scale=False,  # otherwise uint8 -> float32!
    184         backend_kwargs={"open_kwargs": {"overview_level": overview_level}},
    185     )

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/api.py:814, in open_dataarray(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    631 def open_dataarray(
    632     filename_or_obj: T_PathFileOrDataStore,
    633     *,
   (...)    652     **kwargs,
    653 ) -> DataArray:
    654     """Open a DataArray from a file or file-like object containing a single
    655     data variable.
    656 
   (...)    811     open_dataset
    812     """
--> 814     dataset = open_dataset(
    815         filename_or_obj,
    816         decode_cf=decode_cf,
    817         mask_and_scale=mask_and_scale,
    818         decode_times=decode_times,
    819         concat_characters=concat_characters,
    820         decode_coords=decode_coords,
    821         engine=engine,
    822         chunks=chunks,
    823         cache=cache,
    824         drop_variables=drop_variables,
    825         create_default_indexes=create_default_indexes,
    826         inline_array=inline_array,
    827         chunked_array_type=chunked_array_type,
    828         from_array_kwargs=from_array_kwargs,
    829         backend_kwargs=backend_kwargs,
    830         use_cftime=use_cftime,
    831         decode_timedelta=decode_timedelta,
    832         **kwargs,
    833     )
    835     if len(dataset.data_vars) != 1:
    836         if len(dataset.data_vars) == 0:

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/api.py:607, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    595 decoders = _resolve_decoders_kwargs(
    596     decode_cf,
    597     open_backend_dataset_parameters=backend.open_dataset_parameters,
   (...)    603     decode_coords=decode_coords,
    604 )
    606 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 607 backend_ds = backend.open_dataset(
    608     filename_or_obj,
    609     drop_variables=drop_variables,
    610     **decoders,
    611     **kwargs,
    612 )
    613 ds = _dataset_from_backend_dataset(
    614     backend_ds,
    615     filename_or_obj,
   (...)    626     **kwargs,
    627 )
    628 return ds

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rioxarray/xarray_plugin.py:59, in RasterioBackend.open_dataset(self, filename_or_obj, drop_variables, parse_coordinates, lock, masked, mask_and_scale, variable, group, default_name, decode_coords, decode_times, decode_timedelta, band_as_variable, open_kwargs)
     57 if open_kwargs is None:
     58     open_kwargs = {}
---> 59 rds = _io.open_rasterio(
     60     filename_or_obj,
     61     parse_coordinates=parse_coordinates,
     62     cache=False,
     63     lock=lock,
     64     masked=masked,
     65     mask_and_scale=mask_and_scale,
     66     variable=variable,
     67     group=group,
     68     default_name=default_name,
     69     decode_times=decode_times,
     70     decode_timedelta=decode_timedelta,
     71     band_as_variable=band_as_variable,
     72     **open_kwargs,
     73 )
     74 if isinstance(rds, xarray.DataArray):
     75     dataset = rds.to_dataset()

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rioxarray/_io.py:1140, in open_rasterio(filename, parse_coordinates, chunks, cache, lock, masked, mask_and_scale, variable, group, default_name, decode_times, decode_timedelta, band_as_variable, **open_kwargs)
   1138     else:
   1139         manager = URIManager(file_opener, filename, mode="r", kwargs=open_kwargs)
-> 1140     riods = manager.acquire()
   1141     captured_warnings = rio_warnings.copy()
   1143 # raise the NotGeoreferencedWarning if applicable

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/file_manager.py:201, in CachingFileManager.acquire(self, needs_lock)
    186 def acquire(self, needs_lock: bool = True) -> T_File:
    187     """Acquire a file object from the manager.
    188 
    189     A new file is only opened if it has expired from the
   (...)    199         An open file object, as returned by ``opener(*args, **kwargs)``.
    200     """
--> 201     file, _ = self._acquire_with_cache_info(needs_lock)
    202     return file

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/file_manager.py:225, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    223     kwargs = kwargs.copy()
    224     kwargs["mode"] = self._mode
--> 225 file = self._opener(*self._args, **kwargs)
    226 if self._mode == "w":
    227     # ensure file doesn't get overridden when opened again
    228     self._mode = "a"

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rasterio/env.py:464, in ensure_env_with_credentials.<locals>.wrapper(*args, **kwds)
    461     session = DummySession()
    463 with env_ctor(session=session):
--> 464     return f(*args, **kwds)

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rasterio/__init__.py:367, in open(fp, mode, driver, width, height, count, crs, transform, dtype, nodata, sharing, thread_safe, opener, **kwargs)
    364     path = _parse_path(raw_dataset_path)
    366 if mode == "r":
--> 367     dataset = DatasetReader(path, driver=driver, sharing=sharing, thread_safe=thread_safe, **kwargs)
    368 elif mode == "r+":
    369     dataset = get_writer_for_path(path, driver=driver)(
    370         path, mode, driver=driver, sharing=sharing, **kwargs
    371     )

File rasterio/_base.pyx:329, in rasterio._base.DatasetBase.__init__()
--> 329 'Could not get source, probably due dynamically evaluated source code.'

RasterioIOError: HTTP response code: 401

Plot browse image#

For convenience you can create simplified figures directly without first loading the data with Xarray. Browse images are cloud-optimized-geotiffs with overviews for efficient previewing.

coincident.plot.plot_vantor_browse(items[0], overview_level=2);
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/file_manager.py:219, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    218 try:
--> 219     file = self._cache[self._key]
    220 except KeyError:

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/lru_cache.py:56, in LRUCache.__getitem__(self, key)
     55 with self._lock:
---> 56     value = self._cache[key]
     57     self._cache.move_to_end(key)

KeyError: [<function open at 0x7f8457e483b0>, ('https://api.maxar.com/discovery/v1/catalogs/imagery/collections/wv01/items/10200100A7501F00/assets/collections/dg-archive/assets/browse/10200100A7501F00.browse.tif',), 'r', (('overview_level', 2), ('sharing', False)), '1bb4b5bb-fb7f-4d3f-a9d1-00f718675dc8']

During handling of the above exception, another exception occurred:

CPLE_HttpResponseError                    Traceback (most recent call last)
File rasterio/_base.pyx:320, in rasterio._base.DatasetBase.__init__()
--> 320 'Could not get source, probably due dynamically evaluated source code.'

File rasterio/_base.pyx:232, in rasterio._base.open_dataset()
--> 232 'Could not get source, probably due dynamically evaluated source code.'

File rasterio/_err.pyx:359, in rasterio._err.exc_wrap_pointer()
--> 359 'Could not get source, probably due dynamically evaluated source code.'

CPLE_HttpResponseError: HTTP response code: 401

During handling of the above exception, another exception occurred:

RasterioIOError                           Traceback (most recent call last)
Cell In[7], line 1
----> 1 coincident.plot.plot_vantor_browse(items[0], overview_level=2);

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/plot/matplotlib.py:139, in plot_vantor_browse(item, ax, overview_level)
    122 def plot_vantor_browse(
    123     item: pystac.Item, ax: plt.Axes | None = None, overview_level: int = 0
    124 ) -> plt.Axes:
    125     """
    126     Map view of Vantor browse image from a STAC item using Matplotlib.
    127 
   (...)    137         The Matplotlib Axes object with the plot.
    138     """
--> 139     da = open_vantor_browse(item, overview_level=overview_level)
    141     if ax is None:
    142         _, ax = plt.subplots(figsize=(8, 11))

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/io/xarray.py:180, in open_vantor_browse(item, overview_level)
    175 env = rasterio.Env(
    176     GDAL_DISABLE_READDIR_ON_OPEN="EMPTY_DIR",
    177     GDAL_HTTP_HEADERS=f"VANTOR-API-KEY:{os.environ['VANTOR_API_KEY']}",
    178 )
    179 with env:
--> 180     return xr.open_dataarray(
    181         href,
    182         engine="rasterio",
    183         mask_and_scale=False,  # otherwise uint8 -> float32!
    184         backend_kwargs={"open_kwargs": {"overview_level": overview_level}},
    185     )

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/api.py:814, in open_dataarray(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    631 def open_dataarray(
    632     filename_or_obj: T_PathFileOrDataStore,
    633     *,
   (...)    652     **kwargs,
    653 ) -> DataArray:
    654     """Open a DataArray from a file or file-like object containing a single
    655     data variable.
    656 
   (...)    811     open_dataset
    812     """
--> 814     dataset = open_dataset(
    815         filename_or_obj,
    816         decode_cf=decode_cf,
    817         mask_and_scale=mask_and_scale,
    818         decode_times=decode_times,
    819         concat_characters=concat_characters,
    820         decode_coords=decode_coords,
    821         engine=engine,
    822         chunks=chunks,
    823         cache=cache,
    824         drop_variables=drop_variables,
    825         create_default_indexes=create_default_indexes,
    826         inline_array=inline_array,
    827         chunked_array_type=chunked_array_type,
    828         from_array_kwargs=from_array_kwargs,
    829         backend_kwargs=backend_kwargs,
    830         use_cftime=use_cftime,
    831         decode_timedelta=decode_timedelta,
    832         **kwargs,
    833     )
    835     if len(dataset.data_vars) != 1:
    836         if len(dataset.data_vars) == 0:

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/api.py:607, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    595 decoders = _resolve_decoders_kwargs(
    596     decode_cf,
    597     open_backend_dataset_parameters=backend.open_dataset_parameters,
   (...)    603     decode_coords=decode_coords,
    604 )
    606 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 607 backend_ds = backend.open_dataset(
    608     filename_or_obj,
    609     drop_variables=drop_variables,
    610     **decoders,
    611     **kwargs,
    612 )
    613 ds = _dataset_from_backend_dataset(
    614     backend_ds,
    615     filename_or_obj,
   (...)    626     **kwargs,
    627 )
    628 return ds

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rioxarray/xarray_plugin.py:59, in RasterioBackend.open_dataset(self, filename_or_obj, drop_variables, parse_coordinates, lock, masked, mask_and_scale, variable, group, default_name, decode_coords, decode_times, decode_timedelta, band_as_variable, open_kwargs)
     57 if open_kwargs is None:
     58     open_kwargs = {}
---> 59 rds = _io.open_rasterio(
     60     filename_or_obj,
     61     parse_coordinates=parse_coordinates,
     62     cache=False,
     63     lock=lock,
     64     masked=masked,
     65     mask_and_scale=mask_and_scale,
     66     variable=variable,
     67     group=group,
     68     default_name=default_name,
     69     decode_times=decode_times,
     70     decode_timedelta=decode_timedelta,
     71     band_as_variable=band_as_variable,
     72     **open_kwargs,
     73 )
     74 if isinstance(rds, xarray.DataArray):
     75     dataset = rds.to_dataset()

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rioxarray/_io.py:1140, in open_rasterio(filename, parse_coordinates, chunks, cache, lock, masked, mask_and_scale, variable, group, default_name, decode_times, decode_timedelta, band_as_variable, **open_kwargs)
   1138     else:
   1139         manager = URIManager(file_opener, filename, mode="r", kwargs=open_kwargs)
-> 1140     riods = manager.acquire()
   1141     captured_warnings = rio_warnings.copy()
   1143 # raise the NotGeoreferencedWarning if applicable

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/file_manager.py:201, in CachingFileManager.acquire(self, needs_lock)
    186 def acquire(self, needs_lock: bool = True) -> T_File:
    187     """Acquire a file object from the manager.
    188 
    189     A new file is only opened if it has expired from the
   (...)    199         An open file object, as returned by ``opener(*args, **kwargs)``.
    200     """
--> 201     file, _ = self._acquire_with_cache_info(needs_lock)
    202     return file

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/xarray/backends/file_manager.py:225, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    223     kwargs = kwargs.copy()
    224     kwargs["mode"] = self._mode
--> 225 file = self._opener(*self._args, **kwargs)
    226 if self._mode == "w":
    227     # ensure file doesn't get overridden when opened again
    228     self._mode = "a"

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rasterio/env.py:464, in ensure_env_with_credentials.<locals>.wrapper(*args, **kwds)
    461     session = DummySession()
    463 with env_ctor(session=session):
--> 464     return f(*args, **kwds)

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/.pixi/envs/docs/lib/python3.14/site-packages/rasterio/__init__.py:367, in open(fp, mode, driver, width, height, count, crs, transform, dtype, nodata, sharing, thread_safe, opener, **kwargs)
    364     path = _parse_path(raw_dataset_path)
    366 if mode == "r":
--> 367     dataset = DatasetReader(path, driver=driver, sharing=sharing, thread_safe=thread_safe, **kwargs)
    368 elif mode == "r+":
    369     dataset = get_writer_for_path(path, driver=driver)(
    370         path, mode, driver=driver, sharing=sharing, **kwargs
    371     )

File rasterio/_base.pyx:329, in rasterio._base.DatasetBase.__init__()
--> 329 'Could not get source, probably due dynamically evaluated source code.'

RasterioIOError: HTTP response code: 401

Load cloud-cover polygon#

For each acquisition, Vantor includes a Multipolygon that estimates cloud cover. Behind the scenes, coincident uses the stac-asset library to read and download assets of any STAC item. Because stac-asset uses asynchronous functions, you have to use await to return results.

bytes = await coincident.io.download.read_href(item, "cloud-cover")
gf_clouds = gpd.read_file(bytes)
gf_clouds.explore()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[12], line 1
----> 1 bytes = await coincident.io.download.read_href(item, "cloud-cover")
      2 gf_clouds = gpd.read_file(bytes)
      3 gf_clouds.explore()

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/io/download.py:100, in read_href(item, asset, config)
     72 """
     73 Open and read a STAC asset href into local memory
     74 
   (...)     96         gf_clouds = gpd.read_file(bytes)
     97 """
     98 href = item.assets.get(asset).href
--> 100 config = _set_config(item, config)
    102 return await stac_asset.read_href(href, config=config)

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/io/download.py:62, in _set_config(item, config)
     57     config = stac_asset.Config(
     58         earthdata_token=os.environ.get("EARTHDATA_TOKEN"),
     59         exclude=exclude_keys,
     60     )
     61 else:
---> 62     config = stac_asset.Config(**config)
     64 return config

TypeError: stac_asset.config.Config() argument after ** must be a mapping, not NoneType

Download browse image + metadata#

coincident wraps the stac-asset library to easily download local copies of a STAC item and all of its assets (e.g. metadata and cloud cover mask)

# Set download directory to match item STAC ID
download_dir = f"/tmp/{item.id}"
local_item = await coincident.io.download.download_item(item, download_dir)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 3
      1 # Set download directory to match item STAC ID
      2 download_dir = f"/tmp/{item.id}"
----> 3 local_item = await coincident.io.download.download_item(item, download_dir)

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/io/download.py:138, in download_item(item, path, config)
    110 """
    111 Downloads a STAC item to a specified local path.
    112 
   (...)    134         localitem = asyncio.run(download_item(item))
    135 """
    136 posixpath = Path(path)
--> 138 config = _set_config(item, config)
    140 # NOTE: prevent in-place modification of remote hrefs with item.clone()
    141 return await stac_asset.download_item(item.clone(), posixpath, config=config)

File ~/checkouts/readthedocs.org/user_builds/coincident/checkouts/stable/src/coincident/io/download.py:62, in _set_config(item, config)
     57     config = stac_asset.Config(
     58         earthdata_token=os.environ.get("EARTHDATA_TOKEN"),
     59         exclude=exclude_keys,
     60     )
     61 else:
---> 62     config = stac_asset.Config(**config)
     64 return config

TypeError: stac_asset.config.Config() argument after ** must be a mapping, not NoneType
!ls {download_dir}
ls: cannot access '/tmp/104001006724D500': No such file or directory