Spaces:
Sleeping
Sleeping
stripped original app
Browse files- .pre-commit-config.yaml +0 -39
- README.md +2 -62
- app/Home.py +3 -104
- app/img/workflow.png +0 -0
- app/pages/1_π_Flood_extent_analysis.py +72 -189
- app/pages/2_π_Documentation.py +2 -92
- app/src/config_parameters.py +5 -42
- app/src/utils.py +9 -37
- app/src/utils_ee.py +0 -36
- app/src/utils_flood_analysis.py +0 -369
- requirements.txt +1 -0
.pre-commit-config.yaml
DELETED
@@ -1,39 +0,0 @@
|
|
1 |
-
default_language_version:
|
2 |
-
python: python3.9
|
3 |
-
|
4 |
-
repos:
|
5 |
-
- repo: https://github.com/pre-commit/pre-commit-hooks
|
6 |
-
rev: v4.4.0
|
7 |
-
hooks:
|
8 |
-
- id: trailing-whitespace
|
9 |
-
- id: end-of-file-fixer
|
10 |
-
- id: check-ast
|
11 |
-
- id: check-toml
|
12 |
-
- id: check-yaml
|
13 |
-
- repo: https://github.com/psf/black
|
14 |
-
rev: 23.1.0
|
15 |
-
hooks:
|
16 |
-
- id: black
|
17 |
-
- repo: https://github.com/PyCQA/isort
|
18 |
-
rev: 5.12.0
|
19 |
-
hooks:
|
20 |
-
- id: isort
|
21 |
-
- repo: https://github.com/pycqa/flake8
|
22 |
-
rev: 6.0.0
|
23 |
-
hooks:
|
24 |
-
- id: flake8
|
25 |
-
additional_dependencies:
|
26 |
-
# - flake8-bugbear==21.9.2
|
27 |
-
# - flake8-comprehensions==3.6.1
|
28 |
-
# - flake8-deprecated==1.3
|
29 |
-
- flake8-docstrings==1.6.0
|
30 |
-
# - flake8-keyword-arguments==0.1.0
|
31 |
-
- repo: https://github.com/pre-commit/mirrors-mypy
|
32 |
-
rev: v0.910
|
33 |
-
hooks:
|
34 |
-
- id: mypy
|
35 |
-
additional_dependencies:
|
36 |
-
- types-PyYAML==5.4.10
|
37 |
-
- types-setuptools==57.4.0
|
38 |
-
- types-requests==2.25.9
|
39 |
-
exclude: tests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
@@ -1,63 +1,3 @@
|
|
1 |
# Flood Mapping Tool
|
2 |
-
|
3 |
-
|
4 |
-
[](https://github.com/mapaction/flood-mapping-tool/blob/main/LICENSE)
|
5 |
-
[](https://github.com/psf/black)
|
6 |
-
[](https://pycqa.github.io/isort/)
|
7 |
-
|
8 |
-
This repository contains a Streamlit app that allows to estimate flood extent using Sentinel-1 synthetic-aperture radar <a href='https://sentinel.esa.int/web/sentinel/user-guidessentinel-1-sar'>SAR</a> data.
|
9 |
-
|
10 |
-
The methodology is based on a <a href='https://un-spider.org/advisory-support/recommended-practices/recommended-\practice-google-earth-engine-flood-mapping'> recommended practice </a> published by the United Nations Platform for Space-based Information for Disaster Management and Emergency Response (UN-SPIDER) and it uses several satellite imagery datasets to produce the final output. The datasets are retrieved from <a href='https://earthengine.google.com/'>Google Earth Engine</a> which is a powerful web-platform for cloud-based processing of remote sensing data on large scales. More information on the methodology is given in the <i>Description</i> page in the Streamlit app.
|
11 |
-
|
12 |
-
This analysis provides a comprehensive overview of a flooding event, across different areas of interest, from settlements to countries. However, as mentioned in the UN-SPIDER website, the methodology is meant for broad information provision in a global context, and contains inherent uncertainties. Therefore, it is important that the tool is not used as the only source of information for rescue response planning.
|
13 |
-
|
14 |
-
## Usage
|
15 |
-
|
16 |
-
#### Requirements
|
17 |
-
|
18 |
-
The Python version currently used is 3.10. Please install all packages from
|
19 |
-
``requirements.txt``:
|
20 |
-
|
21 |
-
```shell
|
22 |
-
pip install -r requirements.txt
|
23 |
-
```
|
24 |
-
|
25 |
-
#### Google Earth Engine authentication
|
26 |
-
|
27 |
-
[Sign up](https://signup.earthengine.google.com/) for a Google Earth Engine account, if you don't already have one. Open a terminal window, type `python` and then paste the following code:
|
28 |
-
|
29 |
-
```python
|
30 |
-
import ee
|
31 |
-
ee.Authenticate()
|
32 |
-
```
|
33 |
-
|
34 |
-
Log in to your Google account to obtain the authorization code and paste it back into the terminal. Once you press "Enter", an authorization token will be saved to your computer under the following file path (depending on your operating system):
|
35 |
-
|
36 |
-
- Windows: `C:\\Users\\USERNAME\\.config\\earthengine\\credentials`
|
37 |
-
- Linux: `/home/USERNAME/.config/earthengine/credentials`
|
38 |
-
- MacOS: `/Users/USERNAME/.config/earthengine/credentials`
|
39 |
-
|
40 |
-
The credentials will be used when initialising Google Earth Engine in the app.
|
41 |
-
|
42 |
-
#### Run the app
|
43 |
-
|
44 |
-
Finally, open a terminal and run
|
45 |
-
|
46 |
-
```shell
|
47 |
-
streamlit run app/Home.py
|
48 |
-
```
|
49 |
-
|
50 |
-
A new browser window will open and you can start using the tool.
|
51 |
-
|
52 |
-
## Contributing
|
53 |
-
|
54 |
-
#### Pre-commit
|
55 |
-
|
56 |
-
All code is formatted according to
|
57 |
-
[black](https://github.com/psf/black) and [flake8](https://flake8.pycqa.org/en/latest) guidelines. The repo is set-up to use [pre-commit](https://github.com/pre-commit/pre-commit). Please run ``pre-commit install`` the first time you are editing. Thereafter all commits will be checked against black and flake8 guidelines.
|
58 |
-
|
59 |
-
To check if your changes pass pre-commit without committing, run:
|
60 |
-
|
61 |
-
```shell
|
62 |
-
pre-commit run --all-files
|
63 |
-
```
|
|
|
1 |
# Flood Mapping Tool
|
2 |
+
TODO: New documentation. For now its kept on python 3.10 like original Mapaction app and packages have not yet been updated.
|
3 |
+
Just create venv and pip install requirements
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Home.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
"""Home page for Streamlit app."""
|
|
|
2 |
import streamlit as st
|
3 |
from src.config_parameters import params
|
4 |
from src.utils import (
|
@@ -26,110 +27,8 @@ st.markdown("# Home")
|
|
26 |
|
27 |
# First section
|
28 |
st.markdown("## Introduction")
|
29 |
-
st.markdown(
|
30 |
-
"""
|
31 |
-
This tool allows to estimate flood extent using Sentinel-1
|
32 |
-
synthetic-aperture radar
|
33 |
-
<a href='%s'>SAR</a> data.<br><br>
|
34 |
-
The methodology is based on a <a href=
|
35 |
-
'%s'>recommended practice</a>
|
36 |
-
published by the United Nations Platform for Space-based Information for
|
37 |
-
Disaster Management and Emergency Response (UN-SPIDER) and it uses several
|
38 |
-
satellite imagery datasets to produce the final output. The datasets are
|
39 |
-
retrieved from <a href='%s'>Google Earth
|
40 |
-
Engine</a> which is a powerful web-platform for cloud-based processing of
|
41 |
-
remote sensing data on large scales. More information on the methodology is
|
42 |
-
given in the Description.<br><br>
|
43 |
-
This analysis provides a comprehensive overview of a flooding event, across
|
44 |
-
different areas of interest, from settlements to countries. However, as
|
45 |
-
mentioned in the UN-SPIDER website, the methodology is meant for broad
|
46 |
-
information provision in a global context, and contains inherent
|
47 |
-
uncertainties. Therefore, it is important that the tool is not used as the
|
48 |
-
only source of information for rescue response planning.
|
49 |
-
"""
|
50 |
-
% (
|
51 |
-
params["url_sentinel_esa"],
|
52 |
-
params["url_unspider_tutorial"],
|
53 |
-
params["url_gee"],
|
54 |
-
),
|
55 |
-
unsafe_allow_html=True,
|
56 |
-
)
|
57 |
|
58 |
# Second section
|
59 |
st.markdown("## How to use the tool")
|
60 |
-
st.markdown(
|
61 |
-
"""
|
62 |
-
<ul>
|
63 |
-
<li><p>
|
64 |
-
In the sidebar, choose <i>Flood extent analysis</i> to start the
|
65 |
-
analysis.
|
66 |
-
</p>
|
67 |
-
<li><p>
|
68 |
-
In the left panel, use the drawing tool to select an area of
|
69 |
-
interest on the map. You can delete your selection by clicking on
|
70 |
-
the bin icon. While the flood mapping is generated regardless of
|
71 |
-
the size of the selected region, you will be able to save raster
|
72 |
-
and vector flooding extent only if the side of the rectangular
|
73 |
-
selection does not exceed 100 km.
|
74 |
-
</p>
|
75 |
-
<li><p>
|
76 |
-
In the right panel click on the title <i>Choose Image Dates</i>
|
77 |
-
in order to expand the section. Here you need to select four dates.
|
78 |
-
The first two identify a range of dates based on which the
|
79 |
-
reference imagery (before the flooding event) is defined. You can
|
80 |
-
select even years worth of data (the reference imagery is
|
81 |
-
calculated as the median between the range of observations), but
|
82 |
-
make sure you take into account wet and dry seasons if only taking
|
83 |
-
a few months. The last two refer to a period of time which comes
|
84 |
-
after the flooding event. By setting periods, not single dates, you
|
85 |
-
allow the selection of enough tiles to cover the area of interest.
|
86 |
-
Sentinel-1 imagery is acquired minimum every 12 days for each point
|
87 |
-
on the globe (see Figure 2 in the documentation).
|
88 |
-
</p>
|
89 |
-
<li>
|
90 |
-
<p>
|
91 |
-
By clicking on <i>Choose parameters</i>, you will be able to
|
92 |
-
set two variables:
|
93 |
-
</p>
|
94 |
-
<ul>
|
95 |
-
<li><p>
|
96 |
-
The <i>threshold</i> is the value against which the
|
97 |
-
difference the two satellite images - before and after the
|
98 |
-
flooding event - is tested. Lower thresholds result in a
|
99 |
-
greater area considered "flooded". It is recommended to set
|
100 |
-
the value to 1.25, which was selected through trial and
|
101 |
-
error. You may want to adjust the value in case of high
|
102 |
-
rates of false positive or negative values, especially in
|
103 |
-
case other sources of information are available and it is
|
104 |
-
possible to compare flood extent estimations between
|
105 |
-
sources.
|
106 |
-
</p>
|
107 |
-
<li><p>
|
108 |
-
The <i>pass direction</i> has to do with the way the
|
109 |
-
satellite travels around the Earth. Depending on your area
|
110 |
-
of interest and time period, you may find more imagery
|
111 |
-
available for either the <i>Ascending</i> or the
|
112 |
-
<i>Descending</i> pass directions (see Figure 2 in the
|
113 |
-
Documentation). It is recommended to leave the parameter
|
114 |
-
unchanged for a first estimation and change its value in
|
115 |
-
case partial or no imagery is produced.
|
116 |
-
</p>
|
117 |
-
</ul>
|
118 |
-
<li><p>
|
119 |
-
Once the parameters are set, you can finally click on <i>Compute
|
120 |
-
flood extent</i> to run the calculations. A map will appear
|
121 |
-
underneath, with a layer containing the flooded area within the
|
122 |
-
area of interest.
|
123 |
-
</p>
|
124 |
-
<li><p>
|
125 |
-
If you wish to export the layer to file, you can click on <i>Export
|
126 |
-
to file</i> and download the raster and/or vector data.
|
127 |
-
</p>
|
128 |
-
</ul>
|
129 |
-
<p>
|
130 |
-
In case you get errors, follow the intructions. If you have doubts,
|
131 |
-
feel free to contact the Data Science team.
|
132 |
-
</p>
|
133 |
-
""",
|
134 |
-
unsafe_allow_html=True,
|
135 |
-
)
|
|
|
1 |
"""Home page for Streamlit app."""
|
2 |
+
|
3 |
import streamlit as st
|
4 |
from src.config_parameters import params
|
5 |
from src.utils import (
|
|
|
27 |
|
28 |
# First section
|
29 |
st.markdown("## Introduction")
|
30 |
+
st.markdown("TODO: new introduction")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
# Second section
|
33 |
st.markdown("## How to use the tool")
|
34 |
+
st.markdown("TODO: new how to use the tool")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/img/workflow.png
DELETED
Binary file (44.6 kB)
|
|
app/pages/1_π_Flood_extent_analysis.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
"""Flood extent analysis page for Streamlit app."""
|
|
|
2 |
import datetime as dt
|
3 |
|
4 |
-
import ee
|
5 |
import folium
|
6 |
import geemap.foliumap as geemap
|
7 |
import requests
|
@@ -15,7 +15,6 @@ from src.utils import (
|
|
15 |
set_tool_page_style,
|
16 |
toggle_menu_button,
|
17 |
)
|
18 |
-
from src.utils_ee import ee_initialize
|
19 |
from src.utils_flood_analysis import derive_flood_extents
|
20 |
from streamlit_folium import st_folium
|
21 |
|
@@ -35,10 +34,6 @@ st.markdown("# Flood extent analysis")
|
|
35 |
# Set page style
|
36 |
set_tool_page_style()
|
37 |
|
38 |
-
# Initialise Google Earth Engine
|
39 |
-
ee_initialize(force_use_service_account=True)
|
40 |
-
|
41 |
-
|
42 |
# Output_created is useful to decide whether the bottom panel with the
|
43 |
# output map should be visualised or not
|
44 |
if "output_created" not in st.session_state:
|
@@ -73,8 +68,8 @@ with col1:
|
|
73 |
draw_options={
|
74 |
"circle": False,
|
75 |
"polyline": False,
|
76 |
-
"polygon":
|
77 |
-
"
|
78 |
"marker": False,
|
79 |
"circlemarker": False,
|
80 |
},
|
@@ -90,62 +85,24 @@ with col2:
|
|
90 |
with st.expander("Choose Image Dates"):
|
91 |
# Callback is added, so that, every time a parameters is changed,
|
92 |
# the bottom panel containing the output map is hidden
|
93 |
-
|
94 |
-
"Start date
|
95 |
-
value=dt.date(year=2022, month=7, day=1),
|
96 |
-
help="It needs to be prior to the flooding event",
|
97 |
on_change=callback,
|
98 |
)
|
99 |
-
|
100 |
-
"End date
|
101 |
-
value=dt.date(year=2022, month=7, day=30),
|
102 |
-
help=(
|
103 |
-
"It needs to be prior to the flooding event, at least 15 "
|
104 |
-
"days subsequent to the date selected above"
|
105 |
-
),
|
106 |
-
on_change=callback,
|
107 |
-
)
|
108 |
-
after_start = st.date_input(
|
109 |
-
"Start date for flooding imagery",
|
110 |
-
value=dt.date(year=2022, month=9, day=1),
|
111 |
-
help="It needs to be subsequent to the flooding event",
|
112 |
-
on_change=callback,
|
113 |
-
)
|
114 |
-
after_end = st.date_input(
|
115 |
-
"End date for flooding imagery",
|
116 |
-
value=dt.date(year=2022, month=9, day=16),
|
117 |
-
help=(
|
118 |
-
"It needs to be subsequent to the flooding event and at "
|
119 |
-
"least 10 days to the date selected above"
|
120 |
-
),
|
121 |
on_change=callback,
|
122 |
)
|
123 |
# Add collapsable container for parameters
|
124 |
with st.expander("Choose Parameters"):
|
125 |
# Add slider for threshold
|
126 |
-
|
127 |
-
label="Select a threshold",
|
128 |
-
min_value=0.0,
|
129 |
-
max_value=5.0,
|
130 |
-
value=1.25,
|
131 |
-
step=0.25,
|
132 |
-
help="Higher values might reduce overall noise",
|
133 |
-
on_change=callback,
|
134 |
-
)
|
135 |
-
# Add radio buttons for pass direction
|
136 |
-
pass_direction = st.radio(
|
137 |
-
"Set pass direction",
|
138 |
-
["Ascending", "Descending"],
|
139 |
-
on_change=callback,
|
140 |
-
)
|
141 |
# Button for computation
|
142 |
-
submitted = st.button("
|
143 |
# Introduce date validation
|
144 |
-
check_dates =
|
145 |
# Introduce drawing validation (a polygon needs to exist)
|
146 |
-
check_drawing =
|
147 |
-
output["all_drawings"] != [] and output["all_drawings"] is not None
|
148 |
-
)
|
149 |
# What happens when button is clicked on?
|
150 |
if submitted:
|
151 |
with col2:
|
@@ -159,144 +116,70 @@ if submitted:
|
|
159 |
# Add output for computation
|
160 |
with st.spinner("Computing... Please wait..."):
|
161 |
# Extract coordinates from drawn polygon
|
162 |
-
coords = output["all_drawings"][-1]["geometry"]["coordinates"][
|
163 |
-
|
164 |
-
]
|
165 |
# Create geometry from coordinates
|
166 |
-
|
167 |
-
|
168 |
-
(
|
169 |
-
detected_flood_vector,
|
170 |
-
detected_flood_raster,
|
171 |
-
_,
|
172 |
-
_,
|
173 |
-
) = derive_flood_extents(
|
174 |
-
aoi=ee_geom_region,
|
175 |
-
before_start_date=str(before_start),
|
176 |
-
before_end_date=str(before_end),
|
177 |
-
after_start_date=str(after_start),
|
178 |
-
after_end_date=str(after_end),
|
179 |
-
difference_threshold=add_slider,
|
180 |
-
polarization="VH",
|
181 |
-
pass_direction=pass_direction,
|
182 |
-
export=False,
|
183 |
-
)
|
184 |
-
# Create output map
|
185 |
-
Map2 = geemap.Map(
|
186 |
-
# basemap="HYBRID",
|
187 |
-
plugin_Draw=False,
|
188 |
-
Draw_export=False,
|
189 |
-
locate_control=False,
|
190 |
-
plugin_LatLngPopup=False,
|
191 |
-
)
|
192 |
-
try:
|
193 |
-
# Add flood vector layer to map
|
194 |
-
Map2.add_layer(
|
195 |
-
ee_object=detected_flood_raster,
|
196 |
-
name="Flood extent raster",
|
197 |
-
)
|
198 |
-
Map2.add_layer(
|
199 |
-
ee_object=detected_flood_vector,
|
200 |
-
name="Flood extent vector",
|
201 |
-
)
|
202 |
-
# Center map on flood raster
|
203 |
-
Map2.centerObject(detected_flood_raster)
|
204 |
-
except ee.EEException:
|
205 |
-
# If error contains the sentence below, it means that
|
206 |
-
# an image could not be properly generated
|
207 |
-
st.error(
|
208 |
-
"""
|
209 |
-
No satellite image found for the selected
|
210 |
-
dates.\n\n
|
211 |
-
Try changing the pass direction.\n\n
|
212 |
-
If this does not work, choose different
|
213 |
-
dates: it is likely that the satellite did not
|
214 |
-
cover the area of interest in the range of
|
215 |
-
dates specified (either before or after the
|
216 |
-
flooding event).
|
217 |
-
"""
|
218 |
-
)
|
219 |
-
else:
|
220 |
-
# If computation was succesfull, save outputs for
|
221 |
-
# output map
|
222 |
-
st.success("Computation complete")
|
223 |
-
st.session_state.output_created = True
|
224 |
-
st.session_state.Map2 = Map2
|
225 |
-
st.session_state.detected_flood_raster = (
|
226 |
-
detected_flood_raster
|
227 |
-
)
|
228 |
-
st.session_state.detected_flood_vector = (
|
229 |
-
detected_flood_vector
|
230 |
-
)
|
231 |
-
st.session_state.ee_geom_region = ee_geom_region
|
232 |
# If computation was successful, create output map in bottom panel
|
233 |
if st.session_state.output_created:
|
234 |
with row2:
|
235 |
# Add collapsable container for output map
|
236 |
with st.expander("Output map", expanded=True):
|
237 |
-
|
238 |
-
|
239 |
-
#
|
240 |
-
|
241 |
-
#
|
242 |
-
if
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
f"{filename}"
|
295 |
-
"_vector_"
|
296 |
-
f"{timestamp}"
|
297 |
-
".geojson"
|
298 |
-
),
|
299 |
-
mime="text/json",
|
300 |
-
)
|
301 |
-
# Output for computation complete
|
302 |
-
st.success("Computation complete")
|
|
|
1 |
"""Flood extent analysis page for Streamlit app."""
|
2 |
+
|
3 |
import datetime as dt
|
4 |
|
|
|
5 |
import folium
|
6 |
import geemap.foliumap as geemap
|
7 |
import requests
|
|
|
15 |
set_tool_page_style,
|
16 |
toggle_menu_button,
|
17 |
)
|
|
|
18 |
from src.utils_flood_analysis import derive_flood_extents
|
19 |
from streamlit_folium import st_folium
|
20 |
|
|
|
34 |
# Set page style
|
35 |
set_tool_page_style()
|
36 |
|
|
|
|
|
|
|
|
|
37 |
# Output_created is useful to decide whether the bottom panel with the
|
38 |
# output map should be visualised or not
|
39 |
if "output_created" not in st.session_state:
|
|
|
68 |
draw_options={
|
69 |
"circle": False,
|
70 |
"polyline": False,
|
71 |
+
"polygon": False,
|
72 |
+
"rectangle": True,
|
73 |
"marker": False,
|
74 |
"circlemarker": False,
|
75 |
},
|
|
|
85 |
with st.expander("Choose Image Dates"):
|
86 |
# Callback is added, so that, every time a parameters is changed,
|
87 |
# the bottom panel containing the output map is hidden
|
88 |
+
start_date = st.date_input(
|
89 |
+
"Start date",
|
|
|
|
|
90 |
on_change=callback,
|
91 |
)
|
92 |
+
end_date = st.date_input(
|
93 |
+
"End date",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
on_change=callback,
|
95 |
)
|
96 |
# Add collapsable container for parameters
|
97 |
with st.expander("Choose Parameters"):
|
98 |
# Add slider for threshold
|
99 |
+
st.text("Add relevant (API) parameters here")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
# Button for computation
|
101 |
+
submitted = st.button("Get flood extent")
|
102 |
# Introduce date validation
|
103 |
+
check_dates = start_date <= end_date
|
104 |
# Introduce drawing validation (a polygon needs to exist)
|
105 |
+
check_drawing = output["all_drawings"] != [] and output["all_drawings"] is not None
|
|
|
|
|
106 |
# What happens when button is clicked on?
|
107 |
if submitted:
|
108 |
with col2:
|
|
|
116 |
# Add output for computation
|
117 |
with st.spinner("Computing... Please wait..."):
|
118 |
# Extract coordinates from drawn polygon
|
119 |
+
coords = output["all_drawings"][-1]["geometry"]["coordinates"][0]
|
120 |
+
print(f"Coords: {coords}")
|
|
|
121 |
# Create geometry from coordinates
|
122 |
+
st.session_state.output_created = True
|
123 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
# If computation was successful, create output map in bottom panel
|
125 |
if st.session_state.output_created:
|
126 |
with row2:
|
127 |
# Add collapsable container for output map
|
128 |
with st.expander("Output map", expanded=True):
|
129 |
+
st.success("Calc complete")
|
130 |
+
# # Export Map2 to streamlit
|
131 |
+
# st.session_state.Map2.to_streamlit()
|
132 |
+
# # Create button to export to file
|
133 |
+
# submitted2 = st.button("Export to file")
|
134 |
+
# # What happens if button is clicked on?
|
135 |
+
# if submitted2:
|
136 |
+
# # Add output for computation
|
137 |
+
# with st.spinner("Computing... Please wait..."):
|
138 |
+
# try:
|
139 |
+
# # Get download url for raster data
|
140 |
+
# raster = st.session_state.detected_flood_raster
|
141 |
+
# url_r = raster.getDownloadUrl(
|
142 |
+
# {
|
143 |
+
# "region": st.session_state.ee_geom_region,
|
144 |
+
# "scale": 30,
|
145 |
+
# "format": "GEO_TIFF",
|
146 |
+
# }
|
147 |
+
# )
|
148 |
+
# except Exception:
|
149 |
+
# st.error(
|
150 |
+
# """
|
151 |
+
# The image size is too big for the image to
|
152 |
+
# be exported to file. Select a smaller area
|
153 |
+
# of interest (side <~ 150km) and repeat the
|
154 |
+
# analysis.
|
155 |
+
# """
|
156 |
+
# )
|
157 |
+
# else:
|
158 |
+
# response_r = requests.get(url_r)
|
159 |
+
# # Get download url for raster data
|
160 |
+
# vector = st.session_state.detected_flood_vector
|
161 |
+
# url_v = vector.getDownloadUrl("GEOJSON")
|
162 |
+
# response_v = requests.get(url_v)
|
163 |
+
# filename = "flood_extent"
|
164 |
+
# timestamp = dt.datetime.now().strftime("%Y-%m-%d_%H-%M")
|
165 |
+
# with row2:
|
166 |
+
# # Create download buttons for raster and vector
|
167 |
+
# # data
|
168 |
+
# with open("flood_extent.tif", "wb"):
|
169 |
+
# ste.download_button(
|
170 |
+
# label="Download Raster Extent",
|
171 |
+
# data=response_r.content,
|
172 |
+
# file_name=(f"{filename}_raster_{timestamp}.tif"),
|
173 |
+
# mime="image/tif",
|
174 |
+
# )
|
175 |
+
# with open("flood_extent.geojson", "wb"):
|
176 |
+
# ste.download_button(
|
177 |
+
# label="Download Vector Extent",
|
178 |
+
# data=response_v.content,
|
179 |
+
# file_name=(
|
180 |
+
# f"{filename}_vector_{timestamp}.geojson"
|
181 |
+
# ),
|
182 |
+
# mime="text/json",
|
183 |
+
# )
|
184 |
+
# # Output for computation complete
|
185 |
+
# st.success("Computation complete")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/pages/2_π_Documentation.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
"""Documentation page for Streamlit app."""
|
|
|
2 |
import streamlit as st
|
3 |
from PIL import Image
|
4 |
from src.config_parameters import params
|
@@ -28,63 +29,9 @@ st.markdown("# Documentation")
|
|
28 |
# First section
|
29 |
st.markdown("## Methodology")
|
30 |
st.markdown(
|
31 |
-
""
|
32 |
-
The methodology is based on the workflow depicted in Figure 1. In
|
33 |
-
addition to Sentinel-1 synthetic-aperture radar <a href='%s'>SAR</a> data,
|
34 |
-
two other datasets are used through <a href='%s'>Google Earth Engine</a>:
|
35 |
-
<ul>
|
36 |
-
<li><p>
|
37 |
-
The <i>WWF HydroSHEDS Void-Filled DEM, 3 Arc-Seconds</i>
|
38 |
-
<a href='%s'>dataset</a> is based on elevation data
|
39 |
-
obtained in 2000 by NASA's Shuttle Radar Topography Mission (SRTM),
|
40 |
-
and it is used to mask out areas with more than 5 percent slope
|
41 |
-
(see following section on limitations).
|
42 |
-
</p>
|
43 |
-
<li><p>
|
44 |
-
The <i>JRC Global Surface Water Mapping Layers, v1.4</i>
|
45 |
-
<a href='%s'>dataset</a> contains maps of the
|
46 |
-
location and temporal distribution of surface water from 1984 to
|
47 |
-
2021, and it is used to mask areas with perennial water bodies,
|
48 |
-
such as rivers or lakes.
|
49 |
-
</p>
|
50 |
-
</ul>
|
51 |
-
"""
|
52 |
-
% (
|
53 |
-
params["url_sentinel_dataset"],
|
54 |
-
params["url_gee"],
|
55 |
-
params["url_elevation_dataset"],
|
56 |
-
params["url_surface_water_dataset"],
|
57 |
-
),
|
58 |
-
unsafe_allow_html=True,
|
59 |
)
|
60 |
|
61 |
-
# Add image workflow
|
62 |
-
img = Image.open("app/img/workflow.png")
|
63 |
-
col1, mid, col2, last = st.columns([5, 3, 10, 10])
|
64 |
-
with col1:
|
65 |
-
st.image(img, width=350)
|
66 |
-
with col2:
|
67 |
-
# Trick to add caption at the bottom of the column, as Streamlit has not
|
68 |
-
# developed a functionality to allign text to bottom
|
69 |
-
space_before_caption = "<br>" * 27
|
70 |
-
st.markdown(
|
71 |
-
space_before_caption,
|
72 |
-
unsafe_allow_html=True,
|
73 |
-
)
|
74 |
-
st.markdown(
|
75 |
-
"""
|
76 |
-
<p style="font-size:%s;">
|
77 |
-
Figure 1. Workflow of the flood mapping methodology (<a href=
|
78 |
-
'%s'>source</a>).
|
79 |
-
</p>
|
80 |
-
"""
|
81 |
-
% (
|
82 |
-
params["docs_caption_fontsize"],
|
83 |
-
params["url_unspider_tutorial_detail"],
|
84 |
-
),
|
85 |
-
unsafe_allow_html=True,
|
86 |
-
)
|
87 |
-
|
88 |
|
89 |
# Second section
|
90 |
st.markdown("## Radar imagery for flood detection")
|
@@ -140,40 +87,3 @@ st.markdown(
|
|
140 |
% (params["docs_caption_fontsize"], params["url_sentinel_img_location"]),
|
141 |
unsafe_allow_html=True,
|
142 |
)
|
143 |
-
|
144 |
-
# Third section
|
145 |
-
st.markdown("## Key limitations")
|
146 |
-
st.markdown(
|
147 |
-
"""
|
148 |
-
Radar imagery is great for detecting floods, as it is good at picking up
|
149 |
-
water and it is not affected by the time of the day or clouds (at this
|
150 |
-
wavelength). But it has its limits, and performs actually quite bad if
|
151 |
-
having to detect water in mountainous regions, especially if with narrow
|
152 |
-
valleys, and in urban areas (urban canyons). The reasons are mainly around
|
153 |
-
the viewing angles, which can cause image distortions. This method may also
|
154 |
-
result in false positives for other land cover changes with smooth
|
155 |
-
surfaces, such as roads and sand. Rough surface texture caused by wind or
|
156 |
-
rainfall may also make it challenging for the radar imagery to identify
|
157 |
-
water bodies.
|
158 |
-
""",
|
159 |
-
unsafe_allow_html=True,
|
160 |
-
)
|
161 |
-
|
162 |
-
|
163 |
-
# Last section
|
164 |
-
st.markdown("## Useful links")
|
165 |
-
st.markdown(
|
166 |
-
"""
|
167 |
-
<a href='%s'>UN-SPIDER recommended practice</a><br>
|
168 |
-
<a href='%s'>Sentinel-1 satellite imagery user guide</a><br>
|
169 |
-
Relevant scientific publications:
|
170 |
-
<a href='%s'>1</a>, <a href='%s'>2</a><br>
|
171 |
-
"""
|
172 |
-
% (
|
173 |
-
params["url_unspider_tutorial"],
|
174 |
-
params["url_sentinel_esa"],
|
175 |
-
params["url_publication_1"],
|
176 |
-
params["url_publication_2"],
|
177 |
-
),
|
178 |
-
unsafe_allow_html=True,
|
179 |
-
)
|
|
|
1 |
"""Documentation page for Streamlit app."""
|
2 |
+
|
3 |
import streamlit as st
|
4 |
from PIL import Image
|
5 |
from src.config_parameters import params
|
|
|
29 |
# First section
|
30 |
st.markdown("## Methodology")
|
31 |
st.markdown(
|
32 |
+
"TODO: new documentation, only kept in Sentinel 1 section unchanged from the Mapaction tool"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
)
|
34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
# Second section
|
37 |
st.markdown("## Radar imagery for flood detection")
|
|
|
87 |
% (params["docs_caption_fontsize"], params["url_sentinel_img_location"]),
|
88 |
unsafe_allow_html=True,
|
89 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/src/config_parameters.py
CHANGED
@@ -1,60 +1,23 @@
|
|
1 |
"""Configuration file."""
|
|
|
2 |
params = {
|
3 |
# Title browser tab
|
4 |
-
"browser_title": "Flood mapping tool -
|
5 |
# Data scientists involved
|
6 |
"data_scientists": {
|
7 |
-
"
|
8 |
-
"Daniele": "[email protected]",
|
9 |
-
"Cate": "[email protected]",
|
10 |
},
|
11 |
# Urls
|
12 |
-
"url_data_science_wiki": (
|
13 |
-
"https://mapaction.atlassian.net/wiki/spaces/GAFO/overview"
|
14 |
-
),
|
15 |
-
"url_gee": "https://earthengine.google.com/",
|
16 |
-
"url_project_wiki": (
|
17 |
-
"https://mapaction.atlassian.net/wiki/spaces/GAFO/pages/15920922751/"
|
18 |
-
"Rapid+flood+mapping+from+satellite+imagery"
|
19 |
-
),
|
20 |
"url_github_repo": "https://github.com/mapaction/flood-extent-tool",
|
21 |
-
"url_sentinel_esa": (
|
22 |
-
"https://sentinel.esa.int/web/sentinel/user-guides/sentinel-1-sar"
|
23 |
-
),
|
24 |
"url_sentinel_dataset": (
|
25 |
-
"https://developers.google.com/earth-engine/datasets/catalog/"
|
26 |
-
"COPERNICUS_S1_GRD"
|
27 |
),
|
28 |
"url_sentinel_img": (
|
29 |
"https://sentinel.esa.int/documents/247904/4748961/Sentinel-1-Repeat-"
|
30 |
"Coverage-Frequency-Geometry-2021.jpg"
|
31 |
),
|
32 |
"url_sentinel_img_location": (
|
33 |
-
"https://sentinel.esa.int/web/sentinel/missions/sentinel-1/"
|
34 |
-
"observation-scenario"
|
35 |
-
),
|
36 |
-
"url_unspider_tutorial": (
|
37 |
-
"https://un-spider.org/advisory-support/recommended-practices/"
|
38 |
-
"recommended-practice-google-earth-engine-flood-mapping"
|
39 |
-
),
|
40 |
-
"url_unspider_tutorial_detail": (
|
41 |
-
"https://un-spider.org/advisory-support/recommended-practices/"
|
42 |
-
"recommended-practice-google-earth-engine-flood-mapping/in-detail"
|
43 |
-
),
|
44 |
-
"url_elevation_dataset": (
|
45 |
-
"https://developers.google.com/earth-engine/datasets/catalog/"
|
46 |
-
"WWF_HydroSHEDS_03VFDEM"
|
47 |
-
),
|
48 |
-
"url_surface_water_dataset": (
|
49 |
-
"https://developers.google.com/earth-engine/datasets/catalog/"
|
50 |
-
"JRC_GSW1_4_GlobalSurfaceWater"
|
51 |
-
),
|
52 |
-
"url_publication_1": (
|
53 |
-
"https://onlinelibrary.wiley.com/doi/full/10.1111/jfr3.12303"
|
54 |
-
),
|
55 |
-
"url_publication_2": (
|
56 |
-
"https://www.sciencedirect.com/science/article/abs/pii/"
|
57 |
-
"S0924271620301702"
|
58 |
),
|
59 |
# Layout and styles
|
60 |
## Sidebar
|
|
|
1 |
"""Configuration file."""
|
2 |
+
|
3 |
params = {
|
4 |
# Title browser tab
|
5 |
+
"browser_title": "Flood mapping tool - 510",
|
6 |
# Data scientists involved
|
7 |
"data_scientists": {
|
8 |
+
"Daniele": "dcastellana@redcross.nl",
|
|
|
|
|
9 |
},
|
10 |
# Urls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
"url_github_repo": "https://github.com/mapaction/flood-extent-tool",
|
|
|
|
|
|
|
12 |
"url_sentinel_dataset": (
|
13 |
+
"https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S1_GRD"
|
|
|
14 |
),
|
15 |
"url_sentinel_img": (
|
16 |
"https://sentinel.esa.int/documents/247904/4748961/Sentinel-1-Repeat-"
|
17 |
"Coverage-Frequency-Geometry-2021.jpg"
|
18 |
),
|
19 |
"url_sentinel_img_location": (
|
20 |
+
"https://sentinel.esa.int/web/sentinel/missions/sentinel-1/observation-scenario"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
),
|
22 |
# Layout and styles
|
23 |
## Sidebar
|
app/src/utils.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
"""Functions for the layout of the Streamlit app, including the sidebar."""
|
|
|
2 |
import base64
|
3 |
import os
|
4 |
from datetime import date
|
5 |
|
6 |
import streamlit as st
|
|
|
7 |
from src.config_parameters import params
|
8 |
|
9 |
|
@@ -183,46 +185,16 @@ def add_about():
|
|
183 |
Returns:
|
184 |
None
|
185 |
"""
|
186 |
-
today = date.today().strftime("%B %d, %Y")
|
187 |
-
|
188 |
# About textbox
|
189 |
st.sidebar.markdown("## About")
|
190 |
st.sidebar.markdown(
|
191 |
-
"""
|
192 |
-
<
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
margin-left:1em;
|
199 |
-
margin: 0px;
|
200 |
-
font-size: 1rem;
|
201 |
-
margin-bottom: 1em;
|
202 |
-
'>
|
203 |
-
Last update: %s
|
204 |
-
</p>
|
205 |
-
<p style='
|
206 |
-
margin-left:1em;
|
207 |
-
font-size: 1rem;
|
208 |
-
margin: 0px
|
209 |
-
'>
|
210 |
-
<a href='%s'>
|
211 |
-
Wiki reference page</a><br>
|
212 |
-
<a href='%s'>
|
213 |
-
GitHub repository</a><br>
|
214 |
-
<a href='%s'>
|
215 |
-
Data Science Lab</a>
|
216 |
-
</p>
|
217 |
-
</div>
|
218 |
-
"""
|
219 |
-
% (
|
220 |
-
params["about_box_background_color"],
|
221 |
-
today,
|
222 |
-
params["url_project_wiki"],
|
223 |
-
params["url_github_repo"],
|
224 |
-
params["url_data_science_wiki"],
|
225 |
-
),
|
226 |
unsafe_allow_html=True,
|
227 |
)
|
228 |
|
|
|
1 |
"""Functions for the layout of the Streamlit app, including the sidebar."""
|
2 |
+
|
3 |
import base64
|
4 |
import os
|
5 |
from datetime import date
|
6 |
|
7 |
import streamlit as st
|
8 |
+
|
9 |
from src.config_parameters import params
|
10 |
|
11 |
|
|
|
185 |
Returns:
|
186 |
None
|
187 |
"""
|
|
|
|
|
188 |
# About textbox
|
189 |
st.sidebar.markdown("## About")
|
190 |
st.sidebar.markdown(
|
191 |
+
f"""
|
192 |
+
<p>
|
193 |
+
Todo: general about stuff <br />
|
194 |
+
<a href='{params["url_github_repo"]}'>
|
195 |
+
Github Repo</a>
|
196 |
+
</p>
|
197 |
+
""",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
unsafe_allow_html=True,
|
199 |
)
|
200 |
|
app/src/utils_ee.py
DELETED
@@ -1,36 +0,0 @@
|
|
1 |
-
"""Module for ee-related functionalities."""
|
2 |
-
import ee
|
3 |
-
import streamlit as st
|
4 |
-
from ee import oauth
|
5 |
-
from google.oauth2 import service_account
|
6 |
-
from src.utils import is_app_on_streamlit
|
7 |
-
|
8 |
-
|
9 |
-
@st.experimental_memo
|
10 |
-
def ee_initialize(force_use_service_account: bool = False):
|
11 |
-
"""Initialise Google Earth Engine.
|
12 |
-
|
13 |
-
Checks whether the app is deployed on Streamlit Cloud and, based on the
|
14 |
-
result, initialises Google Earth Engine in different ways: if the app is
|
15 |
-
run locally, the credentials are retrieved from the user's credentials
|
16 |
-
stored in the local system (personal Google account is used). If the app
|
17 |
-
is deployed on Streamlit Cloud, credentials are taken from the secrets
|
18 |
-
field in the cloud (a dedicated service account is used).
|
19 |
-
Inputs:
|
20 |
-
force_use_service_account (bool): If True, the dedicated Google
|
21 |
-
service account is used, regardless of whether the app is run
|
22 |
-
locally or in the cloud. To be able to use a service account
|
23 |
-
locally, a file called "secrets.toml" should be added to the
|
24 |
-
folder ".streamlit", in the main project folder.
|
25 |
-
|
26 |
-
Returns:
|
27 |
-
None
|
28 |
-
"""
|
29 |
-
if force_use_service_account or is_app_on_streamlit():
|
30 |
-
service_account_keys = st.secrets["ee_keys"]
|
31 |
-
credentials = service_account.Credentials.from_service_account_info(
|
32 |
-
service_account_keys, scopes=oauth.SCOPES
|
33 |
-
)
|
34 |
-
ee.Initialize(credentials)
|
35 |
-
else:
|
36 |
-
ee.Initialize()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/src/utils_flood_analysis.py
DELETED
@@ -1,369 +0,0 @@
|
|
1 |
-
"""Functions to derive flood extent using Google Earth Engine."""
|
2 |
-
import time
|
3 |
-
|
4 |
-
import ee
|
5 |
-
|
6 |
-
|
7 |
-
def _check_task_completed(task_id, verbose=False):
|
8 |
-
"""
|
9 |
-
Return True if a task export completes successfully, else returns false.
|
10 |
-
|
11 |
-
Inputs:
|
12 |
-
task_id (str): Google Earth Engine task id
|
13 |
-
|
14 |
-
Returns:
|
15 |
-
boolean
|
16 |
-
|
17 |
-
"""
|
18 |
-
status = ee.data.getTaskStatus(task_id)[0]
|
19 |
-
if status["state"] in (
|
20 |
-
ee.batch.Task.State.CANCELLED,
|
21 |
-
ee.batch.Task.State.FAILED,
|
22 |
-
):
|
23 |
-
if "error_message" in status:
|
24 |
-
if verbose:
|
25 |
-
print(status["error_message"])
|
26 |
-
return True
|
27 |
-
elif status["state"] == ee.batch.Task.State.COMPLETED:
|
28 |
-
return True
|
29 |
-
return False
|
30 |
-
|
31 |
-
|
32 |
-
def wait_for_tasks(task_ids, timeout=3600, verbose=False):
|
33 |
-
"""
|
34 |
-
Wait for tasks to complete, fail, or timeout.
|
35 |
-
|
36 |
-
Wait for all active tasks if task_ids is not provided.
|
37 |
-
Note: Tasks will not be canceled after timeout, and
|
38 |
-
may continue to run.
|
39 |
-
Inputs:
|
40 |
-
task_ids (list):
|
41 |
-
timeout (int):
|
42 |
-
|
43 |
-
Returns:
|
44 |
-
None
|
45 |
-
"""
|
46 |
-
start = time.time()
|
47 |
-
elapsed = 0
|
48 |
-
while elapsed < timeout or timeout == 0:
|
49 |
-
elapsed = time.time() - start
|
50 |
-
finished = [_check_task_completed(task) for task in task_ids]
|
51 |
-
if all(finished):
|
52 |
-
if verbose:
|
53 |
-
print(f"Tasks {task_ids} completed after {elapsed}s")
|
54 |
-
return True
|
55 |
-
time.sleep(5)
|
56 |
-
if verbose:
|
57 |
-
print(
|
58 |
-
f"Stopped waiting for {len(task_ids)} tasks \
|
59 |
-
after {timeout} seconds"
|
60 |
-
)
|
61 |
-
return False
|
62 |
-
|
63 |
-
|
64 |
-
def export_flood_data(
|
65 |
-
flooded_area_vector,
|
66 |
-
flooded_area_raster,
|
67 |
-
image_before_flood,
|
68 |
-
image_after_flood,
|
69 |
-
region,
|
70 |
-
filename="flood_extents",
|
71 |
-
verbose=False,
|
72 |
-
):
|
73 |
-
"""
|
74 |
-
Export the results of derive_flood_extents function to Google Drive.
|
75 |
-
|
76 |
-
Inputs:
|
77 |
-
flooded_area_vector (ee.FeatureCollection): Detected flood extents as
|
78 |
-
vector geometries.
|
79 |
-
flooded_area_raster (ee.Image): Detected flood extents as a binary
|
80 |
-
raster.
|
81 |
-
image_before_flood (ee.Image): The 'before' Sentinel-1 image.
|
82 |
-
image_after_flood (ee.Image): The 'after' Sentinel-1 image containing
|
83 |
-
view of the flood waters.
|
84 |
-
region (ee.Geometry.Polygon): Geographic extent of analysis area.
|
85 |
-
filename (str): Desired filename prefix for exported files
|
86 |
-
|
87 |
-
Returns:
|
88 |
-
None
|
89 |
-
"""
|
90 |
-
if verbose:
|
91 |
-
print(
|
92 |
-
"Exporting detected flood extents to your Google Drive. \
|
93 |
-
Please wait..."
|
94 |
-
)
|
95 |
-
s1_before_task = ee.batch.Export.image.toDrive(
|
96 |
-
image=image_before_flood,
|
97 |
-
description="export_before_s1_scene",
|
98 |
-
scale=30,
|
99 |
-
region=region,
|
100 |
-
fileNamePrefix=filename + "_s1_before",
|
101 |
-
crs="EPSG:4326",
|
102 |
-
fileFormat="GeoTIFF",
|
103 |
-
)
|
104 |
-
|
105 |
-
s1_after_task = ee.batch.Export.image.toDrive(
|
106 |
-
image=image_after_flood,
|
107 |
-
description="export_flooded_s1_scene",
|
108 |
-
scale=30,
|
109 |
-
region=region,
|
110 |
-
fileNamePrefix=filename + "_s1_after",
|
111 |
-
crs="EPSG:4326",
|
112 |
-
fileFormat="GeoTIFF",
|
113 |
-
)
|
114 |
-
|
115 |
-
raster_task = ee.batch.Export.image.toDrive(
|
116 |
-
image=flooded_area_raster,
|
117 |
-
description="export_flood_extents_raster",
|
118 |
-
scale=30,
|
119 |
-
region=region,
|
120 |
-
fileNamePrefix=filename + "_raster",
|
121 |
-
crs="EPSG:4326",
|
122 |
-
fileFormat="GeoTIFF",
|
123 |
-
)
|
124 |
-
|
125 |
-
vector_task = ee.batch.Export.table.toDrive(
|
126 |
-
collection=flooded_area_vector,
|
127 |
-
description="export_flood_extents_polygons",
|
128 |
-
fileFormat="shp",
|
129 |
-
fileNamePrefix=filename + "_polygons",
|
130 |
-
)
|
131 |
-
|
132 |
-
s1_before_task.start()
|
133 |
-
s1_after_task.start()
|
134 |
-
raster_task.start()
|
135 |
-
vector_task.start()
|
136 |
-
|
137 |
-
if verbose:
|
138 |
-
print("Exporting before Sentinel-1 scene: Task id ", s1_before_task.id)
|
139 |
-
print("Exporting flooded Sentinel-1 scene: Task id ", s1_after_task.id)
|
140 |
-
print("Exporting flood extent geotiff: Task id ", raster_task.id)
|
141 |
-
print("Exporting flood extent shapefile: Task id ", vector_task.id)
|
142 |
-
|
143 |
-
wait_for_tasks(
|
144 |
-
[s1_before_task.id, s1_after_task.id, raster_task.id, vector_task.id]
|
145 |
-
)
|
146 |
-
|
147 |
-
|
148 |
-
def retrieve_image_collection(
|
149 |
-
search_region,
|
150 |
-
start_date,
|
151 |
-
end_date,
|
152 |
-
polarization="VH",
|
153 |
-
pass_direction="Ascending",
|
154 |
-
):
|
155 |
-
"""
|
156 |
-
Retrieve Sentinel-1 immage collection from Google Earth Engine.
|
157 |
-
|
158 |
-
Inputs:
|
159 |
-
search_region (ee.Geometry.Polygon): Geographic extent of image search.
|
160 |
-
start_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
|
161 |
-
end_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
|
162 |
-
polarization (str): Synthetic aperture radar polarization mode, e.g.,
|
163 |
-
'VH' or 'VV'. VH is mostly is the preferred polarization for
|
164 |
-
flood mapping.
|
165 |
-
pass_direction (str): Synthetic aperture radar pass direction, either
|
166 |
-
'Ascending' or 'Descending'.
|
167 |
-
|
168 |
-
Returns:
|
169 |
-
collection (ee.ImageCollection): Sentinel-1 images matching the search
|
170 |
-
criteria.
|
171 |
-
"""
|
172 |
-
collection = (
|
173 |
-
ee.ImageCollection("COPERNICUS/S1_GRD")
|
174 |
-
.filter(ee.Filter.eq("instrumentMode", "IW"))
|
175 |
-
.filter(
|
176 |
-
ee.Filter.listContains(
|
177 |
-
"transmitterReceiverPolarisation", polarization
|
178 |
-
)
|
179 |
-
)
|
180 |
-
.filter(ee.Filter.eq("orbitProperties_pass", pass_direction.upper()))
|
181 |
-
.filter(ee.Filter.eq("resolution_meters", 10))
|
182 |
-
.filterDate(start_date, end_date)
|
183 |
-
.filterBounds(search_region)
|
184 |
-
.select(polarization)
|
185 |
-
)
|
186 |
-
|
187 |
-
return collection
|
188 |
-
|
189 |
-
|
190 |
-
def smooth(image, smoothing_radius=50):
|
191 |
-
"""
|
192 |
-
Reduce the radar speckle by smoothing.
|
193 |
-
|
194 |
-
Inputs:
|
195 |
-
image (ee.Image): Input image.
|
196 |
-
smoothing_radius (int): The radius of the kernel to use for focal mean
|
197 |
-
smoothing.
|
198 |
-
|
199 |
-
Returns:
|
200 |
-
smoothed_image (ee.Image): The resulting image after smoothing is
|
201 |
-
applied.
|
202 |
-
"""
|
203 |
-
smoothed_image = image.focal_mean(
|
204 |
-
radius=smoothing_radius, kernelType="circle", units="meters"
|
205 |
-
)
|
206 |
-
|
207 |
-
return smoothed_image
|
208 |
-
|
209 |
-
|
210 |
-
def mask_permanent_water(image):
|
211 |
-
"""
|
212 |
-
Query the JRC Global Surface Water Mapping Layers, v1.3.
|
213 |
-
|
214 |
-
The goal is to determine where perennial water bodies (water > 10
|
215 |
-
months/yr), and mask these areas.
|
216 |
-
Inputs:
|
217 |
-
image (ee.Image): Input image.
|
218 |
-
|
219 |
-
Returns:
|
220 |
-
masked_image (ee.Image): The resulting image after surface water
|
221 |
-
masking is applied.
|
222 |
-
"""
|
223 |
-
surface_water = ee.Image("JRC/GSW1_4/GlobalSurfaceWater").select(
|
224 |
-
"seasonality"
|
225 |
-
)
|
226 |
-
surface_water_mask = surface_water.gte(10).updateMask(
|
227 |
-
surface_water.gte(10)
|
228 |
-
)
|
229 |
-
|
230 |
-
# Flooded layer where perennial water bodies(water > 10 mo / yr) is
|
231 |
-
# assigned a 0 value
|
232 |
-
where_surface_water = image.where(surface_water_mask, 0)
|
233 |
-
|
234 |
-
masked_image = image.updateMask(where_surface_water)
|
235 |
-
|
236 |
-
return masked_image
|
237 |
-
|
238 |
-
|
239 |
-
def reduce_noise(image):
|
240 |
-
"""
|
241 |
-
Reduce noise in the image.
|
242 |
-
|
243 |
-
Compute connectivity of pixels to eliminate those connected to 8 or fewer
|
244 |
-
neighbours.
|
245 |
-
Inputs:
|
246 |
-
image (ee.Image): A binary image.
|
247 |
-
|
248 |
-
Returns:
|
249 |
-
reduced_noise_image (ee.Image): The resulting image after noise
|
250 |
-
reduction is applied.
|
251 |
-
"""
|
252 |
-
connections = image.connectedPixelCount()
|
253 |
-
reduced_noise_image = image.updateMask(connections.gte(8))
|
254 |
-
|
255 |
-
return reduced_noise_image
|
256 |
-
|
257 |
-
|
258 |
-
def mask_slopes(image):
|
259 |
-
"""
|
260 |
-
Mask out areas with more than 5 % slope with a Digital Elevation Model.
|
261 |
-
|
262 |
-
Inputs:
|
263 |
-
image (ee.Image): Input image.
|
264 |
-
Returns:
|
265 |
-
slopes_masked (ee.Image): The resulting image after slope masking is
|
266 |
-
applied.
|
267 |
-
"""
|
268 |
-
dem = ee.Image("WWF/HydroSHEDS/03VFDEM")
|
269 |
-
terrain = ee.Algorithms.Terrain(dem)
|
270 |
-
slope = terrain.select("slope")
|
271 |
-
slopes_masked = image.updateMask(slope.lt(5))
|
272 |
-
|
273 |
-
return slopes_masked
|
274 |
-
|
275 |
-
|
276 |
-
def derive_flood_extents(
|
277 |
-
aoi,
|
278 |
-
before_start_date,
|
279 |
-
before_end_date,
|
280 |
-
after_start_date,
|
281 |
-
after_end_date,
|
282 |
-
difference_threshold=1.25,
|
283 |
-
polarization="VH",
|
284 |
-
pass_direction="Ascending",
|
285 |
-
export=False,
|
286 |
-
export_filename="flood_extents",
|
287 |
-
):
|
288 |
-
"""
|
289 |
-
Set start and end dates of a period BEFORE and AFTER a flood.
|
290 |
-
|
291 |
-
These periods need to be long enough for Sentinel-1 to acquire an image.
|
292 |
-
|
293 |
-
Inputs:
|
294 |
-
aoi (ee.Geometry.Polygon): Geographic extent of analysis area.
|
295 |
-
before_start_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
|
296 |
-
before_end_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
|
297 |
-
after_start_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
|
298 |
-
after_end_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
|
299 |
-
difference_threshold (float): Threshold to be applied on the
|
300 |
-
differenced image (after flood - before flood). It has been chosen
|
301 |
-
by trial and error. In case your flood extent result shows many
|
302 |
-
false-positive or negative signals, consider changing it.
|
303 |
-
export (bool): Flag to export derived flood extents to Google Drive
|
304 |
-
export_filename (str): Desired filename prefix for exported files. Only
|
305 |
-
used if export=True.
|
306 |
-
|
307 |
-
Returns:
|
308 |
-
flood_vectors (ee.FeatureCollection): Detected flood extents as vector
|
309 |
-
geometries.
|
310 |
-
flood_rasters (ee.Image): Detected flood extents as a binary raster.
|
311 |
-
before_filtered (ee.Image): The 'before' Sentinel-1 image.
|
312 |
-
after_filtered (ee.Image): The 'after' Sentinel-1 image containing view
|
313 |
-
of the flood waters.
|
314 |
-
"""
|
315 |
-
before_flood_img_col = retrieve_image_collection(
|
316 |
-
search_region=aoi,
|
317 |
-
start_date=before_start_date,
|
318 |
-
end_date=before_end_date,
|
319 |
-
polarization=polarization,
|
320 |
-
pass_direction=pass_direction,
|
321 |
-
)
|
322 |
-
after_flood_img_col = retrieve_image_collection(
|
323 |
-
search_region=aoi,
|
324 |
-
start_date=after_start_date,
|
325 |
-
end_date=after_end_date,
|
326 |
-
polarization=polarization,
|
327 |
-
pass_direction=pass_direction,
|
328 |
-
)
|
329 |
-
|
330 |
-
# Create a mosaic of selected tiles and clip to study area
|
331 |
-
before_mosaic = before_flood_img_col.mosaic().clip(aoi)
|
332 |
-
after_mosaic = after_flood_img_col.mosaic().clip(aoi)
|
333 |
-
|
334 |
-
before_filtered = smooth(before_mosaic)
|
335 |
-
after_filtered = smooth(after_mosaic)
|
336 |
-
|
337 |
-
# Calculate the difference between the before and after images
|
338 |
-
difference = after_filtered.divide(before_filtered)
|
339 |
-
|
340 |
-
# Apply the predefined difference - threshold and create the flood extent
|
341 |
-
# mask
|
342 |
-
difference_binary = difference.gt(difference_threshold)
|
343 |
-
difference_binary_masked = mask_permanent_water(difference_binary)
|
344 |
-
difference_binary_masked_reduced_noise = reduce_noise(
|
345 |
-
difference_binary_masked
|
346 |
-
)
|
347 |
-
flood_rasters = mask_slopes(difference_binary_masked_reduced_noise)
|
348 |
-
|
349 |
-
# Export the extent of detected flood in vector format
|
350 |
-
flood_vectors = flood_rasters.reduceToVectors(
|
351 |
-
scale=10,
|
352 |
-
geometryType="polygon",
|
353 |
-
geometry=aoi,
|
354 |
-
eightConnected=False,
|
355 |
-
bestEffort=True,
|
356 |
-
tileScale=2,
|
357 |
-
)
|
358 |
-
|
359 |
-
if export:
|
360 |
-
export_flood_data(
|
361 |
-
flooded_area_vector=flood_vectors,
|
362 |
-
flooded_area_raster=flood_rasters,
|
363 |
-
image_before_flood=before_filtered,
|
364 |
-
image_after_flood=after_filtered,
|
365 |
-
region=aoi,
|
366 |
-
filename=export_filename,
|
367 |
-
)
|
368 |
-
|
369 |
-
return flood_vectors, flood_rasters, before_filtered, after_filtered
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -5,3 +5,4 @@ streamlit==1.14.1
|
|
5 |
streamlit_ext==0.1.4
|
6 |
streamlit-folium==0.7.0
|
7 |
pre-commit==2.18.1
|
|
|
|
5 |
streamlit_ext==0.1.4
|
6 |
streamlit-folium==0.7.0
|
7 |
pre-commit==2.18.1
|
8 |
+
altair<5
|