TCJ21 commited on
Commit
213d2b5
Β·
1 Parent(s): 8b07284

updates containing everything demod 2025-03-12

Browse files
app/pages/0_AOIs.py DELETED
@@ -1,178 +0,0 @@
1
- import json
2
- import os
3
-
4
- import folium
5
- import requests
6
- import streamlit as st
7
- from folium.plugins import Draw
8
- from src.config_parameters import params
9
- from src.gfm import retrieve_all_aois
10
- from src.utils import (
11
- add_about,
12
- set_tool_page_style,
13
- toggle_menu_button,
14
- )
15
- from streamlit_folium import st_folium
16
-
17
- # Page configuration
18
- st.set_page_config(layout="wide", page_title=params["browser_title"])
19
-
20
- # If app is deployed hide menu button
21
- toggle_menu_button()
22
-
23
- # Create sidebar
24
- add_about()
25
-
26
- # Page title
27
- st.markdown("# Flood extent analysis")
28
-
29
- # Set page style
30
- set_tool_page_style()
31
-
32
- # Create two rows: top and bottom panel
33
- row1 = st.container()
34
- save_area = False
35
-
36
- # To track whether radio selector was changed or something else on the page
37
- # To prevent reloading on each map change
38
- if "prev_radio_selection" not in st.session_state:
39
- st.session_state["prev_radio_selection"] = ""
40
-
41
- if "all_aois" not in st.session_state:
42
- st.session_state["all_aois"] = retrieve_all_aois()
43
-
44
-
45
- with row1:
46
- feat_group_selected_area = folium.FeatureGroup(name="selected_area")
47
- radio_selection = st.radio(
48
- label="Action Type",
49
- options=["See Areas", "Create New Area", "Delete Area"],
50
- label_visibility="hidden",
51
- )
52
-
53
- # call to render Folium map in Streamlit
54
- folium_map = folium.Map([39, 0], zoom_start=8)
55
-
56
- if radio_selection == "See Areas":
57
- if st.session_state["prev_radio_selection"] != "See Areas":
58
- st.session_state["all_aois"] = retrieve_all_aois()
59
-
60
- for aoi in st.session_state["all_aois"]:
61
- bbox = aoi["geoJSON"]
62
- feat_group_selected_area.add_child(folium.GeoJson(bbox))
63
-
64
- folium_map.fit_bounds(feat_group_selected_area.get_bounds())
65
- st.session_state["prev_radio_selection"] = "See Areas"
66
-
67
- elif radio_selection == "Create New Area":
68
- Draw(
69
- export=False,
70
- draw_options={
71
- "circle": False,
72
- "polyline": False,
73
- "polygon": False,
74
- "rectangle": True,
75
- "marker": False,
76
- "circlemarker": False,
77
- },
78
- ).add_to(folium_map)
79
-
80
- new_area_name = st.text_input("Area name")
81
- save_area = st.button("Save Area")
82
-
83
- st.session_state["prev_radio_selection"] = "Create New Area"
84
-
85
- elif radio_selection == "Delete Area":
86
- # Load existing bboxes
87
- with open("./bboxes/bboxes.json", "r") as f:
88
- bboxes = json.load(f)
89
- existing_areas = bboxes.keys()
90
-
91
- area_to_delete = st.selectbox("Choose area to delete", options=existing_areas)
92
- bbox = bboxes[area_to_delete]["bounding_box"]
93
- feat_group_selected_area.add_child(folium.GeoJson(bbox))
94
- folium_map.fit_bounds(feat_group_selected_area.get_bounds())
95
-
96
- delete_area = st.button("Delete")
97
-
98
- if delete_area:
99
- with open("./bboxes/bboxes.json", "r") as f:
100
- bboxes = json.load(f)
101
- bboxes.pop(area_to_delete, None)
102
- with open("./bboxes/bboxes.json", "w") as f:
103
- json.dump(bboxes, f)
104
- st.toast("Area successfully deleted")
105
- st.session_state["prev_radio_selection"] = "Delete Area"
106
-
107
- with open("./bboxes/bboxes.json", "r") as f:
108
- bboxes = json.load(f)
109
-
110
- # feat_group_selected_area.add_child(geojson_selected_area)
111
- m = st_folium(
112
- folium_map,
113
- width=800,
114
- height=450,
115
- feature_group_to_add=feat_group_selected_area,
116
- )
117
-
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
- username = os.environ["gfm_username"]
132
- password = os.environ["gfm_password"]
133
- base_url = "https://api.gfm.eodc.eu/v1"
134
-
135
- # Get token, setup header
136
- token_url = f"{base_url}/auth/login"
137
-
138
- payload = {"email": username, "password": password}
139
-
140
- response = requests.post(token_url, json=payload)
141
- user_id = response.json()["client_id"]
142
- access_token = response.json()["access_token"]
143
- header = {"Authorization": f"bearer {access_token}"}
144
-
145
- print("authenticated to API")
146
- # Create area of impact
147
- create_aoi_url = f"{base_url}/aoi/create"
148
-
149
- payload = {
150
- "aoi_name": new_area_name,
151
- "description": new_area_name,
152
- "user_id": user_id,
153
- "geoJSON": {"type": "Polygon", "coordinates": coordinates},
154
- }
155
-
156
- r = requests.post(url=create_aoi_url, json=payload, headers=header)
157
- print(r.json())
158
- print("Posted new AOI")
159
-
160
- print("Writing new area to bbox json")
161
- # Load existing bboxes
162
- with open("./bboxes/bboxes.json", "r") as f:
163
- bboxes = json.load(f)
164
-
165
- # If the area doesn't exist, create it
166
- if new_area_name not in bboxes:
167
- bboxes[new_area_name] = {}
168
-
169
- # Save the new bounding box under the date range key
170
- bboxes[new_area_name] = {
171
- "bounding_box": selected_area_geojson,
172
- "date_ranges": [], # Will be populated when files are downloaded
173
- }
174
- # Write the updated data back to file
175
- with open("./bboxes/bboxes.json", "w") as f:
176
- json.dump(bboxes, f, indent=4)
177
-
178
- 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,86 +1,98 @@
1
- """Documentation page for Streamlit app."""
2
 
