diff --git a/openeo_driver/ProcessGraphDeserializer.py b/openeo_driver/ProcessGraphDeserializer.py index e1231ae1..6c41d520 100644 --- a/openeo_driver/ProcessGraphDeserializer.py +++ b/openeo_driver/ProcessGraphDeserializer.py @@ -925,6 +925,36 @@ def load_collection(args: dict, env: EvalEnv) -> DriverDataCube: return env.backend_implementation.catalog.load_collection(collection_id, load_params=load_params, env=env) +@process_registry_100.add_function(spec=read_spec("openeo-processes/experimental/query_stac.json")) +@process_registry_2xx.add_function(spec=read_spec("openeo-processes/experimental/query_stac.json")) +def query_stac(args: ProcessArgs, env: EvalEnv) -> Dict: + url = args.get_required( + "url", + ) + + temporal_extent = None + spatial_extent = None + if "temporal_extent" in args: + temporal_extent = _extract_temporal_extent( + args, field="temporal_extent", process_id="query_stac" + ) + if "spatial_extent" in args: + spatial_extent = _extract_bbox_extent( + args, field="spatial_extent", process_id="query_stac" + ) + + dry_run_tracer: DryRunDataTracer = env.get(ENV_DRY_RUN_TRACER) + if dry_run_tracer: + _log.warning("Dry run tracer not supported for query_stac") + return {} + else: + return env.backend_implementation.query_stac( + url=url, + spatial_extent=spatial_extent, + temporal_extent=temporal_extent, + env=env + ) + def _check_geometry_path_assumption(path: str, process: str, parameter: str): if isinstance(path, str) and path.lstrip().startswith("{"): raise ProcessParameterInvalidException( diff --git a/openeo_driver/backend.py b/openeo_driver/backend.py index 6cd25abf..fec4c6cd 100644 --- a/openeo_driver/backend.py +++ b/openeo_driver/backend.py @@ -16,7 +16,7 @@ import sys from datetime import datetime, timedelta from pathlib import Path -from typing import List, Union, NamedTuple, Dict, Optional, Callable, Iterable, Container, Any +from typing import List, Union, NamedTuple, Dict, Optional, Callable, Iterable, Container, Any, Tuple import flask @@ -35,6 +35,7 @@ from openeo_driver.users import User from openeo_driver.users.oidc import OidcProvider from openeo_driver.util.date_math import simple_job_progress_estimation +from openeo_driver.util.geometry import BoundingBox from openeo_driver.util.logging import just_log_exceptions from openeo_driver.utils import read_json, dict_item, EvalEnv, extract_namedtuple_fields_from_dict, \ get_package_versions @@ -985,6 +986,9 @@ def load_result(self, job_id: str, user_id: Optional[str], load_params: LoadPara def load_stac(self, url: str, load_params: LoadParameters, env: EvalEnv) -> DriverDataCube: raise NotImplementedError + def query_stac(self, url: str, spatial_extent: Union[Dict, BoundingBox, None], temporal_extent: Tuple[Optional[str], Optional[str]], _env: EvalEnv) -> DriverDataCube: + raise NotImplementedError + def load_ml_model(self, job_id: str) -> DriverMlModel: raise NotImplementedError diff --git a/openeo_driver/specs/openeo-processes/experimental/query_stac.json b/openeo_driver/specs/openeo-processes/experimental/query_stac.json new file mode 100644 index 00000000..b3deee29 --- /dev/null +++ b/openeo_driver/specs/openeo-processes/experimental/query_stac.json @@ -0,0 +1,189 @@ +{ + "id": "query_stac", + "summary": "Queries STAC", + "description": "Queries a STAC to construct a FeatureCollection of STAC Items with the results", + "categories": [ + "import" + ], + "experimental": true, + "parameters": [ + { + "name": "url", + "description": "The URL to a static STAC Collection", + "schema": { + "title": "URL", + "type": "string", + "format": "uri", + "subtype": "uri", + "pattern": "^https?://" + } + }, + { + "name": "spatial_extent", + "description": "Limits the data to load to the specified bounding box.", + "schema": [ + { + "title": "Bounding Box", + "type": "object", + "subtype": "bounding-box", + "required": [ + "west", + "south", + "east", + "north" + ], + "properties": { + "west": { + "description": "West (lower left corner, coordinate axis 1).", + "type": "number" + }, + "south": { + "description": "South (lower left corner, coordinate axis 2).", + "type": "number" + }, + "east": { + "description": "East (upper right corner, coordinate axis 1).", + "type": "number" + }, + "north": { + "description": "North (upper right corner, coordinate axis 2).", + "type": "number" + }, + "base": { + "description": "Base (optional, lower left corner, coordinate axis 3).", + "type": [ + "number", + "null" + ], + "default": null + }, + "height": { + "description": "Height (optional, upper right corner, coordinate axis 3).", + "type": [ + "number", + "null" + ], + "default": null + }, + "crs": { + "description": "Coordinate reference system of the extent, specified as as [EPSG code](http://www.epsg-registry.org/) or [WKT2 CRS string](http://docs.opengeospatial.org/is/18-010r7/18-010r7.html). Defaults to `4326` (EPSG code 4326) unless the client explicitly requests a different coordinate reference system.", + "anyOf": [ + { + "title": "EPSG Code", + "type": "integer", + "subtype": "epsg-code", + "minimum": 1000, + "examples": [ + 3857 + ] + }, + { + "title": "WKT2", + "type": "string", + "subtype": "wkt2-definition" + } + ], + "default": 4326 + } + } + } + ], + "optional": false + }, + { + "name": "temporal_extent", + "description": "Limits the data to load to the specified left-closed temporal interval. Applies to all temporal dimensions. The interval has to be specified as an array with exactly two elements:\n\n1. The first element is the start of the temporal interval. The specified instance in time is **included** in the interval.\n2. The second element is the end of the temporal interval. The specified instance in time is **excluded** from the interval.\n\nThe second element must always be greater/later than the first element. Otherwise, a `TemporalExtentEmpty` exception is thrown.\n\nAlso supports open intervals by setting one of the boundaries to `null`, but never both.", + "schema": [ + { + "type": "array", + "subtype": "temporal-interval", + "uniqueItems": true, + "minItems": 2, + "maxItems": 2, + "items": { + "anyOf": [ + { + "type": "string", + "format": "date-time", + "subtype": "date-time", + "description": "Date and time with a time zone." + }, + { + "type": "string", + "format": "date", + "subtype": "date", + "description": "Date only, formatted as `YYYY-MM-DD`. The time zone is UTC. Missing time components are all 0." + }, + { + "type": "null" + } + ] + }, + "examples": [ + [ + "2015-01-01T00:00:00Z", + "2016-01-01T00:00:00Z" + ], + [ + "2015-01-01", + "2016-01-01" + ] + ] + } + ], + "optional": false + } + ], + "returns": { + "description": "A GeoJSON FeatureCollection ", + "schema": { + "type": "object", + "subtype": "geojson" + } + }, + "examples": [ + { + "title": "Load from a STAC API", + "arguments": { + "url": "https://example.com/collections/SENTINEL2", + "spatial_extent": { + "west": 16.1, + "east": 16.6, + "north": 48.6, + "south": 47.2 + }, + "temporal_extent": [ + "2018-01-01", + "2019-01-01" + ] + } + } + ], + "links": [ + { + "rel": "about", + "href": "https://proj.org/usage/projections.html", + "title": "PROJ parameters for cartographic projections" + }, + { + "rel": "about", + "href": "http://www.epsg-registry.org", + "title": "Official EPSG code registry" + }, + { + "rel": "about", + "href": "http://www.epsg.io", + "title": "Unofficial EPSG code database" + }, + { + "href": "http://www.opengeospatial.org/standards/sfa", + "rel": "about", + "title": "Simple Features standard by the OGC" + }, + { + "href": "https://www.rfc-editor.org/rfc/rfc3339.html", + "rel": "about", + "title": "RFC3339: Details about formatting temporal strings" + } + ] +}