feat: add land use for sealed areas as process - #20
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new backend OGC API process for calculating/reporting “land-use for sealed areas” and wires it into the service so it is discoverable/executable via the existing processes framework.
Changes:
- Added a new
LandUseSealedAreaProcess(schema, execute wiring, documentation, compute/types scaffolding) and registered it in the server and auth allowlist. - Refactored process parameter types by splitting GeoJSON and Data Resource helpers into dedicated modules and adding input/output-spec helpers.
- Added a utility helper to validate/select requested output keys.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/src/server.rs | Registers the new land-use sealed area process in the processor list. |
| backend/src/processes/util.rs | Adds to_output_keys helper for validating requested outputs. |
| backend/src/processes/parameters/mod.rs | Refactors parameter types (new submodules, adds area helpers and input/output description helpers). |
| backend/src/processes/parameters/geo_json.rs | Extracts GeoJSON input types and tests into a standalone module. |
| backend/src/processes/parameters/data_resource.rs | Extracts Data Resource types and to_input_value conversion into a standalone module. |
| backend/src/processes/mod.rs | Adds the new land_use_sealed_area module and exports the process type. |
| backend/src/processes/land_use_sealed_area/types.rs | Defines land-use domain/output types and Data Resource builders for summary/site tables. |
| backend/src/processes/land_use_sealed_area/mod.rs | Implements the new process’ OGC API schema + execution plumbing and output selection. |
| backend/src/processes/land_use_sealed_area/description.md | Adds end-user documentation for the new process. |
| backend/src/processes/land_use_sealed_area/compute.rs | Adds computation scaffolding and summary aggregation logic. |
| backend/src/auth.rs | Adds the new process route to the auth allowlist. |
dc4cf36 to
e67a8f6
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (7)
backend/src/processes/land_use_sealed_area/types.rs:23
LandUseSummaryRowOutputis missing#[serde(rename_all = "camelCase")], so its JSON fields will beprevious_year/reporting_year/percentage_changeinstead of matching the DataResource schema (previousYear,reportingYear,percentageChange). Also the DataResource schema defines alandUseTypecolumn and primary key, but the output row type currently has no corresponding field, so produced data cannot conform to its declared schema.
#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)]
pub struct LandUseSummaryRowOutput {
/// Previous year (if available)
pub previous_year: Option<Area>,
/// Reporting year
backend/src/processes/land_use_sealed_area/types.rs:182
summary_to_data_resourcedeclares alandUseTypecolumn (and uses it as the primary key), but thedatarows are currently created without any land-use type value, so the generated table cannot satisfy its own schema/primary key.
data: vec![
land_use_summary_row_to_output(site_rows.total_sealed_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_on_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_off_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_use_of_land, unit_for_area),
backend/src/processes/land_use_sealed_area/types.rs:225
- Unnecessary clone:
site_land_use_row_to_outputtakes ownership ofSiteLandUseRow, so cloningrowhere adds avoidable allocations/copies for every row.
.into_iter()
.map(|row| site_land_use_row_to_output(row.clone(), unit_for_area))
.collect(),
backend/src/processes/land_use_sealed_area/types.rs:138
- The documentation describes the on-site site type as
onSite, butSiteSpecification::from_stronly acceptssite(after lowercasing). As a result, inputs using the documentedonSitevalue would be rejected.
match s.to_lowercase().as_str() {
"site" => Ok(SiteSpecification::Site),
"natureonsite" => Ok(SiteSpecification::NatureOnSite),
"natureoffsite" => Ok(SiteSpecification::NatureOffSite),
other => Err(format!("Unrecognized site specification `{other}`")),
backend/src/processes/land_use_sealed_area/mod.rs:58
previous_year_datais documented as optional, but it is not anOption<...>, so it will be treated as a required input in generated OpenAPI schemas. Making it optional also simplifies distinguishing between "not provided" and "provided but empty".
/// Optional: Site data from the previous reporting period for year-over-year comparison.
#[serde(default, skip_serializing_if = "PreviousLandUseSummary::is_empty")]
pub previous_year_data: PreviousLandUseSummary,
backend/src/processes/land_use_sealed_area/mod.rs:203
- The OpenAPI input description for
previousYearDatacurrently advertises a GeoJSON FeatureCollection (Option<FeatureCollectionGeoJsonInput>), but the actual input type is (and should be)PreviousLandUseSummary(land-use values). This mismatch will mislead API clients and likely cause deserialization errors.
InputSpec {
key: input_keys::PREVIOUS_YEAR_DATA,
title: "Previous Year Data (Optional)",
description: "GeoJSON FeatureCollection from previous reporting period for comparison.",
r#type: generator.root_schema_for::<Option<FeatureCollectionGeoJsonInput>>(),
},
backend/src/processes/land_use_sealed_area/compute.rs:23
compute_site_land_use_datais currently a stub that always returns empty results and no errors. Since the process is registered and callable, this will produce misleading outputs (e.g., 0 areas) while appearing successful. Consider failing fast until the computation is implemented.
pub async fn compute_site_land_use_data(
sites: &FeatureCollectionGeoJsonInput,
_location_name_field: &str,
_location_type_field: &str,
_unit_for_area: UnitForArea,
) -> anyhow::Result<(Vec<SiteLandUseRow>, Vec<String>)> {
let site_rows = Vec::new();
let errors = Vec::new();
e67a8f6 to
ade6f8e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 19 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (7)
backend/src/processes/land_use_sealed_area/types.rs:23
LandUseSummaryRowOutputcurrently serializes with snake_case field names and has nolandUseTypefield, but the DataResource schema below expectslandUseType,previousYear,reportingYear, etc. This will produce mismatched output and makes the summary rows indistinguishable. Consider adding#[serde(rename_all = "camelCase")]and aland_use_typefield that is populated when building the summary table.
#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)]
pub struct LandUseSummaryRowOutput {
/// Previous year (if available)
pub previous_year: Option<Area>,
/// Reporting year
backend/src/processes/land_use_sealed_area/types.rs:189
- The
summary_to_data_resourceschema includes alandUseTypecolumn (and uses it as primary key), but thedatarows are built fromLandUseSummaryRowOutputwithout providing any land-use type value. Populate the land-use type for each row so consumers can interpret the summary table and it matches the declared schema.
data: vec![
land_use_summary_row_to_output(site_rows.total_sealed_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_on_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_off_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_use_of_land, unit_for_area),
backend/src/processes/land_use_sealed_area/types.rs:232
- No need to clone
SiteLandUseRowhere since it is already moved byinto_iter(). Removing the clone avoids an unnecessary allocation/copy and keeps the code simpler.
data: site_rows
.into_iter()
.map(|row| site_land_use_row_to_output(row.clone(), unit_for_area))
.collect(),
backend/src/processes/land_use_sealed_area/mod.rs:572
- This test JSON is missing the required
yearfield (LandUseSealedAreaProcessInputs.year: Year), so deserialization should fail. Add ayear(and optionallypreviousYearData) to match the current inputs schema.
"locationNameField": "location",
"siteTypeField": "siteType",
"unitForArea": "m²"
});
backend/src/processes/land_use_sealed_area/mod.rs:233
- The
previousYearDatainput is described as a "GeoJSON FeatureCollection", but the actual type is aPreviousLandUseSummary(numeric totals). Update the description to avoid misleading API docs/clients.
key: input_keys::PREVIOUS_YEAR_DATA,
title: "Previous Year Data (Optional)",
description: "GeoJSON FeatureCollection from previous reporting period for comparison.",
metadata: vec![],
r#type: generator.root_schema_for::<Option<PreviousLandUseSummary>>(),
backend/src/processes/land_use_sealed_area/description.md:37
- The documentation lists
onSiteas a valid site type, but the implementation (and examples) usesite/natureOnSite/natureOffSite. This mismatch will cause client confusion and input-validation errors.
Reference to the property in the input `GeoJSON` features that identifies the land-use type:
- `onSite`: Area located on the organization's site
- `natureOnSite`: Nature-oriented areas located on the organization's site
- `natureOffSite`: Nature-oriented areas owned or managed by the organization but located off-site
backend/src/processes/parameters/mod.rs:141
Fields.primary_keyis set to"name", but the schema only definesdataanddocumentation_sourcefields, so the primary key refers to a non-existent column. This makes the emitted DataResource schema inconsistent/invalid for consumers.
],
primary_key: vec!["name".to_string()].into(),
},
ade6f8e to
27ff794
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (6)
backend/src/processes/land_use_sealed_area/types.rs:24
LandUseSummaryRowOutputcurrently serializes with snake_case field names and does not include alandUseTypefield, butsummary_to_data_resourcedeclares alandUseTypecolumn (and primary key) in the DataResource schema. This makes the output table inconsistent with its own schema and ambiguous for consumers.
#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)]
pub struct LandUseSummaryRowOutput {
/// Previous year (if available)
pub previous_year: Option<Area>,
/// Reporting year
backend/src/processes/land_use_sealed_area/types.rs:240
- Avoid cloning
SiteLandUseRowwhile mapping; the iterator already yields owned rows, andsite_land_use_row_to_outputtakes ownership. The current.clone()adds unnecessary allocations/copies for every row.
data: site_rows
.into_iter()
.map(|row| site_land_use_row_to_output(row.clone(), unit_for_area))
.collect(),
backend/src/processes/land_use_sealed_area/description.md:32
- The markdown documentation claims the sites’ geometry can be
Pointand uses anonSiteland-use type, but the implementation only accepts Polygon/MultiPolygon and recognizessite/natureOnSite/natureOffSite. Please align the docs with the actual accepted inputs to avoid confusing API consumers.
- `geometry`: The geographical coordinates of the area, which can be a `Point` or `Polygon`. Should use the `EPSG:4326` (WGS 84, latitude/longitude) coordinate reference system.
- `properties`: A JSON object containing fields for location identification and land-use type classification.
### Land-Use Type Field
backend/src/processes/land_use_sealed_area/types.rs:197
summary_to_data_resourcebuilds the summary rows without supplying a value for the schema’slandUseTypecolumn. After addingLandUseSummaryRowOutput.landUseType, populate it here so each row is identifiable and matches the declared primary key.
data: vec![
land_use_summary_row_to_output(site_rows.total_sealed_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_on_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_off_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_use_of_land, unit_for_area),
backend/src/processes/land_use_sealed_area/mod.rs:237
- The process description advertises
previousYearDataas anOption<PreviousLandUseSummary>, butLandUseSealedAreaProcessInputsactually expects aJsonInput<PreviousLandUseSummary>object with{ value, mediaType }(seeprevious_year_datafield and the unit test). This mismatch will mislead clients generated from the process schema and can cause deserialization failures.
key: input_keys::PREVIOUS_YEAR_DATA,
title: "Previous Year Data (Optional)",
description: "GeoJSON FeatureCollection from previous reporting period for comparison.",
metadata: vec![],
r#type: generator.root_schema_for::<Option<PreviousLandUseSummary>>(),
backend/src/processes/parameters/mod.rs:141
DocumentationSourcerows only containdataanddocumentationSource, but the Table Schema setsprimary_keytoname, which is not a column in the data. This makes the DataResource schema invalid/inconsistent for consumers that validate against it.
..Default::default()
},
],
primary_key: vec!["name".to_string()].into(),
},
| diesel-derive-enum = { version = "3.0.0-beta.1", features = ["postgres"] } | ||
| futures = "0.3" | ||
| geo-types = "0.7" | ||
| geoengine-api-client = { git = "https://github.com/geo-engine/geoengine", branch = "feat/vector-expression-api-model" } |
27ff794 to
36ce13a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (8)
backend/src/processes/land_use_sealed_area/types.rs:23
LandUseSummaryRowOutputcurrently serializes with snake_case keys and has nolandUseTypefield, butsummary_to_data_resourcedeclares a table schema withlandUseType/previousYear/reportingYear/percentageChange. This makes the output not conform to the declared schema and also makes summary rows indistinguishable.
#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)]
pub struct LandUseSummaryRowOutput {
/// Previous year (if available)
pub previous_year: Option<Area>,
/// Reporting year
backend/src/processes/land_use_sealed_area/types.rs:225
summary_to_data_resourcebuilds rows without any land-use type identifier, even though the schema declares alandUseTypecolumn (and marks it as the primary key). After addingland_use_typetoLandUseSummaryRowOutput, pass a stable identifier for each row here.
data: vec![
land_use_summary_row_to_output(site_rows.total_sealed_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_on_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_nature_off_site_area, unit_for_area),
land_use_summary_row_to_output(site_rows.total_use_of_land, unit_for_area),
backend/src/processes/land_use_sealed_area/mod.rs:230
- The OpenAPI input spec for
previousYearDatadoes not match the actual input type (Option<JsonInput<PreviousLandUseSummary>>) and the description incorrectly calls it a GeoJSON FeatureCollection. This will mislead clients and generate an incorrect schema.
key: input_keys::PREVIOUS_YEAR_DATA,
title: "Previous Year Data (Optional)",
description: "GeoJSON FeatureCollection from previous reporting period for comparison.",
metadata: vec![],
r#type: generator.root_schema_for::<Option<PreviousLandUseSummary>>(),
backend/src/processes/land_use_sealed_area/description.md:37
- The docs list
onSiteas an accepted land-use type value, but the code (and error message) expectssite/natureOnSite/natureOffSite. This is likely to cause client confusion and validation errors.
- `onSite`: Area located on the organization's site
- `natureOnSite`: Nature-oriented areas located on the organization's site
- `natureOffSite`: Nature-oriented areas owned or managed by the organization but located off-site
backend/src/processes/land_use_sealed_area/types.rs:267
- This
clone()is unnecessary becauserowis already moved into the closure byinto_iter(). Removing it avoids extra allocations/copies.
.map(|row| site_land_use_row_to_output(row.clone(), unit_for_area))
backend/src/processes/land_use_sealed_area/description.md:28
- The docs say each feature
geometrycan be aPointorPolygon, but the implementation only acceptsPolygon/MultiPolygon(and the uploaded dataset is declared asMultiPolygon). Update the documentation to match what the process actually supports.
- `geometry`: The geographical coordinates of the area, which can be a `Point` or `Polygon`. Should use the `EPSG:4326` (WGS 84, latitude/longitude) coordinate reference system.
backend/src/processes/land_use_sealed_area/compute.rs:330
sealed_area_processtakesbbox: BoundingBoxbut then immediately shadows it with anotherbboxand never uses the parameter. This triggers an unused-variable warning and makes the signature misleading.
bbox: BoundingBox,
backend/src/util.rs:390
- Test name contains a typo (
andspawns), which makes it harder to read/search for this test.
async fn it_sets_up_tracing_andspawns_blocking_task_with_preserved_span() {
36ce13a to
b534222
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (7)
backend/src/processes/land_use_sealed_area/types.rs:20
LandUseSummaryRowOutputis serialized with Rust field names (snake_case) because it lacks#[serde(rename_all = "camelCase")], but the correspondingDataResourceschema uses camelCase keys (e.g.,previousYear). This will produce mismatching output keys vs schema.
#[derive(Debug, Clone, Copy, Serialize, JsonSchema, ToSchema)]
pub struct LandUseSummaryRowOutput {
backend/src/processes/parameters/mod.rs:141
primary_keyreferences "name", but the schema only defines "data" and "documentation_source" fields. This makes the generated Table Schema invalid and can break consumers that rely on the declared primary key.
],
primary_key: vec!["name".to_string()].into(),
},
backend/src/processes/land_use_sealed_area/types.rs:199
- The summary table schema includes a
landUseTypecolumn and even declares it as theprimary_key, butLandUseSummaryRowOutputhas nolandUseTypefield anddatais built from rows that don't include it. This will yield an invalid/ambiguous summary table (schema != data).
name: "landUseType".into(),
r#type: Some(TableSchemaType::String),
title: Some("Land-use type".into()),
..Default::default()
},
backend/src/processes/land_use_sealed_area/description.md:37
- The documented land-use type value
onSitedoesn't match what the implementation accepts (site,natureOnSite,natureOffSite). Inputs usingonSitewill fail validation/parsing.
- `onSite`: Area located on the organization's site
- `natureOnSite`: Nature-oriented areas located on the organization's site
- `natureOffSite`: Nature-oriented areas owned or managed by the organization but located off-site
backend/src/processes/land_use_sealed_area/mod.rs:231
- This InputSpec doesn't match the actual API input type:
LandUseSealedAreaProcessInputs.previous_year_dataisOption<JsonInput<PreviousLandUseSummary>>(expects{ value, mediaType }), but the generated schema usesOption<PreviousLandUseSummary>and the description incorrectly mentions a GeoJSON FeatureCollection.
key: input_keys::PREVIOUS_YEAR_DATA,
title: "Previous Year Data (Optional)",
description: "GeoJSON FeatureCollection from previous reporting period for comparison.",
metadata: vec![],
r#type: generator.root_schema_for::<Option<PreviousLandUseSummary>>(),
backend/src/processes/land_use_sealed_area/description.md:28
- The process currently rejects
Pointgeometries during validation (only Polygon/MultiPolygon are supported), so the documentation should not claim Points are accepted.
- `geometry`: The geographical coordinates of the area, which can be a `Point` or `Polygon`. Should use the `EPSG:4326` (WGS 84, latitude/longitude) coordinate reference system.
backend/src/util.rs:407
- This test initializes tracing twice:
setup_tracing(...).init()installs a global subscriber, so the subsequenttry_init()will always fail (and the error is ignored). This is misleading and can hide real subscriber-init issues; it also makes the test harder to reason about.
// Initialize a subscriber for this test
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
b534222 to
60ca82a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
backend/src/processes/land_use_sealed_area/mod.rs:59
- The doc comment says
previous_year_datais "Site data" from the previous reporting period, but the type isJsonInput<PreviousLandUseSummary>(a summary value object), not per-site data. This is misleading for API consumers.
/// Optional: Site data from the previous reporting period for year-over-year comparison.
backend/src/processes/land_use_sealed_area/mod.rs:228
- The OpenAPI input description for
previousYearDatasays it is a "GeoJSON FeatureCollection", but the schema isOption<JsonInput<PreviousLandUseSummary>>(JSON summary values). This makes the generated process description inconsistent with the actual input type.
description: "GeoJSON FeatureCollection from previous reporting period for comparison.",
backend/Cargo.toml:70
geoengine-api-clientis pulled from a floating Git branch. This makes builds non-reproducible because the dependency can change without a Cargo.lock update (and CI may start failing unexpectedly). Prefer pinning to a specific tag or commit (rev) if a crates.io release isn't possible yet.
geoengine-api-client = { git = "https://github.com/geo-engine/geoengine", branch = "feat/vector-expression-api-model" }
backend/src/processes/land_use_sealed_area/description.md:28
- The documentation claims site
geometrycan be aPointorPolygon, but the implementation validates onlyPolygon/MultiPolygonand will reject other geometry types. The docs should match the supported input types.
- `geometry`: The geographical coordinates of the area, which can be a `Point` or `Polygon`. Should use the `EPSG:4326` (WGS 84, latitude/longitude) coordinate reference system.
60ca82a to
da7e1df
Compare
da7e1df to
fcab578
Compare
fcab578 to
dafc728
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
backend/src/processes/land_use_sealed_area/description.md:28
- The docs state that each feature's
geometrycan be aPointorPolygon, but the implementation currently only supportsPolygon/MultiPolygon(seevalidate_and_extract_bboxrejecting other geometry types). This is misleading for API consumers; either update the docs to match the implementation or add point support in the process.
- `geometry`: The geographical coordinates of the area, which can be a `Point` or `Polygon`. Should use the `EPSG:4326` (WGS 84, latitude/longitude) coordinate reference system.
backend/src/processes/parameters/geo_json.rs:40
- This doc comment claims
GeoJsonFeatureCollectioncontains only Polygon features, but the type is just a thin wrapper aroundgeojson::FeatureCollectionand does not validate feature geometry types. This can confuse callers (and currently points are accepted elsewhere).
/// A `GeoJSON` `FeatureCollection` containing only Polygon features.
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct GeoJsonFeatureCollection(FeatureCollection);
backend/src/processes/land_use_sealed_area/mod.rs:242
- The OpenAPI input description for
previousYearDatasays it's a GeoJSON FeatureCollection, but the actual type isOption<JsonInput<PreviousLandUseSummary>>(a JSON object with totals + unit). This will produce incorrect API docs.
InputSpec {
key: input_keys::PREVIOUS_YEAR_DATA,
title: "Previous Year Data (Optional)",
description: "GeoJSON FeatureCollection from previous reporting period for comparison.",
metadata: vec![],
r#type: generator.root_schema_for::<Option<JsonInput<PreviousLandUseSummary>>>(),
backend/src/processes/land_use_sealed_area/mod.rs:1092
- This test writes
test-output.jsoninto the working directory. This can cause flaky tests (parallel runs clobbering the same file) and pollutes CI artifacts. It should be removed or replaced with an assertion-only approach.
.collect();
let requested_outputs = OutputKeys::from_requested_outputs(&requested_outputs).unwrap();
let process = LandUseSealedAreaProcess;
backend/src/util.rs:390
- Test name contains a typo:
andspawnsis missing an underscore/word boundary, which makes it harder to read and search for.
#[tokio::test]
async fn it_sets_up_tracing_andspawns_blocking_task_with_preserved_span() {
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
backend/src/processes/land_use_sealed_area/mod.rs:1101
- This test writes
test-output.jsoninto the working directory, which creates side effects and can cause flaky CI behavior (e.g., parallel runs, read-only workspaces). The output is already asserted below, so the file write should be removed.
std::fs::write(
"test-output.json",
serde_json::to_string_pretty(&result).unwrap(),
).unwrap();
backend/src/util.rs:390
- Test name has a typo (
andspawns) which makes it harder to read/search. Consider adding the missing underscore.
async fn it_sets_up_tracing_andspawns_blocking_task_with_preserved_span() {
No description provided.