Spaces:
Running
Running
changes silvia date ranges and aoi merging
Browse files- app/pages/1_π_Flood_extent_analysis.py +72 -14
- app/src/gfm.py +101 -10
app/pages/1_π_Flood_extent_analysis.py
CHANGED
@@ -5,7 +5,7 @@ import folium
|
|
5 |
import streamlit as st
|
6 |
from folium.plugins import Draw
|
7 |
from src.config_parameters import params
|
8 |
-
from src.gfm import download_gfm_geojson, get_existing_flood_geojson
|
9 |
from src.utils import (
|
10 |
add_about,
|
11 |
set_tool_page_style,
|
@@ -28,21 +28,46 @@ st.markdown("# Flood extent analysis")
|
|
28 |
# Set page style
|
29 |
set_tool_page_style()
|
30 |
|
|
|
|
|
|
|
|
|
31 |
# Create two rows: top and bottom panel
|
32 |
row1 = st.container()
|
33 |
# Crate two columns in the top panel: input map and paramters
|
34 |
col1, col2 = row1.columns([2, 1])
|
35 |
feat_group_selected_area = folium.FeatureGroup(name="selected_area")
|
36 |
with col1:
|
37 |
-
area_type = st.radio(
|
|
|
|
|
|
|
|
|
38 |
if area_type == "Existing area":
|
39 |
with open("./bboxes/bboxes.json", "r") as f:
|
40 |
bboxes = json.load(f)
|
41 |
selected_area = st.selectbox("Select saved area", options=bboxes.keys())
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
elif area_type == "New area":
|
48 |
new_area_name = st.text_input("Area name")
|
@@ -51,6 +76,15 @@ with col1:
|
|
51 |
# Create folium map
|
52 |
# call to render Folium map in Streamlit
|
53 |
folium_map = folium.Map([39, 0], zoom_start=8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
# Add drawing tools to map
|
55 |
if area_type == "New area":
|
56 |
Draw(
|
@@ -120,23 +154,47 @@ if submitted:
|
|
120 |
|
121 |
# Show loader because it will take a while
|
122 |
with st.spinner("Getting GFM files... Please wait..."):
|
123 |
-
# If a new area save the bounding box
|
124 |
if area_type == "New area":
|
125 |
-
#
|
|
|
|
|
|
|
126 |
with open("./bboxes/bboxes.json", "r") as f:
|
127 |
bboxes = json.load(f)
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
with open("./bboxes/bboxes.json", "w") as f:
|
131 |
-
json.dump(bboxes, f)
|
132 |
|
133 |
-
# Download files,
|
134 |
coords = selected_area_geojson["geometry"]["coordinates"][0]
|
135 |
-
download_gfm_geojson(area_name, new_coordinates=coords)
|
|
|
|
|
|
|
|
|
|
|
136 |
|
137 |
# For an existing area just get the latest update from GFM
|
138 |
if area_type == "Existing area":
|
139 |
-
download_gfm_geojson(area_name)
|
|
|
|
|
|
|
|
|
140 |
|
141 |
# Display that getting the files is finished
|
142 |
st.markdown("Getting GFM files finished")
|
|
|
5 |
import streamlit as st
|
6 |
from folium.plugins import Draw
|
7 |
from src.config_parameters import params
|
8 |
+
from src.gfm import download_gfm_geojson, get_existing_flood_geojson, sync_cached_data
|
9 |
from src.utils import (
|
10 |
add_about,
|
11 |
set_tool_page_style,
|
|
|
28 |
# Set page style
|
29 |
set_tool_page_style()
|
30 |
|
31 |
+
# Sync cached data
|
32 |
+
# ! WARNING: will erase your output folder
|
33 |
+
# # # sync_cached_data()
|
34 |
+
|
35 |
# Create two rows: top and bottom panel
|
36 |
row1 = st.container()
|
37 |
# Crate two columns in the top panel: input map and paramters
|
38 |
col1, col2 = row1.columns([2, 1])
|
39 |
feat_group_selected_area = folium.FeatureGroup(name="selected_area")
|
40 |
with col1:
|
41 |
+
area_type = st.radio(
|
42 |
+
label="Area Type",
|
43 |
+
options=["Existing area", "New area"],
|
44 |
+
label_visibility="hidden",
|
45 |
+
)
|
46 |
if area_type == "Existing area":
|
47 |
with open("./bboxes/bboxes.json", "r") as f:
|
48 |
bboxes = json.load(f)
|
49 |
selected_area = st.selectbox("Select saved area", options=bboxes.keys())
|
50 |
+
|
51 |
+
# retrieve and select available dates
|
52 |
+
if selected_area:
|
53 |
+
available_date_ranges = list(
|
54 |
+
bboxes[selected_area].keys()
|
55 |
+
) # will be used again below
|
56 |
+
selected_date_range = st.selectbox(
|
57 |
+
"Select available date range", options=available_date_ranges
|
58 |
+
)
|
59 |
+
|
60 |
+
# display the bounding box
|
61 |
+
bounding_box = bboxes[selected_area][selected_date_range]["bounding_box"]
|
62 |
+
geojson_selected_area = folium.GeoJson(bounding_box)
|
63 |
+
feat_group_selected_area.add_child(geojson_selected_area)
|
64 |
+
|
65 |
+
# geojson_selected_area = folium.GeoJson(bboxes[selected_area])
|
66 |
+
# feat_group_selected_area.add_child(geojson_selected_area)
|
67 |
+
geojson_flood_area = get_existing_flood_geojson(
|
68 |
+
selected_area, selected_date_range
|
69 |
+
)
|
70 |
+
feat_group_selected_area.add_child(geojson_flood_area)
|
71 |
|
72 |
elif area_type == "New area":
|
73 |
new_area_name = st.text_input("Area name")
|
|
|
76 |
# Create folium map
|
77 |
# call to render Folium map in Streamlit
|
78 |
folium_map = folium.Map([39, 0], zoom_start=8)
|
79 |
+
# check if the FeatureGroup has any children (i.e., layers added)
|
80 |
+
if len(feat_group_selected_area._children) > 0:
|
81 |
+
# if there is data, fit the map to the bounds
|
82 |
+
folium_map.fit_bounds(feat_group_selected_area.get_bounds())
|
83 |
+
else:
|
84 |
+
# if there is no data, set a default view
|
85 |
+
# this is necessary to start up the page
|
86 |
+
folium_map = folium.Map(location=[39, 0], zoom_start=8)
|
87 |
+
|
88 |
# Add drawing tools to map
|
89 |
if area_type == "New area":
|
90 |
Draw(
|
|
|
154 |
|
155 |
# Show loader because it will take a while
|
156 |
with st.spinner("Getting GFM files... Please wait..."):
|
|
|
157 |
if area_type == "New area":
|
158 |
+
# Convert date input into a string format for JSON storage
|
159 |
+
date_range_str = f"{start_date}_to_{end_date}"
|
160 |
+
|
161 |
+
# Load existing bboxes
|
162 |
with open("./bboxes/bboxes.json", "r") as f:
|
163 |
bboxes = json.load(f)
|
164 |
+
|
165 |
+
# Get the drawn area
|
166 |
+
selected_area_geojson = m["all_drawings"][-1]
|
167 |
+
|
168 |
+
# If the area doesn't exist, create it
|
169 |
+
if new_area_name not in bboxes:
|
170 |
+
bboxes[new_area_name] = {}
|
171 |
+
|
172 |
+
# Save the new bounding box under the date range key
|
173 |
+
bboxes[new_area_name][date_range_str] = {
|
174 |
+
"bounding_box": selected_area_geojson,
|
175 |
+
"flood_files": [], # Will be populated when files are downloaded
|
176 |
+
}
|
177 |
+
|
178 |
+
# Write the updated data back to file
|
179 |
with open("./bboxes/bboxes.json", "w") as f:
|
180 |
+
json.dump(bboxes, f, indent=4)
|
181 |
|
182 |
+
# Download files, also getting coordinates for GFM
|
183 |
coords = selected_area_geojson["geometry"]["coordinates"][0]
|
184 |
+
# download_gfm_geojson(area_name, new_coordinates=coords)
|
185 |
+
download_gfm_geojson(
|
186 |
+
area_name,
|
187 |
+
bbox=bboxes[new_area_name][date_range_str]["bounding_box"],
|
188 |
+
new_coordinates=coords,
|
189 |
+
)
|
190 |
|
191 |
# For an existing area just get the latest update from GFM
|
192 |
if area_type == "Existing area":
|
193 |
+
# download_gfm_geojson(area_name)
|
194 |
+
download_gfm_geojson(
|
195 |
+
selected_area,
|
196 |
+
bbox=bboxes[selected_area][selected_date_range]["bounding_box"],
|
197 |
+
)
|
198 |
|
199 |
# Display that getting the files is finished
|
200 |
st.markdown("Getting GFM files finished")
|
app/src/gfm.py
CHANGED
@@ -1,17 +1,60 @@
|
|
1 |
import io
|
2 |
import json
|
3 |
import os
|
|
|
4 |
import zipfile
|
5 |
from pathlib import Path
|
6 |
|
7 |
import folium
|
8 |
import requests
|
9 |
from dotenv import load_dotenv
|
|
|
|
|
10 |
|
11 |
load_dotenv()
|
12 |
|
13 |
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
"""
|
16 |
Should provide an existing area name or a new area name with new_coordinates
|
17 |
"""
|
@@ -54,6 +97,35 @@ def download_gfm_geojson(area_name, new_coordinates=None, output_file_path=None)
|
|
54 |
if aoi["aoi_name"] == area_name:
|
55 |
aoi_id = aoi["aoi_id"]
|
56 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
|
58 |
# Get product id
|
59 |
prod_url = f"{base_url}/aoi/{aoi_id}/products"
|
@@ -76,22 +148,41 @@ def download_gfm_geojson(area_name, new_coordinates=None, output_file_path=None)
|
|
76 |
# Download and unzip file
|
77 |
for f in Path(output_file_path).glob("*"):
|
78 |
f.unlink()
|
|
|
79 |
r = requests.get(download_link)
|
|
|
80 |
with zipfile.ZipFile(io.BytesIO(r.content)) as z:
|
81 |
z.extractall(str(Path(output_file_path)))
|
|
|
82 |
|
83 |
|
84 |
-
def get_existing_flood_geojson(area_name, output_file_path=None):
|
85 |
"""
|
86 |
-
Getting a saved GFM flood geojson in an output folder of GFM files
|
87 |
"""
|
|
|
88 |
if not output_file_path:
|
89 |
-
output_file_path = f"./output/{area_name}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
|
91 |
-
for f in Path(output_file_path).glob("*"):
|
92 |
-
if "FLOOD" in str(f) and "geojson" in str(f):
|
93 |
-
geojson_path = f
|
94 |
-
break
|
95 |
|
96 |
-
|
97 |
-
|
|
|
|
|
|
1 |
import io
|
2 |
import json
|
3 |
import os
|
4 |
+
import warnings
|
5 |
import zipfile
|
6 |
from pathlib import Path
|
7 |
|
8 |
import folium
|
9 |
import requests
|
10 |
from dotenv import load_dotenv
|
11 |
+
from shapely.geometry import MultiPolygon, shape
|
12 |
+
from shapely.ops import unary_union
|
13 |
|
14 |
load_dotenv()
|
15 |
|
16 |
|
17 |
+
class FloodGeoJsonError(Exception):
|
18 |
+
"""Custom exception for errors in fetching flood GeoJSON files."""
|
19 |
+
|
20 |
+
pass
|
21 |
+
|
22 |
+
|
23 |
+
def sync_cached_data(bboxes_path="./bboxes/bboxes.json", output_dir="./output"):
|
24 |
+
"""
|
25 |
+
Ensures that all areas in bboxes.json have a corresponding folder in ./output/.
|
26 |
+
Removes any area entry from bboxes.json that does not have an output folder.
|
27 |
+
"""
|
28 |
+
try:
|
29 |
+
# Load existing bounding boxes
|
30 |
+
with open(bboxes_path, "r") as f:
|
31 |
+
bboxes = json.load(f)
|
32 |
+
|
33 |
+
# Get a set of existing output folders
|
34 |
+
existing_folders = {
|
35 |
+
folder.name for folder in Path(output_dir).iterdir() if folder.is_dir()
|
36 |
+
}
|
37 |
+
|
38 |
+
# Remove entries from bboxes.json if the folder does not exist
|
39 |
+
updated_bboxes = {
|
40 |
+
area: data for area, data in bboxes.items() if area in existing_folders
|
41 |
+
}
|
42 |
+
|
43 |
+
# If changes were made, overwrite bboxes.json
|
44 |
+
if len(updated_bboxes) != len(bboxes):
|
45 |
+
with open(bboxes_path, "w") as f:
|
46 |
+
json.dump(updated_bboxes, f, indent=4)
|
47 |
+
print(f"Updated {bboxes_path}: Removed missing areas.")
|
48 |
+
else:
|
49 |
+
print("All areas have matching folders.")
|
50 |
+
|
51 |
+
except FileNotFoundError:
|
52 |
+
print(f"Error: {bboxes_path} not found.")
|
53 |
+
except json.JSONDecodeError:
|
54 |
+
print(f"Error: {bboxes_path} is not a valid JSON file.")
|
55 |
+
|
56 |
+
|
57 |
+
def download_gfm_geojson(area_name, bbox, new_coordinates=None, output_file_path=None):
|
58 |
"""
|
59 |
Should provide an existing area name or a new area name with new_coordinates
|
60 |
"""
|
|
|
97 |
if aoi["aoi_name"] == area_name:
|
98 |
aoi_id = aoi["aoi_id"]
|
99 |
break
|
100 |
+
# print(aoi)
|
101 |
+
|
102 |
+
# # Collect all matching AOIs (same name)
|
103 |
+
# matching_geometries = []
|
104 |
+
# for aoi in response.json()["aois"]:
|
105 |
+
# if aoi["aoi_name"] == area_name:
|
106 |
+
# geojson_geometry = aoi["geoJSON"]["geometry"]
|
107 |
+
# matching_geometries.append(shape(geojson_geometry))
|
108 |
+
|
109 |
+
# if not matching_geometries:
|
110 |
+
# raise ValueError(f"No AOIs found for area name: {area_name}")
|
111 |
+
|
112 |
+
# # Merge all matching AOI geometries into a single unified polygon
|
113 |
+
# merged_geometry = unary_union(matching_geometries)
|
114 |
+
|
115 |
+
# # Handle MultiPolygon cases (if AOIs are disjointed)
|
116 |
+
# if merged_geometry.geom_type == "MultiPolygon":
|
117 |
+
# merged_geometry = MultiPolygon([p for p in merged_geometry])
|
118 |
+
|
119 |
+
# # Convert back to GeoJSON
|
120 |
+
# merged_geojson = {
|
121 |
+
# "type": "Feature",
|
122 |
+
# "properties": {"aoi_name": area_name},
|
123 |
+
# "geometry": json.loads(json.dumps(merged_geometry.__geo_interface__)),
|
124 |
+
# }
|
125 |
+
|
126 |
+
# print(f"Merged {len(matching_geometries)} AOIs into one for '{area_name}'.")
|
127 |
+
|
128 |
+
# return merged_geojson
|
129 |
|
130 |
# Get product id
|
131 |
prod_url = f"{base_url}/aoi/{aoi_id}/products"
|
|
|
148 |
# Download and unzip file
|
149 |
for f in Path(output_file_path).glob("*"):
|
150 |
f.unlink()
|
151 |
+
print("Donwloading...")
|
152 |
r = requests.get(download_link)
|
153 |
+
print("Extracting zip...")
|
154 |
with zipfile.ZipFile(io.BytesIO(r.content)) as z:
|
155 |
z.extractall(str(Path(output_file_path)))
|
156 |
+
print("Done!")
|
157 |
|
158 |
|
159 |
+
def get_existing_flood_geojson(area_name, date_range, output_file_path=None):
|
160 |
"""
|
161 |
+
Getting a saved GFM flood geojson in an output folder of GFM files. Merge in one feature group if multiple.
|
162 |
"""
|
163 |
+
|
164 |
if not output_file_path:
|
165 |
+
output_file_path = f"./output/{area_name}/{date_range}"
|
166 |
+
|
167 |
+
# Ensure the output directory exists
|
168 |
+
# if not Path(output_file_path).exists():
|
169 |
+
# raise FloodGeoJsonError(f"Error: Output folder '{output_file_path}' does not exist.")
|
170 |
+
|
171 |
+
# Combine multiple flood files into a FeatureGroup
|
172 |
+
flood_geojson_group = folium.FeatureGroup(name=f"{area_name} Floods {date_range}")
|
173 |
+
|
174 |
+
for flood_file in Path(output_file_path).glob("*FLOOD*.geojson"):
|
175 |
+
with open(flood_file, "r") as f:
|
176 |
+
geojson_data = json.load(f)
|
177 |
+
flood_layer = folium.GeoJson(geojson_data)
|
178 |
+
flood_geojson_group.add_child(flood_layer)
|
179 |
+
|
180 |
+
# TODO: consider merging multiple flood layers into one, to avoid overlap
|
181 |
+
|
182 |
+
return flood_geojson_group
|
183 |
|
|
|
|
|
|
|
|
|
184 |
|
185 |
+
if __name__ == "__main__":
|
186 |
+
# download_gfm_geojson('Catania')
|
187 |
+
gj = get_existing_flood_geojson("Albufera Floods")
|
188 |
+
print(type(gj))
|