Persist raster tile CRS metadata
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-07 02:03:34 +02:00
parent 71c2cd9411
commit bc87681ab7
8 changed files with 51 additions and 9 deletions
+3 -1
View File
@@ -369,7 +369,9 @@ detections against persisted `vector_features`, persists QA/QC rows and exports
the detection GeoJSON. It never seeds demo detections, enables fixture mode,
fetches live providers or downloads model weights. Configured-YOLO model class
labels are normalized to lowercase for filtering and persisted detections while
the original model label is retained in detection provenance. Current V1 upload
the original model label is retained in detection provenance. Raster tile
manifests generated for AI handoff include source CRS metadata so pixel-space
model outputs can be transformed to WGS84 GeoJSON coordinates. Current V1 upload
support is limited to GeoTIFF-style rasters and GeoJSON/JSON reference vectors.
### Run backend
@@ -939,8 +939,13 @@ class RasterOperationsService:
manifest_tiles: list[dict[str, Any]] = []
tile_paths: list[str] = []
source_crs: str | None = None
with rasterio.open(dataset.storage_path) as source:
raw_source_crs = getattr(source, "crs", None)
source_crs = raw_source_crs.to_string() if hasattr(raw_source_crs, "to_string") else (
str(raw_source_crs) if raw_source_crs else dataset.crs
)
source_count = getattr(source, "count", 0)
if not source_count:
source_count = 1
@@ -981,6 +986,7 @@ class RasterOperationsService:
"pixel_window": [int(xoff), int(yoff), int(tile_width), int(tile_height)],
"bounds": RasterOperationsService._window_bounds_to_list(bounds),
"transform": [float(item) for item in transform.to_gdal()],
"crs": source_crs,
"index": tile_index,
},
)
@@ -994,10 +1000,14 @@ class RasterOperationsService:
except AppError:
source_metadata = {"bounds": [0.0, 0.0, 0.0, 0.0]}
bounds = source_metadata.get("bounds", [0.0, 0.0, 0.0, 0.0])
manifest_crs = source_crs or source_metadata.get("crs") or dataset.crs
manifest_payload = {
"tile_set_id": tile_set_id,
"source_dataset_id": str(dataset.id),
"source_raster_id": str(dataset.id),
"crs": manifest_crs,
"source_crs": manifest_crs,
"dataset_crs": dataset.crs,
"bounds": [float(value) for value in bounds],
"tile_size": int(tile_size),
"overlap": int(overlap),
@@ -606,6 +606,10 @@ def test_raster_tile_returns_manifest_payload(monkeypatch, tmp_path) -> None:
float(window.yoff + window.height),
)
class FakeCRS:
def to_string(self):
return "EPSG:31370"
class FakeSource:
width = 10
height = 10
@@ -614,6 +618,7 @@ def test_raster_tile_returns_manifest_payload(monkeypatch, tmp_path) -> None:
self.profile = {"width": self.width, "height": self.height, "count": 1, "dtype": "uint8", "transform": None}
self.transform = None
self.nodata = 0
self.crs = FakeCRS()
def read(self, *args, **kwargs):
return FakeArray()
@@ -670,7 +675,10 @@ def test_raster_tile_returns_manifest_payload(monkeypatch, tmp_path) -> None:
assert payload["manifest"]["overlap"] == 1
assert payload["manifest"]["source_dataset_id"] == str(dataset_id)
assert payload["manifest"]["source_raster_id"] == str(dataset_id)
assert payload["manifest"]["crs"] == "EPSG:31370"
assert payload["manifest"]["source_crs"] == "EPSG:31370"
assert payload["manifest"]["count"] == payload["count"]
assert payload["manifest"]["tiles"][0]["crs"] == "EPSG:31370"
assert payload["manifest"]["tiles"][0]["bounds"] == [0.0, 0.0, 4.0, 4.0]
assert payload["manifest"]["ai_inference"] is False
assert payload["manifest"]["tile_server"] is None