Hydrology / app.py
jeffrey1963's picture
Update app.py
6c5becf verified
import os
import geopandas as gpd
import rasterio
import gradio as gr
import matplotlib.pyplot as plt
import pandas as pd
from rasterio.plot import show
# File paths (ensure these exist in Hugging Face)
tiff_paths = {
"Tippecanoe": "tippecanoe_corn.tif",
"White": "white_corn.tif",
"Carroll": "carroll_corn.tif"
}
shapefile_paths = {
"Tippecanoe": "tippecanoe_corn_fields.shp",
"White": "white_corn_fields.shp",
"Carroll": "carroll_corn_fields.shp"
}
filtered_shapefile_paths = {
"Tippecanoe": "tippecanoe_filtered.shp",
"White": "white_filtered.shp",
"Carroll": "carroll_filtered.shp"
}
merged_filtered_corn_shapefile = "merged_filtered_corn_fields.shp"
merged_hydrology_shapefile = "merged_hydrology.shp"
county_shapefile = "indiana_counties.shp" # Ensure this is uploaded!
import geopandas as gpd
# πŸ“Œ Function: Read and Summarize Filtered Corn Fields
def simple_summarize_corn_fields():
summary = ""
# Ensure filenames are consistent (all lowercase)
counties = ["tippecanoe", "white", "carroll"]
for county in counties:
shapefile_path = f"{county}_filtered_corn_fields.shp"
try:
# Read the shapefile
gdf = gpd.read_file(shapefile_path)
# Check if it's empty
if gdf.empty:
summary += f"❌ No data for {county.capitalize()}.\n"
continue
# Calculate the total area
total_area = gdf['area_acres'].sum()
num_fields = len(gdf)
summary += (f"βœ… **{county.capitalize()}**:\n"
f" - Total Area: {round(total_area, 2)} acres\n"
f" - Number of Fields: {num_fields}\n\n")
except Exception as e:
summary += f"❌ Error processing {county.capitalize()}: {str(e)}\n"
return summary
# πŸ“Œ Function: Check Merged Hydrology Shapefile
def check_merged_hydrology():
hydrology_path = "merged_hydrology.shp"
try:
gdf = gpd.read_file(hydrology_path)
# Check if the file loaded properly
if gdf.empty:
return "❌ ERROR: Shapefile is empty."
# Display basic information
crs_info = f"βœ… File Loaded Successfully!\nCRS: {gdf.crs}"
geom_info = f"Geometry Head:\n{gdf.geometry.head()}"
empty_check = f"Is Empty: {gdf.empty}"
return f"{crs_info}\n{geom_info}\n{empty_check}"
except Exception as e:
return f"❌ Error loading shapefile: {str(e)}"
import geopandas as gpd
import matplotlib.pyplot as plt
def plot_shapefile(shapefile_path, title):
"""Loads and plots a shapefile"""
try:
gdf = gpd.read_file(shapefile_path)
fig, ax = plt.subplots(figsize=(8, 8))
gdf.plot(ax=ax, color="green", alpha=0.5, edgecolor="none") # βœ… SAME FOR ALL FILES
plt.title(title)
plot_path = f"{title.replace(' ', '_')}.png"
plt.savefig(plot_path)
plt.close()
return plot_path
except Exception as e:
return f"❌ ERROR: {str(e)}"
# πŸ“Œ Function: View TIFF Before Processing
def view_tiff(county):
if county not in tiff_paths:
return f"❌ Error: County {county} not recognized."
tiff_path = tiff_paths[county]
try:
with rasterio.open(tiff_path) as src:
fig, ax = plt.subplots(figsize=(8, 8))
show(src, ax=ax, title=f"{county} Corn TIFF")
plt.savefig(f"{county}_tiff.png")
plt.close()
return f"{county}_tiff.png"
except Exception as e:
return f"❌ ERROR: {str(e)}"
# πŸ“Œ Function: Convert TIFF to Shapefile & Plot
def convert_tiff_to_shapefile(county):
if county not in tiff_paths:
return f"❌ Error: County {county} not recognized."
shapefile_path = shapefile_paths[county]
try:
gdf = gpd.read_file(shapefile_path)
fig, ax = plt.subplots(figsize=(8, 8))
gdf.plot(ax=ax, color="green", alpha=0.5, edgecolor="black", label=f"{county} Corn Fields")
plt.title(f"{county} Corn Shapefile")
plot_path = f"{county}_shapefile.png"
plt.savefig(plot_path)
plt.close()
return f"βœ… Converted {county} TIFF to Shapefile: {shapefile_path}", plot_path
except Exception as e:
return f"❌ ERROR: {str(e)}", None
# πŸ“Œ Function: Filter Corn Fields & Plot
def filter_corn_fields(county):
if county not in shapefile_paths:
return f"❌ Error: County {county} not recognized."
input_shapefile = f"{county.lower()}_corn_fields.shp"
output_shapefile = f"{county.lower()}_filtered_corn_fields.shp"
try:
gdf = gpd.read_file(input_shapefile)
gdf["area_acres"] = gdf.geometry.area / 4046.856 # Convert to acres
filtered_gdf = gdf[gdf["area_acres"] >= 60] # Keep only fields >60 acres
filtered_gdf.to_file(output_shapefile)
# Plot filtered shapefile
fig, ax = plt.subplots(figsize=(8, 8))
filtered_gdf.plot(ax=ax, color="yellow", alpha=0.5, edgecolor="black", label=f"{county} Corn Fields (>60 acres)")
plt.title(f"{county} Filtered Corn Fields")
plot_path = f"{county}_filtered.png"
plt.savefig(plot_path)
plt.close()
return f"βœ… Filtered {county} Corn Fields (Saved as {output_shapefile})", plot_path
except Exception as e:
return f"❌ ERROR: {str(e)}", None
def merge_filtered_counties():
"""Merges filtered corn fields, saves, and directly plots."""
merged_filtered_shapefile = "merged_filtered_corn_fields.shp"
filtered_shapefiles = [
"tippecanoe_filtered_corn_fields.shp",
"white_filtered_corn_fields.shp",
"carroll_filtered_corn_fields.shp"
]
try:
gdfs = [gpd.read_file(shp) for shp in filtered_shapefiles]
merged_gdf = gpd.GeoDataFrame(pd.concat(gdfs, ignore_index=True))
merged_gdf.to_file(merged_filtered_shapefile)
# βœ… Directly Plot the Merged Filtered Corn Fields
fig, ax = plt.subplots(figsize=(8, 8))
merged_gdf.plot(ax=ax, color="green", alpha=0.5, edgecolor="black")
plt.title("Merged Filtered Corn Fields")
plot_path = "merged_filtered_plot.png"
plt.savefig(plot_path)
plt.close()
return f"βœ… Merged filtered counties into: {merged_filtered_shapefile}", plot_path
except Exception as e:
return f"❌ ERROR: {str(e)}", None
def plot_merged_hydrology():
hydrology_path = "merged_hydrology.shp"
try:
gdf = gpd.read_file(hydrology_path)
# Debug checks
print("βœ… Hydrology file loaded successfully.")
print("CRS:", gdf.crs)
print("Number of geometries:", len(gdf))
print("Geometry type:\n", gdf.geom_type.value_counts())
if gdf.empty:
return "❌ Hydrology shapefile is empty.", None
# Plotting the hydrology features
fig, ax = plt.subplots(figsize=(10, 10))
gdf.plot(ax=ax, color="blue", alpha=0.5, edgecolor="none")
plt.title("Merged Hydrology Features")
plot_path = "merged_hydrology_plot.png"
plt.savefig(plot_path)
plt.close()
return "βœ… Hydrology plot generated successfully.", plot_path
except Exception as e:
return f"❌ ERROR while plotting hydrology: {str(e)}", None
# πŸ“Œ Function: Overlay Hydrology and Plot Intersections
def overlay_hydrology_and_plot():
try:
corn_gdf = gpd.read_file(merged_filtered_corn_shapefile)
hydro_gdf = gpd.read_file(merged_hydrology_shapefile)
# Ensure CRS matches
if corn_gdf.crs != hydro_gdf.crs:
hydro_gdf = hydro_gdf.to_crs(corn_gdf.crs)
# Calculate intersections
intersections = gpd.overlay(corn_gdf, hydro_gdf, how="intersection")
if intersections.empty:
return "❌ No overlap detected between corn fields and hydrology features.", None
# Plot the overlay
fig, ax = plt.subplots(figsize=(10, 10))
corn_gdf.plot(ax=ax, color="yellow", alpha=0.5, edgecolor="none", label="Corn Fields")
hydro_gdf.plot(ax=ax, color="blue", alpha=0.3, edgecolor="none", label="Hydrology")
intersections.plot(ax=ax, color="red", alpha=0.7, edgecolor="none", label="Overlap")
plt.legend()
plt.title("Hydrology Overlay on Corn Fields")
output_path = "hydrology_overlay.png"
plt.savefig(output_path)
plt.close()
return f"βœ… Hydrology overlay complete with intersections detected!", output_path
except Exception as e:
return f"❌ Error during hydrology overlay: {str(e)}", None
import geopandas as gpd
import pandas as pd
import geopandas as gpd
import pandas as pd
import geopandas as gpd
import pandas as pd
# πŸ“Œ Function: Summarize Corn Fields and Display in a Table
def summarize_corn_fields_table():
# Initialize an empty list to store summary data
summary_data = []
# List of counties (consistent lowercase filenames)
counties = ["tippecanoe", "white", "carroll"]
for county in counties:
shapefile_path = f"{county}_filtered_corn_fields.shp"
try:
# Read the shapefile
gdf = gpd.read_file(shapefile_path)
# Skip if empty
if gdf.empty:
summary_data.append({
"County": county.capitalize(),
"Total Area (acres)": "No data",
"Number of Fields": 0,
"Average Field Size (acres)": "No data",
"Max Field Size (acres)": "No data",
"Min Field Size (acres)": "No data"
})
continue
# Calculate metrics
total_area = gdf['area_acres'].sum()
num_fields = len(gdf)
avg_field_size = gdf['area_acres'].mean()
max_field_size = gdf['area_acres'].max()
min_field_size = gdf['area_acres'].min()
# Add data to summary list
summary_data.append({
"County": county.capitalize(),
"Total Area (acres)": round(total_area, 2),
"Number of Fields": num_fields,
"Average Field Size (acres)": round(avg_field_size, 2),
"Max Field Size (acres)": round(max_field_size, 2),
"Min Field Size (acres)": round(min_field_size, 2)
})
except Exception as e:
summary_data.append({
"County": county.capitalize(),
"Total Area (acres)": f"Error: {str(e)}",
"Number of Fields": "-",
"Average Field Size (acres)": "-",
"Max Field Size (acres)": "-",
"Min Field Size (acres)": "-"
})
# Convert summary to a DataFrame for table display
summary_df = pd.DataFrame(summary_data)
return summary_df
import geopandas as gpd
def calculate_distance_to_river1():
"""
Calculates the distance from each cornfield to the Wabash River
and updates the shapefiles with a new 'distance_to_wabash' column.
"""
try:
# File paths for filtered cornfield shapefiles and hydrology shapefile
county_files = {
"Tippecanoe": "tippecanoe_filtered_corn_fields.shp",
"White": "white_filtered_corn_fields.shp",
"Carroll": "carroll_filtered_corn_fields.shp"
}
river_shapefile = "merged_hydrology.shp"
# Load the hydrology shapefile
river_gdf = gpd.read_file(river_shapefile)
river_geometry = river_gdf.unary_union
results = {}
for county, corn_shapefile in county_files.items():
# Load cornfield shapefile
corn_gdf = gpd.read_file(corn_shapefile)
# Ensure CRS consistency
if corn_gdf.crs != river_gdf.crs:
river_gdf = river_gdf.to_crs(corn_gdf.crs)
# Calculate distance from centroids of cornfields to the Wabash River
corn_gdf['distance_to_wabash'] = corn_gdf.geometry.centroid.distance(river_geometry)
# Save updated shapefile with distances
output_path = corn_shapefile.replace('.shp', '_with_distances.shp')
corn_gdf.to_file(output_path)
# Collect statistics for output
min_distance = corn_gdf['distance_to_wabash'].min()
max_distance = corn_gdf['distance_to_wabash'].max()
avg_distance = corn_gdf['distance_to_wabash'].mean()
results[county] = {
"Min Distance (m)": round(min_distance, 2),
"Max Distance (m)": round(max_distance, 2),
"Avg Distance (m)": round(avg_distance, 2)
}
# Create summary table
summary_text = "βœ… Distance Calculation Complete\n\n"
for county, stats in results.items():
summary_text += f"### {county} County:\n"
for stat, value in stats.items():
summary_text += f"- {stat}: {value} m\n"
summary_text += "\n"
return summary_text
except Exception as e:
return f"❌ ERROR: {str(e)}"
def calculate_distance_to_river():
"""
Calculates the distance from each cornfield to the Wabash River
and updates the shapefiles with a new 'distance_t' column (shortened for shapefile compatibility).
"""
try:
# File paths for filtered cornfield shapefiles and hydrology shapefile
county_files = {
"Tippecanoe": "tippecanoe_filtered_corn_fields.shp",
"White": "white_filtered_corn_fields.shp",
"Carroll": "carroll_filtered_corn_fields.shp"
}
river_shapefile = "merged_hydrology.shp"
# Load the hydrology shapefile
river_gdf = gpd.read_file(river_shapefile)
river_geometry = river_gdf.unary_union
results = {}
for county, corn_shapefile in county_files.items():
# Load cornfield shapefile
corn_gdf = gpd.read_file(corn_shapefile)
# Ensure CRS consistency
if corn_gdf.crs != river_gdf.crs:
river_gdf = river_gdf.to_crs(corn_gdf.crs)
# βœ… Use 'distance_t' to respect the 10-character limit
corn_gdf['distance_t'] = corn_gdf.geometry.centroid.distance(river_geometry)
# Save updated shapefile with distances
output_path = corn_shapefile.replace('.shp', '_with_distances.shp')
corn_gdf.to_file(output_path)
# Collect statistics for output
min_distance = corn_gdf['distance_t'].min()
max_distance = corn_gdf['distance_t'].max()
avg_distance = corn_gdf['distance_t'].mean()
results[county] = {
"Min Distance (m)": round(min_distance, 2),
"Max Distance (m)": round(max_distance, 2),
"Avg Distance (m)": round(avg_distance, 2)
}
# Create summary table
summary_text = "βœ… Distance Calculation Complete\n\n"
for county, stats in results.items():
summary_text += f"### {county} County:\n"
for stat, value in stats.items():
summary_text += f"- {stat}: {value} m\n"
summary_text += "\n"
return summary_text
except Exception as e:
return f"❌ ERROR: {str(e)}"
def check_shapefile_columns():
counties = {
"Tippecanoe": "tippecanoe_filtered_corn_fields_with_distances.shp",
"White": "white_filtered_corn_fields_with_distances.shp",
"Carroll": "carroll_filtered_corn_fields_with_distances.shp"
}
results = {}
for county, shapefile in counties.items():
try:
gdf = gpd.read_file(shapefile)
if gdf.empty:
results[county] = "❌ Shapefile is empty."
else:
results[county] = list(gdf.columns)
except Exception as e:
results[county] = f"❌ Error reading {county}: {str(e)}"
return results
def calculate_nitrogen_runoff_with_distances():
counties = {
"Tippecanoe": "tippecanoe_filtered_corn_fields_with_distances.shp",
"White": "white_filtered_corn_fields_with_distances.shp",
"Carroll": "carroll_filtered_corn_fields_with_distances.shp"
}
nitrogen_application_rate = 250 # lbs/acre of AA
runoff_percentage = 0.20 # 20% runoff
results = {}
for county, shapefile in counties.items():
try:
gdf = gpd.read_file(shapefile)
# Check if necessary columns exist
if gdf.empty or "area_acres" not in gdf.columns or "distance_t" not in gdf.columns:
results[county] = "❌ Missing 'area_acres' or 'distance_t' data."
continue
# Calculate total N applied
gdf["total_N_applied"] = gdf["area_acres"] * nitrogen_application_rate
# Calculate N runoff (20% for now, distance will be considered later)
gdf["N_runoff_lbs"] = gdf["total_N_applied"] * runoff_percentage
# Sum total runoff for the county
total_runoff = gdf["N_runoff_lbs"].sum()
results[county] = f"βœ… Total N Runoff: {round(total_runoff, 2)} lbs (Distance Included)"
except Exception as e:
results[county] = f"❌ Error processing {county}: {str(e)}"
return results
# πŸ“Œ Function: Calculate Runoff (N & P) and Generate Table
def calculate_runoff_and_generate_table():
counties = ["Tippecanoe", "White", "Carroll"]
results = []
aa_rate = 250 # lbs/acre for Anhydrous Ammonia (N)
map_rate = 75 # lbs/acre for MAP (P)
n_cleanup_cost = 0.50 # $ per lb for Nitrogen
p_cleanup_cost = 1.50 # $ per lb for Phosphorus
# Nutrient contents from fertilizer
N_from_AA = aa_rate * 0.82 # 82% N from Anhydrous Ammonia
N_from_MAP = map_rate * 0.11 # 11% N from MAP
P_from_MAP = map_rate * 0.52 # 52% P from MAP
total_N_per_acre = N_from_AA + N_from_MAP # lbs per acre
total_P_per_acre = P_from_MAP # lbs per acre
for county in counties:
shapefile_path = f"{county.lower()}_filtered_corn_fields_with_distances.shp"
try:
# Load shapefile
gdf = gpd.read_file(shapefile_path)
# Ensure necessary columns exist
if 'area_acres' not in gdf.columns or 'distance_t' not in gdf.columns:
results.append({
"County": county,
"Total Area (acres)": "Missing Data",
"N Runoff (lbs)": "N/A",
"P Runoff (lbs)": "N/A",
"N Cleanup Cost ($)": "N/A",
"P Cleanup Cost ($)": "N/A",
})
continue
# Calculate total area
total_area = gdf['area_acres'].sum()
# Apply constant runoff rates
n_runoff = total_N_per_acre * total_area * 0.20 # 20% N runoff
p_runoff = total_P_per_acre * total_area * 0.05 # 5% P runoff
# Calculate cleanup costs
n_cleanup = n_runoff * n_cleanup_cost
p_cleanup = p_runoff * p_cleanup_cost
# Add results to the table
results.append({
"County": county,
"Total Area (acres)": round(total_area, 2),
"N Runoff (lbs)": round(n_runoff, 2),
"P Runoff (lbs)": round(p_runoff, 2),
"N Cleanup Cost ($)": round(n_cleanup, 2),
"P Cleanup Cost ($)": round(p_cleanup, 2),
})
except Exception as e:
results.append({
"County": county,
"Total Area (acres)": f"Error: {str(e)}",
"N Runoff (lbs)": "N/A",
"P Runoff (lbs)": "N/A",
"N Cleanup Cost ($)": "N/A",
"P Cleanup Cost ($)": "N/A",
})
# Convert results to a table
runoff_df = pd.DataFrame(results)
return runoff_df
# πŸ“Œ Function: Plot ALL Layers Together
def plot_all_layers():
try:
fig, ax = plt.subplots(figsize=(8, 8))
# βœ… Plot County Boundaries
if os.path.exists(county_shapefile):
county_gdf = gpd.read_file(county_shapefile)
county_gdf.boundary.plot(ax=ax, color="black", linewidth=1, label="County Boundaries")
# βœ… Plot Corn Fields
if os.path.exists(merged_filtered_corn_shapefile):
corn_gdf = gpd.read_file(merged_filtered_corn_shapefile)
corn_gdf.plot(ax=ax, color="yellow", alpha=0.5, edgecolor="none", label="Corn Fields (>60 acres)")
# βœ… Plot Hydrology
if os.path.exists(merged_hydrology_shapefile):
hydro_gdf = gpd.read_file(merged_hydrology_shapefile)
hydro_gdf.plot(ax=ax, color="blue", alpha=0.5, edgecolor="none", label="Hydrology Features")
plt.title("Corn Fields (>60 acres), Hydrology, and County Boundaries")
plt.legend()
plt.savefig("all_layers.png")
plt.close()
return "all_layers.png"
except Exception as e:
return f"❌ ERROR: {str(e)}"
## **🌎 Gradio UI (Student Interface)**
with gr.Blocks() as demo:
gr.Markdown("# 🌽 Hydrology & Corn Runoff Analysis")
with gr.Row():
county_input = gr.Textbox(label="Enter County Name (Tippecanoe, White, Carroll)")
view_tiff_btn = gr.Button("View County TIFF")
tiff_output = gr.Image(label="TIFF Preview")
with gr.Row():
process_btn = gr.Button("Convert TIFF to Shapefile")
process_output = gr.Textbox(label="Processing Status")
shapefile_plot_output = gr.Image(label="Shapefile Preview")
with gr.Row():
filter_btn = gr.Button("Filter Corn Fields (>60 acres)")
filter_output = gr.Textbox(label="Filtering Status")
filtered_plot_output = gr.Image(label="Filtered Corn Fields")
with gr.Row():
merge_btn = gr.Button("Merge Filtered Counties")
merge_output = gr.Textbox(label="Merging Status")
merged_plot_output = gr.Image(label="Merged Corn Fields") # βœ… Make sure this matches
merge_btn.click(merge_filtered_counties, outputs=[merge_output, merged_plot_output])
with gr.Row():
hydrology_plot_btn = gr.Button("Plot Merged Hydrology")
hydrology_plot_output = gr.Image(label="Hydrology Features")
# Button click handler
hydrology_plot_btn.click(plot_merged_hydrology, outputs=[hydrology_plot_output, hydrology_plot_output])
with gr.Row():
hydro_overlay_btn = gr.Button("Overlay Hydrology with Intersections")
hydro_overlay_output = gr.Textbox(label="Hydrology Overlay Status")
hydro_overlay_plot = gr.Image(label="Hydrology Overlaid on Corn Fields")
# 🟒 Add the Click Action
hydro_overlay_btn.click(
overlay_hydrology_and_plot,
outputs=[hydro_overlay_output, hydro_overlay_plot]
)
# ➑️ Add Button for Table Summary
with gr.Row():
summary_btn = gr.Button("Summarize Corn Fields in Table")
summary_table = gr.DataFrame(label="Corn Field Summary by County")
with gr.Row():
distance_btn = gr.Button("Calculate Distances to Wabash River")
distance_output = gr.Textbox(label="Distance Summary")
# with gr.Row():
# check_columns_btn = gr.Button("Check Columns in Shapefiles")
# check_columns_output = gr.Textbox(label="Shapefile Columns by County")
# with gr.Row():
# distance_n_runoff_btn = gr.Button("Calculate N Runoff Using Distances")
# distance_n_runoff_output = gr.Textbox(label="Nitrogen Runoff Summary (with Distances)")
# πŸ“Œ Add Button for Runoff Summary Table
with gr.Row():
runoff_summary_btn = gr.Button("Calculate N & P Runoff Summary")
runoff_summary_table = gr.DataFrame(label="Nutrient Runoff and Cleanup Costs by County")
# with gr.Row():
# plot_btn = gr.Button("View Final Map")
# final_plot_output = gr.Image(label="Final Map (All Layers)")
# with gr.Row():
# check_hydro_btn = gr.Button("Check Merged Hydrology")
# hydro_check_output = gr.Textbox(label="Hydrology Shapefile Check")
# Trigger check when button is clicked
# check_hydro_btn.click(check_merged_hydrology, outputs=hydro_check_output)
# 🟒 Button Clicks
view_tiff_btn.click(view_tiff, inputs=county_input, outputs=tiff_output)
process_btn.click(convert_tiff_to_shapefile, inputs=county_input, outputs=[process_output, shapefile_plot_output])
filter_btn.click(filter_corn_fields, inputs=county_input, outputs=[filter_output, filtered_plot_output])
merge_btn.click(merge_filtered_counties, outputs=merge_output)
hydro_overlay_btn.click(
overlay_hydrology_and_plot,
outputs=[hydro_overlay_output, hydro_overlay_plot]
)
# ➑️ Add to Button Click Actions
summary_btn.click(summarize_corn_fields_table, outputs=summary_table)
distance_btn.click(calculate_distance_to_river, outputs=distance_output)
# distance_n_runoff_btn.click(calculate_nitrogen_runoff_with_distances, outputs=distance_n_runoff_output)
# ➑️ Connect Button to Function
runoff_summary_btn.click(
calculate_runoff_and_generate_table,
outputs=runoff_summary_table
)
# check_columns_btn.click(check_shapefile_columns, outputs=check_columns_output)
# plot_btn.click(plot_all_layers, outputs=final_plot_output)
demo.launch()