Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions algorithm_catalog/eodc/sen2like/benchmark_scenarios/sen2like.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"id": "sen2like",
"type": "openeo",
"description": "Sen2like test",
"backend": "https://openeo.eodc.eu/openeo/1.2.0",
"process_graph": {
"ardsen2like": {
"process_id": "sen2like",
"namespace": "https://raw.githubusercontent.com/ESA-APEx/apex_algorithms/refs/heads/sen2like/algorithm_catalog/eodc/sen2like/openeo_udp/sen2like.json",
"arguments": {
"bbox": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the 'bbox' parameter would be 'spatial_extent' according to current udp definition.
If I however try to run this scenario, it gives "No Sentinel2 tiles available for provided request!"
Test job id: d5c7336a-fe72-4867-9762-c4fad4c10025

Suggested change
"bbox": {
"spatial_extent": {
"east": 16.414,
"north": 48.008,
"south": 47.962,
"west": 16.342
},
"temporal_extent": [
"2023-05-01T00:00:00Z",
"2023-05-30T00:00:00Z"
]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now updated the parameter name. Sorry, I initially tested it on the dev backend, where it ran through successfully, but I only deployed the latest changes to production today.

"east": 16.414,
"north": 48.008,
"south": 47.962,
"west": 16.342
},
"temporal_extent": [
"2023-05-01",
"2023-05-30"
]
},
"result": true
}
},
"reference_data": {
},
"reference_options": {
}
}
]
17 changes: 17 additions & 0 deletions algorithm_catalog/eodc/sen2like/openeo_udp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Sen2like

The Sen2Like processor was developed by ESA as part of the EU Copernicus program. It creates Sentinel-2 like harmonized (Level-2H) or fused (Level-2F) surface reflectances by harmonizing Sentinel-2 and Landsat 8/Landsat 9 to increase the temporal revisits. The fusion involves the upscaling of Landsat 8/Landsat 9 data to Sentinel-2 resolution. Furthermore, the resulting sen2like L2H/ L2F data can be processed using openEO to generate statistics, vegetation indices, do comparisons with other datasets, etc.

### Methodology

The processing of the incoming Landsat and Sentinel-2 L1C data includes the following main processing steps: Geometric Processing, Stitching, Geometric Check, Inter-calibration, Atmospheric correction, BRDF Adjustment, SBAF, Topographic Correction, Data Fusion. If Sentinel-2 L2A data is provided, sen2like processing will not include Atmospheric and Topographic Correction. In the geometric processing step, the input images are co-registered to a Sentinel-2 reference image. The atmospheric correction step makes use of the sen2cor processor and additionally relies on Copernicus Atmosphere Monitoring Service (CAMS) Near Real Time and Reanalysis data as well as the Copernicus Digital Elevation Model. The Data Fusion step alignes Landsat 8 image pixel spacing fully with Sentinel2 image pixel spacing. Depending on the band, the resolution of the Landsat L2F product is 10 m, 20 m or 30 m.

### Quality

The geometric check process is a Quality Control step of the product. For further information, see Sen2like User Manual.

### Links

- [RD1] openEO platform Sen2like documentation https://docs.openeo.cloud/usecases/ard/sen2like

- [RD1] Saunier, S. (2025). Sen2like User Manual https://github.com/senbox-org/sen2like/blob/master/sen2like/docs/source/S2-SEN2LIKE-UM-V1.10.pdf"
72 changes: 72 additions & 0 deletions algorithm_catalog/eodc/sen2like/openeo_udp/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import json
from pathlib import Path

import openeo
from openeo.api.process import Parameter
from openeo.rest._datacube import THIS
from openeo.rest.udp import build_process_dict

def generate():
connection = openeo.connect("https://openeo.eodc.eu/openeo/1.2.0/").authenticate_oidc()

spatial_extent = Parameter.spatial_extent(
name="spatial_extent",
description="Limits the data to process to the specified bounding box or polygons.\\n\\nFor raster data, the process loads the pixel into the data cube if the point at the pixel center intersects with the bounding box or any of the polygons (as defined in the Simple Features standard by the OGC).\\nFor vector data, the process loads the geometry into the data cube if the geometry is fully within the bounding box or any of the polygons (as defined in the Simple Features standard by the OGC). Empty geometries may only be in the data cube if no spatial extent has been provided.\\n\\nEmpty geometries are ignored.\\nSet this parameter to null to set no limit for the spatial extent."
)

temporal_extent = Parameter.temporal_interval(
name="temporal_extent",
description="Temporal extent specified as two-element array with start and end date/date-time."
)

schema = {
"type": "string",
"enum": ["B01", "B02", "B03", "B04", "B05", "B06", "B07", "B08", "B8A", "B11", "B12"],
}
bands = Parameter.array(
name="bands",
description="Sentinel-2 bands to include in the composite.",
item_schema=schema,
default=["B04", "B03", "B02"],
optional=True,
)

collection = 'SENTINEL2_L2A'

s2_l1c = connection.load_collection(
collection,
spatial_extent=spatial_extent,
temporal_extent=temporal_extent,
bands=bands)

