YashMK89 commited on
Commit
7a7dd74
·
verified ·
1 Parent(s): 8b8d12a

update app.py

Browse files
Files changed (1) hide show
  1. app.py +474 -320
app.py CHANGED
@@ -53,24 +53,32 @@ with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
53
 
54
  ee.Initialize(project='ee-yashsacisro24')
55
 
56
- # Load Sentinel dataset options from JSON file
57
  with open("sentinel_datasets.json") as f:
58
  data = json.load(f)
59
 
60
- # Display the title and dataset selection
61
  st.title("Sentinel Dataset")
62
 
63
- # Select dataset category and subcategory (case-insensitive selection)
64
  main_selection = st.selectbox("Select Sentinel Dataset Category", list(data.keys()))
65
 
 
66
  if main_selection:
67
  sub_options = data[main_selection]["sub_options"]
68
  sub_selection = st.selectbox("Select Specific Dataset ID", list(sub_options.keys()))
69
 
 
 
 
 
 
 
 
 
70
  # Earth Engine Index Calculator Section
71
  st.header("Earth Engine Index Calculator")
72
 
73
- # Choose Index or Custom Formula (case-insensitive)
74
  index_choice = st.selectbox("Select an Index or Enter Custom Formula", ['NDVI', 'NDWI', 'Average NO₂', 'Custom Formula'])
75
 
76
  # Initialize custom_formula variable
@@ -84,10 +92,31 @@ elif index_choice.lower() == 'ndwi':
84
  elif index_choice.lower() == 'average no₂':
85
  st.write("Formula for Average NO₂: Average NO₂ = Mean(NO2 band)")
86
  elif index_choice.lower() == 'custom formula':
87
- custom_formula = st.text_input("Enter Custom Formula (e.g., '(B5 - B4) / (B5 + B4)')")
88
- st.write(f"Custom Formula: {custom_formula}") # Display the custom formula after the user inputs it
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- # Reducer selection
91
  reducer_choice = st.selectbox(
92
  "Select Reducer",
93
  ['mean', 'sum', 'median', 'min', 'max', 'count'],
@@ -123,6 +152,93 @@ shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", [
123
  # Ask user to upload a file based on shape type (case-insensitive)
124
  file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"])
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  # Date Input for Start and End Dates
127
  start_date = st.date_input("Start Date", value=pd.to_datetime('2020-01-01'))
128
  end_date = st.date_input("End Date", value=pd.to_datetime('2020-12-31'))
@@ -131,6 +247,9 @@ end_date = st.date_input("End Date", value=pd.to_datetime('2020-12-31'))
131
  start_date_str = start_date.strftime('%Y-%m-%d')
132
  end_date_str = end_date.strftime('%Y-%m-%d')
133
 
 
 
 
134
  # Initialize session state for storing results if not already done
135
  if 'results' not in st.session_state:
136
  st.session_state.results = []
@@ -143,7 +262,7 @@ if 'map_data' not in st.session_state:
143
  def parameters_changed():
144
  return (
145
  st.session_state.last_params.get('main_selection') != main_selection or
146
- st.session_state.last_params.get('sub_selection') != sub_selection or
147
  st.session_state.last_params.get('index_choice') != index_choice or
148
  st.session_state.last_params.get('start_date_str') != start_date_str or
149
  st.session_state.last_params.get('end_date_str') != end_date_str or
@@ -156,7 +275,7 @@ if parameters_changed():
156
  st.session_state.results = [] # Clear the previous results
157
  st.session_state.last_params = {
158
  'main_selection': main_selection,
159
- 'sub_selection': sub_selection,
160
  'index_choice': index_choice,
161
  'start_date_str': start_date_str,
162
  'end_date_str': end_date_str,
@@ -164,339 +283,374 @@ if parameters_changed():
164
  'file_upload': file_upload
165
  }
166
 
167
- # Function to get the corresponding reducer based on user input
168
- def get_reducer(reducer_name):
169
- """
170
- Map user-friendly reducer names to Earth Engine reducer objects.
171
-
172
- Args:
173
- reducer_name (str): The name of the reducer (e.g., 'mean', 'sum', 'median').
174
-
175
- Returns:
176
- ee.Reducer: The corresponding Earth Engine reducer.
177
- """
178
- reducers = {
179
- 'mean': ee.Reducer.mean(),
180
- 'sum': ee.Reducer.sum(),
181
- 'median': ee.Reducer.median(),
182
- 'min': ee.Reducer.min(),
183
- 'max': ee.Reducer.max(),
184
- 'count': ee.Reducer.count(),
185
- }
186
-
187
- # Default to 'mean' if the reducer_name is not recognized
188
- return reducers.get(reducer_name.lower(), ee.Reducer.mean())
189
-
190
- # Function to calculate NDVI
191
- def calculate_ndvi(image, geometry):
192
  ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
193
-
194
- # Perform reduction on the region with the selected reducer
195
- result = ndvi.reduceRegion(
196
- reducer=get_reducer(reducer_choice),
197
- geometry=geometry,
198
- scale=30
199
- )
200
-
201
- # Output debugging information
202
- result_value = result.get('NDVI')
203
- try:
204
- calculated_value = result_value.getInfo()
205
- st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}")
206
- except Exception as e:
207
- st.error(f"Error retrieving NDVI result: {e}")
208
-
209
- return result_value
210
-
211
 
212
  # Function to calculate NDWI
213
- def calculate_ndwi(image, geometry):
214
  ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI')
215
- result = ndwi.reduceRegion(
216
- reducer=get_reducer(reducer_choice),
217
- geometry=geometry,
218
- scale=30
219
- )
220
-
221
- # Output debugging information
222
- result_value = result.get('NDWI')
223
- try:
224
- calculated_value = result_value.getInfo()
225
- st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}")
226
- except Exception as e:
227
- st.error(f"Error retrieving NDVI result: {e}")
228
-
229
- return result_value
230
-
231
- # Function to calculate Average NO₂ for Sentinel-5P
232
- def calculate_avg_no2_sentinel5p(image, geometry):
233
- no2 = image.select('NO2').reduceRegion(
234
- reducer=get_reducer(reducer_choice),
235
- geometry=geometry,
236
- scale=1000
237
- )
238
-
239
- # Output debugging information
240
- result_value = result.get('NDVI')
241
- try:
242
- calculated_value = result_value.getInfo()
243
- st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}")
244
- except Exception as e:
245
- st.error(f"Error retrieving NDVI result: {e}")
246
-
247
- return result_value
248
 
