Zeel commited on
Commit
6e56783
1 Parent(s): 64cc170

show buffer correctly

Browse files
Files changed (1) hide show
  1. app.py +147 -84
app.py CHANGED
@@ -15,13 +15,16 @@ import plotly.express as px
15
  import branca.colormap as cm
16
 
17
  st.set_page_config(layout="wide")
18
- m = st.markdown("""
 
19
  <style>
20
  div.stButton > button:first-child {
21
  background-color: #006400;
22
  color:#ffffff;
23
  }
24
- </style>""", unsafe_allow_html=True)
 
 
25
 
26
  # Logo
27
  cols = st.columns([1, 7, 1])
@@ -42,7 +45,7 @@ with cols[1]:
42
 
43
  ############################################
44
  # Hyperparameters
45
- ############################################
46
  st.write("<h2><div style='text-align: center;'>User Inputs</div></h2>", unsafe_allow_html=True)
47
 
48
  st.write("Select the vegetation indices to calculate:")
@@ -63,9 +66,10 @@ with st.expander("EVI/EVI2 Parameters"):
63
  cols = st.columns(5)
64
  evi_vars = {}
65
  for col, name, default in zip(cols, ["G", "C1", "C2", "L", "C"], [2.5, 6, 7.5, 1, 2.4]):
66
- value = col.number_input(f'{name}', value=default)
67
  evi_vars[name] = value
68
 
 
69
  ############################################
70
  # Functions
71
  ############################################
@@ -75,82 +79,109 @@ def daterange_str_to_dates(daterange_str):
75
  end_date = pd.to_datetime(end_date)
76
  return start_date, end_date
77
 
 
78
  def daterange_dates_to_str(start_date, end_date):
79
  return f"{start_date.strftime('%Y/%m/%d')}-{end_date.strftime('%Y/%m/%d')}"
80
 
 
81
  def daterange_str_to_year(daterange_str):
82
  start_date, _ = daterange_str.split("-")
83
  year = pd.to_datetime(start_date).year
84
  return year
85
 
 
86
  def shape_3d_to_2d(shape):
87
  if shape.has_z:
88
  return transform(lambda x, y, z: (x, y), shape)
89
  else:
90
  return shape
91
 
92
- def preprocess_gdf(gdf):
93
- gdf = gdf.to_crs(epsg=7761) # epsg for Gujarat
94
 
 
 
95
 
96
  gdf["geometry"] = gdf["geometry"].apply(shape_3d_to_2d)
97
  return gdf
98
 
 
99
  def check_valid_geometry(geometry_gdf):
100
  geometry = geometry_gdf.geometry.item()
101
  if geometry.type != "Polygon":
102
- st.error(
103
- f"Selected geometry is of type '{geometry.type}'. Please provide a 'Polygon' geometry."
104
- )
105
  st.stop()
106
-
107
- def add_geometry_to_maps(map_list):
 
108
  for m in map_list:
109
- m.add_gdf(buffer_geometry_gdf, layer_name="Geometry Buffer", style_function=lambda x: {"color": "red", "fillOpacity": 0.0})
110
- m.add_gdf(geometry_gdf, layer_name="Geometry", style_function=lambda x: {"color": "blue", "fillOpacity": 0.0})
 
 
 
 
 
 
 
 
 
111
 
112
  def get_dem_slope_maps(buffer_ee_geometry):
113
  # Create the map for DEM
114
  dem_map = gee_folium.Map()
115
- dem_map.add_tile_layer(wayback_mapping[latest_date], name=f"Esri Wayback - {latest_date.replace('-', '/')}", attribution="Esri")
 
 
116
 
117
- dem_layer = ee.Image("USGS/SRTMGL1_003")
118
  # Set the target resolution to 10 meters
119
  target_resolution = 10
120
- dem_layer = dem_layer.resample('bilinear').reproject(crs='EPSG:4326', scale=target_resolution).clip(buffer_ee_geometry)
 
 
121
 
122
  # Generate contour lines using elevation thresholds
123
  terrain = ee.Algorithms.Terrain(dem_layer)
124
  contour_interval = 1
125
- contours = terrain.select('elevation').subtract(terrain.select('elevation').mod(contour_interval)).rename('contours')
 
 
126
 
127
  # Calculate the minimum and maximum values
128
- stats = contours.reduceRegion(reducer=ee.Reducer.minMax(),scale=30,maxPixels=1e13)
129
- max_value = stats.get('contours_max').getInfo()
130
- min_value = stats.get('contours_min').getInfo()
131
  vis_params = {"min": min_value, "max": max_value, "palette": ["blue", "green", "yellow", "red"]}
132
  dem_map.addLayer(contours, vis_params, "Contours")
133
  # Create a colormap
134
- colormap = cm.LinearColormap(colors=vis_params['palette'], vmin=vis_params['min'], vmax=vis_params['max'])
135
  dem_map.add_child(colormap)
136
-
137
  # Create the map for Slope
138
  slope_map = gee_folium.Map()
