WNTR_GIS / app.py
razaali10's picture
Update app.py
565f251 verified
import streamlit as st
import wntr
import geopandas as gpd
import pandas as pd
import tempfile
import zipfile
import os
import numpy as np
st.set_page_config(layout="wide")
st.title("🌊 EPANET INP β†’ Simulation β†’ Shapefiles")
uploaded_file = st.file_uploader("Upload EPANET .inp file", type=["inp"])
# Allow multiple shapefile uploads
st.subheader("πŸ“₯ Or Import from Shapefiles")
node_files = st.file_uploader("Upload all Node Shapefile components (.shp, .shx, .dbf, .prj)", accept_multiple_files=True)
link_files = st.file_uploader("Upload all Link Shapefile components (.shp, .shx, .dbf, .prj)", accept_multiple_files=True)
def extract_shapefile(files, temp_dir, layer_name):
shp_path = None
for file in files:
suffix = os.path.splitext(file.name)[-1]
file_path = os.path.join(temp_dir, f"{layer_name}{suffix}")
with open(file_path, "wb") as f:
f.write(file.read())
if suffix == ".shp":
shp_path = file_path
return shp_path
if node_files and link_files:
try:
temp_shape_dir = tempfile.mkdtemp()
node_shp_path = extract_shapefile(node_files, temp_shape_dir, "nodes")
link_shp_path = extract_shapefile(link_files, temp_shape_dir, "links")
node_gdf = gpd.read_file(node_shp_path)
link_gdf = gpd.read_file(link_shp_path)
wn = wntr.network.WaterNetworkModel()
for _, row in node_gdf.iterrows():
wn.add_junction(str(row['ID']), base_demand=0.0, elevation=float(row['elevation']),
coordinates=(row.geometry.x, row.geometry.y))
for _, row in link_gdf.iterrows():
wn.add_pipe(str(row['ID']),
start_node_name=str(row['Node1']),
end_node_name=str(row['Node2']),
length=float(row['length']),
diameter=float(row['diameter']),
roughness=float(row['roughness']),
minor_loss=0.0, status='OPEN')
st.success("Water network built from shapefiles!")
sim = wntr.sim.WNTRSimulator(wn)
results = sim.run_sim()
st.success("Simulation from shapefile model complete.")
except Exception as e:
st.error(f"Error loading shapefiles: {e}")
if uploaded_file:
temp_dir = tempfile.mkdtemp()
inp_path = os.path.join(temp_dir, uploaded_file.name)
with open(inp_path, "wb") as f:
f.write(uploaded_file.read())
st.success("INP file uploaded. Running simulation...")
try:
wn = wntr.network.WaterNetworkModel(inp_path)
sim = wntr.sim.EpanetSimulator(wn)
results = sim.run_sim()
st.success("Simulation complete.")
gis = wn.to_gis()
node_gdf = pd.concat([gis.junctions, gis.tanks, gis.reservoirs])
link_gdf = pd.concat([gis.pipes, gis.pumps, gis.valves])
node_gdf["pressure"] = results.node["pressure"].mean(axis=0)
node_gdf["demand"] = results.node["demand"].mean(axis=0)
link_gdf["flow"] = results.link["flowrate"].mean(axis=0)
link_gdf["velocity"] = results.link["velocity"].mean(axis=0)
node_gdf = node_gdf.set_geometry("geometry")
link_gdf = link_gdf.set_geometry("geometry")
if node_gdf.crs is None:
crs_choice = st.selectbox("Select CRS for shapefiles", ["EPSG:4326", "EPSG:3857", "None"])
if crs_choice != "None":
node_gdf = node_gdf.set_crs(crs_choice)
link_gdf = link_gdf.set_crs(crs_choice)
else:
st.warning("No CRS selected β€” export may fail.")
node_path = os.path.join(temp_dir, "nodes_with_results.shp")
link_path = os.path.join(temp_dir, "links_with_results.shp")
node_gdf.to_file(node_path, driver="ESRI Shapefile")
link_gdf.to_file(link_path, driver="ESRI Shapefile")
zip_path = os.path.join(temp_dir, "simulation_shapefiles.zip")
with zipfile.ZipFile(zip_path, 'w') as zipf:
for f in os.listdir(temp_dir):
if f.endswith((".shp", ".shx", ".dbf", ".prj")):
zipf.write(os.path.join(temp_dir, f), arcname=f)
with open(zip_path, "rb") as f:
st.download_button("πŸ“¦ Download Shapefiles with Results", f.read(), "simulation_shapefiles.zip")
st.subheader("πŸ§ͺ Preview of Node Results")
st.dataframe(node_gdf[["pressure", "demand"]].head())
st.subheader("πŸ”— Preview of Link Results")
st.dataframe(link_gdf[["flow", "velocity"]].head())
# ------------------------- Criticality Analysis -------------------------
st.subheader("⚠️ Pipe Criticality Analysis")
if st.button("Run Criticality Analysis on Large Pipes"):
try:
wn.options.time.duration = 72 * 3600
wn.options.hydraulic.demand_model = 'PDD'
wn.options.hydraulic.required_pressure = 17.57
wn.options.hydraulic.minimum_pressure = 0
pipes = wn.query_link_attribute('diameter', np.greater_equal, 24 * 0.0254,
link_type=wntr.network.model.Pipe)
pipes = list(pipes.index)
pressure_threshold = 14.06
sim = wntr.sim.WNTRSimulator(wn)
results = sim.run_sim()
min_pressure = results.node['pressure'].loc[:, wn.junction_name_list].min()
below_threshold_normal_conditions = set(min_pressure[min_pressure < pressure_threshold].index)
junctions_impacted = {}
for pipe_name in pipes:
wn.reset_initial_values()
pipe = wn.get_link(pipe_name)
act = wntr.network.controls.ControlAction(pipe, 'status', wntr.network.LinkStatus.Closed)
cond = wntr.network.controls.SimTimeCondition(wn, '=', '24:00:00')
ctrl = wntr.network.controls.Control(cond, act)
wn.add_control('close pipe ' + pipe_name, ctrl)
try:
sim = wntr.sim.WNTRSimulator(wn)
results = sim.run_sim()
min_pressure = results.node['pressure'].loc[:, wn.junction_name_list].min()
below_threshold = set(min_pressure[min_pressure < pressure_threshold].index)
junctions_impacted[pipe_name] = below_threshold - below_threshold_normal_conditions
except:
junctions_impacted[pipe_name] = set()
wn.remove_control('close pipe ' + pipe_name)
number_of_junctions_impacted = {k: len(v) for k, v in junctions_impacted.items()}
st.write("Junctions impacted per pipe:", number_of_junctions_impacted)
if number_of_junctions_impacted:
worst_pipe = max(number_of_junctions_impacted, key=number_of_junctions_impacted.get)
st.write(f"🚨 Most critical pipe: {worst_pipe} impacts {number_of_junctions_impacted[worst_pipe]} junctions")
except Exception as e:
st.error(f"Criticality analysis failed: {e}")
# ------------------------------------------------------------------------
except Exception as e:
st.error(f"Simulation failed: {e}")