249
- # Function to calculate Custom Formula
250
- def calculate_custom_formula(image, geometry, formula, scale=30):
251
- """
252
- Calculate a custom formula on an image and return the result for a given geometry,
253
- using a user-specified reducer.
254
- """
255
- # Dynamically generate the dictionary of band references from the image
256
- band_names = image.bandNames().getInfo()
257
- band_dict = {band: image.select(band) for band in band_names}
258
 
259
- # Use the formula with the bands in the image
260
- result_image = image.expression(formula, band_dict).rename('CustomResult')
261
-
262
- # Reduce the region to get the result based on the specified reducer
263
- result = result_image.reduceRegion(
264
- reducer=get_reducer(reducer_choice),
265
- geometry=geometry,
266
- scale=scale
267
- )
268
 
269
- # Output debugging information
270
- result_value = result.get('CustomResult')
271
- try:
272
- calculated_value = result_value.getInfo()
273
- st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}")
274
- except Exception as e:
275
- st.error(f"Error retrieving NDVI result: {e}")
276
 
277
- return result_value
278
-
279
- # Function to get the most recent image from the collection
280
- def get_most_recent_image(image_collection):
281
- image = image_collection.sort('system:time_start', False).first()
282
- return image
283
-
284
-
285
- # Function to process the custom formula
286
- def process_custom_formula(image, geometry, formula):
287
- return calculate_custom_formula(image, geometry, formula)
288
 
289
- locations_df = None # Initialize locations_df to None
290
- polygons_df = None # Ensure polygons_df is initialized at the beginning
 
 
 
291
 
292
- # Process each point (with additional checks for file validity)
293
- # Check the shape type and assign polygons_df only for Polygon data
294
- if file_upload:
295
- # locations_df = None # Initialize locations_df to None
296
- # polygons_df = None # Ensure polygons_df is initialized at the beginning
297
-
298
- file_extension = os.path.splitext(file_upload.name)[1].lower()
299
 
300
- # Read file based on shape type
301
- if shape_type == 'Point':
302
- if file_extension == '.csv':
303
- locations_df = read_csv(file_upload)
304
- elif file_extension == '.geojson':
305
- locations_df = read_geojson(file_upload)
306
- elif file_extension == '.kml':
307
- locations_df = read_kml(file_upload)
308
- else:
309
- st.error("Unsupported file type. Please upload a CSV, GeoJSON, or KML file for points.")
310
- elif shape_type == 'Polygon':
311
- if file_extension == '.geojson':
312
- polygons_df = read_geojson(file_upload)
313
- elif file_extension == '.kml':
314
- polygons_df = read_kml(file_upload)
315
- else:
316
- st.error("Unsupported file type. Please upload a GeoJSON or KML file for polygons.")
317
 
 
 
 
 
318
 
