UjjwalKGupta commited on
Commit
44aadb9
·
verified ·
1 Parent(s): 4725b66

Transparent Geometry, Add Colormap, Visualize MaxNDVI

Browse files
Files changed (1) hide show
  1. app.py +126 -71
app.py CHANGED
@@ -15,6 +15,7 @@ import geemap.foliumap as geemapfolium
15
  from streamlit_folium import st_folium
16
  from datetime import datetime
17
  import numpy as np
 
18
 
19
  # Enable fiona driver
20
  fiona.drvsupport.supported_drivers['LIBKML'] = 'rw'
@@ -98,6 +99,23 @@ def reduce_zonal_ndvi(image, ee_object):
98
  # Set the reduced NDVI mean as a property on the image
99
  return ndvi.set('NDVI_mean', reduced.get('NDVI'))
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  # Calculate NDVI
102
  def calculate_NDVI(image):
103
  ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
@@ -112,15 +130,19 @@ def get_zonal_ndviYoY(collection, ee_object):
112
  geometry=ee_object.geometry(),
113
  scale=10,
114
  maxPixels=1e12)
115
- return reduced_max_ndvi.get('NDVI').getInfo()
 
 
116
 
117
  # Get Zonal NDVI
118
  def get_zonal_ndvi(collection, geom_ee_object, return_ndvi=True):
119
  reduced_collection = collection.map(lambda image: reduce_zonal_ndvi(image, ee_object=geom_ee_object))
 
120
  stats_list = reduced_collection.aggregate_array('NDVI_mean').getInfo()
121
  filenames = reduced_collection.aggregate_array('system:index').getInfo()
 
122
  dates = [f.split("_")[0].split('T')[0] for f in filenames]
123
- df = pd.DataFrame({'NDVI': stats_list, 'Dates': dates, 'Imagery': filenames, 'Id': filenames})
124
  if return_ndvi==True:
125
  return df, reduced_collection
126
  else:
@@ -199,10 +221,14 @@ st.title("Zonal Average NDVI Trend Calculator")
199
  input_container = st.container()
200
  # Function to create dropdowns for date input
201
  def date_selector(label):
202
- day = st.selectbox(f"Select {label} day", list(range(1, 32)), key=f"{label}_day")
203
- month = st.selectbox(f"Select {label} month",
204
- ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
205
- key=f"{label}_month")
 
 
 
 
206
  month = datetime.strptime(month, "%B").month
207
  try:
208
  # Try to create a date
@@ -265,14 +291,16 @@ if uploaded_file is not None and submit_button:
265
  end_year = datetime.now().year
266
 
267
  # Create an empty resultant dataframe
268
- columns = ['Dates', 'Imagery', 'AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Ratio', 'Id']
269
  combined_df = pd.DataFrame(columns=columns)
270
 
 
271
  max_ndvi_geoms = []
272
  max_ndvi_buffered_geoms = []
273
  years=[]
274
  ndvi_collections = []
275
  df_geoms = []
 
276
  for year in range(start_year, end_year+1):
277
  try:
278
  # Construct start and end dates for every year
@@ -280,95 +308,122 @@ if uploaded_file is not None and submit_button:
280
  end_ddmm = str(year)+pd.to_datetime(end_date).strftime("-%m-%d")
281
 
282
  # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
283
- collection = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', max_cloud_cover)).filter(ee.Filter.date(start_ddmm, end_ddmm)).select(['B4', 'B8'])
284
 
285
  # Get Zonal Max composite NDVI based on collection and geometries (Original KML and Buffered KML)
286
- max_ndvi_geoms.append(get_zonal_ndviYoY(collection.filterBounds(geom_ee_object), geom_ee_object))
287
- max_ndvi_buffered_geoms.append(get_zonal_ndviYoY(collection.filterBounds(buffered_ee_object), buffered_ee_object))
 
 
 
288
  years.append(str(year))
289
 
290
  # Get Zonal NDVI
