YashMK89 commited on
Commit
ad7d92d
·
verified ·
1 Parent(s): 73468b1

update app.py

Browse files
Files changed (1) hide show
  1. app.py +989 -180
app.py CHANGED
@@ -1,3 +1,949 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import json
3
  import ee
@@ -114,19 +1060,7 @@ def calculate_custom_formula(image, geometry, selected_bands, custom_formula, re
114
  for band in selected_bands:
115
  band_scale = image.select(band).projection().nominalScale().getInfo()
116
  band_scales.append(band_scale)
117
-
118
- # Determine the finest (smallest) scale among the selected bands
119
  default_scale = min(band_scales) if band_scales else 30 # Default to 30m if no bands are found
120
-
121
- # # Compute the finest scale among all bands
122
- # band_scales = [
123
- # first_image.select(band).projection().nominalScale().getInfo()
124
- # for band in first_image.bandNames().getInfo()
125
- # ]
126
- # default_scale = min(band_scales)
127
-
128
- # Use user-defined scale if provided, otherwise use the finest scale
129
-
130
  scale = user_scale if user_scale is not None else default_scale
131
 
132
  # Rescale all bands to the chosen scale
@@ -135,7 +1069,6 @@ def calculate_custom_formula(image, geometry, selected_bands, custom_formula, re
135
  band_image = image.select(band)
136
  band_scale = band_image.projection().nominalScale().getInfo()
137
  if band_scale != scale:
138
- # Resample the band to match the target scale
139
  rescaled_band = band_image.resample('bilinear').reproject(
140
  crs=band_image.projection().crs(),
141
  scale=scale
@@ -151,7 +1084,7 @@ def calculate_custom_formula(image, geometry, selected_bands, custom_formula, re
151
  value = rescaled_bands[band].reduceRegion(
152
  reducer=reducer,
153
  geometry=geometry,
154
- scale=scale # Use the determined scale here
155
  ).get(band).getInfo()
156
  reduced_values[band] = float(value if value is not None else 0)
157
 
@@ -164,9 +1097,7 @@ def calculate_custom_formula(image, geometry, selected_bands, custom_formula, re
164
  # Validate the result
165
  if not isinstance(result, (int, float)):
166
  raise ValueError("Formula did not result in a numeric value.")
167
-
168
  return ee.Image.constant(result).rename('custom_result')
169
-
170
  except ZeroDivisionError:
171
  st.error("Error: Division by zero in the formula.")
172
  return ee.Image(0).rename('custom_result').set('error', 'Division by zero')
@@ -242,37 +1173,29 @@ def aggregate_data_yearly(collection):
242
  yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
243
  return ee.ImageCollection(yearly_images)
244
 
245
- # Define the function before using it
246
  def calculate_cloud_percentage(image, cloud_band='QA60'):
247
- """
248
- Calculate the percentage of cloud-covered pixels in an image using the QA60 bitmask.
249
- Assumes the presence of the QA60 cloud mask band.
250
- """
251
- # Decode the QA60 bitmask
252
  qa60 = image.select(cloud_band)
253
- opaque_clouds = qa60.bitwiseAnd(1 << 10) # Bit 10: Opaque clouds
254
- cirrus_clouds = qa60.bitwiseAnd(1 << 11) # Bit 11: Cirrus clouds
255
- # Combine both cloud types into a single cloud mask
256
  cloud_mask = opaque_clouds.Or(cirrus_clouds)
257
- # Count total pixels and cloudy pixels
258
  total_pixels = qa60.reduceRegion(
259
  reducer=ee.Reducer.count(),
260
  geometry=image.geometry(),
261
- scale=60, # QA60 resolution is 60 meters
262
  maxPixels=1e13
263
  ).get(cloud_band)
264
  cloudy_pixels = cloud_mask.reduceRegion(
265
  reducer=ee.Reducer.sum(),
266
  geometry=image.geometry(),
267
- scale=60, # QA60 resolution is 60 meters
268
  maxPixels=1e13
269
  ).get(cloud_band)
270
- # Calculate cloud percentage
271
  if total_pixels == 0:
272
- return 0 # Avoid division by zero
273
  return ee.Number(cloudy_pixels).divide(ee.Number(total_pixels)).multiply(100)
274
 
275
- # Use the function in preprocessing
276
  def preprocess_collection(collection, tile_cloud_threshold, pixel_cloud_threshold):
277
  def filter_tile(image):
278
  cloud_percentage = calculate_cloud_percentage(image, cloud_band='QA60')
@@ -290,6 +1213,7 @@ def preprocess_collection(collection, tile_cloud_threshold, pixel_cloud_threshol
290
  masked_collection = filtered_collection.map(mask_cloudy_pixels)
291
  return masked_collection
292
 
 
293
  def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, custom_formula, original_lat_col, original_lon_col, kernel_size=None, include_boundary=None, user_scale=None):
294
  if shape_type.lower() == "point":
295
  latitude = row.get('latitude')
@@ -314,9 +1238,11 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
314
  roi = roi.buffer(-30).bounds()
315
  except ValueError:
316
  return None
 
317
  collection = ee.ImageCollection(dataset_id) \
318
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
319
  .filterBounds(roi)
 
320
  if aggregation_period.lower() == 'custom (start date to end date)':
321
  collection = aggregate_data_custom(collection)
322
  elif aggregation_period.lower() == 'daily':
@@ -327,6 +1253,7 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
327
  collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
328
  elif aggregation_period.lower() == 'yearly':
329
  collection = aggregate_data_yearly(collection)
 
330
  image_list = collection.toList(collection.size())
331
  processed_weeks = set()
332
  aggregated_results = []
@@ -357,6 +1284,7 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
357
  timestamp = image.get('year')
358
  period_label = 'Year'
359
  date = ee.Date(timestamp).format('YYYY').getInfo()
 
360
  index_image = calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice, dataset_id, user_scale=user_scale)
361
  try:
362
  index_value = index_image.reduceRegion(
@@ -381,18 +1309,23 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
381
  st.error(f"Error retrieving value for {location_name}: {e}")
382
  return aggregated_results
383
 
 
384
  def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, original_lat_col, original_lon_col, custom_formula="", kernel_size=None, include_boundary=None, tile_cloud_threshold=0, pixel_cloud_threshold=0, user_scale=None):
385
  aggregated_results = []
386
  total_steps = len(locations_df)
387
  progress_bar = st.progress(0)
388
  progress_text = st.empty()
389
  start_time = time.time()
 
390
  raw_collection = ee.ImageCollection(dataset_id) \
391
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str))
 
392
  st.write(f"Original Collection Size: {raw_collection.size().getInfo()}")
 
393
  if tile_cloud_threshold > 0 or pixel_cloud_threshold > 0:
394
  raw_collection = preprocess_collection(raw_collection, tile_cloud_threshold, pixel_cloud_threshold)
395
  st.write(f"Preprocessed Collection Size: {raw_collection.size().getInfo()}")
 
396
  with ThreadPoolExecutor(max_workers=10) as executor:
397
  futures = []
398
  for idx, row in locations_df.iterrows():
@@ -423,15 +1356,17 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
423
  progress_percentage = completed / total_steps
424
  progress_bar.progress(progress_percentage)
425
  progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
 
426
  end_time = time.time()
427
  processing_time = end_time - start_time
 
428
  if aggregated_results:
429
  result_df = pd.DataFrame(aggregated_results)
430
  if aggregation_period.lower() == 'custom (start date to end date)':
431
  agg_dict = {
432
  'Start Date': 'first',
433
  'End Date': 'first',
434
- 'Calculated Value': 'mean' # Ensure this column is named 'Calculated Value'
435
  }
436
  if shape_type.lower() == 'point':
437
  agg_dict[original_lat_col] = 'first'
@@ -440,8 +1375,8 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
440
  aggregated_output['Date Range'] = aggregated_output['Start Date'] + " to " + aggregated_output['End Date']
441
  return aggregated_output.to_dict(orient='records'), processing_time
442
  else:
443
- return result_df.to_dict(orient='records'), processing_time
444
- return [], processing_time
445
 
446
  # Streamlit App Logic
447
  st.markdown("<h5>Image Collection</h5>", unsafe_allow_html=True)
@@ -518,12 +1453,10 @@ if main_selection:
518
  st.write(f"You selected: {main_selection} -> {sub_options[sub_selection]}")
519
  st.write(f"Dataset ID: {sub_selection}")
520
  dataset_id = sub_selection
521
-
522
  # Fetch the default scale for the selected dataset
523
  try:
524
  collection = ee.ImageCollection(dataset_id)
525
  first_image = collection.first()
526
- # Select the first band to avoid issues with multiple projections
527
  default_scale = first_image.select(0).projection().nominalScale().getInfo()
528
  st.write(f"Default Scale for Selected Dataset: {default_scale} meters")
529
  except Exception as e:
@@ -571,23 +1504,25 @@ if main_selection and sub_selection:
571
  st.warning("Please enter a custom formula to proceed.")
572
  st.stop()
573
  st.write(f"Custom Formula: {custom_formula}")
574
-
575
  reducer_choice = st.selectbox(
576
  "Select Reducer (e.g, mean , sum , median , min , max , count)",
577
  ['mean', 'sum', 'median', 'min', 'max', 'count'],
578
  index=0
579
  )
 
580
  start_date = st.date_input("Start Date", value=pd.to_datetime('2024-11-01'))
581
  end_date = st.date_input("End Date", value=pd.to_datetime('2024-12-01'))
582
  start_date_str = start_date.strftime('%Y-%m-%d')
583
  end_date_str = end_date.strftime('%Y-%m-%d')
 
584
  if imagery_base == "Sentinel" and "Sentinel-2" in sub_options[sub_selection]:
585
  st.markdown("<h5>Cloud Filtering</h5>", unsafe_allow_html=True)
586
  tile_cloud_threshold = st.slider(
587
  "Select Maximum Tile-Based Cloud Coverage Threshold (%)",
588
  min_value=0,
589
  max_value=100,
590
- value=20,
591
  step=5,
592
  help="Tiles with cloud coverage exceeding this threshold will be excluded."
593
  )
@@ -595,18 +1530,21 @@ if imagery_base == "Sentinel" and "Sentinel-2" in sub_options[sub_selection]:
595
  "Select Maximum Pixel-Based Cloud Coverage Threshold (%)",
596
  min_value=0,
597
  max_value=100,
598
- value=10,
599
  step=5,
600
  help="Individual pixels with cloud coverage exceeding this threshold will be masked."
601
  )
 
602
  aggregation_period = st.selectbox(
603
  "Select Aggregation Period (e.g, Custom(Start Date to End Date) , Daily , Weekly , Monthly , Yearly)",
604
  ["Custom (Start Date to End Date)", "Daily", "Weekly", "Monthly", "Yearly"],
605
  index=0
606
  )
 
607
  shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
608
  kernel_size = None
609
  include_boundary = None
 
610
  if shape_type.lower() == "point":
611
  kernel_size = st.selectbox(
612
  "Select Calculation Area(e.g, Point , 3x3 Kernel , 5x5 Kernel)",
@@ -620,6 +1558,7 @@ elif shape_type.lower() == "polygon":
620
  value=True,
621
  help="Check to include pixels on the polygon boundary; uncheck to exclude them."
622
  )
 
623
  st.markdown("<h5>Calculation Scale</h5>", unsafe_allow_html=True)
624
  default_scale = ee.ImageCollection(dataset_id).first().select(0).projection().nominalScale().getInfo()
625
  user_scale = st.number_input(
@@ -633,6 +1572,7 @@ file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KM
633
  locations_df = pd.DataFrame()
634
  original_lat_col = None
635
  original_lon_col = None
 
636
  if file_upload is not None:
637
  if shape_type.lower() == "point":
638
  if file_upload.name.endswith('.csv'):
@@ -752,7 +1692,6 @@ if st.button(f"Calculate {custom_formula}"):
752
  if not locations_df.empty:
753
  with st.spinner("Processing Data..."):
754
  try:
755
- # Call the aggregation function with updated parameters
756
  results, processing_time = process_aggregation(
757
  locations_df,
758
  start_date_str,
@@ -771,14 +1710,10 @@ if st.button(f"Calculate {custom_formula}"):
771
  pixel_cloud_threshold=pixel_cloud_threshold if "pixel_cloud_threshold" in locals() else 0,
772
  user_scale=user_scale
773
  )
774
-
775
- # Process and display results
776
  if results:
777
  result_df = pd.DataFrame(results)
778
  st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
779
  st.dataframe(result_df)
780
-
781
- # Download button for CSV
782
  filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
783
  st.download_button(
784
  label="Download results as CSV",
@@ -786,14 +1721,8 @@ if st.button(f"Calculate {custom_formula}"):
786
  file_name=filename,
787
  mime='text/csv'
788
  )
789
-
790
- # Success message
791
  st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
792
-
793
- # Graph Visualization Section
794
  st.markdown("<h5>Graph Visualization</h5>", unsafe_allow_html=True)
795
-
796
- # Dynamically identify the time column
797
  if aggregation_period.lower() == 'custom (start date to end date)':
798
  x_column = 'Date Range'
799
  elif 'Date' in result_df.columns:
@@ -807,139 +1736,19 @@ if st.button(f"Calculate {custom_formula}"):
807
  else:
808
  st.warning("No valid time column found for plotting.")
809
  st.stop()
810
-
811
- # Dynamically identify the value column
812
- y_column = None
813
- if 'Calculated Value' in result_df.columns:
814
- y_column = 'Calculated Value'
815
- elif 'Aggregated Value' in result_df.columns:
816
- y_column = 'Aggregated Value'
817
- else:
818
- st.warning("No value column found for plotting. Available columns: " + ", ".join(result_df.columns))
819
- st.stop()
820
-
821
- # Ensure we have valid data to plot
822
- if result_df.empty:
823
- st.warning("No data available for plotting.")
824
- st.stop()
825
-
826
- # # Line Chart
827
- # try:
828
- # st.subheader("Line Chart")
829
- # if x_column == 'Location Name':
830
- # st.line_chart(result_df.set_index(x_column)[y_column])
831
- # else:
832
- # # Convert to datetime for better sorting
833
- # result_df[x_column] = pd.to_datetime(result_df[x_column], errors='ignore')
834
- # result_df = result_df.sort_values(x_column)
835
- # st.line_chart(result_df.set_index(x_column)[y_column])
836
- # except Exception as e:
837
- # st.error(f"Error creating line chart: {str(e)}")
838
-
839
- # # Bar Chart
840
- # try:
841
- # st.subheader("Bar Chart")
842
- # if x_column == 'Location Name':
843
- # st.bar_chart(result_df.set_index(x_column)[y_column])
844
- # else:
845
- # result_df[x_column] = pd.to_datetime(result_df[x_column], errors='ignore')
846
- # result_df = result_df.sort_values(x_column)
847
- # st.bar_chart(result_df.set_index(x_column)[y_column])
848
- # except Exception as e:
849
- # st.error(f"Error creating bar chart: {str(e)}")
850
-
851
- # Advanced Plot (Plotly)
852
- try:
853
- st.subheader("Advanced Interactive Plot (Plotly)")
854
- if x_column == 'Location Name':
855
- fig = px.bar(
856
- result_df,
857
- x=x_column,
858
- y=y_column,
859
- color='Location Name',
860
- title=f"{custom_formula} by Location"
861
- )
862
- else:
863
- fig = px.line(
864
- result_df,
865
- x=x_column,
866
- y=y_column,
867
- color='Location Name',
868
- title=f"{custom_formula} Over Time"
869
- )
870
- st.plotly_chart(fig)
871
- except Exception as e:
872
- st.error(f"Error creating interactive plot: {str(e)}")
873
-
874
  else:
875
  st.warning("No results were generated. Check your inputs or formula.")
876
  st.info(f"Total processing time: {processing_time:.2f} seconds.")
877
-
878
  except Exception as e:
879
  st.error(f"An error occurred during processing: {str(e)}")
880
  else:
881
- st.warning("Please upload a valid file to proceed.")
882
- # if st.button(f"Calculate {custom_formula}"):
883
- # if not locations_df.empty:
884
- # with st.spinner("Processing Data..."):
885
- # try:
886
- # results, processing_time = process_aggregation(
887
- # locations_df,
888
- # start_date_str,
889
- # end_date_str,
890
- # dataset_id,
891
- # selected_bands,
892
- # reducer_choice,
893
- # shape_type,
894
- # aggregation_period,
895
- # original_lat_col,
896
- # original_lon_col,
897
- # custom_formula,
898
- # kernel_size,
899
- # include_boundary,
900
- # tile_cloud_threshold=tile_cloud_threshold if "tile_cloud_threshold" in locals() else 0,
901
- # pixel_cloud_threshold=pixel_cloud_threshold if "pixel_cloud_threshold" in locals() else 0,
902
- # user_scale=user_scale
903
- # )
904
- # if results:
905
- # result_df = pd.DataFrame(results)
906
- # st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
907
- # st.dataframe(result_df)
908
- # filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
909
- # st.download_button(
910
- # label="Download results as CSV",
911
- # data=result_df.to_csv(index=False).encode('utf-8'),
912
- # file_name=filename,
913
- # mime='text/csv'
914
- # )
915
- # st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
916
- # st.markdown("<h5>Graph Visualization</h5>", unsafe_allow_html=True)
917
- # if aggregation_period.lower() == 'custom (start date to end date)':
918
- # x_column = 'Date Range'
919
- # elif 'Date' in result_df.columns:
920
- # x_column = 'Date'
921
- # elif 'Week' in result_df.columns:
922
- # x_column = 'Week'
923
- # elif 'Month' in result_df.columns:
924
- # x_column = 'Month'
925
- # elif 'Year' in result_df.columns:
926
- # x_column = 'Year'
927
- # else:
928
- # st.warning("No valid time column found for plotting.")
929
- # st.stop()
930
- # y_column = 'Calculated Value'
931
- # fig = px.line(
932
- # result_df,
933
- # x=x_column,
934
- # y=y_column,
935
- # color='Location Name',
936
- # title=f"{custom_formula} Over Time"
937
- # )
938
- # st.plotly_chart(fig)
939
- # else:
940
- # st.warning("No results were generated. Check your inputs or formula.")
941
- # st.info(f"Total processing time: {processing_time:.2f} seconds.")
942
- # except Exception as e:
943
- # st.error(f"An error occurred during processing: {str(e)}")
944
- # else:
945
- # st.warning("Please upload a valid file to proceed.")
 
1
+ # import streamlit as st
2
+ # import json
3
+ # import ee
4
+ # import os
5
+ # import pandas as pd
6
+ # import geopandas as gpd
7
+ # from datetime import datetime
8
+ # import leafmap.foliumap as leafmap
9
+ # import re
10
+ # from shapely.geometry import base
11
+ # from xml.etree import ElementTree as XET
12
+ # from concurrent.futures import ThreadPoolExecutor, as_completed
13
+ # import time
14
+ # import matplotlib.pyplot as plt
15
+ # import plotly.express as px
16
+
17
+ # # Set up the page layout
18
+ # st.set_page_config(layout="wide")
19
+
20
+ # # Custom button styling
21
+ # m = st.markdown(
22
+ # """
23
+ # <style>
24
+ # div.stButton > button:first-child {
25
+ # background-color: #006400;
26
+ # color:#ffffff;
27
+ # }
28
+ # </style>""",
29
+ # unsafe_allow_html=True,
30
+ # )
31
+
32
+ # # Logo and Title
33
+ # st.write(
34
+ # f"""
35
+ # <div style="display: flex; justify-content: space-between; align-items: center;">
36
+ # <img src="https://huggingface.co/spaces/YashMK89/SATRANG/resolve/main/ISRO_Logo.png" style="width: 20%; margin-right: auto;">
37
+ # <img src="https://huggingface.co/spaces/YashMK89/SATRANG/resolve/main/SAC_Logo.png" style="width: 20%; margin-left: auto;">
38
+ # </div>
39
+ # """,
40
+ # unsafe_allow_html=True,
41
+ # )
42
+ # st.markdown(
43
+ # f"""
44
+ # <div style="display: flex; flex-direction: column; align-items: center;">
45
+ # <img src="https://huggingface.co/spaces/YashMK89/SATRANG/resolve/main/SATRANG.png" style="width: 30%;">
46
+ # <h3 style="text-align: center; margin: 0;">( Spatial and Temporal Aggregation for Remote-sensing Analysis of GEE Data )</h3>
47
+ # </div>
48
+ # <hr>
49
+ # """,
50
+ # unsafe_allow_html=True,
51
+ # )
52
+
53
+ # # Authenticate and initialize Earth Engine
54
+ # earthengine_credentials = os.environ.get("EE_Authentication")
55
+ # os.makedirs(os.path.expanduser("~/.config/earthengine/"), exist_ok=True)
56
+ # with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f:
57
+ # f.write(earthengine_credentials)
58
+ # ee.Initialize(project='ee-yashsacisro24')
59
+
60
+ # # Helper function to get reducer
61
+ # def get_reducer(reducer_name):
62
+ # reducers = {
63
+ # 'mean': ee.Reducer.mean(),
64
+ # 'sum': ee.Reducer.sum(),
65
+ # 'median': ee.Reducer.median(),
66
+ # 'min': ee.Reducer.min(),
67
+ # 'max': ee.Reducer.max(),
68
+ # 'count': ee.Reducer.count(),
69
+ # }
70
+ # return reducers.get(reducer_name.lower(), ee.Reducer.mean())
71
+
72
+ # # Function to convert geometry to Earth Engine format
73
+ # def convert_to_ee_geometry(geometry):
74
+ # if isinstance(geometry, base.BaseGeometry):
75
+ # if geometry.is_valid:
76
+ # geojson = geometry.__geo_interface__
77
+ # return ee.Geometry(geojson)
78
+ # else:
79
+ # raise ValueError("Invalid geometry: The polygon geometry is not valid.")
80
+ # elif isinstance(geometry, dict) or isinstance(geometry, str):
81
+ # try:
82
+ # if isinstance(geometry, str):
83
+ # geometry = json.loads(geometry)
84
+ # if 'type' in geometry and 'coordinates' in geometry:
85
+ # return ee.Geometry(geometry)
86
+ # else:
87
+ # raise ValueError("GeoJSON format is invalid.")
88
+ # except Exception as e:
89
+ # raise ValueError(f"Error parsing GeoJSON: {e}")
90
+ # elif isinstance(geometry, str) and geometry.lower().endswith(".kml"):
91
+ # try:
92
+ # tree = XET.parse(geometry)
93
+ # kml_root = tree.getroot()
94
+ # kml_namespace = {'kml': 'http://www.opengis.net/kml/2.2'}
95
+ # coordinates = kml_root.findall(".//kml:coordinates", kml_namespace)
96
+ # if coordinates:
97
+ # coords_text = coordinates[0].text.strip()
98
+ # coords = coords_text.split()
99
+ # coords = [tuple(map(float, coord.split(','))) for coord in coords]
100
+ # geojson = {"type": "Polygon", "coordinates": [coords]}
101
+ # return ee.Geometry(geojson)
102
+ # else:
103
+ # raise ValueError("KML does not contain valid coordinates.")
104
+ # except Exception as e:
105
+ # raise ValueError(f"Error parsing KML: {e}")
106
+ # else:
107
+ # raise ValueError("Unsupported geometry input type. Supported types are Shapely, GeoJSON, and KML.")
108
+
109
+ # # Function to calculate custom formula with dynamic scale handling
110
+ # def calculate_custom_formula(image, geometry, selected_bands, custom_formula, reducer_choice, dataset_id, user_scale=None):
111
+ # try:
112
+ # # Fetch the nominal scales of the selected bands
113
+ # band_scales = []
114
+ # for band in selected_bands:
115
+ # band_scale = image.select(band).projection().nominalScale().getInfo()
116
+ # band_scales.append(band_scale)
117
+
118
+ # # Determine the finest (smallest) scale among the selected bands
119
+ # default_scale = min(band_scales) if band_scales else 30 # Default to 30m if no bands are found
120
+
121
+ # # # Compute the finest scale among all bands
122
+ # # band_scales = [
123
+ # # first_image.select(band).projection().nominalScale().getInfo()
124
+ # # for band in first_image.bandNames().getInfo()
125
+ # # ]
126
+ # # default_scale = min(band_scales)
127
+
128
+ # # Use user-defined scale if provided, otherwise use the finest scale
129
+
130
+ # scale = user_scale if user_scale is not None else default_scale
131
+
132
+ # # Rescale all bands to the chosen scale
133
+ # rescaled_bands = {}
134
+ # for band in selected_bands:
135
+ # band_image = image.select(band)
136
+ # band_scale = band_image.projection().nominalScale().getInfo()
137
+ # if band_scale != scale:
138
+ # # Resample the band to match the target scale
139
+ # rescaled_band = band_image.resample('bilinear').reproject(
140
+ # crs=band_image.projection().crs(),
141
+ # scale=scale
142
+ # )
143
+ # rescaled_bands[band] = rescaled_band
144
+ # else:
145
+ # rescaled_bands[band] = band_image
146
+
147
+ # # Validate and extract band values
148
+ # reduced_values = {}
149
+ # reducer = get_reducer(reducer_choice)
150
+ # for band in selected_bands:
151
+ # value = rescaled_bands[band].reduceRegion(
152
+ # reducer=reducer,
153
+ # geometry=geometry,
154
+ # scale=scale # Use the determined scale here
155
+ # ).get(band).getInfo()
156
+ # reduced_values[band] = float(value if value is not None else 0)
157
+
158
+ # # Evaluate the custom formula
159
+ # formula = custom_formula
160
+ # for band in selected_bands:
161
+ # formula = formula.replace(band, str(reduced_values[band]))
162
+ # result = eval(formula, {"__builtins__": {}}, reduced_values)
163
+
164
+ # # Validate the result
165
+ # if not isinstance(result, (int, float)):
166
+ # raise ValueError("Formula did not result in a numeric value.")
167
+
168
+ # return ee.Image.constant(result).rename('custom_result')
169
+
170
+ # except ZeroDivisionError:
171
+ # st.error("Error: Division by zero in the formula.")
172
+ # return ee.Image(0).rename('custom_result').set('error', 'Division by zero')
173
+ # except SyntaxError:
174
+ # st.error(f"Error: Invalid syntax in formula '{custom_formula}'.")
175
+ # return ee.Image(0).rename('custom_result').set('error', 'Invalid syntax')
176
+ # except ValueError as e:
177
+ # st.error(f"Error: {str(e)}")
178
+ # return ee.Image(0).rename('custom_result').set('error', str(e))
179
+ # except Exception as e:
180
+ # st.error(f"Unexpected error: {e}")
181
+ # return ee.Image(0).rename('custom_result').set('error', str(e))
182
+
183
+ # # Aggregation functions
184
+ # def aggregate_data_custom(collection):
185
+ # collection = collection.map(lambda image: image.set('day', ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')))
186
+ # grouped_by_day = collection.aggregate_array('day').distinct()
187
+ # def calculate_daily_mean(day):
188
+ # daily_collection = collection.filter(ee.Filter.eq('day', day))
189
+ # daily_mean = daily_collection.mean()
190
+ # return daily_mean.set('day', day)
191
+ # daily_images = ee.List(grouped_by_day.map(calculate_daily_mean))
192
+ # return ee.ImageCollection(daily_images)
193
+
194
+ # def aggregate_data_daily(collection):
195
+ # def set_day_start(image):
196
+ # date = ee.Date(image.get('system:time_start'))
197
+ # day_start = date.format('YYYY-MM-dd')
198
+ # return image.set('day_start', day_start)
199
+ # collection = collection.map(set_day_start)
200
+ # grouped_by_day = collection.aggregate_array('day_start').distinct()
201
+ # def calculate_daily_mean(day_start):
202
+ # daily_collection = collection.filter(ee.Filter.eq('day_start', day_start))
203
+ # daily_mean = daily_collection.mean()
204
+ # return daily_mean.set('day_start', day_start)
205
+ # daily_images = ee.List(grouped_by_day.map(calculate_daily_mean))
206
+ # return ee.ImageCollection(daily_images)
207
+
208
+ # def aggregate_data_weekly(collection, start_date_str, end_date_str):
209
+ # start_date = ee.Date(start_date_str)
210
+ # end_date = ee.Date(end_date_str)
211
+ # days_diff = end_date.difference(start_date, 'day')
212
+ # num_weeks = days_diff.divide(7).ceil().getInfo()
213
+ # weekly_images = []
214
+ # for week in range(num_weeks):
215
+ # week_start = start_date.advance(week * 7, 'day')
216
+ # week_end = week_start.advance(7, 'day')
217
+ # weekly_collection = collection.filterDate(week_start, week_end)
218
+ # if weekly_collection.size().getInfo() > 0:
219
+ # weekly_mean = weekly_collection.mean()
220
+ # weekly_mean = weekly_mean.set('week_start', week_start.format('YYYY-MM-dd'))
221
+ # weekly_images.append(weekly_mean)
222
+ # return ee.ImageCollection.fromImages(weekly_images)
223
+
224
+ # def aggregate_data_monthly(collection, start_date, end_date):
225
+ # collection = collection.filterDate(start_date, end_date)
226
+ # collection = collection.map(lambda image: image.set('month', ee.Date(image.get('system:time_start')).format('YYYY-MM')))
227
+ # grouped_by_month = collection.aggregate_array('month').distinct()
228
+ # def calculate_monthly_mean(month):
229
+ # monthly_collection = collection.filter(ee.Filter.eq('month', month))
230
+ # monthly_mean = monthly_collection.mean()
231
+ # return monthly_mean.set('month', month)
232
+ # monthly_images = ee.List(grouped_by_month.map(calculate_monthly_mean))
233
+ # return ee.ImageCollection(monthly_images)
234
+
235
+ # def aggregate_data_yearly(collection):
236
+ # collection = collection.map(lambda image: image.set('year', ee.Date(image.get('system:time_start')).format('YYYY')))
237
+ # grouped_by_year = collection.aggregate_array('year').distinct()
238
+ # def calculate_yearly_mean(year):
239
+ # yearly_collection = collection.filter(ee.Filter.eq('year', year))
240
+ # yearly_mean = yearly_collection.mean()
241
+ # return yearly_mean.set('year', year)
242
+ # yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
243
+ # return ee.ImageCollection(yearly_images)
244
+
245
+ # # Define the function before using it
246
+ # def calculate_cloud_percentage(image, cloud_band='QA60'):
247
+ # """
248
+ # Calculate the percentage of cloud-covered pixels in an image using the QA60 bitmask.
249
+ # Assumes the presence of the QA60 cloud mask band.
250
+ # """
251
+ # # Decode the QA60 bitmask
252
+ # qa60 = image.select(cloud_band)
253
+ # opaque_clouds = qa60.bitwiseAnd(1 << 10) # Bit 10: Opaque clouds
254
+ # cirrus_clouds = qa60.bitwiseAnd(1 << 11) # Bit 11: Cirrus clouds
255
+ # # Combine both cloud types into a single cloud mask
256
+ # cloud_mask = opaque_clouds.Or(cirrus_clouds)
257
+ # # Count total pixels and cloudy pixels
258
+ # total_pixels = qa60.reduceRegion(
259
+ # reducer=ee.Reducer.count(),
260
+ # geometry=image.geometry(),
261
+ # scale=60, # QA60 resolution is 60 meters
262
+ # maxPixels=1e13
263
+ # ).get(cloud_band)
264
+ # cloudy_pixels = cloud_mask.reduceRegion(
265
+ # reducer=ee.Reducer.sum(),
266
+ # geometry=image.geometry(),
267
+ # scale=60, # QA60 resolution is 60 meters
268
+ # maxPixels=1e13
269
+ # ).get(cloud_band)
270
+ # # Calculate cloud percentage
271
+ # if total_pixels == 0:
272
+ # return 0 # Avoid division by zero
273
+ # return ee.Number(cloudy_pixels).divide(ee.Number(total_pixels)).multiply(100)
274
+
275
+ # # Use the function in preprocessing
276
+ # def preprocess_collection(collection, tile_cloud_threshold, pixel_cloud_threshold):
277
+ # def filter_tile(image):
278
+ # cloud_percentage = calculate_cloud_percentage(image, cloud_band='QA60')
279
+ # return image.set('cloud_percentage', cloud_percentage).updateMask(cloud_percentage.lt(tile_cloud_threshold))
280
+
281
+ # def mask_cloudy_pixels(image):
282
+ # qa60 = image.select('QA60')
283
+ # opaque_clouds = qa60.bitwiseAnd(1 << 10)
284
+ # cirrus_clouds = qa60.bitwiseAnd(1 << 11)
285
+ # cloud_mask = opaque_clouds.Or(cirrus_clouds)
286
+ # clear_pixels = cloud_mask.Not()
287
+ # return image.updateMask(clear_pixels)
288
+
289
+ # filtered_collection = collection.map(filter_tile)
290
+ # masked_collection = filtered_collection.map(mask_cloudy_pixels)
291
+ # return masked_collection
292
+
293
+ # def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, custom_formula, original_lat_col, original_lon_col, kernel_size=None, include_boundary=None, user_scale=None):
294
+ # if shape_type.lower() == "point":
295
+ # latitude = row.get('latitude')
296
+ # longitude = row.get('longitude')
297
+ # if pd.isna(latitude) or pd.isna(longitude):
298
+ # return None
299
+ # location_name = row.get('name', f"Location_{row.name}")
300
+ # if kernel_size == "3x3 Kernel":
301
+ # buffer_size = 45
302
+ # roi = ee.Geometry.Point([longitude, latitude]).buffer(buffer_size).bounds()
303
+ # elif kernel_size == "5x5 Kernel":
304
+ # buffer_size = 75
305
+ # roi = ee.Geometry.Point([longitude, latitude]).buffer(buffer_size).bounds()
306
+ # else:
307
+ # roi = ee.Geometry.Point([longitude, latitude])
308
+ # elif shape_type.lower() == "polygon":
309
+ # polygon_geometry = row.get('geometry')
310
+ # location_name = row.get('name', f"Polygon_{row.name}")
311
+ # try:
312
+ # roi = convert_to_ee_geometry(polygon_geometry)
313
+ # if not include_boundary:
314
+ # roi = roi.buffer(-30).bounds()
315
+ # except ValueError:
316
+ # return None
317
+ # collection = ee.ImageCollection(dataset_id) \
318
+ # .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
319
+ # .filterBounds(roi)
320
+ # if aggregation_period.lower() == 'custom (start date to end date)':
321
+ # collection = aggregate_data_custom(collection)
322
+ # elif aggregation_period.lower() == 'daily':
323
+ # collection = aggregate_data_daily(collection)
324
+ # elif aggregation_period.lower() == 'weekly':
325
+ # collection = aggregate_data_weekly(collection, start_date_str, end_date_str)
326
+ # elif aggregation_period.lower() == 'monthly':
327
+ # collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
328
+ # elif aggregation_period.lower() == 'yearly':
329
+ # collection = aggregate_data_yearly(collection)
330
+ # image_list = collection.toList(collection.size())
331
+ # processed_weeks = set()
332
+ # aggregated_results = []
333
+ # for i in range(image_list.size().getInfo()):
334
+ # image = ee.Image(image_list.get(i))
335
+ # if aggregation_period.lower() == 'custom (start date to end date)':
336
+ # timestamp = image.get('day')
337
+ # period_label = 'Date'
338
+ # date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
339
+ # elif aggregation_period.lower() == 'daily':
340
+ # timestamp = image.get('day_start')
341
+ # period_label = 'Date'
342
+ # date = ee.String(timestamp).getInfo()
343
+ # elif aggregation_period.lower() == 'weekly':
344
+ # timestamp = image.get('week_start')
345
+ # period_label = 'Week'
346
+ # date = ee.String(timestamp).getInfo()
347
+ # if (pd.to_datetime(date) < pd.to_datetime(start_date_str) or
348
+ # pd.to_datetime(date) > pd.to_datetime(end_date_str) or
349
+ # date in processed_weeks):
350
+ # continue
351
+ # processed_weeks.add(date)
352
+ # elif aggregation_period.lower() == 'monthly':
353
+ # timestamp = image.get('month')
354
+ # period_label = 'Month'
355
+ # date = ee.Date(timestamp).format('YYYY-MM').getInfo()
356
+ # elif aggregation_period.lower() == 'yearly':
357
+ # timestamp = image.get('year')
358
+ # period_label = 'Year'
359
+ # date = ee.Date(timestamp).format('YYYY').getInfo()
360
+ # index_image = calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice, dataset_id, user_scale=user_scale)
361
+ # try:
362
+ # index_value = index_image.reduceRegion(
363
+ # reducer=get_reducer(reducer_choice),
364
+ # geometry=roi,
365
+ # scale=user_scale
366
+ # ).get('custom_result')
367
+ # calculated_value = index_value.getInfo()
368
+ # if isinstance(calculated_value, (int, float)):
369
+ # result = {
370
+ # 'Location Name': location_name,
371
+ # period_label: date,
372
+ # 'Start Date': start_date_str,
373
+ # 'End Date': end_date_str,
374
+ # 'Calculated Value': calculated_value
375
+ # }
376
+ # if shape_type.lower() == 'point':
377
+ # result[original_lat_col] = latitude
378
+ # result[original_lon_col] = longitude
379
+ # aggregated_results.append(result)
380
+ # except Exception as e:
381
+ # st.error(f"Error retrieving value for {location_name}: {e}")
382
+ # return aggregated_results
383
+
384
+ # def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, original_lat_col, original_lon_col, custom_formula="", kernel_size=None, include_boundary=None, tile_cloud_threshold=0, pixel_cloud_threshold=0, user_scale=None):
385
+ # aggregated_results = []
386
+ # total_steps = len(locations_df)
387
+ # progress_bar = st.progress(0)
388
+ # progress_text = st.empty()
389
+ # start_time = time.time()
390
+ # raw_collection = ee.ImageCollection(dataset_id) \
391
+ # .filterDate(ee.Date(start_date_str), ee.Date(end_date_str))
392
+ # st.write(f"Original Collection Size: {raw_collection.size().getInfo()}")
393
+ # if tile_cloud_threshold > 0 or pixel_cloud_threshold > 0:
394
+ # raw_collection = preprocess_collection(raw_collection, tile_cloud_threshold, pixel_cloud_threshold)
395
+ # st.write(f"Preprocessed Collection Size: {raw_collection.size().getInfo()}")
396
+ # with ThreadPoolExecutor(max_workers=10) as executor:
397
+ # futures = []
398
+ # for idx, row in locations_df.iterrows():
399
+ # future = executor.submit(
400
+ # process_single_geometry,
401
+ # row,
402
+ # start_date_str,
403
+ # end_date_str,
404
+ # dataset_id,
405
+ # selected_bands,
406
+ # reducer_choice,
407
+ # shape_type,
408
+ # aggregation_period,
409
+ # custom_formula,
410
+ # original_lat_col,
411
+ # original_lon_col,
412
+ # kernel_size,
413
+ # include_boundary,
414
+ # user_scale=user_scale
415
+ # )
416
+ # futures.append(future)
417
+ # completed = 0
418
+ # for future in as_completed(futures):
419
+ # result = future.result()
420
+ # if result:
421
+ # aggregated_results.extend(result)
422
+ # completed += 1
423
+ # progress_percentage = completed / total_steps
424
+ # progress_bar.progress(progress_percentage)
425
+ # progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
426
+ # end_time = time.time()
427
+ # processing_time = end_time - start_time
428
+ # if aggregated_results:
429
+ # result_df = pd.DataFrame(aggregated_results)
430
+ # if aggregation_period.lower() == 'custom (start date to end date)':
431
+ # agg_dict = {
432
+ # 'Start Date': 'first',
433
+ # 'End Date': 'first',
434
+ # 'Calculated Value': 'mean' # Ensure this column is named 'Calculated Value'
435
+ # }
436
+ # if shape_type.lower() == 'point':
437
+ # agg_dict[original_lat_col] = 'first'
438
+ # agg_dict[original_lon_col] = 'first'
439
+ # aggregated_output = result_df.groupby('Location Name').agg(agg_dict).reset_index()
440
+ # aggregated_output['Date Range'] = aggregated_output['Start Date'] + " to " + aggregated_output['End Date']
441
+ # return aggregated_output.to_dict(orient='records'), processing_time
442
+ # else:
443
+ # return result_df.to_dict(orient='records'), processing_time
444
+ # return [], processing_time
445
+
446
+ # # Streamlit App Logic
447
+ # st.markdown("<h5>Image Collection</h5>", unsafe_allow_html=True)
448
+ # imagery_base = st.selectbox("Select Imagery Base", ["Sentinel", "Landsat", "MODIS", "VIIRS", "Custom Input"], index=0)
449
+ # data = {}
450
+ # if imagery_base == "Sentinel":
451
+ # dataset_file = "sentinel_datasets.json"
452
+ # try:
453
+ # with open(dataset_file) as f:
454
+ # data = json.load(f)
455
+ # except FileNotFoundError:
456
+ # st.error(f"Dataset file '{dataset_file}' not found.")
457
+ # data = {}
458
+ # elif imagery_base == "Landsat":
459
+ # dataset_file = "landsat_datasets.json"
460
+ # try:
461
+ # with open(dataset_file) as f:
462
+ # data = json.load(f)
463
+ # except FileNotFoundError:
464
+ # st.error(f"Dataset file '{dataset_file}' not found.")
465
+ # data = {}
466
+ # elif imagery_base == "MODIS":
467
+ # dataset_file = "modis_datasets.json"
468
+ # try:
469
+ # with open(dataset_file) as f:
470
+ # data = json.load(f)
471
+ # except FileNotFoundError:
472
+ # st.error(f"Dataset file '{dataset_file}' not found.")
473
+ # data = {}
474
+ # elif imagery_base == "VIIRS":
475
+ # dataset_file = "viirs_datasets.json"
476
+ # try:
477
+ # with open(dataset_file) as f:
478
+ # data = json.load(f)
479
+ # except FileNotFoundError:
480
+ # st.error(f"Dataset file '{dataset_file}' not found.")
481
+ # data = {}
482
+ # elif imagery_base == "Custom Input":
483
+ # custom_dataset_id = st.text_input("Enter Custom Earth Engine Dataset ID (e.g., AHN/AHN4)", value="")
484
+ # if custom_dataset_id:
485
+ # try:
486
+ # if custom_dataset_id.startswith("ee.ImageCollection("):
487
+ # custom_dataset_id = custom_dataset_id.replace("ee.ImageCollection('", "").replace("')", "")
488
+ # collection = ee.ImageCollection(custom_dataset_id)
489
+ # first_image = collection.first()
490
+ # default_scale = first_image.projection().nominalScale().getInfo()
491
+ # band_names = first_image.bandNames().getInfo()
492
+ # data = {
493
+ # f"Custom Dataset: {custom_dataset_id}": {
494
+ # "sub_options": {custom_dataset_id: f"Custom Dataset ({custom_dataset_id})"},
495
+ # "bands": {custom_dataset_id: band_names}
496
+ # }
497
+ # }
498
+ # st.write(f"Fetched bands for {custom_dataset_id}: {', '.join(band_names)}")
499
+ # st.write(f"Default Scale for Dataset: {default_scale} meters")
500
+ # except Exception as e:
501
+ # st.error(f"Error fetching dataset: {str(e)}. Please check the dataset ID and ensure it's valid in Google Earth Engine.")
502
+ # data = {}
503
+ # else:
504
+ # st.warning("Please enter a custom dataset ID to proceed.")
505
+ # data = {}
506
+ # if not data:
507
+ # st.error("No valid dataset available. Please check your inputs.")
508
+ # st.stop()
509
+
510
+ # st.markdown("<hr><h5><b>{}</b></h5>".format(imagery_base), unsafe_allow_html=True)
511
+ # main_selection = st.selectbox(f"Select {imagery_base} Dataset Category", list(data.keys()))
512
+ # sub_selection = None
513
+ # dataset_id = None
514
+ # if main_selection:
515
+ # sub_options = data[main_selection]["sub_options"]
516
+ # sub_selection = st.selectbox(f"Select Specific {imagery_base} Dataset ID", list(sub_options.keys()))
517
+ # if sub_selection:
518
+ # st.write(f"You selected: {main_selection} -> {sub_options[sub_selection]}")
519
+ # st.write(f"Dataset ID: {sub_selection}")
520
+ # dataset_id = sub_selection
521
+
522
+ # # Fetch the default scale for the selected dataset
523
+ # try:
524
+ # collection = ee.ImageCollection(dataset_id)
525
+ # first_image = collection.first()
526
+ # # Select the first band to avoid issues with multiple projections
527
+ # default_scale = first_image.select(0).projection().nominalScale().getInfo()
528
+ # st.write(f"Default Scale for Selected Dataset: {default_scale} meters")
529
+ # except Exception as e:
530
+ # st.error(f"Error fetching default scale: {str(e)}")
531
+
532
+ # st.markdown("<hr><h5><b>Earth Engine Index Calculator</b></h5>", unsafe_allow_html=True)
533
+ # if main_selection and sub_selection:
534
+ # dataset_bands = data[main_selection]["bands"].get(sub_selection, [])
535
+ # st.write(f"Available Bands for {sub_options[sub_selection]}: {', '.join(dataset_bands)}")
536
+ # selected_bands = st.multiselect(
537
+ # "Select 1 or 2 Bands for Calculation",
538
+ # options=dataset_bands,
539
+ # default=[dataset_bands[0]] if dataset_bands else [],
540
+ # help=f"Select 1 or 2 bands from: {', '.join(dataset_bands)}"
541
+ # )
542
+ # if len(selected_bands) < 1:
543
+ # st.warning("Please select at least one band.")
544
+ # st.stop()
545
+ # if selected_bands:
546
+ # if len(selected_bands) == 1:
547
+ # default_formula = f"{selected_bands[0]}"
548
+ # example = f"'{selected_bands[0]} * 2' or '{selected_bands[0]} + 1'"
549
+ # else:
550
+ # default_formula = f"({selected_bands[0]} - {selected_bands[1]}) / ({selected_bands[0]} + {selected_bands[1]})"
551
+ # example = f"'{selected_bands[0]} * {selected_bands[1]} / 2' or '({selected_bands[0]} - {selected_bands[1]}) / ({selected_bands[0]} + {selected_bands[1]})'"
552
+ # custom_formula = st.text_input(
553
+ # "Enter Custom Formula (e.g (B8 - B4) / (B8 + B4) , B4*B3/2)",
554
+ # value=default_formula,
555
+ # help=f"Use only these bands: {', '.join(selected_bands)}. Examples: {example}"
556
+ # )
557
+ # def validate_formula(formula, selected_bands):
558
+ # allowed_chars = set(" +-*/()0123456789.")
559
+ # terms = re.findall(r'[a-zA-Z][a-zA-Z0-9_]*', formula)
560
+ # invalid_terms = [term for term in terms if term not in selected_bands]
561
+ # if invalid_terms:
562
+ # return False, f"Invalid terms in formula: {', '.join(invalid_terms)}. Use only {', '.join(selected_bands)}."
563
+ # if not all(char in allowed_chars or char in ''.join(selected_bands) for char in formula):
564
+ # return False, "Formula contains invalid characters. Use only bands, numbers, and operators (+, -, *, /, ())"
565
+ # return True, ""
566
+ # is_valid, error_message = validate_formula(custom_formula, selected_bands)
567
+ # if not is_valid:
568
+ # st.error(error_message)
569
+ # st.stop()
570
+ # elif not custom_formula:
571
+ # st.warning("Please enter a custom formula to proceed.")
572
+ # st.stop()
573
+ # st.write(f"Custom Formula: {custom_formula}")
574
+
575
+ # reducer_choice = st.selectbox(
576
+ # "Select Reducer (e.g, mean , sum , median , min , max , count)",
577
+ # ['mean', 'sum', 'median', 'min', 'max', 'count'],
578
+ # index=0
579
+ # )
580
+ # start_date = st.date_input("Start Date", value=pd.to_datetime('2024-11-01'))
581
+ # end_date = st.date_input("End Date", value=pd.to_datetime('2024-12-01'))
582
+ # start_date_str = start_date.strftime('%Y-%m-%d')
583
+ # end_date_str = end_date.strftime('%Y-%m-%d')
584
+ # if imagery_base == "Sentinel" and "Sentinel-2" in sub_options[sub_selection]:
585
+ # st.markdown("<h5>Cloud Filtering</h5>", unsafe_allow_html=True)
586
+ # tile_cloud_threshold = st.slider(
587
+ # "Select Maximum Tile-Based Cloud Coverage Threshold (%)",
588
+ # min_value=0,
589
+ # max_value=100,
590
+ # value=20,
591
+ # step=5,
592
+ # help="Tiles with cloud coverage exceeding this threshold will be excluded."
593
+ # )
594
+ # pixel_cloud_threshold = st.slider(
595
+ # "Select Maximum Pixel-Based Cloud Coverage Threshold (%)",
596
+ # min_value=0,
597
+ # max_value=100,
598
+ # value=10,
599
+ # step=5,
600
+ # help="Individual pixels with cloud coverage exceeding this threshold will be masked."
601
+ # )
602
+ # aggregation_period = st.selectbox(
603
+ # "Select Aggregation Period (e.g, Custom(Start Date to End Date) , Daily , Weekly , Monthly , Yearly)",
604
+ # ["Custom (Start Date to End Date)", "Daily", "Weekly", "Monthly", "Yearly"],
605
+ # index=0
606
+ # )
607
+ # shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
608
+ # kernel_size = None
609
+ # include_boundary = None
610
+ # if shape_type.lower() == "point":
611
+ # kernel_size = st.selectbox(
612
+ # "Select Calculation Area(e.g, Point , 3x3 Kernel , 5x5 Kernel)",
613
+ # ["Point", "3x3 Kernel", "5x5 Kernel"],
614
+ # index=0,
615
+ # help="Choose 'Point' for exact point calculation, or a kernel size for area averaging."
616
+ # )
617
+ # elif shape_type.lower() == "polygon":
618
+ # include_boundary = st.checkbox(
619
+ # "Include Boundary Pixels",
620
+ # value=True,
621
+ # help="Check to include pixels on the polygon boundary; uncheck to exclude them."
622
+ # )
623
+ # st.markdown("<h5>Calculation Scale</h5>", unsafe_allow_html=True)
624
+ # default_scale = ee.ImageCollection(dataset_id).first().select(0).projection().nominalScale().getInfo()
625
+ # user_scale = st.number_input(
626
+ # "Enter Calculation Scale (meters) [Leave blank to use dataset's default scale]",
627
+ # min_value=1.0,
628
+ # value=float(default_scale),
629
+ # help=f"Default scale for this dataset is {default_scale} meters. Adjust if needed."
630
+ # )
631
+
632
+ # file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"])
633
+ # locations_df = pd.DataFrame()
634
+ # original_lat_col = None
635
+ # original_lon_col = None
636
+ # if file_upload is not None:
637
+ # if shape_type.lower() == "point":
638
+ # if file_upload.name.endswith('.csv'):
639
+ # locations_df = pd.read_csv(file_upload)
640
+ # st.write("Preview of your uploaded data (first 5 rows):")
641
+ # st.dataframe(locations_df.head())
642
+ # all_columns = locations_df.columns.tolist()
643
+ # col1, col2 = st.columns(2)
644
+ # with col1:
645
+ # original_lat_col = st.selectbox(
646
+ # "Select Latitude Column",
647
+ # options=all_columns,
648
+ # index=all_columns.index('latitude') if 'latitude' in all_columns else 0,
649
+ # help="Select the column containing latitude values"
650
+ # )
651
+ # with col2:
652
+ # original_lon_col = st.selectbox(
653
+ # "Select Longitude Column",
654
+ # options=all_columns,
655
+ # index=all_columns.index('longitude') if 'longitude' in all_columns else 0,
656
+ # help="Select the column containing longitude values"
657
+ # )
658
+ # if not pd.api.types.is_numeric_dtype(locations_df[original_lat_col]) or not pd.api.types.is_numeric_dtype(locations_df[original_lon_col]):
659
+ # st.error("Error: Selected Latitude and Longitude columns must contain numeric values")
660
+ # st.stop()
661
+ # locations_df = locations_df.rename(columns={
662
+ # original_lat_col: 'latitude',
663
+ # original_lon_col: 'longitude'
664
+ # })
665
+ # elif file_upload.name.endswith('.geojson'):
666
+ # locations_df = gpd.read_file(file_upload)
667
+ # if 'geometry' in locations_df.columns:
668
+ # locations_df['latitude'] = locations_df['geometry'].y
669
+ # locations_df['longitude'] = locations_df['geometry'].x
670
+ # original_lat_col = 'latitude'
671
+ # original_lon_col = 'longitude'
672
+ # else:
673
+ # st.error("GeoJSON file doesn't contain geometry column")
674
+ # st.stop()
675
+ # elif file_upload.name.endswith('.kml'):
676
+ # kml_string = file_upload.read().decode('utf-8')
677
+ # try:
678
+ # root = XET.fromstring(kml_string)
679
+ # ns = {'kml': 'http://www.opengis.net/kml/2.2'}
680
+ # points = []
681
+ # for placemark in root.findall('.//kml:Placemark', ns):
682
+ # name = placemark.findtext('kml:name', default=f"Point_{len(points)}", namespaces=ns)
683
+ # coords_elem = placemark.find('.//kml:Point/kml:coordinates', ns)
684
+ # if coords_elem is not None:
685
+ # coords_text = coords_elem.text.strip()
686
+ # coords = [c.strip() for c in coords_text.split(',')]
687
+ # if len(coords) >= 2:
688
+ # lon, lat = float(coords[0]), float(coords[1])
689
+ # points.append({'name': name, 'geometry': f"POINT ({lon} {lat})"})
690
+ # if not points:
691
+ # st.error("No valid Point data found in the KML file.")
692
+ # else:
693
+ # locations_df = gpd.GeoDataFrame(points, geometry=gpd.GeoSeries.from_wkt([p['geometry'] for p in points]), crs="EPSG:4326")
694
+ # locations_df['latitude'] = locations_df['geometry'].y
695
+ # locations_df['longitude'] = locations_df['geometry'].x
696
+ # original_lat_col = 'latitude'
697
+ # original_lon_col = 'longitude'
698
+ # except Exception as e:
699
+ # st.error(f"Error parsing KML file: {str(e)}")
700
+ # if not locations_df.empty and 'latitude' in locations_df.columns and 'longitude' in locations_df.columns:
701
+ # m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10)
702
+ # for _, row in locations_df.iterrows():
703
+ # latitude = row['latitude']
704
+ # longitude = row['longitude']
705
+ # if pd.isna(latitude) or pd.isna(longitude):
706
+ # continue
707
+ # m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name'))
708
+ # st.write("Map of Uploaded Points:")
709
+ # m.to_streamlit()
710
+ # elif shape_type.lower() == "polygon":
711
+ # if file_upload.name.endswith('.csv'):
712
+ # st.error("CSV upload not supported for polygons. Please upload a GeoJSON or KML file.")
713
+ # elif file_upload.name.endswith('.geojson'):
714
+ # locations_df = gpd.read_file(file_upload)
715
+ # if 'geometry' not in locations_df.columns:
716
+ # st.error("GeoJSON file doesn't contain geometry column")
717
+ # st.stop()
718
+ # elif file_upload.name.endswith('.kml'):
719
+ # kml_string = file_upload.read().decode('utf-8')
720
+ # try:
721
+ # root = XET.fromstring(kml_string)
722
+ # ns = {'kml': 'http://www.opengis.net/kml/2.2'}
723
+ # polygons = []
724
+ # for placemark in root.findall('.//kml:Placemark', ns):
725
+ # name = placemark.findtext('kml:name', default=f"Polygon_{len(polygons)}", namespaces=ns)
726
+ # coords_elem = placemark.find('.//kml:Polygon//kml:coordinates', ns)
727
+ # if coords_elem is not None:
728
+ # coords_text = ' '.join(coords_elem.text.split())
729
+ # coord_pairs = [pair.split(',')[:2] for pair in coords_text.split() if pair]
730
+ # if len(coord_pairs) >= 4:
731
+ # coords_str = " ".join([f"{float(lon)} {float(lat)}" for lon, lat in coord_pairs])
732
+ # polygons.append({'name': name, 'geometry': f"POLYGON (({coords_str}))"})
733
+ # if not polygons:
734
+ # st.error("No valid Polygon data found in the KML file.")
735
+ # else:
736
+ # locations_df = gpd.GeoDataFrame(polygons, geometry=gpd.GeoSeries.from_wkt([p['geometry'] for p in polygons]), crs="EPSG:4326")
737
+ # except Exception as e:
738
+ # st.error(f"Error parsing KML file: {str(e)}")
739
+ # if not locations_df.empty and 'geometry' in locations_df.columns:
740
+ # centroid_lat = locations_df.geometry.centroid.y.mean()
741
+ # centroid_lon = locations_df.geometry.centroid.x.mean()
742
+ # m = leafmap.Map(center=[centroid_lat, centroid_lon], zoom=10)
743
+ # for _, row in locations_df.iterrows():
744
+ # polygon = row['geometry']
745
+ # if polygon.is_valid:
746
+ # gdf = gpd.GeoDataFrame([row], geometry=[polygon], crs=locations_df.crs)
747
+ # m.add_gdf(gdf=gdf, layer_name=row.get('name', 'Unnamed Polygon'))
748
+ # st.write("Map of Uploaded Polygons:")
749
+ # m.to_streamlit()
750
+
751
+ # if st.button(f"Calculate {custom_formula}"):
752
+ # if not locations_df.empty:
753
+ # with st.spinner("Processing Data..."):
754
+ # try:
755
+ # # Call the aggregation function with updated parameters
756
+ # results, processing_time = process_aggregation(
757
+ # locations_df,
758
+ # start_date_str,
759
+ # end_date_str,
760
+ # dataset_id,
761
+ # selected_bands,
762
+ # reducer_choice,
763
+ # shape_type,
764
+ # aggregation_period,
765
+ # original_lat_col,
766
+ # original_lon_col,
767
+ # custom_formula=custom_formula,
768
+ # kernel_size=kernel_size,
769
+ # include_boundary=include_boundary,
770
+ # tile_cloud_threshold=tile_cloud_threshold if "tile_cloud_threshold" in locals() else 0,
771
+ # pixel_cloud_threshold=pixel_cloud_threshold if "pixel_cloud_threshold" in locals() else 0,
772
+ # user_scale=user_scale
773
+ # )
774
+
775
+ # # Process and display results
776
+ # if results:
777
+ # result_df = pd.DataFrame(results)
778
+ # st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
779
+ # st.dataframe(result_df)
780
+
781
+ # # Download button for CSV
782
+ # filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
783
+ # st.download_button(
784
+ # label="Download results as CSV",
785
+ # data=result_df.to_csv(index=False).encode('utf-8'),
786
+ # file_name=filename,
787
+ # mime='text/csv'
788
+ # )
789
+
790
+ # # Success message
791
+ # st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
792
+
793
+ # # Graph Visualization Section
794
+ # st.markdown("<h5>Graph Visualization</h5>", unsafe_allow_html=True)
795
+
796
+ # # Dynamically identify the time column
797
+ # if aggregation_period.lower() == 'custom (start date to end date)':
798
+ # x_column = 'Date Range'
799
+ # elif 'Date' in result_df.columns:
800
+ # x_column = 'Date'
801
+ # elif 'Week' in result_df.columns:
802
+ # x_column = 'Week'
803
+ # elif 'Month' in result_df.columns:
804
+ # x_column = 'Month'
805
+ # elif 'Year' in result_df.columns:
806
+ # x_column = 'Year'
807
+ # else:
808
+ # st.warning("No valid time column found for plotting.")
809
+ # st.stop()
810
+
811
+ # # Dynamically identify the value column
812
+ # y_column = None
813
+ # if 'Calculated Value' in result_df.columns:
814
+ # y_column = 'Calculated Value'
815
+ # elif 'Aggregated Value' in result_df.columns:
816
+ # y_column = 'Aggregated Value'
817
+ # else:
818
+ # st.warning("No value column found for plotting. Available columns: " + ", ".join(result_df.columns))
819
+ # st.stop()
820
+
821
+ # # Ensure we have valid data to plot
822
+ # if result_df.empty:
823
+ # st.warning("No data available for plotting.")
824
+ # st.stop()
825
+
826
+ # # # Line Chart
827
+ # # try:
828
+ # # st.subheader("Line Chart")
829
+ # # if x_column == 'Location Name':
830
+ # # st.line_chart(result_df.set_index(x_column)[y_column])
831
+ # # else:
832
+ # # # Convert to datetime for better sorting
833
+ # # result_df[x_column] = pd.to_datetime(result_df[x_column], errors='ignore')
834
+ # # result_df = result_df.sort_values(x_column)
835
+ # # st.line_chart(result_df.set_index(x_column)[y_column])
836
+ # # except Exception as e:
837
+ # # st.error(f"Error creating line chart: {str(e)}")
838
+
839
+ # # # Bar Chart
840
+ # # try:
841
+ # # st.subheader("Bar Chart")
842
+ # # if x_column == 'Location Name':
843
+ # # st.bar_chart(result_df.set_index(x_column)[y_column])
844
+ # # else:
845
+ # # result_df[x_column] = pd.to_datetime(result_df[x_column], errors='ignore')
846
+ # # result_df = result_df.sort_values(x_column)
847
+ # # st.bar_chart(result_df.set_index(x_column)[y_column])
848
+ # # except Exception as e:
849
+ # # st.error(f"Error creating bar chart: {str(e)}")
850
+
851
+ # # Advanced Plot (Plotly)
852
+ # try:
853
+ # st.subheader("Advanced Interactive Plot (Plotly)")
854
+ # if x_column == 'Location Name':
855
+ # fig = px.bar(
856
+ # result_df,
857
+ # x=x_column,
858
+ # y=y_column,
859
+ # color='Location Name',
860
+ # title=f"{custom_formula} by Location"
861
+ # )
862
+ # else:
863
+ # fig = px.line(
864
+ # result_df,
865
+ # x=x_column,
866
+ # y=y_column,
867
+ # color='Location Name',
868
+ # title=f"{custom_formula} Over Time"
869
+ # )
870
+ # st.plotly_chart(fig)
871
+ # except Exception as e:
872
+ # st.error(f"Error creating interactive plot: {str(e)}")
873
+
874
+ # else:
875
+ # st.warning("No results were generated. Check your inputs or formula.")
876
+ # st.info(f"Total processing time: {processing_time:.2f} seconds.")
877
+
878
+ # except Exception as e:
879
+ # st.error(f"An error occurred during processing: {str(e)}")
880
+ # else:
881
+ # st.warning("Please upload a valid file to proceed.")
882
+ # # if st.button(f"Calculate {custom_formula}"):
883
+ # # if not locations_df.empty:
884
+ # # with st.spinner("Processing Data..."):
885
+ # # try:
886
+ # # results, processing_time = process_aggregation(
887
+ # # locations_df,
888
+ # # start_date_str,
889
+ # # end_date_str,
890
+ # # dataset_id,
891
+ # # selected_bands,
892
+ # # reducer_choice,
893
+ # # shape_type,
894
+ # # aggregation_period,
895
+ # # original_lat_col,
896
+ # # original_lon_col,
897
+ # # custom_formula,
898
+ # # kernel_size,
899
+ # # include_boundary,
900
+ # # tile_cloud_threshold=tile_cloud_threshold if "tile_cloud_threshold" in locals() else 0,
901
+ # # pixel_cloud_threshold=pixel_cloud_threshold if "pixel_cloud_threshold" in locals() else 0,
902
+ # # user_scale=user_scale
903
+ # # )
904
+ # # if results:
905
+ # # result_df = pd.DataFrame(results)
906
+ # # st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
907
+ # # st.dataframe(result_df)
908
+ # # filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
909
+ # # st.download_button(
910
+ # # label="Download results as CSV",
911
+ # # data=result_df.to_csv(index=False).encode('utf-8'),
912
+ # # file_name=filename,
913
+ # # mime='text/csv'
914
+ # # )
915
+ # # st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
916
+ # # st.markdown("<h5>Graph Visualization</h5>", unsafe_allow_html=True)
917
+ # # if aggregation_period.lower() == 'custom (start date to end date)':
918
+ # # x_column = 'Date Range'
919
+ # # elif 'Date' in result_df.columns:
920
+ # # x_column = 'Date'
921
+ # # elif 'Week' in result_df.columns:
922
+ # # x_column = 'Week'
923
+ # # elif 'Month' in result_df.columns:
924
+ # # x_column = 'Month'
925
+ # # elif 'Year' in result_df.columns:
926
+ # # x_column = 'Year'
927
+ # # else:
928
+ # # st.warning("No valid time column found for plotting.")
929
+ # # st.stop()
930
+ # # y_column = 'Calculated Value'
931
+ # # fig = px.line(
932
+ # # result_df,
933
+ # # x=x_column,
934
+ # # y=y_column,
935
+ # # color='Location Name',
936
+ # # title=f"{custom_formula} Over Time"
937
+ # # )
938
+ # # st.plotly_chart(fig)
939
+ # # else:
940
+ # # st.warning("No results were generated. Check your inputs or formula.")
941
+ # # st.info(f"Total processing time: {processing_time:.2f} seconds.")
942
+ # # except Exception as e:
943
+ # # st.error(f"An error occurred during processing: {str(e)}")
944
+ # # else:
945
+ # # st.warning("Please upload a valid file to proceed.")
946
+
947
  import streamlit as st
948
  import json
949
  import ee
 
1060
  for band in selected_bands:
1061
  band_scale = image.select(band).projection().nominalScale().getInfo()
1062
  band_scales.append(band_scale)
 
 
1063
  default_scale = min(band_scales) if band_scales else 30 # Default to 30m if no bands are found
 
 
 
 
 
 
 
 
 
 
1064
  scale = user_scale if user_scale is not None else default_scale
1065
 
1066
  # Rescale all bands to the chosen scale
 
1069
  band_image = image.select(band)
1070
  band_scale = band_image.projection().nominalScale().getInfo()
1071
  if band_scale != scale:
 
1072
  rescaled_band = band_image.resample('bilinear').reproject(
1073
  crs=band_image.projection().crs(),
1074
  scale=scale
 
1084
  value = rescaled_bands[band].reduceRegion(
1085
  reducer=reducer,
1086
  geometry=geometry,
1087
+ scale=scale
1088
  ).get(band).getInfo()
1089
  reduced_values[band] = float(value if value is not None else 0)
1090
 
 
1097
  # Validate the result
1098
  if not isinstance(result, (int, float)):
1099
  raise ValueError("Formula did not result in a numeric value.")
 
1100
  return ee.Image.constant(result).rename('custom_result')
 
1101
  except ZeroDivisionError:
1102
  st.error("Error: Division by zero in the formula.")
1103
  return ee.Image(0).rename('custom_result').set('error', 'Division by zero')
 
1173
  yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
1174
  return ee.ImageCollection(yearly_images)
1175
 
1176
+ # Cloud percentage calculation
1177
  def calculate_cloud_percentage(image, cloud_band='QA60'):
 
 
 
 
 
1178
  qa60 = image.select(cloud_band)
1179
+ opaque_clouds = qa60.bitwiseAnd(1 << 10)
1180
+ cirrus_clouds = qa60.bitwiseAnd(1 << 11)
 
1181
  cloud_mask = opaque_clouds.Or(cirrus_clouds)
 
1182
  total_pixels = qa60.reduceRegion(
1183
  reducer=ee.Reducer.count(),
1184
  geometry=image.geometry(),
1185
+ scale=60,
1186
  maxPixels=1e13
1187
  ).get(cloud_band)
1188
  cloudy_pixels = cloud_mask.reduceRegion(
1189
  reducer=ee.Reducer.sum(),
1190
  geometry=image.geometry(),
1191
+ scale=60,
1192
  maxPixels=1e13
1193
  ).get(cloud_band)
 
1194
  if total_pixels == 0:
1195
+ return 0
1196
  return ee.Number(cloudy_pixels).divide(ee.Number(total_pixels)).multiply(100)
1197
 
1198
+ # Preprocessing function
1199
  def preprocess_collection(collection, tile_cloud_threshold, pixel_cloud_threshold):
1200
  def filter_tile(image):
1201
  cloud_percentage = calculate_cloud_percentage(image, cloud_band='QA60')
 
1213
  masked_collection = filtered_collection.map(mask_cloudy_pixels)
1214
  return masked_collection
1215
 
1216
+ # Process single geometry
1217
  def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, custom_formula, original_lat_col, original_lon_col, kernel_size=None, include_boundary=None, user_scale=None):
1218
  if shape_type.lower() == "point":
1219
  latitude = row.get('latitude')
 
1238
  roi = roi.buffer(-30).bounds()
1239
  except ValueError:
1240
  return None
1241
+
1242
  collection = ee.ImageCollection(dataset_id) \
1243
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
1244
  .filterBounds(roi)
1245
+
1246
  if aggregation_period.lower() == 'custom (start date to end date)':
1247
  collection = aggregate_data_custom(collection)
1248
  elif aggregation_period.lower() == 'daily':
 
1253
  collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
1254
  elif aggregation_period.lower() == 'yearly':
1255
  collection = aggregate_data_yearly(collection)
1256
+
1257
  image_list = collection.toList(collection.size())
1258
  processed_weeks = set()
1259
  aggregated_results = []
 
1284
  timestamp = image.get('year')
1285
  period_label = 'Year'
1286
  date = ee.Date(timestamp).format('YYYY').getInfo()
1287
+
1288
  index_image = calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice, dataset_id, user_scale=user_scale)
1289
  try:
1290
  index_value = index_image.reduceRegion(
 
1309
  st.error(f"Error retrieving value for {location_name}: {e}")
1310
  return aggregated_results
1311
 
1312
+ # Process aggregation
1313
  def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, selected_bands, reducer_choice, shape_type, aggregation_period, original_lat_col, original_lon_col, custom_formula="", kernel_size=None, include_boundary=None, tile_cloud_threshold=0, pixel_cloud_threshold=0, user_scale=None):
1314
  aggregated_results = []
1315
  total_steps = len(locations_df)
1316
  progress_bar = st.progress(0)
1317
  progress_text = st.empty()
1318
  start_time = time.time()
1319
+
1320
  raw_collection = ee.ImageCollection(dataset_id) \
1321
  .filterDate(ee.Date(start_date_str), ee.Date(end_date_str))
1322
+
1323
  st.write(f"Original Collection Size: {raw_collection.size().getInfo()}")
1324
+
1325
  if tile_cloud_threshold > 0 or pixel_cloud_threshold > 0:
1326
  raw_collection = preprocess_collection(raw_collection, tile_cloud_threshold, pixel_cloud_threshold)
1327
  st.write(f"Preprocessed Collection Size: {raw_collection.size().getInfo()}")
1328
+
1329
  with ThreadPoolExecutor(max_workers=10) as executor:
1330
  futures = []
1331
  for idx, row in locations_df.iterrows():
 
1356
  progress_percentage = completed / total_steps
1357
  progress_bar.progress(progress_percentage)
1358
  progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
1359
+
1360
  end_time = time.time()
1361
  processing_time = end_time - start_time
1362
+
1363
  if aggregated_results:
1364
  result_df = pd.DataFrame(aggregated_results)
1365
  if aggregation_period.lower() == 'custom (start date to end date)':
1366
  agg_dict = {
1367
  'Start Date': 'first',
1368
  'End Date': 'first',
1369
+ 'Calculated Value': 'mean'
1370
  }
1371
  if shape_type.lower() == 'point':
1372
  agg_dict[original_lat_col] = 'first'
 
1375
  aggregated_output['Date Range'] = aggregated_output['Start Date'] + " to " + aggregated_output['End Date']
1376
  return aggregated_output.to_dict(orient='records'), processing_time
1377
  else:
1378
+ return result_df.to_dict(orient='records'), processing_time
1379
+ return [], processing_time
1380
 
1381
  # Streamlit App Logic
1382
  st.markdown("<h5>Image Collection</h5>", unsafe_allow_html=True)
 
1453
  st.write(f"You selected: {main_selection} -> {sub_options[sub_selection]}")
1454
  st.write(f"Dataset ID: {sub_selection}")
1455
  dataset_id = sub_selection
 
1456
  # Fetch the default scale for the selected dataset
1457
  try:
1458
  collection = ee.ImageCollection(dataset_id)
1459
  first_image = collection.first()
 
1460
  default_scale = first_image.select(0).projection().nominalScale().getInfo()
1461
  st.write(f"Default Scale for Selected Dataset: {default_scale} meters")
1462
  except Exception as e:
 
1504
  st.warning("Please enter a custom formula to proceed.")
1505
  st.stop()
1506
  st.write(f"Custom Formula: {custom_formula}")
1507
+
1508
  reducer_choice = st.selectbox(
1509
  "Select Reducer (e.g, mean , sum , median , min , max , count)",
1510
  ['mean', 'sum', 'median', 'min', 'max', 'count'],
1511
  index=0
1512
  )
1513
+
1514
  start_date = st.date_input("Start Date", value=pd.to_datetime('2024-11-01'))
1515
  end_date = st.date_input("End Date", value=pd.to_datetime('2024-12-01'))
1516
  start_date_str = start_date.strftime('%Y-%m-%d')
1517
  end_date_str = end_date.strftime('%Y-%m-%d')
1518
+
1519
  if imagery_base == "Sentinel" and "Sentinel-2" in sub_options[sub_selection]:
1520
  st.markdown("<h5>Cloud Filtering</h5>", unsafe_allow_html=True)
1521
  tile_cloud_threshold = st.slider(
1522
  "Select Maximum Tile-Based Cloud Coverage Threshold (%)",
1523
  min_value=0,
1524
  max_value=100,
1525
+ value=10, # Reduced from 20
1526
  step=5,
1527
  help="Tiles with cloud coverage exceeding this threshold will be excluded."
1528
  )
 
1530
  "Select Maximum Pixel-Based Cloud Coverage Threshold (%)",
1531
  min_value=0,
1532
  max_value=100,
1533
+ value=5, # Reduced from 10
1534
  step=5,
1535
  help="Individual pixels with cloud coverage exceeding this threshold will be masked."
1536
  )