139
- slope_map.add_tile_layer(wayback_mapping[latest_date], name=f"Esri Wayback - {latest_date.replace('-', '/')}", attribution="Esri")
 
 
140
 
141
  # Calculate slope from the DEM
142
- slope_layer = ee.Terrain.slope(ee.Image("USGS/SRTMGL1_003").resample('bilinear').reproject(crs='EPSG:4326', scale=target_resolution)).clip(buffer_ee_geometry).rename('slope')
 
 
 
 
 
 
143
  # Calculate the minimum and maximum values
144
- stats = slope_layer.reduceRegion(reducer=ee.Reducer.minMax(),scale=30,maxPixels=1e13)
145
- max_value = stats.get('slope_max').getInfo()
146
- min_value = stats.get('slope_min').getInfo()
147
  vis_params = {"min": min_value, "max": max_value, "palette": ["blue", "green", "yellow", "red"]}
148
  slope_map.addLayer(slope_layer, vis_params, "Slope Layer")
149
  # Create a colormap
150
- colormap = cm.LinearColormap(colors=vis_params['palette'], vmin=vis_params['min'], vmax=vis_params['max'])
151
  slope_map.add_child(colormap)
152
  return dem_map, slope_map
153
-
 
154
  def add_indices(image, nir_band, red_band, blue_band):
155
  # Add negative cloud
156
  neg_cloud = image.select("MSK_CLDPRB").multiply(-1).rename("Neg_MSK_CLDPRB")
@@ -159,13 +190,19 @@ def add_indices(image, nir_band, red_band, blue_band):
159
  blue = image.select(blue_band).divide(10000)
160
  numerator = nir.subtract(red)
161
  ndvi = (numerator).divide(nir.add(red)).rename("NDVI").clamp(-1, 1)
162
- # EVI formula taken from: https://en.wikipedia.org/wiki/Enhanced_vegetation_index
163
-
164
- denominator = nir.add(red.multiply(evi_vars['C1'])).subtract(blue.multiply(evi_vars['C2'])).add(evi_vars['L'])
165
- evi = numerator.divide(denominator).multiply(evi_vars['G']).rename("EVI").clamp(-1, 1)
166
- evi2 = numerator.divide(nir.add(evi_vars['L']).add(red.multiply(evi_vars['C']))).multiply(evi_vars['G']).rename("EVI2").clamp(-1, 1)
 
 
 
 
 
167
  return image.addBands([neg_cloud, ndvi, evi, evi2])
168
 
 
169
  def process_date(daterange, satellite, veg_indices):
170
  start_date, end_date = daterange
171
  daterange_str = daterange_dates_to_str(start_date, end_date)
@@ -175,7 +212,7 @@ def process_date(daterange, satellite, veg_indices):
175
  collection = attrs["collection"]
176
  collection = collection.filterBounds(buffer_ee_geometry)
177
  collection = collection.filterDate(start_date, end_date)
178
-
179
  bucket = {}
180
  for veg_index in veg_indices:
181
  mosaic_veg_index = collection.qualityMosaic(veg_index)
@@ -190,12 +227,12 @@ def process_date(daterange, satellite, veg_indices):
190
  buffer_mean_veg_index = fc["features"][0]["properties"][veg_index]
191
  bucket[f"{veg_index}_buffer"] = buffer_mean_veg_index
192
  bucket[f"{veg_index}_ratio"] = mean_veg_index / buffer_mean_veg_index
193
- bucket[f"mosaic_{veg_index}"] = mosaic_veg_index
194
-
195
  # Get median mosaic
196
  bucket["mosaic_visual_max_ndvi"] = collection.qualityMosaic("NDVI")
197
  bucket["mosaic_visual_median"] = collection.median()
198
- bucket["image_visual_least_cloud"] = collection.sort('CLOUDY_PIXEL_PERCENTAGE').first()
199
 
200
  if satellite == "COPERNICUS/S2_SR_HARMONIZED":
201
  cloud_mask_probability = fc["features"][0]["properties"]["MSK_CLDPRB"] / 100
@@ -211,6 +248,7 @@ def process_date(daterange, satellite, veg_indices):
211
  suffix = f" - Imagery not available"
212
  write_info(f"{prefix}{suffix}")
213
 
 
214
  def write_info(info):
215
  st.write(f"<span style='color:#006400;'>{info}</span>", unsafe_allow_html=True)
216
 
@@ -251,6 +289,7 @@ def one_time_setup():
251
  with open("wayback_imagery.json") as f:
252
  st.session_state.wayback_mapping = json.load(f)
253
 
 
254
  if "one_time_setup_done" not in st.session_state:
255
  one_time_setup()
256
  st.session_state.one_time_setup_done = True
@@ -274,7 +313,9 @@ jan_1 = pd.to_datetime(f"{max_year}/01/01", format="%Y/%m/%d")
274
  dec_31 = pd.to_datetime(f"{max_year}/12/31", format="%Y/%m/%d")
275
  nov_15 = pd.to_datetime(f"{max_year}/11/15", format="%Y/%m/%d")