319
- if locations_df is not None and not locations_df.empty:
320
- # Ensure the necessary columns exist in the dataframe
321
- if 'latitude' not in locations_df.columns or 'longitude' not in locations_df.columns:
322
- st.error("Uploaded file is missing required 'latitude' or 'longitude' columns.")
323
- else:
324
- # Display a preview of the points data
325
- st.write("Preview of the uploaded points data:")
326
- st.dataframe(locations_df.head())
327
-
328
- # Create a LeafMap object to display the points
329
- m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10)
330
-
331
- # Add points to the map using a loop
332
- for _, row in locations_df.iterrows():
333
- latitude = row['latitude']
334
- longitude = row['longitude']
335
-
336
- # Check if latitude or longitude are NaN and skip if they are
337
- if pd.isna(latitude) or pd.isna(longitude):
338
- continue # Skip this row and move to the next one
339
-
340
- m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name'))
341
-
342
- # Display map
343
- st.write("Map of Uploaded Points:")
344
- m.to_streamlit()
345
-
346
- # Store the map in session_state
347
- st.session_state.map_data = m
348
-
349
- # Process each point for index calculation
350
- for idx, row in locations_df.iterrows():
351
- latitude = row['latitude']
352
- longitude = row['longitude']
353
- location_name = row.get('name', f"Location_{idx}")
354
-
355
- # Skip processing if latitude or longitude is NaN
356
- if pd.isna(latitude) or pd.isna(longitude):
357
- continue # Skip this row and move to the next one
358
-
359
- # Define the region of interest (ROI)
360
- roi = ee.Geometry.Point([longitude, latitude])
361
-
362
- # Load Sentinel-2 image collection
363
- collection = ee.ImageCollection(sub_options[sub_selection]) \
364
- .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
365
- .filterBounds(roi)
366
-
367
- image = get_most_recent_image(collection)
368
- if not image:
369
- st.warning(f"No images found for {location_name}.")
370
- else:
371
- st.write(f"Found images for {location_name}.")
372
- # Perform the calculation based on user selection
373
- # Perform the calculation based on user selection
374
- result = None
375
- if index_choice == 'NDVI':
376
- result = calculate_ndvi(image, roi)
377
- elif index_choice == 'NDWI':
378
- result = calculate_ndwi(image, roi)
379
- elif index_choice == 'Average NO₂':
380
- if 'NO2' in image.bandNames().getInfo():
381
- result = calculate_avg_no2_sentinel5p(image, roi)
382
- else:
383
- st.warning(f"No NO2 band found for {location_name}. Please use Sentinel-5P for NO₂ data.")
384
- elif index_choice.lower() == 'custom formula' and custom_formula:
385
- result = process_custom_formula(image, roi, custom_formula)
386
-
387
- # Validate result before using getInfo
388
- if result is not None:
389
- calculated_value = None # Initialize the calculated_value as None
390
-
391
- # Check if the result is a dictionary
392
- if isinstance(result, dict):
393
- # Extract the value using the appropriate key (adjust the key name as needed)
394
- calculated_value = result.get('CustomResult', None) # Replace 'CustomResult' if using NDVI, NDWI, etc.
395
- else:
396
- try:
397
- # If it's an Earth Engine object, get the value using getInfo
398
- calculated_value = result.getInfo()
399
- except Exception as e:
400
- st.error(f"Error getting result info: {e}")
401
-
402
- # If a valid calculated_value is found, append the result to session_state
403
- if calculated_value is not None:
404
- st.session_state.results.append({
405
- 'Location Name': location_name,
406
- 'Latitude': latitude,
407
- 'Longitude': longitude,
408
- 'Calculated Value': calculated_value
409
- })
410
- else:
411
- st.warning(f"No value calculated for {location_name}.")
412
- else:
413
- st.warning(f"No value calculated for {location_name}.")
414
-
415
 
416
- # Check if polygons_df is populated for polygons
417
- if polygons_df is not None:
418
- st.write("Preview of the uploaded polygons data:")
419
- st.dataframe(polygons_df.head())
420
 
421
- m = leafmap.Map(center=[polygons_df.geometry.centroid.y.mean(), polygons_df.geometry.centroid.x.mean()], zoom=10)
 
 
 
 
 
422
 
423
- for _, row in polygons_df.iterrows():
424
- polygon = row['geometry']
425
- if polygon.is_valid:
426
- gdf = gpd.GeoDataFrame([row], geometry=[polygon], crs=polygons_df.crs)
427
- m.add_gdf(gdf=gdf, layer_name=row.get('name', 'Unnamed Polygon'))
428
 
429
- st.write("Map of Uploaded Polygons:")
430
- m.to_streamlit()
431
- st.session_state.map_data = m
 
432
 
433
- for idx, row in polygons_df.iterrows():
434
- polygon = row['geometry']
435
- location_name = row.get('name', f"Polygon_{idx}")
 
 
 
436
 
437
- try:
438
- roi = convert_to_ee_geometry(polygon)
439
- except ValueError as e:
440
- st.error(str(e))
441
- continue
442
 
443
- collection = ee.ImageCollection(sub_options[sub_selection]) \
444
- .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
445
- .filterBounds(roi)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
 
447
- image = get_most_recent_image(collection)
 
448
 
449
- if not image:
450
- st.warning(f"No images found for {location_name}.")
451
- else:
452
- st.write(f"Found an image for {location_name}.")
453
- result = None
454
- if index_choice.lower() == 'ndvi':
455
- result = calculate_ndvi(image, roi)
456
- elif index_choice.lower() == 'ndwi':
457
- result = calculate_ndwi(image, roi)
458
- elif index_choice.lower() == 'average no₂':
459
- if 'NO2' in image.bandNames().getInfo():
460
- result = calculate_avg_no2_sentinel5p(image, roi)
461
- else:
462
- st.warning(f"No NO2 band found for {location_name}. Please use Sentinel-5P for NO₂ data.")
463
- elif index_choice.lower() == 'custom formula' and custom_formula:
464
- result = process_custom_formula(image, roi, custom_formula)
465
-
466
- if result is not None:
467
- # Initialize the calculated_value as None
468
- calculated_value = None
469
 
