UjjwalKGupta commited on
Commit
ee32a0f
·
verified ·
1 Parent(s): f6b20a0

Add geometry API

Browse files
Files changed (1) hide show
  1. app.py +56 -5
app.py CHANGED
@@ -17,6 +17,8 @@ import requests
17
  import kml2geojson
18
  import folium
19
  import xml.etree.ElementTree as ET
 
 
20
 
21
  # --- Helper Functions ---
22
 
@@ -41,7 +43,7 @@ def one_time_setup():
41
  print(f"Earth Engine initialization failed: {inner_e}")
42
 
43
 
44
- def _process_spatial_data(data_bytes):
45
  """Core function to process bytes of a KML or GeoJSON file."""
46
  # Read the first few bytes to determine file type without consuming the stream
47
  start_of_file = data_bytes.read(100)
@@ -73,7 +75,7 @@ def get_gdf_from_file(file_obj):
73
  data_bytes = BytesIO(f.read())
74
  return _process_spatial_data(data_bytes)
75
 
76
- def get_gdf_from_url(url):
77
  """Downloads and reads a KML/GeoJSON from a URL."""
78
  if not url or not url.strip():
79
  return None
@@ -99,7 +101,7 @@ def get_gdf_from_url(url):
99
  raise ValueError(f"Failed to download file from URL: {e}")
100
 
101
 
102
- def find_best_epsg(geometry):
103
  """Finds the most suitable EPSG code for a given geometry based on its centroid."""
104
  if geometry.geom_type == "Polygon":
105
  centroid = geometry.centroid
@@ -132,13 +134,13 @@ def shape_3d_to_2d(shape):
132
  return transform(lambda x, y, z: (x, y), shape)
133
  return shape
134
 
135
- def preprocess_gdf(gdf):
136
  """Preprocesses a GeoDataFrame by converting geometries to 2D and fixing invalid ones."""
137
  gdf["geometry"] = gdf["geometry"].apply(shape_3d_to_2d)
138
  gdf["geometry"] = gdf.buffer(0)
139
  return gdf
140
 
141
- def to_best_crs(gdf):
142
  """Converts a GeoDataFrame to the most suitable CRS."""
143
  if not gdf.empty and gdf["geometry"].iloc[0] is not None:
144
  best_epsg_code = find_best_epsg(gdf.to_crs(epsg=4326)["geometry"].iloc[0])
@@ -364,6 +366,54 @@ def process_and_display(file_obj, url_str, buffer_m, progress=gr.Progress()):
364
  progress(1, desc="Done!")
365
  return m._repr_html_(), None, stats_df, dem_html, slope_html, geometry_json, buffer_geometry_json
