diff --git a/src/backend/app/projects/project_logic.py b/src/backend/app/projects/project_logic.py index 984e76ef..63513717 100644 --- a/src/backend/app/projects/project_logic.py +++ b/src/backend/app/projects/project_logic.py @@ -20,6 +20,8 @@ from app.projects import project_schemas from minio import S3Error from psycopg.rows import dict_row +from rio_tiler.io import Reader +from rio_tiler.errors import TileOutsideBounds async def get_centroids(db: Connection): @@ -269,3 +271,23 @@ def get_project_info_from_s3(project_id: uuid.UUID, task_id: uuid.UUID): except Exception as e: log.exception(f"An error occurred while retrieving assets info: {e}") raise HTTPException(status_code=500, detail=str(e)) + + +def read_tile_from_cog(cog_path: str, x: int, y: int, z: int) -> bytes: + """ + Helper function to safely read a tile from a COG file. + This function is run in a separate thread. + """ + try: + # Open the COG file safely and fetch the specified tile + with Reader(cog_path) as tiff: + img = tiff.tile(int(x), int(y), int(z), tilesize=256) + tile = img.render() # Render the tile as a PNG byte array + return tile + + except TileOutsideBounds: + # Reraise to handle in async function + raise + except Exception as e: + # Catch any unforeseen errors and reraise as needed + raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index 7c09f6c8..32c25657 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -34,9 +34,9 @@ from app.tasks import task_schemas from app.utils import geojson_to_kml, timestamp from app.users import user_schemas -from rio_tiler.io import Reader from rio_tiler.errors import TileOutsideBounds from minio.deleteobjects import DeleteObject +import asyncio router = APIRouter( @@ -591,7 +591,6 @@ async def odm_webhook( tags=["Image Processing"], ) async def get_orthophoto_tile( - # user_data: Annotated[AuthUser, Depends(login_required)], project_id: str, task_id: str, z: int, @@ -599,28 +598,19 @@ async def get_orthophoto_tile( y: int, ): """ - Endpoint to serve COG tiles as PNG images. - - :param project_id: ID of the project. - :param task_id: ID of the task. - :param z: Zoom level. - :param x: Tile X coordinate. - :param y: Tile Y coordinate. - :return: PNG image tile. + Endpoint to serve COG tiles as PNG images with safer and more efficient handling. """ + cog_path = get_cog_path(settings.S3_BUCKET_NAME, project_id, task_id) + try: - cog_path = get_cog_path(settings.S3_BUCKET_NAME, project_id, task_id) - with Reader(cog_path) as tiff: - try: - img = tiff.tile(int(x), int(y), int(z), tilesize=256, expression=None) - tile = img.render() - return Response(content=tile, media_type="image/png") - - except TileOutsideBounds: - return [] - raise HTTPException( - status_code=200, detail="Tile is outside the bounds of the image." - ) + # Use asyncio.to_thread to move blocking raster file I/O to a separate thread + tile = await asyncio.to_thread( + project_logic.read_tile_from_cog, cog_path, x, y, z + ) + return Response(content=tile, media_type="image/png") + except TileOutsideBounds: + # Return a 204 No Content if tile is outside the bounds + return Response(status_code=204, content="") except Exception as e: - raise HTTPException(status_code=500, detail=f"Error generating tile: {str(e)}") + raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")