diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb2e4b1..19603d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -8,8 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- New proposal processes for DGGS-based workflows: + - `aggregate_k_ring` - Aggregate values over DGGS k-ring neighborhoods, comparable to `aggregate_spatial_window`. + - `apply_neighborhood_dggs` - Apply custom focal processes over DGGS neighborhoods, comparable to `apply_neighborhood`, + - `apply_kernel_dggs` - Apply weighted DGGS neighborhood convolution based on topological distance, comparable to `apply_kernel`. + - `filter_k_ring` - Filter based on rings around DGGS zones. + - `resample_dggs` - Up- and downsample based on DGGS resolution levels, comparable to `resample_spatial`, + - `resample_cube_dggs` - Up and downsample based on a target DGGS data cube, comparable to `resample_cube_spatial`. + - `mask_dggs` - Mask a DGGS data cube based on a DGGS-based mask, comparable to `mask`. + ### Changed +- `apply_kernel`, `apply_neighborhood`, `resample_spatial`, `resample_cube_spatial`, and `aggregate_spatial_window`: Added explicit DGGS guidance and updated cross-references to DGGS-native alternatives. +- `reduce_spatial` and `resample_cube_spatial`: Added a recommended DGGS alternative. +- `aggregate_spatial`, `filter_bbox`, `filter_spatial`, `load_collection`, `load_stac`, and `mask_polygon`: Added DGGS-specific behavior clarification (zone centroid inclusion rule for spatial filtering/masking/aggregation). +- `resample_spatial`: Added support for DGGS input; a DGGS data cube can be converted to raster when a suitable target projection is provided. + ### Fixed ## [2.0.0-rc.2] - 2026-02-02 @@ -31,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Processes that have been marked as stable: `apply_polygon`, `date_between`, `date_shift`, `filter_labels`, `inspect` - Clarified for various mathematical functions the defined input and output ranges. Mention that `NaN` is returned outside of the defined input range where possible. -- Clarified for several comparison processes how `NaN` values have to be handled. +- Clarified for several comparison processes how `NaN` values have to be handled. - Clarified for various processes the handling of no-data values and `null`, see also the [implementation guide](meta/implementation.md#no-data-value). [#480](https://github.com/Open-EO/openeo-processes/issues/480) - Added a [section about character encodings to the implementation guide](meta/implementation.md#character-encoding). Removed any character encoding related wording from the process specifications itself. diff --git a/aggregate_spatial.json b/aggregate_spatial.json index c0aeb939..db9068cd 100644 --- a/aggregate_spatial.json +++ b/aggregate_spatial.json @@ -4,29 +4,44 @@ "description": "Aggregates statistics for one or more geometries (e.g. zonal statistics for polygons) over the spatial dimensions. The given data cube can have multiple additional dimensions and for all these dimensions results will be computed individually.\n\nAn 'unbounded' aggregation over the full extent of the horizontal spatial dimensions can be computed with the process ``reduce_spatial()``.\n\nThis process passes a list of values to the reducer. The list of values has an undefined order, therefore processes such as ``last()`` and ``first()`` that depend on the order of the values will lead to unpredictable results.", "categories": [ "cubes", - "aggregate" + "aggregate", + "dggs" ], "parameters": [ { "name": "data", - "description": "A raster data cube with at least two spatial dimensions.\n\nThe data cube implicitly gets restricted to the bounds of the geometries as if ``filter_spatial()`` would have been used with the same values for the corresponding parameters immediately before this process.", - "schema": { - "type": "object", - "subtype": "datacube", - "dimensions": [ - { - "type": "spatial", - "axis": [ - "x", - "y" - ] - } - ] - } + "description": "A raster or DGGS data cube.\n\nThe data cube implicitly gets restricted to the bounds of the geometries as if ``filter_spatial()`` would have been used with the same values for the corresponding parameters immediately before this process.", + "schema": [ + { + "title": "Raster data cube", + "description": "A raster data cube with at least two spatial dimensions.", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "spatial", + "axis": [ + "x", + "y" + ] + } + ] + }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + ] }, { "name": "geometries", - "description": "Geometries for which the aggregation will be computed. Feature properties are preserved for vector data cubes and all GeoJSON Features.\n\nOne value will be computed per label in the dimension of type `geometry`, GeoJSON `Feature` or `Geometry`. For a `FeatureCollection` multiple values will be computed, one value per contained `Feature`. No values will be computed for empty geometries. For example, a single value will be computed for a `MultiPolygon`, but two values will be computed for a `FeatureCollection` containing two polygons.\n\n- For **polygons**, the process considers all pixels for which the point at the pixel center intersects with the corresponding polygon (as defined in the Simple Features standard by the OGC).\n- For **points**, the process considers the closest pixel center.\n- For **lines** (line strings), the process considers all the pixels whose centers are closest to at least one point on the line.\n\nThus, pixels may be part of multiple geometries and be part of multiple aggregations. No operation is applied to geometries that are outside of the bounds of the data.", + "description": "Geometries for which the aggregation will be computed. Feature properties are preserved for vector data cubes and all GeoJSON Features.\n\nOne value will be computed per label in the dimension of type `geometry`, GeoJSON `Feature` or `Geometry`. For a `FeatureCollection` multiple values will be computed, one value per contained `Feature`. No values will be computed for empty geometries. For example, a single value will be computed for a `MultiPolygon`, but two values will be computed for a `FeatureCollection` containing two polygons.\n\n- For **polygons**, the process considers all values for which the point at the pixel or DGGS zone centroid intersects with the corresponding polygon (as defined in the Simple Features standard by the OGC).\n- For **points**, the process considers the closest pixel or DGGS zone based on the centroid.\n- For **lines** (line strings), the process considers all the values whose pixel or zone centroid are closest to at least one point on the line.\n\nPixels or DGGS zones may be part of multiple geometries and be part of multiple aggregations. No operation is applied to geometries that are outside of the bounds of the data.", "schema": [ { "title": "Vector Data Cube", @@ -84,7 +99,7 @@ }, { "name": "target_dimension", - "description": "By default (which is `null`), the process only computes the results and doesn't add a new dimension.\n\nIf this parameter contains a new dimension name, the computation also stores information about the total count of pixels (valid + invalid pixels) and the number of valid pixels (see ``is_valid()``) for each computed value. These values are added as a new dimension. The new dimension of type `other` has the dimension labels `value`, `total_count` and `valid_count`.\n\nFails with a `TargetDimensionExists` exception if a dimension with the specified name exists.", + "description": "By default (which is `null`), the process only computes the results and doesn't add a new dimension.\n\nIf this parameter contains a new dimension name, the computation also stores information about the total count of values (valid + invalid values) and the number of valid values (see ``is_valid()``) for each computed value. These values are added as a new dimension. The new dimension of type `other` has the dimension labels `value`, `total_count` and `valid_count`.\n\nFails with a `TargetDimensionExists` exception if a dimension with the specified name exists.", "schema": { "type": [ "string", diff --git a/apply_kernel.json b/apply_kernel.json index 49fb8f82..621440bf 100644 --- a/apply_kernel.json +++ b/apply_kernel.json @@ -1,7 +1,7 @@ { "id": "apply_kernel", "summary": "Apply a spatial convolution with a kernel", - "description": "Applies a 2D convolution (i.e. a focal operation with a weighted kernel) on the horizontal spatial dimensions (axes `x` and `y`) of a raster data cube.\n\nEach value in the kernel is multiplied with the corresponding pixel value and all products are summed up afterwards. The sum is then multiplied with the factor.\n\nThe process can't handle non-numerical or infinite numerical values in the data cube. Boolean values are converted to integers (`false` = 0, `true` = 1), but all other non-numerical, NaN, no-data, or infinite values are replaced with zeroes by default (see parameter `replace_invalid`).\n\nFor cases requiring more generic focal operations or non-numerical values, see ``apply_neighborhood()``.", + "description": "Applies a 2D convolution (i.e. a focal operation with a weighted kernel) on the horizontal spatial dimensions (axes `x` and `y`) of a raster data cube.\n\nEach value in the kernel is multiplied with the corresponding pixel value and all products are summed up afterwards. The sum is then multiplied with the factor.\n\nThe process can't handle non-numerical or infinite numerical values in the data cube. Boolean values are converted to integers (`false` = 0, `true` = 1), but all other non-numerical, NaN, no-data, or infinite values are replaced with zeroes by default (see parameter `replace_invalid`).\n\nFor DGGS-native weighted neighborhood operations, see ``apply_kernel_dggs()``.\n\nFor cases requiring more generic focal operations or non-numerical values, see ``apply_neighborhood()`` for raster data cubes and ``apply_neighborhood_dggs()`` for DGGS data cubes.", "categories": [ "cubes", "math > image filter" diff --git a/apply_neighborhood.json b/apply_neighborhood.json index dd7a89d9..50985356 100644 --- a/apply_neighborhood.json +++ b/apply_neighborhood.json @@ -1,7 +1,7 @@ { "id": "apply_neighborhood", "summary": "Apply a process to pixels in a n-dimensional neighborhood", - "description": "Applies a focal process to a data cube.\n\nA focal process is a process that works on a 'neighborhood' of pixels. The neighborhood can extend into multiple dimensions, this extent is specified by the `size` argument. It is not only (part of) the size of the input window, but also the size of the output for a given position of the sliding window. The sliding window moves with multiples of `size`.\n\nAn overlap can be specified so that neighborhoods can have overlapping boundaries. This allows for continuity of the output. The overlap region must be included in the data cube or array returned by `process`, but any changed values will be ignored. The missing overlap at the borders of the original data cube is made available as no-data values in the sub-data cubes.\n\nThe neighborhood size should be kept small enough, to avoid running beyond computational resources, but a too-small size will result in a larger number of process invocations, which may slow down processing. Window sizes for spatial dimensions typically range from 64 to 512 pixels, while overlaps of 8 to 32 pixels are common.\n\nFor the special case of 2D convolution, it is recommended to use ``apply_kernel()``.", + "description": "Applies a focal process to a data cube.\n\nA focal process is a process that works on a 'neighborhood' of pixels. The neighborhood can extend into multiple dimensions, this extent is specified by the `size` argument. It is not only (part of) the size of the input window, but also the size of the output for a given position of the sliding window. The sliding window moves with multiples of `size`.\n\nAn overlap can be specified so that neighborhoods can have overlapping boundaries. This allows for continuity of the output. The overlap region must be included in the data cube or array returned by `process`, but any changed values will be ignored. The missing overlap at the borders of the original data cube is made available as no-data values in the sub-data cubes.\n\nThe neighborhood size should be kept small enough, to avoid running beyond computational resources, but a too-small size will result in a larger number of process invocations, which may slow down processing. Window sizes for spatial dimensions typically range from 64 to 512 pixels, while overlaps of 8 to 32 pixels are common.\n\nFor DGGS-native topological neighborhoods, see ``apply_neighborhood_dggs()``.\n\nFor the special case of 2D convolution, it is recommended to use ``apply_kernel()`` for raster data cubes and ``apply_kernel_dggs()`` for DGGS data cubes.", "categories": [ "cubes" ], diff --git a/apply_polygon.json b/apply_polygon.json index 685782b0..1c067ab3 100644 --- a/apply_polygon.json +++ b/apply_polygon.json @@ -1,7 +1,7 @@ { "id": "apply_polygon", "summary": "Apply a process to segments of the data cube", - "description": "Applies a process to segments of the data cube that are defined by the given polygons. For each polygon provided, all pixels for which the point at the pixel center intersects with the polygon (as defined in the Simple Features standard by the OGC) are collected into sub data cubes. If a pixel is part of multiple of the provided polygons (e.g., when the polygons overlap), the `GeometriesOverlap` exception is thrown. Each sub data cube is passed individually to the given process.", + "description": "Applies a process to segments of the data cube that are defined by the given polygons. For each polygon provided, all pixels for which the pixel centroid intersects with the polygon (as defined in the Simple Features standard by the OGC) are collected into sub data cubes. If a pixel is part of multiple of the provided polygons (e.g., when the polygons overlap), the `GeometriesOverlap` exception is thrown. Each sub data cube is passed individually to the given process.", "categories": [ "cubes" ], @@ -97,7 +97,7 @@ }, { "name": "mask_value", - "description": "All pixels for which the point at the pixel center **does not** intersect with the polygon are replaced with the given value. The default value is `null`, which uses the no-data value of the data cube.\n\nThis parameter can provide a distinction between no-data values within the polygon and masked pixels outside of it.", + "description": "All pixels for which the pixel centroid **does not** intersect with the polygon are replaced with the given value. The default value is `null`, which uses the no-data value of the data cube.\n\nThis parameter can provide a distinction between no-data values within the polygon and masked pixels outside of it.", "schema": [ { "type": "number" diff --git a/dev/.words b/dev/.words index 38f6c0f8..7ec5ee4f 100644 --- a/dev/.words +++ b/dev/.words @@ -7,6 +7,10 @@ center centers dekad DEM-based +DGGS +DGGS-based +DGGS-native +DGGRS Domini gamma0 GeoJSON @@ -30,6 +34,7 @@ resampled resamples Resamples resampling +reprojection Sentinel-2 Sentinel-2A Sentinel-2B diff --git a/dev/package.json b/dev/package.json index 14a1f1ab..fa085615 100644 --- a/dev/package.json +++ b/dev/package.json @@ -1,37 +1,38 @@ { - "name": "@openeo/processes", - "version": "2.0.0-rc.2", - "author": "openEO Consortium", - "contributors": [ - { - "name": "Matthias Mohr" + "name": "@openeo/processes", + "version": "2.0.0-rc.2", + "author": "openEO Consortium", + "contributors": [ + { + "name": "Matthias Mohr" + } + ], + "license": "Apache-2.0", + "description": "Validates the processes specified in this repository.", + "homepage": "http://openeo.org", + "bugs": { + "url": "https://github.com/Open-EO/openeo-processes/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Open-EO/openeo-processes.git" + }, + "devDependencies": { + "@openeo/processes-lint": "github:Open-EO/openeo-processes-lint#dggs", + "concat-json-files": "^1.1.0", + "http-server": "^14.1.1" + }, + "scripts": { + "check-tests": "node check-tests.js", + "has-tests": "node has-tests.js", + "lint": "openeo-processes-lint testConfig.json", + "test": "npm run has-tests && npm run check-tests && npm run lint", + "generate": "concat-json-files \"../{*,proposals/*}.json\" -t \"processes.json\"", + "start": "npm run generate && http-server -p 9876 -o docs.html -c-1" + }, + "dependencies": { + "ajv": "^8.17.1", + "json5": "^2.2.3" } - ], - "license": "Apache-2.0", - "description": "Validates the processes specified in this repository.", - "homepage": "http://openeo.org", - "bugs": { - "url": "https://github.com/Open-EO/openeo-processes/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/Open-EO/openeo-processes.git" - }, - "devDependencies": { - "@openeo/processes-lint": "^0.1.5", - "concat-json-files": "^1.1.0", - "http-server": "^14.1.1" - }, - "scripts": { - "check-tests": "node check-tests.js", - "has-tests": "node has-tests.js", - "lint": "openeo-processes-lint testConfig.json", - "test": "npm run has-tests && npm run check-tests && npm run lint", - "generate": "concat-json-files \"../{*,proposals/*}.json\" -t \"processes.json\"", - "start": "npm run generate && http-server -p 9876 -o docs.html -c-1" - }, - "dependencies": { - "ajv": "^8.17.1", - "json5": "^2.2.3" - } + } diff --git a/filter_bbox.json b/filter_bbox.json index 21b6e8cd..8344ff26 100644 --- a/filter_bbox.json +++ b/filter_bbox.json @@ -1,7 +1,7 @@ { "id": "filter_bbox", "summary": "Spatial filter using a bounding box", - "description": "Limits the data cube to the specified bounding box.\n\n* For raster data cubes, the filter retains a pixel in the data cube if the point at the pixel center intersects with the bounding box (as defined in the Simple Features standard by the OGC). Alternatively, ``filter_spatial()`` can be used to filter by geometry.\n* For vector data cubes, the filter retains the geometry in the data cube if the geometry is fully within the bounding box (as defined in the Simple Features standard by the OGC). All geometries that were empty or not contained fully within the bounding box will be removed from the data cube.\n\nAlternatively, filter spatially with geometries using ``filter_spatial()`` (on a raster data cube) or ``filter_vector()`` (on a vector data cube).", + "description": "Limits the data cube to the specified bounding box.\n\n* For raster data cubes, the filter retains a pixel in the data cube if the pixel centroid intersects with the bounding box (as defined in the Simple Features standard by the OGC). Alternatively, ``filter_spatial()`` can be used to filter by geometry.\n* For vector data cubes, the filter retains the geometry in the data cube if the geometry is fully within the bounding box (as defined in the Simple Features standard by the OGC). All geometries that were empty or not contained fully within the bounding box will be removed from the data cube.\n* For data cubes with a DGGS dimension, the filter retains a DGGS zone if the zone centroid intersects with the bounding box.\n\nAlternatively, filter spatially with geometries using ``filter_spatial()`` (on a raster data cube) or ``filter_vector()`` (on a vector data cube).", "categories": [ "cubes", "filter" @@ -25,6 +25,16 @@ } ] }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + }, { "title": "Vector data cube", "type": "object", @@ -107,7 +117,7 @@ } ], "returns": { - "description": "A data cube restricted to the bounding box. The dimensions and dimension properties (name, type, labels, reference system and resolution) remain unchanged, except that the spatial dimensions have less (or the same) dimension labels.", + "description": "A data cube restricted to the bounding box. The dimensions and dimension properties (name, type, labels, reference system and resolution) remain unchanged, except that the spatial or DGGS dimensions have less (or the same) dimension labels.", "schema": [ { "title": "Raster data cube", @@ -123,6 +133,16 @@ } ] }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + }, { "title": "Vector data cube", "type": "object", @@ -163,3 +183,4 @@ } ] } + diff --git a/filter_spatial.json b/filter_spatial.json index f1b5f8b8..5985e026 100644 --- a/filter_spatial.json +++ b/filter_spatial.json @@ -1,7 +1,7 @@ { "id": "filter_spatial", - "summary": "Spatial filter raster data cubes using geometries", - "description": "Limits the raster data cube over the spatial dimensions to the specified geometries.\n\n- For **polygons**, the filter retains a pixel in the data cube if the point at the pixel center intersects with at least one of the polygons (as defined in the Simple Features standard by the OGC).\n- For **points**, the process considers the closest pixel center.\n- For **lines** (line strings), the process considers all the pixels whose centers are closest to at least one point on the line.\n\nMore specifically, pixels outside of the bounding box of the given geometries will not be available after filtering. All pixels inside the bounding box that are not retained will be set to no-data values.\n\n Alternatively, use ``filter_bbox()`` to filter with a bounding box or ``filter_vector()`` to filter a vector data cube based on geometries. Use ``mask_polygon()`` to mask without changing the spatial extent of your data cube.", + "summary": "Spatially filter data cubes using geometries", + "description": "Limits a raster data cube over the horizontal spatial dimensions (axes `x` and `y`) or a DGGS data cube over its DGGS dimension to the specified geometries.\n\n- For **polygons**, the process considers all values for which the pixel or DGGS zone centroid intersects with the corresponding polygon (as defined in the Simple Features standard by the OGC).\n- For **points**, the process considers the closest pixel or DGGS zone based on the centroid.\n- For **lines** (line strings), the process considers all the values whose pixel or zone centroid are closest to at least one point on the line.\n\nMore specifically, pixels or DGGS zones outside of the bounding box of the given geometries will not be available after filtering. All pixels or DGGS zones with their centroids inside the bounding box that are not retained will be set to no-data values.\n\nAlternatively, use ``filter_bbox()`` to filter with a bounding box or ``filter_vector()`` to filter a vector data cube based on geometries. Use ``mask_polygon()`` to mask without changing the spatial extent of your data cube.", "categories": [ "cubes", "filter" @@ -9,20 +9,33 @@ "parameters": [ { "name": "data", - "description": "A raster data cube.", - "schema": { - "type": "object", - "subtype": "datacube", - "dimensions": [ - { - "type": "spatial", - "axis": [ - "x", - "y" - ] - } - ] - } + "description": "A raster or DGGS data cube.", + "schema": [ + { + "title": "Raster data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "spatial", + "axis": [ + "x", + "y" + ] + } + ] + }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + ] }, { "name": "geometries", @@ -49,20 +62,33 @@ } ], "returns": { - "description": "A raster data cube restricted to the specified geometries. The dimensions and dimension properties (name, type, labels, reference system and resolution) remain unchanged, except that the spatial dimensions have less (or the same) dimension labels.", - "schema": { - "type": "object", - "subtype": "datacube", - "dimensions": [ - { - "type": "spatial", - "axis": [ - "x", - "y" - ] - } - ] - } + "description": "A data cube restricted to the specified geometries. The dimensions and dimension properties (name, type, labels, reference system and resolution) remain unchanged, except that the spatial or DGGS dimensions have less (or the same) dimension labels.", + "schema": [ + { + "title": "Raster data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "spatial", + "axis": [ + "x", + "y" + ] + } + ] + }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + ] }, "links": [ { diff --git a/load_collection.json b/load_collection.json index 69606f03..65dd896b 100644 --- a/load_collection.json +++ b/load_collection.json @@ -18,7 +18,7 @@ }, { "name": "spatial_extent", - "description": "Limits the data to load from the collection to the specified bounding box or polygons.\n\n* For 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).\n* For 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\nThe GeoJSON can be one of the following feature types:\n\n* A `Polygon` or `MultiPolygon` geometry,\n* a `Feature` with a `Polygon` or `MultiPolygon` geometry, or\n* a `FeatureCollection` containing at least one `Feature`; where each `Feature` has a `Polygon` or `MultiPolygon` geometry.\n* Empty geometries are ignored.\n\nSet this parameter to `null` to set no limit for the spatial extent. Be careful with this when loading large datasets! It is recommended to use this parameter instead of using ``filter_bbox()`` or ``filter_spatial()`` directly after loading unbounded data.", + "description": "Limits the data to load from the collection to the specified bounding box or polygons.\n\n* For raster or DGGS data, the process loads the value into the data cube at the pixel or DGGS zone centroid intersects with the bounding box or any of the polygons (as defined in the Simple Features standard by the OGC).\n* For 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\nThe GeoJSON can be one of the following feature types:\n\n* A `Polygon` or `MultiPolygon` geometry,\n* a `Feature` with a `Polygon` or `MultiPolygon` geometry, or\n* a `FeatureCollection` containing at least one `Feature`; where each `Feature` has a `Polygon` or `MultiPolygon` geometry.\n* Empty geometries are ignored.\n\nSet this parameter to `null` to set no limit for the spatial extent. Be careful with this when loading large datasets! It is recommended to use this parameter instead of using ``filter_bbox()`` or ``filter_spatial()`` directly after loading unbounded data.", "schema": [ { "title": "Bounding Box", @@ -87,14 +87,14 @@ }, { "title": "GeoJSON", - "description": "Deprecated in favor of ``load_geojson()``. Limits the data cube to the bounding box of the given geometries. For raster data, all pixels inside the bounding box that do not intersect with any of the polygons will be set to the no-data value of the data cube.\n\nThe GeoJSON type `GeometryCollection` is not supported. Empty geometries are ignored.", + "description": "Deprecated in favor of ``load_geojson()``. Limits the data cube to the bounding box of the given geometries. For raster or DGGS data, all pixels or DGGS zones with their centroids inside the bounding box that do not intersect with any of the polygons will be set to the no-data value of the data cube.\n\nThe GeoJSON type `GeometryCollection` is not supported. Empty geometries are ignored.", "type": "object", "subtype": "geojson", "deprecated": true }, { "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 the no-data value of the data cube. Empty geometries are ignored.", + "description": "Limits the data cube to the bounding box of the given geometries in the vector data cube. For raster or DGGS data, all pixels or DGGS zones with their centroids inside the bounding box that do not intersect with any of the polygons will be set to the no-data value of the data cube. Empty geometries are ignored.", "type": "object", "subtype": "datacube", "dimensions": [ diff --git a/mask_polygon.json b/mask_polygon.json index 83160c22..5774349f 100644 --- a/mask_polygon.json +++ b/mask_polygon.json @@ -1,28 +1,42 @@ { "id": "mask_polygon", "summary": "Apply a polygon mask", - "description": "Applies a (multi) polygon mask to a raster data cube. To apply a raster mask use ``mask()``.\n\nAll pixels for which the point at the pixel center **does not** intersect with any polygon (as defined in the Simple Features standard by the OGC) are replaced. This behavior can be inverted by setting the parameter `inside` to `true`. The pixel values are replaced with the value specified for `replacement`, which defaults to the no-data value of the raster data cube. No data values in `data` will be left untouched by the masking operation.", + "description": "Applies a (multi) polygon mask to a raster or DGGS data cube. To apply a raster mask use ``mask()``.\n\nAll values for which the point at the pixel or DGGS zone centroid **does not** intersect with any polygon (as defined in the Simple Features standard by the OGC) are replaced. This behavior can be inverted by setting the parameter `inside` to `true`. The pixel or zone values are replaced with the value specified for `replacement`, which defaults to the no-data value of the data cube. No data values in `data` will be left untouched by the masking operation.", "categories": [ "cubes", - "masks" + "masks", + "dggs" ], "parameters": [ { "name": "data", - "description": "A raster data cube.", - "schema": { - "type": "object", - "subtype": "datacube", - "dimensions": [ - { - "type": "spatial", - "axis": [ - "x", - "y" - ] - } - ] - } + "description": "A raster or DGGS data cube.", + "schema": [ + { + "title": "Raster data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "spatial", + "axis": [ + "x", + "y" + ] + } + ] + }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + ] }, { "name": "mask", @@ -73,7 +87,7 @@ }, { "name": "inside", - "description": "If set to `true` all pixels for which the point at the pixel center **does** intersect with any polygon are replaced.", + "description": "If set to `true` all values for which the point at the pixel or DGGS zone centroid **does** intersect with any polygon are replaced.", "schema": { "type": "boolean" }, @@ -82,20 +96,33 @@ } ], "returns": { - "description": "A masked raster data cube with the same dimensions. The dimension properties (name, type, labels, reference system and resolution) remain unchanged.", - "schema": { - "type": "object", - "subtype": "datacube", - "dimensions": [ - { - "type": "spatial", - "axis": [ - "x", - "y" - ] - } - ] - } + "description": "A masked data cube with the same dimensions. The dimension properties (name, type, labels, reference system and resolution) remain unchanged.", + "schema": [ + { + "title": "Raster data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "spatial", + "axis": [ + "x", + "y" + ] + } + ] + }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + ] }, "links": [ { diff --git a/meta/dggs.md b/meta/dggs.md new file mode 100644 index 00000000..9e53f085 --- /dev/null +++ b/meta/dggs.md @@ -0,0 +1,75 @@ +# DGGS in openEO + +## Introduction + +**DGGS** (Discrete Global Grid System) is a way to divide the Earth into a set of fixed, regular, and hierarchical **grid of zones** (often hexagons or squares). Each zone has a unique ID and can be refined into smaller zones for higher resolution. The grid covers the whole Earth - no gaps, no overlaps. Instead of continuous coordinates, space is divided into **zones**. Each zone represents an area and different DGGS implementations use different shapes, e.g. Hexagons or Squares. Every point on Earth belongs to exactly one zone (at a given resolution). Well-known DGGS implementations are H3, S2 and rHEALPix. + +## DGGS as a dimension + +openEO could integrate DGGS in the following ways: + +1. **Mapping to raster grids (x and y)**: Expose the traditional spatial dimensions with axes x and y to the user, hiding DGGS while mapping DGGS zones to raster pixels. + - This feels most natural to users that are unaware of DGGS and familiar with raster data. The datacube would be a **raster data cube**. + - It would allow for use of all existing openEO processes, including neighborhood operations such as `aggregate_spatial_window` and `apply_kernel`, provided that backends implement the necessary logic. + - Hiding the specifics of DGGS also hides its advantages (e.g. global consistency, hierarchical aggregation, zone-based indexing). Operations based on the DGGS zones can't be applied. + - This implementation is complex and potentially inefficient for backends, as it requires solving all challenges internally. This may include conversion between DGGS and raster grids for operations that are problematic/undefined in DGGS (e.g., Gaussian blur vs. Distance-weighted k-ring). +2. **Geometry dimension**: Expose DGGS as a geometry dimension to the user. + - Each grid zone could be represented as a geometry (e.g. polygon or multi-polygon) with an associated DGGS zone ID. The datacube would be a **vector data cube**. + - Backends would need to keep track that the geometry dimension is based on a DGGS to efficiently apply operations that require knowledge of DGGS relationships such as adjacency and parent-child relation. Otherwise, this would need to be retrieved from the geometries themselves, which is likely to be less efficient. + - Processes don't distinguish between "normal" geometry dimensions and DGGS-based geometry dimensions, which means that special DGGS processes that would require knowledge of DGGS can't be defined unambiguously. Users may pass in any geometry dimension accidentally. + - Raster-based processes such as `apply_kernel`, `aggregate_spatial`, or `resample_spatial` can't be applied. Operations based on DGGS zones can be applied, e.g. `apply_dimension` and `reduce_dimension` along the geometry dimension. +3. **New dimension type**: Specify a new dimension type within openEO that specifically handles DGGS grid zones. + - This approach would be DGGS-native and provide all benefits of DGGS, but it will require the addition of processes that can handle the DGGS specific operations. + - It would allow the definition of new processes that directly operate on DGGS concepts, such as parent/child aggregation, k-ring neighborhood operations, or resolution changes without reprojection. It also allows that processes directly require the DGGS dimension type in process parameters and return values, which otherwise is impossible. + - Requires a new version of the openEO API and STAC datacube extension that adds the new dimension type. + - A set of new and adapted processes that explicitly support DGGS semantics must be specified. + +## Processes + +### Loading the datacube + +It could make sense for backends to offer multiple ways (e.g. 1 and 2) to load a DGGS dataset, giving all options to a user: + +- `load_collection` in general should load a data cube as it is defined by the collection metadata. + - If the collection metadata for a DGGS dataset exposes the dimensions *x, y, t, bands* for example, `load_collection` must return a raster datacube with exactly those dimensions. + + - If a backend decides to expose a DGGS dataset as *geometry, t, bands*, `load_collection` must return a vector datacube with exactly those dimensions. + +- An alternative process could be defined that allows to load the dataset with different dimension types. + +`load_collection` has to define zone inclusion rules, e.g. which zones to include and how to handle partial zones. + +### Other processes + +The following list discusses processes and their handling of DGGS. + +#### Raster data cubes + +The following processes require a datacube with x and y dimensions as input: + +- `apply_kernel` / `apply_neighborhood` / `aggregate_spatial_window`: **Problematic.** Can't be implemented for DGGS. +- `ard_...`, `atmospheric_correction`, `cloud_detection`, `sar_backscatter`, etc.: **Problematic.** These typically rely on established raster-based algorithms and libraries. Supporting DGGS would require either conversion to raster data cubes or re-implementation of the algorithms for DGGS, which may not be supported by the underlying software at all. +- `resample_spatial`: **Partially problematic.** The process is designed for raster data cubes and could be used to convert from DGGS to raster data cubes, but not vice-versa. A new process based on DGGS semantics is required if the datacube should keep the DGGS dimension. +- `resample_cube_spatial`: **Partially problematic.** Can be used for DGGS, but the `method` parameter is raster-centric. +- `aggregate_spatial`: Unproblematic. Must compute the values for the given geometries, which is unproblematic given the zone inclusion and weighting rules are well-defined. +- `filter_bbox` / `filter_spatial` / `mask_polygon`: Unproblematic. Processes have to define zone inclusion rules, e.g. which zones to include and how to handle partial zones. +- `mask`: Unproblematic when both data and mask are compatible (i.e. both have a DGGS-based dimension). If one of the parameters has a DGGS and the other parameter has raster dimensions, the mask needs to be converted. +- `ndvi`: Unproblematic as it's a per-pixel operation. +- `reduce_spatial`: Unproblematic. Process is not needed for DGGS, `reduce_dimension` along the DGGS dimension can be used instead. + +The process specifications may need to change to remove the restriction of raster data cubes as input and specific DGGS wording may need to be added. + +#### Vector data cubes + +The following processes require a datacube with geometry dimension as input: + +- `vector_buffer`: **Problematic.** Buffers can be computed around DGGS zone geometries, but the result is no longer a DGGS zone and breaks the discrete grid semantics. +- `vector_reproject`: **Problematic.** Reprojection of DGGS zone geometries is possible, but it may distort the original DGGS grid and invalidate zone IDs or hierarchical relationships. +- `filter_bbox` / `filter_spatial` / `filter_vector`: Unproblematic, but there's no filter process specific to filter based on DGGS zone IDs or other discrete grid semantics. +- `vector_to_random_points` / `vector_to_regular_points`: Unproblematic. These can generate points inside DGGS zone geometries. + +## Conclusion + +- The new DGGS dimension type is a clean approach, but requires significant changes in the specification. +- For the time being, DGGS can be handled using the two alternative methods, but each has their clear downsides. +- ... \ No newline at end of file diff --git a/proposals/aggregate_k_ring.json b/proposals/aggregate_k_ring.json new file mode 100644 index 00000000..b697fd06 --- /dev/null +++ b/proposals/aggregate_k_ring.json @@ -0,0 +1,121 @@ +{ + "id": "aggregate_k_ring", + "summary": "Aggregate values over DGGS k-ring neighborhoods", + "description": "Aggregates values in a DGGS data cube over topological k-ring neighborhoods.\n\nFor each DGGS zone, all zones within `k` adjacency steps in the same DGGS reference system (DGGRS) are selected and passed to the reducer.\n\nFor a raster-based alternative, see ``aggregate_spatial_window()``.", + "categories": [ + "cubes", + "aggregate", + "reducer", + "dggs" + ], + "experimental": true, + "parameters": [ + { + "name": "data", + "description": "A data cube with a DGGS dimension.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "k", + "description": "Neighborhood distance in topological adjacency steps.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "reducer", + "description": "A reducer applied to all neighborhood values for each zone.", + "schema": { + "type": "object", + "subtype": "process-graph", + "parameters": [ + { + "name": "data", + "description": "A labeled array of values from the k-ring neighborhood.", + "schema": { + "type": "array", + "subtype": "labeled-array", + "items": { + "description": "Any data type." + } + } + }, + { + "name": "context", + "description": "Additional data passed by the user.", + "schema": { + "description": "Any data type." + }, + "optional": true, + "default": null + } + ], + "returns": { + "description": "The aggregated value for the current DGGS zone.", + "schema": { + "description": "Any data type." + } + } + } + }, + { + "name": "context", + "description": "Additional data to be passed to the reducer.", + "schema": { + "description": "Any data type." + }, + "optional": true, + "default": null + } + ], + "returns": { + "description": "A DGGS data cube with aggregated values and the same dimensions. The dimension properties (name, type, labels, reference system and resolution) remain unchanged.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + "exceptions": { + "DimensionNotAvailable": { + "message": "A DGGS dimension with the specified name does not exist." + } + }, + "examples": [ + { + "description": "Aggregate each zone with the mean value of its first-ring neighborhood.", + "arguments": { + "data": { + "from_parameter": "data" + }, + "k": 1, + "reducer": { + "process_graph": { + "m": { + "process_id": "mean", + "arguments": { + "data": { + "from_parameter": "data" + } + }, + "result": true + } + } + } + } + } + ] +} diff --git a/proposals/aggregate_spatial_window.json b/proposals/aggregate_spatial_window.json index 7b0d198f..e4563f8f 100644 --- a/proposals/aggregate_spatial_window.json +++ b/proposals/aggregate_spatial_window.json @@ -1,7 +1,7 @@ { "id": "aggregate_spatial_window", "summary": "Zonal statistics for rectangular windows", - "description": "Aggregates statistics over the horizontal spatial dimensions (axes `x` and `y`) of the data cube.\n\nThe pixel grid for the axes `x` and `y` is divided into non-overlapping windows with the size specified in the parameter `size`. If the number of values for the axes `x` and `y` is not a multiple of the corresponding window size, the behavior specified in the parameters `boundary` and `align` is applied.\nFor each of these windows, the reducer process computes the result.", + "description": "Aggregates statistics over the horizontal spatial dimensions (axes `x` and `y`) of the data cube.\n\nThe pixel grid for the axes `x` and `y` is divided into non-overlapping windows with the size specified in the parameter `size`. If the number of values for the axes `x` and `y` is not a multiple of the corresponding window size, the behavior specified in the parameters `boundary` and `align` is applied.\nFor each of these windows, the reducer process computes the result.\n\nFor DGGS-native neighborhood aggregation, see ``aggregate_k_ring()``.", "categories": [ "cubes", "aggregate" diff --git a/proposals/apply_kernel_dggs.json b/proposals/apply_kernel_dggs.json new file mode 100644 index 00000000..f5e37b1e --- /dev/null +++ b/proposals/apply_kernel_dggs.json @@ -0,0 +1,72 @@ +{ + "id": "apply_kernel_dggs", + "summary": "Apply a weighted DGGS neighborhood kernel", + "description": "Applies a weighted focal operation on DGGS neighborhoods.\n\nFor each DGGS zone, the neighborhood is defined by topological ring distance in the same DGGS reference system (DGGRS). The weight at index `d` in `weights` is applied to all zones at ring distance `d` from the center zone.\n\nThis process is DGGS-native and provides a DGGS counterpart to ``apply_kernel()`` without relying on raster x/y kernels.", + "categories": [ + "cubes", + "math > image filter", + "dggs" + ], + "experimental": true, + "parameters": [ + { + "name": "data", + "description": "A data cube with a DGGS dimension.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "weights", + "description": "Kernel weights by topological ring distance. The first value is the center-zone weight (`d = 0`), the second value applies to all zones in ring `d = 1`, and so on.", + "schema": { + "type": "array", + "minItems": 1, + "items": { + "type": "number" + } + } + }, + { + "name": "factor", + "description": "A factor multiplied to each computed value after the weighted neighborhood sum.", + "schema": { + "type": "number" + }, + "default": 1, + "optional": true + }, + { + "name": "replace_invalid", + "description": "Value used to replace non-numerical, NaN, no-data, or infinite numerical values before applying the weighted sum.", + "schema": { + "type": "number" + }, + "default": 0, + "optional": true + } + ], + "returns": { + "description": "A DGGS data cube with the newly computed values and the same dimensions. The dimension properties (name, type, labels, reference system and resolution) remain unchanged.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + "exceptions": { + "DimensionNotAvailable": { + "message": "A DGGS dimension with the specified name does not exist." + } + } +} diff --git a/proposals/apply_neighborhood_dggs.json b/proposals/apply_neighborhood_dggs.json new file mode 100644 index 00000000..9172f3d3 --- /dev/null +++ b/proposals/apply_neighborhood_dggs.json @@ -0,0 +1,238 @@ +{ + "id": "apply_neighborhood_dggs", + "summary": "Apply a process to neighborhoods in a DGGS data cube", + "description": "Applies a focal process to a data cube with a DGGS dimension.\n\nA focal process is a process that works on a neighborhood of DGGS zones. The neighborhood can extend into the DGGS dimension and into additional dimensions, this extent is specified by the `size` argument. It is not only (part of) the size of the input neighborhood, but also the size of the output for a given position of the sliding neighborhood. Along the DGGS dimension, the neighborhood is defined by topological adjacency steps in the same DGGS reference system (DGGRS).\n\nAn overlap can be specified so that neighborhoods can have overlapping boundaries. This allows for continuity of the output. The overlap region must be included in the data cube returned by `process`, but any changed values in overlapping regions will be ignored. Along the DGGS dimension, the overlap extends the requested core neighborhood by additional k-rings. Requested adjacent zones that are not available in the original data cube are made available as no-data values in the sub-data cubes.\n\nThe neighborhood size should be kept small enough to avoid running beyond computational resources, but a too-small size will result in a larger number of process invocations, which may slow down processing.\n\nThis process is DGGS-native and is the topological counterpart of ``apply_neighborhood()`` for raster data cubes.", + "categories": [ + "cubes", + "dggs" + ], + "experimental": true, + "parameters": [ + { + "name": "data", + "description": "A data cube with a DGGS dimension.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "process", + "description": "Process to be applied on all neighborhoods.", + "schema": { + "type": "object", + "subtype": "process-graph", + "parameters": [ + { + "name": "data", + "description": "The input data, which is a subset of the data cube as specified in `size` and `overlap`. If the given size and overlap result in a one-dimensional data cube it is converted to a labeled array.", + "schema": [ + { + "title": "Multi-dimensional data", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + }, + { + "title": "One-dimensional data", + "type": "array", + "subtype": "labeled-array" + } + ] + }, + { + "name": "context", + "description": "Additional data passed by the user.", + "schema": { + "description": "Any data type." + }, + "optional": true, + "default": null + } + ], + "returns": { + "description": "A data cube with the newly computed values. The dimensionality must correspond to the input data. The dimension properties (name, type, labels, reference system and resolution) must remain unchanged. Otherwise, a `DataCubePropertiesImmutable` exception is thrown.", + "schema": [ + { + "title": "Multi-dimensional data", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + }, + { + "title": "One-dimensional data", + "type": "array" + } + ] + } + } + }, + { + "name": "size", + "description": "Neighborhood sizes along each dimension.\n\nThis object maps dimension names to either a physical measure (e.g. 100 m, 10 days) or k-rings (e.g. 3 k-rings). For the DGGS dimension, the unit must be `k` or `null`, in which case `k` is assumed. For dimensions not specified, the default is to provide all values. Be aware that including all values from overly large dimensions may not be processed at once.", + "schema": { + "type": "array", + "items": { + "type": "object", + "subtype": "chunk-size", + "required": [ + "dimension", + "value" + ], + "properties": { + "dimension": { + "type": "string" + }, + "value": { + "default": null, + "anyOf": [ + { + "type": "null", + "title": "All values" + }, + { + "type": "number", + "minimum": 0, + "description": "See the `unit` parameter for more information." + }, + { + "type": "string", + "subtype": "duration", + "description": "[ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations), e.g. `P1D` for one day.", + "pattern": "^(-?)P(?=\\d|T\\d)(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)([DW]))?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+(?:\\.\\d+)?)S)?)?$" + } + ] + }, + "unit": { + "description": "The unit the values are given in, either in meters (`m`) or k-rings (`k`). For the DGGS dimension, `k` counts topological adjacency steps and `null` defaults to `k`. For other dimensions, `null` uses the unit specified for the dimension or otherwise the default unit of the reference system.", + "default": null, + "anyOf": [ + { + "title": "Default unit", + "type": "null" + }, + { + "title": "Specific unit", + "type": "string", + "enum": [ + "k", + "m" + ] + } + ] + } + } + } + } + }, + { + "name": "overlap", + "description": "Overlap of neighborhoods along each dimension to avoid edge effects. By default no overlap is provided.\n\nFor instance a temporal dimension can add 1 month before and after a neighborhood. For the DGGS dimension, overlap is given in k-rings and extends the requested core neighborhood by the given number of additional rings. Values returned for these overlap rings must be included in the sub-data cube returned by `process`, but changes to them are ignored when merging the result back into the output data cube.\n\nBe aware that large overlaps increase the need for computational resources and modifying overlapping data in subsequent operations has no effect.", + "optional": true, + "default": null, + "schema": [ + { + "title": "Without overlap", + "type": "null" + }, + { + "title": "With overlap", + "type": "array", + "items": { + "type": "object", + "subtype": "chunk-size", + "required": [ + "dimension", + "value" + ], + "properties": { + "dimension": { + "type": "string" + }, + "value": { + "default": null, + "anyOf": [ + { + "type": "null", + "title": "No values" + }, + { + "type": "number", + "minimum": 0, + "description": "See the `unit` parameter for more information." + }, + { + "type": "string", + "subtype": "duration", + "description": "[ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations), e.g. `P1D` for one day.", + "pattern": "^(-?)P(?=\\d|T\\d)(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)([DW]))?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+(?:\\.\\d+)?)S)?)?$" + } + ] + }, + "unit": { + "description": "The unit the values are given in, either in meters (`m`) or k-rings (`k`). For the DGGS dimension, `k` counts topological adjacency steps and `null` defaults to `k`. For other dimensions, `null` uses the unit specified for the dimension or otherwise the default unit of the reference system.", + "default": null, + "anyOf": [ + { + "title": "Default unit", + "type": "null" + }, + { + "title": "Specific unit", + "type": "string", + "enum": [ + "k", + "m" + ] + } + ] + } + } + } + } + ] + }, + { + "name": "context", + "description": "Additional data to be passed to the process.", + "schema": { + "description": "Any data type." + }, + "optional": true, + "default": null + } + ], + "returns": { + "description": "A DGGS data cube with the newly computed values and the same dimensions. The dimension properties (name, type, labels, reference system and resolution) remain unchanged.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + "exceptions": { + "DimensionNotAvailable": { + "message": "A DGGS dimension with the specified name does not exist." + }, + "DataCubePropertiesImmutable": { + "message": "The dimension properties (name, type, labels, reference system and resolution) must remain unchanged." + } + } +} diff --git a/proposals/filter_k_ring.json b/proposals/filter_k_ring.json new file mode 100644 index 00000000..ebd83105 --- /dev/null +++ b/proposals/filter_k_ring.json @@ -0,0 +1,80 @@ +{ + "id": "filter_k_ring", + "summary": "Select DGGS zones within a k-ring neighborhood", + "description": "Selects DGGS zones from a data cube based on topological neighborhood distance in a DGGS.\n\nStarting from one or more source zone identifiers, all zones are selected that are reachable within `k` adjacency steps in the same DGGS reference system (DGGRS).\n\nIt is useful for neighborhood operations such as local smoothing, context expansion, and region growing on DGGS-based data cubes.", + "categories": [ + "cubes", + "filter", + "dggs" + ], + "experimental": true, + "parameters": [ + { + "name": "data", + "description": "A data cube with a DGGS dimension.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "zones", + "description": "One or more DGGS zone identifiers used as neighborhood seeds.", + "schema": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + }, + { + "name": "k", + "description": "Neighborhood distance in topological adjacency steps. `0` selects only the source zones.", + "schema": { + "type": "integer", + "minimum": 0 + } + } + ], + "returns": { + "description": "A data cube with the same dimensions and properties as the input, restricted to the selected DGGS zones.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + "exceptions": { + "DimensionNotAvailable": { + "message": "A DGGS dimension with the specified name does not exist." + }, + "ZoneNotAvailable": { + "message": "At least one of the source zone identifiers is not available in the data cube." + } + }, + "examples": [ + { + "description": "Select all zones within two adjacency steps around zone C1A.", + "arguments": { + "data": { + "from_parameter": "data" + }, + "zones": [ + "C1A" + ], + "k": 2 + } + } + ] +} + diff --git a/proposals/load_stac.json b/proposals/load_stac.json index 34c91e85..a434c476 100644 --- a/proposals/load_stac.json +++ b/proposals/load_stac.json @@ -21,7 +21,7 @@ }, { "name": "spatial_extent", - "description": "Limits the data to load to the specified bounding box or polygons.\n\n* For 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).\n* For 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\nThe GeoJSON can be one of the following feature types:\n\n* A `Polygon` or `MultiPolygon` geometry,\n* a `Feature` with a `Polygon` or `MultiPolygon` geometry, or\n* a `FeatureCollection` containing at least one `Feature`; where each `Feature` has a `Polygon` or `MultiPolygon` geometry.\n\nSet this parameter to `null` to set no limit for the spatial extent. Be careful with this when loading large datasets! It is recommended to use this parameter instead of using ``filter_bbox()`` or ``filter_spatial()`` directly after loading unbounded data.", + "description": "Limits the data to load to the specified bounding box or polygons.\n\n* For raster or DGGS data, the process loads the value into the data cube if the pixel or DGGS zone centroid intersects with the bounding box or any of the polygons (as defined in the Simple Features standard by the OGC).\n* For 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\nThe GeoJSON can be one of the following feature types:\n\n* A `Polygon` or `MultiPolygon` geometry,\n* a `Feature` with a `Polygon` or `MultiPolygon` geometry, or\n* a `FeatureCollection` containing at least one `Feature`; where each `Feature` has a `Polygon` or `MultiPolygon` geometry.\n\nSet this parameter to `null` to set no limit for the spatial extent. Be careful with this when loading large datasets! It is recommended to use this parameter instead of using ``filter_bbox()`` or ``filter_spatial()`` directly after loading unbounded data.", "schema": [ { "title": "Bounding Box", @@ -90,13 +90,13 @@ }, { "title": "GeoJSON", - "description": "Limits the data cube to the bounding box of the given geometries. For raster data, all pixels inside the bounding box that do not intersect with any of the polygons will be set to the no-data value of the data cube.\n\nThe GeoJSON type `GeometryCollection` is not supported. Empty geometries are ignored.", + "description": "Limits the data cube to the bounding box of the given geometries. For raster or DGGS data, all pixels or DGGS zones with their centroids inside the bounding box that do not intersect with any of the polygons will be set to the no-data value of the data cube.\n\nThe GeoJSON type `GeometryCollection` is not supported. Empty geometries are ignored.", "type": "object", "subtype": "geojson" }, { "title": "Vector data cube", - "description": "Limits the data cube to the bounding box of the given geometries in the vector data cube. All pixels inside the bounding box that do not intersect with any of the polygons will be set to the no-data value of the data cube. Empty geometries are ignored.", + "description": "Limits the data cube to the bounding box of the given geometries in the vector data cube. For raster or DGGS data, all pixels or DGGS zones with their centroids inside the bounding box that do not intersect with any of the polygons will be set to the no-data value of the data cube. Empty geometries are ignored.", "type": "object", "subtype": "datacube", "dimensions": [ diff --git a/proposals/mask_dggs.json b/proposals/mask_dggs.json new file mode 100644 index 00000000..90299f20 --- /dev/null +++ b/proposals/mask_dggs.json @@ -0,0 +1,80 @@ +{ + "id": "mask_dggs", + "summary": "Apply a DGGS mask", + "description": "Applies a mask to a DGGS data cube.\n\nA mask is a DGGS data cube for which corresponding zones among `data` and `mask` are compared and those zones in `data` are replaced whose values in `mask` are non-zero (for numbers) or `true` (for boolean values). This behavior can be inverted by setting the parameter `inside` to `true`. The zone values are replaced with the value specified for `replacement`, which defaults to the no-data value of the data cube. No-data values in `data` are left untouched by the masking operation.", + "categories": [ + "cubes", + "masks", + "dggs" + ], + "experimental": true, + "parameters": [ + { + "name": "data", + "description": "A data cube with a DGGS dimension.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "mask", + "description": "A DGGS data cube containing the mask values. Every DGGS zone in `data` must have a corresponding zone in `mask`.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "replacement", + "description": "The value used to replace masked values with. `null` refers to the no-data value of the data cube provided for `data`.", + "schema": [ + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "optional": true + }, + { + "name": "inside", + "description": "If set to `true`, zones for which the corresponding value in `mask` is zero (for numbers) or `false` (for boolean values) are replaced.", + "schema": { + "type": "boolean" + }, + "default": false, + "optional": true + } + ], + "returns": { + "description": "A masked DGGS data cube with the same dimensions. The dimension properties (name, type, labels, reference system and resolution) remain unchanged.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + } +} diff --git a/proposals/reduce_spatial.json b/proposals/reduce_spatial.json index d9233cf7..c5c21475 100644 --- a/proposals/reduce_spatial.json +++ b/proposals/reduce_spatial.json @@ -1,7 +1,7 @@ { "id": "reduce_spatial", "summary": "Reduce spatial dimensions 'x' and 'y'", - "description": "Applies a reducer to a data cube by collapsing all the pixel values along the horizontal spatial dimensions (i.e. axes `x` and `y`) into an output value computed by the reducer. The horizontal spatial dimensions are dropped.\n\nAn aggregation over certain spatial areas can be computed with the process ``aggregate_spatial()``.\n\nThis process passes a list of values to the reducer. The list of values has an undefined order, therefore processes such as ``last()`` and ``first()`` that depend on the order of the values will lead to unpredictable results.", + "description": "Applies a reducer to a data cube by collapsing all the pixel values along the horizontal spatial dimensions (i.e. axes `x` and `y`) into an output value computed by the reducer. The horizontal spatial dimensions are dropped.\n\nAn aggregation over certain spatial areas can be computed with the process ``aggregate_spatial()``.\n\nThis process passes a list of values to the reducer. The list of values has an undefined order, therefore processes such as ``last()`` and ``first()`` that depend on the order of the values will lead to unpredictable results.\n\nFor data cubes with a DGGS dimension, use ``reduce_dimension()`` along the DGGS dimension instead.", "categories": [ "aggregate", "cubes", diff --git a/proposals/resample_cube_dggs.json b/proposals/resample_cube_dggs.json new file mode 100644 index 00000000..33c887ff --- /dev/null +++ b/proposals/resample_cube_dggs.json @@ -0,0 +1,110 @@ +{ + "id": "resample_cube_dggs", + "summary": "Align one DGGS data cube to another", + "description": "Aligns the DGGS dimension of a source data cube to the DGGS dimension of a target data cube without reprojection.\n\nThis process is intended for DGGS-native cube alignment when both cubes use the same DGGS reference system (DGGRS) and the alignment makes sense in terms of zone hierarchy and target resolution.", + "categories": [ + "cubes", + "reproject", + "dggs" + ], + "experimental": true, + "parameters": [ + { + "name": "data", + "description": "A data cube with a DGGS dimension.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "target", + "description": "A target data cube with a DGGS dimension that defines the desired DGGS resolution and zone alignment.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "method", + "description": "Alignment method used to map values between DGGS zones. The default `near` preserves the closest matching zone semantics. Alternatively, a custom reducer applied to all child zone values when downsampling.", + "schema": [ + { + "type": "string", + "enum": [ + "near", + "average", + "mode", + "sum", + "min", + "max" + ] + }, + { + "type": "object", + "subtype": "process-graph", + "parameters": [ + { + "name": "data", + "description": "A labeled array of values from child zones.", + "schema": { + "type": "array", + "subtype": "labeled-array", + "items": { + "description": "Any data type." + } + } + }, + { + "name": "context", + "description": "Additional data passed by the user.", + "schema": { + "description": "Any data type." + }, + "optional": true, + "default": null + } + ], + "returns": { + "description": "The value to assign to the parent DGGS zone.", + "schema": { + "description": "Any data type." + } + } + } + ], + "optional": true, + "default": "near" + } + ], + "returns": { + "description": "A data cube with the same dimensions as the input. The DGGS dimension keeps its name and type, while labels are aligned to the target DGGS cube.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + "exceptions": { + "DimensionNotAvailable": { + "message": "A DGGS dimension with the specified name does not exist." + }, + "IncompatibleDataCubes": { + "message": "The data cube and the target data cube are not compatible for DGGS alignment." + } + } +} diff --git a/proposals/resample_dggs.json b/proposals/resample_dggs.json new file mode 100644 index 00000000..7498863c --- /dev/null +++ b/proposals/resample_dggs.json @@ -0,0 +1,119 @@ +{ + "id": "resample_dggs", + "summary": "Resample a DGGS dimension to another DGGS resolution", + "description": "Resamples a DGGS dimension to a target DGGS resolution level without reprojection.\n\nThis process is intended for DGGS-native resolution changes and relies on zone hierarchy mappings defined by the DGGS reference system (DGGRS). It can be used for both upsampling and downsampling, depending on the selected method. For downsampling to a coarser DGGS resolution, a custom reducer can be provided to aggregate child zone values to their parent zones.", + "categories": [ + "cubes", + "reproject", + "aggregate", + "reducer", + "dggs" + ], + "experimental": true, + "parameters": [ + { + "name": "data", + "description": "A data cube with a DGGS dimension.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + { + "name": "resolution", + "description": "Target DGGS resolution level.", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "method", + "description": "Alignment method used to map values between DGGS zones. The default `near` preserves the closest matching zone semantics. Alternatively, a custom reducer applied to all child zone values when downsampling.", + "schema": [ + { + "type": "string", + "enum": [ + "near", + "average", + "mode", + "sum", + "min", + "max" + ] + }, + { + "type": "object", + "subtype": "process-graph", + "parameters": [ + { + "name": "data", + "description": "A labeled array of values from child zones.", + "schema": { + "type": "array", + "subtype": "labeled-array", + "items": { + "description": "Any data type." + } + } + }, + { + "name": "context", + "description": "Additional data passed by the user.", + "schema": { + "description": "Any data type." + }, + "optional": true, + "default": null + } + ], + "returns": { + "description": "The value to assign to the parent DGGS zone.", + "schema": { + "description": "Any data type." + } + } + } + ], + "optional": true, + "default": "near" + }, + { + "name": "context", + "description": "Additional data to be passed to the custom reducer.", + "schema": { + "description": "Any data type." + }, + "optional": true, + "default": null + } + ], + "returns": { + "description": "A data cube with the same dimensions as the input. The DGGS dimension keeps its name and type, while labels and resolution may change depending on the given parameters.", + "schema": { + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + }, + "exceptions": { + "DimensionNotAvailable": { + "message": "A DGGS dimension with the specified name does not exist." + }, + "ResolutionMismatch": { + "message": "The target DGGS resolution is incompatible with the input DGGS resolution levels." + }, + "ReducerUnsupported": { + "message": "A custom `reducer` is only supported when resampling to a coarser DGGS resolution." + } + } +} diff --git a/resample_cube_spatial.json b/resample_cube_spatial.json index 4da4e595..f38e442f 100644 --- a/resample_cube_spatial.json +++ b/resample_cube_spatial.json @@ -1,7 +1,7 @@ { "id": "resample_cube_spatial", "summary": "Resample the spatial dimensions to match a target data cube", - "description": "Resamples the spatial dimensions (x,y) from a source data cube to align with the corresponding dimensions of the given target data cube. Returns a new data cube with the resampled dimensions.\n\nTo resample a data cube to a specific resolution or projection regardless of an existing target data cube, refer to ``resample_spatial()``.", + "description": "Resamples the spatial dimensions (x,y) from a source data cube to align with the corresponding dimensions of the given target data cube. Returns a new data cube with the resampled dimensions.\n\nTo resample a data cube to a specific resolution or projection regardless of an existing target data cube, refer to ``resample_spatial()``. To resample DGGS data cubes towards a target DGGS data cube, see ``resample_cube_dggs()``.", "categories": [ "cubes", "reproject" diff --git a/resample_spatial.json b/resample_spatial.json index 705cba22..0add8bd4 100644 --- a/resample_spatial.json +++ b/resample_spatial.json @@ -1,28 +1,42 @@ { "id": "resample_spatial", "summary": "Resample and warp the spatial dimensions", - "description": "Resamples the spatial dimensions (x,y) of the data cube to a specified resolution and/or warps the data cube to the target projection. At least `resolution` or `projection` must be specified.\n\nRelated processes:\n\n* Use ``filter_bbox()`` to set the target spatial extent.\n* To spatially align two data cubes with each other (e.g. for merging), better use the process ``resample_cube_spatial()``.", + "description": "Resamples the spatial dimensions (x,y) of the data cube to a specified resolution and/or warps the data cube to the target projection. At least `resolution` or `projection` must be specified.\n\nThis process is raster-specific, but it can also be used to convert a DGGS data cube to a raster data cube when a suitable target projection and resampling method is provided. For DGGS-native resolution changes without reprojection, see ``resample_dggs()``.\n\nRelated processes:\n\n* Use ``filter_bbox()`` to set the target spatial extent.\n* To spatially align two raster data cubes with each other (e.g. for merging), better use the process ``resample_cube_spatial()``.\n* For DGGS-to-DGGS alignment, see ``resample_cube_dggs()``.", "categories": [ "cubes", - "reproject" + "reproject", + "dggs" ], "parameters": [ { "name": "data", - "description": "A raster data cube.", - "schema": { - "type": "object", - "subtype": "datacube", - "dimensions": [ - { - "type": "spatial", - "axis": [ - "x", - "y" - ] - } - ] - } + "description": "A data cube. Raster input is resampled as usual.\n\nDGGS input is only meaningful when it can be converted to a raster data cube in the target projection.", + "schema": [ + { + "title": "Raster data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "spatial", + "axis": [ + "x", + "y" + ] + } + ] + }, + { + "title": "DGGS data cube", + "type": "object", + "subtype": "datacube", + "dimensions": [ + { + "type": "dggs" + } + ] + } + ] }, { "name": "resolution", diff --git a/tests/aggregate_k_ring.json5 b/tests/aggregate_k_ring.json5 new file mode 100644 index 00000000..c78cca2e --- /dev/null +++ b/tests/aggregate_k_ring.json5 @@ -0,0 +1,6 @@ +{ + "id": "aggregate_k_ring", + "level": "L3-DGGS", + "experimental": true, + "tests": [] +} diff --git a/tests/apply_kernel_dggs.json5 b/tests/apply_kernel_dggs.json5 new file mode 100644 index 00000000..0da1f2e8 --- /dev/null +++ b/tests/apply_kernel_dggs.json5 @@ -0,0 +1,6 @@ +{ + "id": "apply_kernel_dggs", + "level": "L3-DGGS", + "experimental": true, + "tests": [] +} diff --git a/tests/apply_neighborhood_dggs.json5 b/tests/apply_neighborhood_dggs.json5 new file mode 100644 index 00000000..18d89f97 --- /dev/null +++ b/tests/apply_neighborhood_dggs.json5 @@ -0,0 +1,6 @@ +{ + "id": "apply_neighborhood_dggs", + "level": "L3-DGGS", + "experimental": true, + "tests": [] +} diff --git a/tests/filter_k_ring.json5 b/tests/filter_k_ring.json5 new file mode 100644 index 00000000..2dd7c6ae --- /dev/null +++ b/tests/filter_k_ring.json5 @@ -0,0 +1,6 @@ +{ + "id": "filter_k_ring", + "level": "L3-DGGS", + "experimental": true, + "tests": [] +} diff --git a/tests/mask_dggs.json5 b/tests/mask_dggs.json5 new file mode 100644 index 00000000..cc157704 --- /dev/null +++ b/tests/mask_dggs.json5 @@ -0,0 +1,6 @@ +{ + "id": "mask_dggs", + "level": "L3-DGGS", + "experimental": true, + "tests": [] +} diff --git a/tests/resample_cube_dggs.json5 b/tests/resample_cube_dggs.json5 new file mode 100644 index 00000000..c1d81692 --- /dev/null +++ b/tests/resample_cube_dggs.json5 @@ -0,0 +1,6 @@ +{ + "id": "resample_cube_dggs", + "level": "L3-DGGS", + "experimental": true, + "tests": [] +} diff --git a/tests/resample_dggs.json5 b/tests/resample_dggs.json5 new file mode 100644 index 00000000..80a0e079 --- /dev/null +++ b/tests/resample_dggs.json5 @@ -0,0 +1,6 @@ +{ + "id": "resample_dggs", + "level": "L3-DGGS", + "experimental": true, + "tests": [] +} diff --git a/tests/schema/schema.json b/tests/schema/schema.json index fc96c084..64eeea09 100644 --- a/tests/schema/schema.json +++ b/tests/schema/schema.json @@ -33,6 +33,7 @@ "L3-UDF", "L3-Clim", "L3-ARD", + "L3-DGGS", "L4" ] }, @@ -117,6 +118,7 @@ "L3-UDF", "L3-Clim", "L3-ARD", + "L3-DGGS", "L4", null ] @@ -125,4 +127,4 @@ } } } -} \ No newline at end of file +}