feat: operationalize Flemish land and nature themes
This commit is contained in:
@@ -45,6 +45,7 @@ class ThematicRasterProduct:
|
||||
legend_min_label: str
|
||||
legend_max_label: str
|
||||
limitation_message: str
|
||||
included_source_values: tuple[int, ...] = ()
|
||||
|
||||
|
||||
class ThematicRasterAcquisitionService:
|
||||
@@ -100,6 +101,44 @@ class ThematicRasterAcquisitionService:
|
||||
"synoniem met natuur, bos, publieke toegankelijkheid of planologische bestemming."
|
||||
),
|
||||
),
|
||||
ThematicRasterProduct(
|
||||
key="forest_land_use_2025",
|
||||
display_name="Bos volgens Landgebruik Vlaanderen 2025",
|
||||
theme="forest",
|
||||
metric_kind="binary_area",
|
||||
coverage_id="lu:lu_landgebruik_vlaa_2025_v3",
|
||||
native_resolution_m=10.0,
|
||||
source_value_unit="class_0_1",
|
||||
observation_year=2025,
|
||||
source_version="Toestand 2025 versie 3",
|
||||
catalog_url="https://www.vlaanderen.be/datavindplaats/catalogus/landgebruik-vlaanderen-toestand-2025",
|
||||
legend_min_label="Geen bosklasse",
|
||||
legend_max_label="Bos",
|
||||
limitation_message=(
|
||||
"10 m-afleiding van bronklasse 12 (bos) uit Landgebruik Vlaanderen 2025. De oppervlakte is "
|
||||
"resolutiegebonden en vormt geen juridische bosgrens, boomtelling, kroonbedekking of houtvolume."
|
||||
),
|
||||
included_source_values=(12,),
|
||||
),
|
||||
ThematicRasterProduct(
|
||||
key="agricultural_land_use_2025",
|
||||
display_name="Akker en landbouwgrasland 2025",
|
||||
theme="agriculture",
|
||||
metric_kind="binary_area",
|
||||
coverage_id="lu:lu_landgebruik_vlaa_2025_v3",
|
||||
native_resolution_m=10.0,
|
||||
source_value_unit="class_0_1",
|
||||
observation_year=2025,
|
||||
source_version="Toestand 2025 versie 3",
|
||||
catalog_url="https://www.vlaanderen.be/datavindplaats/catalogus/landgebruik-vlaanderen-toestand-2025",
|
||||
legend_min_label="Ander landgebruik",
|
||||
legend_max_label="Akker of landbouwgrasland",
|
||||
limitation_message=(
|
||||
"10 m-afleiding van bronklassen 13 (akker) en 14 (grasland in landbouwgebruik). Dit is werkelijk "
|
||||
"landgebruik en geen ALZ-perceelaangifte, eigendomsgrens, teeltregister of juridische bestemming."
|
||||
),
|
||||
included_source_values=(13, 14),
|
||||
),
|
||||
ThematicRasterProduct(
|
||||
key="population_density_2019",
|
||||
display_name="Inwonersdichtheid per hectare 2019",
|
||||
@@ -176,6 +215,7 @@ class ThematicRasterAcquisitionService:
|
||||
license_note=ThematicRasterAcquisitionService.LICENSE_NOTE,
|
||||
legend_min_label=product.legend_min_label,
|
||||
legend_max_label=product.legend_max_label,
|
||||
included_source_values=list(product.included_source_values),
|
||||
limitation_message=product.limitation_message,
|
||||
).model_dump()
|
||||
for product in ThematicRasterAcquisitionService._products().values()
|
||||
@@ -461,7 +501,29 @@ class ThematicRasterAcquisitionService:
|
||||
invalid = np.ma.getmaskarray(band) | ~np.isfinite(raw)
|
||||
if source.nodata is not None:
|
||||
invalid |= np.isclose(raw, float(source.nodata))
|
||||
normalized = np.ma.array(raw, mask=invalid)
|
||||
source_values = np.ma.array(raw, mask=invalid).compressed().astype("float64")
|
||||
if product.included_source_values:
|
||||
rounded = np.rint(source_values)
|
||||
if not np.allclose(source_values, rounded, atol=0.0001):
|
||||
raise AppError(
|
||||
code="THEMATIC_RASTER_INVALID_VALUES",
|
||||
message="Categorical land-use coverage contains non-integer source classes",
|
||||
status_code=502,
|
||||
)
|
||||
if source_values.size and (
|
||||
float(source_values.min()) < 0
|
||||
or float(source_values.max()) > 255
|
||||
):
|
||||
raise AppError(
|
||||
code="THEMATIC_RASTER_INVALID_VALUES",
|
||||
message="Categorical land-use coverage contains source classes outside the governed range",
|
||||
status_code=502,
|
||||
)
|
||||
source_classes = np.where(invalid, 0, np.rint(raw)).astype("int16")
|
||||
binary = np.isin(source_classes, product.included_source_values).astype("float32")
|
||||
normalized = np.ma.array(binary, mask=invalid)
|
||||
else:
|
||||
normalized = np.ma.array(raw, mask=invalid)
|
||||
values = normalized.compressed().astype("float64")
|
||||
ThematicRasterAcquisitionService._validate_values(values, product)
|
||||
profile = source.profile.copy()
|
||||
@@ -482,6 +544,9 @@ class ThematicRasterAcquisitionService:
|
||||
"maximum_value": float(values.max()),
|
||||
"p02_value": float(np.percentile(values, 2)),
|
||||
"p98_value": float(np.percentile(values, 98)),
|
||||
"included_source_values": list(product.included_source_values),
|
||||
"source_minimum_value": float(source_values.min()),
|
||||
"source_maximum_value": float(source_values.max()),
|
||||
}
|
||||
except AppError:
|
||||
raise
|
||||
@@ -563,6 +628,7 @@ class ThematicRasterAcquisitionService:
|
||||
"analysis_resolution_m": product.native_resolution_m,
|
||||
"source_crs": ThematicRasterAcquisitionService.SOURCE_CRS,
|
||||
"source_value_unit": product.source_value_unit,
|
||||
"included_source_values": list(product.included_source_values),
|
||||
"observation_year": product.observation_year,
|
||||
"observation_date_precision": "year",
|
||||
"valid_pixel_count": validation["valid_pixel_count"],
|
||||
|
||||
Reference in New Issue
Block a user