291
- df_geom, ndvi_collection = get_zonal_ndvi(collection.filterBounds(geom_ee_object), geom_ee_object)
292
  df_buffered_geom, ndvi_collection = get_zonal_ndvi(collection.filterBounds(buffered_ee_object), buffered_ee_object)
293
  ndvi_collections.append(ndvi_collection)
294
  df_geoms.append(df_geom)
295
 
296
 
297
- # Merge both Zonalstats and create resultant dataframe
298
  resultant_df = pd.merge(df_geom, df_buffered_geom, on='Id', how='inner')
299
- resultant_df = resultant_df.rename(columns={'NDVI_x': 'AvgNDVI_Inside', 'NDVI_y': 'Avg_NDVI_Buffer', 'Imagery_x': 'Imagery', 'Dates_x': 'Dates'})
 
300
  resultant_df['Ratio'] = resultant_df['AvgNDVI_Inside'] / resultant_df['Avg_NDVI_Buffer']
301
  resultant_df.drop(columns=['Imagery_y', 'Dates_y'], inplace=True)
302
 
303
  # Re-order the columns of the resultant dataframe
304
- resultant_df = resultant_df[['Dates', 'Imagery', 'AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Ratio', 'Id']]
305
 
306
  # Append to empty dataframe
307
  combined_df = pd.concat([combined_df, resultant_df], ignore_index=True)
308
- except:
309
  continue
310
 
311
-
312
- # Write the final table
313
- st.write("NDVI details based on Sentinel-2 Surface Reflectance Bands")
314
- st.write(combined_df[['Dates', 'Imagery', 'AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Ratio']])
315
-
316
 
317
- # Plot the multiyear timeseries
318
- st.write("Multiyear Time Series Plot (for given duration)")
319
- st.line_chart(combined_df[['AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Dates']].set_index('Dates'))
320
-
321
- # Create a DataFrame for YoY profile
322
- yoy_df = pd.DataFrame({'Year': years, 'NDVI_Inside': max_ndvi_geoms, 'NDVI_Buffer': max_ndvi_buffered_geoms})
323
- yoy_df['Ratio'] = yoy_df['NDVI_Inside'] / yoy_df['NDVI_Buffer']
324
- slope, intercept = np.polyfit(list(range(1, len(years)+1)), yoy_df['NDVI_Inside'], 1)
325
-
326
- # plot the time series
327
- st.write("Year on Year Profile using Maximum NDVI Composite (computed for given duration)")
328
- st.line_chart(yoy_df[['NDVI_Inside', 'NDVI_Buffer', 'Ratio', 'Year']].set_index('Year'))
329
- st.write("Slope (trend) and Intercept are {}, {} respectively. ".format(np.round(slope, 4), np.round(intercept, 4)))
330
-
331
- #Get Latest NDVI Collection with completeness
332
- ndvi_collection = None
333
- for i in range(len(ndvi_collections)):
334
- #Check size of NDVI collection
335
- ndvi_collection = ndvi_collections[len(ndvi_collections)-i-1]
336
- df_geom = df_geoms[len(ndvi_collections)-i-1]
337
- if ndvi_collection.size().getInfo()>0:
338
- break
339
-
340
- #Map Visualization
341
- st.write("Map Visualization")
342
 
343
- # Function to create the map
344
- def create_map():
345
- m = geemapfolium.Map(center=(polygon_info['centroid'][1],polygon_info['centroid'][0]), zoom=14) # Create a Folium map
346
 
