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.
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:
Authenticate: How to authenticate for searching and access DEDL collections.
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.
Search data: How to search DEDL data using filters obtained by the βqueryablesβ API.
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
- 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)
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))
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)
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"
}
}
}
SearchΒΆ
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)
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]