From 9c7b071262dd63e7fc35493ba2ff822e4e81900f Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Fri, 27 Sep 2024 10:33:53 +0545 Subject: [PATCH] feat: added KML download format in download boundaries API --- src/backend/app/projects/project_routes.py | 40 ++++++++++++--- src/backend/app/utils.py | 57 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index 6a5b9bd0..fddf3626 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -1,3 +1,4 @@ +import json import os import uuid from typing import Annotated, Optional @@ -29,6 +30,7 @@ from app.users.user_deps import login_required from app.users.user_schemas import AuthUser from app.tasks import task_schemas +from app.utils import geojson_to_kml router = APIRouter( @@ -55,6 +57,10 @@ async def download_boundaries( default=False, description="Whether to split the area or not. Set to True to download task boundaries, otherwise AOI will be downloaded.", ), + export_type: str = Query( + default="geojson", + description="The format of the file to download. Options are 'geojson' or 'kml'.", + ), ): """Downloads the AOI or task boundaries for a project as a GeoJSON file. @@ -64,6 +70,7 @@ async def download_boundaries( user_data (AuthUser): The authenticated user data, checks if the user has permission. task_id (Optional[UUID]): The task ID in UUID format. If not provided and split_area is True, all tasks will be downloaded. split_area (bool): Whether to split the area or not. Set to True to download task boundaries, otherwise AOI will be downloaded. + export_type (str): The format of the file to download. Can be either 'geojson' or 'kml'. Returns: Response: The HTTP response object containing the downloaded file. @@ -76,17 +83,36 @@ async def download_boundaries( if out is None: raise HTTPException(status_code=404, detail="Geometry not found.") - filename = ( - (f"task_{task_id}.geojson" if task_id else "project_outline.geojson") - if split_area - else "project_aoi.geojson" - ) + # Determine filename and content-type based on export type + if export_type == "geojson": + filename = ( + (f"task_{task_id}.geojson" if task_id else "project_outline.geojson") + if split_area + else "project_aoi.geojson" + ) + content_type = "application/geo+json" + content = out + + elif export_type == "kml": + filename = ( + (f"task_{task_id}.kml" if task_id else "project_outline.kml") + if split_area + else "project_aoi.kml" + ) + content_type = "application/vnd.google-earth.kml+xml" + if isinstance(out, str): + out = json.loads(out) + content = geojson_to_kml(out) + else: + raise HTTPException( + status_code=400, detail="Invalid export type specified." + ) headers = { "Content-Disposition": f"attachment; filename={filename}", - "Content-Type": "application/geo+json", + "Content-Type": content_type, } - return Response(content=out, headers=headers) + return Response(content=content, headers=headers) except HTTPException as e: log.error(f"Error during boundaries download: {e.detail}") diff --git a/src/backend/app/utils.py b/src/backend/app/utils.py index ae30711f..cc347631 100644 --- a/src/backend/app/utils.py +++ b/src/backend/app/utils.py @@ -470,3 +470,60 @@ async def send_reset_password_email(email: str, token: str): ) except Exception as e: log.error(f"Error sending email: {e}") + + +def geojson_to_kml(geojson_data: dict) -> str: + """ + Converts GeoJSON data to KML format. + + Args: + geojson_data (dict): GeoJSON data as a dictionary. + + Returns: + str: KML formatted string. + """ + kml_output = [ + '', + '', + "", + ] + + # Iterate through each feature in the GeoJSON + for feature in geojson_data.get("features", []): + geometry_type = feature["geometry"]["type"] + coordinates = feature["geometry"]["coordinates"] + + # Create a KML Placemark for each feature + kml_output.append("") + + # Add properties as name or description if available + if "properties" in feature and feature["properties"]: + if "name" in feature["properties"]: + kml_output.append(f"{feature['properties']['name']}") + if "description" in feature["properties"]: + kml_output.append( + f"{feature['properties']['description']}" + ) + + # Handle different geometry types (Point, LineString, Polygon) + if geometry_type == "Point": + lon, lat = coordinates + kml_output.append(f"{lon},{lat}") + elif geometry_type == "LineString": + coord_string = " ".join([f"{lon},{lat}" for lon, lat in coordinates]) + kml_output.append( + f"{coord_string}" + ) + elif geometry_type == "Polygon": + for polygon in coordinates: + coord_string = " ".join([f"{lon},{lat}" for lon, lat in polygon]) + kml_output.append( + f"{coord_string}" + ) + + kml_output.append("") + + kml_output.append("") + kml_output.append("") + + return "\n".join(kml_output)