347
- n_layers = 4 #controls the number of images to be displayed
348
- for i in range(min(n_layers, ndvi_collection.size().getInfo())):
349
- ndvi_image = ee.Image(ndvi_collection.toList(ndvi_collection.size()).get(i))
350
- date = df_geom.iloc[i]["Dates"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
- # Add the image to the map as a layer
353
- layer_name = f"Image {i+1} - {date}"
354
- vis_params = {'min': -1, 'max': 1, 'palette': ['blue', 'white', 'green']} # Example visualization for Sentinel-2
355
- m.add_layer(ndvi_image, vis_params, layer_name, z_index=i+10, opacity=0.5)
356
-
357
- m.add_layer(geom_ee_object, {}, 'KML Original', z_index=1)
358
- m.add_layer(buffered_ee_object, {}, 'KML Buffered', z_index=2)
359
-
360
- m.add_layer_control()
361
- return m
362
-
363
- # Create Folium Map object
364
- if "map" not in st.session_state or submit_button:
365
- st.session_state["map"] = create_map()
366
-
367
- # Display the map and allow interactions without triggering reruns
368
- with st.container():
369
- st_folium(st.session_state["map"], width=725, returned_objects=[])
370
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  else:
 
372
  st.write('ValueError: "Input must have single polygon geometry"')
373
  st.write(gdf)
374
  st.stop()
 
15
  from streamlit_folium import st_folium
16
  from datetime import datetime
17
  import numpy as np
18
+ import branca.colormap as cm
19
 
20
  # Enable fiona driver
21
  fiona.drvsupport.supported_drivers['LIBKML'] = 'rw'
 
99
  # Set the reduced NDVI mean as a property on the image
100
  return ndvi.set('NDVI_mean', reduced.get('NDVI'))
101
 
102
+ # Function to compute cloud probability and add it as a property to the image
103
+ def reduce_zonal_cloud_probability(image, ee_object):
104
+ # Compute cloud probability using the SCL band (Scene Classification Layer) in Sentinel-2
105
+ cloud_probability = image.select('MSK_CLDPRB').rename('cloud_probability')
106
+
107
+ # Reduce the region to get the mean cloud probability value for the given geometry
108
+ reduced = cloud_probability.reduceRegion(
109
+ reducer=ee.Reducer.mean(),
110
+ geometry=ee_object.geometry(),
111
+ scale=10,
112
+ maxPixels=1e12
113
+ )
114
+
115
+ # Set the reduced cloud probability mean as a property on the image
116
+ return image.set('cloud_probability_mean', reduced.get('cloud_probability'))
117
+
118
+
119
  # Calculate NDVI
120
  def calculate_NDVI(image):
121
  ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
 
130
  geometry=ee_object.geometry(),
131
  scale=10,
132
  maxPixels=1e12)
133
+ return reduced_max_ndvi.get('NDVI').getInfo(), max_ndvi
134
+
135
+
136
 
137
  # Get Zonal NDVI
138
  def get_zonal_ndvi(collection, geom_ee_object, return_ndvi=True):
139
  reduced_collection = collection.map(lambda image: reduce_zonal_ndvi(image, ee_object=geom_ee_object))
140
+ cloud_prob_collection = collection.map(lambda image: reduce_zonal_cloud_probability(image, ee_object=geom_ee_object))
141
  stats_list = reduced_collection.aggregate_array('NDVI_mean').getInfo()
142
  filenames = reduced_collection.aggregate_array('system:index').getInfo()
143
+ cloud_probabilities = cloud_prob_collection.aggregate_array('cloud_probability_mean').getInfo()
144
  dates = [f.split("_")[0].split('T')[0] for f in filenames]
145
+ df = pd.DataFrame({'NDVI': stats_list, 'Dates': dates, 'Imagery': filenames, 'Id': filenames, 'CLDPRB': cloud_probabilities})
146
  if return_ndvi==True:
147
  return df, reduced_collection
148
  else:
 
221
  input_container = st.container()
222
  # Function to create dropdowns for date input
223
  def date_selector(label):
224
+ day_options = list(range(1, 32))
225
+ if label=='start':
226
+ day = st.selectbox(f"Select {label} day", day_options, key=f"{label}_day", index=0)
227
+ else:
228
+ day = st.selectbox(f"Select {label} day", day_options, key=f"{label}_day", index=14)
229
+
230
+ month_options = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
231
+ month = st.selectbox(f"Select {label} month", month_options, key=f"{label}_month",index=11)
232
  month = datetime.strptime(month, "%B").month
233
  try:
234
  # Try to create a date
 
291
  end_year = datetime.now().year
292
 
293
  # Create an empty resultant dataframe
