Spaces:
Running
Running
mvp of flood map connected to GFM with some local storage for earlier requested areas
Browse files- app/pages/1_π_Flood_extent_analysis.py +94 -76
- app/src/gfm.py +39 -27
- bboxes/bboxes.json +1 -0
app/pages/1_π_Flood_extent_analysis.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1 |
-
|
|
|
2 |
|
3 |
import folium
|
4 |
import streamlit as st
|
5 |
-
from folium.plugins import Draw
|
6 |
from src.config_parameters import params
|
7 |
-
from src.gfm import
|
8 |
from src.utils import (
|
9 |
add_about,
|
10 |
set_tool_page_style,
|
@@ -27,98 +28,115 @@ st.markdown("# Flood extent analysis")
|
|
27 |
# Set page style
|
28 |
set_tool_page_style()
|
29 |
|
30 |
-
# Output_created is useful to decide whether the bottom panel with the
|
31 |
-
# output map should be visualised or not
|
32 |
-
if "output_created" not in st.session_state:
|
33 |
-
st.session_state.output_created = True
|
34 |
-
|
35 |
-
|
36 |
-
# Function to be used to hide bottom panel (when setting parameters for a
|
37 |
-
# new analysis)
|
38 |
-
def callback():
|
39 |
-
"""Set output created to zero: reset tool."""
|
40 |
-
st.session_state.output_created = True
|
41 |
-
|
42 |
-
|
43 |
# Create two rows: top and bottom panel
|
44 |
row1 = st.container()
|
45 |
-
row2 = st.container()
|
46 |
# Crate two columns in the top panel: input map and paramters
|
47 |
col1, col2 = row1.columns([2, 1])
|
|
|
48 |
with col1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
# Add collapsable container for input map
|
50 |
with st.expander("Input map", expanded=True):
|
51 |
# Create folium map
|
52 |
-
|
53 |
-
|
54 |
-
zoom_start=3,
|
55 |
-
control_scale=True,
|
56 |
-
)
|
57 |
# Add drawing tools to map
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
|
|
|
|
75 |
with col2:
|
76 |
# Add collapsable container for image dates
|
77 |
with st.expander("Choose Image Dates"):
|
78 |
-
|
79 |
-
|
80 |
-
start_date = st.date_input(
|
81 |
-
"Start date",
|
82 |
-
on_change=callback,
|
83 |
-
)
|
84 |
-
end_date = st.date_input(
|
85 |
-
"End date",
|
86 |
-
on_change=callback,
|
87 |
-
)
|
88 |
# Add collapsable container for parameters
|
89 |
with st.expander("Choose Parameters"):
|
90 |
# Add slider for threshold
|
91 |
st.text("Add relevant (API) parameters here")
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
101 |
if submitted:
|
102 |
with col2:
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
# Output error if dates are not valid
|
104 |
if not check_dates:
|
105 |
st.error("Make sure that the dates were inserted correctly")
|
|
|
106 |
# Output error if no polygons were drawn
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from pathlib import Path
|
3 |
|
4 |
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 |
# 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(label="", options=["Existing area", "New area"])
|
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 |
+
geojson_selected_area = folium.GeoJson(bboxes[selected_area])
|
43 |
+
feat_group_selected_area.add_child(geojson_selected_area)
|
44 |
+
geojson_flood_area = get_existing_flood_geojson(selected_area)
|
45 |
+
feat_group_selected_area.add_child(geojson_flood_area)
|
46 |
+
|
47 |
+
elif area_type == "New area":
|
48 |
+
new_area_name = st.text_input("Area name")
|
49 |
# Add collapsable container for input map
|
50 |
with st.expander("Input map", expanded=True):
|
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(
|
57 |
+
export=False,
|
58 |
+
draw_options={
|
59 |
+
"circle": False,
|
60 |
+
"polyline": False,
|
61 |
+
"polygon": False,
|
62 |
+
"rectangle": True,
|
63 |
+
"marker": False,
|
64 |
+
"circlemarker": False,
|
65 |
+
},
|
66 |
+
).add_to(folium_map)
|
67 |
+
|
68 |
+
m = st_folium(
|
69 |
+
folium_map,
|
70 |
+
width=800,
|
71 |
+
height=450,
|
72 |
+
feature_group_to_add=feat_group_selected_area,
|
73 |
+
)
|
74 |
with col2:
|
75 |
# Add collapsable container for image dates
|
76 |
with st.expander("Choose Image Dates"):
|
77 |
+
start_date = st.date_input("Start date")
|
78 |
+
end_date = st.date_input("End date")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
# Add collapsable container for parameters
|
80 |
with st.expander("Choose Parameters"):
|
81 |
# Add slider for threshold
|
82 |
st.text("Add relevant (API) parameters here")
|
83 |
+
|
84 |
+
# Button to trigger GFM data retrieval
|
85 |
+
if area_type == "New area":
|
86 |
+
button_text = "Get new flood extent"
|
87 |
+
else:
|
88 |
+
button_text = "Update flood extent"
|
89 |
+
submitted = st.button(button_text)
|
90 |
+
|
91 |
+
|
92 |
+
# If the button is clicked do the following
|
93 |
if submitted:
|
94 |
with col2:
|
95 |
+
# Some basic validation on dates and that there's an area if relevant
|
96 |
+
get_gfm = True
|
97 |
+
check_dates = start_date <= end_date
|
98 |
+
if area_type == "New area":
|
99 |
+
check_drawing = m["all_drawings"] != [] and m["all_drawings"] is not None
|
100 |
+
|
101 |
# Output error if dates are not valid
|
102 |
if not check_dates:
|
103 |
st.error("Make sure that the dates were inserted correctly")
|
104 |
+
get_gfm = False
|
105 |
# Output error if no polygons were drawn
|
106 |
+
if area_type == "New area":
|
107 |
+
if not check_drawing:
|
108 |
+
st.error("Please create a region using the rectangle tool on the map.")
|
109 |
+
get_gfm = False
|
110 |
+
elif new_area_name == "":
|
111 |
+
st.error("Please provide a name for the new area")
|
112 |
+
get_gfm = False
|
113 |
+
|
114 |
+
# Only if checks pass go and get the GFM data
|
115 |
+
if get_gfm:
|
116 |
+
if area_type == "Existing area":
|
117 |
+
area_name = selected_area
|
118 |
+
elif area_type == "New area":
|
119 |
+
area_name = new_area_name
|
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 |
+
# Save bounding box
|
126 |
+
with open("./bboxes/bboxes.json", "r") as f:
|
127 |
+
bboxes = json.load(f)
|
128 |
+
selected_area_geojson = m["all_drawings"][-1]
|
129 |
+
bboxes[new_area_name] = selected_area_geojson
|
130 |
+
with open("./bboxes/bboxes.json", "w") as f:
|
131 |
+
json.dump(bboxes, f)
|
132 |
+
|
133 |
+
# Download files, for a new area also get coordinates to save to GFM
|
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")
|
app/src/gfm.py
CHANGED
@@ -7,12 +7,14 @@ from pathlib import Path
|
|
7 |
import folium
|
8 |
import requests
|
9 |
from dotenv import load_dotenv
|
10 |
-
from streamlit_folium import st_folium
|
11 |
|
12 |
load_dotenv()
|
13 |
|
14 |
|
15 |
-
def
|
|
|
|
|
|
|
16 |
username = os.environ["gfm_username"]
|
17 |
password = os.environ["gfm_password"]
|
18 |
base_url = "https://api.gfm.eodc.eu/v1"
|
@@ -28,25 +30,28 @@ def download_stuff(coordinates):
|
|
28 |
header = {"Authorization": f"bearer {access_token}"}
|
29 |
print("logged in")
|
30 |
|
31 |
-
#
|
32 |
-
|
|
|
|
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
40 |
|
41 |
-
r = requests.post(url=create_aoi_url, json=payload, headers=header)
|
42 |
-
print(r.text)
|
43 |
-
print("Posted new AOI")
|
44 |
# Get Area of Impact
|
45 |
aoi_url = f"{base_url}/aoi/user/{user_id}"
|
46 |
-
|
47 |
response = requests.get(aoi_url, headers=header)
|
|
|
|
|
48 |
for aoi in response.json()["aois"]:
|
49 |
-
if aoi["aoi_name"] ==
|
50 |
aoi_id = aoi["aoi_id"]
|
51 |
break
|
52 |
|
@@ -62,24 +67,31 @@ def download_stuff(coordinates):
|
|
62 |
download_link = response.json()["download_link"]
|
63 |
print("Got download link")
|
64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
# Download and unzip file
|
66 |
-
for f in Path(
|
67 |
f.unlink()
|
68 |
r = requests.get(download_link)
|
69 |
with zipfile.ZipFile(io.BytesIO(r.content)) as z:
|
70 |
-
z.extractall(str(Path(
|
|
|
71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
|
73 |
-
|
74 |
-
# get geosjon with floods
|
75 |
-
for f in Path("./output").glob("*"):
|
76 |
if "FLOOD" in str(f) and "geojson" in str(f):
|
77 |
geojson_path = f
|
|
|
78 |
|
79 |
-
|
80 |
-
flood_geojson
|
81 |
-
|
82 |
-
m = folium.Map([54, -2], zoom_start=7)
|
83 |
-
folium.GeoJson(flood_geojson, name="geojson").add_to(m)
|
84 |
-
m
|
85 |
-
st_folium(m, width=800, height=450, returned_objects=[])
|
|
|
7 |
import folium
|
8 |
import requests
|
9 |
from dotenv import load_dotenv
|
|
|
10 |
|
11 |
load_dotenv()
|
12 |
|
13 |
|
14 |
+
def download_gfm_geojson(area_name, new_coordinates=None, output_file_path=None):
|
15 |
+
"""
|
16 |
+
Should provide an existing area name or a new area name with new_coordinates
|
17 |
+
"""
|
18 |
username = os.environ["gfm_username"]
|
19 |
password = os.environ["gfm_password"]
|
20 |
base_url = "https://api.gfm.eodc.eu/v1"
|
|
|
30 |
header = {"Authorization": f"bearer {access_token}"}
|
31 |
print("logged in")
|
32 |
|
33 |
+
# Only if new coordinates are provided create the AOI in GFM
|
34 |
+
if new_coordinates:
|
35 |
+
# Create area of impact
|
36 |
+
create_aoi_url = f"{base_url}/aoi/create"
|
37 |
|
38 |
+
payload = {
|
39 |
+
"aoi_name": area_name,
|
40 |
+
"description": area_name,
|
41 |
+
"user_id": user_id,
|
42 |
+
"geoJSON": {"type": "Polygon", "coordinates": [new_coordinates]},
|
43 |
+
}
|
44 |
+
|
45 |
+
r = requests.post(url=create_aoi_url, json=payload, headers=header)
|
46 |
+
print("Posted new AOI")
|
47 |
|
|
|
|
|
|
|
48 |
# Get Area of Impact
|
49 |
aoi_url = f"{base_url}/aoi/user/{user_id}"
|
|
|
50 |
response = requests.get(aoi_url, headers=header)
|
51 |
+
|
52 |
+
# TODO: now only getting the first AOI, should extend to getting the whole list and unioning the geojsons
|
53 |
for aoi in response.json()["aois"]:
|
54 |
+
if aoi["aoi_name"] == area_name:
|
55 |
aoi_id = aoi["aoi_id"]
|
56 |
break
|
57 |
|
|
|
67 |
download_link = response.json()["download_link"]
|
68 |
print("Got download link")
|
69 |
|
70 |
+
# Set output file path and create directory if it doesn't exist
|
71 |
+
if not output_file_path:
|
72 |
+
output_file_path = f"./output/{area_name}"
|
73 |
+
|
74 |
+
Path(output_file_path).mkdir(parents=True, exist_ok=True)
|
75 |
+
|
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 |
+
flood_geojson = folium.GeoJson(json.load(open(geojson_path)))
|
97 |
+
return flood_geojson
|
|
|
|
|
|
|
|
|
|
bboxes/bboxes.json
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"Denia": {"type": "Feature", "properties": {}, "geometry": {"type": "Polygon", "coordinates": [[[-0.387268, 38.786204], [-0.387268, 38.967951], [0.222473, 38.967951], [0.222473, 38.786204], [-0.387268, 38.786204]]]}}, "Test": {"type": "Feature", "properties": {}, "geometry": {"type": "Polygon", "coordinates": [[[-1.779785, 39.656456], [-1.779785, 39.888665], [-0.977783, 39.888665], [-0.977783, 39.656456], [-1.779785, 39.656456]]]}}, "Gambia": {"type": "Feature", "properties": {}, "geometry": {"type": "Polygon", "coordinates": [[[-16.864014, 12.972442], [-16.864014, 13.83808], [-13.370361, 13.83808], [-13.370361, 12.972442], [-16.864014, 12.972442]]]}}}
|