Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .streamlit/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[theme]
base = "light"
primaryColor = "#2F6B9A"
backgroundColor = "#FFFFFF"
secondaryBackgroundColor = "#F8F8F8"
textColor = "#000000"
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ local_etl:
@USE_LOCAL=true poetry run python -m src.main.__main__
@echo "Hazard App run successfully"

app:
@echo "Running Streamlit app..."
@PYTHONPATH=. poetry run streamlit run src/main/app.py

help:
@echo "Available make targets:"
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@
A CLI tool to process hazard data (flood, earthquake,
landslide, deforestation, cyclone, coastal erosion) and export results to AWS S3.

## Poetry Usage

Install Poetry (if not already installed):

```bash
curl -sSL https://install.python-poetry.org | python3 -
```

Project installation:

```bash
poetry --version
```

## Features

- Prepare exposure data from various sources
- Process multiple hazards with xarray and geopandas
- Export processed datasets to S3 using the VSI interface
- Configurable via `src/utils/constants.py`
- Interactive web app UI via Streamlit

## Requirements

Expand All @@ -33,6 +48,7 @@ make aws_etl # Run the hazard processing pipeline
make local_etl # Run the hazard processing pipeline locally without S3
make test # Run unit tests
make lint # Run lint checks
make app # Run the interactive Streamlit application
```

## Configuration
Expand Down
25 changes: 25 additions & 0 deletions docs/app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Interactive Web App (app.py)

Minimal guide for the Streamlit interface.

## Running the App

• `make app`
• `PYTHONPATH=. poetry run streamlit run src/main/app.py`

## Sidebar Controls

• **Use Local ETL (no S3)**: save results locally
• **Run Full Pipeline**: execute entire data workflow
• **Select Hazard**: choose one of `flood`, `earthquake`, `landslide`,
`deforestation`, `cyclone`, `coastal_erosion`
• **Run [Hazard] Analysis**: process selected hazard and display results

## Tabs

• **Analysis**: display and download processed data as CSV
• **Visualisation**: view previously saved results

## File Location

`src/main/app.py`
Binary file added img/MA-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
911 changes: 910 additions & 1 deletion poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ xarray = "^2025.3.1"
pandas = "^2.2.3"
rioxarray = "^0.18.2"
rasterstats = "^0.20.0"
streamlit = "^1.45.0"
plotly = "^6.0.1"
fsspec = "^2025.3.2"



Expand Down
139 changes: 139 additions & 0 deletions src/main/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import importlib
import os
from datetime import datetime
from pathlib import Path

import geopandas as gpd
import pandas as pd
import rioxarray as rxr
import streamlit as st
from dotenv import load_dotenv

import src.main.hazards as hazards
from src.main.__main__ import main as run_full_pipeline
from src.main.hazards import coastal_erosion, process_cyclone, process_deforestation
from src.utils import constants
from src.utils.utils import compute_hazard_mask, compute_population_exposure

load_dotenv(dotenv_path=os.path.expanduser("~/.hazard_tool_rc"))


def _lookup_opened_data(hazard_name: str):
"""
Load the admin boundaries, population raster,
and preprocessed hazard exposure raster.
Returns: admin_gdf, pop_raster, exposure_raster
"""
admin_gdf = gpd.read_file(constants.ADMIN_VECTOR_PATH)
pop_raster = rxr.open_rasterio(constants.POPULATION_RASTER_PATH, masked=True)
hazard_raster = rxr.open_rasterio(
constants.HAZARD_RASTER_PATH[hazard_name], masked=True
)
threshold = constants.HAZARD_THRESHOLD[hazard_name]

hazard_mask = compute_hazard_mask(hazard_raster, pop_raster, threshold)
exposure_raster = compute_population_exposure(hazard_mask, pop_raster)

return admin_gdf, pop_raster, exposure_raster


st.set_page_config(page_title="Hazard Processing Tool", layout="wide")
st.image("img/MA-logo.png", width=300)
st.title("Hazard Processing Tool")
st.markdown("Process hazard data and export results locally or to S3.")

st.sidebar.header("Controls")
use_local = st.sidebar.checkbox("Use Local ETL (no S3)", value=True)


def _handle_run_full():
os.environ["USE_LOCAL"] = "true" if use_local else "false"
importlib.reload(constants)
import src.utils.s3 as s3mod

importlib.reload(s3mod) # noqa: E702
import src.utils.utils as utilmod

importlib.reload(utilmod) # noqa: E702
with st.spinner(f"Executing full pipeline ({'Local' if use_local else 'AWS'})..."):
run_full_pipeline()
st.sidebar.success("Pipeline complete!")


if st.sidebar.button("Run Full Pipeline"):
_handle_run_full()

hazard_options = [
"flood",
"earthquake",
"landslide",
"deforestation",
"cyclone",
"coastal_erosion",
]
hazard_choice = st.sidebar.selectbox("Select Hazard", hazard_options)
tabs = st.tabs(["Analysis", "Visualisation"])

with tabs[0]:
st.header("Hazard Analysis")

if st.button(f"Run {hazard_choice.capitalize()} Analysis"):
os.environ["USE_LOCAL"] = "true" if use_local else "false"
importlib.reload(constants)

with st.spinner(f"Processing {hazard_choice}..."):
if hazard_choice in ["flood", "earthquake", "landslide"]:
admin_gdf, pop_raster, exposure_raster = _lookup_opened_data(
hazard_choice
)
df = hazards.process_hazards(admin_gdf, pop_raster, exposure_raster)
elif hazard_choice == "deforestation":
admin_gdf = gpd.read_file(constants.ADMIN_VECTOR_PATH)
df = process_deforestation(admin_gdf)
elif hazard_choice == "cyclone":
admin_gdf = gpd.read_file(constants.ADMIN_VECTOR_PATH)
df = process_cyclone(admin_gdf)
else: # coastal_erosion
admin_gdf = gpd.read_file(constants.ADMIN_VECTOR_PATH)
df = coastal_erosion(admin_gdf)

st.success(f"{hazard_choice.capitalize()} analysis complete!")
st.session_state["result_df"] = df
st.dataframe(df, use_container_width=True)

# Save the result
output_path = constants.HAZARD_OUTPUT_PATH[hazard_choice]
if use_local:
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
df.to_csv(output_path, index=False)
else:
# Still save locally even if tick was to S3
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
df.to_csv(output_path, index=False)

if "result_df" in st.session_state:
csv_data = st.session_state["result_df"].to_csv(index=False).encode("utf-8")
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
fname = f"{hazard_choice}_results_{ts}.csv"
st.download_button(
"Download Results as CSV", data=csv_data, file_name=fname, mime="text/csv"
)
else:
st.info("Click 'Run … Analysis' above to start.")

with tabs[1]:
st.header("Visualisation")

if "result_df" in st.session_state:
df = st.session_state["result_df"]
else:
csv_path = Path(constants.HAZARD_OUTPUT_PATH[hazard_choice])
df = pd.read_csv(csv_path) if csv_path.exists() else None

if df is not None:
st.dataframe(df, use_container_width=True)
else:
st.info("Run an analysis (or the full pipeline) to visualise results.")

st.markdown("---")
st.markdown("Built on the hazard-processing-tool AWS pipeline.")