276
  dec_15 = pd.to_datetime(f"{max_year}/12/15", format="%Y/%m/%d")
277
- input_daterange = st.date_input("Date Range (Ignore year. App will compute indices for all possible years)", (nov_15, dec_15), jan_1, dec_31)
 
 
278
  min_year = int(st.number_input("Minimum Year", value=2019, min_value=2015, step=1))
279
  max_year = int(st.number_input("Maximum Year", value=max_year, min_value=2015, step=1))
280
 
@@ -298,34 +339,45 @@ buffer = st.number_input("Buffer (m)", value=50, min_value=0, step=1)
298
 
299
  input_gdf = preprocess_gdf(gpd.read_file(file_url))
300
 
 
301
  # Input: Geometry
302
  def format_fn(x):
303
  return input_gdf.drop(columns=["geometry"]).loc[x].to_dict()
 
 
304
  input_geometry_idx = st.selectbox("Select the geometry", input_gdf.index, format_func=format_fn)
305
  geometry_gdf = input_gdf[input_gdf.index == input_geometry_idx]
306
- buffer_geometry_gdf = geometry_gdf.copy()
307
- buffer_geometry_gdf["geometry"] = buffer_geometry_gdf["geometry"].buffer(buffer)
308
  check_valid_geometry(geometry_gdf)
309
 
 
 
 
 
 
 
 
310
  # Derived Inputs
311
  ee_geometry = ee.Geometry(geometry_gdf.to_crs(4326).geometry.item().__geo_interface__)
312
  ee_feature_collection = ee.FeatureCollection(ee_geometry)
313
  buffer_ee_geometry = ee.Geometry(buffer_geometry_gdf.to_crs(4326).geometry.item().__geo_interface__)
314
- buffer_ee_geometry = buffer_ee_geometry.difference(ee_geometry)
315
  buffer_ee_feature_collection = ee.FeatureCollection(buffer_ee_geometry)
316
 
317
  # visualize the geometry
318
  m = leaf_folium.Map()
319
  keys = list(wayback_mapping.keys())
320
  latest_date = sorted(keys, key=lambda x: pd.to_datetime(x))[-1]
321
- m.add_tile_layer(wayback_mapping[latest_date], name=f"Esri Wayback - {latest_date.replace('-', '/')}", attribution="Esri")
322
- #m.add_layer(buffer_ee_feature_collection)
323
- add_geometry_to_maps([m])
324
- write_info(f"""
 
 
 
325
  <div style="text-align: center;">
326
  Latest Esri Imagery - {latest_date.replace('-', '/')}
327
  </div>
328
- """)
 
329
  m.to_streamlit()
330
 
331
  # Generate stats
@@ -333,16 +385,18 @@ stats_df = pd.DataFrame(
333
  {
334
  "Area (m^2)": geometry_gdf.area.item(),
335
  "Perimeter (m)": geometry_gdf.length.item(),
336
- "Points": json.loads(geometry_gdf.to_crs(4326).to_json())['features'][0]['geometry']['coordinates'],
337
  }
338
  )
339
  st.write("<h3><div style='text-align: center;'>Geometry Metrics</div></h3>", unsafe_allow_html=True)
340
- st.markdown(f"""| Metric | Value |
 
341
  | --- | --- |
342
  | Area (m^2) | {stats_df['Area (m^2)'].item():.2f} m^2 = {stats_df['Area (m^2)'].item()/10000:.2f} ha |
343
  | Perimeter (m) | {stats_df['Perimeter (m)'].item():.2f} m |
344
  | Points | {stats_df['Points'][0]} |
345
- """)
 
346
 
347
  stats_csv = stats_df.to_csv(index=False)
348
  st.download_button("Download Geometry Metrics", stats_csv, "geometry_metrics.csv", "text/csv", use_container_width=True)
@@ -362,13 +416,13 @@ if submit:
362
  start_month = input_daterange[0].month
363
  end_day = input_daterange[1].day
364
  end_month = input_daterange[1].month
365
-
366
  dates = []
367
- for year in range(min_year, max_year+1):
368
  start_date = pd.to_datetime(f"{year}-{start_month:02d}-{start_day:02d}")
369
  end_date = pd.to_datetime(f"{year}-{end_month:02d}-{end_day:02d}")
370
  dates.append((start_date, end_date))
371
-
372
  result_df = pd.DataFrame()
373
  for satellite, attrs in satellites.items():
374
  if not satellite_selected[satellite]:
@@ -386,7 +440,6 @@ print("Printing result...")
386
  if "result" in st.session_state:
387
  result_df = st.session_state.result
388
  print(result_df.columns)
389
-
390
 
391
  # drop rows with all NaN values
392
  result_df = result_df.dropna(how="all")
@@ -394,40 +447,42 @@ if "result" in st.session_state:
394
  result_df = result_df.dropna(axis=1, how="all")
395
  print(result_df.columns)
396
  print(result_df.head(2))
397
-
398
  # df.reset_index(inplace=True)
399
  # df.index = pd.to_datetime(df["index"], format="%Y-%m")
