Normalize area geometries to two dimensions
This commit is contained in:
@@ -64,6 +64,8 @@
|
|||||||
the full readiness gate, one Alembic head and complete offline migration SQL.
|
the full readiness gate, one Alembic head and complete offline migration SQL.
|
||||||
- Fixed fail-closed AdminVector GeoJSON serialization for Pandas timestamp
|
- Fixed fail-closed AdminVector GeoJSON serialization for Pandas timestamp
|
||||||
properties exposed by the live NGI GeoPackage.
|
properties exposed by the live NGI GeoPackage.
|
||||||
|
- Hardened shared AOI geometry normalization to persist valid 2D polygons when
|
||||||
|
an authoritative source supplies harmless zero-valued Z coordinates.
|
||||||
|
|
||||||
## Sprint 240 Operational forest, agriculture, nature and soil themes (2026-07-17)
|
## Sprint 240 Operational forest, agriculture, nature and soil themes (2026-07-17)
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyproj import Transformer
|
from pyproj import Transformer
|
||||||
|
from shapely import force_2d
|
||||||
from shapely.geometry import GeometryCollection, MultiPolygon, Polygon, box, shape
|
from shapely.geometry import GeometryCollection, MultiPolygon, Polygon, box, shape
|
||||||
from shapely.ops import transform
|
from shapely.ops import transform
|
||||||
from shapely.validation import make_valid
|
from shapely.validation import make_valid
|
||||||
|
|
||||||
|
|
||||||
def normalize_to_multipolygon(raw_geometry: dict[str, Any]) -> MultiPolygon:
|
def normalize_to_multipolygon(raw_geometry: dict[str, Any]) -> MultiPolygon:
|
||||||
geom = shape(raw_geometry)
|
geom = force_2d(shape(raw_geometry))
|
||||||
if geom.is_empty:
|
if geom.is_empty:
|
||||||
raise ValueError("Geometry is empty")
|
raise ValueError("Geometry is empty")
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ if str(SCRIPTS) not in sys.path:
|
|||||||
sys.path.insert(0, str(SCRIPTS))
|
sys.path.insert(0, str(SCRIPTS))
|
||||||
|
|
||||||
import provision_belgium_north_sea_scope as operator # noqa: E402
|
import provision_belgium_north_sea_scope as operator # noqa: E402
|
||||||
|
from app.utils.geometry import normalize_to_multipolygon # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def marine_feature(identifier: str, geometry):
|
def marine_feature(identifier: str, geometry):
|
||||||
@@ -77,6 +78,26 @@ def test_marine_scope_derivation_fails_when_a_required_unit_is_missing() -> None
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_area_geometry_normalization_drops_source_z_dimension() -> None:
|
||||||
|
geometry = normalize_to_multipolygon(
|
||||||
|
{
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[2.5, 49.5, 0],
|
||||||
|
[6.4, 49.5, 0],
|
||||||
|
[6.4, 51.5, 0],
|
||||||
|
[2.5, 51.5, 0],
|
||||||
|
[2.5, 49.5, 0],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert geometry.has_z is False
|
||||||
|
assert geometry.geom_type == "MultiPolygon"
|
||||||
|
|
||||||
|
|
||||||
def test_archive_extraction_accepts_one_safe_geopackage_and_rejects_traversal(tmp_path: Path) -> None:
|
def test_archive_extraction_accepts_one_safe_geopackage_and_rejects_traversal(tmp_path: Path) -> None:
|
||||||
archive = tmp_path / "adminvector.zip"
|
archive = tmp_path / "adminvector.zip"
|
||||||
with zipfile.ZipFile(archive, "w") as handle:
|
with zipfile.ZipFile(archive, "w") as handle:
|
||||||
|
|||||||
@@ -30,6 +30,11 @@
|
|||||||
serialization was corrected to explicit string conversion and guarded by a
|
serialization was corrected to explicit string conversion and guarded by a
|
||||||
regression assertion; downloaded source artifacts remain operator cache
|
regression assertion; downloaded source artifacts remain operator cache
|
||||||
only.
|
only.
|
||||||
|
- The corrected Tower fetch-only run validated all seven generated artifacts
|
||||||
|
and 101 current MSP 2026 features. The first API persistence run then exposed
|
||||||
|
a separate live-only PostGIS constraint: AdminVector AOI boundaries carry a
|
||||||
|
zero-valued Z coordinate while `areas.geometry` is intentionally 2D.
|
||||||
|
Shared AOI normalization now removes Z before validation and persistence.
|
||||||
|
|
||||||
- Froze the RC geography as all Belgian land plus the separately labelled
|
- Froze the RC geography as all Belgian land plus the separately labelled
|
||||||
territorial sea, EEZ and continental shelf.
|
territorial sea, EEZ and continental shelf.
|
||||||
|
|||||||
Reference in New Issue
Block a user