Timo21 commited on
Commit
b4e2e7b
Β·
2 Parent(s): 44d7bee adf9eea

Merge pull request #22 from rodekruis/use_gfm_aois

Browse files
app/pages/0_AOIs.py DELETED
@@ -1,169 +0,0 @@
1
- import json
2
- import os
3
- from pathlib import Path
4
-
5
- import folium
6
- import requests
7
- import streamlit as st
8
- from folium.plugins import Draw
9
- from src.config_parameters import params
10
- from src.gfm import download_gfm_geojson, get_existing_flood_geojson, sync_cached_data
11
- from src.utils import (
12
- add_about,
13
- set_tool_page_style,
14
- toggle_menu_button,
15
- )
16
- from streamlit_folium import st_folium
17
-
18
- # Page configuration
19
- st.set_page_config(layout="wide", page_title=params["browser_title"])
20
-
21
- # If app is deployed hide menu button
22
- toggle_menu_button()
23
-
24
- # Create sidebar
25
- add_about()
26
-
27
- # Page title
28
- st.markdown("# Flood extent analysis")
29
-
30
- # Set page style
31
- set_tool_page_style()
32
-
33
- # Create two rows: top and bottom panel
34
- row1 = st.container()
35
- save_area = False
36
-
37
- with row1:
38
- action_type = st.radio(
39
- label="Action Type",
40
- options=["See Areas", "Create New Area", "Delete Area"],
41
- label_visibility="hidden",
42
- )
43
-
44
- # call to render Folium map in Streamlit
45
- folium_map = folium.Map([39, 0], zoom_start=8)
46
- feat_group_selected_area = folium.FeatureGroup(name="selected_area")
47
-
48
- if action_type == "See Areas":
49
- with open("./bboxes/bboxes.json", "r") as f:
50
- bboxes = json.load(f)
51
- for area_name in bboxes.keys():
52
- bbox = bboxes[area_name]["bounding_box"]
53
- feat_group_selected_area.add_child(folium.GeoJson(bbox))
54
-
55
- folium_map.fit_bounds(feat_group_selected_area.get_bounds())
56
-
57
- elif action_type == "Create New Area":
58
- Draw(
59
- export=False,
60
- draw_options={
61
- "circle": False,
62
- "polyline": False,
63
- "polygon": False,
64
- "rectangle": True,
65
- "marker": False,
66
- "circlemarker": False,
67
- },
68
- ).add_to(folium_map)
69
-
70
- new_area_name = st.text_input("Area name")
71
- save_area = st.button("Save Area")
72
-
73
- elif action_type == "Delete Area":
74
- # Load existing bboxes
75
- with open("./bboxes/bboxes.json", "r") as f:
76
- bboxes = json.load(f)
77
- existing_areas = bboxes.keys()
78
-
79
- area_to_delete = st.selectbox("Choose area to delete", options=existing_areas)
80
- bbox = bboxes[area_to_delete]["bounding_box"]
81
- feat_group_selected_area.add_child(folium.GeoJson(bbox))
82
- folium_map.fit_bounds(feat_group_selected_area.get_bounds())
83
-
84
- delete_area = st.button("Delete")
85
-
86
- if delete_area:
87
- with open("./bboxes/bboxes.json", "r") as f:
88
- bboxes = json.load(f)
89
- bboxes.pop(area_to_delete, None)
90
- with open("./bboxes/bboxes.json", "w") as f:
91
- json.dump(bboxes, f)
92
- st.toast("Area successfully deleted")
93
-
94
- with open("./bboxes/bboxes.json", "r") as f:
95
- bboxes = json.load(f)
96
-
97
- # geojson_catania = get_existing_flood_geojson("Catania")
98
- # print(geojson_catania)
99
- # geojson_selected_area = folium.GeoJson(geojson_catania)
100
-
101
- # feat_group_selected_area.add_child(geojson_selected_area)
102
- m = st_folium(
103
- folium_map,
104
- width=800,
105
- height=450,
106
- feature_group_to_add=feat_group_selected_area,
107
- )
108
-
109
- if save_area:
110
- check_drawing = m["all_drawings"] != [] and m["all_drawings"] is not None
111
- if not check_drawing:
112
- st.error("Please create a region using the rectangle tool on the map.")
113
- elif new_area_name == "":
114
- st.error("Please provide a name for the new area")
115
- else:
116
- # Get the drawn area
117
- selected_area_geojson = m["all_drawings"][-1]
118
-
119
- print("starting to post new area name to gfm api")
120
- coordinates = selected_area_geojson["geometry"]["coordinates"]
121
-
122
- username = os.environ["gfm_username"]
123
- password = os.environ["gfm_password"]
124
- base_url = "https://api.gfm.eodc.eu/v1"
125
-
126
- # Get token, setup header
127
- token_url = f"{base_url}/auth/login"
128
-
129
- payload = {"email": username, "password": password}
130
-
131
- response = requests.post(token_url, json=payload)
132
- user_id = response.json()["client_id"]
133
- access_token = response.json()["access_token"]
134
- header = {"Authorization": f"bearer {access_token}"}
135
-
136
- print("authenticated to API")
137
- # Create area of impact
138
- create_aoi_url = f"{base_url}/aoi/create"
139
-
140
- payload = {
141
- "aoi_name": new_area_name,
142
- "description": new_area_name,
143
- "user_id": user_id,
144
- "geoJSON": {"type": "Polygon", "coordinates": coordinates},
145
- }
146
-
147
- r = requests.post(url=create_aoi_url, json=payload, headers=header)
148
- print(r.json())
149
- print("Posted new AOI")
150
-
151
- print("Writing new area to bbox json")
152
- # Load existing bboxes
153
- with open("./bboxes/bboxes.json", "r") as f:
154
- bboxes = json.load(f)
155
-
156
- # If the area doesn't exist, create it
157
- if new_area_name not in bboxes:
158
- bboxes[new_area_name] = {}
159
-
160
- # Save the new bounding box under the date range key
161
- bboxes[new_area_name] = {
162
- "bounding_box": selected_area_geojson,
163
- "date_ranges": [], # Will be populated when files are downloaded
164
- }
165
- # Write the updated data back to file
166
- with open("./bboxes/bboxes.json", "w") as f:
167
- json.dump(bboxes, f, indent=4)
168
-
169
- st.toast("Area successfully created")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/pages/0_🌍_AOIs.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import folium
2
+ import streamlit as st
3
+ from folium.plugins import Draw
4
+ from src.config_parameters import params
5
+ from src.gfm import create_aoi, delete_aoi, retrieve_all_aois
6
+ from src.utils import (
7
+ add_about,
8
+ get_aoi_id_from_selector_preview,
9
+ set_tool_page_style,
10
+ toggle_menu_button,
11
+ )
12
+ from streamlit_folium import st_folium
13
+
14
+ # Page configuration
15
+ st.set_page_config(layout="wide", page_title=params["browser_title"])
16
+
17
+ # If app is deployed hide menu button
18
+ toggle_menu_button()
19
+
20
+ # Create sidebar
21
+ add_about()
22
+
23
+ # Page title
24
+ st.markdown("# AOIs")
25
+
26
+ # Set page style
27
+ set_tool_page_style()
28
+
29
+ row1 = st.container()
30
+ save_area = False
31
+
32
+ # To track whether radio selector was changed to reload AOIs if necessary, initialised as See Areas to prevent double loading
33
+ if "prev_radio_selection" not in st.session_state:
34
+ st.session_state["prev_radio_selection"] = "See Areas"
35
+
36
+ if "all_aois" not in st.session_state:
37
+ st.session_state["all_aois"] = retrieve_all_aois()
38
+
39
+
40
+ feat_group_selected_area = folium.FeatureGroup(name="selected_area")
41
+ radio_selection = st.radio(
42
+ label="Action Type",
43
+ options=["See Areas", "Create New Area", "Delete Area"],
44
+ label_visibility="hidden",
45
+ )
46
+
47
+ # call to render Folium map in Streamlit
48
+ folium_map = folium.Map([39, 0], zoom_start=8)
49
+
50
+ # See Areas will show all areas collected from GFM.
51
+ # Collecting AOIs is done on first page load and when switching from a different radio selection back to See Areas
52
+ if radio_selection == "See Areas":
53
+ if st.session_state["prev_radio_selection"] != "See Areas":
54
+ st.session_state["all_aois"] = retrieve_all_aois()
55
+
56
+ # Add each AOI as a feature group to the map
57
+ for aoi in st.session_state["all_aois"].values():
58
+ feat_group_selected_area.add_child(folium.GeoJson(aoi["bbox"]))
59
+
60
+ folium_map.fit_bounds(feat_group_selected_area.get_bounds())
61
+ st.session_state["prev_radio_selection"] = "See Areas"
62
+
63
+ # When creating a new area the map will have a rectangle drawing option which will be saved with Save button
64
+ elif radio_selection == "Create New Area":
65
+ Draw(
66
+ export=False,
67
+ draw_options={
68
+ "circle": False,
69
+ "polyline": False,
70
+ "polygon": False,
71
+ "rectangle": True,
72
+ "marker": False,
73
+ "circlemarker": False,
74
+ },
75
+ ).add_to(folium_map)
76
+
77
+ new_area_name = st.text_input("Area name")
78
+ save_area = st.button("Save Area")
79
+
80
+ st.session_state["prev_radio_selection"] = "Create New Area"
81
+
82
+ # Delete area does exactly that, it will show the selected area and a delete button
83
+ elif radio_selection == "Delete Area":
84
+ existing_areas = [
85
+ aoi["name_id_preview"] for aoi in st.session_state["all_aois"].values()
86
+ ]
87
+
88
+ area_to_delete_name_id = st.selectbox(
89
+ "Choose area to delete", options=existing_areas
90
+ )
91
+ selected_area_id = get_aoi_id_from_selector_preview(
92
+ st.session_state["all_aois"], area_to_delete_name_id
93
+ )
94
+
95
+ bbox = st.session_state["all_aois"][selected_area_id]["bbox"]
96
+ feat_group_selected_area.add_child(folium.GeoJson(bbox))
97
+ folium_map.fit_bounds(feat_group_selected_area.get_bounds())
98
+
99
+ delete_area = st.button("Delete")
100
+ st.session_state["prev_radio_selection"] = "Delete Area"
101
+
102
+ # If clicked will delete both from API and the session state to also remove from the Delete Area selector
103
+ if delete_area:
104
+ delete_aoi(selected_area_id)
105
+ st.session_state["all_aois"].pop(selected_area_id, None)
106
+ st.toast("Area successfully deleted")
107
+ st.rerun()
108
+
109
+ # Create map with features based on the radio selector handling above
110
+ m = st_folium(
111
+ folium_map,
112
+ width=800,
113
+ height=450,
114
+ feature_group_to_add=feat_group_selected_area,
115
+ )
116
+
117
+ # If in Create New Area the save button is clicked it will write the new area to the API
118
+ if save_area:
119
+ check_drawing = m["all_drawings"] != [] and m["all_drawings"] is not None
120
+ if not check_drawing:
121
+ st.error("Please create a region using the rectangle tool on the map.")
122
+ elif new_area_name == "":
123
+ st.error("Please provide a name for the new area")
124
+ else:
125
+ # Get the drawn area
126
+ selected_area_geojson = m["all_drawings"][-1]
127
+
128
+ print("starting to post new area name to gfm api")
129
+ coordinates = selected_area_geojson["geometry"]["coordinates"]
130
+
131
+ create_aoi(new_area_name, coordinates)
132
+ st.toast("Area successfully created")
133
+
134
+ st.session_state["prev_page"] = "aois"
app/pages/1_🌍_Flood_extent_analysis.py DELETED
@@ -1,130 +0,0 @@
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, sync_cached_data
9
- from src.utils import (
10
- add_about,
11
- set_tool_page_style,
12
- toggle_menu_button,
13
- )
14
- from streamlit_folium import st_folium
15
-
16
- # Page configuration
17
- st.set_page_config(layout="wide", page_title=params["browser_title"])
18
-
19
- # If app is deployed hide menu button
20
- toggle_menu_button()
21
-
22
- # Create sidebar
23
- add_about()
24
-
25
- # Page title
26
- st.markdown("# Flood extent analysis")
27
-
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
- with open("./bboxes/bboxes.json", "r") as f:
42
- bboxes = json.load(f)
43
- selected_area = st.selectbox("Select saved area", options=bboxes.keys())
44
-
45
- # retrieve and select available dates
46
- if selected_area:
47
- area_folder = Path(f"./output/{selected_area}")
48
- if area_folder.exists():
49
- available_product_times = [
50
- str(f.name) for f in area_folder.iterdir() if f.is_dir()
51
- ]
52
-
53
- available_product_times = [
54
- prod_time.replace("_", ":") for prod_time in available_product_times
55
- ]
56
- selected_product_time = st.selectbox(
57
- "Select available date range", options=available_product_times
58
- )
59
- else:
60
- selected_product_time = None
61
-
62
- # display the bounding box
63
- bounding_box = bboxes[selected_area]["bounding_box"]
64
- geojson_selected_area = folium.GeoJson(bounding_box)
65
- feat_group_selected_area.add_child(geojson_selected_area)
66
-
67
- # geojson_selected_area = folium.GeoJson(bboxes[selected_area])
68
- # feat_group_selected_area.add_child(geojson_selected_area)
69
- if selected_product_time:
70
- geojson_flood_area = get_existing_flood_geojson(
71
- selected_area, selected_product_time
72
- )
73
- feat_group_selected_area.add_child(geojson_flood_area)
74
-
75
- # Add collapsable container for input map
76
- with st.expander("Input map", expanded=True):
77
- # Create folium map
78
- # call to render Folium map in Streamlit
79
- folium_map = folium.Map([39, 0], zoom_start=8)
80
- # check if the FeatureGroup has any children (i.e., layers added)
81
- if len(feat_group_selected_area._children) > 0:
82
- # if there is data, fit the map to the bounds
83
- folium_map.fit_bounds(feat_group_selected_area.get_bounds())
84
- else:
85
- # if there is no data, set a default view
86
- # this is necessary to start up the page
87
- folium_map = folium.Map(location=[39, 0], zoom_start=8)
88
-
89
- m = st_folium(
90
- folium_map,
91
- width=800,
92
- height=450,
93
- feature_group_to_add=feat_group_selected_area,
94
- )
95
- with col2:
96
- # Add collapsable container for image dates
97
- with st.expander("Choose Dates", expanded=True):
98
- start_date = st.date_input("Start date")
99
- end_date = st.date_input("End date")
100
- # Add collapsable container for parameters
101
- with st.expander("Choose Parameters"):
102
- # Add slider for threshold
103
- st.text("Add relevant (API) parameters here")
104
-
105
- submitted = st.button("Update flood extent")
106
-
107
-
108
- # If the button is clicked do the following
109
- if submitted:
110
- with col2:
111
- # Some basic validation on dates and that there's an area if relevant
112
- get_gfm = True
113
- check_dates = start_date <= end_date
114
-
115
- # Output error if dates are not valid
116
- if not check_dates:
117
- st.error("Make sure that the dates were inserted correctly")
118
- get_gfm = False
119
-
120
- # Only if checks pass go and get the GFM data
121
- if get_gfm:
122
- # Show loader because it will take a while
123
- with st.spinner("Getting GFM files... Please wait..."):
124
- # download_gfm_geojson(area_name)
125
- download_gfm_geojson(
126
- selected_area, from_date=start_date, to_date=end_date
127
- )
128
-
129
- # Display that getting the files is finished
130
- st.markdown("Getting GFM files finished")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/pages/1_πŸ’§_Flood_extent_analysis.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import date, timedelta
2
+
3
+ import folium
4
+ import pandas as pd
5
+ import streamlit as st
6
+ from src.config_parameters import params
7
+ from src.gfm import (
8
+ download_flood_product,
9
+ get_area_products,
10
+ retrieve_all_aois,
11
+ )
12
+ from src.utils import (
13
+ add_about,
14
+ get_aoi_id_from_selector_preview,
15
+ get_existing_flood_geojson,
16
+ set_tool_page_style,
17
+ toggle_menu_button,
18
+ )
19
+ from streamlit_folium import st_folium
20
+
21
+ today = date.today()
22
+
23
+ default_date_yesterday = today - timedelta(days=1)
24
+
25
+ # Page configuration
26
+ st.set_page_config(layout="wide", page_title=params["browser_title"])
27
+
28
+ # If app is deployed hide menu button
29
+ toggle_menu_button()
30
+
31
+ # Create sidebar
32
+ add_about()
33
+
34
+ # Page title
35
+ st.markdown("# Flood extent analysis")
36
+
37
+ # Set page style
38
+ set_tool_page_style()
39
+
40
+ # Create two rows: top and bottom panel
41
+ row1 = st.container()
42
+ row2 = st.container()
43
+ # Crate two columns in the top panel: input map and paramters
44
+ col1, col2, col3, col4, col5 = row1.columns([1, 1, 1, 1, 1])
45
+ col2_1, col2_2 = row2.columns([3, 2])
46
+
47
+ # Retrieve AOIs to fill AOI selector
48
+ if "all_aois" not in st.session_state:
49
+ st.session_state["all_aois"] = retrieve_all_aois()
50
+
51
+ # If coming from a different page, all aois may be filled but not up to date, retrigger
52
+ if st.session_state["prev_page"] != "flood_extent":
53
+ st.session_state["all_aois"] = retrieve_all_aois()
54
+
55
+ if "all_products" not in st.session_state:
56
+ st.session_state["all_products"] = None
57
+
58
+
59
+ # To force removing product checkboxes when AOI selector changes
60
+ def on_area_selector_change():
61
+ print("Area selector changed, removing product checkboxes")
62
+ st.session_state["all_products"] = None
63
+
64
+
65
+ # Contains AOI selector
66
+ with col1:
67
+ selected_area_name_id = st.selectbox(
68
+ "Select saved AOI",
69
+ options=[
70
+ aoi["name_id_preview"] for aoi in st.session_state["all_aois"].values()
71
+ ],
72
+ on_change=on_area_selector_change,
73
+ )
74
+
75
+ selected_area_id = get_aoi_id_from_selector_preview(
76
+ st.session_state["all_aois"], selected_area_name_id
77
+ )
78
+
79
+ # Contain datepickers
80
+ with col2:
81
+ today = date.today()
82
+ two_weeks_ago = today - timedelta(days=14)
83
+ start_date = st.date_input("Start date", value=two_weeks_ago)
84
+
85
+ with col3:
86
+ end_date = st.date_input("End date", value=today)
87
+
88
+ # Contains available products button
89
+ with col4:
90
+ st.text(
91
+ "Button info",
92
+ help="""
93
+ This will show the timestamps of all available GFM products
94
+ that intersect with the AOI for the selected date range.
95
+ Getting the available products is a relatively fast operation
96
+ it will not trigger any product downloads.
97
+ """,
98
+ )
99
+ show_available_products = st.button("Show available products")
100
+
101
+ # If button above is triggered, get products from GFM
102
+ # Then save all products to the session state and rerun the app to display them
103
+ if show_available_products:
104
+ products = get_area_products(selected_area_id, start_date, end_date)
105
+ st.session_state["all_products"] = products
106
+ st.rerun()
107
+
108
+ # Contains the product checkboxes if they exist after pushing the "Show available products" button
109
+ with col2_2:
110
+ checkboxes = list()
111
+ # Products are checked against the index to check whether they are already downloaded
112
+ index_df = pd.read_csv("./output/index.csv")
113
+ if st.session_state["all_products"]:
114
+ for product in st.session_state["all_products"]:
115
+ suffix = ""
116
+ if product["product_id"] in index_df["product"].values:
117
+ suffix = " - Already Downloaded"
118
+ checkbox = st.checkbox(product["product_time"] + suffix)
119
+ checkboxes.append(checkbox)
120
+
121
+ # Contains the "Download Products" button
122
+ with col5:
123
+ st.text(
124
+ "Button info",
125
+ help=""
126
+ """
127
+ Will download the selected products (click "Show available products"
128
+ first if there are none). Products that show that they have already
129
+ been downloaded can be left checked, they will be skipped.
130
+ """,
131
+ )
132
+ download_products = st.button("Download products")
133
+
134
+ # If the button is clicked download all checked products that have not been downloaded yet
135
+ if download_products:
136
+ index_df = pd.read_csv("./output/index.csv")
137
+ for i, checkbox in enumerate(checkboxes):
138
+ if checkbox:
139
+ product_to_download = st.session_state["all_products"][i]
140
+ if product_to_download["product_id"] not in index_df["product"].values:
141
+ with st.spinner(
142
+ f"Getting GFM files for {product_to_download['product_time']}, this may take a couple of minutes"
143
+ ):
144
+ download_flood_product(selected_area_id, product_to_download)
145
+ st.rerun()
146
+
147
+ # For all the selected products add them to the map if they are available
148
+ feature_groups = []
149
+ if st.session_state["all_products"]:
150
+ index_df = pd.read_csv("./output/index.csv")
151
+ for i, checkbox in enumerate(checkboxes):
152
+ if checkbox:
153
+ product_id = st.session_state["all_products"][i]["product_id"]
154
+ if product_id in index_df["product"].values:
155
+ feat_group_flood = get_existing_flood_geojson(product_id)
156
+ feature_groups.append(feat_group_flood)
157
+
158
+ # Contains the map
159
+ with col2_1:
160
+ if selected_area_id:
161
+ # display the bounding box
162
+ bounding_box = st.session_state["all_aois"][selected_area_id]["bbox"]
163
+ geojson_selected_area = folium.GeoJson(bounding_box)
164
+ feat_group_selected_area = folium.FeatureGroup(name="selected_area")
165
+ feat_group_selected_area.add_child(geojson_selected_area)
166
+ feature_groups.append(feat_group_selected_area)
167
+
168
+ # Create folium map
169
+ folium_map = folium.Map([39, 0], zoom_start=8)
170
+ folium_map.fit_bounds(feat_group_selected_area.get_bounds())
171
+
172
+ m = st_folium(
173
+ folium_map,
174
+ width=800,
175
+ height=450,
176
+ feature_group_to_add=feature_groups,
177
+ )
178
+
179
+ # Keep track of which page we're currently on for page switch events
180
+ st.session_state["prev_page"] = "flood_extent"
app/pages/2_πŸ“–_Documentation.py CHANGED
@@ -1,4 +1,4 @@
1
- """Documentation page for Streamlit app."""
2
 