400
  for column in result_df.columns:
401
  result_df[column] = pd.to_numeric(result_df[column], errors="ignore")
402
-
403
  df_numeric = result_df.select_dtypes(include=["float64"])
404
  st.write(df_numeric)
405
-
406
  df_numeric_csv = df_numeric.to_csv(index=True)
407
- st.download_button("Download Time Series Data", df_numeric_csv, "vegetation_indices.csv", "text/csv", use_container_width=True)
408
-
 
 
409
  df_numeric.index = [daterange_str_to_year(daterange) for daterange in df_numeric.index]
410
  for veg_index in veg_indices:
411
  fig = px.line(df_numeric, y=[veg_index, f"{veg_index}_buffer"], markers=True)
412
  fig.update_layout(xaxis=dict(tickvals=df_numeric.index, ticktext=df_numeric.index))
413
  st.plotly_chart(fig)
414
 
415
- st.write("<h3><div style='text-align: center;'>Visual Comparison between Two Years</div></h3>", unsafe_allow_html=True)
 
 
416
  cols = st.columns(2)
417
 
418
  with cols[0]:
419
  year_1 = st.selectbox("Year 1", result_df.index, index=0, format_func=lambda x: daterange_str_to_year(x))
420
  with cols[1]:
421
- year_2 = st.selectbox("Year 2", result_df.index, index=len(result_df.index) - 1, format_func=lambda x: daterange_str_to_year(x))
422
-
423
- vis_params = {'min': 0, 'max': 1, 'palette': ['white', 'green']} # Example visualisation for Sentinel-2
 
 
424
 
425
  # Create a colormap and name it as NDVI
426
- colormap = cm.LinearColormap(
427
- colors=vis_params['palette'],
428
- vmin=vis_params['min'],
429
- vmax=vis_params['max']
430
- )
431
 
432
  for veg_index in veg_indices:
433
  st.write(f"<h3><div style='text-align: center;'>{veg_index}</div></h3>", unsafe_allow_html=True)
@@ -437,23 +492,24 @@ if "result" in st.session_state:
437
  with col:
438
  m = gee_folium.Map()
439
  veg_index_layer = gee_folium.ee_tile_layer(mosaic, {"bands": [veg_index], "min": 0, "max": 1})
440
-
441
  if satellite == "COPERNICUS/S2_SR_HARMONIZED":
442
  min_all = 0
443
  max_all = 255
444
  else:
445
  raise ValueError(f"Unknown satellite: {satellite}")
446
 
447
- m.add_layer(
448
- mosaic.select(veg_index), vis_params
449
- )
450
  # add colorbar
451
  # m.add_colorbar(colors=["#000000", "#00FF00"], vmin=0.0, vmax=1.0)
452
  add_geometry_to_maps([m])
453
  m.add_child(colormap)
454
  m.to_streamlit()
455
 
456
- for name, key in zip(["RGB (Least Cloud Tile Crop)", "RGB (Max NDVI Mosaic)", "RGB (Median Mosaic)"], ["image_visual_least_cloud", "mosaic_visual_max_ndvi", "mosaic_visual_median"]):
 
 
 
457
  st.write(f"<h3><div style='text-align: center;'>{name}</div></h3>", unsafe_allow_html=True)
458
  cols = st.columns(2)
459
  for col, daterange_str in zip(cols, [year_1, year_2]):
@@ -464,13 +520,11 @@ if "result" in st.session_state:
464
  m = gee_folium.Map()
465
  visual_mosaic = result_df.loc[daterange_str, key]
466
  # visual_layer = gee_folium.ee_tile_layer(mosaic, {"bands": ["R", "G", "B"], "min": min_all, "max": max_all})
467
-
468
- m.add_layer(
469
- visual_mosaic.select(["R", "G", "B"])
470
- )
471
  add_geometry_to_maps([m])
472
  m.to_streamlit()
473
-
474
  st.write("<h3><div style='text-align: center;'>Esri RGB Imagery</div></h3>", unsafe_allow_html=True)
475
  cols = st.columns(2)
476
  for col, daterange_str in zip(cols, [year_1, year_2]):
@@ -481,22 +535,31 @@ if "result" in st.session_state:
481
  m = leaf_folium.Map()
482
  m.add_tile_layer(wayback_mapping[esri_date], name=f"Esri Wayback Imagery - {esri_date}", attribution="Esri")
483
  add_geometry_to_maps([m])
484
- write_info(f"""
 
485
  <div style="text-align: center;">
486
  Esri Imagery - {esri_date.replace('-', '/')}
487
  </div>
488
- """)
 
489
  m.to_streamlit()
490
 
491
- st.write("<h3><div style='text-align: center;'>DEM and Slope from SRTM at 30m resolution</div></h3>", unsafe_allow_html=True)
 
 
 
492
  cols = st.columns(2)
493
- dem_map, slope_map = get_dem_slope_maps(ee.Geometry(buffer_geometry_gdf.to_crs(4326).geometry.item().__geo_interface__))
 
 
494
  for col, param_map, title in zip(cols, [dem_map, slope_map], ["DEM Map", "Slope Map"]):
