Skip to content

Commit

Permalink
Merge branch 'feat/orthophoto-display' of github.com:hotosm/drone-tm …
Browse files Browse the repository at this point in the history
…into feat/orthophoto-display
  • Loading branch information
Sujit committed Nov 5, 2024
2 parents 567a639 + ed2157b commit 14b8761
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 38 deletions.
12 changes: 6 additions & 6 deletions src/backend/app/projects/image_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def download_images_from_s3(self, bucket_name, local_dir):
:param local_dir: Local directory to save the images.
:return: List of local image file paths.
"""
prefix = f"projects/{self.project_id}/{self.task_id}"
prefix = f"dtm-data/projects/{self.project_id}/{self.task_id}"

objects = list_objects_from_bucket(bucket_name, prefix)

Expand Down Expand Up @@ -167,7 +167,9 @@ def process_images_from_s3(self, bucket_name, name=None, options=[], webhook=Non
)

# Upload the results into s3
s3_path = f"projects/{self.project_id}/{self.task_id}/assets.zip"
s3_path = (
f"dtm-data/projects/{self.project_id}/{self.task_id}/assets.zip"
)
add_file_to_bucket(bucket_name, path_to_download, s3_path)
# now update the task as completed in Db.
# Call the async function using asyncio
Expand Down Expand Up @@ -228,7 +230,7 @@ async def download_and_upload_assets_from_odm_to_s3(
assets_path = task.download_zip(output_file_path)

# Upload the results into S3 (Minio)
s3_path = f"projects/{dtm_project_id}/{dtm_task_id}/assets.zip"
s3_path = f"dtm-data/projects/{dtm_project_id}/{dtm_task_id}/assets.zip"
log.info(f"Uploading {output_file_path} to S3 path: {s3_path}")
add_file_to_bucket(settings.S3_BUCKET_NAME, assets_path, s3_path)

Expand All @@ -249,9 +251,7 @@ async def download_and_upload_assets_from_odm_to_s3(
log.info(f"Orthophoto found at {orthophoto_path}")

# Upload the orthophoto to S3
s3_ortho_path = (
f"projects/{dtm_project_id}/{dtm_task_id}/orthophoto/odm_orthophoto.tif"
)
s3_ortho_path = f"dtm-data/projects/{dtm_project_id}/{dtm_task_id}/orthophoto/odm_orthophoto.tif"
log.info(f"Uploading orthophoto to S3 path: {s3_ortho_path}")
add_file_to_bucket(settings.S3_BUCKET_NAME, orthophoto_path, s3_ortho_path)

Expand Down
28 changes: 25 additions & 3 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 @@ -69,7 +71,7 @@ async def upload_file_to_s3(
str: The S3 URL for the uploaded file.
"""
# Define the S3 file path
file_path = f"/projects/{project_id}/{file_name}"
file_path = f"dtm-data/projects/{project_id}/{file_name}"

# Read the file bytes
file_bytes = await file.read()
Expand Down Expand Up @@ -227,7 +229,7 @@ def get_project_info_from_s3(project_id: uuid.UUID, task_id: uuid.UUID):
"""
try:
# Prefix for the images
images_prefix = f"projects/{project_id}/{task_id}/images/"
images_prefix = f"dtm-data/projects/{project_id}/{task_id}/images/"

# List and count the images
objects = list_objects_from_bucket(
Expand All @@ -241,7 +243,7 @@ def get_project_info_from_s3(project_id: uuid.UUID, task_id: uuid.UUID):
# Generate a presigned URL for the assets ZIP file
try:
# Check if the object exists
assets_path = f"projects/{project_id}/{task_id}/assets.zip"
assets_path = f"dtm-data/projects/{project_id}/{task_id}/assets.zip"
get_object_metadata(settings.S3_BUCKET_NAME, assets_path)

# If it exists, generate the presigned URL
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)}")
44 changes: 19 additions & 25 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 @@ -315,11 +315,15 @@ async def generate_presigned_url(

# Process each image in the request
for image in data.image_name:
image_path = f"projects/{data.project_id}/{data.task_id}/images/{image}"
image_path = (
f"dtm-data/projects/{data.project_id}/{data.task_id}/images/{image}"
)

# If replace_existing is True, delete the image first
if replace_existing:
image_dir = f"projects/{data.project_id}/{data.task_id}/images/"
image_dir = (
f"dtm-data/projects/{data.project_id}/{data.task_id}/images/"
)
try:
# Prepare the list of objects to delete (recursively if necessary)
delete_object_list = map(
Expand Down Expand Up @@ -587,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("dtm-data", 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)}")
3 changes: 1 addition & 2 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,7 @@ def set_image_url(cls, values):
"""Set image_url before rendering the model."""
project_id = values.id
if project_id:
image_dir = f"projects/{project_id}/map_screenshot.png"
# values.image_url = get_image_dir_url(settings.S3_BUCKET_NAME, image_dir)
image_dir = f"dtm-data/projects/{project_id}/map_screenshot.png"
values.image_url = get_presigned_url(settings.S3_BUCKET_NAME, image_dir, 5)
return values

Expand Down
2 changes: 1 addition & 1 deletion src/backend/app/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def get_cog_path(bucket_name: str, project_id: str, task_id: str):
str: The presigned URL to access the COG file.
"""
# Construct the S3 path for the COG file
s3_path = f"projects/{project_id}/{task_id}/orthophoto/odm_orthophoto.tif"
s3_path = f"dtm-data/projects/{project_id}/{task_id}/orthophoto/odm_orthophoto.tif"

# Get the presigned URL
return get_presigned_url(bucket_name, s3_path)
4 changes: 3 additions & 1 deletion src/backend/app/waypoints/waypoint_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ async def get_task_waypoint(
dem_path = f"/tmp/{uuid.uuid4()}/dem.tif"
try:
get_file_from_bucket(
settings.S3_BUCKET_NAME, f"projects/{project_id}/dem.tif", dem_path
settings.S3_BUCKET_NAME,
f"dtm-data/projects/{project_id}/dem.tif",
dem_path,
)
# TODO: Do this with inmemory data
outfile_with_elevation = "/tmp/output_file_with_elevation.geojson"
Expand Down

0 comments on commit 14b8761

Please sign in to comment.