sen2like = s2_l1c.process('sen2like', {
'data': THIS,
'target_product': 'L2F',
'export_original_files': True,
'cloud_cover': 50})

returns = {
"description": "A data cube with the newly computed values.\n\nThe result will combine Sentinel-2 and Landsat timesteps for the requested extent. Just like Sentinel 2 data, sen2like generates .SAFE output files, which are zipped for the purpose of openEO. For both Landsat and Sentinel acquisitions the .SAFE files include the requested bands. Note, that the sen2like Landsat outputs do not include equivalents of Sentinel-2 bands B05, B06, B07.",
"schema": {
"type": "object",
"subtype": "datacube"
}
}

return build_process_dict(
process_graph=sen2like,
process_id="sen2like",
summary="Computes a harmonzed Sentinel-2 and Landsat timeseries.",
description=(Path(__file__).parent / "README.md").read_text(),
parameters=[
spatial_extent,
temporal_extent
],
returns=returns,
categories=["sentinel-2", "ARD"]
)


if __name__ == "__main__":
with open("sen2like.json", "w") as f:
json.dump(generate(), f, indent=2)
171 changes: 171 additions & 0 deletions algorithm_catalog/eodc/sen2like/openeo_udp/sen2like.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{
"process_graph": {
"loadcollection1": {
"process_id": "load_collection",
"arguments": {
"bands": {
"from_parameter": "bands"
},
"id": "SENTINEL2_L2A",
"spatial_extent": {
"from_parameter": "spatial_extent"
},
"temporal_extent": {
"from_parameter": "temporal_extent"
}
}
},
"sen2like1": {
"process_id": "sen2like",
"arguments": {
"cloud_cover": 50,
"data": {
"from_node": "loadcollection1"
},
"export_original_files": true,
"target_product": "L2F"
},
"result": true
}
},
"id": "sen2like",
"description": "# Sen2like\n\nThe Sen2Like processor was developed by ESA as part of the EU Copernicus program. It creates Sentinel-2 like harmonized (Level-2H) or fused (Level-2F) surface reflectances by harmonizing Sentinel-2 and Landsat 8/Landsat 9 to increase the temporal revisits. Based on the resulting L2F product, multiple indices can be computed, such as the NDVI and LAI. The fusion also involves the upscaling of Landsat 8/Landsat 9 data to Sentinel-2 resolution. With the new L2F data higher time-series resolution vegetation indices (such as NDVI, LAI, FAPAR, FCOVER) can be calculated.\n\n### Methodology\n\n\n\n### Quality\n\n\n\n### Links\n\n- [RD1] openEO platform Sen2like documentation https://docs.openeo.cloud/usecases/ard/sen2like",
"parameters": [
{
"name": "bands",
"description": "Only adds the specified bands into the data cube so that bands that don't match the list of band names are not available. Applies to all dimensions of type `bands`.\n\nEither the unique band name (metadata field `name` in bands) or one of the common band names (metadata field `common_name` in bands) can be specified. If the unique band name and the common name conflict, the unique band name has a higher priority.\n\nThe order of the specified array defines the order of the bands in the data cube. If multiple bands match a common name, all matched bands are included in the original order.\n\nIt is recommended to use this parameter instead of using ``filter_bands()`` directly after loading unbounded data.",
"schema": [
{
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"subtype": "band-name"
}
},
{
"title": "No filter",
"description": "Don't filter bands. All bands are included in the data cube.",
"type": "null"
}
],
"default": null,
"optional": true
},
{
"name": "spatial_extent",
"description": "Limits the data to process to the specified bounding box or polygons.\\n\\nFor raster data, the process loads the pixel into the data cube if the point at the pixel center intersects with the bounding box or any of the polygons (as defined in the Simple Features standard by the OGC).\\nFor vector data, the process loads the geometry into the data cube if the geometry is fully within the bounding box or any of the polygons (as defined in the Simple Features standard by the OGC). Empty geometries may only be in the data cube if no spatial extent has been provided.\\n\\nEmpty geometries are ignored.\\nSet this parameter to null to set no limit for the spatial extent.",
"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
}
}
},
{
"title": "Vector data cube",
"description": "Limits the data cube to the bounding box of the given geometries in the vector data cube. For raster data, all pixels inside the bounding box that do not intersect with any of the polygons will be set to no data (`null`). Empty geometries are ignored.",
"type": "object",
"subtype": "datacube",
"dimensions": [
{
"type": "geometry"
}
]
},
{
"title": "No filter",
"description": "Don't filter spatially. All data is included in the data cube.",
"type": "null"
}
]
},
{
"name": "temporal_extent",
"description": "Temporal extent specified as two-element array with start and end date/date-time.",
"schema": {
"type": "array",
"subtype": "temporal-interval",
"uniqueItems": true,
"minItems": 2,
"maxItems": 2,
"items": {
"anyOf": [
{
"type": "string",
"subtype": "date-time",
"format": "date-time"
},
{
"type": "string",
"subtype": "date",
"format": "date"
},
{
"type": "null"
}
]
}
}
}
]
}
Loading
Loading