TCJ21 commited on
Commit
cc1cab1
Β·
1 Parent(s): eb2388c

mvp of flood map connected to GFM with some local storage for earlier requested areas

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