flood-mapping-tool / app /pages /1_πŸ’§_Flood_extent_analysis.py
Simon Riezebos
Make link download
2a18dc5
raw
history blame contribute delete
11.9 kB
from datetime import date, timedelta
import folium
import pandas as pd
import streamlit as st
from src import hf_utils
from src.config_parameters import params
from src.gfm import get_cached_aois, get_cached_gfm_handler
from src.utils import (
add_about,
get_aoi_id_from_selector_preview,
get_existing_geojson,
set_tool_page_style,
toggle_menu_button,
)
from streamlit_folium import st_folium
today = date.today()
default_date_yesterday = today - timedelta(days=1)
# Page configuration
st.set_page_config(layout="wide", page_title=params["browser_title"])
# If app is deployed hide menu button
toggle_menu_button()
# Create sidebar
add_about()
# Page title
st.markdown("# Flood extent analysis")
# Set page style
set_tool_page_style()
# Create two rows: top and bottom panel
row1 = st.container()
row2 = st.container()
# Create two columns in the top panel: input map and paramters
col1, col2, col3, col4 = row1.columns([1, 1, 1, 2])
col2_1, col2_2 = row2.columns([3, 2])
# Retrieve GFM Handler and AOIs to fill AOI selector
gfm = get_cached_gfm_handler()
aois = get_cached_aois()
if "all_products" not in st.session_state:
st.session_state["all_products"] = None
# To force removing product checkboxes when AOI selector changes
def on_area_selector_change():
print("Area selector changed, removing product checkboxes")
st.session_state["all_products"] = None
# Contains AOI selector
with col1:
selected_area_name_id = st.selectbox(
"Select saved AOI",
options=[aoi["name_id_preview"] for aoi in aois.values()],
on_change=on_area_selector_change,
)
selected_area_id = get_aoi_id_from_selector_preview(aois, selected_area_name_id)
# Contain datepickers
with col2:
today = date.today()
two_weeks_ago = today - timedelta(days=14)
start_date = st.date_input("Start date", value=two_weeks_ago)
with col3:
end_date = st.date_input("End date", value=today)
# Contains available products button
with col4:
st.text(
"Button info",
help="""
This will show the timestamps of all available GFM products
that intersect with the AOI for the selected date range.
Getting the available products is a relatively fast operation
it will not trigger any product downloads.
""",
)
show_available_products = st.button("Show GFM products")
# If button above is triggered, get products from GFM
# Then save all products to the session state and rerun the app to display them
if show_available_products:
products = gfm.get_area_products(selected_area_id, start_date, end_date)
st.session_state["all_products"] = products
st.rerun()
# Contains the product checkboxes if they exist after pushing the "Show available products" button
with col2_2:
row_checkboxes = st.container()
row_buttons = st.container()
with row_checkboxes:
checkboxes = list()
# Products are checked against the index to check whether they are already downloaded
index_df = hf_utils.get_geojson_index_df()
if st.session_state["all_products"]:
# Get unique product time groups
unique_time_groups = set()
for product in st.session_state["all_products"]:
unique_time_groups.add(product["product_time_group"])
# Create dataframe for the table
product_data = []
for time_group in sorted(unique_time_groups):
# Check if any product in this group is already downloaded
products_in_group = [
p
for p in st.session_state["all_products"]
if p["product_time_group"] == time_group
]
dataset_link = ""
for product in products_in_group:
if product["product_id"] in index_df["product"].values:
available_status = "Available in Floodmap"
flood_geojson_path = index_df.loc[
index_df["product"] == product["product_id"],
"flood_geojson_path",
].values[0]
dataset_link = f"https://huggingface.co/datasets/rodekruis/flood-mapping/resolve/main/{flood_geojson_path}?download=true"
product_data.append(
{
"Check": False,
"Product time": time_group,
"Available": dataset_link,
}
)
product_groups_df = pd.DataFrame(product_data)
# Create the data editor with checkbox column
product_groups_st_df = st.data_editor(
product_groups_df,
column_config={
"Check": st.column_config.CheckboxColumn(
"Select",
help="Select products to process",
default=False,
),
"Product time": st.column_config.TextColumn(
"Product Time Group", disabled=True
),
"Available": st.column_config.LinkColumn("Available in dataset"),
},
hide_index=True,
disabled=["Product time", "Available"],
)
# Convert checkbox states to list for compatibility with existing code
checkboxes = product_groups_st_df["Check"].tolist()
with row_buttons:
below_checkbox_col1, below_checkbox_col2 = row_buttons.columns([1, 1])
# Contains the "Download Products" button
with below_checkbox_col1:
st.text(
"Button info",
help=""
"""
Will download the selected products from GFM to the Floodmap app
(click "Show available products" first if there are none).
Products that show that they have already been downloaded can be left checked,
they will be skipped.
""",
)
download_products = st.button("Download to Floodmap")
# If the button is clicked download all checked products that have not been downloaded yet
if download_products:
index_df = hf_utils.get_geojson_index_df()
# Get selected time groups from the table
selected_time_groups = product_groups_st_df[product_groups_st_df["Check"]][
"Product time"
].tolist()
# For each selected time group
for time_group in selected_time_groups:
# Get all products for this time group
products_in_group = [
p
for p in st.session_state["all_products"]
if p["product_time_group"] == time_group
]
# Download each product in the group that hasn't been downloaded yet
for product_to_download in products_in_group:
if product_to_download["product_id"] not in index_df["product"].values:
with st.spinner(
f"Getting GFM files for {product_to_download['product_time']}, this may take a couple of minutes"
):
gfm.download_flood_product(
selected_area_id, product_to_download
)
st.rerun()
# For all the selected products add them to the map if they are available
feature_groups = []
flood_featuregroup = None
selected_geojsons = []
if st.session_state["all_products"]:
index_df = hf_utils.get_geojson_index_df()
# Get unique time groups
unique_time_groups = sorted(
set(p["product_time_group"] for p in st.session_state["all_products"])
)
# For each checkbox (which corresponds to a time group)
for i, checkbox in enumerate(checkboxes):
if checkbox:
time_group = unique_time_groups[i]
# Get all products for this time group
products_in_group = [
p
for p in st.session_state["all_products"]
if p["product_time_group"] == time_group
]
# Create a feature group for this time group
flood_featuregroup = folium.FeatureGroup(name=time_group)
footprint_featuregroup = folium.FeatureGroup(name="Sentinel footprint")
group_has_features = False
# Add all available products from this group to the feature group
for product in products_in_group:
if product["product_id"] in index_df["product"].values:
# Get the raw geojsons for further usage in the app
flood_geojson = get_existing_geojson(product["product_id"], "flood")
selected_geojsons.append(flood_geojson)
# Convert geojsons to folium features to display on the map
flood_folium_geojson = folium.GeoJson(
flood_geojson,
style_function=lambda x: {
"fillColor": "#ff0000",
"color": "#ff0000",
"fillOpacity": 0.2,
},
)
flood_featuregroup.add_child(flood_folium_geojson)
footprint_geojson = get_existing_geojson(
product["product_id"], "footprint"
)
footprint_folium_geojson = folium.GeoJson(
footprint_geojson,
style_function=lambda x: {
"fillColor": "yellow",
"color": "yellow",
"fillOpacity": 0.2,
"weight": 0,
},
)
footprint_featuregroup.add_child(footprint_folium_geojson)
group_has_features = True
# Only add the feature group if it contains any features
if group_has_features:
feature_groups.append(flood_featuregroup)
feature_groups.append(footprint_featuregroup)
# Contains the map
with col2_1:
if selected_area_id:
# display the bounding box
bounding_box = aois[selected_area_id]["bbox"]
geojson_selected_area = folium.GeoJson(
bounding_box,
style_function=lambda x: {"fillOpacity": 0.2, "weight": 1},
)
feat_group_selected_area = folium.FeatureGroup(name="selected_area")
feat_group_selected_area.add_child(geojson_selected_area)
feature_groups.append(feat_group_selected_area)
# Create folium map
folium_map = folium.Map([39, 0], zoom_start=8)
folium_map.fit_bounds(feat_group_selected_area.get_bounds())
m = st_folium(
folium_map, width=800, height=450, feature_group_to_add=feature_groups
)
if flood_featuregroup:
flood_part_of_legend = """
<div style="display: flex; align-items: center;">
<div style="width: 20px; height: 20px; background: rgba(255, 0, 0, .2); border: 1px solid red;"></div>
<div style="margin-left: 5px;">Floods</div>
</div>
<div style="display: flex; align-items: center;">
<div style="width: 20px; height: 20px; background: rgba(255, 255, 0, .2); border: 1px solid yellow;"></div>
<div style="margin-left: 5px;">Sentinel Footprint</div>
</div>
"""
else:
flood_part_of_legend = ""
st.markdown(
f"""
<div style="display: flex; align-items: center; gap: 20px;">
<div style="display: flex; align-items: center;">
<div style="width: 20px; height: 20px; background: rgba(51, 136, 255, .2); border: 1px solid #3388ff;"></div>
<div style="margin-left: 5px;">AOI</div>
</div>
{flood_part_of_legend}
</div>
""",
unsafe_allow_html=True,
)
# Keep track of which page we're currently on for page switch events
st.session_state["prev_page"] = "flood_extent"