495
  with col:
496
  add_geometry_to_maps([param_map])
497
- write_info(f"""
 
498
  <div style="text-align: center;">
499
  {title}
500
  </div>
501
- """)
 
502
  param_map.to_streamlit()
 
15
  import branca.colormap as cm
16
 
17
  st.set_page_config(layout="wide")
18
+ m = st.markdown(
19
+ """
20
  <style>
21
  div.stButton > button:first-child {
22
  background-color: #006400;
23
  color:#ffffff;
24
  }
25
+ </style>""",
26
+ unsafe_allow_html=True,
27
+ )
28
 
29
  # Logo
30
  cols = st.columns([1, 7, 1])
 
45
 
46
  ############################################
47
  # Hyperparameters
48
+ ############################################
49
  st.write("<h2><div style='text-align: center;'>User Inputs</div></h2>", unsafe_allow_html=True)
50
 
51
  st.write("Select the vegetation indices to calculate:")
 
66
  cols = st.columns(5)
67
  evi_vars = {}
68
  for col, name, default in zip(cols, ["G", "C1", "C2", "L", "C"], [2.5, 6, 7.5, 1, 2.4]):
69
+ value = col.number_input(f"{name}", value=default)
70
  evi_vars[name] = value
71
 
72
+
73
  ############################################
74
  # Functions
75
  ############################################
 
79
  end_date = pd.to_datetime(end_date)
80
  return start_date, end_date
81
 
82
+
83
  def daterange_dates_to_str(start_date, end_date):
84
  return f"{start_date.strftime('%Y/%m/%d')}-{end_date.strftime('%Y/%m/%d')}"
85
 
86
+
87
  def daterange_str_to_year(daterange_str):
88
  start_date, _ = daterange_str.split("-")
89
  year = pd.to_datetime(start_date).year
90
  return year
91
 
92
+
93
  def shape_3d_to_2d(shape):
94
  if shape.has_z:
95
  return transform(lambda x, y, z: (x, y), shape)
96
  else:
97
  return shape
98
 
 
 
99
 
100
+ def preprocess_gdf(gdf):
101
+ gdf = gdf.to_crs(epsg=7761) # epsg for Gujarat
102
 
103
  gdf["geometry"] = gdf["geometry"].apply(shape_3d_to_2d)
104
  return gdf
105
 
106
+
107
  def check_valid_geometry(geometry_gdf):
108
  geometry = geometry_gdf.geometry.item()
109
  if geometry.type != "Polygon":
110
+ st.error(f"Selected geometry is of type '{geometry.type}'. Please provide a 'Polygon' geometry.")
 
 
111
  st.stop()
112
+
113
+
114
+ def add_geometry_to_maps(map_list, opacity=0.0):
115
  for m in map_list:
116
+ m.add_gdf(
117
+ buffer_geometry_gdf,
118
+ layer_name="Geometry Buffer",
119
+ style_function=lambda x: {"color": "red", "fillOpacity": opacity, "fillColor": "red"},
120
+ )
121
+ m.add_gdf(
122
+ geometry_gdf,
123
+ layer_name="Geometry",
124
+ style_function=lambda x: {"color": "blue", "fillOpacity": opacity, "fillColor": "blue"},
125
+ )
126
+
127
 
128
  def get_dem_slope_maps(buffer_ee_geometry):
129
  # Create the map for DEM
130
  dem_map = gee_folium.Map()
131
+ dem_map.add_tile_layer(
132
+ wayback_mapping[latest_date], name=f"Esri Wayback - {latest_date.replace('-', '/')}", attribution="Esri"
133
+ )
134
 
135
+ dem_layer = ee.Image("USGS/SRTMGL1_003")
136
  # Set the target resolution to 10 meters
137
  target_resolution = 10
138
+ dem_layer = (
139
+ dem_layer.resample("bilinear").reproject(crs="EPSG:4326", scale=target_resolution).clip(buffer_ee_geometry)
140
+ )
141
 
142
  # Generate contour lines using elevation thresholds
143
  terrain = ee.Algorithms.Terrain(dem_layer)
144
  contour_interval = 1
145
+ contours = (
146
+ terrain.select("elevation").subtract(terrain.select("elevation").mod(contour_interval)).rename("contours")
147
+ )
148
 
149
  # Calculate the minimum and maximum values
150
+ stats = contours.reduceRegion(reducer=ee.Reducer.minMax(), scale=30, maxPixels=1e13)
151
+ max_value = stats.get("contours_max").getInfo()
152
+ min_value = stats.get("contours_min").getInfo()
153
  vis_params = {"min": min_value, "max": max_value, "palette": ["blue", "green", "yellow", "red"]}
154
  dem_map.addLayer(contours, vis_params, "Contours")
155
  # Create a colormap
156
+ colormap = cm.LinearColormap(colors=vis_params["palette"], vmin=vis_params["min"], vmax=vis_params["max"])
157
  dem_map.add_child(colormap)
