File size: 5,076 Bytes
b7aeeca
 
 
 
 
 
8a8c13b
 
 
 
b7aeeca
8a8c13b
b7aeeca
 
 
 
8a8c13b
b7aeeca
8a8c13b
 
b7aeeca
 
 
 
 
 
8a8c13b
b7aeeca
 
 
 
 
8a8c13b
b7aeeca
 
 
 
 
 
 
8a8c13b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b7aeeca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a8c13b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b7aeeca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a8c13b
b7aeeca
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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)