366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  def calculate_indices(
368
  geometry_json, buffer_geometry_json, veg_indices, evi_vars, date_range,
369
  min_year, max_year, progress=gr.Progress()
@@ -678,6 +728,7 @@ with gr.Blocks(theme=theme, title="Kamlan: KML Analyzer") as demo:
678
  # --- Event Handlers ---
679
  def process_on_load(request: gr.Request):
680
  """Checks for a 'file_url' query parameter when the app loads."""
 
681
  return request.query_params.get("file_url", "")
682
 
683
  demo.load(process_on_load, None, url_input)
 
17
  import kml2geojson
18
  import folium
19
  import xml.etree.ElementTree as ET
20
+ from fastapi import FastAPI, HTTPException
21
+ from urllib.parse import unquote
22
 
23
  # --- Helper Functions ---
24
 
 
43
  print(f"Earth Engine initialization failed: {inner_e}")
44
 
45
 
46
+ def _process_spatial_data(data_bytes: BytesIO) -> gpd.GeoDataFrame:
47
  """Core function to process bytes of a KML or GeoJSON file."""
48
  # Read the first few bytes to determine file type without consuming the stream
49
  start_of_file = data_bytes.read(100)
 
75
  data_bytes = BytesIO(f.read())
76
  return _process_spatial_data(data_bytes)
77
 
78
+ def get_gdf_from_url(url: str) -> gpd.GeoDataFrame:
79
  """Downloads and reads a KML/GeoJSON from a URL."""
80
  if not url or not url.strip():
81
  return None
 
101
  raise ValueError(f"Failed to download file from URL: {e}")
102
 
103
 
104
+ def find_best_epsg(geometry) -> int:
105
  """Finds the most suitable EPSG code for a given geometry based on its centroid."""
106
  if geometry.geom_type == "Polygon":
107
  centroid = geometry.centroid
 
134
  return transform(lambda x, y, z: (x, y), shape)
135
  return shape
136
 
137
+ def preprocess_gdf(gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
138
  """Preprocesses a GeoDataFrame by converting geometries to 2D and fixing invalid ones."""
139
  gdf["geometry"] = gdf["geometry"].apply(shape_3d_to_2d)
140
  gdf["geometry"] = gdf.buffer(0)
141
  return gdf
142
 
143
+ def to_best_crs(gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
144
  """Converts a GeoDataFrame to the most suitable CRS."""
145
  if not gdf.empty and gdf["geometry"].iloc[0] is not None:
146
  best_epsg_code = find_best_epsg(gdf.to_crs(epsg=4326)["geometry"].iloc[0])
 
366
  progress(1, desc="Done!")
367
  return m._repr_html_(), None, stats_df, dem_html, slope_html, geometry_json, buffer_geometry_json
368
 
369
+ # Initialize the FastAPI app first
370
+ app = FastAPI()
371
+
372
+ @app.get("/api/geomtry")
373
+ def calculate_geometry_metrics(request: FileUrlRequest):
374
+ """
375
+ Accepts a URL to a KML/GeoJSON file, calculates the area and perimeter
376
+ of the first valid polygon found, and returns the results in JSON format.
377
+ """
378
+ try:
379
+ # 1. Fetch and read the spatial file from the URL
380
+ input_gdf = get_gdf_from_url(str(request.url))
381
+
382
+ # 2. Preprocess the GeoDataFrame
383
+ processed_gdf = preprocess_gdf(input_gdf)
384
+
385
+ # 3. Isolate the first valid polygon
386
+ polygon_gdf = processed_gdf[processed_gdf.geometry.type == 'Polygon']
387
+ if polygon_gdf.empty:
388
+ raise ValueError("No valid polygon found in the provided file.")
389
+
390
+ # Take just the first polygon for this example
391
+ first_polygon_gdf = polygon_gdf.iloc[[0]]
392
+
393
+ # 4. Convert to a suitable Coordinate Reference System for measurement
394
+ metric_gdf = to_best_crs(first_polygon_gdf)
395
+
396
+ # 5. Calculate area and perimeter
397
+ # .item() extracts the single value from the GeoSeries
398
+ area_sq_meters = metric_gdf.area.item()
399
+ perimeter_meters = metric_gdf.length.item()
400
+
401
+ area_hectares = area_sq_meters / 10000
402
+
403
+ # 6. Return the results
404
+ return {
405
+ "area": round(area_hectares, 4),
406
+ "perimeter": round(perimeter_meters, 4),
407
+ }
408
+
409
+ except ValueError as e:
410
+ # Handle known errors (e.g., bad URL, no polygon)
411
+ raise HTTPException(status_code=400, detail=str(e))
412
+ except Exception as e:
413
+ # Handle unexpected server errors
414
+ raise HTTPException(status_code=500, detail=f"An unexpected server error occurred: {e}")
415
+
416
+
417
  def calculate_indices(
418
  geometry_json, buffer_geometry_json, veg_indices, evi_vars, date_range,
419
  min_year, max_year, progress=gr.Progress()
 
728
  # --- Event Handlers ---
729
  def process_on_load(request: gr.Request):
730
  """Checks for a 'file_url' query parameter when the app loads."""
731
+
732
  return request.query_params.get("file_url", "")
733
 
734
  demo.load(process_on_load, None, url_input)