3
- import streamlit as st
4
- from src.config_parameters import params
5
- from src.utils import (
6
- add_about,
7
- set_doc_page_style,
8
- toggle_menu_button,
9
- )
 
 
 
 
 
 
10
 
11
- # Page configuration
12
- st.set_page_config(layout="wide", page_title=params["browser_title"])
13
 
14
- # If app is deployed hide menu button
15
- toggle_menu_button()
16
 
17
- # Create sidebar
18
- add_about()
19
 
20
- # Set page style
21
- set_doc_page_style()
 
 
 
22
 
23
- # Page title
24
- st.markdown("# Documentation")
25
 
26
- # First section
27
- st.markdown("## Methodology")
28
- st.markdown(
29
- "TODO: new documentation, only kept in Sentinel 1 section unchanged from the Mapaction tool"
30
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
 
32
 
33
- # Second section
34
- st.markdown("## Radar imagery for flood detection")
35
- st.markdown(
36
- """
37
- While there are multiple change detections techniques for radar imagery,
38
- the one used by Sentinel-1 is one of the simplest. Active radar satellites
39
- produce active radiation directed at the land, and images are formed as a
40
- function of the time it takes for that radiation to reach back to the
41
- satellite. Because of this, radar systems are side-looking (otherwise
42
- radiation from multiple areas would reach back at the same time). To be
43
- detected and imaged, radiation needs to be scattered back, but not all
44
- surfaces are equally able to scatter back, and that ability is also
45
- influenced by the radiation's wavelength (shorter wavelengths are better at
46
- detecting smaller objects, while longer wavelengths allow penetration,
47
- which is good for forest canopies for example, and biomass studies).
48
- Sentinel-1 satellites are C-band (~ 6 cm).<br><br>
49
- Water is characterised by a mirror-like reflection mechanism, meaning that
50
- no or very little radiation is scattered back to the satellite, so pixels
51
- on the image will appear very dark. This very simple change detection takes
52
- a "before" image, and looks for drops in intensity, dark spots, in the
53
- "after" image.<br><br>
54
- Sentinel-1 data is the result of measurements from a constellation of two
55
- satellites, assing over the same areas following the same orbit on average
56
- every 6 days. On Google Earth Engine, the processing level is Ground Range
57
- Detected (GRD), meaning that it has been detected, multi-looked and
58
- projected to ground range using an Earth ellipsoid model. GRD products
59
- report on intensity of radiation, but have lost the phase and amplitude
60
- information which is needed for other applications (interferometry for
61
- example). These satellites emits in different polarizations, and can
62
- acquire both single horizonal or vertical, or dual polarizations. Flood
63
- water is best detected by using VH (vertical transmit and horizontal
64
- receive), although VV (vertical transmit and vertical receive) can be
65
- effective to identify partially submerged features. This tool uses VH
66
- polarization. Figure 2 shows an overview of the Sentinel-1 observation
67
- plan, where pass directions and coverage frequencies are highlighted.
68
- """,
69
- unsafe_allow_html=True,
70
- )
71
 
72
- # Add image satellite overview
73
- st.image(
74
- "%s" % params["url_sentinel_img"],
75
- width=1000,
76
- )
77
- st.markdown(
78
- """
79
- <p style="font-size:%s;">
80
- Figure 2. Overview of the Sentinel-1 observation plan (<a href=
81
- '%s'>source</a>).
82
- </p>
83
- """
84
- % (params["docs_caption_fontsize"], params["url_sentinel_img_location"]),
85
- unsafe_allow_html=True,
86
- )
 
1
+ # """Documentation page for Streamlit app."""
2
 
3
+ # import streamlit as st
4
+ # from src.config_parameters import params
5
+ # from src.utils import (
6
+ # add_about,
7
+ # set_doc_page_style,
8
+ # toggle_menu_button,
9
+ # )
10
+
11
+ # # Page configuration
12
+ # st.set_page_config(layout="wide", page_title=params["browser_title"])
13
+
14
+ # # If app is deployed hide menu button
15
+ # toggle_menu_button()
16
 
17
+ # # Create sidebar
18
+ # add_about()
19
 
20
+ # # Set page style
21
+ # set_doc_page_style()
22
 
23
+ # # Page title
24
+ # st.markdown("# Documentation")
25
 
26
+ # # First section
27
+ # st.markdown("## Methodology")
28
+ # st.markdown(
29
+ # "TODO: new documentation, only kept in Sentinel 1 section unchanged from the Mapaction tool"
30
+ # )
31
 
 
 
32
 
33
+ # # Second section
34
+ # st.markdown("## Radar imagery for flood detection")
35
+ # st.markdown(
36
+ # """
37
+ # While there are multiple change detections techniques for radar imagery,
38
+ # the one used by Sentinel-1 is one of the simplest. Active radar satellites
39
+ # produce active radiation directed at the land, and images are formed as a
40
+ # function of the time it takes for that radiation to reach back to the
41
+ # satellite. Because of this, radar systems are side-looking (otherwise
42
+ # radiation from multiple areas would reach back at the same time). To be
43
+ # detected and imaged, radiation needs to be scattered back, but not all
44
+ # surfaces are equally able to scatter back, and that ability is also
45
+ # influenced by the radiation's wavelength (shorter wavelengths are better at
46
+ # detecting smaller objects, while longer wavelengths allow penetration,
47
+ # which is good for forest canopies for example, and biomass studies).
48
+ # Sentinel-1 satellites are C-band (~ 6 cm).<br><br>
49
+ # Water is characterised by a mirror-like reflection mechanism, meaning that
50
+ # no or very little radiation is scattered back to the satellite, so pixels
51
+ # on the image will appear very dark. This very simple change detection takes
52
+ # a "before" image, and looks for drops in intensity, dark spots, in the
53
+ # "after" image.<br><br>
54
+ # Sentinel-1 data is the result of measurements from a constellation of two
55
+ # satellites, assing over the same areas following the same orbit on average
56
+ # every 6 days. On Google Earth Engine, the processing level is Ground Range
57
+ # Detected (GRD), meaning that it has been detected, multi-looked and
58
+ # projected to ground range using an Earth ellipsoid model. GRD products
59
+ # report on intensity of radiation, but have lost the phase and amplitude
60
+ # information which is needed for other applications (interferometry for
61
+ # example). These satellites emits in different polarizations, and can
62
+ # acquire both single horizonal or vertical, or dual polarizations. Flood
63
+ # water is best detected by using VH (vertical transmit and horizontal
64
+ # receive), although VV (vertical transmit and vertical receive) can be
65
+ # effective to identify partially submerged features. This tool uses VH
66
+ # polarization. Figure 2 shows an overview of the Sentinel-1 observation
67
+ # plan, where pass directions and coverage frequencies are highlighted.
68
+ # """,
69
+ # unsafe_allow_html=True,
70
+ # )
71
+
72
+ # # Add image satellite overview
73
+ # st.image(
74
+ # "%s" % params["url_sentinel_img"],
75
+ # width=1000,
76
+ # )
77
+ # st.markdown(
78
+ # """
79
+ # <p style="font-size:%s;">
80
+ # Figure 2. Overview of the Sentinel-1 observation plan (<a href=
81
+ # '%s'>source</a>).
82
+ # </p>
83
+ # """
84
+ # % (params["docs_caption_fontsize"], params["url_sentinel_img_location"]),
85
+ # unsafe_allow_html=True,
86
+ # )
87
+
88
+ import streamlit as st
89
 
90
+ slider_value = st.slider(label="number of checkboxe", min_value=1, max_value=20)
91
 
92
+ checkboxes = {}
93
+ for i in range(1, slider_value):
94
+ checkbox = st.checkbox(f"Checkbox {i}")
95
+ checkboxes[f"checkbox_{i}"] = checkbox
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ if checkboxes["checkbox_4"]:
98
+ print("checkbox 4 checked")
 
 
 
 
 
 
 
 
 
 
 
 
 
app/src/gfm.py CHANGED
@@ -1,62 +1,20 @@
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,100 +27,141 @@ 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 retrieve_all_aois():
126
- print("Retrieving all AOIs from GFM API")
127
- username = os.environ["gfm_username"]
128
- password = os.environ["gfm_password"]
129
- base_url = "https://api.gfm.eodc.eu/v1"
130
 
131
- # Get token, setup header
132
- token_url = f"{base_url}/auth/login"
133
 
134
- payload = {"email": username, "password": password}
135
 
136
- response = requests.post(token_url, json=payload)
137
- user_id = response.json()["client_id"]
138
- access_token = response.json()["access_token"]
 
 
139
  header = {"Authorization": f"bearer {access_token}"}
140
 
 
 
141
  aoi_url = f"{base_url}/aoi/user/{user_id}"
142
  response = requests.get(aoi_url, headers=header)
143
 
144
  aois = response.json()["aois"]
145
 
 
 
 
 
 
 
 
 
 
146
  return aois
147
 
148
 
149
- def get_existing_flood_geojson(area_name, product_time, output_file_path=None):
150
- """
151
- Getting a saved GFM flood geojson in an output folder of GFM files. Merge in one feature group if multiple.
152
- """
153
- product_time = product_time.replace(":", "_")
154
- if not output_file_path:
155
- output_file_path = f"./output/{area_name}/{product_time}"
156
 
157
- # Combine multiple flood files into a FeatureGroup
158
- flood_geojson_group = folium.FeatureGroup(name=f"{area_name} Floods {product_time}")
 
 
 
159
 
160
- for flood_file in Path(output_file_path).glob("*FLOOD*.geojson"):
161
- with open(flood_file, "r") as f:
162
- geojson_data = json.load(f)
163
- flood_layer = folium.GeoJson(geojson_data)
164
- flood_geojson_group.add_child(flood_layer)
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
- # TODO: consider merging multiple flood layers into one, to avoid overlap
 
 
 
167
 
168
- return flood_geojson_group
 
 
1
  import io
 
2
  import os
3
+ import shutil
4
+ import tempfile
5
  import zipfile
6
  from pathlib import Path
7
 
8
+ import pandas as pd
9
  import requests
10
  from dotenv import load_dotenv
11
 
12
  load_dotenv()
13
 
14
 
15
+ # TODO: These functions should be transformed into a proper class mainly to prevent authentication on each call
16
+ # Authentication
17
+ def get_gfm_user_and_token():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  username = os.environ["gfm_username"]
19
  password = os.environ["gfm_password"]
20
  base_url = "https://api.gfm.eodc.eu/v1"
 
27
  response = requests.post(token_url, json=payload)
28
  user_id = response.json()["client_id"]
29
  access_token = response.json()["access_token"]
30
+ print("retrieved user id and access token")
 
31
 
32
+ return user_id, access_token
33
+
34
+
35
+ # Gets all products for an AOI in a daterange
36
+ def get_area_products(area_id, from_date, to_date):
37
+ user_id, access_token = get_gfm_user_and_token()
38
+ header = {"Authorization": f"bearer {access_token}"}
39
 
40
+ base_url = "https://api.gfm.eodc.eu/v1"
 
 
 
 
41
 
 
42
  params = {
43
  "time": "range",
44
  "from": f"{from_date}T00:00:00",
45
  "to": f"{to_date}T00:00:00",
46
  }
47
+ prod_url = f"{base_url}/aoi/{area_id}/products"
48
  response = requests.get(prod_url, headers=header, params=params)
49
  products = response.json()["products"]
50
+ print(f"Found {len(products)} products for {area_id}")
51
+
52
+ return products
53
+
54
+
55
+ # Will download a product by product_id, saves the flood geojson and updates the index for caching
56
+ def download_flood_product(area_id, product, output_file_path=None):
57
+ user_id, access_token = get_gfm_user_and_token()
58
+ header = {"Authorization": f"bearer {access_token}"}
59
+
60
+ base_url = "https://api.gfm.eodc.eu/v1"
61
 
62
  if not output_file_path:
63
  base_file_path = "./output"
64
 
65
+ product_id = product["product_id"]
66
+ product_time = product["product_time"]
 
67
 
68
+ output_file_path = f"{base_file_path}"
69
+ Path(output_file_path).mkdir(parents=True, exist_ok=True)
 
 
 
 
70
 
71
+ print(f"Downloading product: {product_id}")
72
 
73
+ download_url = f"{base_url}/download/product/{product_id}"
74
+ response = requests.get(download_url, headers=header)
75
+ download_link = response.json()["download_link"]
76
 
77
+ # Download and unzip file
78
+ r = requests.get(download_link)
79
+ buffer = io.BytesIO(r.content)
 
 
80
 
81
+ with tempfile.TemporaryDirectory() as tempdir:
82
+ with zipfile.ZipFile(buffer) as z:
83
+ z.extractall(tempdir)
84
+ for flood_file in Path(tempdir).glob("*FLOOD*.geojson"):
85
+ dest_file = output_file_path + "/" + flood_file.name
86
+ shutil.copy(flood_file, dest_file)
87
 
88
+ df = pd.DataFrame(
89
+ {
90
+ "aoi_id": [area_id],
91
+ "datetime": [product_time],
92
+ "product": [product_id],
93
+ "geojson_path": [dest_file],
94
+ }
95
+ )
96
 
97
+ index_file_path = Path(f"{output_file_path}/index.csv")
98
+ if index_file_path.is_file():
99
+ existing_df = pd.read_csv(index_file_path)
100
+ df = pd.concat([existing_df, df])
 
101
 
102
+ df.to_csv(index_file_path, index=False)
 
103
 
104
+ print(f"Product {product_id} downloaded succesfully")
105
 
106
+
107
+ # Gets all AOIs and transforms them to a dict with some features that are useful in the app, like the bbox
108
+ def retrieve_all_aois():
109
+ print("Retrieving all AOIs from GFM API")
110
+ user_id, access_token = get_gfm_user_and_token()
111
  header = {"Authorization": f"bearer {access_token}"}
112
 
113
+ base_url = "https://api.gfm.eodc.eu/v1"
114
+
115
  aoi_url = f"{base_url}/aoi/user/{user_id}"
116
  response = requests.get(aoi_url, headers=header)
117
 
118
  aois = response.json()["aois"]
119
 
120
+ aois = {
121
+ aoi["aoi_id"]: {
122
+ "name": aoi["aoi_name"],
123
+ "bbox": aoi["geoJSON"],
124
+ "name_id_preview": f"{aoi['aoi_name']} - {aoi['aoi_id'][:6]}...",
125
+ }
126
+ for aoi in aois
127
+ }
128
+
129
  return aois
130
 
131
 
132
+ # Creates AOI on GFM using API
133
+ def create_aoi(new_area_name, coordinates):
134
+ user_id, access_token = get_gfm_user_and_token()
135
+ header = {"Authorization": f"bearer {access_token}"}
 
 
 
136
 
137
+ base_url = "https://api.gfm.eodc.eu/v1"
138
+
139
+ # Create area of impact
140
+ print("Creating new area of impact")
141
+ create_aoi_url = f"{base_url}/aoi/create"
142
 
143
+ payload = {
144
+ "aoi_name": new_area_name,
145
+ "description": new_area_name,
146
+ "user_id": user_id,
147
+ "geoJSON": {"type": "Polygon", "coordinates": coordinates},
148
+ }
149
+
150
+ r = requests.post(url=create_aoi_url, json=payload, headers=header)
151
+ print("Posted new AOI")
152
+
153
+
154
+ # Deletes AOI on GFM using API
155
+ def delete_aoi(aoi_id):
156
+ user_id, access_token = get_gfm_user_and_token()
157
+ header = {"Authorization": f"bearer {access_token}"}
158
+
159
+ base_url = "https://api.gfm.eodc.eu/v1"
160
 
161
+ # Create area of impact
162
+ print(f"Deleting area of impact {aoi_id}")
163
+ delete_aoi_url = f"{base_url}/aoi/delete/id/{aoi_id}"
164
+ print(delete_aoi_url)
165
 
166
+ r = requests.delete(url=delete_aoi_url, headers=header)
167
+ 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
@@ -12,4 +12,5 @@ dependencies = [
12
  "streamlit>=1.41.1",
13
  "streamlit-folium>=0.24.0",
14
  "ipykernel>=6.29.5",
 
15
  ]
 
12
  "streamlit>=1.41.1",
13
  "streamlit-folium>=0.24.0",
14
  "ipykernel>=6.29.5",
15
+ "geojson>=3.2.0",
16
  ]
