from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import httpx import pyproj from typing import List, Dict, Optional, Any from distance import calculate_distance from area import calculate_area from coordinate_converter import convert_coordinates from terrain import extract_terrain_data app = FastAPI(title="GIS Tools API") # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Define request and response models class CoordinateRequest(BaseModel): latitude: float longitude: float crs: str = "EPSG:4326" class ElevationResponse(BaseModel): latitude: float longitude: float elevation: float wgs84_latitude: float wgs84_longitude: float original_crs: str class CRSInfo(BaseModel): code: str name: str class DistanceRequest(BaseModel): point1: CoordinateRequest point2: CoordinateRequest class AreaRequest(BaseModel): coordinates: List[CoordinateRequest] class TerrainRequest(BaseModel): latitude: float longitude: float crs: str = "EPSG:4326" # Helper: Convert to WGS84 def convert_to_wgs84(lon, lat, source_crs): if source_crs == "EPSG:4326": return (lon, lat) try: transformer = pyproj.Transformer.from_crs(source_crs, "EPSG:4326", always_xy=True) lon, lat = transformer.transform(lon, lat) return (lon, lat) except Exception as e: raise Exception(f"Coordinate transformation error: {str(e)}") # Helper: Query Open-Meteo async def query_open_meteo(lat, lon): url = f"https://api.open-meteo.com/v1/elevation?latitude={lat}&longitude={lon}" async with httpx.AsyncClient() as client: try: response = await client.get(url) response.raise_for_status() data = response.json() if "elevation" in data and isinstance(data["elevation"], list) and len(data["elevation"]) > 0: return float(data["elevation"][0]) else: raise Exception("No elevation data found in API response") except httpx.HTTPStatusError as e: raise Exception(f"Elevation API error: {str(e)}") except Exception as e: raise Exception(f"Error querying elevation: {str(e)}") # Route for fetching elevation data @app.post("/api/elevation", response_model=ElevationResponse) async def get_elevation(request: CoordinateRequest): try: wgs84_coords = convert_to_wgs84(request.longitude, request.latitude, request.crs) elevation = await query_open_meteo(wgs84_coords[1], wgs84_coords[0]) return ElevationResponse( latitude=request.latitude, longitude=request.longitude, elevation=elevation, wgs84_latitude=wgs84_coords[1], wgs84_longitude=wgs84_coords[0], original_crs=request.crs ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # Route for calculating distance @app.post("/api/distance") async def calculate_distance_route(request: DistanceRequest): # Extract coordinates and CRS p1 = (request.point1.longitude, request.point1.latitude, request.point1.crs) p2 = (request.point2.longitude, request.point2.latitude, request.point2.crs) return {"distance": calculate_distance(p1, p2)} # Route for calculating area @app.post("/api/area") async def calculate_area_route(request: AreaRequest): # Convert CoordinateRequest list to list of (lon, lat, crs) coords = [(c.longitude, c.latitude, c.crs) for c in request.coordinates] return {"area": calculate_area(coords)} # Route for converting coordinates @app.post("/api/coordinate-converter") async def coordinate_converter_route(request: CoordinateRequest): return convert_coordinates(request.longitude, request.latitude, request.crs) # Route for extracting terrain data @app.post("/api/terrain") async def terrain_data_route(request: TerrainRequest): return await extract_terrain_data(request.latitude, request.longitude, request.crs) # Route for fetching CRS list @app.get("/api/crs-list", response_model=List[CRSInfo]) async def get_crs_list(): crs_list = [ {"code": "EPSG:4326", "name": "WGS 84"}, {"code": "EPSG:3857", "name": "Web Mercator"}, {"code": "EPSG:2154", "name": "RGF93 / Lambert-93"}, {"code": "EPSG:27700", "name": "OSGB 1936 / British National Grid"}, {"code": "EPSG:3785", "name": "Popular Visualisation CRS / Mercator"}, {"code": "EPSG:4269", "name": "NAD83"}, {"code": "EPSG:4267", "name": "NAD27"}, {"code": "EPSG:32633", "name": "WGS 84 / UTM zone 33N"}, ] return crs_list # Health check endpoint @app.get("/api/health") async def health_check(): return {"status": "ok"} if __name__ == "__main__": import uvicorn import os port = int(os.environ.get("PORT", 7860)) uvicorn.run(app, host="0.0.0.0", port=port)