Update add_indices
Browse files
app.py
CHANGED
|
@@ -23,6 +23,7 @@ import xml.etree.ElementTree as ET
|
|
| 23 |
def one_time_setup():
|
| 24 |
"""Initializes the Earth Engine API."""
|
| 25 |
try:
|
|
|
|
| 26 |
ee.Initialize()
|
| 27 |
except Exception as e:
|
| 28 |
try:
|
|
@@ -36,11 +37,12 @@ def one_time_setup():
|
|
| 36 |
credentials = ee.ServiceAccountCredentials('ujjwal@ee-ujjwaliitd.iam.gserviceaccount.com', credentials_path)
|
| 37 |
ee.Initialize(credentials, project='ee-ujjwaliitd')
|
| 38 |
else:
|
|
|
|
| 39 |
raise e
|
| 40 |
except Exception as inner_e:
|
|
|
|
| 41 |
print(f"Earth Engine initialization failed: {inner_e}")
|
| 42 |
-
|
| 43 |
-
# For this Gradio app, we'll let it proceed and fail gracefully later if EE is needed.
|
| 44 |
|
| 45 |
def _process_spatial_data(data_bytes):
|
| 46 |
"""Core function to process bytes of a KML or GeoJSON file."""
|
|
@@ -78,7 +80,7 @@ def get_gdf_from_url(url):
|
|
| 78 |
"""Downloads and reads a KML/GeoJSON from a URL."""
|
| 79 |
if not url or not url.strip():
|
| 80 |
return None
|
| 81 |
-
|
| 82 |
# Handle Google Drive URLs
|
| 83 |
if "drive.google.com" in url:
|
| 84 |
if "/file/d/" in url:
|
|
@@ -173,39 +175,44 @@ def get_wayback_data():
|
|
| 173 |
"""Fetches and parses Wayback imagery data from ArcGIS."""
|
| 174 |
try:
|
| 175 |
url = "https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/WMTS/1.0.0/WMTSCapabilities.xml"
|
| 176 |
-
response = requests.get(url)
|
| 177 |
-
response.raise_for_status()
|
| 178 |
-
|
| 179 |
-
# Parse XML
|
| 180 |
root = ET.fromstring(response.content)
|
| 181 |
-
|
|
|
|
| 182 |
ns = {
|
| 183 |
-
"wmts": "
|
| 184 |
-
"ows": "
|
| 185 |
-
"xlink": "https://www.w3.org/1999/xlink",
|
| 186 |
}
|
| 187 |
-
|
| 188 |
# Use a robust XPath to find all 'Layer' elements anywhere in the document.
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
layer_data = []
|
| 193 |
for layer in layers:
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
wayback_df = pd.DataFrame(layer_data)
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
| 206 |
wayback_df.set_index("date", inplace=True)
|
| 207 |
-
return wayback_df
|
| 208 |
-
|
| 209 |
except requests.exceptions.RequestException as e:
|
| 210 |
print(f"Could not fetch Wayback data from URL: {e}")
|
| 211 |
return pd.DataFrame()
|
|
@@ -216,7 +223,7 @@ def get_wayback_data():
|
|
| 216 |
print(f"An unexpected error occurred in get_wayback_data: {e}")
|
| 217 |
return pd.DataFrame() # Return empty dataframe on failure
|
| 218 |
|
| 219 |
-
def add_indices(image, nir_band, red_band, blue_band, green_band, evi_vars):
|
| 220 |
"""Calculates and adds multiple vegetation indices to an Earth Engine image."""
|
| 221 |
# It's safer to work with the image bands directly
|
| 222 |
nir = image.select(nir_band).divide(10000)
|
|
@@ -242,16 +249,25 @@ def add_indices(image, nir_band, red_band, blue_band, green_band, evi_vars):
|
|
| 242 |
}).rename('EVI2')
|
| 243 |
|
| 244 |
# RandomForest (This part requires a pre-trained model asset in GEE)
|
| 245 |
-
# For demonstration, we'll return a constant image if asset is not available
|
| 246 |
try:
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
classifier = ee.Classifier.smileRandomForest(50).train(
|
| 249 |
features=table,
|
| 250 |
-
classProperty=
|
| 251 |
-
inputProperties=
|
| 252 |
)
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
| 255 |
rf = ee.Image.constant(0).rename('RandomForest')
|
| 256 |
|
| 257 |
|
|
@@ -273,7 +289,7 @@ def add_indices(image, nir_band, red_band, blue_band, green_band, evi_vars):
|
|
| 273 |
|
| 274 |
# --- Gradio App Logic ---
|
| 275 |
|
| 276 |
-
# Initialize GEE and fetch wayback data once
|
| 277 |
one_time_setup()
|
| 278 |
WAYBACK_DF = get_wayback_data()
|
| 279 |
|
|
@@ -319,13 +335,12 @@ def process_and_display(file_obj, url_str, buffer_m, progress=gr.Progress()):
|
|
| 319 |
|
| 320 |
progress(0.5, desc="Generating map and stats...")
|
| 321 |
|
| 322 |
-
# Create map
|
| 323 |
m = folium.Map()
|
| 324 |
-
|
| 325 |
if not WAYBACK_DF.empty:
|
| 326 |
-
#
|
| 327 |
-
latest_item = WAYBACK_DF.iloc[
|
| 328 |
-
print(latest_item)
|
| 329 |
wayback_url = (
|
| 330 |
latest_item["ResourceURL_Template"]
|
| 331 |
.replace("{TileMatrixSet}", "GoogleMapsCompatible")
|
|
@@ -338,13 +353,13 @@ def process_and_display(file_obj, url_str, buffer_m, progress=gr.Progress()):
|
|
| 338 |
attr=f"Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community | Imagery Date: {latest_item.name.strftime('%Y-%m-%d')}",
|
| 339 |
name="Latest Esri Satellite"
|
| 340 |
).add_to(m)
|
| 341 |
-
|
| 342 |
# Add geometries to the map
|
| 343 |
m = add_geometry_to_map(m, geometry_gdf, buffer_geometry_gdf, opacity=0.3)
|
| 344 |
-
|
| 345 |
# Add a layer control panel
|
| 346 |
m.add_child(folium.LayerControl())
|
| 347 |
-
|
| 348 |
# Fit the map view to the bounds of the geometry
|
| 349 |
bounds = geometry_gdf.to_crs(epsg=4326).total_bounds
|
| 350 |
map_bounds = [[bounds[1], bounds[0]], [bounds[3], bounds[2]]] # Format: [[south, west], [north, east]]
|
|
@@ -365,11 +380,15 @@ def process_and_display(file_obj, url_str, buffer_m, progress=gr.Progress()):
|
|
| 365 |
progress(1, desc="Done!")
|
| 366 |
return m._repr_html_(), None, stats_df, geometry_json, buffer_geometry_json
|
| 367 |
|
|
|
|
| 368 |
def calculate_indices(
|
| 369 |
geometry_json, buffer_geometry_json, veg_indices, evi_vars, date_range,
|
| 370 |
min_year, max_year, progress=gr.Progress()
|
| 371 |
):
|
| 372 |
"""Calculates vegetation indices based on user inputs."""
|
|
|
|
|
|
|
|
|
|
| 373 |
if not all([geometry_json, buffer_geometry_json, veg_indices]):
|
| 374 |
return "Please process a file and select at least one index first.", None, None, None
|
| 375 |
|
|
@@ -381,7 +400,7 @@ def calculate_indices(
|
|
| 381 |
# Convert to EE geometry
|
| 382 |
ee_geometry = ee.Geometry(json.loads(geometry_gdf.to_crs(4326).to_json())['features'][0]['geometry'])
|
| 383 |
buffer_ee_geometry = ee.Geometry(json.loads(buffer_geometry_gdf.to_crs(4326).to_json())['features'][0]['geometry'])
|
| 384 |
-
|
| 385 |
# Date ranges
|
| 386 |
start_day, start_month = date_range[0].day, date_range[0].month
|
| 387 |
end_day, end_month = date_range[1].day, date_range[1].month
|
|
@@ -394,10 +413,10 @@ def calculate_indices(
|
|
| 394 |
collection = (
|
| 395 |
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
| 396 |
.select(
|
| 397 |
-
["B2", "B3", "B4", "B8", "MSK_CLDPRB"],
|
| 398 |
-
["Blue", "Green", "Red", "NIR", "MSK_CLDPRB"]
|
| 399 |
)
|
| 400 |
-
.map(lambda img: add_indices(img, 'NIR', 'Red', 'Blue', 'Green', evi_vars))
|
| 401 |
)
|
| 402 |
|
| 403 |
result_rows = []
|
|
@@ -411,11 +430,21 @@ def calculate_indices(
|
|
| 411 |
row = {'daterange': f"{start_date}_to_{end_date}"}
|
| 412 |
for veg_index in veg_indices:
|
| 413 |
mosaic = filtered_collection.qualityMosaic(veg_index)
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
result_rows.append(row)
|
| 420 |
|
| 421 |
if not result_rows:
|
|
@@ -427,14 +456,17 @@ def calculate_indices(
|
|
| 427 |
# Create plots
|
| 428 |
plots = []
|
| 429 |
for veg_index in veg_indices:
|
| 430 |
-
plot_df = result_df[[veg_index, f"{veg_index}_buffer", f"{veg_index}_ratio"]]
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
|
|
|
| 434 |
|
| 435 |
return None, result_df, plots, "Calculation complete."
|
| 436 |
|
| 437 |
except Exception as e:
|
|
|
|
|
|
|
| 438 |
return f"An error occurred during calculation: {e}", None, None, None
|
| 439 |
|
| 440 |
|
|
@@ -498,7 +530,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Kamlan: KML Analyzer") as demo:
|
|
| 498 |
|
| 499 |
|
| 500 |
# --- Event Handlers ---
|
| 501 |
-
|
| 502 |
def process_on_load(request: gr.Request):
|
| 503 |
"""Checks for a 'file_url' query parameter when the app loads and populates the URL input field."""
|
| 504 |
file_url = request.query_params.get("file_url")
|
|
@@ -542,7 +574,6 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Kamlan: KML Analyzer") as demo:
|
|
| 542 |
# Catch any other unexpected errors during the process
|
| 543 |
return f"An error occurred in the calculation wrapper: {e}", None, None
|
| 544 |
|
| 545 |
-
# **FIX**: Cleaned up the outputs to have a direct one-to-one mapping.
|
| 546 |
calculate_button.click(
|
| 547 |
fn=calculate_wrapper,
|
| 548 |
inputs=[
|
|
@@ -554,7 +585,6 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Kamlan: KML Analyzer") as demo:
|
|
| 554 |
outputs=[results_info_box, timeseries_table, plot_output]
|
| 555 |
)
|
| 556 |
|
| 557 |
-
|
| 558 |
gr.HTML("""
|
| 559 |
<div style="text-align: center; margin-top: 20px;">
|
| 560 |
<p>Developed by <a href="https://sustainability-lab.github.io/">Sustainability Lab</a>, <a href="https://www.iitgn.ac.in/">IIT Gandhinagar</a></p>
|
|
@@ -563,4 +593,4 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Kamlan: KML Analyzer") as demo:
|
|
| 563 |
""")
|
| 564 |
|
| 565 |
if __name__ == "__main__":
|
| 566 |
-
demo.launch(debug=True)
|
|
|
|
| 23 |
def one_time_setup():
|
| 24 |
"""Initializes the Earth Engine API."""
|
| 25 |
try:
|
| 26 |
+
# Attempt to initialize with default credentials
|
| 27 |
ee.Initialize()
|
| 28 |
except Exception as e:
|
| 29 |
try:
|
|
|
|
| 37 |
credentials = ee.ServiceAccountCredentials('ujjwal@ee-ujjwaliitd.iam.gserviceaccount.com', credentials_path)
|
| 38 |
ee.Initialize(credentials, project='ee-ujjwaliitd')
|
| 39 |
else:
|
| 40 |
+
# If no credentials are found, re-raise the original exception
|
| 41 |
raise e
|
| 42 |
except Exception as inner_e:
|
| 43 |
+
# If the fallback also fails, print the error
|
| 44 |
print(f"Earth Engine initialization failed: {inner_e}")
|
| 45 |
+
|
|
|
|
| 46 |
|
| 47 |
def _process_spatial_data(data_bytes):
|
| 48 |
"""Core function to process bytes of a KML or GeoJSON file."""
|
|
|
|
| 80 |
"""Downloads and reads a KML/GeoJSON from a URL."""
|
| 81 |
if not url or not url.strip():
|
| 82 |
return None
|
| 83 |
+
|
| 84 |
# Handle Google Drive URLs
|
| 85 |
if "drive.google.com" in url:
|
| 86 |
if "/file/d/" in url:
|
|
|
|
| 175 |
"""Fetches and parses Wayback imagery data from ArcGIS."""
|
| 176 |
try:
|
| 177 |
url = "https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/WMTS/1.0.0/WMTSCapabilities.xml"
|
| 178 |
+
response = requests.get(url, timeout=30)
|
| 179 |
+
response.raise_for_status()
|
|
|
|
|
|
|
| 180 |
root = ET.fromstring(response.content)
|
| 181 |
+
|
| 182 |
+
# Define the namespaces found in the XML document
|
| 183 |
ns = {
|
| 184 |
+
"wmts": "http://www.opengis.net/wmts/1.0",
|
| 185 |
+
"ows": "http://www.opengis.net/ows/1.1",
|
|
|
|
| 186 |
}
|
| 187 |
+
|
| 188 |
# Use a robust XPath to find all 'Layer' elements anywhere in the document.
|
| 189 |
+
layers = root.findall(".//wmts:Layer", ns)
|
| 190 |
+
|
|
|
|
| 191 |
layer_data = []
|
| 192 |
for layer in layers:
|
| 193 |
+
title_element = layer.find("ows:Title", ns)
|
| 194 |
+
resource_element = layer.find("wmts:ResourceURL", ns)
|
| 195 |
+
|
| 196 |
+
# Ensure elements and their attributes exist before trying to access them
|
| 197 |
+
if title_element is not None and resource_element is not None and title_element.text is not None:
|
| 198 |
+
template = resource_element.get("template")
|
| 199 |
+
if template:
|
| 200 |
+
layer_data.append({
|
| 201 |
+
"Title": title_element.text,
|
| 202 |
+
"ResourceURL_Template": template
|
| 203 |
+
})
|
| 204 |
+
|
| 205 |
+
if not layer_data:
|
| 206 |
+
print("Warning: No valid layers with titles and resource URLs found in Wayback XML response.")
|
| 207 |
+
return pd.DataFrame()
|
| 208 |
|
| 209 |
wayback_df = pd.DataFrame(layer_data)
|
| 210 |
+
# Extract date and handle potential parsing errors by coercing them to NaT
|
| 211 |
+
wayback_df["date"] = pd.to_datetime(wayback_df["Title"].str.extract(r"(\d{4}-\d{2}-\d{2})")[0], errors="coerce")
|
| 212 |
+
# Drop rows where date could not be parsed
|
| 213 |
+
wayback_df.dropna(subset=['date'], inplace=True)
|
| 214 |
wayback_df.set_index("date", inplace=True)
|
| 215 |
+
return wayback_df.sort_index()
|
|
|
|
| 216 |
except requests.exceptions.RequestException as e:
|
| 217 |
print(f"Could not fetch Wayback data from URL: {e}")
|
| 218 |
return pd.DataFrame()
|
|
|
|
| 223 |
print(f"An unexpected error occurred in get_wayback_data: {e}")
|
| 224 |
return pd.DataFrame() # Return empty dataframe on failure
|
| 225 |
|
| 226 |
+
def add_indices(image, nir_band, red_band, blue_band, green_band, swir1_band, swir2_band, evi_vars):
|
| 227 |
"""Calculates and adds multiple vegetation indices to an Earth Engine image."""
|
| 228 |
# It's safer to work with the image bands directly
|
| 229 |
nir = image.select(nir_band).divide(10000)
|
|
|
|
| 249 |
}).rename('EVI2')
|
| 250 |
|
| 251 |
# RandomForest (This part requires a pre-trained model asset in GEE)
|
|
|
|
| 252 |
try:
|
| 253 |
+
# FIX: Select and rename bands from the training data to match what the classifier expects.
|
| 254 |
+
table = ee.FeatureCollection('projects/in793-aq-nb-24330048/assets/cleanedVDI').select(
|
| 255 |
+
["B2", "B4", "B8", "cVDI"],
|
| 256 |
+
["Blue", "Red", "NIR", 'cVDI']
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
bands = ['Blue', 'Red', 'NIR']
|
| 260 |
+
label = 'cVDI'
|
| 261 |
+
|
| 262 |
classifier = ee.Classifier.smileRandomForest(50).train(
|
| 263 |
features=table,
|
| 264 |
+
classProperty=label,
|
| 265 |
+
inputProperties=bands,
|
| 266 |
)
|
| 267 |
+
# Classify the image. The image already has bands named 'Blue', 'Red', 'NIR' from the previous select.
|
| 268 |
+
rf = image.classify(classifier).multiply(ee.Number(0.2)).add(ee.Number(0.1)).rename('RandomForest')
|
| 269 |
+
except Exception as e:
|
| 270 |
+
print(f"Random Forest calculation failed: {e}") # More specific error message
|
| 271 |
rf = ee.Image.constant(0).rename('RandomForest')
|
| 272 |
|
| 273 |
|
|
|
|
| 289 |
|
| 290 |
# --- Gradio App Logic ---
|
| 291 |
|
| 292 |
+
# Initialize GEE and fetch wayback data once at the start
|
| 293 |
one_time_setup()
|
| 294 |
WAYBACK_DF = get_wayback_data()
|
| 295 |
|
|
|
|
| 335 |
|
| 336 |
progress(0.5, desc="Generating map and stats...")
|
| 337 |
|
| 338 |
+
# Create map
|
| 339 |
m = folium.Map()
|
| 340 |
+
|
| 341 |
if not WAYBACK_DF.empty:
|
| 342 |
+
# The DataFrame is already sorted by date, so the last item is the latest.
|
| 343 |
+
latest_item = WAYBACK_DF.iloc[-1]
|
|
|
|
| 344 |
wayback_url = (
|
| 345 |
latest_item["ResourceURL_Template"]
|
| 346 |
.replace("{TileMatrixSet}", "GoogleMapsCompatible")
|
|
|
|
| 353 |
attr=f"Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community | Imagery Date: {latest_item.name.strftime('%Y-%m-%d')}",
|
| 354 |
name="Latest Esri Satellite"
|
| 355 |
).add_to(m)
|
| 356 |
+
|
| 357 |
# Add geometries to the map
|
| 358 |
m = add_geometry_to_map(m, geometry_gdf, buffer_geometry_gdf, opacity=0.3)
|
| 359 |
+
|
| 360 |
# Add a layer control panel
|
| 361 |
m.add_child(folium.LayerControl())
|
| 362 |
+
|
| 363 |
# Fit the map view to the bounds of the geometry
|
| 364 |
bounds = geometry_gdf.to_crs(epsg=4326).total_bounds
|
| 365 |
map_bounds = [[bounds[1], bounds[0]], [bounds[3], bounds[2]]] # Format: [[south, west], [north, east]]
|
|
|
|
| 380 |
progress(1, desc="Done!")
|
| 381 |
return m._repr_html_(), None, stats_df, geometry_json, buffer_geometry_json
|
| 382 |
|
| 383 |
+
|
| 384 |
def calculate_indices(
|
| 385 |
geometry_json, buffer_geometry_json, veg_indices, evi_vars, date_range,
|
| 386 |
min_year, max_year, progress=gr.Progress()
|
| 387 |
):
|
| 388 |
"""Calculates vegetation indices based on user inputs."""
|
| 389 |
+
# **FIX**: Ensure GEE is initialized before making any calls.
|
| 390 |
+
one_time_setup()
|
| 391 |
+
|
| 392 |
if not all([geometry_json, buffer_geometry_json, veg_indices]):
|
| 393 |
return "Please process a file and select at least one index first.", None, None, None
|
| 394 |
|
|
|
|
| 400 |
# Convert to EE geometry
|
| 401 |
ee_geometry = ee.Geometry(json.loads(geometry_gdf.to_crs(4326).to_json())['features'][0]['geometry'])
|
| 402 |
buffer_ee_geometry = ee.Geometry(json.loads(buffer_geometry_gdf.to_crs(4326).to_json())['features'][0]['geometry'])
|
| 403 |
+
|
| 404 |
# Date ranges
|
| 405 |
start_day, start_month = date_range[0].day, date_range[0].month
|
| 406 |
end_day, end_month = date_range[1].day, date_range[1].month
|
|
|
|
| 413 |
collection = (
|
| 414 |
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
| 415 |
.select(
|
| 416 |
+
["B2", "B3", "B4", "B8", "B11", "B12", "MSK_CLDPRB"],
|
| 417 |
+
["Blue", "Green", "Red", "NIR", "SWIR1", "SWIR2", "MSK_CLDPRB"]
|
| 418 |
)
|
| 419 |
+
.map(lambda img: add_indices(img, 'NIR', 'Red', 'Blue', 'Green', "SWIR1", "SWIR2", evi_vars))
|
| 420 |
)
|
| 421 |
|
| 422 |
result_rows = []
|
|
|
|
| 430 |
row = {'daterange': f"{start_date}_to_{end_date}"}
|
| 431 |
for veg_index in veg_indices:
|
| 432 |
mosaic = filtered_collection.qualityMosaic(veg_index)
|
| 433 |
+
|
| 434 |
+
mean_dict = mosaic.reduceRegion(reducer=ee.Reducer.mean(), geometry=ee_geometry, scale=10, maxPixels=1e9).getInfo()
|
| 435 |
+
mean_val = mean_dict.get(veg_index) if mean_dict else None
|
| 436 |
+
|
| 437 |
+
buffer_mean_dict = mosaic.reduceRegion(reducer=ee.Reducer.mean(), geometry=buffer_ee_geometry, scale=10, maxPixels=1e9).getInfo()
|
| 438 |
+
buffer_mean_val = buffer_mean_dict.get(veg_index) if buffer_mean_dict else None
|
| 439 |
+
|
| 440 |
+
row[veg_index] = mean_val if mean_val is not None else np.nan
|
| 441 |
+
row[f"{veg_index}_buffer"] = buffer_mean_val if buffer_mean_val is not None else np.nan
|
| 442 |
+
|
| 443 |
+
if mean_val is not None and buffer_mean_val is not None and buffer_mean_val != 0:
|
| 444 |
+
row[f"{veg_index}_ratio"] = mean_val / buffer_mean_val
|
| 445 |
+
else:
|
| 446 |
+
row[f"{veg_index}_ratio"] = np.nan
|
| 447 |
+
|
| 448 |
result_rows.append(row)
|
| 449 |
|
| 450 |
if not result_rows:
|
|
|
|
| 456 |
# Create plots
|
| 457 |
plots = []
|
| 458 |
for veg_index in veg_indices:
|
| 459 |
+
plot_df = result_df[[veg_index, f"{veg_index}_buffer", f"{veg_index}_ratio"]].dropna()
|
| 460 |
+
if not plot_df.empty:
|
| 461 |
+
fig = px.line(plot_df, x=plot_df.index, y=plot_df.columns, markers=True, title=f"{veg_index} Time Series")
|
| 462 |
+
fig.update_layout(xaxis_title="Year", yaxis_title="Index Value")
|
| 463 |
+
plots.append(fig)
|
| 464 |
|
| 465 |
return None, result_df, plots, "Calculation complete."
|
| 466 |
|
| 467 |
except Exception as e:
|
| 468 |
+
import traceback
|
| 469 |
+
traceback.print_exc()
|
| 470 |
return f"An error occurred during calculation: {e}", None, None, None
|
| 471 |
|
| 472 |
|
|
|
|
| 530 |
|
| 531 |
|
| 532 |
# --- Event Handlers ---
|
| 533 |
+
|
| 534 |
def process_on_load(request: gr.Request):
|
| 535 |
"""Checks for a 'file_url' query parameter when the app loads and populates the URL input field."""
|
| 536 |
file_url = request.query_params.get("file_url")
|
|
|
|
| 574 |
# Catch any other unexpected errors during the process
|
| 575 |
return f"An error occurred in the calculation wrapper: {e}", None, None
|
| 576 |
|
|
|
|
| 577 |
calculate_button.click(
|
| 578 |
fn=calculate_wrapper,
|
| 579 |
inputs=[
|
|
|
|
| 585 |
outputs=[results_info_box, timeseries_table, plot_output]
|
| 586 |
)
|
| 587 |
|
|
|
|
| 588 |
gr.HTML("""
|
| 589 |
<div style="text-align: center; margin-top: 20px;">
|
| 590 |
<p>Developed by <a href="https://sustainability-lab.github.io/">Sustainability Lab</a>, <a href="https://www.iitgn.ac.in/">IIT Gandhinagar</a></p>
|
|
|
|
| 593 |
""")
|
| 594 |
|
| 595 |
if __name__ == "__main__":
|
| 596 |
+
demo.launch(debug=True)
|