Spaces:
Sleeping
Sleeping
| 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 --- | |
| 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="/") | |