UjjwalKGupta commited on
Commit
1dfb603
·
verified ·
1 Parent(s): 124373b

update KML

Browse files
Files changed (1) hide show
  1. app.py +146 -43
app.py CHANGED
@@ -1,50 +1,153 @@
 
 
 
 
 
1
  from fastapi import FastAPI
2
- from pydantic import BaseModel
3
 
4
- # Initialize the FastAPI application
5
- app = FastAPI(
6
- title="JSON Calculator API",
7
- description="A simple REST API to perform addition and multiplication on two numbers and return the result as a JSON object.",
8
- version="1.0.0",
9
- )
10
-
11
- # Define the structure of the incoming request data using Pydantic
12
- # This ensures that any request to the API has these two float fields
13
- class CalculationInput(BaseModel):
14
- number_1: float
15
- number_2: float
16
-
17
- @app.get("/")
18
- def read_root():
19
  """
20
- A simple root endpoint to check if the API is running.
 
 
 
 
 
 
 
21
  """
22
- return {"status": "ok", "message": "Welcome to the JSON Calculator API. Send a POST request to /calculate."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- @app.post("/calculate")
25
- def calculate_numbers(data: CalculationInput):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  """
27
- This endpoint receives two numbers, calculates their sum and product,
28
- and returns the result in a structured JSON format.
 
 
 
 
 
 
29
  """
30
- num1 = data.number_1
31
- num2 = data.number_2
32
-
33
- # Perform the calculations
34
- addition_result = num1 + num2
35
- multiplication_result = num1 * num2
36
-
37
- # Create the response dictionary
38
- results_dict = {
39
- "calculation_inputs": {
40
- "number_1": num1,
41
- "number_2": num2
42
- },
43
- "calculation_outputs": {
44
- "addition": addition_result,
45
- "multiplication": multiplication_result
46
- }
47
- }
48
-
49
- # FastAPI automatically converts this dictionary to a JSON response
50
- return results_dict
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import geopandas as gpd
3
+ import requests
4
+ import io
5
+ import re
6
  from fastapi import FastAPI
7
+ import kml2geojson # Using this library for robust KML parsing
8
 
9
+ def get_gdf_from_file_url(file_url: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  """
11
+ Downloads a file from a URL (handling Google Drive links), determines if it's
12
+ KML or GeoJSON, and reads it into a GeoDataFrame.
13
+
14
+ Args:
15
+ file_url: The URL of the KML or GeoJSON file.
16
+
17
+ Returns:
18
+ A GeoDataFrame containing the data from the file.
19
  """
20
+ # --- Handle different Google Drive URL formats ---
21
+ if "drive.google.com" in file_url:
22
+ # Use regex to extract the file ID robustly
23
+ match = re.search(r'/d/([a-zA-Z0-9_-]+)', file_url) or re.search(r'id=([a-zA-Z0-9_-]+)', file_url)
24
+ if match:
25
+ file_id = match.group(1)
26
+ download_url = f'https://drive.google.com/uc?export=download&id={file_id}'
27
+ else:
28
+ raise ValueError("Could not extract file ID from Google Drive URL.")
29
+ else:
30
+ download_url = file_url
31
+
32
+ # --- Download the file content ---
33
+ response = requests.get(download_url, timeout=30)
34
+ response.raise_for_status()
35
+
36
+ bytes_data = io.BytesIO(response.content)
37
+ # Decode for sniffing, handle potential encoding issues
38
+ try:
39
+ string_data = response.content.decode('utf-8')
40
+ except UnicodeDecodeError:
41
+ string_data = response.content.decode('latin-1')
42
+
43
 
44
+ # --- Determine file type and parse ---
45
+ # If it looks like XML, it's likely KML.
46
+ if string_data.strip().startswith("<?xml"):
47
+ # kml2geojson requires the file path or a stream of the file
48
+ bytes_data.seek(0) # Reset stream position
49
+ geojson_data = kml2geojson.convert(bytes_data)
50
+
51
+ # Extract features from the converted GeoJSON
52
+ # The output of kml2geojson is a list of geojson objects
53
+ all_features = []
54
+ for gj in geojson_data:
55
+ all_features.extend(gj.get('features', []))
56
+
57
+ if not all_features:
58
+ raise ValueError("KML file parsed, but no features were found.")
59
+
60
+ input_gdf = gpd.GeoDataFrame.from_features(all_features, crs="EPSG:4326")
61
+ else:
62
+ # Assume it's GeoJSON or another format GeoPandas can read directly
63
+ bytes_data.seek(0) # Reset stream position
64
+ input_gdf = gpd.read_file(bytes_data)
65
+
66
+ return input_gdf
67
+
68
+
69
+ def calculate_geometry(file_url: str) -> tuple[str, str]:
70
  """
71
+ Main function for the Gradio interface. It orchestrates the downloading,
72
+ parsing, validation, and calculation of area and perimeter.
73
+
74
+ Args:
75
+ file_url: The URL of the KML or GeoJSON file.
76
+
77
+ Returns:
78
+ A tuple containing the formatted area and perimeter strings.
79
  """
80
+ if not file_url or not file_url.strip():
81
+ return "Error: Please provide a URL.", ""
82
+
83
+ try:
84
+ # 1. Get GeoDataFrame from the URL
85
+ input_gdf = get_gdf_from_file_url(file_url)
86
+
87
+ if input_gdf.empty:
88
+ return "Error: Could not find any geometric features in the file.", ""
89
+
90
+ # 2. Find the first valid polygon in the GeoDataFrame
91
+ geometry_gdf = None
92
+ for i, row in input_gdf.iterrows():
93
+ if row.geometry.geom_type in ['Polygon', 'MultiPolygon']:
94
+ # Create a new GDF with just this valid geometry
95
+ geometry_gdf = gpd.GeoDataFrame([row], crs=input_gdf.crs)
96
+ break
97
+
98
+ if geometry_gdf is None:
99
+ return "Error: No valid Polygon or MultiPolygon geometry found in the file.", ""
100
+
101
+ # 3. Reproject for accurate measurement
102
+ # Ensure the initial CRS is set correctly (WGS84) before reprojecting
103
+ if geometry_gdf.crs is None or geometry_gdf.crs.to_epsg() != 4326:
104
+ geometry_gdf = geometry_gdf.set_crs(epsg=4326, allow_override=True)
105
+
106
+ # Determine the correct UTM zone for accurate measurements in meters
107
+ centroid = geometry_gdf.geometry.iloc[0].centroid
108
+ utm_zone = int((centroid.x + 180) / 6) + 1
109
+ epsg_code = 32600 + utm_zone if centroid.y >= 0 else 32700 + utm_zone
110
+
111
+ gdf_proj = geometry_gdf.to_crs(epsg=epsg_code)
112
+
113
+ # 4. Calculate area and perimeter
114
+ area_m2 = gdf_proj.geometry.iloc[0].area
115
+ perimeter_m = gdf_proj.geometry.iloc[0].length
116
+
117
+ # 5. Format output
118
+ area_km2 = area_m2 / 1_000_000
119
+ perimeter_km = perimeter_m / 1_000
120
+
121
+ area_str = f"{area_m2:,.2f} square meters ({area_km2:,.2f} km²)"
122
+ perimeter_str = f"{perimeter_m:,.2f} meters ({perimeter_km:,.2f} km)"
123
+
124
+ return area_str, perimeter_str
125
+
126
+ except Exception as e:
127
+ return f"An error occurred: {e}", ""
128
+
129
+ # Create the Gradio user interface
130
+ gradio_interface = gr.Interface(
131
+ fn=calculate_geometry,
132
+ inputs=gr.Textbox(
133
+ label="Google Drive KML/GeoJSON File URL",
134
+ placeholder="Paste the URL to your file here..."
135
+ ),
136
+ outputs=[
137
+ gr.Textbox(label="Calculated Area", interactive=False),
138
+ gr.Textbox(label="Calculated Perimeter", interactive=False)
139
+ ],
140
+ title="Polygon Area & Perimeter Calculator 🗺️",
141
+ description=(
142
+ "Enter the public Google Drive URL of a KML or GeoJSON file that contains a polygon. "
143
+ "The tool will find the first valid polygon and calculate its area and perimeter."
144
+ ),
145
+ examples=[
146
+ ["https://drive.google.com/file/d/123KCak3o1VUcrYQO6v-HxVPYDrYLw4Ft/view?usp=drivesdk"]
147
+ ],
148
+ allow_flagging="never"
149
+ )
150
+
151
+ # Initialize the FastAPI app and mount the Gradio interface
152
+ app = FastAPI()
153
+ app = gr.mount_gradio_app(app, gradio_interface, path="/")