158
+
159
  # Create the map for Slope
160
  slope_map = gee_folium.Map()
161
+ slope_map.add_tile_layer(
162
+ wayback_mapping[latest_date], name=f"Esri Wayback - {latest_date.replace('-', '/')}", attribution="Esri"
163
+ )
164
 
165
  # Calculate slope from the DEM
166
+ slope_layer = (
167
+ ee.Terrain.slope(
168
+ ee.Image("USGS/SRTMGL1_003").resample("bilinear").reproject(crs="EPSG:4326", scale=target_resolution)
169
+ )
170
+ .clip(buffer_ee_geometry)
171
+ .rename("slope")
172
+ )
173
  # Calculate the minimum and maximum values
174
+ stats = slope_layer.reduceRegion(reducer=ee.Reducer.minMax(), scale=30, maxPixels=1e13)
175
+ max_value = stats.get("slope_max").getInfo()
176
+ min_value = stats.get("slope_min").getInfo()
177
  vis_params = {"min": min_value, "max": max_value, "palette": ["blue", "green", "yellow", "red"]}
178
  slope_map.addLayer(slope_layer, vis_params, "Slope Layer")
179
  # Create a colormap
180
+ colormap = cm.LinearColormap(colors=vis_params["palette"], vmin=vis_params["min"], vmax=vis_params["max"])
181
  slope_map.add_child(colormap)
182
  return dem_map, slope_map
183
+
184
+
185
  def add_indices(image, nir_band, red_band, blue_band):
186
  # Add negative cloud
187
  neg_cloud = image.select("MSK_CLDPRB").multiply(-1).rename("Neg_MSK_CLDPRB")
 
190
  blue = image.select(blue_band).divide(10000)
191
  numerator = nir.subtract(red)
192
  ndvi = (numerator).divide(nir.add(red)).rename("NDVI").clamp(-1, 1)
193
+ # EVI formula taken from: https://en.wikipedia.org/wiki/Enhanced_vegetation_index
194
+
195
+ denominator = nir.add(red.multiply(evi_vars["C1"])).subtract(blue.multiply(evi_vars["C2"])).add(evi_vars["L"])
196
+ evi = numerator.divide(denominator).multiply(evi_vars["G"]).rename("EVI").clamp(-1, 1)
197
+ evi2 = (
198
+ numerator.divide(nir.add(evi_vars["L"]).add(red.multiply(evi_vars["C"])))
199
+ .multiply(evi_vars["G"])
200
+ .rename("EVI2")
201
+ .clamp(-1, 1)
202
+ )
203
  return image.addBands([neg_cloud, ndvi, evi, evi2])
204
 
205
+
206
  def process_date(daterange, satellite, veg_indices):
207
  start_date, end_date = daterange
208
  daterange_str = daterange_dates_to_str(start_date, end_date)
 
212
  collection = attrs["collection"]
213
  collection = collection.filterBounds(buffer_ee_geometry)
214
  collection = collection.filterDate(start_date, end_date)
215
+
216
  bucket = {}
217
  for veg_index in veg_indices:
218
  mosaic_veg_index = collection.qualityMosaic(veg_index)
 
227
  buffer_mean_veg_index = fc["features"][0]["properties"][veg_index]
228
  bucket[f"{veg_index}_buffer"] = buffer_mean_veg_index
229
  bucket[f"{veg_index}_ratio"] = mean_veg_index / buffer_mean_veg_index
230
+ bucket[f"mosaic_{veg_index}"] = mosaic_veg_index
231
+
232
  # Get median mosaic
233
  bucket["mosaic_visual_max_ndvi"] = collection.qualityMosaic("NDVI")
234
  bucket["mosaic_visual_median"] = collection.median()
235
+ bucket["image_visual_least_cloud"] = collection.sort("CLOUDY_PIXEL_PERCENTAGE").first()
236
 
237
  if satellite == "COPERNICUS/S2_SR_HARMONIZED":
238
  cloud_mask_probability = fc["features"][0]["properties"]["MSK_CLDPRB"] / 100
 
248
  suffix = f" - Imagery not available"
249
  write_info(f"{prefix}{suffix}")
250
 
251
+
252
  def write_info(info):
253
  st.write(f"<span style='color:#006400;'>{info}</span>", unsafe_allow_html=True)
254
 
 
289
  with open("wayback_imagery.json") as f:
290
  st.session_state.wayback_mapping = json.load(f)
291
 
292
+
293
  if "one_time_setup_done" not in st.session_state:
294
  one_time_setup()
295
  st.session_state.one_time_setup_done = True
 
313
  dec_31 = pd.to_datetime(f"{max_year}/12/31", format="%Y/%m/%d")
314
  nov_15 = pd.to_datetime(f"{max_year}/11/15", format="%Y/%m/%d")
315
  dec_15 = pd.to_datetime(f"{max_year}/12/15", format="%Y/%m/%d")