3
  import streamlit as st
4
  from src.config_parameters import params
 
1
+ # """Documentation page for Streamlit app."""
2
 
3
  import streamlit as st
4
  from src.config_parameters import params
app/src/gfm.py CHANGED
@@ -1,62 +1,18 @@
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
- class FloodGeoJsonError(Exception):
15
- """Custom exception for errors in fetching flood GeoJSON files."""
16
-
17
- pass
18
-
19
-
20
- def sync_cached_data(bboxes_path="./bboxes/bboxes.json", output_dir="./output"):
21
- """
22
- Ensures that all areas in bboxes.json have a corresponding folder in ./output/.
23
- Removes any area entry from bboxes.json that does not have an output folder.
24
- """
25
- try:
26
- # Load existing bounding boxes
27
- with open(bboxes_path, "r") as f:
28
- bboxes = json.load(f)
29
-
30
- # Get a set of existing output folders
31
- existing_folders = {
32
- folder.name for folder in Path(output_dir).iterdir() if folder.is_dir()
33
- }
34
-
35
- # Remove entries from bboxes.json if the folder does not exist
36
- updated_bboxes = {
37
- area: data for area, data in bboxes.items() if area in existing_folders
38
- }
39
-
40
- # If changes were made, overwrite bboxes.json
41
- if len(updated_bboxes) != len(bboxes):
42
- with open(bboxes_path, "w") as f:
43
- json.dump(updated_bboxes, f, indent=4)
44
- print(f"Updated {bboxes_path}: Removed missing areas.")
45
- else:
46
- print("All areas have matching folders.")
47
-
48
- except FileNotFoundError:
49
- print(f"Error: {bboxes_path} not found.")
50
- except json.JSONDecodeError:
51
- print(f"Error: {bboxes_path} is not a valid JSON file.")
52
-
53
-
54
- def download_gfm_geojson(
55
- area_name, from_date=None, to_date=None, output_file_path=None
56
- ):
57
- """
58
- Should provide an existing area name or a new area name with new_coordinates
59
- """
60
  username = os.environ["gfm_username"]
