Skip to article frontmatterSkip to article content

HDA Tutorial - Queryables

This notebook demonstrates how to use the queryables API to filter C3S and DestinE digital twin collections by leveraging variable terms that dynamically adjust based on user selections.

πŸš€ Launch in JupyterHub

How to use the queryables APIΒΆ

The queryables API returns a list of variable terms that can be used for filtering the specified collection.

The queryables API presents the appropriate filters for the selected dataset, determined by the chosen values. If the user selects a certain variable, the choice is narrowed down for other variables. This notebook demonstrates how to filter data in a specific collection using the list of variable terms returned by the queryables API.

The notebook is dedicated to collections from C3S and DestinE digital twins. C3S and DestinE digital twins datasets offer a wide range of possibilities in terms of information and constraints and the queryables API is a valuable tool for exploring these datasets.

Throughout this notebook, you will learn:

  1. Authenticate: How to authenticate for searching and access DEDL collections.

  2. Queryables: How to exploit the STAC API filter extension features. The β€œqueryables” API helps users to determine the property names and types available for filtering data.

  3. Search data: How to search DEDL data using filters obtained by the β€œqueryables” API.

  4. Download data: How to download DEDL data through HDA.

The detailed HDA API and definition of each endpoint and parameters is available in the HDA Swagger UI at: STAC API - Filter Extension

Prequisites:
- For queryables API: none
- For filtering data inside collections : DestinE user account

AuthenticateΒΆ

Define some constants for the API URLsΒΆ

In this section, we define the relevant constants, holding the URL strings for the different endpoints.

# Collection https://hda.data.destination-earth.eu/ui/dataset/EO.ECMWF.DAT.CAMS_EUROPE_AIR_QUALITY_FORECASTS as default
COLLECTION_ID = "EO.ECMWF.DAT.CAMS_EUROPE_AIR_QUALITY_FORECASTS"

# Core API
HDA_API_URL = "https://hda.data.destination-earth.eu"

# STAC API
## Core
STAC_API_URL = f"{HDA_API_URL}/stac"

## Item Search
SEARCH_URL = f"{STAC_API_URL}/search"

##Β Collections
COLLECTIONS_URL = f"{STAC_API_URL}/collections"

##Β Queryables
QUERYABLES_URL = f"{STAC_API_URL}/queryables"
QUERYABLES_BY_COLLECTION_ID = f"{COLLECTIONS_URL}/{COLLECTION_ID}/queryables"
HDA_FILTERS =''

## HTTP Success
HTTP_SUCCESS_CODE = 200

Import the relevant modules and define some functionsΒΆ

We start off by importing the relevant modules for DestnE authentication, HTTP requests, json handling, widgets and some utility.

import destinelab as deauth
pip install --quiet --upgrade DEDLUtils
Note: you may need to restart the kernel to use updated packages.
from DEDLUtils import dedl_utilities
#import dedl_utilities
import requests
import json
from getpass import getpass

import ipywidgets as widgets
from IPython.display import display, clear_output, HTML
from ipywidgets import Layout, Box
import datetime

from rich.console import Console
import rich.table

from IPython.display import JSON

Obtain Authentication TokenΒΆ

To perform a query on HDA we need to be authenticated.

DESP_USERNAME = input("Please input your DESP username or email: ")
DESP_PASSWORD = getpass("Please input your DESP password: ")

auth = deauth.AuthHandler(DESP_USERNAME, DESP_PASSWORD)
access_token = auth.get_token()
if access_token is not None:
    print("DEDL/DESP Access Token Obtained Successfully")
else:
    print("Failed to Obtain DEDL/DESP Access Token")

auth_headers = {"Authorization": f"Bearer {access_token}"}
Please input your DESP username or email:  eum-dedl-user
Please input your DESP password:  Β·Β·Β·Β·Β·Β·Β·Β·
Response code: 200
DEDL/DESP Access Token Obtained Successfully

QueryablesΒΆ

The β€œqueryables” API helps users to determine the property names and types available for filtering data inside a specific collection.

Below a dropdown menu to choose the collection. We can choose the collection of which we want to inspect the filters.

