Add Legend and Histogram
Browse files
app.py
CHANGED
|
@@ -77,7 +77,7 @@ 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
|
| 80 |
-
|
| 81 |
# Handle Google Drive URLs
|
| 82 |
if "drive.google.com" in url:
|
| 83 |
if "/file/d/" in url:
|
|
@@ -174,32 +174,32 @@ def get_wayback_data():
|
|
| 174 |
url = "https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/WMTS/1.0.0/WMTSCapabilities.xml"
|
| 175 |
response = requests.get(url)
|
| 176 |
response.raise_for_status() # Ensure request was successful
|
| 177 |
-
|
| 178 |
# Parse XML
|
| 179 |
root = ET.fromstring(response.content)
|
| 180 |
-
|
| 181 |
ns = {
|
| 182 |
-
"wmts": "
|
| 183 |
-
"ows": "
|
| 184 |
-
"xlink": "
|
| 185 |
}
|
| 186 |
-
|
| 187 |
# Use a robust XPath to find all 'Layer' elements anywhere in the document.
|
| 188 |
# This is less brittle than specifying the full path.
|
| 189 |
layers = root.findall(".//wmts:Contents/wmts:Layer", ns)
|
| 190 |
-
|
| 191 |
layer_data = []
|
| 192 |
for layer in layers:
|
| 193 |
title = layer.find("ows:Title", ns)
|
| 194 |
identifier = layer.find("ows:Identifier", ns)
|
| 195 |
resource = layer.find("wmts:ResourceURL", ns) # Tile URL template
|
| 196 |
-
|
| 197 |
title_text = title.text if title is not None else "N/A"
|
| 198 |
identifier_text = identifier.text if identifier is not None else "N/A"
|
| 199 |
url_template = resource.get("template") if resource is not None else "N/A"
|
| 200 |
-
|
| 201 |
layer_data.append({"Title": title_text, "ResourceURL_Template": url_template})
|
| 202 |
-
|
| 203 |
wayback_df = pd.DataFrame(layer_data)
|
| 204 |
wayback_df["date"] = pd.to_datetime(wayback_df["Title"].str.extract(r"(\d{4}-\d{2}-\d{2})").squeeze(), errors="coerce")
|
| 205 |
wayback_df.set_index("date", inplace=True)
|
|
@@ -344,7 +344,7 @@ def process_and_display(file_obj, url_str, buffer_m, progress=gr.Progress()):
|
|
| 344 |
.replace("{TileCol}", "{x}")
|
| 345 |
)
|
| 346 |
folium.TileLayer(tiles=wayback_url, attr="Esri", name=wayback_title).add_to(m)
|
| 347 |
-
|
| 348 |
m = add_geometry_to_map(m, geometry_gdf, buffer_geometry_gdf, opacity=0.3)
|
| 349 |
m.add_child(folium.LayerControl())
|
| 350 |
bounds = geometry_gdf.to_crs(epsg=4326).total_bounds
|
|
@@ -426,7 +426,7 @@ def calculate_indices(
|
|
| 426 |
for veg_index in veg_indices:
|
| 427 |
plot_cols = [veg_index, f"{veg_index}_buffer", f"{veg_index}_ratio"]
|
| 428 |
existing_plot_cols = [col for col in plot_cols if col in result_df.columns]
|
| 429 |
-
|
| 430 |
plot_df = result_df[['Year'] + existing_plot_cols].dropna()
|
| 431 |
|
| 432 |
if not plot_df.empty:
|
|
@@ -442,42 +442,60 @@ def calculate_indices(
|
|
| 442 |
traceback.print_exc()
|
| 443 |
return f"An error occurred during calculation: {e}", None, None, None
|
| 444 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
def generate_comparison_maps(geometry_json, selected_index, selected_years, evi_vars, date_start_str, date_end_str, progress=gr.Progress()):
|
| 446 |
-
"""Generates side-by-side maps for a selected index and two selected years."""
|
| 447 |
if not geometry_json or not selected_index or not selected_years:
|
| 448 |
return "Please process a file and select an index and years first.", "", ""
|
| 449 |
if len(selected_years) != 2:
|
| 450 |
return "Please select exactly two years to compare.", "", ""
|
| 451 |
-
|
| 452 |
one_time_setup()
|
| 453 |
geometry_gdf = gpd.read_file(geometry_json).to_crs(4326)
|
| 454 |
ee_geometry = ee.Geometry(json.loads(geometry_gdf.to_json())['features'][0]['geometry'])
|
| 455 |
bounds = geometry_gdf.total_bounds
|
| 456 |
map_bounds = [[bounds[1], bounds[0]], [bounds[3], bounds[2]]]
|
| 457 |
-
|
| 458 |
start_month, start_day = map(int, date_start_str.split('-'))
|
| 459 |
end_month, end_day = map(int, date_end_str.split('-'))
|
| 460 |
|
| 461 |
-
vis_params = {
|
| 462 |
-
"min": 0.0, "max": 1.0,
|
| 463 |
-
"palette": ['FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901', '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01', '012E01', '011D01', '011301']
|
| 464 |
-
}
|
| 465 |
-
|
| 466 |
maps_html = []
|
| 467 |
for i, year in enumerate(selected_years):
|
| 468 |
progress((i + 1) / 2, desc=f"Generating map for {year}")
|
| 469 |
start_date = f"{year}-{start_month:02d}-{start_day:02d}"
|
| 470 |
end_date = f"{year}-{end_month:02d}-{end_day:02d}"
|
| 471 |
-
|
|
|
|
| 472 |
wayback_url = None
|
| 473 |
wayback_title = "Default Satellite"
|
| 474 |
if not WAYBACK_DF.empty:
|
| 475 |
try:
|
| 476 |
target_date = datetime(int(year), start_month, 15)
|
| 477 |
-
# Find the index of the closest date in the wayback dataframe
|
| 478 |
nearest_idx = WAYBACK_DF.index.get_indexer([target_date], method='nearest')[0]
|
| 479 |
wayback_item = WAYBACK_DF.iloc[nearest_idx]
|
| 480 |
-
|
| 481 |
wayback_title = f"Esri Wayback ({wayback_item.name.strftime('%Y-%m-%d')})"
|
| 482 |
wayback_url = (
|
| 483 |
wayback_item["ResourceURL_Template"]
|
|
@@ -488,9 +506,9 @@ def generate_comparison_maps(geometry_json, selected_index, selected_years, evi_
|
|
| 488 |
)
|
| 489 |
except Exception as e:
|
| 490 |
print(f"Could not find a suitable Wayback basemap for {year}: {e}")
|
| 491 |
-
# Fallback to default values if any error occurs
|
| 492 |
wayback_url = None
|
| 493 |
wayback_title = "Default Satellite"
|
|
|
|
| 494 |
|
| 495 |
collection = (
|
| 496 |
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
|
@@ -508,17 +526,60 @@ def generate_comparison_maps(geometry_json, selected_index, selected_years, evi_
|
|
| 508 |
continue
|
| 509 |
|
| 510 |
mosaic = collection.qualityMosaic(selected_index)
|
| 511 |
-
clipped_image = mosaic.select(selected_index).clip(ee_geometry)
|
| 512 |
|
| 513 |
-
# Create
|
| 514 |
m = gee_folium.Map(zoom_start=14)
|
| 515 |
if wayback_url:
|
| 516 |
m.add_tile_layer(wayback_url, name=wayback_title, attribution="Esri")
|
| 517 |
else:
|
| 518 |
m.add_basemap("SATELLITE")
|
| 519 |
|
| 520 |
-
|
| 521 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
folium.GeoJson(geometry_gdf, name="Geometry", style_function=lambda x: {"color": "yellow", "fillOpacity": 0, "weight": 2.5}).add_to(m)
|
| 523 |
m.fit_bounds(map_bounds, padding=(10, 10))
|
| 524 |
m.addLayerControl()
|
|
@@ -529,7 +590,7 @@ def generate_comparison_maps(geometry_json, selected_index, selected_years, evi_
|
|
| 529 |
|
| 530 |
return f"Comparison generated for {selected_years[0]} and {selected_years[1]}.", maps_html[0], maps_html[1]
|
| 531 |
|
| 532 |
-
# --- Gradio Interface ---
|
| 533 |
theme = gr.themes.Soft(primary_hue="teal", secondary_hue="orange").set(
|
| 534 |
background_fill_primary="white"
|
| 535 |
)
|
|
@@ -556,7 +617,7 @@ with gr.Blocks(theme=theme, title="Kamlan: KML Analyzer") as demo:
|
|
| 556 |
url_input = gr.Textbox(label="Or Provide File URL", placeholder="e.g., https://.../my_file.kml")
|
| 557 |
buffer_input = gr.Number(label="Buffer (meters)", value=50)
|
| 558 |
process_button = gr.Button("Process Input", variant="primary")
|
| 559 |
-
|
| 560 |
with gr.Accordion("Advanced Settings", open=False):
|
| 561 |
gr.Markdown("### Select Vegetation Indices")
|
| 562 |
all_veg_indices = ["GujVDI", "NDVI", "EVI", "EVI2", "RandomForest", "CI"]
|
|
@@ -581,14 +642,14 @@ with gr.Blocks(theme=theme, title="Kamlan: KML Analyzer") as demo:
|
|
| 581 |
max_year_input = gr.Number(label="End Year", value=today.year, precision=0)
|
| 582 |
|
| 583 |
calculate_button = gr.Button("Calculate Vegetation Indices", variant="primary")
|
| 584 |
-
|
| 585 |
|
| 586 |
with gr.Column(scale=2):
|
| 587 |
gr.Markdown("## 2. Results")
|
| 588 |
info_box = gr.Textbox(label="Status", interactive=False, placeholder="Status messages will appear here...")
|
| 589 |
map_output = gr.HTML(label="Map View")
|
| 590 |
stats_output = gr.DataFrame(label="Geometry Metrics")
|
| 591 |
-
|
| 592 |
gr.Markdown("### Digital Elevation Model (DEM) and Slope")
|
| 593 |
with gr.Row():
|
| 594 |
dem_map_output = gr.HTML(label="DEM Map")
|
|
@@ -605,9 +666,9 @@ with gr.Blocks(theme=theme, title="Kamlan: KML Analyzer") as demo:
|
|
| 605 |
with gr.Row():
|
| 606 |
comparison_index_select = gr.Radio(all_veg_indices, label="Select Index for Comparison", value="NDVI")
|
| 607 |
comparison_years_select = gr.CheckboxGroup(label="Select Two Years to Compare", choices=[])
|
| 608 |
-
|
| 609 |
compare_button = gr.Button("Generate Comparison Maps", variant="secondary")
|
| 610 |
-
|
| 611 |
with gr.Row():
|
| 612 |
map_year_1_output = gr.HTML(label="Comparison Map 1")
|
| 613 |
map_year_2_output = gr.HTML(label="Comparison Map 2")
|
|
@@ -640,15 +701,15 @@ with gr.Blocks(theme=theme, title="Kamlan: KML Analyzer") as demo:
|
|
| 640 |
geometry_json, buffer_json, veg_indices,
|
| 641 |
evi_vars, date_range, int(min_year), int(max_year), progress
|
| 642 |
)
|
| 643 |
-
|
| 644 |
status_message = error_msg or success_msg
|
| 645 |
first_plot = plots[0] if plots else None
|
| 646 |
df_display = df.round(3) if df is not None else None
|
| 647 |
-
|
| 648 |
available_years = []
|
| 649 |
if df is not None and 'Year' in df.columns:
|
| 650 |
available_years = sorted(df['Year'].unique().tolist())
|
| 651 |
-
|
| 652 |
return status_message, df_display, df, first_plot, gr.update(choices=available_years, value=[])
|
| 653 |
|
| 654 |
except Exception as e:
|
|
|
|
| 77 |
"""Downloads and reads a KML/GeoJSON from a URL."""
|
| 78 |
if not url or not url.strip():
|
| 79 |
return None
|
| 80 |
+
|
| 81 |
# Handle Google Drive URLs
|
| 82 |
if "drive.google.com" in url:
|
| 83 |
if "/file/d/" in url:
|
|
|
|
| 174 |
url = "https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/WMTS/1.0.0/WMTSCapabilities.xml"
|
| 175 |
response = requests.get(url)
|
| 176 |
response.raise_for_status() # Ensure request was successful
|
| 177 |
+
|
| 178 |
# Parse XML
|
| 179 |
root = ET.fromstring(response.content)
|
| 180 |
+
|
| 181 |
ns = {
|
| 182 |
+
"wmts": "http://www.opengis.net/wmts/1.0",
|
| 183 |
+
"ows": "http://www.opengis.net/ows/1.1",
|
| 184 |
+
"xlink": "http://www.w3.org/1999/xlink",
|
| 185 |
}
|
| 186 |
+
|
| 187 |
# Use a robust XPath to find all 'Layer' elements anywhere in the document.
|
| 188 |
# This is less brittle than specifying the full path.
|
| 189 |
layers = root.findall(".//wmts:Contents/wmts:Layer", ns)
|
| 190 |
+
|
| 191 |
layer_data = []
|
| 192 |
for layer in layers:
|
| 193 |
title = layer.find("ows:Title", ns)
|
| 194 |
identifier = layer.find("ows:Identifier", ns)
|
| 195 |
resource = layer.find("wmts:ResourceURL", ns) # Tile URL template
|
| 196 |
+
|
| 197 |
title_text = title.text if title is not None else "N/A"
|
| 198 |
identifier_text = identifier.text if identifier is not None else "N/A"
|
| 199 |
url_template = resource.get("template") if resource is not None else "N/A"
|
| 200 |
+
|
| 201 |
layer_data.append({"Title": title_text, "ResourceURL_Template": url_template})
|
| 202 |
+
|
| 203 |
wayback_df = pd.DataFrame(layer_data)
|
| 204 |
wayback_df["date"] = pd.to_datetime(wayback_df["Title"].str.extract(r"(\d{4}-\d{2}-\d{2})").squeeze(), errors="coerce")
|
| 205 |
wayback_df.set_index("date", inplace=True)
|
|
|
|
| 344 |
.replace("{TileCol}", "{x}")
|
| 345 |
)
|
| 346 |
folium.TileLayer(tiles=wayback_url, attr="Esri", name=wayback_title).add_to(m)
|
| 347 |
+
|
| 348 |
m = add_geometry_to_map(m, geometry_gdf, buffer_geometry_gdf, opacity=0.3)
|
| 349 |
m.add_child(folium.LayerControl())
|
| 350 |
bounds = geometry_gdf.to_crs(epsg=4326).total_bounds
|
|
|
|
| 426 |
for veg_index in veg_indices:
|
| 427 |
plot_cols = [veg_index, f"{veg_index}_buffer", f"{veg_index}_ratio"]
|
| 428 |
existing_plot_cols = [col for col in plot_cols if col in result_df.columns]
|
| 429 |
+
|
| 430 |
plot_df = result_df[['Year'] + existing_plot_cols].dropna()
|
| 431 |
|
| 432 |
if not plot_df.empty:
|
|
|
|
| 442 |
traceback.print_exc()
|
| 443 |
return f"An error occurred during calculation: {e}", None, None, None
|
| 444 |
|
| 445 |
+
# NEW/MODIFIED FUNCTIONS START HERE
|
| 446 |
+
|
| 447 |
+
def get_histogram(index_name, image, geometry, bins):
|
| 448 |
+
"""Calculates the histogram for an image within a given geometry using GEE."""
|
| 449 |
+
try:
|
| 450 |
+
# Request histogram data from Earth Engine
|
| 451 |
+
hist_info = image.reduceRegion(
|
| 452 |
+
reducer=ee.Reducer.fixedHistogram(min=bins[0], max=bins[-1], steps=len(bins)-1),
|
| 453 |
+
geometry=geometry,
|
| 454 |
+
scale=10, # Scale in meters appropriate for Sentinel-2
|
| 455 |
+
maxPixels=1e9
|
| 456 |
+
).get(index_name).getInfo()
|
| 457 |
+
|
| 458 |
+
# Extract histogram counts
|
| 459 |
+
if hist_info:
|
| 460 |
+
histogram = [item[1] for item in hist_info]
|
| 461 |
+
return np.array(histogram), bins
|
| 462 |
+
else:
|
| 463 |
+
# Return empty histogram if no data
|
| 464 |
+
return np.array([0] * (len(bins) - 1)), bins
|
| 465 |
+
except Exception as e:
|
| 466 |
+
print(f"Could not compute histogram for {index_name}: {e}")
|
| 467 |
+
return np.array([0] * (len(bins) - 1)), bins
|
| 468 |
+
|
| 469 |
def generate_comparison_maps(geometry_json, selected_index, selected_years, evi_vars, date_start_str, date_end_str, progress=gr.Progress()):
|
| 470 |
+
"""Generates side-by-side maps for a selected index and two selected years with a classified legend."""
|
| 471 |
if not geometry_json or not selected_index or not selected_years:
|
| 472 |
return "Please process a file and select an index and years first.", "", ""
|
| 473 |
if len(selected_years) != 2:
|
| 474 |
return "Please select exactly two years to compare.", "", ""
|
| 475 |
+
|
| 476 |
one_time_setup()
|
| 477 |
geometry_gdf = gpd.read_file(geometry_json).to_crs(4326)
|
| 478 |
ee_geometry = ee.Geometry(json.loads(geometry_gdf.to_json())['features'][0]['geometry'])
|
| 479 |
bounds = geometry_gdf.total_bounds
|
| 480 |
map_bounds = [[bounds[1], bounds[0]], [bounds[3], bounds[2]]]
|
| 481 |
+
|
| 482 |
start_month, start_day = map(int, date_start_str.split('-'))
|
| 483 |
end_month, end_day = map(int, date_end_str.split('-'))
|
| 484 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
maps_html = []
|
| 486 |
for i, year in enumerate(selected_years):
|
| 487 |
progress((i + 1) / 2, desc=f"Generating map for {year}")
|
| 488 |
start_date = f"{year}-{start_month:02d}-{start_day:02d}"
|
| 489 |
end_date = f"{year}-{end_month:02d}-{end_day:02d}"
|
| 490 |
+
|
| 491 |
+
# --- Wayback Basemap Logic (unchanged) ---
|
| 492 |
wayback_url = None
|
| 493 |
wayback_title = "Default Satellite"
|
| 494 |
if not WAYBACK_DF.empty:
|
| 495 |
try:
|
| 496 |
target_date = datetime(int(year), start_month, 15)
|
|
|
|
| 497 |
nearest_idx = WAYBACK_DF.index.get_indexer([target_date], method='nearest')[0]
|
| 498 |
wayback_item = WAYBACK_DF.iloc[nearest_idx]
|
|
|
|
| 499 |
wayback_title = f"Esri Wayback ({wayback_item.name.strftime('%Y-%m-%d')})"
|
| 500 |
wayback_url = (
|
| 501 |
wayback_item["ResourceURL_Template"]
|
|
|
|
| 506 |
)
|
| 507 |
except Exception as e:
|
| 508 |
print(f"Could not find a suitable Wayback basemap for {year}: {e}")
|
|
|
|
| 509 |
wayback_url = None
|
| 510 |
wayback_title = "Default Satellite"
|
| 511 |
+
# --- End Wayback Logic ---
|
| 512 |
|
| 513 |
collection = (
|
| 514 |
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
|
|
|
| 526 |
continue
|
| 527 |
|
| 528 |
mosaic = collection.qualityMosaic(selected_index)
|
|
|
|
| 529 |
|
| 530 |
+
# Create the base map
|
| 531 |
m = gee_folium.Map(zoom_start=14)
|
| 532 |
if wayback_url:
|
| 533 |
m.add_tile_layer(wayback_url, name=wayback_title, attribution="Esri")
|
| 534 |
else:
|
| 535 |
m.add_basemap("SATELLITE")
|
| 536 |
|
| 537 |
+
# --- START: New Legend and Layer Logic ---
|
| 538 |
+
if selected_index in ["NDVI", "RandomForest", "GujVDI", "CI", "EVI", "EVI2"]:
|
| 539 |
+
# Define bins for classification
|
| 540 |
+
bins = [0, 0.2, 0.4, 0.6, 0.8, 1]
|
| 541 |
+
histogram, bin_edges = get_histogram(selected_index, mosaic.select(selected_index), ee_geometry, bins)
|
| 542 |
+
|
| 543 |
+
total_pix = np.sum(histogram)
|
| 544 |
+
# Format histogram values as percentages, handle division by zero
|
| 545 |
+
if total_pix > 0:
|
| 546 |
+
formatted_histogram = [f"{h*100/total_pix:.2f}" for h in histogram]
|
| 547 |
+
else:
|
| 548 |
+
formatted_histogram = ["0.00"] * len(histogram)
|
| 549 |
+
|
| 550 |
+
# Add the classified legend to the map
|
| 551 |
+
m.add_legend(
|
| 552 |
+
title=f"{selected_index} Classification ({year})",
|
| 553 |
+
legend_dict={
|
| 554 |
+
"0-0.2: Open/Sparse Vegetation ({}%)".format(formatted_histogram[0]): "#FF0000",
|
| 555 |
+
"0.2-0.4: Low Vegetation ({}%)".format(formatted_histogram[1]): "#FFFF00",
|
| 556 |
+
"0.4-0.6: Moderate Vegetation ({}%)".format(formatted_histogram[2]): "#FFA500",
|
| 557 |
+
"0.6-0.8: Dense Vegetation ({}%)".format(formatted_histogram[3]): "#00FE00",
|
| 558 |
+
"0.8-1: Very Dense Vegetation ({}%)".format(formatted_histogram[4]): "#00A400",
|
| 559 |
+
},
|
| 560 |
+
position="bottomright",
|
| 561 |
+
draggable=False,
|
| 562 |
+
)
|
| 563 |
+
|
| 564 |
+
# Define visualization parameters for the classified layer
|
| 565 |
+
ind_vis_params = {
|
| 566 |
+
"min": 0,
|
| 567 |
+
"max": 1,
|
| 568 |
+
"palette": ["#FF0000", "#FFFF00", "#FFA500", "#00FE00", "#00A400"],
|
| 569 |
+
}
|
| 570 |
+
# Add the classified raster layer to the map
|
| 571 |
+
m.addLayer(mosaic.select(selected_index).clip(ee_geometry), ind_vis_params, f"{selected_index} Classified Layer ({year})")
|
| 572 |
+
|
| 573 |
+
else: # Fallback for indices without a classified legend (original behavior)
|
| 574 |
+
vis_params = {
|
| 575 |
+
"min": 0.0, "max": 1.0,
|
| 576 |
+
"palette": ['FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901', '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01', '012E01', '011D01', '011301']
|
| 577 |
+
}
|
| 578 |
+
clipped_image = mosaic.select(selected_index).clip(ee_geometry)
|
| 579 |
+
m.addLayer(clipped_image, vis_params, f"{selected_index} {year}")
|
| 580 |
+
m.add_colorbar(vis_params=vis_params, label=f"{selected_index} Value")
|
| 581 |
+
# --- END: New Legend and Layer Logic ---
|
| 582 |
+
|
| 583 |
folium.GeoJson(geometry_gdf, name="Geometry", style_function=lambda x: {"color": "yellow", "fillOpacity": 0, "weight": 2.5}).add_to(m)
|
| 584 |
m.fit_bounds(map_bounds, padding=(10, 10))
|
| 585 |
m.addLayerControl()
|
|
|
|
| 590 |
|
| 591 |
return f"Comparison generated for {selected_years[0]} and {selected_years[1]}.", maps_html[0], maps_html[1]
|
| 592 |
|
| 593 |
+
# --- Gradio Interface (unchanged) ---
|
| 594 |
theme = gr.themes.Soft(primary_hue="teal", secondary_hue="orange").set(
|
| 595 |
background_fill_primary="white"
|
| 596 |
)
|
|
|
|
| 617 |
url_input = gr.Textbox(label="Or Provide File URL", placeholder="e.g., https://.../my_file.kml")
|
| 618 |
buffer_input = gr.Number(label="Buffer (meters)", value=50)
|
| 619 |
process_button = gr.Button("Process Input", variant="primary")
|
| 620 |
+
|
| 621 |
with gr.Accordion("Advanced Settings", open=False):
|
| 622 |
gr.Markdown("### Select Vegetation Indices")
|
| 623 |
all_veg_indices = ["GujVDI", "NDVI", "EVI", "EVI2", "RandomForest", "CI"]
|
|
|
|
| 642 |
max_year_input = gr.Number(label="End Year", value=today.year, precision=0)
|
| 643 |
|
| 644 |
calculate_button = gr.Button("Calculate Vegetation Indices", variant="primary")
|
| 645 |
+
|
| 646 |
|
| 647 |
with gr.Column(scale=2):
|
| 648 |
gr.Markdown("## 2. Results")
|
| 649 |
info_box = gr.Textbox(label="Status", interactive=False, placeholder="Status messages will appear here...")
|
| 650 |
map_output = gr.HTML(label="Map View")
|
| 651 |
stats_output = gr.DataFrame(label="Geometry Metrics")
|
| 652 |
+
|
| 653 |
gr.Markdown("### Digital Elevation Model (DEM) and Slope")
|
| 654 |
with gr.Row():
|
| 655 |
dem_map_output = gr.HTML(label="DEM Map")
|
|
|
|
| 666 |
with gr.Row():
|
| 667 |
comparison_index_select = gr.Radio(all_veg_indices, label="Select Index for Comparison", value="NDVI")
|
| 668 |
comparison_years_select = gr.CheckboxGroup(label="Select Two Years to Compare", choices=[])
|
| 669 |
+
|
| 670 |
compare_button = gr.Button("Generate Comparison Maps", variant="secondary")
|
| 671 |
+
|
| 672 |
with gr.Row():
|
| 673 |
map_year_1_output = gr.HTML(label="Comparison Map 1")
|
| 674 |
map_year_2_output = gr.HTML(label="Comparison Map 2")
|
|
|
|
| 701 |
geometry_json, buffer_json, veg_indices,
|
| 702 |
evi_vars, date_range, int(min_year), int(max_year), progress
|
| 703 |
)
|
| 704 |
+
|
| 705 |
status_message = error_msg or success_msg
|
| 706 |
first_plot = plots[0] if plots else None
|
| 707 |
df_display = df.round(3) if df is not None else None
|
| 708 |
+
|
| 709 |
available_years = []
|
| 710 |
if df is not None and 'Year' in df.columns:
|
| 711 |
available_years = sorted(df['Year'].unique().tolist())
|
| 712 |
+
|
| 713 |
return status_message, df_display, df, first_plot, gr.update(choices=available_years, value=[])
|
| 714 |
|
| 715 |
except Exception as e:
|