316
+ input_daterange = st.date_input(
317
+ "Date Range (Ignore year. App will compute indices for all possible years)", (nov_15, dec_15), jan_1, dec_31
318
+ )
319
  min_year = int(st.number_input("Minimum Year", value=2019, min_value=2015, step=1))
320
  max_year = int(st.number_input("Maximum Year", value=max_year, min_value=2015, step=1))
321
 
 
339
 
340
  input_gdf = preprocess_gdf(gpd.read_file(file_url))
341
 
342
+
343
  # Input: Geometry
344
  def format_fn(x):
345
  return input_gdf.drop(columns=["geometry"]).loc[x].to_dict()
346
+
347
+
348
  input_geometry_idx = st.selectbox("Select the geometry", input_gdf.index, format_func=format_fn)
349
  geometry_gdf = input_gdf[input_gdf.index == input_geometry_idx]
 
 
350
  check_valid_geometry(geometry_gdf)
351
 
352
+ outer_geometry_gdf = geometry_gdf.copy()
353
+ outer_geometry_gdf["geometry"] = outer_geometry_gdf["geometry"].buffer(buffer)
354
+ buffer_geometry_gdf = (
355
+ outer_geometry_gdf.difference(geometry_gdf).reset_index().drop(columns="index")
356
+ ) # reset index forces GeoSeries to GeoDataFrame
357
+ buffer_geometry_gdf["Name"] = "Buffer"
358
+
359
  # Derived Inputs
360
  ee_geometry = ee.Geometry(geometry_gdf.to_crs(4326).geometry.item().__geo_interface__)
361
  ee_feature_collection = ee.FeatureCollection(ee_geometry)
362
  buffer_ee_geometry = ee.Geometry(buffer_geometry_gdf.to_crs(4326).geometry.item().__geo_interface__)
 
363
  buffer_ee_feature_collection = ee.FeatureCollection(buffer_ee_geometry)
364
 
365
  # visualize the geometry
366
  m = leaf_folium.Map()
367
  keys = list(wayback_mapping.keys())
368
  latest_date = sorted(keys, key=lambda x: pd.to_datetime(x))[-1]
369
+ m.add_tile_layer(
370
+ wayback_mapping[latest_date], name=f"Esri Wayback - {latest_date.replace('-', '/')}", attribution="Esri"
371
+ )
372
+ # m.add_layer(buffer_ee_feature_collection)
373
+ add_geometry_to_maps([m], opacity=0.3)
374
+ write_info(
375
+ f"""
376
  <div style="text-align: center;">
377
  Latest Esri Imagery - {latest_date.replace('-', '/')}
378
  </div>
379
+ """
380
+ )
381
  m.to_streamlit()
382
 
383
  # Generate stats
 
385
  {
386
  "Area (m^2)": geometry_gdf.area.item(),
387
  "Perimeter (m)": geometry_gdf.length.item(),
388
+ "Points": json.loads(geometry_gdf.to_crs(4326).to_json())["features"][0]["geometry"]["coordinates"],
389
  }
390
  )
391
  st.write("<h3><div style='text-align: center;'>Geometry Metrics</div></h3>", unsafe_allow_html=True)
392
+ st.markdown(
393
+ f"""| Metric | Value |
394
  | --- | --- |
395
  | Area (m^2) | {stats_df['Area (m^2)'].item():.2f} m^2 = {stats_df['Area (m^2)'].item()/10000:.2f} ha |
396
  | Perimeter (m) | {stats_df['Perimeter (m)'].item():.2f} m |
397
  | Points | {stats_df['Points'][0]} |
398
+ """
399
+ )
400
 
401
  stats_csv = stats_df.to_csv(index=False)
402
  st.download_button("Download Geometry Metrics", stats_csv, "geometry_metrics.csv", "text/csv", use_container_width=True)
 
416
  start_month = input_daterange[0].month
417
  end_day = input_daterange[1].day
418
  end_month = input_daterange[1].month
419
+
420
  dates = []
421
+ for year in range(min_year, max_year + 1):
422
  start_date = pd.to_datetime(f"{year}-{start_month:02d}-{start_day:02d}")
423
  end_date = pd.to_datetime(f"{year}-{end_month:02d}-{end_day:02d}")
424
  dates.append((start_date, end_date))
425
+
426
  result_df = pd.DataFrame()
427
  for satellite, attrs in satellites.items():
428
  if not satellite_selected[satellite]:
 
440
  if "result" in st.session_state:
441
  result_df = st.session_state.result
442
  print(result_df.columns)
 
443
 
444
  # drop rows with all NaN values
445
  result_df = result_df.dropna(how="all")
 
447
  result_df = result_df.dropna(axis=1, how="all")
448
  print(result_df.columns)
449
  print(result_df.head(2))
450
+
451
  # df.reset_index(inplace=True)
452
  # df.index = pd.to_datetime(df["index"], format="%Y-%m")
453
  for column in result_df.columns:
454
  result_df[column] = pd.to_numeric(result_df[column], errors="ignore")
455
+
456
  df_numeric = result_df.select_dtypes(include=["float64"])
457
  st.write(df_numeric)