470
- # Check if the result is a dictionary (e.g., custom formula result)
471
- if isinstance(result, dict) and 'CustomResult' in result:
472
- calculated_value = result['CustomResult'] # Extract the numeric value from the dictionary
473
- # If the result is a numeric value (e.g., NDVI, NDWI, or NO2)
474
- elif isinstance(result, (int, float)):
475
- calculated_value = result
476
-
477
- # If a valid calculated_value is found, append the result to session_state
478
- if calculated_value is not None:
479
- st.session_state.results.append({
480
- 'Location Name': location_name,
481
- 'Calculated Value': calculated_value
482
- })
483
-
484
- # After processing, show the results
485
- if st.session_state.results:
486
- result_df = pd.DataFrame(st.session_state.results)
487
-
488
- if shape_type.lower() == 'point':
489
- st.write("Processed Results Table (Points):")
490
- st.dataframe(result_df[['Location Name', 'Latitude', 'Longitude', 'Calculated Value']])
491
- else:
492
- st.write("Processed Results Table (Polygons):")
493
- st.dataframe(result_df[['Location Name', 'Calculated Value']])
494
 
495
- filename = f"{main_selection}_{sub_selection}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{shape_type}.csv"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
 
497
- st.download_button(
498
- label="Download results as CSV",
499
- data=result_df.to_csv(index=False).encode('utf-8'),
500
- file_name=filename,
501
- mime='text/csv'
502
- )
 
53
 
54
  ee.Initialize(project='ee-yashsacisro24')
55
 
56
+ # Load the Sentinel dataset options from JSON file
57
  with open("sentinel_datasets.json") as f:
58
  data = json.load(f)
59
 
60
+ # Display the title for the Streamlit app
61
  st.title("Sentinel Dataset")
62
 
63
+ # Select dataset category (main selection)
64
  main_selection = st.selectbox("Select Sentinel Dataset Category", list(data.keys()))
65
 
66
+ # If a category is selected, display the sub-options (specific datasets)
67
  if main_selection:
68
  sub_options = data[main_selection]["sub_options"]
69
  sub_selection = st.selectbox("Select Specific Dataset ID", list(sub_options.keys()))
70
 
71
+ # Display the selected dataset ID based on user input
72
+ if sub_selection:
73
+ st.write(f"You selected: {main_selection} -> {sub_selection}")
74
+ st.write(f"Dataset ID: {sub_options[sub_selection]}")
75
+
76
+ # Fetch the correct dataset ID from the sub-selection
77
+ dataset_id = sub_options[sub_selection]
78
+
79
  # Earth Engine Index Calculator Section
80
  st.header("Earth Engine Index Calculator")
81
 
 
82
  index_choice = st.selectbox("Select an Index or Enter Custom Formula", ['NDVI', 'NDWI', 'Average NO₂', 'Custom Formula'])
83
 
84
  # Initialize custom_formula variable
 
92
  elif index_choice.lower() == 'average no₂':
93
  st.write("Formula for Average NO₂: Average NO₂ = Mean(NO2 band)")
94
  elif index_choice.lower() == 'custom formula':
95
+ custom_formula = st.text_input("Enter Custom Formula (e.g., B5,B4)")
96
+ # Check if custom formula is empty and show warning
97
+ if not custom_formula:
98
+ st.warning("Please enter a custom formula before proceeding.")
99
+ else:
100
+ st.write(f"Custom Formula: (band1 - band2) / (band1 + band2)") # Display the custom formula after the user inputs it
101
+
102
+ # Function to get the corresponding reducer based on user input
103
+ def get_reducer(reducer_name):
104
+ """
105
+ Map user-friendly reducer names to Earth Engine reducer objects.
106
+ """
107
+ reducers = {
108
+ 'mean': ee.Reducer.mean(),
109
+ 'sum': ee.Reducer.sum(),
110
+ 'median': ee.Reducer.median(),
111
+ 'min': ee.Reducer.min(),
112
+ 'max': ee.Reducer.max(),
113
+ 'count': ee.Reducer.count(),
114
+ }
115
+
116
+ # Default to 'mean' if the reducer_name is not recognized
117
+ return reducers.get(reducer_name.lower(), ee.Reducer.mean())
118
 