61
  password = os.environ["gfm_password"]
62
  base_url = "https://api.gfm.eodc.eu/v1"
@@ -69,76 +25,142 @@ def download_gfm_geojson(
69
  response = requests.post(token_url, json=payload)
70
  user_id = response.json()["client_id"]
71
  access_token = response.json()["access_token"]
72
- header = {"Authorization": f"bearer {access_token}"}
73
- print("logged in")
 
74
 
75
- # Get Area of Impact
76
- aoi_url = f"{base_url}/aoi/user/{user_id}"
77
- response = requests.get(aoi_url, headers=header)
78
 
79
- # TODO: now only getting the first AOI, should extend to getting the whole list and unioning the geojsons
80
- for aoi in response.json()["aois"]:
81
- if aoi["aoi_name"] == area_name:
82
- aoi_id = aoi["aoi_id"]
83
- break
 
84
 
85
- # Get all product IDs
86
  params = {
87
  "time": "range",
88
  "from": f"{from_date}T00:00:00",
89
  "to": f"{to_date}T00:00:00",
90
  }
91
- prod_url = f"{base_url}/aoi/{aoi_id}/products"
92
  response = requests.get(prod_url, headers=header, params=params)
93
  products = response.json()["products"]
94
- print(f"Found {len(products)} products for {area_name}")
 
 
 
 
 
 
 
 
 
 
95
 
96
  if not output_file_path:
97
  base_file_path = "./output"
98
 
99
- # Download all available flood products
100
- for product in products:
101
- product_id = product["product_id"]
 
 
102
 
103
- # Converts product_time from e.g. "2025-01-05T06:10:37" to ""2025-01-05 06h
104
- # Reason for bucketing per hour is that products are often seconds or minutes apart and should be grouped
105
- product_time = product["product_time"]
106
- product_time = product_time.split(":")[0].replace("T", " ") + "h"
107
- output_file_path = f"{base_file_path}/{area_name}/{product_time}"
108
- Path(output_file_path).mkdir(parents=True, exist_ok=True)
109
 
110
- print(f"Downloading product: {product_id}")
 
 
111
 
112
- download_url = f"{base_url}/download/product/{product_id}"
113
- response = requests.get(download_url, headers=header)
114
- download_link = response.json()["download_link"]
115
 
116
- # Download and unzip file
117
- r = requests.get(download_link)
118
- with zipfile.ZipFile(io.BytesIO(r.content)) as z:
119
- print("Extracting...")
120
- z.extractall(str(Path(output_file_path)))
 
 
121
 
122
- print("Done!")
 
 
 
 
 
 
 
123
 
 
 
 
 
124
 
125
- def get_existing_flood_geojson(area_name, product_time, output_file_path=None):
126
- """
127
- Getting a saved GFM flood geojson in an output folder of GFM files. Merge in one feature group if multiple.
128
- """
129
- product_time = product_time.replace(":", "_")
130
- if not output_file_path:
131
- output_file_path = f"./output/{area_name}/{product_time}"
132
 
133
- # Combine multiple flood files into a FeatureGroup
134
- flood_geojson_group = folium.FeatureGroup(name=f"{area_name} Floods {product_time}")
135
 
136
- for flood_file in Path(output_file_path).glob("*FLOOD*.geojson"):
137
- with open(flood_file, "r") as f:
138
- geojson_data = json.load(f)
139
- flood_layer = folium.GeoJson(geojson_data)
140
- flood_geojson_group.add_child(flood_layer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
- # TODO: consider merging multiple flood layers into one, to avoid overlap
 
 
 
143
 
144
- return flood_geojson_group
 
 
1
  import io
 
2
  import os
3
  import zipfile
4
  from pathlib import Path
5
 
6
+ import pandas as pd
7
  import requests
8
  from dotenv import load_dotenv
9
 
10
  load_dotenv()
11
 
12
 
13
+ # TODO: These functions should be transformed into a proper class mainly to prevent authentication on each call
14
+ # Authentication
15
+ def get_gfm_user_and_token():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  username = os.environ["gfm_username"]
17
  password = os.environ["gfm_password"]
18
  base_url = "https://api.gfm.eodc.eu/v1"
 
25
  response = requests.post(token_url, json=payload)
26
  user_id = response.json()["client_id"]
27
  access_token = response.json()["access_token"]
28
+ print("retrieved user id and access token")
29
+
30
+ return user_id, access_token
31
 
 
 
 
32
 
33
+ # Gets all products for an AOI in a daterange
34
+ def get_area_products(area_id, from_date, to_date):
35
+ user_id, access_token = get_gfm_user_and_token()
36
+ header = {"Authorization": f"bearer {access_token}"}
37
+
38
+ base_url = "https://api.gfm.eodc.eu/v1"
39
 
 
40
  params = {
41
  "time": "range",
42
  "from": f"{from_date}T00:00:00",
43
  "to": f"{to_date}T00:00:00",
44
  }
45
+ prod_url = f"{base_url}/aoi/{area_id}/products"
46
  response = requests.get(prod_url, headers=header, params=params)
47
  products = response.json()["products"]
48
+ print(f"Found {len(products)} products for {area_id}")
49
+
50
+ return products
51
+
52
+
53
+ # Will download a product by product_id, saves the flood geojson and updates the index for caching
54
+ def download_flood_product(area_id, product, output_file_path=None):
55
+ user_id, access_token = get_gfm_user_and_token()
56
+ header = {"Authorization": f"bearer {access_token}"}
57
+
58
+ base_url = "https://api.gfm.eodc.eu/v1"
59
 
60
  if not output_file_path:
61
  base_file_path = "./output"
62
 
63
+ product_id = product["product_id"]
64
+ product_time = product["product_time"]
65
+
66
+ output_file_path = f"{base_file_path}"
67
+ Path(output_file_path).mkdir(parents=True, exist_ok=True)
68
 
69
+ print(f"Downloading product: {product_id}")
 
 
 
 
 
70
 
71
+ download_url = f"{base_url}/download/product/{product_id}"
72
+ response = requests.get(download_url, headers=header)
73
+ download_link = response.json()["download_link"]
74
 
75
+ # Download and unzip file
76
+ r = requests.get(download_link)
77
+ buffer = io.BytesIO(r.content)
78
 
79
+ with zipfile.ZipFile(buffer, "r") as z:
80
+ namelist = z.namelist()
81
+ for name in namelist:
82
+ if "FLOOD" in name and ".geojson" in name:
83
+ flood_filename = name
84
+ break
85
+ z.extract(flood_filename, output_file_path)
86
 
87
+ df = pd.DataFrame(
88
+ {
89
+ "aoi_id": [area_id],
90
+ "datetime": [product_time],
91
+ "product": [product_id],
92
+ "geojson_path": [output_file_path + "/" + flood_filename],
93
+ }
94
+ )
95
 
96
+ index_file_path = Path(f"{output_file_path}/index.csv")
97
+ if index_file_path.is_file():
98
+ existing_df = pd.read_csv(index_file_path)
99
+ df = pd.concat([existing_df, df])
100
 
101
+ df.to_csv(index_file_path, index=False)
102
+
103
+ print(f"Product {product_id} downloaded succesfully")
 
 
 
 
104
 
 
 
105
 
106
+ # Gets all AOIs and transforms them to a dict with some features that are useful in the app, like the bbox
107
+ def retrieve_all_aois():
108
+ print("Retrieving all AOIs from GFM API")
109
+ user_id, access_token = get_gfm_user_and_token()
110
+ header = {"Authorization": f"bearer {access_token}"}
111
+
112
+ base_url = "https://api.gfm.eodc.eu/v1"
113
+
114
+ aoi_url = f"{base_url}/aoi/user/{user_id}"
115
+ response = requests.get(aoi_url, headers=header)
116
+
117
+ aois = response.json()["aois"]
118
+
119
+ aois = {
120
+ aoi["aoi_id"]: {
121
+ "name": aoi["aoi_name"],
122
+ "bbox": aoi["geoJSON"],
123
+ "name_id_preview": f"{aoi['aoi_name']} - {aoi['aoi_id'][:6]}...",
124
+ }
125
+ for aoi in aois
126
+ }
127
+
128
+ return aois
129
+
130
+
131
+ # Creates AOI on GFM using API
132
+ def create_aoi(new_area_name, coordinates):
133
+ user_id, access_token = get_gfm_user_and_token()
134
+ header = {"Authorization": f"bearer {access_token}"}
135
+
136
+ base_url = "https://api.gfm.eodc.eu/v1"
137
+
138
+ # Create area of impact
139
+ print("Creating new area of impact")
140
+ create_aoi_url = f"{base_url}/aoi/create"
141
+
142
+ payload = {
143
+ "aoi_name": new_area_name,
144
+ "description": new_area_name,
145
+ "user_id": user_id,
146
+ "geoJSON": {"type": "Polygon", "coordinates": coordinates},
147
+ }
148
+
149
+ r = requests.post(url=create_aoi_url, json=payload, headers=header)
150
+ print("Posted new AOI")
151
+
152
+
153
+ # Deletes AOI on GFM using API
154
+ def delete_aoi(aoi_id):
155
+ user_id, access_token = get_gfm_user_and_token()
156
+ header = {"Authorization": f"bearer {access_token}"}
157
+
158
+ base_url = "https://api.gfm.eodc.eu/v1"
159
 
160
+ # Create area of impact
161
+ print(f"Deleting area of impact {aoi_id}")
162
+ delete_aoi_url = f"{base_url}/aoi/delete/id/{aoi_id}"
163
+ print(delete_aoi_url)
164
 
165
+ r = requests.delete(url=delete_aoi_url, headers=header)
166
+ print("AOI deleted")
app/src/utils.py CHANGED
@@ -1,14 +1,21 @@
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
 
 
 
 
 
 
 
12
  # Check if app is deployed
13
  def is_app_on_streamlit():
14
  """Check whether the app is on streamlit or runs locally."""
@@ -148,3 +155,21 @@ def add_about():
148
  % (params["about_box_background_color"], contacts_text),
149
  unsafe_allow_html=True,
150
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """Functions for the layout of the Streamlit app, including the sidebar."""
2
 
3
+ import json
4
  import os
 
5
 
6
+ import folium
7
+ import pandas as pd
8
  import streamlit as st
9
 
10
  from src.config_parameters import params
11
 
12
 
13
+ def get_aoi_id_from_selector_preview(all_aois, name_id_preview):
14
+ for aoi_id, aoi in all_aois.items():
15
+ if aoi["name_id_preview"] == name_id_preview:
16
+ return aoi_id
17
+
18
+
19
  # Check if app is deployed
20
  def is_app_on_streamlit():
21
  """Check whether the app is on streamlit or runs locally."""
 
155
  % (params["about_box_background_color"], contacts_text),
156
  unsafe_allow_html=True,
157
  )
158
+
159
+
160
+ def get_existing_flood_geojson(product_id, output_file_path="./output/"):
161
+ """
162
+ Getting a saved GFM flood geojson in an output folder of GFM files. Merge in one feature group if multiple.
163
+ """
164
+ index_df = pd.read_csv(output_file_path + "index.csv")
165
+ geojson_path = index_df[index_df["product"] == product_id].geojson_path.values[0]
166
+
167
+ # Combine multiple flood files into a FeatureGroup
168
+ flood_geojson_group = folium.FeatureGroup(name=product_id)
169
+
170
+ with open(geojson_path, "r") as f:
171
+ geojson_data = json.load(f)
172
+ flood_layer = folium.GeoJson(geojson_data)
173
+ flood_geojson_group.add_child(flood_layer)
174
+
175
+ return flood_geojson_group
pyproject.toml CHANGED
@@ -11,5 +11,5 @@ dependencies = [
11
  "python-dotenv==1.0.1",
12
  "streamlit>=1.41.1",
13
  "streamlit-folium>=0.24.0",
14
- "ipykernel>=6.29.5",
15
  ]
 
11
  "python-dotenv==1.0.1",
12
  "streamlit>=1.41.1",
13
  "streamlit-folium>=0.24.0",
14
+ "ipykernel>=6.29.5"
15
  ]