458
+
459
  df_numeric_csv = df_numeric.to_csv(index=True)
460
+ st.download_button(
461
+ "Download Time Series Data", df_numeric_csv, "vegetation_indices.csv", "text/csv", use_container_width=True
462
+ )
463
+
464
  df_numeric.index = [daterange_str_to_year(daterange) for daterange in df_numeric.index]
465
  for veg_index in veg_indices:
466
  fig = px.line(df_numeric, y=[veg_index, f"{veg_index}_buffer"], markers=True)
467
  fig.update_layout(xaxis=dict(tickvals=df_numeric.index, ticktext=df_numeric.index))
468
  st.plotly_chart(fig)
469
 
470
+ st.write(
471
+ "<h3><div style='text-align: center;'>Visual Comparison between Two Years</div></h3>", unsafe_allow_html=True
472
+ )
473
  cols = st.columns(2)
474
 
475
  with cols[0]:
476
  year_1 = st.selectbox("Year 1", result_df.index, index=0, format_func=lambda x: daterange_str_to_year(x))
477
  with cols[1]:
478
+ year_2 = st.selectbox(
479
+ "Year 2", result_df.index, index=len(result_df.index) - 1, format_func=lambda x: daterange_str_to_year(x)
480
+ )
481
+
482
+ vis_params = {"min": 0, "max": 1, "palette": ["white", "green"]} # Example visualisation for Sentinel-2
483
 
484
  # Create a colormap and name it as NDVI
485
+ colormap = cm.LinearColormap(colors=vis_params["palette"], vmin=vis_params["min"], vmax=vis_params["max"])
 
 
 
 
486
 
487
  for veg_index in veg_indices:
488
  st.write(f"<h3><div style='text-align: center;'>{veg_index}</div></h3>", unsafe_allow_html=True)
 
492
  with col:
493
  m = gee_folium.Map()
494
  veg_index_layer = gee_folium.ee_tile_layer(mosaic, {"bands": [veg_index], "min": 0, "max": 1})
495
+
496
  if satellite == "COPERNICUS/S2_SR_HARMONIZED":
497
  min_all = 0
498
  max_all = 255
499
  else:
500
  raise ValueError(f"Unknown satellite: {satellite}")
501
 
502
+ m.add_layer(mosaic.select(veg_index), vis_params)
 
 
503
  # add colorbar
504
  # m.add_colorbar(colors=["#000000", "#00FF00"], vmin=0.0, vmax=1.0)
505
  add_geometry_to_maps([m])
506
  m.add_child(colormap)
507
  m.to_streamlit()
508
 
509
+ for name, key in zip(
510
+ ["RGB (Least Cloud Tile Crop)", "RGB (Max NDVI Mosaic)", "RGB (Median Mosaic)"],
511
+ ["image_visual_least_cloud", "mosaic_visual_max_ndvi", "mosaic_visual_median"],
512
+ ):
513
  st.write(f"<h3><div style='text-align: center;'>{name}</div></h3>", unsafe_allow_html=True)
514
  cols = st.columns(2)
515
  for col, daterange_str in zip(cols, [year_1, year_2]):
 
520
  m = gee_folium.Map()
521
  visual_mosaic = result_df.loc[daterange_str, key]
522
  # visual_layer = gee_folium.ee_tile_layer(mosaic, {"bands": ["R", "G", "B"], "min": min_all, "max": max_all})
523
+
524
+ m.add_layer(visual_mosaic.select(["R", "G", "B"]))
 
 
525
  add_geometry_to_maps([m])
526
  m.to_streamlit()
527
+
528
  st.write("<h3><div style='text-align: center;'>Esri RGB Imagery</div></h3>", unsafe_allow_html=True)
529
  cols = st.columns(2)
530
  for col, daterange_str in zip(cols, [year_1, year_2]):
 
535
  m = leaf_folium.Map()
536
  m.add_tile_layer(wayback_mapping[esri_date], name=f"Esri Wayback Imagery - {esri_date}", attribution="Esri")
537
  add_geometry_to_maps([m])
538
+ write_info(
539
+ f"""
540
  <div style="text-align: center;">
541
  Esri Imagery - {esri_date.replace('-', '/')}
542
  </div>
543
+ """
544
+ )
545
  m.to_streamlit()
546
 
547
+ st.write(
548
+ "<h3><div style='text-align: center;'>DEM and Slope from SRTM at 30m resolution</div></h3>",
549
+ unsafe_allow_html=True,
550
+ )
551
  cols = st.columns(2)
552
+ dem_map, slope_map = get_dem_slope_maps(
553
+ ee.Geometry(buffer_geometry_gdf.to_crs(4326).geometry.item().__geo_interface__)
554
+ )
555
  for col, param_map, title in zip(cols, [dem_map, slope_map], ["DEM Map", "Slope Map"]):
556
  with col:
557
  add_geometry_to_maps([param_map])
558
+ write_info(
559
+ f"""
560
  <div style="text-align: center;">
561
  {title}
562
  </div>
563
+ """
564
+ )
565
  param_map.to_streamlit()