UjjwalKGupta commited on
Commit
8b60930
·
verified ·
1 Parent(s): 3082096
Files changed (1) hide show
  1. app.py +77 -59
app.py CHANGED
@@ -3,8 +3,8 @@ import geopandas as gpd
3
  import requests
4
  import io
5
  import re
6
- from fastapi import FastAPI
7
- import kml2geojson
8
 
9
  # All helper functions from the previous version remain the same.
10
 
@@ -12,12 +12,6 @@ def get_gdf_from_file_url(file_url: str):
12
  """
13
  Downloads a file from a URL (handling Google Drive links), determines if it's
14
  KML or GeoJSON, and reads it into a GeoDataFrame.
15
-
16
- Args:
17
- file_url: The URL of the KML or GeoJSON file.
18
-
19
- Returns:
20
- A GeoDataFrame containing the data from the file.
21
  """
22
  if "drive.google.com" in file_url:
23
  match = re.search(r'/d/([a-zA-Z0-9_-]+)', file_url) or re.search(r'id=([a-zA-Z0-9_-]+)', file_url)
@@ -54,57 +48,90 @@ def get_gdf_from_file_url(file_url: str):
54
  return input_gdf
55
 
56
 
57
- def calculate_geometry(file_url: str) -> tuple[str, str]:
58
  """
59
- Main calculation logic.
 
60
  """
61
  if not file_url or not file_url.strip():
62
- return "Error: No URL provided.", ""
63
 
64
- try:
65
- input_gdf = get_gdf_from_file_url(file_url)
66
- if input_gdf.empty:
67
- return "Error: Could not find any geometric features in the file.", ""
68
-
69
- geometry_gdf = None
70
- for i, row in input_gdf.iterrows():
71
- if row.geometry.geom_type in ['Polygon', 'MultiPolygon']:
72
- geometry_gdf = gpd.GeoDataFrame([row], crs=input_gdf.crs)
73
- break
74
-
75
- if geometry_gdf is None:
76
- return "Error: No valid Polygon or MultiPolygon geometry found in the file.", ""
77
-
78
- if geometry_gdf.crs is None or geometry_gdf.crs.to_epsg() != 4326:
79
- geometry_gdf = geometry_gdf.set_crs(epsg=4326, allow_override=True)
80
-
81
- centroid = geometry_gdf.geometry.iloc[0].centroid
82
- utm_zone = int((centroid.x + 180) / 6) + 1
83
- epsg_code = 32600 + utm_zone if centroid.y >= 0 else 32700 + utm_zone
84
- gdf_proj = geometry_gdf.to_crs(epsg=epsg_code)
85
-
86
- area_m2 = gdf_proj.geometry.iloc[0].area
87
- perimeter_m = gdf_proj.geometry.iloc[0].length
88
-
89
- area_km2 = area_m2 / 1_000_000
90
- perimeter_km = perimeter_m / 1_000
91
-
92
- area_str = f"{area_m2:,.2f} square meters ({area_km2:,.2f} km²)"
93
- perimeter_str = f"{perimeter_m:,.2f} meters ({perimeter_km:,.2f} km)"
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  return area_str, perimeter_str
96
  except Exception as e:
97
  return f"An error occurred: {e}", ""
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- # --- New Gradio Interface using Blocks for more control ---
101
  with gr.Blocks() as demo:
102
  gr.Markdown(
103
  "# Polygon Area & Perimeter Calculator 🗺️\n"
104
  "Enter the public Google Drive URL of a KML or GeoJSON file, or load this page with "
105
  "`?file_url=<your_url>` to process automatically."
106
  )
107
-
108
  with gr.Column():
109
  url_input = gr.Textbox(
110
  label="Google Drive KML/GeoJSON File URL",
@@ -123,38 +150,29 @@ with gr.Blocks() as demo:
123
  inputs=url_input
124
  )
125
 
126
- # This function runs when the page loads to check for a URL query parameter
127
  def process_url_on_load(request: gr.Request):
128
  file_url = request.query_params.get("file_url")
129
  if file_url:
130
- area, perimeter = calculate_geometry(file_url)
131
- # Return a dictionary to update the components
132
  return {
133
  url_input: file_url,
134
  area_output: area,
135
  perimeter_output: perimeter
136
  }
137
- # If no URL, return empty values
138
- return {
139
- url_input: "",
140
- area_output: "",
141
- perimeter_output: ""
142
- }
143
-
144
- # Define the event handlers
145
  submit_button.click(
146
- fn=calculate_geometry,
147
  inputs=url_input,
148
  outputs=[area_output, perimeter_output]
149
  )
150
 
151
  demo.load(
152
  fn=process_url_on_load,
153
- inputs=None, # The 'load' event doesn't take direct component inputs
154
  outputs=[url_input, area_output, perimeter_output]
155
  )
156
 
157
- # Initialize the FastAPI app and mount the Gradio interface
158
- app = FastAPI()
159
- app = gr.mount_gradio_app(app, demo, path="/")
160
 
 
 
 
3
  import requests
4
  import io
5
  import re
6
+ from fastapi import FastAPI, HTTPException
7
+ from urllib.parse import unquote
8
 
9
  # All helper functions from the previous version remain the same.
10
 
 
12
  """
13
  Downloads a file from a URL (handling Google Drive links), determines if it's
14
  KML or GeoJSON, and reads it into a GeoDataFrame.
 
 
 
 
 
 
15
  """
16
  if "drive.google.com" in file_url:
17
  match = re.search(r'/d/([a-zA-Z0-9_-]+)', file_url) or re.search(r'id=([a-zA-Z0-9_-]+)', file_url)
 
48
  return input_gdf
49
 
50
 
51
+ def calculate_geometry_data(file_url: str) -> dict:
52
  """
53
+ Performs the core calculation and returns structured data (a dictionary).
54
+ This is used by both the API and the Gradio UI.
55
  """
56
  if not file_url or not file_url.strip():
57
+ raise ValueError("No URL provided.")
58
 
59
+ input_gdf = get_gdf_from_file_url(file_url)
60
+ if input_gdf.empty:
61
+ raise ValueError("Could not find any geometric features in the file.")
62
+
63
+ geometry_gdf = None
64
+ for i, row in input_gdf.iterrows():
65
+ if row.geometry.geom_type in ['Polygon', 'MultiPolygon']:
66
+ geometry_gdf = gpd.GeoDataFrame([row], crs=input_gdf.crs)
67
+ break
68
+
69
+ if geometry_gdf is None:
70
+ raise ValueError("No valid Polygon or MultiPolygon geometry found in the file.")
71
+
72
+ if geometry_gdf.crs is None or geometry_gdf.crs.to_epsg() != 4326:
73
+ geometry_gdf = geometry_gdf.set_crs(epsg=4326, allow_override=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ centroid = geometry_gdf.geometry.iloc[0].centroid
76
+ utm_zone = int((centroid.x + 180) / 6) + 1
77
+ epsg_code = 32600 + utm_zone if centroid.y >= 0 else 32700 + utm_zone
78
+ gdf_proj = geometry_gdf.to_crs(epsg=epsg_code)
79
+
80
+ area_m2 = gdf_proj.geometry.iloc[0].area
81
+ perimeter_m = gdf_proj.geometry.iloc[0].length
82
+
83
+ return {
84
+ "area": {"value": area_m2, "unit": "square_meters"},
85
+ "perimeter": {"value": perimeter_m, "unit": "meters"},
86
+ "area_km2": {"value": area_m2 / 1_000_000, "unit": "square_kilometers"},
87
+ "perimeter_km": {"value": perimeter_m / 1_000, "unit": "kilometers"}
88
+ }
89
+
90
+
91
+ def calculate_geometry_for_ui(file_url: str) -> tuple[str, str]:
92
+ """
93
+ Main calculation logic for the Gradio UI.
94
+ Formats the data from calculate_geometry_data into human-readable strings.
95
+ """
96
+ if not file_url or not file_url.strip():
97
+ return "Error: No URL provided.", ""
98
+ try:
99
+ data = calculate_geometry_data(file_url)
100
+ area_str = f"{data['area']['value']:,.2f} sq meters ({data['area_km2']['value']:,.2f} km²)"
101
+ perimeter_str = f"{data['perimeter']['value']:,.2f} meters ({data['perimeter_km']['value']:,.2f} km)"
102
  return area_str, perimeter_str
103
  except Exception as e:
104
  return f"An error occurred: {e}", ""
105
 
106
+ # Initialize the FastAPI app first
107
+ app = FastAPI()
108
+
109
+ # --- NEW: Add a dedicated API endpoint ---
110
+ @app.get("/api/geometry")
111
+ def get_geometry_api(file_url: str):
112
+ """
113
+ API endpoint to calculate geometry from a KML/GeoJSON file URL.
114
+ Returns data in JSON format.
115
+ """
116
+ if not file_url:
117
+ raise HTTPException(status_code=400, detail="Missing 'file_url' query parameter.")
118
+ try:
119
+ # Decode the URL in case it's URL-encoded
120
+ decoded_url = unquote(file_url)
121
+ data = calculate_geometry_data(decoded_url)
122
+ return data
123
+ except Exception as e:
124
+ raise HTTPException(status_code=500, detail=str(e))
125
+
126
 
127
+ # --- Gradio Interface using Blocks for more control ---
128
  with gr.Blocks() as demo:
129
  gr.Markdown(
130
  "# Polygon Area & Perimeter Calculator 🗺️\n"
131
  "Enter the public Google Drive URL of a KML or GeoJSON file, or load this page with "
132
  "`?file_url=<your_url>` to process automatically."
133
  )
134
+ # ... (rest of the Gradio UI code is the same)
135
  with gr.Column():
136
  url_input = gr.Textbox(
137
  label="Google Drive KML/GeoJSON File URL",
 
150
  inputs=url_input
151
  )
152
 
 
153
  def process_url_on_load(request: gr.Request):
154
  file_url = request.query_params.get("file_url")
155
  if file_url:
156
+ area, perimeter = calculate_geometry_for_ui(file_url)
 
157
  return {
158
  url_input: file_url,
159
  area_output: area,
160
  perimeter_output: perimeter
161
  }
162
+ return { url_input: "", area_output: "", perimeter_output: "" }
163
+
 
 
 
 
 
 
164
  submit_button.click(
165
+ fn=calculate_geometry_for_ui,
166
  inputs=url_input,
167
  outputs=[area_output, perimeter_output]
168
  )
169
 
170
  demo.load(
171
  fn=process_url_on_load,
172
+ inputs=None,
173
  outputs=[url_input, area_output, perimeter_output]
174
  )
175
 
 
 
 
176
 
177
+ # Mount the Gradio app onto the FastAPI app
178
+ app = gr.mount_gradio_app(app, demo, path="/")