uv.lock CHANGED
@@ -225,6 +225,7 @@ version = "0.1.0"
225
  source = { virtual = "." }
226
  dependencies = [
227
  { name = "folium" },
 
228
  { name = "geopandas" },
229
  { name = "ipykernel" },
230
  { name = "python-dotenv" },
@@ -236,6 +237,7 @@ dependencies = [
236
  [package.metadata]
237
  requires-dist = [
238
  { name = "folium", specifier = ">=0.19.4" },
 
239
  { name = "geopandas", specifier = ">=1.0.1" },
240
  { name = "ipykernel", specifier = ">=6.29.5" },
241
  { name = "python-dotenv", specifier = "==1.0.1" },
@@ -260,6 +262,15 @@ wheels = [
260
  { url = "https://files.pythonhosted.org/packages/fc/ab/d1f47c48a14e17cd487c8b467b573291fae75477b067241407e7889a3692/folium-0.19.4-py2.py3-none-any.whl", hash = "sha256:bea5246b6a6aa61b96d1c51399dd63254bacbd6ba8a826eeb491f45242032dfd", size = 110511 },
261
  ]
262
 
 
 
 
 
 
 
 
 
 
263
  [[package]]
264
  name = "geopandas"
265
  version = "1.0.1"
 
225
  source = { virtual = "." }
226
  dependencies = [
227
  { name = "folium" },
228
+ { name = "geojson" },
229
  { name = "geopandas" },
230
  { name = "ipykernel" },
231
  { name = "python-dotenv" },
 
237
  [package.metadata]
238
  requires-dist = [
239
  { name = "folium", specifier = ">=0.19.4" },
240
+ { name = "geojson", specifier = ">=3.2.0" },
241
  { name = "geopandas", specifier = ">=1.0.1" },
242
  { name = "ipykernel", specifier = ">=6.29.5" },
243
  { name = "python-dotenv", specifier = "==1.0.1" },
 
262
  { url = "https://files.pythonhosted.org/packages/fc/ab/d1f47c48a14e17cd487c8b467b573291fae75477b067241407e7889a3692/folium-0.19.4-py2.py3-none-any.whl", hash = "sha256:bea5246b6a6aa61b96d1c51399dd63254bacbd6ba8a826eeb491f45242032dfd", size = 110511 },
263
  ]
264
 
265
+ [[package]]
266
+ name = "geojson"
267
+ version = "3.2.0"
268
+ source = { registry = "https://pypi.org/simple" }
269
+ sdist = { url = "https://files.pythonhosted.org/packages/85/5a/33e761df75c732fcea94aaf01f993d823138581d10c91133da58bc231e63/geojson-3.2.0.tar.gz", hash = "sha256:b860baba1e8c6f71f8f5f6e3949a694daccf40820fa8f138b3f712bd85804903", size = 24574 }
270
+ wheels = [
271
+ { url = "https://files.pythonhosted.org/packages/18/67/a7fa2d650602731c90e0a86279841b4586e14228199e8c09165ba4863e29/geojson-3.2.0-py3-none-any.whl", hash = "sha256:69d14156469e13c79479672eafae7b37e2dcd19bdfd77b53f74fa8fe29910b52", size = 15040 },
272
+ ]
273
+
274
  [[package]]
275
  name = "geopandas"
276
  version = "1.0.1"