prefix = "EO.ECMWF"

dedlUtils=dedl_utilities.DEDLUtilities(COLLECTION_ID )
dedlUtils.create_collections_dropdown(prefix)

# Layout
# Define the layout for the dropdown
dropdown_layout = Layout(display='space-between', justify_content='center', width='90%')
# Create a box to hold the dropdown with the specified layout
box = Box([dedlUtils.dropdown, dedlUtils.outputArea], layout=dropdown_layout)
display( box)  


Loading...

The QUERYABLES ENDPOINT (above) returns the applicable filters under the section named β€˜properties’.

The β€˜properties’ section contains

  • the name of the filter, title,

  • the filter type,

  • the possible filter values, enum (conditioned by the values selected for the other filters)

  • and the the default (or chosen) default applied

We can print the’properties’ section for the selected collection in the table below. The table shows the filters and the values applied by default when we perform a search for the chosen dataset without specifying any filter.

filters_resp=requests.get(dedlUtils.queryablesByCollectionId)
filters_resp.raise_for_status()

filters = filters_resp.json()["properties"]
dedlQueryablesUtils=dedl_utilities.DEDLQueryablesUtilities(dedlUtils.collectionId)
console = Console()
console.print(dedlQueryablesUtils.create_queryables_table(filters))
Loading...

Calling the queryables API specifying filters, that means using as parameters the values chosen for filtering the selected dataset, the API replies with the applicable filters, conditioned by the chosen values. Then if the user selects a certain value for a parameter then the choice is narrowed down for other variables.

The queryables API, in this way, helps user to build a correct search request for the given dataset.

Below an interactive example, to see that once you select a value for a property the choice is narrowed down for other variables.

# Event listeners
dedlQueryablesUtils=dedl_utilities.DEDLQueryablesUtilities(dedlUtils.collectionId)
dedlQueryablesUtils.update_queryables_dropdowns()
display(dedlQueryablesUtils.dropdownContainer, dedlQueryablesUtils.outputArea)
Loading...

Filtering a collection with the list returned by the queryable APIΒΆ

This section wil show how to use the list of variable terms returned by the queryables API for filtering a specific dataset.

If you choose a digital twins collection, check if the access is grantedΒΆ

If DT access is not granted, you will not be able to search and access DT data.

auth.is_DTaccess_allowed(access_token)
DT Output access allowed
True

Build the query from the selected valuesΒΆ

The parameters chosen in the previous steps can be used to build the corresponding HDA queries.

# The JSON objects containing the generic query parameters:
jsonGenericQuery = '{"collections": ["'+dedlUtils.collectionId+'"], "datetime": "2024-04-01T00:00:00Z/2024-04-19T00:00:00Z"}'
# Convert JSON strings to Python dictionaries
dictQuery = json.loads(jsonGenericQuery)

# Include the filters selected in the previous steps inside the JSON containing the generic query parameters:
dictQuery['query'] = dedlQueryablesUtils.hdaFilters
# Convert the merged dictionary back to a JSON string
queryJson = json.dumps(dictQuery, indent=4)

print(queryJson)
{
    "collections": [
        "EO.ECMWF.DAT.CAMS_EUROPE_AIR_QUALITY_REANALYSES"
    ],
    "datetime": "2024-04-01T00:00:00Z/2024-04-19T00:00:00Z",
    "query": {
        "ecmwf:variable": {
            "eq": "sulphur_dioxide"
        },
        "ecmwf:model": {
            "eq": "monarch"
        },
        "ecmwf:level": {
            "eq": "5000"
        },
        "ecmwf:type": {
            "eq": "interim_reanalysis"
        },
        "ecmwf:year": {
            "eq": "2023"
        },
        "ecmwf:month": {
            "eq": "03"
        }
    }
}
response = requests.post("https://hda.data.destination-earth.eu/stac/search", headers=auth_headers, json= json.loads(queryJson) )
#print(response)
# Requests to ADS data always return a single item containing all the requested data
product = response.json()["features"][0]
JSON(product, expanded= False)
Loading...

DownloadΒΆ

Once we have found the product we can download it:

download_url = product["assets"]["downloadLink"]["href"]
print(download_url )
https://hda.data.destination-earth.eu/stac/collections/EO.ECMWF.DAT.CAMS_EUROPE_AIR_QUALITY_REANALYSES/items/CAMS_EU_AIR_QUALITY_RE_20240401_20240418_5f267e4fcd5228c9714c086fa6f2592fa7bd57cc/download?provider=cop_ads&_dc_qs=%257B%2522data_format%2522%253A%2B%2522zip%2522%252C%2B%2522level%2522%253A%2B%25225000%2522%252C%2B%2522model%2522%253A%2B%2522monarch%2522%252C%2B%2522month%2522%253A%2B%252203%2522%252C%2B%2522type%2522%253A%2B%2522interim_reanalysis%2522%252C%2B%2522variable%2522%253A%2B%2522sulphur_dioxide%2522%252C%2B%2522year%2522%253A%2B%25222023%2522%257D
from tqdm import tqdm
import time
import re
direct_download_url=''

response = requests.get(download_url, headers=auth_headers)
if (response.status_code == HTTP_SUCCESS_CODE):
    direct_download_url = product['assets']['downloadLink']['href']
else:
    print(response.text)


# we poll as long as the data is not ready
if direct_download_url=='':
    while url := response.headers.get("Location"):
        print(f"order status: {response.json()['status']}")
        response = requests.get(url, headers=auth_headers, stream=True)
        response.raise_for_status()

filename = re.findall('filename=\"?(.+)\"?', response.headers["Content-Disposition"])[0]
total_size = int(response.headers.get("content-length", 0))

print(f"downloading {filename}")

with tqdm(total=total_size, unit="B", unit_scale=True) as progress_bar:
    with open(filename, 'wb') as f:
        for data in response.iter_content(1024):
            progress_bar.update(len(data))
            f.write(data)
{"description":"Product is not available yet, please try again using given updated location","status":"accepted","location":"https://hda.data.destination-earth.eu/stac/collections/EO.ECMWF.DAT.CAMS_EUROPE_AIR_QUALITY_REANALYSES/items/CAMS_EU_AIR_QUALITY_RE_20240401_20240418_5f267e4fcd5228c9714c086fa6f2592fa7bd57cc/download?_dc_qs=%257B%2522data_format%2522%253A%2B%2522zip%2522%252C%2B%2522level%2522%253A%2B%25225000%2522%252C%2B%2522model%2522%253A%2B%2522monarch%2522%252C%2B%2522month%2522%253A%2B%252203%2522%252C%2B%2522type%2522%253A%2B%2522interim_reanalysis%2522%252C%2B%2522variable%2522%253A%2B%2522sulphur_dioxide%2522%252C%2B%2522year%2522%253A%2B%25222023%2522%257D&processID=cams-europe-air-quality-reanalyses&type=process&jobID=7651cb16-a68a-4715-be53-6e146503dd71&status=accepted&created=2024-12-23T22%3A39%3A45.832302&updated=2024-12-23T22%3A39%3A45.832302&links=%7B%27href%27%3A+%27https%3A%2F%2Fads.atmosphere.copernicus.eu%2Fapi%2Fretrieve%2Fv1%2Fprocesses%2Fcams-europe-air-quality-reanalyses%2Fexecution%27%2C+%27rel%27%3A+%27self%27%7D&links=%7B%27href%27%3A+%27https%3A%2F%2Fads.atmosphere.copernicus.eu%2Fapi%2Fretrieve%2Fv1%2Fjobs%2F7651cb16-a68a-4715-be53-6e146503dd71%27%2C+%27rel%27%3A+%27monitor%27%2C+%27type%27%3A+%27application%2Fjson%27%2C+%27title%27%3A+%27job+status+info%27%7D&metadata=datasetMetadata&metadata=origin&orderId=7651cb16-a68a-4715-be53-6e146503dd71&provider=cop_ads"}
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: accepted
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
order status: running
downloading 553c60c19f8bc485560b5a9f0437995.zip
100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 875M/875M [04:28<00:00, 3.27MB/s]