UjjwalKGupta commited on
Commit
3082096
·
verified ·
1 Parent(s): 53aa699

update FileURL

Browse files
Files changed (1) hide show
  1. app.py +66 -59
app.py CHANGED
@@ -4,7 +4,9 @@ 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
  """
@@ -17,9 +19,7 @@ def get_gdf_from_file_url(file_url: str):
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)
@@ -29,38 +29,26 @@ def get_gdf_from_file_url(file_url: str):
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
@@ -68,53 +56,36 @@ def get_gdf_from_file_url(file_url: str):
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
 
@@ -122,32 +93,68 @@ def calculate_geometry(file_url: str) -> tuple[str, str]:
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="/")
 
 
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
 
11
  def get_gdf_from_file_url(file_url: str):
12
  """
 
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)
24
  if match:
25
  file_id = match.group(1)
 
29
  else:
30
  download_url = file_url
31
 
 
32
  response = requests.get(download_url, timeout=30)
33
  response.raise_for_status()
34
 
35
  bytes_data = io.BytesIO(response.content)
 
36
  try:
37
  string_data = response.content.decode('utf-8')
38
  except UnicodeDecodeError:
39
  string_data = response.content.decode('latin-1')
40
 
 
 
 
41
  if string_data.strip().startswith("<?xml"):
42
+ bytes_data.seek(0)
 
43
  geojson_data = kml2geojson.convert(bytes_data)
 
 
 
44
  all_features = []
45
  for gj in geojson_data:
46
  all_features.extend(gj.get('features', []))
 
47
  if not all_features:
48
  raise ValueError("KML file parsed, but no features were found.")
 
49
  input_gdf = gpd.GeoDataFrame.from_features(all_features, crs="EPSG:4326")
50
  else:
51
+ bytes_data.seek(0)
 
52
  input_gdf = gpd.read_file(bytes_data)
53
 
54
  return input_gdf
 
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
 
 
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",
111
+ placeholder="Paste URL here or load from page URL..."
112
+ )
113
+ submit_button = gr.Button("Calculate", variant="primary")
114
+
115
+ with gr.Row():
116
+ area_output = gr.Textbox(label="Calculated Area", interactive=False)
117
+ perimeter_output = gr.Textbox(label="Calculated Perimeter", interactive=False)
118
+
119
+ gr.Examples(
120
+ examples=[
121
+ ["https://drive.google.com/file/d/123KCak3o1VUcrYQO6v-HxVPYDrYLw4Ft/view?usp=drivesdk"]
122
+ ],
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
+