1537
+
1538
  aggregation_period = st.selectbox(
1539
  "Select Aggregation Period (e.g, Custom(Start Date to End Date) , Daily , Weekly , Monthly , Yearly)",
1540
  ["Custom (Start Date to End Date)", "Daily", "Weekly", "Monthly", "Yearly"],
1541
  index=0
1542
  )
1543
+
1544
  shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
1545
  kernel_size = None
1546
  include_boundary = None
1547
+
1548
  if shape_type.lower() == "point":
1549
  kernel_size = st.selectbox(
1550
  "Select Calculation Area(e.g, Point , 3x3 Kernel , 5x5 Kernel)",
 
1558
  value=True,
1559
  help="Check to include pixels on the polygon boundary; uncheck to exclude them."
1560
  )
1561
+
1562
  st.markdown("<h5>Calculation Scale</h5>", unsafe_allow_html=True)
1563
  default_scale = ee.ImageCollection(dataset_id).first().select(0).projection().nominalScale().getInfo()
1564
  user_scale = st.number_input(
 
1572
  locations_df = pd.DataFrame()
1573
  original_lat_col = None
1574
  original_lon_col = None
1575
+
1576
  if file_upload is not None:
1577
  if shape_type.lower() == "point":
1578
  if file_upload.name.endswith('.csv'):
 
1692
  if not locations_df.empty:
1693
  with st.spinner("Processing Data..."):
1694
  try:
 
1695
  results, processing_time = process_aggregation(
1696
  locations_df,
1697
  start_date_str,
 
1710
  pixel_cloud_threshold=pixel_cloud_threshold if "pixel_cloud_threshold" in locals() else 0,
1711
  user_scale=user_scale
1712
  )
 
 
1713
  if results:
1714
  result_df = pd.DataFrame(results)
1715
  st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
1716
  st.dataframe(result_df)
 
 
1717
  filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
1718
  st.download_button(
1719
  label="Download results as CSV",
 
1721
  file_name=filename,
1722
  mime='text/csv'
1723
  )
 
 
1724
  st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
 
 
1725
  st.markdown("<h5>Graph Visualization</h5>", unsafe_allow_html=True)
 
 
1726
  if aggregation_period.lower() == 'custom (start date to end date)':
1727
  x_column = 'Date Range'
1728
  elif 'Date' in result_df.columns:
 
1736
  else:
1737
  st.warning("No valid time column found for plotting.")
1738
  st.stop()
1739
+ y_column = 'Calculated Value'
1740
+ fig = px.line(
1741
+ result_df,
1742
+ x=x_column,
1743
+ y=y_column,
1744
+ color='Location Name',
1745
+ title=f"{custom_formula} Over Time"
1746
+ )
1747
+ st.plotly_chart(fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1748
  else:
1749
  st.warning("No results were generated. Check your inputs or formula.")
1750
  st.info(f"Total processing time: {processing_time:.2f} seconds.")
 
1751
  except Exception as e:
1752
  st.error(f"An error occurred during processing: {str(e)}")
1753
  else:
1754
+ st.warning("Please upload a valid file to proceed.")