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}")