From d191eb037a624eccdb36550390d04c30b721da3f Mon Sep 17 00:00:00 2001 From: Victor Engmark Date: Thu, 20 Jun 2024 09:26:40 +1200 Subject: [PATCH 1/2] fix: Treat item as `dict` We load the item from a JSON file into a `dict` in the production code, but none of our automated tests verify this code path, so it broke. This partially reverts commit ed177c622dbb93587f00524635051c5f0f4a8b9f. --- scripts/stac/imagery/collection.py | 13 ++++++------- scripts/stac/imagery/tests/collection_test.py | 3 ++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/stac/imagery/collection.py b/scripts/stac/imagery/collection.py index 444a6d2a6..486ae19b2 100644 --- a/scripts/stac/imagery/collection.py +++ b/scripts/stac/imagery/collection.py @@ -1,6 +1,5 @@ import os from collections.abc import Callable -from dataclasses import asdict from datetime import datetime from typing import Any @@ -12,7 +11,7 @@ from scripts.files.fs import write from scripts.json_codec import dict_to_json_bytes from scripts.stac.imagery.capture_area import generate_capture_area, gsd_to_float -from scripts.stac.imagery.item import BoundingBox, ImageryItem +from scripts.stac.imagery.item import BoundingBox from scripts.stac.imagery.metadata_constants import ( DATA_CATEGORIES, DEM, @@ -130,18 +129,18 @@ def add_capture_area(self, polygons: list[BaseGeometry], target: str, artifact_t if StacExtensions.file.value not in self.stac["stac_extensions"]: self.stac["stac_extensions"].append(StacExtensions.file.value) - def add_item(self, item: ImageryItem) -> None: + def add_item(self, item: dict[Any, Any]) -> None: """Add an `Item` to the `links` of the `Collection`. Args: item: STAC Item to add """ - item_self_link = next((feat for feat in item.links if feat["rel"] == "self"), None) - file_checksum = checksum.multihash_as_hex(dict_to_json_bytes(asdict(item))) + item_self_link = next((feat for feat in item["links"] if feat["rel"] == "self"), None) + file_checksum = checksum.multihash_as_hex(dict_to_json_bytes(item)) if item_self_link: self.add_link(href=item_self_link["href"], file_checksum=file_checksum) - self.update_temporal_extent(item.properties.start_datetime, item.properties.end_datetime) - self.update_spatial_extent(item.bbox) + self.update_temporal_extent(item["properties"]["start_datetime"], item["properties"]["end_datetime"]) + self.update_spatial_extent(item["bbox"]) def add_link(self, href: str, file_checksum: str) -> None: """Add a `link` to the existing `links` list of the Collection. diff --git a/scripts/stac/imagery/tests/collection_test.py b/scripts/stac/imagery/tests/collection_test.py index a8b23fe0d..16270a646 100644 --- a/scripts/stac/imagery/tests/collection_test.py +++ b/scripts/stac/imagery/tests/collection_test.py @@ -1,5 +1,6 @@ import json import os +from dataclasses import asdict from datetime import datetime, timezone from shutil import rmtree from tempfile import TemporaryDirectory, mkdtemp @@ -138,7 +139,7 @@ def test_add_item(metadata: CollectionMetadata, subtests: SubTests) -> None: "BR34_5000_0304", item_file_path, now_function, start_datetime, end_datetime, geometry, bbox, collection.stac["id"] ) - collection.add_item(item) + collection.add_item(asdict(item)) links = collection.stac["links"].copy() From fb6adcfbdc5256745969097e8487ad2709fdcdeb Mon Sep 17 00:00:00 2001 From: Victor Engmark Date: Thu, 13 Jun 2024 12:07:24 +1200 Subject: [PATCH 2/2] fix: Orient exterior rings counter-clockwise TDE-1205 [GeoJSON requires this](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6): > A linear ring MUST follow the right-hand rule with respect to the area > it bounds, i.e., exterior rings are counterclockwise, and holes are > clockwise. However, [Shapely does not do this by default](https://github.com/shapely/shapely/issues/622). --- scripts/stac/imagery/capture_area.py | 5 +++-- scripts/stac/imagery/tests/capture_area_test.py | 10 +++++++--- scripts/stac/imagery/tests/collection_test.py | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/scripts/stac/imagery/capture_area.py b/scripts/stac/imagery/capture_area.py index ccc8085de..6d20bcbb8 100644 --- a/scripts/stac/imagery/capture_area.py +++ b/scripts/stac/imagery/capture_area.py @@ -1,5 +1,6 @@ import json -from typing import Any, Sequence +from collections.abc import Sequence +from typing import Any from linz_logger import get_log from shapely import BufferCapStyle, BufferJoinStyle, to_geojson, union_all @@ -75,7 +76,7 @@ def merge_polygons(polygons: Sequence[BaseGeometry], buffer_distance: float) -> union_buffered = union_all(buffered_polygons) # Negative buffer back in the polygons union_unbuffered = union_buffered.buffer(-buffer_distance, cap_style=BufferCapStyle.flat, join_style=BufferJoinStyle.mitre) - union_simplified = union_unbuffered.simplify(buffer_distance) + union_simplified = union_unbuffered.simplify(buffer_distance).reverse() return union_simplified diff --git a/scripts/stac/imagery/tests/capture_area_test.py b/scripts/stac/imagery/tests/capture_area_test.py index 4e10ef1c9..268bb4795 100644 --- a/scripts/stac/imagery/tests/capture_area_test.py +++ b/scripts/stac/imagery/tests/capture_area_test.py @@ -10,17 +10,21 @@ def test_merge_polygons() -> None: polygons = [] polygons.append(Polygon([(0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0), (0.0, 1.0)])) polygons.append(Polygon([(1.0, 1.0), (2.0, 1.0), (2.0, 0.0), (1.0, 0.0), (1.0, 1.0)])) - expected_merged_polygon = Polygon([(0.0, 1.0), (2.0, 1.0), (2.0, 0.0), (0.0, 0.0), (0.0, 1.0)]) + expected_merged_polygon_geos_3_11 = Polygon([(1.0, 1.0), (0.0, 1.0), (0.0, 0.0), (2.0, 0.0), (2.0, 1.0), (1.0, 1.0)]) + expected_merged_polygon_geos_3_12 = Polygon([(0.0, 1.0), (0.0, 0.0), (2.0, 0.0), (2.0, 1.0), (0.0, 1.0)]) merged_polygons = merge_polygons(polygons, 0) print(f"Polygon A: {to_feature(polygons[0])}") print(f"Polygon B: {to_feature(polygons[1])}") - print(f"GeoJSON expected: {to_feature(expected_merged_polygon)}") + print(f"GeoJSON expected GEOS 3.11: {to_feature(expected_merged_polygon_geos_3_11)}") + print(f"GeoJSON expected GEOS 3.12: {to_feature(expected_merged_polygon_geos_3_12)}") print(f"GeoJSON result: {to_feature(merged_polygons)}") # Using `Polygon.equals()` as merge_polygons might return a different set of coordinates for the same geometry # In this example: `Polygon([(2.0, 1.0), (2.0, 0.0), (0.0, 0.0), (0.0, 1.0), (2.0, 1.0)])` - assert merged_polygons.equals(expected_merged_polygon) + assert merged_polygons.equals_exact(expected_merged_polygon_geos_3_11, 0.0) or merged_polygons.equals_exact( + expected_merged_polygon_geos_3_12, 0.0 + ) def test_merge_polygons_with_rounding() -> None: diff --git a/scripts/stac/imagery/tests/collection_test.py b/scripts/stac/imagery/tests/collection_test.py index 16270a646..fdcb824bf 100644 --- a/scripts/stac/imagery/tests/collection_test.py +++ b/scripts/stac/imagery/tests/collection_test.py @@ -314,8 +314,8 @@ def test_capture_area_added(metadata: CollectionMetadata, subtests: SubTests) -> with subtests.test(): assert collection.stac["assets"]["capture_area"]["file:checksum"] in ( - "1220b15694be7495af38e0f70af67cfdc4f19b8bc415a2eb77d780e7a32c6e5b42c2", # geos 3.11 - "122040fc8700d5d2d04600f730e10677b19d33f3b1e43b02c7867f4cfc2101930863", # geos 3.12 + "1220369cd5d4179f5f68ca0fd9be70b9f66033fcc6bb2f3305c0ad977adc79d7ad53", # geos 3.11 + "122060feab333d28f33f165cee2d3db31a71ba9fee40d163e922dad09581a50c19e6", # geos 3.12 ) with subtests.test():