294
+ columns = ['Dates', 'Imagery', 'AvgNDVI_Inside', 'CLDPRB', 'Avg_NDVI_Buffer', 'CLDPRB_Buffer', 'Ratio', 'Id' ]
295
  combined_df = pd.DataFrame(columns=columns)
296
 
297
+ # Create empty lists of parameters
298
  max_ndvi_geoms = []
299
  max_ndvi_buffered_geoms = []
300
  years=[]
301
  ndvi_collections = []
302
  df_geoms = []
303
+ max_ndvis = []
304
  for year in range(start_year, end_year+1):
305
  try:
306
  # Construct start and end dates for every year
 
308
  end_ddmm = str(year)+pd.to_datetime(end_date).strftime("-%m-%d")
309
 
310
  # Filter data based on the date, bounds, cloud coverage and select NIR and Red Band
311
+ collection = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', max_cloud_cover)).filter(ee.Filter.date(start_ddmm, end_ddmm))
312
 
313
  # Get Zonal Max composite NDVI based on collection and geometries (Original KML and Buffered KML)
314
+ max_ndvi_geom, max_ndvi = get_zonal_ndviYoY(collection.filterBounds(geom_ee_object), geom_ee_object) # max_NDVI is image common to both
315
+ max_ndvi_geoms.append(max_ndvi_geom)
316
+ max_ndvi_geom, max_ndvi = get_zonal_ndviYoY(collection.filterBounds(buffered_ee_object), buffered_ee_object)
317
+ max_ndvi_buffered_geoms.append(max_ndvi_geom)
318
+ max_ndvis.append(max_ndvi)
319
  years.append(str(year))
320
 
321
  # Get Zonal NDVI
322
+ df_geom, ndvi_collection = get_zonal_ndvi(collection.filterBounds(geom_ee_object), geom_ee_object) # ndvi collection is common to both
323
  df_buffered_geom, ndvi_collection = get_zonal_ndvi(collection.filterBounds(buffered_ee_object), buffered_ee_object)
324
  ndvi_collections.append(ndvi_collection)
325
  df_geoms.append(df_geom)
326
 
327
 
328
+ # Merge both Zonalstats on ID and create resultant dataframe
329
  resultant_df = pd.merge(df_geom, df_buffered_geom, on='Id', how='inner')
330
+ resultant_df = resultant_df.rename(columns={'NDVI_x': 'AvgNDVI_Inside', 'NDVI_y': 'Avg_NDVI_Buffer', 'Imagery_x': 'Imagery', 'Dates_x': 'Dates',
331
+ 'CLDPRB_x': 'CLDPRB', 'CLDPRB_y': 'CLDPRB_Buffer'})
332
  resultant_df['Ratio'] = resultant_df['AvgNDVI_Inside'] / resultant_df['Avg_NDVI_Buffer']
333
  resultant_df.drop(columns=['Imagery_y', 'Dates_y'], inplace=True)
334
 
335
  # Re-order the columns of the resultant dataframe
336
+ resultant_df = resultant_df[columns]
337
 
338
  # Append to empty dataframe
339
  combined_df = pd.concat([combined_df, resultant_df], ignore_index=True)
340
+ except Exception as e:
341
  continue
342
 
343
+ if len(combined_df)>1:
344
+ # Write the final table
345
+ st.write("NDVI details based on Sentinel-2 Surface Reflectance Bands")
346
+ st.write(combined_df[columns[:-1]])
 
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
+ # Plot the multiyear timeseries
350
+ st.write("Multiyear Time Series Plot (for given duration)")
351
+ st.line_chart(combined_df[['AvgNDVI_Inside', 'Avg_NDVI_Buffer', 'Dates']].set_index('Dates'))
352
 
