Spaces:
Sleeping
Sleeping
File size: 6,361 Bytes
1dfb603 8b60930 3d0838a 3082096 5a3406a 1dfb603 5a3406a 1dfb603 5a3406a 1dfb603 3082096 1dfb603 3082096 1dfb603 8b60930 5a3406a 8b60930 5a3406a 1dfb603 8b60930 1dfb603 8b60930 1dfb603 8b60930 1dfb603 8b60930 3082096 8b60930 3082096 8b60930 3082096 8b60930 3082096 8b60930 3082096 8b60930 3082096 8b60930 3082096 1dfb603 3082096 8b60930 |
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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
import gradio as gr
import geopandas as gpd
import requests
import io
import re
from fastapi import FastAPI, HTTPException
from urllib.parse import unquote
import kml2geojson
# All helper functions from the previous version remain the same.
def get_gdf_from_file_url(file_url: str):
"""
Downloads a file from a URL (handling Google Drive links), determines if it's
KML or GeoJSON, and reads it into a GeoDataFrame.
"""
if "drive.google.com" in file_url:
match = re.search(r'/d/([a-zA-Z0-9_-]+)', file_url) or re.search(r'id=([a-zA-Z0-9_-]+)', file_url)
if match:
file_id = match.group(1)
download_url = f'https://drive.google.com/uc?export=download&id={file_id}'
else:
raise ValueError("Could not extract file ID from Google Drive URL.")
else:
download_url = file_url
response = requests.get(download_url, timeout=30)
response.raise_for_status()
bytes_data = io.BytesIO(response.content)
try:
string_data = response.content.decode('utf-8')
except UnicodeDecodeError:
string_data = response.content.decode('latin-1')
if string_data.strip().startswith("<?xml"):
bytes_data.seek(0)
geojson_data = kml2geojson.convert(bytes_data)
all_features = []
for gj in geojson_data:
all_features.extend(gj.get('features', []))
if not all_features:
raise ValueError("KML file parsed, but no features were found.")
input_gdf = gpd.GeoDataFrame.from_features(all_features, crs="EPSG:4326")
else:
bytes_data.seek(0)
input_gdf = gpd.read_file(bytes_data)
return input_gdf
def calculate_geometry_data(file_url: str) -> dict:
"""
Performs the core calculation and returns structured data (a dictionary).
This is used by both the API and the Gradio UI.
"""
if not file_url or not file_url.strip():
raise ValueError("No URL provided.")
input_gdf = get_gdf_from_file_url(file_url)
if input_gdf.empty:
raise ValueError("Could not find any geometric features in the file.")
geometry_gdf = None
for i, row in input_gdf.iterrows():
if row.geometry.geom_type in ['Polygon', 'MultiPolygon']:
geometry_gdf = gpd.GeoDataFrame([row], crs=input_gdf.crs)
break
if geometry_gdf is None:
raise ValueError("No valid Polygon or MultiPolygon geometry found in the file.")
if geometry_gdf.crs is None or geometry_gdf.crs.to_epsg() != 4326:
geometry_gdf = geometry_gdf.set_crs(epsg=4326, allow_override=True)
centroid = geometry_gdf.geometry.iloc[0].centroid
utm_zone = int((centroid.x + 180) / 6) + 1
epsg_code = 32600 + utm_zone if centroid.y >= 0 else 32700 + utm_zone
gdf_proj = geometry_gdf.to_crs(epsg=epsg_code)
area_m2 = gdf_proj.geometry.iloc[0].area
perimeter_m = gdf_proj.geometry.iloc[0].length
return {
"area": {"value": area_m2, "unit": "square_meters"},
"perimeter": {"value": perimeter_m, "unit": "meters"},
"area_km2": {"value": area_m2 / 1_000_000, "unit": "square_kilometers"},
"perimeter_km": {"value": perimeter_m / 1_000, "unit": "kilometers"}
}
def calculate_geometry_for_ui(file_url: str) -> tuple[str, str]:
"""
Main calculation logic for the Gradio UI.
Formats the data from calculate_geometry_data into human-readable strings.
"""
if not file_url or not file_url.strip():
return "Error: No URL provided.", ""
try:
data = calculate_geometry_data(file_url)
area_str = f"{data['area']['value']:,.2f} sq meters ({data['area_km2']['value']:,.2f} km²)"
perimeter_str = f"{data['perimeter']['value']:,.2f} meters ({data['perimeter_km']['value']:,.2f} km)"
return area_str, perimeter_str
except Exception as e:
return f"An error occurred: {e}", ""
# Initialize the FastAPI app first
app = FastAPI()
# --- NEW: Add a dedicated API endpoint ---
@app.get("/api/geometry")
def get_geometry_api(file_url: str):
"""
API endpoint to calculate geometry from a KML/GeoJSON file URL.
Returns data in JSON format.
"""
if not file_url:
raise HTTPException(status_code=400, detail="Missing 'file_url' query parameter.")
try:
# Decode the URL in case it's URL-encoded
decoded_url = unquote(file_url)
data = calculate_geometry_data(decoded_url)
return data
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# --- Gradio Interface using Blocks for more control ---
with gr.Blocks() as demo:
gr.Markdown(
"# Polygon Area & Perimeter Calculator 🗺️\n"
"Enter the public Google Drive URL of a KML or GeoJSON file, or load this page with "
"`?file_url=<your_url>` to process automatically."
)
# ... (rest of the Gradio UI code is the same)
with gr.Column():
url_input = gr.Textbox(
label="Google Drive KML/GeoJSON File URL",
placeholder="Paste URL here or load from page URL..."
)
submit_button = gr.Button("Calculate", variant="primary")
with gr.Row():
area_output = gr.Textbox(label="Calculated Area", interactive=False)
perimeter_output = gr.Textbox(label="Calculated Perimeter", interactive=False)
gr.Examples(
examples=[
["https://drive.google.com/file/d/123KCak3o1VUcrYQO6v-HxVPYDrYLw4Ft/view?usp=drivesdk"]
],
inputs=url_input
)
def process_url_on_load(request: gr.Request):
file_url = request.query_params.get("file_url")
if file_url:
area, perimeter = calculate_geometry_for_ui(file_url)
return {
url_input: file_url,
area_output: area,
perimeter_output: perimeter
}
return { url_input: "", area_output: "", perimeter_output: "" }
submit_button.click(
fn=calculate_geometry_for_ui,
inputs=url_input,
outputs=[area_output, perimeter_output]
)
demo.load(
fn=process_url_on_load,
inputs=None,
outputs=[url_input, area_output, perimeter_output]
)
# Mount the Gradio app onto the FastAPI app
app = gr.mount_gradio_app(app, demo, path="/")
|