Persist raster tile CRS metadata
This commit is contained in:
+3
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user