119
+ # Streamlit selectbox for reducer choice
120
  reducer_choice = st.selectbox(
121
  "Select Reducer",
122
  ['mean', 'sum', 'median', 'min', 'max', 'count'],
 
152
  # Ask user to upload a file based on shape type (case-insensitive)
153
  file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"])
154
 
155
+ if file_upload is not None:
156
+ # Read the user-uploaded file
157
+ if shape_type.lower() == "point":
158
+ # Handle different file types for Point data
159
+ if file_upload.name.endswith('.csv'):
160
+ locations_df = pd.read_csv(file_upload)
161
+ elif file_upload.name.endswith('.geojson'):
162
+ locations_df = gpd.read_file(file_upload)
163
+ elif file_upload.name.endswith('.kml'):
164
+ locations_df = gpd.read_file(file_upload)
165
+ else:
166
+ st.error("Unsupported file format. Please upload CSV, GeoJSON, or KML.")
167
+ locations_df = pd.DataFrame()
168
+
169
+ # Processing the point data
170
+ with st.spinner('Processing data...'):
171
+ if locations_df is not None and not locations_df.empty:
172
+ # Ensure the necessary columns exist in the dataframe
173
+ if 'latitude' not in locations_df.columns or 'longitude' not in locations_df.columns:
174
+ st.error("Uploaded file is missing required 'latitude' or 'longitude' columns.")
175
+ else:
176
+ # Display a preview of the points data
177
+ st.write("Preview of the uploaded points data:")
178
+ st.dataframe(locations_df.head())
179
+
180
+ # Create a LeafMap object to display the points
181
+ m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10)
182
+
183
+ # Add points to the map using a loop
184
+ for _, row in locations_df.iterrows():
185
+ latitude = row['latitude']
186
+ longitude = row['longitude']
187
+
188
+ # Check if latitude or longitude are NaN and skip if they are
189
+ if pd.isna(latitude) or pd.isna(longitude):
190
+ continue # Skip this row and move to the next one
191
+
192
+ m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name'))
193
+
194
+ # Display map
195
+ st.write("Map of Uploaded Points:")
196
+ m.to_streamlit()
197
+
198
+ # Store the map in session_state
199
+ st.session_state.map_data = m
200
+
201
+ elif shape_type.lower() == "polygon":
202
+ # Handle different file types for Polygon data
203
+ if file_upload.name.endswith('.csv'):
204
+ locations_df = pd.read_csv(file_upload)
205
+ elif file_upload.name.endswith('.geojson'):
206
+ locations_df = gpd.read_file(file_upload)
207
+ elif file_upload.name.endswith('.kml'):
208
+ locations_df = gpd.read_file(file_upload)
209
+ else:
210
+ st.error("Unsupported file format. Please upload CSV, GeoJSON, or KML.")
211
+ locations_df = pd.DataFrame()
212
+
213
+ # Processing the polygon data
214
+ with st.spinner('Processing data...'):
215
+ if locations_df is not None and not locations_df.empty:
216
+ # Ensure the 'geometry' column exists in the dataframe
217
+ if 'geometry' not in locations_df.columns:
218
+ st.error("Uploaded file is missing required 'geometry' column.")
219
+ else:
220
+ # Display a preview of the polygons data
221
+ st.write("Preview of the uploaded polygons data:")
222
+ st.dataframe(locations_df.head())
223
+
224
+ # Create a LeafMap object to display the polygons
225
+ m = leafmap.Map(center=[locations_df.geometry.centroid.y.mean(), locations_df.geometry.centroid.x.mean()], zoom=10)
226
+
227
+ # Add polygons to the map using a loop
228
+ for _, row in locations_df.iterrows():
229
+ polygon = row['geometry']
230
+ if polygon.is_valid:
231
+ gdf = gpd.GeoDataFrame([row], geometry=[polygon], crs=locations_df.crs)
232
+ m.add_gdf(gdf=gdf, layer_name=row.get('name', 'Unnamed Polygon'))
233
+
234
+ # Display map
235
+ st.write("Map of Uploaded Polygons:")
236
+ m.to_streamlit()
237
+
238
+ # Store the map in session_state
239
+ st.session_state.map_data = m
240
+
241
+
242
  # Date Input for Start and End Dates
243
  start_date = st.date_input("Start Date", value=pd.to_datetime('2020-01-01'))
244
  end_date = st.date_input("End Date", value=pd.to_datetime('2020-12-31'))
 
247
  start_date_str = start_date.strftime('%Y-%m-%d')
248
  end_date_str = end_date.strftime('%Y-%m-%d')
249
 
250
+ # Aggregation period selection
251
+ aggregation_period = st.selectbox("Select Aggregation Period", ["Daily", "Weekly", "Monthly", "Yearly"], index=0)
252
+
253
  # Initialize session state for storing results if not already done
254
  if 'results' not in st.session_state:
255
  st.session_state.results = []
 
262
  def parameters_changed():
