Skip to content

Commit

Permalink
feat: added KML download format in download boundaries API
Browse files Browse the repository at this point in the history
  • Loading branch information
Pradip-p committed Sep 27, 2024
1 parent 83919d4 commit 9c7b071
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 7 deletions.
40 changes: 33 additions & 7 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
import uuid
from typing import Annotated, Optional
Expand Down Expand Up @@ -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(
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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}")
Expand Down
57 changes: 57 additions & 0 deletions src/backend/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<kml xmlns="http://www.opengis.net/kml/2.2">',
"<Document>",
]

# 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("<Placemark>")

# Add properties as name or description if available
if "properties" in feature and feature["properties"]:
if "name" in feature["properties"]:
kml_output.append(f"<name>{feature['properties']['name']}</name>")
if "description" in feature["properties"]:
kml_output.append(
f"<description>{feature['properties']['description']}</description>"
)

# Handle different geometry types (Point, LineString, Polygon)
if geometry_type == "Point":
lon, lat = coordinates
kml_output.append(f"<Point><coordinates>{lon},{lat}</coordinates></Point>")
elif geometry_type == "LineString":
coord_string = " ".join([f"{lon},{lat}" for lon, lat in coordinates])
kml_output.append(
f"<LineString><coordinates>{coord_string}</coordinates></LineString>"
)
elif geometry_type == "Polygon":
for polygon in coordinates:
coord_string = " ".join([f"{lon},{lat}" for lon, lat in polygon])
kml_output.append(
f"<Polygon><outerBoundaryIs><LinearRing><coordinates>{coord_string}</coordinates></LinearRing></outerBoundaryIs></Polygon>"
)

kml_output.append("</Placemark>")

kml_output.append("</Document>")
kml_output.append("</kml>")

return "\n".join(kml_output)

0 comments on commit 9c7b071

Please sign in to comment.