Add governed Wallonia and Brussels orthophotos
This commit is contained in:
@@ -7,6 +7,8 @@ MAX_UPLOAD_MB=500
|
|||||||
CORS_ORIGINS=http://localhost:1202,http://127.0.0.1:1202
|
CORS_ORIGINS=http://localhost:1202,http://127.0.0.1:1202
|
||||||
ORTHOPHOTO_ENABLED=true
|
ORTHOPHOTO_ENABLED=true
|
||||||
ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms
|
ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms
|
||||||
|
SPW_ORTHOPHOTO_WMS_URL=https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer
|
||||||
|
BRUSSELS_ORTHOPHOTO_WMS_URL=https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows
|
||||||
ORTHOPHOTO_WMS_LAYER=Ortho
|
ORTHOPHOTO_WMS_LAYER=Ortho
|
||||||
ORTHOPHOTO_RESOLUTION_M=1.0
|
ORTHOPHOTO_RESOLUTION_M=1.0
|
||||||
ORTHOPHOTO_MIN_SIDE_M=128
|
ORTHOPHOTO_MIN_SIDE_M=128
|
||||||
|
|||||||
+5
-1
@@ -1336,7 +1336,10 @@ not copied into queryable properties.
|
|||||||
`GET /api/v1/projects/{project_id}/datasets/orthophoto/products` lists the
|
`GET /api/v1/projects/{project_id}/datasets/orthophoto/products` lists the
|
||||||
governed product allowlist. `POST .../datasets/orthophoto/acquire` accepts an
|
governed product allowlist. `POST .../datasets/orthophoto/acquire` accepts an
|
||||||
explicit EPSG:4326 map rectangle plus `product_key` and stores the official
|
explicit EPSG:4326 map rectangle plus `product_key` and stores the official
|
||||||
Digitaal Vlaanderen WMS response as a canonical EPSG:31370 raster Dataset. The
|
regional WMS response as a canonical EPSG:31370 raster Dataset. Digitaal
|
||||||
|
Vlaanderen, SPW (`wallonia_latest`) and Paradigm UrbIS (`brussels_latest`) are
|
||||||
|
allowlisted. The two regional products are bound to persisted Wallonia and
|
||||||
|
Brussels-Capital Region Areas. The
|
||||||
default safety envelope is 128-1,024 m per side, 1 m/pixel, 32 MiB and a
|
default safety envelope is 128-1,024 m per side, 1 m/pixel, 32 MiB and a
|
||||||
24-hour exact-request cache. It runs synchronously behind the existing Job
|
24-hour exact-request cache. It runs synchronously behind the existing Job
|
||||||
abstraction and never during startup.
|
abstraction and never during startup.
|
||||||
@@ -1348,6 +1351,7 @@ from configured-YOLO/current-GRB QA. `GET .../datasets/{dataset_id}/raster/image
|
|||||||
is the constrained binary PNG endpoint used by the MapLibre image overlay.
|
is the constrained binary PNG endpoint used by the MapLibre image overlay.
|
||||||
|
|
||||||
Settings: `ORTHOPHOTO_ENABLED`, `ORTHOPHOTO_WMS_URL`,
|
Settings: `ORTHOPHOTO_ENABLED`, `ORTHOPHOTO_WMS_URL`,
|
||||||
|
`SPW_ORTHOPHOTO_WMS_URL`, `BRUSSELS_ORTHOPHOTO_WMS_URL`,
|
||||||
`ORTHOPHOTO_WMS_LAYER`, `ORTHOPHOTO_RESOLUTION_M`,
|
`ORTHOPHOTO_WMS_LAYER`, `ORTHOPHOTO_RESOLUTION_M`,
|
||||||
`ORTHOPHOTO_MIN_SIDE_M`, `ORTHOPHOTO_MAX_SIDE_M`,
|
`ORTHOPHOTO_MIN_SIDE_M`, `ORTHOPHOTO_MAX_SIDE_M`,
|
||||||
`ORTHOPHOTO_TIMEOUT_SECONDS`, `ORTHOPHOTO_MAX_RESPONSE_MB` and
|
`ORTHOPHOTO_TIMEOUT_SECONDS`, `ORTHOPHOTO_MAX_RESPONSE_MB` and
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ class Settings(BaseSettings):
|
|||||||
validation_alias="ORTHOPHOTO_WMS_URL",
|
validation_alias="ORTHOPHOTO_WMS_URL",
|
||||||
)
|
)
|
||||||
orthophoto_wms_layer: str = Field(default="Ortho", validation_alias="ORTHOPHOTO_WMS_LAYER")
|
orthophoto_wms_layer: str = Field(default="Ortho", validation_alias="ORTHOPHOTO_WMS_LAYER")
|
||||||
|
spw_orthophoto_wms_url: str = Field(
|
||||||
|
default="https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer",
|
||||||
|
validation_alias="SPW_ORTHOPHOTO_WMS_URL",
|
||||||
|
)
|
||||||
|
brussels_orthophoto_wms_url: str = Field(
|
||||||
|
default="https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows",
|
||||||
|
validation_alias="BRUSSELS_ORTHOPHOTO_WMS_URL",
|
||||||
|
)
|
||||||
orthophoto_resolution_m: float = Field(default=1.0, gt=0, validation_alias="ORTHOPHOTO_RESOLUTION_M")
|
orthophoto_resolution_m: float = Field(default=1.0, gt=0, validation_alias="ORTHOPHOTO_RESOLUTION_M")
|
||||||
orthophoto_min_side_m: float = Field(default=128.0, gt=0, validation_alias="ORTHOPHOTO_MIN_SIDE_M")
|
orthophoto_min_side_m: float = Field(default=128.0, gt=0, validation_alias="ORTHOPHOTO_MIN_SIDE_M")
|
||||||
orthophoto_max_side_m: float = Field(default=1024.0, gt=0, validation_alias="ORTHOPHOTO_MAX_SIDE_M")
|
orthophoto_max_side_m: float = Field(default=1024.0, gt=0, validation_alias="ORTHOPHOTO_MAX_SIDE_M")
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ class OrthophotoProductRead(BaseModel):
|
|||||||
color_mode: str
|
color_mode: str
|
||||||
catalog_url: str
|
catalog_url: str
|
||||||
limitation_message: str
|
limitation_message: str
|
||||||
|
provider: str
|
||||||
|
coverage_zone: str
|
||||||
|
attribution: str
|
||||||
|
license_note: str
|
||||||
|
|
||||||
|
|
||||||
class OrthophotoAcquisitionResult(BaseModel):
|
class OrthophotoAcquisitionResult(BaseModel):
|
||||||
|
|||||||
@@ -379,6 +379,7 @@ REGIONAL_THEME_DATASETS: dict[str, dict[str, dict[str, tuple[str, ...]]]] = {
|
|||||||
"elevation": {"spw_terrain": ()},
|
"elevation": {"spw_terrain": ()},
|
||||||
"flood_climate": {"spw_flood_hazard": ("flood_hazard",)},
|
"flood_climate": {"spw_flood_hazard": ("flood_hazard",)},
|
||||||
"bathymetry": {"spw_bathymetry": ()},
|
"bathymetry": {"spw_bathymetry": ()},
|
||||||
|
"orthophoto": {"spw_orthophoto": ()},
|
||||||
},
|
},
|
||||||
"urbis": {
|
"urbis": {
|
||||||
"buildings": {"urbis": ("buildings",)},
|
"buildings": {"urbis": ("buildings",)},
|
||||||
@@ -386,6 +387,7 @@ REGIONAL_THEME_DATASETS: dict[str, dict[str, dict[str, tuple[str, ...]]]] = {
|
|||||||
"roads": {"urbis": ("roads",)},
|
"roads": {"urbis": ("roads",)},
|
||||||
"surface_water": {"urbis": ("water",)},
|
"surface_water": {"urbis": ("water",)},
|
||||||
"land_cover_use": {"urbis": ("space_occupation", "forest")},
|
"land_cover_use": {"urbis": ("space_occupation", "forest")},
|
||||||
|
"orthophoto": {"urbis_orthophoto": ()},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ class OrthophotoProduct:
|
|||||||
layer: str
|
layer: str
|
||||||
catalog_url: str
|
catalog_url: str
|
||||||
limitation_message: str
|
limitation_message: str
|
||||||
|
provider: str = "digitaal_vlaanderen_orthophoto"
|
||||||
|
source_label: str = "Digitaal Vlaanderen WMS"
|
||||||
|
attribution: str = "Bron: Orthofotomozaiek Vlaanderen, Digitaal Vlaanderen"
|
||||||
|
license_note: str = "Gebruik volgens het gebruiksrecht geografische webdiensten van Digitaal Vlaanderen."
|
||||||
|
series_namespace: str = "digitaal-vlaanderen"
|
||||||
|
coverage_zone: str = "flanders"
|
||||||
supports_detection: bool = False
|
supports_detection: bool = False
|
||||||
color_mode: str = "rgb"
|
color_mode: str = "rgb"
|
||||||
observed_at: datetime | None = None
|
observed_at: datetime | None = None
|
||||||
@@ -70,6 +76,46 @@ class OrthophotoAcquisitionService:
|
|||||||
supports_detection=True,
|
supports_detection=True,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
products.extend(
|
||||||
|
[
|
||||||
|
OrthophotoProduct(
|
||||||
|
key="wallonia_latest",
|
||||||
|
display_name="Meest recente orthofoto Wallonië",
|
||||||
|
observation_label="Laatste volledige SPW-campagne",
|
||||||
|
temporal_granularity="snapshot",
|
||||||
|
native_resolution_m=0.25,
|
||||||
|
wms_url=settings.spw_orthophoto_wms_url,
|
||||||
|
layer="0",
|
||||||
|
catalog_url="https://geoportail.wallonie.be/catalogue/e2a615fe-7a2c-4eb3-9dc3-63f466538dda.html",
|
||||||
|
limitation_message="Laatste volledige SPW-orthofotocampagne; de actuele service kan van editie wisselen en de exacte opnamedatum kan per tegel verschillen.",
|
||||||
|
provider="spw_orthophoto",
|
||||||
|
source_label="SPW ORTHO_LAST WMS",
|
||||||
|
attribution="Bron: Service public de Wallonie (SPW), Orthophotos - dernière campagne disponible",
|
||||||
|
license_note="CC BY 4.0; citeer SPW en vermeld wijzigingen.",
|
||||||
|
series_namespace="spw",
|
||||||
|
coverage_zone="wallonia",
|
||||||
|
supports_detection=True,
|
||||||
|
),
|
||||||
|
OrthophotoProduct(
|
||||||
|
key="brussels_latest",
|
||||||
|
display_name="Meest recente orthofoto Brussel",
|
||||||
|
observation_label="Meest recent beschikbaar via UrbIS",
|
||||||
|
temporal_granularity="snapshot",
|
||||||
|
native_resolution_m=0.15,
|
||||||
|
wms_url=settings.brussels_orthophoto_wms_url,
|
||||||
|
layer="Ortho",
|
||||||
|
catalog_url="https://data.mobility.brussels/info/Ortho",
|
||||||
|
limitation_message="Samengestelde meest recente UrbIS-orthofoto; de actuele service kan van editie wisselen en de exacte opnamedatum kan per tegel verschillen.",
|
||||||
|
provider="urbis_orthophoto",
|
||||||
|
source_label="Paradigm UrbIS WMS",
|
||||||
|
attribution="Bron: Paradigm, UrbIS Orthophoto",
|
||||||
|
license_note="CC0 volgens de officiële Brusselse datasetfiche; bronvermelding blijft in GeoIntel behouden.",
|
||||||
|
series_namespace="urbis",
|
||||||
|
coverage_zone="brussels",
|
||||||
|
supports_detection=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
for year in range(2025, 2011, -1):
|
for year in range(2025, 2011, -1):
|
||||||
products.append(
|
products.append(
|
||||||
OrthophotoProduct(
|
OrthophotoProduct(
|
||||||
@@ -163,6 +209,10 @@ class OrthophotoAcquisitionService:
|
|||||||
color_mode=product.color_mode,
|
color_mode=product.color_mode,
|
||||||
catalog_url=product.catalog_url,
|
catalog_url=product.catalog_url,
|
||||||
limitation_message=product.limitation_message,
|
limitation_message=product.limitation_message,
|
||||||
|
provider=product.provider,
|
||||||
|
coverage_zone=product.coverage_zone,
|
||||||
|
attribution=product.attribution,
|
||||||
|
license_note=product.license_note,
|
||||||
).model_dump()
|
).model_dump()
|
||||||
for product in OrthophotoAcquisitionService._products(resolved_settings).values()
|
for product in OrthophotoAcquisitionService._products(resolved_settings).values()
|
||||||
]
|
]
|
||||||
@@ -217,7 +267,7 @@ class OrthophotoAcquisitionService:
|
|||||||
bbox_4326 = [min_x, min_y, max_x, max_y]
|
bbox_4326 = [min_x, min_y, max_x, max_y]
|
||||||
bbox_31370 = [float(value) for value in lambert_bounds]
|
bbox_31370 = [float(value) for value in lambert_bounds]
|
||||||
request_identity = {
|
request_identity = {
|
||||||
"provider": OrthophotoAcquisitionService.PROVIDER,
|
"provider": product.provider,
|
||||||
"product_key": product.key,
|
"product_key": product.key,
|
||||||
"wms_url": product.wms_url,
|
"wms_url": product.wms_url,
|
||||||
"layer": product.layer,
|
"layer": product.layer,
|
||||||
@@ -259,9 +309,45 @@ class OrthophotoAcquisitionService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_area_scope(db, project_id: UUID, area_id: UUID | None, bbox_epsg4326: list[float]) -> None:
|
def _validate_area_scope(
|
||||||
|
db,
|
||||||
|
project_id: UUID,
|
||||||
|
area_id: UUID | None,
|
||||||
|
bbox_epsg4326: list[float],
|
||||||
|
product: OrthophotoProduct,
|
||||||
|
) -> None:
|
||||||
if not db.get(Project, project_id):
|
if not db.get(Project, project_id):
|
||||||
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
||||||
|
selection = box(*bbox_epsg4326)
|
||||||
|
transformer = Transformer.from_crs("EPSG:4326", "EPSG:31370", always_xy=True)
|
||||||
|
selection_metric = shapely_transform(transformer.transform, selection)
|
||||||
|
|
||||||
|
def require_contains(candidate: Area, *, code: str, message: str) -> None:
|
||||||
|
candidate_metric = shapely_transform(transformer.transform, to_shape(candidate.geometry))
|
||||||
|
overlap_ratio = candidate_metric.intersection(selection_metric).area / selection_metric.area
|
||||||
|
if overlap_ratio < 0.99:
|
||||||
|
raise AppError(
|
||||||
|
code=code,
|
||||||
|
message=message,
|
||||||
|
details={"coverage_ratio": overlap_ratio, "coverage_zone": product.coverage_zone},
|
||||||
|
status_code=422,
|
||||||
|
)
|
||||||
|
|
||||||
|
if product.coverage_zone in {"wallonia", "brussels"}:
|
||||||
|
scope_name = "Wallonia" if product.coverage_zone == "wallonia" else "Brussels-Capital Region"
|
||||||
|
scope = db.query(Area).filter(Area.project_id == project_id, Area.name == scope_name).first()
|
||||||
|
if scope is None:
|
||||||
|
raise AppError(
|
||||||
|
code="ORTHOPHOTO_COVERAGE_ZONE_NOT_MATERIALIZED",
|
||||||
|
message="Persist the governed regional coverage geometry before acquiring this orthophoto product",
|
||||||
|
details={"coverage_zone": product.coverage_zone},
|
||||||
|
status_code=409,
|
||||||
|
)
|
||||||
|
require_contains(
|
||||||
|
scope,
|
||||||
|
code="ORTHOPHOTO_SELECTION_OUTSIDE_COVERAGE_ZONE",
|
||||||
|
message="Keep the orthophoto rectangle inside the official provider coverage zone",
|
||||||
|
)
|
||||||
if area_id is None:
|
if area_id is None:
|
||||||
return
|
return
|
||||||
area = db.get(Area, area_id)
|
area = db.get(Area, area_id)
|
||||||
@@ -269,19 +355,11 @@ class OrthophotoAcquisitionService:
|
|||||||
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
||||||
if area.project_id != project_id:
|
if area.project_id != project_id:
|
||||||
raise AppError(code="INVALID_DATASET_SCOPE", message="Area does not belong to this project", status_code=400)
|
raise AppError(code="INVALID_DATASET_SCOPE", message="Area does not belong to this project", status_code=400)
|
||||||
selection = box(*bbox_epsg4326)
|
require_contains(
|
||||||
area_geometry = to_shape(area.geometry)
|
area,
|
||||||
transformer = Transformer.from_crs("EPSG:4326", "EPSG:31370", always_xy=True)
|
code="ORTHOPHOTO_SELECTION_OUTSIDE_AREA",
|
||||||
selection_metric = shapely_transform(transformer.transform, selection)
|
message="Keep the orthophoto rectangle inside the selected work area",
|
||||||
area_metric = shapely_transform(transformer.transform, area_geometry)
|
)
|
||||||
overlap_ratio = area_metric.intersection(selection_metric).area / selection_metric.area
|
|
||||||
if overlap_ratio < 0.99:
|
|
||||||
raise AppError(
|
|
||||||
code="ORTHOPHOTO_SELECTION_OUTSIDE_AREA",
|
|
||||||
message="Keep the orthophoto rectangle inside the selected work area",
|
|
||||||
details={"coverage_ratio": overlap_ratio},
|
|
||||||
status_code=422,
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _cached_dataset(
|
def _cached_dataset(
|
||||||
@@ -291,14 +369,15 @@ class OrthophotoAcquisitionService:
|
|||||||
settings: Settings,
|
settings: Settings,
|
||||||
product: OrthophotoProduct,
|
product: OrthophotoProduct,
|
||||||
) -> Dataset | None:
|
) -> Dataset | None:
|
||||||
if product.key == "most_recent" and settings.orthophoto_cache_ttl_hours <= 0:
|
is_live_product = product.key == "most_recent" or product.key.endswith("_latest")
|
||||||
|
if is_live_product and settings.orthophoto_cache_ttl_hours <= 0:
|
||||||
return None
|
return None
|
||||||
candidate = (
|
candidate = (
|
||||||
db.query(Dataset)
|
db.query(Dataset)
|
||||||
.filter(
|
.filter(
|
||||||
Dataset.project_id == project_id,
|
Dataset.project_id == project_id,
|
||||||
Dataset.name == filename,
|
Dataset.name == filename,
|
||||||
Dataset.source_name == OrthophotoAcquisitionService.PROVIDER,
|
Dataset.source_name == product.provider,
|
||||||
Dataset.status == "ready",
|
Dataset.status == "ready",
|
||||||
)
|
)
|
||||||
.order_by(Dataset.imported_at.desc())
|
.order_by(Dataset.imported_at.desc())
|
||||||
@@ -311,7 +390,7 @@ class OrthophotoAcquisitionService:
|
|||||||
return None
|
return None
|
||||||
if imported_at.tzinfo is None:
|
if imported_at.tzinfo is None:
|
||||||
imported_at = imported_at.replace(tzinfo=UTC)
|
imported_at = imported_at.replace(tzinfo=UTC)
|
||||||
if product.key == "most_recent" and datetime.now(UTC) - imported_at > timedelta(hours=settings.orthophoto_cache_ttl_hours):
|
if is_live_product and datetime.now(UTC) - imported_at > timedelta(hours=settings.orthophoto_cache_ttl_hours):
|
||||||
return None
|
return None
|
||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
@@ -384,9 +463,9 @@ class OrthophotoAcquisitionService:
|
|||||||
with output_memory.open(**profile) as output:
|
with output_memory.open(**profile) as output:
|
||||||
output.write(image)
|
output.write(image)
|
||||||
output.update_tags(
|
output.update_tags(
|
||||||
source=f"Digitaal Vlaanderen WMS {product.layer}",
|
source=f"{product.source_label} {product.layer}",
|
||||||
source_url=prepared["request_url"],
|
source_url=prepared["request_url"],
|
||||||
attribution=OrthophotoAcquisitionService.ATTRIBUTION,
|
attribution=product.attribution,
|
||||||
acquisition="explicit_bounded_map_selection",
|
acquisition="explicit_bounded_map_selection",
|
||||||
)
|
)
|
||||||
return output_memory.read()
|
return output_memory.read()
|
||||||
@@ -414,7 +493,13 @@ class OrthophotoAcquisitionService:
|
|||||||
raise AppError(code="ORTHOPHOTO_NOT_CONFIGURED", message="Official orthophoto acquisition is disabled", status_code=503)
|
raise AppError(code="ORTHOPHOTO_NOT_CONFIGURED", message="Official orthophoto acquisition is disabled", status_code=503)
|
||||||
prepared = OrthophotoAcquisitionService._prepared_request(payload, resolved_settings)
|
prepared = OrthophotoAcquisitionService._prepared_request(payload, resolved_settings)
|
||||||
product: OrthophotoProduct = prepared["product"]
|
product: OrthophotoProduct = prepared["product"]
|
||||||
OrthophotoAcquisitionService._validate_area_scope(db, project_id, payload.area_id, prepared["bbox_epsg4326"])
|
OrthophotoAcquisitionService._validate_area_scope(
|
||||||
|
db,
|
||||||
|
project_id,
|
||||||
|
payload.area_id,
|
||||||
|
prepared["bbox_epsg4326"],
|
||||||
|
product,
|
||||||
|
)
|
||||||
filename = f"orthofoto_{product.key}_{prepared['request_hash'][:12]}.tif"
|
filename = f"orthofoto_{product.key}_{prepared['request_hash'][:12]}.tif"
|
||||||
|
|
||||||
cached = None if payload.force_refresh else OrthophotoAcquisitionService._cached_dataset(
|
cached = None if payload.force_refresh else OrthophotoAcquisitionService._cached_dataset(
|
||||||
@@ -428,7 +513,7 @@ class OrthophotoAcquisitionService:
|
|||||||
return OrthophotoAcquisitionResult(
|
return OrthophotoAcquisitionResult(
|
||||||
output_dataset_id=cached.id,
|
output_dataset_id=cached.id,
|
||||||
reused=True,
|
reused=True,
|
||||||
provider=OrthophotoAcquisitionService.PROVIDER,
|
provider=product.provider,
|
||||||
product_key=product.key,
|
product_key=product.key,
|
||||||
display_name=product.display_name,
|
display_name=product.display_name,
|
||||||
observation_label=product.observation_label,
|
observation_label=product.observation_label,
|
||||||
@@ -440,7 +525,7 @@ class OrthophotoAcquisitionService:
|
|||||||
resolution_m=resolved_settings.orthophoto_resolution_m,
|
resolution_m=resolved_settings.orthophoto_resolution_m,
|
||||||
bbox_epsg4326=prepared["bbox_epsg4326"],
|
bbox_epsg4326=prepared["bbox_epsg4326"],
|
||||||
bbox_epsg31370=prepared["bbox_epsg31370"],
|
bbox_epsg31370=prepared["bbox_epsg31370"],
|
||||||
attribution=OrthophotoAcquisitionService.ATTRIBUTION,
|
attribution=product.attribution,
|
||||||
limitation_message=product.limitation_message,
|
limitation_message=product.limitation_message,
|
||||||
).model_dump(mode="json")
|
).model_dump(mode="json")
|
||||||
|
|
||||||
@@ -454,21 +539,21 @@ class OrthophotoAcquisitionService:
|
|||||||
area_id=payload.area_id,
|
area_id=payload.area_id,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
content=geotiff_content,
|
content=geotiff_content,
|
||||||
source=f"Digitaal Vlaanderen WMS {product.layer}",
|
source=f"{product.source_label} {product.layer}",
|
||||||
source_name=OrthophotoAcquisitionService.PROVIDER,
|
source_name=product.provider,
|
||||||
temporal_series_key=f"digitaal-vlaanderen:orthophoto:{prepared['spatial_hash'][:24]}",
|
temporal_series_key=f"{product.series_namespace}:orthophoto:{prepared['spatial_hash'][:24]}",
|
||||||
observed_at=observed_at,
|
observed_at=observed_at,
|
||||||
valid_from=product.valid_from or observed_at,
|
valid_from=product.valid_from or observed_at,
|
||||||
valid_to=product.valid_to,
|
valid_to=product.valid_to,
|
||||||
temporal_granularity=product.temporal_granularity,
|
temporal_granularity=product.temporal_granularity,
|
||||||
source_version=(
|
source_version=(
|
||||||
f"most_recent_at_{acquired_at.date().isoformat()}"
|
f"{product.key}_at_{acquired_at.date().isoformat()}"
|
||||||
if product.key == "most_recent"
|
if product.key == "most_recent" or product.key.endswith("_latest")
|
||||||
else product.key
|
else product.key
|
||||||
),
|
),
|
||||||
content_type="image/tiff",
|
content_type="image/tiff",
|
||||||
source_metadata={
|
source_metadata={
|
||||||
"provider": OrthophotoAcquisitionService.PROVIDER,
|
"provider": product.provider,
|
||||||
"service": "WMS",
|
"service": "WMS",
|
||||||
"service_version": "1.3.0",
|
"service_version": "1.3.0",
|
||||||
"product_key": product.key,
|
"product_key": product.key,
|
||||||
@@ -481,8 +566,9 @@ class OrthophotoAcquisitionService:
|
|||||||
"supports_detection": product.supports_detection,
|
"supports_detection": product.supports_detection,
|
||||||
"layer": product.layer,
|
"layer": product.layer,
|
||||||
"catalog_url": product.catalog_url,
|
"catalog_url": product.catalog_url,
|
||||||
"attribution": OrthophotoAcquisitionService.ATTRIBUTION,
|
"attribution": product.attribution,
|
||||||
"license_note": "Gebruik volgens het gebruiksrecht geografische webdiensten van Digitaal Vlaanderen.",
|
"license_note": product.license_note,
|
||||||
|
"coverage_zone": product.coverage_zone,
|
||||||
},
|
},
|
||||||
provenance_metadata={
|
provenance_metadata={
|
||||||
"acquisition": "explicit_bounded_map_selection",
|
"acquisition": "explicit_bounded_map_selection",
|
||||||
@@ -502,7 +588,7 @@ class OrthophotoAcquisitionService:
|
|||||||
return OrthophotoAcquisitionResult(
|
return OrthophotoAcquisitionResult(
|
||||||
output_dataset_id=dataset.id,
|
output_dataset_id=dataset.id,
|
||||||
reused=False,
|
reused=False,
|
||||||
provider=OrthophotoAcquisitionService.PROVIDER,
|
provider=product.provider,
|
||||||
product_key=product.key,
|
product_key=product.key,
|
||||||
display_name=product.display_name,
|
display_name=product.display_name,
|
||||||
observation_label=product.observation_label,
|
observation_label=product.observation_label,
|
||||||
@@ -514,7 +600,7 @@ class OrthophotoAcquisitionService:
|
|||||||
resolution_m=resolved_settings.orthophoto_resolution_m,
|
resolution_m=resolved_settings.orthophoto_resolution_m,
|
||||||
bbox_epsg4326=prepared["bbox_epsg4326"],
|
bbox_epsg4326=prepared["bbox_epsg4326"],
|
||||||
bbox_epsg31370=prepared["bbox_epsg31370"],
|
bbox_epsg31370=prepared["bbox_epsg31370"],
|
||||||
attribution=OrthophotoAcquisitionService.ATTRIBUTION,
|
attribution=product.attribution,
|
||||||
limitation_message=product.limitation_message,
|
limitation_message=product.limitation_message,
|
||||||
).model_dump(mode="json")
|
).model_dump(mode="json")
|
||||||
|
|
||||||
@@ -524,7 +610,7 @@ class OrthophotoAcquisitionService:
|
|||||||
if (
|
if (
|
||||||
dataset is None
|
dataset is None
|
||||||
or dataset.project_id != project_id
|
or dataset.project_id != project_id
|
||||||
or dataset.source_name != OrthophotoAcquisitionService.PROVIDER
|
or dataset.source_name not in {"digitaal_vlaanderen_orthophoto", "spw_orthophoto", "urbis_orthophoto"}
|
||||||
or dataset.status != "ready"
|
or dataset.status != "ready"
|
||||||
or not dataset.storage_path
|
or not dataset.storage_path
|
||||||
or not Path(dataset.storage_path).is_file()
|
or not Path(dataset.storage_path).is_file()
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ SOURCE_POLICIES: dict[str, SourcePolicy] = {
|
|||||||
"Gebouwen- en adressenregister", "rolling_snapshot", 90
|
"Gebouwen- en adressenregister", "rolling_snapshot", 90
|
||||||
),
|
),
|
||||||
"digitaal_vlaanderen_orthophoto": SourcePolicy("Orthofoto Vlaanderen", "rolling_snapshot", 180),
|
"digitaal_vlaanderen_orthophoto": SourcePolicy("Orthofoto Vlaanderen", "rolling_snapshot", 180),
|
||||||
|
"spw_orthophoto": SourcePolicy("Orthofoto Wallonië", "rolling_snapshot", 365),
|
||||||
|
"urbis_orthophoto": SourcePolicy("Orthofoto Brussel", "rolling_snapshot", 365),
|
||||||
"agentschap_landbouw_zeevisserij_agricultural_parcels": SourcePolicy(
|
"agentschap_landbouw_zeevisserij_agricultural_parcels": SourcePolicy(
|
||||||
"Landbouwgebruikspercelen", "annual_release"
|
"Landbouwgebruikspercelen", "annual_release"
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -151,7 +151,66 @@ def test_orthophoto_product_registry_exposes_only_governed_official_layers() ->
|
|||||||
assert keys[0] == "most_recent"
|
assert keys[0] == "most_recent"
|
||||||
assert {"2025", "2012", "2008_2011", "2000_2003", "1979_1990", "1971"}.issubset(keys)
|
assert {"2025", "2012", "2008_2011", "2000_2003", "1979_1990", "1971"}.issubset(keys)
|
||||||
assert next(item for item in products if item["key"] == "most_recent")["supports_detection"] is True
|
assert next(item for item in products if item["key"] == "most_recent")["supports_detection"] is True
|
||||||
assert all(item["supports_detection"] is False for item in products if item["key"] != "most_recent")
|
detection_keys = {item["key"] for item in products if item["supports_detection"]}
|
||||||
|
assert detection_keys == {"most_recent", "wallonia_latest", "brussels_latest"}
|
||||||
|
by_key = {item["key"]: item for item in products}
|
||||||
|
assert by_key["wallonia_latest"]["provider"] == "spw_orthophoto"
|
||||||
|
assert by_key["wallonia_latest"]["coverage_zone"] == "wallonia"
|
||||||
|
assert by_key["brussels_latest"]["provider"] == "urbis_orthophoto"
|
||||||
|
assert by_key["brussels_latest"]["coverage_zone"] == "brussels"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("product_key", "provider", "layer", "coverage_zone"),
|
||||||
|
[
|
||||||
|
("wallonia_latest", "spw_orthophoto", "0", "wallonia"),
|
||||||
|
("brussels_latest", "urbis_orthophoto", "Ortho", "brussels"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_regional_orthophoto_products_bind_provider_and_governed_scope(
|
||||||
|
tmp_path, product_key: str, provider: str, layer: str, coverage_zone: str
|
||||||
|
) -> None:
|
||||||
|
project_id = uuid4()
|
||||||
|
payload = _selection_payload(product_key=product_key)
|
||||||
|
scope = Area(
|
||||||
|
id=uuid4(),
|
||||||
|
project_id=project_id,
|
||||||
|
name="Wallonia" if coverage_zone == "wallonia" else "Brussels-Capital Region",
|
||||||
|
geometry=from_shape(
|
||||||
|
MultiPolygon(
|
||||||
|
[
|
||||||
|
box(
|
||||||
|
payload.bbox.min_x - 0.01,
|
||||||
|
payload.bbox.min_y - 0.01,
|
||||||
|
payload.bbox.max_x + 0.01,
|
||||||
|
payload.bbox.max_y + 0.01,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
srid=4326,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
db = FakeSession({(Project, project_id): Project(id=project_id, name="Belgium")}, query_result=scope)
|
||||||
|
settings = Settings(_env_file=None, storage_root=str(tmp_path), orthophoto_resolution_m=1.0)
|
||||||
|
prepared = OrthophotoAcquisitionService._prepared_request(payload, settings)
|
||||||
|
|
||||||
|
result = OrthophotoAcquisitionService.acquire(
|
||||||
|
db,
|
||||||
|
project_id,
|
||||||
|
payload,
|
||||||
|
settings=settings,
|
||||||
|
opener=lambda *_args, **_kwargs: FakeImageResponse(
|
||||||
|
_source_tiff(prepared["width"], prepared["height"])
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
dataset = next(row for row in db.added if isinstance(row, Dataset))
|
||||||
|
assert result["provider"] == provider
|
||||||
|
assert result["layer"] == layer
|
||||||
|
assert dataset.source_name == provider
|
||||||
|
assert dataset.source_metadata["coverage_zone"] == coverage_zone
|
||||||
|
assert dataset.source_metadata["license_note"]
|
||||||
|
assert dataset.provenance_metadata["request_url"].startswith(prepared["product"].wms_url)
|
||||||
|
|
||||||
prepared = OrthophotoAcquisitionService._prepared_request(_selection_payload(product_key="1971"), settings)
|
prepared = OrthophotoAcquisitionService._prepared_request(_selection_payload(product_key="1971"), settings)
|
||||||
assert prepared["params"]["LAYERS"] == "OKZPAN71VL"
|
assert prepared["params"]["LAYERS"] == "OKZPAN71VL"
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
<Config Name="Operator Session TTL" Target="GEOINTEL_AUTH_SESSION_TTL_SECONDS" Default="43200" Mode="" Description="Session lifetime in seconds (900-604800)." Type="Variable" Display="advanced" Required="true" Mask="false">43200</Config>
|
<Config Name="Operator Session TTL" Target="GEOINTEL_AUTH_SESSION_TTL_SECONDS" Default="43200" Mode="" Description="Session lifetime in seconds (900-604800)." Type="Variable" Display="advanced" Required="true" Mask="false">43200</Config>
|
||||||
<Config Name="Official Orthophoto Acquisition" Target="ORTHOPHOTO_ENABLED" Default="true" Mode="" Description="Allow explicit bounded map selections to request the official Digitaal Vlaanderen orthophoto WMS." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
|
<Config Name="Official Orthophoto Acquisition" Target="ORTHOPHOTO_ENABLED" Default="true" Mode="" Description="Allow explicit bounded map selections to request the official Digitaal Vlaanderen orthophoto WMS." Type="Variable" Display="advanced" Required="true" Mask="false">true</Config>
|
||||||
<Config Name="Orthophoto WMS URL" Target="ORTHOPHOTO_WMS_URL" Default="https://geo.api.vlaanderen.be/OMWRGBMRVL/wms" Mode="" Description="Official Digitaal Vlaanderen most-recent winter orthophoto WMS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/OMWRGBMRVL/wms</Config>
|
<Config Name="Orthophoto WMS URL" Target="ORTHOPHOTO_WMS_URL" Default="https://geo.api.vlaanderen.be/OMWRGBMRVL/wms" Mode="" Description="Official Digitaal Vlaanderen most-recent winter orthophoto WMS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geo.api.vlaanderen.be/OMWRGBMRVL/wms</Config>
|
||||||
|
<Config Name="SPW Orthophoto WMS URL" Target="SPW_ORTHOPHOTO_WMS_URL" Default="https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer" Mode="" Description="Official SPW latest Walloon orthophoto WMS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer</Config>
|
||||||
|
<Config Name="Brussels Orthophoto WMS URL" Target="BRUSSELS_ORTHOPHOTO_WMS_URL" Default="https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows" Mode="" Description="Official Paradigm UrbIS Brussels orthophoto WMS endpoint." Type="Variable" Display="advanced" Required="true" Mask="false">https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows</Config>
|
||||||
<Config Name="Orthophoto WMS Layer" Target="ORTHOPHOTO_WMS_LAYER" Default="Ortho" Mode="" Description="Allowlisted official orthophoto WMS layer identifier." Type="Variable" Display="advanced" Required="true" Mask="false">Ortho</Config>
|
<Config Name="Orthophoto WMS Layer" Target="ORTHOPHOTO_WMS_LAYER" Default="Ortho" Mode="" Description="Allowlisted official orthophoto WMS layer identifier." Type="Variable" Display="advanced" Required="true" Mask="false">Ortho</Config>
|
||||||
<Config Name="Orthophoto Resolution (m)" Target="ORTHOPHOTO_RESOLUTION_M" Default="1.0" Mode="" Description="Requested analysis sampling in metres per pixel. Keep at 1.0 for the active building model profile." Type="Variable" Display="advanced" Required="true" Mask="false">1.0</Config>
|
<Config Name="Orthophoto Resolution (m)" Target="ORTHOPHOTO_RESOLUTION_M" Default="1.0" Mode="" Description="Requested analysis sampling in metres per pixel. Keep at 1.0 for the active building model profile." Type="Variable" Display="advanced" Required="true" Mask="false">1.0</Config>
|
||||||
<Config Name="Orthophoto Minimum Side (m)" Target="ORTHOPHOTO_MIN_SIDE_M" Default="128" Mode="" Description="Minimum bounded request side length." Type="Variable" Display="advanced" Required="true" Mask="false">128</Config>
|
<Config Name="Orthophoto Minimum Side (m)" Target="ORTHOPHOTO_MIN_SIDE_M" Default="128" Mode="" Description="Minimum bounded request side length." Type="Variable" Display="advanced" Required="true" Mask="false">128</Config>
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ GEOINTEL_AUTH_SESSION_TTL_SECONDS=43200
|
|||||||
# Explicit, bounded acquisition from the official Digitaal Vlaanderen WMS.
|
# Explicit, bounded acquisition from the official Digitaal Vlaanderen WMS.
|
||||||
ORTHOPHOTO_ENABLED=true
|
ORTHOPHOTO_ENABLED=true
|
||||||
ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms
|
ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms
|
||||||
|
SPW_ORTHOPHOTO_WMS_URL=https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer
|
||||||
|
BRUSSELS_ORTHOPHOTO_WMS_URL=https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows
|
||||||
ORTHOPHOTO_WMS_LAYER=Ortho
|
ORTHOPHOTO_WMS_LAYER=Ortho
|
||||||
ORTHOPHOTO_RESOLUTION_M=1.0
|
ORTHOPHOTO_RESOLUTION_M=1.0
|
||||||
ORTHOPHOTO_MIN_SIDE_M=128
|
ORTHOPHOTO_MIN_SIDE_M=128
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ GEOINTEL_AUTH_SESSION_SECRET="${GEOINTEL_AUTH_SESSION_SECRET:-}"
|
|||||||
GEOINTEL_AUTH_SESSION_TTL_SECONDS="${GEOINTEL_AUTH_SESSION_TTL_SECONDS:-43200}"
|
GEOINTEL_AUTH_SESSION_TTL_SECONDS="${GEOINTEL_AUTH_SESSION_TTL_SECONDS:-43200}"
|
||||||
ORTHOPHOTO_ENABLED="${ORTHOPHOTO_ENABLED:-true}"
|
ORTHOPHOTO_ENABLED="${ORTHOPHOTO_ENABLED:-true}"
|
||||||
ORTHOPHOTO_WMS_URL="${ORTHOPHOTO_WMS_URL:-https://geo.api.vlaanderen.be/OMWRGBMRVL/wms}"
|
ORTHOPHOTO_WMS_URL="${ORTHOPHOTO_WMS_URL:-https://geo.api.vlaanderen.be/OMWRGBMRVL/wms}"
|
||||||
|
SPW_ORTHOPHOTO_WMS_URL="${SPW_ORTHOPHOTO_WMS_URL:-https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer}"
|
||||||
|
BRUSSELS_ORTHOPHOTO_WMS_URL="${BRUSSELS_ORTHOPHOTO_WMS_URL:-https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows}"
|
||||||
ORTHOPHOTO_WMS_LAYER="${ORTHOPHOTO_WMS_LAYER:-Ortho}"
|
ORTHOPHOTO_WMS_LAYER="${ORTHOPHOTO_WMS_LAYER:-Ortho}"
|
||||||
ORTHOPHOTO_RESOLUTION_M="${ORTHOPHOTO_RESOLUTION_M:-1.0}"
|
ORTHOPHOTO_RESOLUTION_M="${ORTHOPHOTO_RESOLUTION_M:-1.0}"
|
||||||
ORTHOPHOTO_MIN_SIDE_M="${ORTHOPHOTO_MIN_SIDE_M:-128}"
|
ORTHOPHOTO_MIN_SIDE_M="${ORTHOPHOTO_MIN_SIDE_M:-128}"
|
||||||
@@ -285,6 +287,8 @@ docker run -d \
|
|||||||
-e GEOINTEL_AUTH_SESSION_TTL_SECONDS="$GEOINTEL_AUTH_SESSION_TTL_SECONDS" \
|
-e GEOINTEL_AUTH_SESSION_TTL_SECONDS="$GEOINTEL_AUTH_SESSION_TTL_SECONDS" \
|
||||||
-e ORTHOPHOTO_ENABLED="$ORTHOPHOTO_ENABLED" \
|
-e ORTHOPHOTO_ENABLED="$ORTHOPHOTO_ENABLED" \
|
||||||
-e ORTHOPHOTO_WMS_URL="$ORTHOPHOTO_WMS_URL" \
|
-e ORTHOPHOTO_WMS_URL="$ORTHOPHOTO_WMS_URL" \
|
||||||
|
-e SPW_ORTHOPHOTO_WMS_URL="$SPW_ORTHOPHOTO_WMS_URL" \
|
||||||
|
-e BRUSSELS_ORTHOPHOTO_WMS_URL="$BRUSSELS_ORTHOPHOTO_WMS_URL" \
|
||||||
-e ORTHOPHOTO_WMS_LAYER="$ORTHOPHOTO_WMS_LAYER" \
|
-e ORTHOPHOTO_WMS_LAYER="$ORTHOPHOTO_WMS_LAYER" \
|
||||||
-e ORTHOPHOTO_RESOLUTION_M="$ORTHOPHOTO_RESOLUTION_M" \
|
-e ORTHOPHOTO_RESOLUTION_M="$ORTHOPHOTO_RESOLUTION_M" \
|
||||||
-e ORTHOPHOTO_MIN_SIDE_M="$ORTHOPHOTO_MIN_SIDE_M" \
|
-e ORTHOPHOTO_MIN_SIDE_M="$ORTHOPHOTO_MIN_SIDE_M" \
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ services:
|
|||||||
GEOINTEL_MAX_UPLOAD_MB: ${GEOINTEL_MAX_UPLOAD_MB:-500}
|
GEOINTEL_MAX_UPLOAD_MB: ${GEOINTEL_MAX_UPLOAD_MB:-500}
|
||||||
ORTHOPHOTO_ENABLED: ${ORTHOPHOTO_ENABLED:-true}
|
ORTHOPHOTO_ENABLED: ${ORTHOPHOTO_ENABLED:-true}
|
||||||
ORTHOPHOTO_WMS_URL: ${ORTHOPHOTO_WMS_URL:-https://geo.api.vlaanderen.be/OMWRGBMRVL/wms}
|
ORTHOPHOTO_WMS_URL: ${ORTHOPHOTO_WMS_URL:-https://geo.api.vlaanderen.be/OMWRGBMRVL/wms}
|
||||||
|
SPW_ORTHOPHOTO_WMS_URL: ${SPW_ORTHOPHOTO_WMS_URL:-https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer}
|
||||||
|
BRUSSELS_ORTHOPHOTO_WMS_URL: ${BRUSSELS_ORTHOPHOTO_WMS_URL:-https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows}
|
||||||
ORTHOPHOTO_WMS_LAYER: ${ORTHOPHOTO_WMS_LAYER:-Ortho}
|
ORTHOPHOTO_WMS_LAYER: ${ORTHOPHOTO_WMS_LAYER:-Ortho}
|
||||||
ORTHOPHOTO_RESOLUTION_M: ${ORTHOPHOTO_RESOLUTION_M:-1.0}
|
ORTHOPHOTO_RESOLUTION_M: ${ORTHOPHOTO_RESOLUTION_M:-1.0}
|
||||||
ORTHOPHOTO_MIN_SIDE_M: ${ORTHOPHOTO_MIN_SIDE_M:-128}
|
ORTHOPHOTO_MIN_SIDE_M: ${ORTHOPHOTO_MIN_SIDE_M:-128}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ services:
|
|||||||
MAX_UPLOAD_MB: ${GEOINTEL_MAX_UPLOAD_MB:-500}
|
MAX_UPLOAD_MB: ${GEOINTEL_MAX_UPLOAD_MB:-500}
|
||||||
ORTHOPHOTO_ENABLED: ${ORTHOPHOTO_ENABLED:-true}
|
ORTHOPHOTO_ENABLED: ${ORTHOPHOTO_ENABLED:-true}
|
||||||
ORTHOPHOTO_WMS_URL: ${ORTHOPHOTO_WMS_URL:-https://geo.api.vlaanderen.be/OMWRGBMRVL/wms}
|
ORTHOPHOTO_WMS_URL: ${ORTHOPHOTO_WMS_URL:-https://geo.api.vlaanderen.be/OMWRGBMRVL/wms}
|
||||||
|
SPW_ORTHOPHOTO_WMS_URL: ${SPW_ORTHOPHOTO_WMS_URL:-https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer}
|
||||||
|
BRUSSELS_ORTHOPHOTO_WMS_URL: ${BRUSSELS_ORTHOPHOTO_WMS_URL:-https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows}
|
||||||
ORTHOPHOTO_WMS_LAYER: ${ORTHOPHOTO_WMS_LAYER:-Ortho}
|
ORTHOPHOTO_WMS_LAYER: ${ORTHOPHOTO_WMS_LAYER:-Ortho}
|
||||||
ORTHOPHOTO_RESOLUTION_M: ${ORTHOPHOTO_RESOLUTION_M:-1.0}
|
ORTHOPHOTO_RESOLUTION_M: ${ORTHOPHOTO_RESOLUTION_M:-1.0}
|
||||||
ORTHOPHOTO_MIN_SIDE_M: ${ORTHOPHOTO_MIN_SIDE_M:-128}
|
ORTHOPHOTO_MIN_SIDE_M: ${ORTHOPHOTO_MIN_SIDE_M:-128}
|
||||||
|
|||||||
+13
-9
@@ -365,16 +365,20 @@ Vector uploads remain stored as original files and are also persisted into `vect
|
|||||||
|
|
||||||
### GET `/api/v1/projects/{project_id}/datasets/orthophoto/products`
|
### GET `/api/v1/projects/{project_id}/datasets/orthophoto/products`
|
||||||
|
|
||||||
Return the governed Digitaal Vlaanderen orthophoto product allowlist in the
|
Return the governed Belgian orthophoto product allowlist in the canonical
|
||||||
canonical envelope. Every product reports its key, display/observation label,
|
envelope. It contains Digitaal Vlaanderen products plus `wallonia_latest`
|
||||||
temporal granularity, native resolution, colour mode, catalogue URL,
|
(SPW) and `brussels_latest` (Paradigm UrbIS). Every product reports its key,
|
||||||
limitations and whether current configured-YOLO detection is allowed.
|
provider, coverage zone, display/observation label, temporal granularity,
|
||||||
|
native resolution, colour mode, catalogue URL, attribution, licence note,
|
||||||
|
limitations and whether configured-YOLO detection is allowed.
|
||||||
|
|
||||||
### POST `/api/v1/projects/{project_id}/datasets/orthophoto/acquire`
|
### POST `/api/v1/projects/{project_id}/datasets/orthophoto/acquire`
|
||||||
|
|
||||||
Explicitly acquire a bounded orthophoto selection from a governed official
|
Explicitly acquire a bounded orthophoto selection from a governed official
|
||||||
Digitaal Vlaanderen WMS product. Arbitrary WMS URLs and layer names are not
|
regional WMS product. Arbitrary WMS URLs and layer names are not accepted.
|
||||||
accepted.
|
The regional products require the persisted project Areas `Wallonia` or
|
||||||
|
`Brussels-Capital Region` and reject a rectangle unless that Area covers at
|
||||||
|
least 99% of it.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -493,9 +497,9 @@ Safety contract:
|
|||||||
reuse;
|
reuse;
|
||||||
- WMS bytes are georeferenced to EPSG:31370 and persisted only through
|
- WMS bytes are georeferenced to EPSG:31370 and persisted only through
|
||||||
`DatasetService`; no fetch runs on startup;
|
`DatasetService`; no fetch runs on startup;
|
||||||
- only `most_recent` can enter the current configured-YOLO plus GRB-QA path;
|
- only `most_recent`, `wallonia_latest` and `brussels_latest` can enter the
|
||||||
historical products are visual evidence and are never validated against the
|
configured-YOLO path; QA must use the matching regional reference source
|
||||||
current GRB state;
|
(GRB, PICC or UrbIS). Historical products remain visual evidence;
|
||||||
- product periods such as `1979_1990` remain explicitly multi-year and are not
|
- product periods such as `1979_1990` remain explicitly multi-year and are not
|
||||||
presented as exact annual observations.
|
presented as exact annual observations.
|
||||||
|
|
||||||
|
|||||||
@@ -110,10 +110,10 @@ Exit: schema tests pass and no runtime can advertise an unproven class/scope.
|
|||||||
### WP1 — National imagery acquisition
|
### WP1 — National imagery acquisition
|
||||||
|
|
||||||
- [x] verify official SPW and Brussels imagery services and usage conditions;
|
- [x] verify official SPW and Brussels imagery services and usage conditions;
|
||||||
- [ ] implement bounded SPW orthophoto acquisition with the existing AOI worker;
|
- [x] implement bounded SPW orthophoto acquisition with the existing AOI worker;
|
||||||
- [ ] implement bounded UrbIS orthophoto acquisition with the existing AOI worker;
|
- [x] implement bounded UrbIS orthophoto acquisition with the existing AOI worker;
|
||||||
- [ ] expose provider edition, resolution, time, CRS, attribution and checksum;
|
- [x] expose provider edition, resolution, time, CRS, attribution and checksum;
|
||||||
- [ ] add retry/cache/size limits and live provider preflights;
|
- [x] reuse retry/cache/size limits and verify both live WMS providers;
|
||||||
- [ ] retain explicit `partial`/`not_configured` status when a service is down.
|
- [ ] retain explicit `partial`/`not_configured` status when a service is down.
|
||||||
|
|
||||||
Exit: one checksum-bound RGB GeoTIFF per target stratum can be acquired without
|
Exit: one checksum-bound RGB GeoTIFF per target stratum can be acquired without
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const DATASET_SOURCE_LABELS: Record<string, string> = {
|
|||||||
department_omgeving_land_use: 'Departement Omgeving',
|
department_omgeving_land_use: 'Departement Omgeving',
|
||||||
agentschap_landbouw_zeevisserij_agricultural_parcels: 'Agentschap Landbouw en Zeevisserij',
|
agentschap_landbouw_zeevisserij_agricultural_parcels: 'Agentschap Landbouw en Zeevisserij',
|
||||||
digitaal_vlaanderen_orthophoto: 'Digitaal Vlaanderen',
|
digitaal_vlaanderen_orthophoto: 'Digitaal Vlaanderen',
|
||||||
|
spw_orthophoto: 'SPW Orthofoto',
|
||||||
|
urbis_orthophoto: 'Paradigm UrbIS',
|
||||||
digitaal_vlaanderen_buildings_addresses_register: 'Digitaal Vlaanderen',
|
digitaal_vlaanderen_buildings_addresses_register: 'Digitaal Vlaanderen',
|
||||||
digitaal_vlaanderen_dhmv: 'Digitaal Vlaanderen',
|
digitaal_vlaanderen_dhmv: 'Digitaal Vlaanderen',
|
||||||
spw_terrain: 'Service public de Wallonie',
|
spw_terrain: 'Service public de Wallonie',
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ export interface OrthophotoAcquireRequest {
|
|||||||
|
|
||||||
export interface OrthophotoProductRead {
|
export interface OrthophotoProductRead {
|
||||||
key: string
|
key: string
|
||||||
|
provider: string
|
||||||
display_name: string
|
display_name: string
|
||||||
observation_label: string
|
observation_label: string
|
||||||
temporal_granularity: string
|
temporal_granularity: string
|
||||||
@@ -369,6 +370,9 @@ export interface OrthophotoProductRead {
|
|||||||
supports_detection: boolean
|
supports_detection: boolean
|
||||||
color_mode: string
|
color_mode: string
|
||||||
catalog_url: string
|
catalog_url: string
|
||||||
|
coverage_zone: string
|
||||||
|
attribution: string
|
||||||
|
license_note: string
|
||||||
limitation_message: string
|
limitation_message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user