263
  return (
264
  st.session_state.last_params.get('main_selection') != main_selection or
265
+ st.session_state.last_params.get('dataset_id') != dataset_id or
266
  st.session_state.last_params.get('index_choice') != index_choice or
267
  st.session_state.last_params.get('start_date_str') != start_date_str or
268
  st.session_state.last_params.get('end_date_str') != end_date_str or
 
275
  st.session_state.results = [] # Clear the previous results
276
  st.session_state.last_params = {
277
  'main_selection': main_selection,
278
+ 'dataset_id': dataset_id,
279
  'index_choice': index_choice,
280
  'start_date_str': start_date_str,
281
  'end_date_str': end_date_str,
 
283
  'file_upload': file_upload
284
  }
285
 
286
+ # Function to calculate NDVI with the selected reducer
287
+ def calculate_ndvi(image, geometry, reducer_choice):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
289
+ return ndvi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  # Function to calculate NDWI
292
+ def calculate_ndwi(image, geometry, reducer_choice):
293
  ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI')
294
+ return ndwi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
+ def calculate_custom_formula(image, geometry, custom_formula, reducer_choice, scale=30):
 
 
 
 
 
 
 
 
297
 
298
+ # Calculate NDWI using the user-specified bands
299
+ band1 = custom_formula[:custom_formula.find(",")]
300
+ band2 = custom_formula[custom_formula.find(",")+1:]
301
+ custom_formula = image.normalizedDifference([band1, band2]).rename('custom formula')
302
+ return custom_formula
 
 
 
 
303
 
304
+ # Modify aggregation functions to return the correct time period and aggregated results
305
+ def aggregate_data_daily(collection):
306
+ # Extract day from the image date (using the exact date)
307
+ collection = collection.map(lambda image: image.set('day', ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')))
 
 
 
308
 
309
+ # Group images by day (distinct days)
310
+ grouped_by_day = collection.aggregate_array('day').distinct()
 
 
 
 
 
 
 
 
 
311
 
312
+ def calculate_daily_mean(day):
313
+ # Filter the collection by the specific day
314
+ daily_collection = collection.filter(ee.Filter.eq('day', day))
315
+ daily_mean = daily_collection.mean() # Calculate mean for the day
316
+ return daily_mean.set('day', day)
317
 
318
+ # Calculate the daily mean for each day
319
+ daily_images = ee.List(grouped_by_day.map(calculate_daily_mean))
320
+
321
+ return ee.ImageCollection(daily_images)
 
 
 
322
 
323
+ def aggregate_data_weekly(collection):
324
+ # Extract week and year from the image date
325
+ collection = collection.map(lambda image: image.set('week', ee.Date(image.get('system:time_start')).format('YYYY-ww')))
326
+
327
+ # Group images by week
328
+ grouped_by_week = collection.aggregate_array('week').distinct()
 
 
 
 
 
 
 
 
 
 
 
329
 
330
+ def calculate_weekly_mean(week):
331
+ weekly_collection = collection.filter(ee.Filter.eq('week', week))
332
+ weekly_mean = weekly_collection.mean()
333
+ return weekly_mean.set('week', week)
334
 
335
+ # Calculate the weekly mean for each week
336
+ weekly_images = ee.List(grouped_by_week.map(calculate_weekly_mean))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
+ return ee.ImageCollection(weekly_images)
 
 
 
339
 
340
+ def aggregate_data_monthly(collection):
341
+ # Extract month and year from the image date
342
+ collection = collection.map(lambda image: image.set('month', ee.Date(image.get('system:time_start')).format('YYYY-MM')))
343
+
344
+ # Group images by month
345
+ grouped_by_month = collection.aggregate_array('month').distinct()
346
 
347
+ def calculate_monthly_mean(month):
348
+ monthly_collection = collection.filter(ee.Filter.eq('month', month))
349
+ monthly_mean = monthly_collection.mean()
350
+ return monthly_mean.set('month', month)
 
351
 
352
+ # Calculate the monthly mean for each month
353
+ monthly_images = ee.List(grouped_by_month.map(calculate_monthly_mean))
354
+
355
+ return ee.ImageCollection(monthly_images)
356
 
357
+ def aggregate_data_yearly(collection):
358
+ # Extract year from the image date
359
+ collection = collection.map(lambda image: image.set('year', ee.Date(image.get('system:time_start')).format('YYYY')))
360
+
361
+ # Group images by year
362
+ grouped_by_year = collection.aggregate_array('year').distinct()
363
 
364
+ def calculate_yearly_mean(year):
365
+ yearly_collection = collection.filter(ee.Filter.eq('year', year))
366
+ yearly_mean = yearly_collection.mean()
367
+ return yearly_mean.set('year', year)
 
368
 
369
+ # Calculate the yearly mean for each year
370
+ yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
371
+
372
+ return ee.ImageCollection(yearly_images)
373
+
374
+ # Function to calculate index based on the selected choice
375
+ def calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_formula):
376
+ if index_choice.lower() == 'ndvi':
377
+ return calculate_ndvi(image, roi, reducer_choice)
378
+ elif index_choice.lower() == 'ndwi':
379
+ return calculate_ndwi(image, roi, reducer_choice)
380
+ elif index_choice.lower() == 'average no₂':
381
+ mean_no2 = image.select('NO2').mean().rename('Average NO₂')
382
+ return mean_no2
383
+ elif index_choice.lower() == 'custom formula':
384
+ # Pass the custom formula here, not the index_choice
385
+ return calculate_custom_formula(image, roi, custom_formula, reducer_choice)
386
+ else:
387
+ st.write("Please Select any one option...."+ index_choice.lower())
388
 
389
+ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, index_choice, reducer_choice, shape_type, aggregation_period, custom_formula=""):
390
+ aggregated_results = []
391
 
392
+ # Check if the index_choice is 'custom formula' and the custom formula is empty
393
+ if index_choice.lower() == 'custom_formula' and not custom_formula:
394
+ st.error("Custom formula cannot be empty. Please provide a formula.")
395
+ return aggregated_results # Return early to avoid further processing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
+ # Initialize progress bar
398
+ total_steps = len(locations_df)
399
+ progress_bar = st.progress(0)
400
+ progress_text = st.empty()
401
+
402
+ with st.spinner('Processing data...'):
403
+ if shape_type.lower() == "point":
404
+ for idx, row in locations_df.iterrows():
405
+ # Check if the latitude and longitude columns exist and have values
406
+ latitude = row.get('latitude')
407
+ longitude = row.get('longitude')
 
 
 
 
 
 
 
 
 
 
 
 
 
408
 
409
+ if pd.isna(latitude) or pd.isna(longitude):
410
+ st.warning(f"Skipping location {idx} with missing latitude or longitude")
411
+ continue
412
+
413
+ location_name = row.get('name', f"Location_{idx}")
414
+
415
+ roi = ee.Geometry.Point([longitude, latitude])
416
+
417
+ collection = ee.ImageCollection(dataset_id) \
418
+ .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
419
+ .filterBounds(roi)
420
+
421
+ # Aggregate data based on the selected period
422
+ if aggregation_period.lower() == 'daily':
423
+ collection = aggregate_data_daily(collection)
424
+ elif aggregation_period.lower() == 'weekly':
425
+ collection = aggregate_data_weekly(collection)
426
+ elif aggregation_period.lower() == 'monthly':
427
+ collection = aggregate_data_monthly(collection)
428
+ elif aggregation_period.lower() == 'yearly':
429
+ collection = aggregate_data_yearly(collection)
430
+
431
+ # Process each image in the collection
432
+ image_list = collection.toList(collection.size())
433
+
434
+ for i in range(image_list.size().getInfo()):
435
+ image = ee.Image(image_list.get(i))
436
+
437
+ if aggregation_period.lower() == 'daily':
438
+ timestamp = image.get('day')
439
+ elif aggregation_period.lower() == 'weekly':
440
+ timestamp = image.get('week')
441
+ elif aggregation_period.lower() == 'monthly':
442
+ timestamp = image.get('month')
443
+ elif aggregation_period.lower() == 'yearly':
444
+ timestamp = image.get('year')
445
+
446
+ date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
447
+
448
+ # Calculate the index for each period
449
+ index_image = calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_formula)
450
+
451
+ # Skip if index_image is None
452
+ if index_image is None:
453
+ st.warning(f"Index calculation failed for {location_name} on {date}. Skipping this entry.")
454
+ continue
455
+
456
+ # Reduce the region to get the aggregated value
457
+ try:
458
+ index_value = index_image.reduceRegion(
459
+ reducer=get_reducer(reducer_choice),
460
+ geometry=roi,
461
+ scale=30
462
+ ).get(index_image.bandNames().get(0))
463
+
464
+ calculated_value = index_value.getInfo()
465
+
466
+ # Append the results if valid
467
+ if isinstance(calculated_value, (int, float)):
468
+ aggregated_results.append({
469
+ 'Location Name': location_name,
470
+ 'Latitude': latitude,
471
+ 'Longitude': longitude,
472
+ 'Date': date,
473
+ 'Calculated Value': calculated_value
474
+ })
475
+ else:
476
+ st.warning(f"Skipping invalid value for {location_name} on {date}")
477
+ except Exception as e:
478
+ st.error(f"Error retrieving value for {location_name}: {e}")
479
+
480
+ # Update progress bar
481
+ progress_percentage = (idx + 1) / total_steps
482
+ progress_bar.progress(progress_percentage)
483
+ progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
484
+
485
+ elif shape_type.lower() == "polygon":
486
+ for idx, row in locations_df.iterrows():
487
+ polygon_name = row.get('name', f"Polygon_{idx}")
488
+ polygon_geometry = row.get('geometry')
489
+
490
+ location_name = polygon_name
491
+
492
+ try:
493
+ roi = convert_to_ee_geometry(polygon_geometry)
494
+ except ValueError as e:
495
+ st.warning(f"Skipping invalid polygon {polygon_name}: {e}")
496
+ continue
497
+
498
+ collection = ee.ImageCollection(dataset_id) \
499
+ .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
500
+ .filterBounds(roi)
501
+
502
+ # Aggregate data based on the selected period
503
+ if aggregation_period.lower() == 'daily':
504
+ collection = aggregate_data_daily(collection)
505
+ elif aggregation_period.lower() == 'weekly':
506
+ collection = aggregate_data_weekly(collection)
507
+ elif aggregation_period.lower() == 'monthly':
508
+ collection = aggregate_data_monthly(collection)
509
+ elif aggregation_period.lower() == 'yearly':
510
+ collection = aggregate_data_yearly(collection)
511
+
512
+ # Process each image in the collection
513
+ image_list = collection.toList(collection.size())
514
+
515
+ for i in range(image_list.size().getInfo()):
516
+ image = ee.Image(image_list.get(i))
517
+
518
+ if aggregation_period.lower() == 'daily':
519
+ timestamp = image.get('day')
520
+ elif aggregation_period.lower() == 'weekly':
521
+ timestamp = image.get('week')
522
+ elif aggregation_period.lower() == 'monthly':
523
+ timestamp = image.get('month')
524
+ elif aggregation_period.lower() == 'yearly':
525
+ timestamp = image.get('year')
526
+
527
+ date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
528
+
529
+ # Calculate the index for each period
530
+ index_image = calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_formula)
531
+
532
+ # Skip if index_image is None
533
+ if index_image is None:
534
+ st.warning(f"Index calculation failed for {location_name} on {date}. Skipping this entry.")
535
+ continue
536
+
537
+ # Reduce the region to get the aggregated value
538
+ try:
539
+ index_value = index_image.reduceRegion(
540
+ reducer=get_reducer(reducer_choice),
541
+ geometry=roi,
542
+ scale=30
543
+ ).get(index_image.bandNames().get(0))
544
+
545
+ calculated_value = index_value.getInfo()
546
+
547
+ # Append the results if valid
548
+ if isinstance(calculated_value, (int, float)):
549
+ aggregated_results.append({
550
+ 'Location Name': location_name,
551
+ 'Date': date,
552
+ 'Calculated Value': calculated_value
553
+ })
554
+ else:
555
+ st.warning(f"Skipping invalid value for {location_name} on {date}")
556
+ except Exception as e:
557
+ st.error(f"Error retrieving value for {location_name}: {e}")
558
+
559
+ # Update progress bar
560
+ progress_percentage = (idx + 1) / total_steps
561
+ progress_bar.progress(progress_percentage)
562
+ progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
563
+
564
+ return aggregated_results
565
+
566
+ # When the user clicks the process button, start the calculation
567
+ if st.button(f"Calculate ({index_choice})"):
568
+ if file_upload is not None:
569
+ # Read the user-uploaded file
570
+ if shape_type.lower() == "point":
571
+ if file_upload.name.endswith('.csv'):
572
+ locations_df = read_csv(file_upload)
573
+ elif file_upload.name.endswith('.geojson'):
574
+ locations_df = read_geojson(file_upload)
575
+ elif file_upload.name.endswith('.kml'):
576
+ locations_df = read_kml(file_upload)
577
+ else:
578
+ st.error("Unsupported file format. Please upload CSV, GeoJSON, or KML.")
579
+ locations_df = pd.DataFrame()
580
+
581
+ # Process results for the selected aggregation period
582
+ results = process_aggregation(
583
+ locations_df,
584
+ start_date_str,
585
+ end_date_str,
586
+ dataset_id,
587
+ index_choice,
588
+ reducer_choice,
589
+ shape_type,
590
+ aggregation_period,
591
+ custom_formula
592
+ )
593
+
594
+ # Display the results in a DataFrame
595
+ if results:
596
+ result_df = pd.DataFrame(results)
597
+ st.write(f"Processed Results Table ({aggregation_period}):")
598
+ st.dataframe(result_df)
599
+
600
+ # Provide a download button for the result CSV file
601
+ filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{aggregation_period.lower()}.csv"
602
+ st.download_button(
603
+ label="Download results as CSV",
604
+ data=result_df.to_csv(index=False).encode('utf-8'),
605
+ file_name=filename,
606
+ mime='text/csv'
607
+ )
608
+
609
+ # Once processing is complete, hide the spinner
610
+ st.spinner('') # This will stop the spinner
611
+ st.success('Processing complete!')
612
+ else:
613
+ st.warning("No results were generated.")
614
+
615
+ elif shape_type.lower() == "polygon":
616
+ if file_upload.name.endswith('.geojson'):
617
+ locations_df = read_geojson(file_upload)
618
+ else:
619
+ st.error("Please upload a valid GeoJSON file for polygons.")
620
+
621
+ # Process results for the selected aggregation period
622
+ results = process_aggregation(
623
+ locations_df,
624
+ start_date_str,
625
+ end_date_str,
626
+ dataset_id,
627
+ index_choice,
628
+ reducer_choice,
629
+ shape_type,
630
+ aggregation_period,
631
+ custom_formula
632
+ )
633
+
634
+ # Display the results in a DataFrame
635
+ if results:
636
+ result_df = pd.DataFrame(results)
637
+ st.write(f"Processed Results Table ({aggregation_period}):")
638
+ st.dataframe(result_df)
639
+
640
+ # Provide a download button for the result CSV file
641
+ filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{aggregation_period.lower()}.csv"
642
+ st.download_button(
643
+ label="Download results as CSV",
644
+ data=result_df.to_csv(index=False).encode('utf-8'),
645
+ file_name=filename,
646
+ mime='text/csv'
647
+ )
648
+ # Once processing is complete, hide the spinner
649
+ st.spinner('') # This will stop the spinner
650
+ st.success('Processing complete!')
651
+ else:
652
+ st.warning("No results were generated.")
653
+
654
+ else:
655
+ st.warning("Please upload a file.")
656