YashMK89 commited on
Commit
d8c2f55
·
verified ·
1 Parent(s): 6a3e572

update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -57
app.py CHANGED
@@ -30,8 +30,8 @@ m = st.markdown(
30
  st.write(
31
  f"""
32
  <div style="display: flex; justify-content: space-between; align-items: center;">
33
- <img src="https://huggingface.co/spaces/YashMK89/GEE_Calculator/resolve/main/ISRO_Logo.png" style="width: 20%; margin-right: auto;">
34
- <img src="https://huggingface.co/spaces/YashMK89/GEE_Calculator/resolve/main/SAC_Logo.png" style="width: 20%; margin-left: auto;">
35
  </div>
36
  """,
37
  unsafe_allow_html=True,
@@ -56,20 +56,63 @@ with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
56
 
57
  ee.Initialize(project='ee-yashsacisro24')
58
 
59
- # Load the Sentinel dataset options from JSON file
60
- with open("sentinel_datasets.json") as f:
61
- data = json.load(f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  # Display the title for the Streamlit app
64
- st.title("Sentinel Dataset")
65
 
66
  # Select dataset category (main selection)
67
- main_selection = st.selectbox("Select Sentinel Dataset Category", list(data.keys()))
 
 
 
 
 
 
 
68
 
69
  # If a category is selected, display the sub-options (specific datasets)
70
  if main_selection:
71
  sub_options = data[main_selection]["sub_options"]
72
- sub_selection = st.selectbox("Select Specific Dataset ID", list(sub_options.keys()))
73
 
74
  # Display the selected dataset ID based on user input
75
  if sub_selection:
@@ -90,41 +133,56 @@ if main_selection and sub_selection:
90
  "Select 1 or 2 Bands for Calculation",
91
  options=dataset_bands,
92
  default=[dataset_bands[0]] if dataset_bands else [],
93
- help="Select at least 1 band and up to 2 bands."
94
  )
95
 
96
  # Ensure minimum 1 and maximum 2 bands are selected
97
  if len(selected_bands) < 1:
98
  st.warning("Please select at least one band.")
99
  st.stop()
100
- # elif len(selected_bands) > 2:
101
- # st.warning("You can select a maximum of 2 bands.")
102
- # st.stop()
103
 
104
  # Show custom formula input if bands are selected
105
  if selected_bands:
106
- default_formula = (
107
- f"{selected_bands[0]}" if len(selected_bands) == 1
108
- else f"({selected_bands[0]} - {selected_bands[1]}) / ({selected_bands[0]} + {selected_bands[1]})"
109
- )
 
 
 
 
110
  custom_formula = st.text_input(
111
- "Enter Custom Formula (e.g., 'B3*B5/2' or '(B8 - B4) / (B8 + B4)')",
112
  value=default_formula,
113
- help=f"Use {', '.join(selected_bands)} in your formula. Example: 'B3*B5/2'"
114
  )
115
 
116
- if not custom_formula:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  st.warning("Please enter a custom formula to proceed.")
118
  st.stop()
119
 
120
- # Display the formula
121
  st.write(f"Custom Formula: {custom_formula}")
 
 
122
 
123
  # Function to get the corresponding reducer based on user input
124
  def get_reducer(reducer_name):
125
- """
126
- Map user-friendly reducer names to Earth Engine reducer objects.
127
- """
128
  reducers = {
129
  'mean': ee.Reducer.mean(),
130
  'sum': ee.Reducer.sum(),
@@ -187,8 +245,12 @@ end_date = st.date_input("End Date", value=pd.to_datetime('2024-12-01'))
187
  start_date_str = start_date.strftime('%Y-%m-%d')
188
  end_date_str = end_date.strftime('%Y-%m-%d')
189
 
190
- # Aggregation period selection (changed "Daily" to "Custom (Start Date to End Date)")
191
- aggregation_period = st.selectbox("Select Aggregation Period (e.g, Custom(Start Date to End Date) , Weekly , Monthly , Yearly)", ["Custom (Start Date to End Date)", "Weekly", "Monthly", "Yearly"], index=0)
 
 
 
 
192
 
193
  # Ask user whether they want to process 'Point' or 'Polygon' data
194
  shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
@@ -209,7 +271,7 @@ elif shape_type.lower() == "polygon":
209
  value=True,
210
  help="Check to include pixels on the polygon boundary; uncheck to exclude them."
211
  )
212
-
213
  # Ask user to upload a file based on shape type
214
  file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"])
215
 
@@ -295,6 +357,8 @@ if 'last_params' not in st.session_state:
295
  st.session_state.last_params = {}
296
  if 'map_data' not in st.session_state:
297
  st.session_state.map_data = None
 
 
298
 
299
  # Function to check if parameters have changed
300
  def parameters_changed():
@@ -327,27 +391,26 @@ if parameters_changed():
327
  'include_boundary': include_boundary
328
  }
329
 
330
- # Function to calculate custom formula using eval safely
331
  def calculate_custom_formula(image, geometry, selected_bands, custom_formula, reducer_choice, scale=30):
332
  try:
333
  band_values = {}
 
 
334
  for band in selected_bands:
335
- band_names = image.bandNames().getInfo()
336
  if band not in band_names:
337
- raise ValueError(f"The band '{band}' does not exist in the image.")
338
  band_values[band] = image.select(band)
339
 
340
  reducer = get_reducer(reducer_choice)
341
  reduced_values = {}
342
  for band in selected_bands:
343
- reduced_value = band_values[band].reduceRegion(
344
  reducer=reducer,
345
  geometry=geometry,
346
  scale=scale
347
  ).get(band).getInfo()
348
- if reduced_value is None:
349
- reduced_value = 0
350
- reduced_values[band] = float(reduced_value)
351
 
352
  formula = custom_formula
353
  for band in selected_bands:
@@ -355,27 +418,28 @@ def calculate_custom_formula(image, geometry, selected_bands, custom_formula, re
355
 
356
  result = eval(formula, {"__builtins__": {}}, reduced_values)
357
  if not isinstance(result, (int, float)):
358
- raise ValueError("Formula evaluation did not result in a numeric value.")
 
359
  return ee.Image.constant(result).rename('custom_result')
360
 
361
  except ZeroDivisionError:
362
- st.error("Error: Division by zero occurred in the formula.")
363
  return ee.Image(0).rename('custom_result').set('error', 'Division by zero')
364
  except SyntaxError:
365
- st.error(f"Error: Invalid formula syntax in '{custom_formula}'.")
366
  return ee.Image(0).rename('custom_result').set('error', 'Invalid syntax')
367
  except ValueError as e:
368
  st.error(f"Error: {str(e)}")
369
  return ee.Image(0).rename('custom_result').set('error', str(e))
370
  except Exception as e:
371
- st.error(f"Unexpected error evaluating formula: {e}")
372
  return ee.Image(0).rename('custom_result').set('error', str(e))
373
 
374
  # Function to calculate index for a period
375
  def calculate_index_for_period(image, roi, selected_bands, custom_formula, reducer_choice):
376
  return calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice)
377
 
378
- # Aggregation functions (renamed aggregate_data_daily to aggregate_data_custom)
379
  def aggregate_data_custom(collection):
380
  collection = collection.map(lambda image: image.set('day', ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')))
381
  grouped_by_day = collection.aggregate_array('day').distinct()
@@ -423,7 +487,7 @@ def aggregate_data_yearly(collection):
423
  yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
424
  return ee.ImageCollection(yearly_images)
425
 
426
- # Process aggregation function with kernel and boundary options
427
  def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, custom_formula="", kernel_size=None, include_boundary=None):
428
  aggregated_results = []
429
 
@@ -446,14 +510,11 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
446
 
447
  location_name = row.get('name', f"Location_{idx}")
448
 
449
- # Define the region of interest based on kernel size
450
  if kernel_size == "3x3 Kernel":
451
- # Assuming 30m resolution, 3x3 kernel = 90m x 90m
452
- buffer_size = 45 # Half of 90m to center the square
453
  roi = ee.Geometry.Point([longitude, latitude]).buffer(buffer_size).bounds()
454
  elif kernel_size == "5x5 Kernel":
455
- # 5x5 kernel = 150m x 150m
456
- buffer_size = 75 # Half of 150m
457
  roi = ee.Geometry.Point([longitude, latitude]).buffer(buffer_size).bounds()
458
  else: # Point
459
  roi = ee.Geometry.Point([longitude, latitude])
@@ -462,7 +523,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
462
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
463
  .filterBounds(roi)
464
 
465
- # Changed 'daily' to 'custom (start date to end date)'
466
  if aggregation_period.lower() == 'custom (start date to end date)':
467
  collection = aggregate_data_custom(collection)
468
  elif aggregation_period.lower() == 'weekly':
@@ -477,7 +537,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
477
  for i in range(image_list.size().getInfo()):
478
  image = ee.Image(image_list.get(i))
479
 
480
- # Changed 'daily' to 'custom (start date to end date)'
481
  if aggregation_period.lower() == 'custom (start date to end date)':
482
  timestamp = image.get('day')
483
  period_label = 'Date'
@@ -539,7 +598,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
539
  try:
540
  roi = convert_to_ee_geometry(polygon_geometry)
541
  if not include_boundary:
542
- # Erode the polygon by a small buffer (e.g., 1 pixel = 30m) to exclude boundary
543
  roi = roi.buffer(-30).bounds()
544
  except ValueError as e:
545
  st.warning(f"Skipping invalid polygon {polygon_name}: {e}")
@@ -549,7 +607,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
549
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
550
  .filterBounds(roi)
551
 
552
- # Changed 'daily' to 'custom (start date to end date)'
553
  if aggregation_period.lower() == 'custom (start date to end date)':
554
  collection = aggregate_data_custom(collection)
555
  elif aggregation_period.lower() == 'weekly':
@@ -564,7 +621,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
564
  for i in range(image_list.size().getInfo()):
565
  image = ee.Image(image_list.get(i))
566
 
567
- # Changed 'daily' to 'custom (start date to end date)'
568
  if aggregation_period.lower() == 'custom (start date to end date)':
569
  timestamp = image.get('day')
570
  period_label = 'Date'
@@ -617,7 +673,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
617
 
618
  if aggregated_results:
619
  result_df = pd.DataFrame(aggregated_results)
620
- # Changed 'daily' to 'custom (start date to end date)'
621
  if aggregation_period.lower() == 'custom (start date to end date)':
622
  agg_dict = {
623
  'Start Date': 'first',
@@ -635,7 +690,7 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
635
  return []
636
 
637
  # Button to trigger calculation
638
- if st.button(f"Calculate({custom_formula})"):
639
  if file_upload is not None:
640
  if shape_type.lower() in ["point", "polygon"]:
641
  results = process_aggregation(
@@ -653,19 +708,41 @@ if st.button(f"Calculate({custom_formula})"):
653
  )
654
  if results:
655
  result_df = pd.DataFrame(results)
656
- st.write(f"Processed Results Table ({aggregation_period}):")
657
  st.dataframe(result_df)
658
- filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{aggregation_period.lower()}.csv"
659
  st.download_button(
660
  label="Download results as CSV",
661
  data=result_df.to_csv(index=False).encode('utf-8'),
662
  file_name=filename,
663
  mime='text/csv'
664
  )
665
- st.spinner('')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  st.success('Processing complete!')
667
  else:
668
- st.warning("No results were generated.")
669
  else:
670
- st.warning("Please upload a file.")
671
-
 
 
30
  st.write(
31
  f"""
32
  <div style="display: flex; justify-content: space-between; align-items: center;">
33
+ <img src="https://huggingface.co/spaces/YashMK89/GEE_Calculator/resolve/main/ISRO_Logo.png" style="width: 20%; margin-right: auto;">
34
+ <img src="https://huggingface.co/spaces/YashMK89/GEE_Calculator/resolve/main/SAC_Logo.png" style="width: 20%; margin-left: auto;">
35
  </div>
36
  """,
37
  unsafe_allow_html=True,
 
56
 
57
  ee.Initialize(project='ee-yashsacisro24')
58
 
59
+ # Imagery base selection
60
+ imagery_base = st.selectbox("Select Imagery Base", ["Sentinel", "Landsat", "MODIS", "Custom Input"], index=0)
61
+
62
+ # Load the appropriate dataset based on imagery base
63
+ if imagery_base == "Sentinel":
64
+ dataset_file = "sentinel_datasets.json"
65
+ with open(dataset_file) as f:
66
+ data = json.load(f)
67
+ elif imagery_base == "Landsat":
68
+ dataset_file = "landsat_datasets.json"
69
+ with open(dataset_file) as f:
70
+ data = json.load(f)
71
+ elif imagery_base == "MODIS":
72
+ dataset_file = "modis_datasets.json"
73
+ with open(dataset_file) as f:
74
+ data = json.load(f)
75
+ elif imagery_base == "Custom Input":
76
+ custom_dataset_id = st.text_input("Enter Custom Earth Engine Dataset ID (e.g., ee.ImageCollection('AHN/AHN4'))", value="")
77
+ if custom_dataset_id:
78
+ try:
79
+ # Remove potential "ee.ImageCollection()" wrapper for simplicity
80
+ if custom_dataset_id.startswith("ee.ImageCollection("):
81
+ custom_dataset_id = custom_dataset_id.replace("ee.ImageCollection('", "").replace("')", "")
82
+ # Fetch dataset info from GEE
83
+ collection = ee.ImageCollection(custom_dataset_id)
84
+ band_names = collection.first().bandNames().getInfo()
85
+ data = {
86
+ f"Custom Dataset: {custom_dataset_id}": {
87
+ "sub_options": {custom_dataset_id: f"Custom Dataset ({custom_dataset_id})"},
88
+ "bands": {custom_dataset_id: band_names}
89
+ }
90
+ }
91
+ st.write(f"Fetched bands for {custom_dataset_id}: {', '.join(band_names)}")
92
+ except Exception as e:
93
+ st.error(f"Error fetching dataset: {str(e)}. Please check the dataset ID and ensure it's valid in Google Earth Engine.")
94
+ data = {}
95
+ else:
96
+ st.warning("Please enter a custom dataset ID to proceed.")
97
+ data = {}
98
 
99
  # Display the title for the Streamlit app
100
+ st.title(f"{imagery_base} Dataset")
101
 
102
  # Select dataset category (main selection)
103
+ if data:
104
+ main_selection = st.selectbox(f"Select {imagery_base} Dataset Category", list(data.keys()))
105
+ else:
106
+ main_selection = None
107
+
108
+ # Initialize sub_selection and dataset_id as None
109
+ sub_selection = None
110
+ dataset_id = None
111
 
112
  # If a category is selected, display the sub-options (specific datasets)
113
  if main_selection:
114
  sub_options = data[main_selection]["sub_options"]
115
+ sub_selection = st.selectbox(f"Select Specific {imagery_base} Dataset ID", list(sub_options.keys()))
116
 
117
  # Display the selected dataset ID based on user input
118
  if sub_selection:
 
133
  "Select 1 or 2 Bands for Calculation",
134
  options=dataset_bands,
135
  default=[dataset_bands[0]] if dataset_bands else [],
136
+ help=f"Select 1 or 2 bands from: {', '.join(dataset_bands)}"
137
  )
138
 
139
  # Ensure minimum 1 and maximum 2 bands are selected
140
  if len(selected_bands) < 1:
141
  st.warning("Please select at least one band.")
142
  st.stop()
 
 
 
143
 
144
  # Show custom formula input if bands are selected
145
  if selected_bands:
146
+ # Provide a default formula based on the number of selected bands
147
+ if len(selected_bands) == 1:
148
+ default_formula = f"{selected_bands[0]}"
149
+ example = f"'{selected_bands[0]} * 2' or '{selected_bands[0]} + 1'"
150
+ else: # len(selected_bands) == 2
151
+ default_formula = f"({selected_bands[0]} - {selected_bands[1]}) / ({selected_bands[0]} + {selected_bands[1]})"
152
+ example = f"'{selected_bands[0]} * {selected_bands[1]} / 2' or '({selected_bands[0]} - {selected_bands[1]}) / ({selected_bands[0]} + {selected_bands[1]})'"
153
+
154
  custom_formula = st.text_input(
155
+ "Enter Custom Formula",
156
  value=default_formula,
157
+ help=f"Use only these bands: {', '.join(selected_bands)}. Examples: {example}"
158
  )
159
 
160
+ # Validate the formula
161
+ def validate_formula(formula, selected_bands):
162
+ allowed_chars = set(" +-*/()0123456789.")
163
+ terms = re.findall(r'[a-zA-Z][a-zA-Z0-9_]*', formula)
164
+ invalid_terms = [term for term in terms if term not in selected_bands]
165
+ if invalid_terms:
166
+ return False, f"Invalid terms in formula: {', '.join(invalid_terms)}. Use only {', '.join(selected_bands)}."
167
+ if not all(char in allowed_chars or char in ''.join(selected_bands) for char in formula):
168
+ return False, "Formula contains invalid characters. Use only bands, numbers, and operators (+, -, *, /, ())"
169
+ return True, ""
170
+
171
+ is_valid, error_message = validate_formula(custom_formula, selected_bands)
172
+ if not is_valid:
173
+ st.error(error_message)
174
+ st.stop()
175
+ elif not custom_formula:
176
  st.warning("Please enter a custom formula to proceed.")
177
  st.stop()
178
 
179
+ # Display the validated formula
180
  st.write(f"Custom Formula: {custom_formula}")
181
+
182
+ # The rest of your code (reducer, geometry conversion, date input, aggregation, etc.) remains unchanged...
183
 
184
  # Function to get the corresponding reducer based on user input
185
  def get_reducer(reducer_name):
 
 
 
186
  reducers = {
187
  'mean': ee.Reducer.mean(),
188
  'sum': ee.Reducer.sum(),
 
245
  start_date_str = start_date.strftime('%Y-%m-%d')
246
  end_date_str = end_date.strftime('%Y-%m-%d')
247
 
248
+ # Aggregation period selection
249
+ aggregation_period = st.selectbox(
250
+ "Select Aggregation Period (e.g, Custom(Start Date to End Date) , Weekly , Monthly , Yearly)",
251
+ ["Custom (Start Date to End Date)", "Weekly", "Monthly", "Yearly"],
252
+ index=0
253
+ )
254
 
255
  # Ask user whether they want to process 'Point' or 'Polygon' data
256
  shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
 
271
  value=True,
272
  help="Check to include pixels on the polygon boundary; uncheck to exclude them."
273
  )
274
+
275
  # Ask user to upload a file based on shape type
276
  file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"])
277
 
 
357
  st.session_state.last_params = {}
358
  if 'map_data' not in st.session_state:
359
  st.session_state.map_data = None
360
+ if 'show_example' not in st.session_state:
361
+ st.session_state.show_example = True
362
 
363
  # Function to check if parameters have changed
364
  def parameters_changed():
 
391
  'include_boundary': include_boundary
392
  }
393
 
394
+ # Function to calculate custom formula
395
  def calculate_custom_formula(image, geometry, selected_bands, custom_formula, reducer_choice, scale=30):
396
  try:
397
  band_values = {}
398
+ band_names = image.bandNames().getInfo()
399
+
400
  for band in selected_bands:
 
401
  if band not in band_names:
402
+ raise ValueError(f"Band '{band}' not found in the dataset.")
403
  band_values[band] = image.select(band)
404
 
405
  reducer = get_reducer(reducer_choice)
406
  reduced_values = {}
407
  for band in selected_bands:
408
+ value = band_values[band].reduceRegion(
409
  reducer=reducer,
410
  geometry=geometry,
411
  scale=scale
412
  ).get(band).getInfo()
413
+ reduced_values[band] = float(value if value is not None else 0)
 
 
414
 
415
  formula = custom_formula
416
  for band in selected_bands:
 
418
 
419
  result = eval(formula, {"__builtins__": {}}, reduced_values)
420
  if not isinstance(result, (int, float)):
421
+ raise ValueError("Formula did not result in a numeric value.")
422
+
423
  return ee.Image.constant(result).rename('custom_result')
424
 
425
  except ZeroDivisionError:
426
+ st.error("Error: Division by zero in the formula.")
427
  return ee.Image(0).rename('custom_result').set('error', 'Division by zero')
428
  except SyntaxError:
429
+ st.error(f"Error: Invalid syntax in formula '{custom_formula}'.")
430
  return ee.Image(0).rename('custom_result').set('error', 'Invalid syntax')
431
  except ValueError as e:
432
  st.error(f"Error: {str(e)}")
433
  return ee.Image(0).rename('custom_result').set('error', str(e))
434
  except Exception as e:
435
+ st.error(f"Unexpected error: {e}")
436
  return ee.Image(0).rename('custom_result').set('error', str(e))
437
 
438
  # Function to calculate index for a period
439
  def calculate_index_for_period(image, roi, selected_bands, custom_formula, reducer_choice):
440
  return calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice)
441
 
442
+ # Aggregation functions
443
  def aggregate_data_custom(collection):
444
  collection = collection.map(lambda image: image.set('day', ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')))
445
  grouped_by_day = collection.aggregate_array('day').distinct()
 
487
  yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
488
  return ee.ImageCollection(yearly_images)
489
 
490
+ # Process aggregation function
491
  def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, custom_formula="", kernel_size=None, include_boundary=None):
492
  aggregated_results = []
493
 
 
510
 
511
  location_name = row.get('name', f"Location_{idx}")
512
 
 
513
  if kernel_size == "3x3 Kernel":
514
+ buffer_size = 45 # 90m x 90m
 
515
  roi = ee.Geometry.Point([longitude, latitude]).buffer(buffer_size).bounds()
516
  elif kernel_size == "5x5 Kernel":
517
+ buffer_size = 75 # 150m x 150m
 
518
  roi = ee.Geometry.Point([longitude, latitude]).buffer(buffer_size).bounds()
519
  else: # Point
520
  roi = ee.Geometry.Point([longitude, latitude])
 
523
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
524
  .filterBounds(roi)
525
 
 
526
  if aggregation_period.lower() == 'custom (start date to end date)':
527
  collection = aggregate_data_custom(collection)
528
  elif aggregation_period.lower() == 'weekly':
 
537
  for i in range(image_list.size().getInfo()):
538
  image = ee.Image(image_list.get(i))
539
 
 
540
  if aggregation_period.lower() == 'custom (start date to end date)':
541
  timestamp = image.get('day')
542
  period_label = 'Date'
 
598
  try:
599
  roi = convert_to_ee_geometry(polygon_geometry)
600
  if not include_boundary:
 
601
  roi = roi.buffer(-30).bounds()
602
  except ValueError as e:
603
  st.warning(f"Skipping invalid polygon {polygon_name}: {e}")
 
607
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
608
  .filterBounds(roi)
609
 
 
610
  if aggregation_period.lower() == 'custom (start date to end date)':
611
  collection = aggregate_data_custom(collection)
612
  elif aggregation_period.lower() == 'weekly':
 
621
  for i in range(image_list.size().getInfo()):
622
  image = ee.Image(image_list.get(i))
623
 
 
624
  if aggregation_period.lower() == 'custom (start date to end date)':
625
  timestamp = image.get('day')
626
  period_label = 'Date'
 
673
 
674
  if aggregated_results:
675
  result_df = pd.DataFrame(aggregated_results)
 
676
  if aggregation_period.lower() == 'custom (start date to end date)':
677
  agg_dict = {
678
  'Start Date': 'first',
 
690
  return []
691
 
692
  # Button to trigger calculation
693
+ if st.button(f"Calculate {custom_formula}"):
694
  if file_upload is not None:
695
  if shape_type.lower() in ["point", "polygon"]:
696
  results = process_aggregation(
 
708
  )
709
  if results:
710
  result_df = pd.DataFrame(results)
711
+ st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
712
  st.dataframe(result_df)
713
+ filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
714
  st.download_button(
715
  label="Download results as CSV",
716
  data=result_df.to_csv(index=False).encode('utf-8'),
717
  file_name=filename,
718
  mime='text/csv'
719
  )
720
+ # Show an example calculation
721
+ if st.session_state.show_example and results:
722
+ example_result = results[0]
723
+ example_image = ee.ImageCollection(dataset_id).filterDate(start_date_str, end_date_str).first()
724
+ example_roi = (
725
+ ee.Geometry.Point([example_result['Longitude'], example_result['Latitude']])
726
+ if shape_type.lower() == 'point'
727
+ else convert_to_ee_geometry(locations_df['geometry'].iloc[0])
728
+ )
729
+ example_values = {}
730
+ for band in selected_bands:
731
+ value = example_image.select(band).reduceRegion(
732
+ reducer=get_reducer(reducer_choice),
733
+ geometry=example_roi,
734
+ scale=30
735
+ ).get(band).getInfo()
736
+ example_values[band] = float(value if value is not None else 0)
737
+ example_formula = custom_formula
738
+ for band in selected_bands:
739
+ example_formula = example_formula.replace(band, str(example_values[band]))
740
+ # st.write(f"Example Calculation: {custom_formula} -> {example_formula} = {example_result.get('Calculated Value', example_result.get('Aggregated Value'))}")
741
+ st.session_state.show_example = False
742
  st.success('Processing complete!')
743
  else:
744
+ st.warning("No results were generated. Check your inputs or formula.")
745
  else:
746
+ st.warning("Please upload a file to process.")
747
+ else:
748
+ st.warning("Please upload a file to proceed.")