353
+ # Create a DataFrame for YoY profile
354
+ yoy_df = pd.DataFrame({'Year': years, 'NDVI_Inside': max_ndvi_geoms, 'NDVI_Buffer': max_ndvi_buffered_geoms})
355
+ yoy_df['Ratio'] = yoy_df['NDVI_Inside'] / yoy_df['NDVI_Buffer']
356
+ slope, intercept = np.polyfit(list(range(1, len(years)+1)), yoy_df['NDVI_Inside'], 1)
357
+
358
+ # plot the time series
359
+ st.write("Year on Year Profile using Maximum NDVI Composite (computed for given duration)")
360
+ st.line_chart(yoy_df[['NDVI_Inside', 'NDVI_Buffer', 'Ratio', 'Year']].set_index('Year'))
361
+ st.write("Slope (trend) and Intercept are {}, {} respectively. ".format(np.round(slope, 4), np.round(intercept, 4)))
362
+
363
+ #Get Latest NDVI Collection with completeness
364
+ ndvi_collection = None
365
+ for i in range(len(ndvi_collections)):
366
+ #Check size of NDVI collection
367
+ ndvi_collection = ndvi_collections[len(ndvi_collections)-i-1]
368
+ df_geom = df_geoms[len(ndvi_collections)-i-1]
369
+ if ndvi_collection.size().getInfo()>0:
370
+ break
371
 
372
+ #Map Visualization
373
+ st.write("Map Visualization")
374
+
375
+ # Function to create the map
376
+ def create_map():
377
+ m = geemapfolium.Map(center=(polygon_info['centroid'][1],polygon_info['centroid'][0]), zoom=14) # Create a Folium map
378
+
379
+ vis_params = {'min': -1, 'max': 1, 'palette': ['blue', 'white', 'green']} # Example visualization for Sentinel-2
380
+
381
+ # Create a colormap and name it as NDVI
382
+ colormap = cm.LinearColormap(
383
+ colors=vis_params['palette'],
384
+ vmin=vis_params['min'],
385
+ vmax=vis_params['max']
386
+ )
387
+ colormap.caption = 'NDVI'
388
+
389
+ n_layers = 4 #controls the number of images to be displayed
390
+ for i in range(min(n_layers, ndvi_collection.size().getInfo())):
391
+ ndvi_image = ee.Image(ndvi_collection.toList(ndvi_collection.size()).get(i))
392
+ date = df_geom.iloc[i]["Dates"]
393
+
394
+ # Add the image to the map as a layer
395
+ layer_name = f"Sentinel-2 NDVI - {date}"
396
+ m.add_layer(ndvi_image, vis_params, layer_name, z_index=i+10, opacity=0.5)
397
+
398
+ for i in range(len(max_ndvis)):
399
+ layer_name = f"Sentinel-2 MaxNDVI-{years[i]}"
400
+ m.add_layer(max_ndvis[i], vis_params, layer_name, z_index=i+20, opacity=0.5)
401
+
402
+ # Add the colormap to the map
403
+ m.add_child(colormap)
404
+
405
+ geom_vis_params = {'color': '000000', 'pointSize': 3,'pointShape': 'circle','width': 2,'lineType': 'solid','fillColor': '00000000'}
406
+ buffer_vis_params = {'color': 'FF0000', 'pointSize': 3,'pointShape': 'circle','width': 2,'lineType': 'solid','fillColor': '00000000'}
407
+
408
+ m.add_layer(geom_ee_object.style(**geom_vis_params), {}, 'KML Original', z_index=1, opacity=1)
409
+ m.add_layer(buffered_ee_object.style(**buffer_vis_params), {}, 'KML Buffered', z_index=2, opacity=1)
410
+
411
+ m.add_layer_control()
412
+ return m
413
+
414
+ # Create Folium Map object and store it in streamlit session
415
+ if "map" not in st.session_state or submit_button:
416
+ st.session_state["map"] = create_map()
417
+
418
+ # Display the map and allow interactions without triggering reruns
419
+ with st.container():
420
+ st_folium(st.session_state["map"], width=725, returned_objects=[])
421
+ else:
422
+ # Failed to find any Sentinel-2 Image in given period
423
+ st.write("No Sentinel-2 Imagery found for the given period.")
424
+ st.stop()
425
  else:
426
+ # Failed to have single polygon geometry
427
  st.write('ValueError: "Input must have single polygon geometry"')
428
  st.write(gdf)
429
  st.stop()