Skip to content

Commit

Permalink
Merge pull request #325 from hotosm/feat/orthophoto-display
Browse files Browse the repository at this point in the history
refactor orthophoto tile retrieval for improved safety and efficiency
  • Loading branch information
Pradip-p authored Nov 5, 2024
2 parents bd739d8 + ed2157b commit a6531b2
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 23 deletions.
22 changes: 22 additions & 0 deletions src/backend/app/projects/project_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)}")
36 changes: 13 additions & 23 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -591,36 +591,26 @@ 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,
x: int,
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)}")

0 comments on commit a6531b2

Please sign in to comment.