diff --git a/backend/app/services/dataset_service.py b/backend/app/services/dataset_service.py index 3ad47138..77896bf8 100644 --- a/backend/app/services/dataset_service.py +++ b/backend/app/services/dataset_service.py @@ -145,6 +145,43 @@ class DatasetService: crs_assumed=metadata_json.get("crs_assumed"), ) + @staticmethod + def _extract_raster_bounds_json(metadata_json: dict[str, Any]) -> dict[str, float] | None: + existing = metadata_json.get("bounds_json") + if isinstance(existing, dict): + return existing + bounds = metadata_json.get("bounds") + if isinstance(bounds, (list, tuple)) and len(bounds) == 4: + return { + "minx": float(bounds[0]), + "miny": float(bounds[1]), + "maxx": float(bounds[2]), + "maxy": float(bounds[3]), + } + return None + + @staticmethod + def _extract_raster_resolution_json(metadata_json: dict[str, Any]) -> dict[str, float] | None: + existing = metadata_json.get("resolution_json") + if isinstance(existing, dict): + return existing + resolution = metadata_json.get("resolution") + if isinstance(resolution, (list, tuple)) and len(resolution) >= 2: + return {"x": float(resolution[0]), "y": float(resolution[1])} + return None + + @staticmethod + def _extract_raster_bands_json(metadata_json: dict[str, Any]) -> dict[str, Any] | None: + existing = metadata_json.get("bands_json") + if isinstance(existing, dict): + return existing + bands_json: dict[str, Any] = {} + if metadata_json.get("band_count") is not None: + bands_json["band_count"] = int(metadata_json["band_count"]) + if metadata_json.get("dtype") is not None: + bands_json["dtype"] = metadata_json["dtype"] + return bands_json or None + @staticmethod async def upload_dataset( db: Session, @@ -222,6 +259,14 @@ class DatasetService: StorageService.remove_dataset_file(storage_info["storage_path"]) raise + bounds_json = metadata.get("bounds_json") if isinstance(metadata, dict) else None + resolution_json = metadata.get("resolution_json") if isinstance(metadata, dict) else None + bands_json = metadata.get("bands_json") if isinstance(metadata, dict) else None + if canonical_type == "raster" and isinstance(metadata, dict): + bounds_json = DatasetService._extract_raster_bounds_json(metadata) + resolution_json = DatasetService._extract_raster_resolution_json(metadata) + bands_json = DatasetService._extract_raster_bands_json(metadata) + dataset = Dataset( id=dataset_id, project_id=project_id, @@ -242,9 +287,9 @@ class DatasetService: size_bytes=storage_info["size_bytes"], checksum_sha256=storage_info["checksum_sha256"], crs=metadata.get("crs") if isinstance(metadata, dict) else None, - bounds_json=metadata.get("bounds_json") if isinstance(metadata, dict) else None, - resolution_json=metadata.get("resolution_json") if isinstance(metadata, dict) else None, - bands_json=metadata.get("bands_json") if isinstance(metadata, dict) else None, + bounds_json=bounds_json, + resolution_json=resolution_json, + bands_json=bands_json, metadata_json=metadata, status=status, ) @@ -317,11 +362,19 @@ class DatasetService: dataset.status = "failed" raise + bounds_json = metadata.get("bounds_json") if isinstance(metadata, dict) else dataset.bounds_json + resolution_json = metadata.get("resolution_json") if isinstance(metadata, dict) else dataset.resolution_json + bands_json = metadata.get("bands_json") if isinstance(metadata, dict) else dataset.bands_json + if DatasetService._is_raster_type(dataset.dataset_type) and isinstance(metadata, dict): + bounds_json = DatasetService._extract_raster_bounds_json(metadata) + resolution_json = DatasetService._extract_raster_resolution_json(metadata) + bands_json = DatasetService._extract_raster_bands_json(metadata) + dataset.crs = metadata.get("crs") if isinstance(metadata, dict) else dataset.crs - dataset.bounds_json = metadata.get("bounds_json") if isinstance(metadata, dict) else dataset.bounds_json + dataset.bounds_json = bounds_json dataset.metadata_json = metadata - dataset.resolution_json = metadata.get("resolution_json") if isinstance(metadata, dict) else dataset.resolution_json - dataset.bands_json = metadata.get("bands_json") if isinstance(metadata, dict) else dataset.bands_json + dataset.resolution_json = resolution_json + dataset.bands_json = bands_json db.add(dataset) db.commit() diff --git a/backend/tests/test_sprint122_raster_upload_metadata_mapping.py b/backend/tests/test_sprint122_raster_upload_metadata_mapping.py new file mode 100644 index 00000000..cd7b5928 --- /dev/null +++ b/backend/tests/test_sprint122_raster_upload_metadata_mapping.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import asyncio +from types import SimpleNamespace +from uuid import uuid4 + +from app.models import Project +from app.services.dataset_service import DatasetService + + +class FakeUploadFile: + filename = "real-orthophoto.tif" + content_type = "image/tiff" + + async def read(self) -> bytes: + return b"fake-raster" + + +class FakeSession: + def __init__(self, project_id): + self.project_id = project_id + self.added = [] + + def get(self, model, item_id): + if model is Project and item_id == self.project_id: + return SimpleNamespace(id=item_id) + return None + + def add(self, item): + self.added.append(item) + + def commit(self): + return None + + def refresh(self, _item): + return None + + +def test_raster_upload_maps_metadata_bounds_resolution_and_bands(monkeypatch) -> None: + project_id = uuid4() + db = FakeSession(project_id) + + monkeypatch.setattr( + "app.services.dataset_service.StorageService.persist_dataset_file", + lambda **_: { + "storage_path": "/tmp/real-orthophoto.tif", + "original_filename": "real-orthophoto.tif", + "stored_filename": "real-orthophoto.tif", + "content_type": "image/tiff", + "size_bytes": 11, + "checksum_sha256": "checksum", + }, + ) + monkeypatch.setattr( + "app.services.dataset_service.extract_raster_metadata", + lambda _path: { + "driver": "GTiff", + "crs": "EPSG:31370", + "bounds": [193277.5, 205708.2, 193777.5, 206208.2], + "resolution": [0.9765625, 0.9765625], + "dtype": ["uint8", "uint8", "uint8"], + }, + ) + + response = asyncio.run( + DatasetService.upload_dataset( + db=db, + project_id=project_id, + file=FakeUploadFile(), + dataset_type="raster", + source="user_upload", + ) + ) + + assert response.status == "ready" + assert response.crs == "EPSG:31370" + assert response.bounds_json == { + "minx": 193277.5, + "miny": 205708.2, + "maxx": 193777.5, + "maxy": 206208.2, + } + assert db.added[0].bounds_json == response.bounds_json + assert db.added[0].resolution_json == {"x": 0.9765625, "y": 0.9765625} + assert db.added[0].bands_json == {"dtype": ["uint8", "uint8", "uint8"]}