Spaces:
Sleeping
Sleeping
update app.py
Browse files
app.py
CHANGED
@@ -7,6 +7,9 @@ import geopandas as gpd
|
|
7 |
from datetime import datetime
|
8 |
import leafmap.foliumap as leafmap
|
9 |
import re
|
|
|
|
|
|
|
10 |
|
11 |
# Set up the page layout
|
12 |
st.set_page_config(layout="wide")
|
@@ -92,12 +95,20 @@ elif index_choice.lower() == 'ndwi':
|
|
92 |
elif index_choice.lower() == 'average no₂':
|
93 |
st.write("Formula for Average NO₂: Average NO₂ = Mean(NO2 band)")
|
94 |
elif index_choice.lower() == 'custom formula':
|
95 |
-
custom_formula = st.text_input("Enter Custom Formula (e.g., B5,B4)")
|
96 |
# Check if custom formula is empty and show warning
|
97 |
if not custom_formula:
|
98 |
st.warning("Please enter a custom formula before proceeding.")
|
99 |
else:
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
# Function to get the corresponding reducer based on user input
|
103 |
def get_reducer(reducer_name):
|
@@ -123,13 +134,63 @@ reducer_choice = st.selectbox(
|
|
123 |
index=0 # Default to 'mean'
|
124 |
)
|
125 |
|
126 |
-
# Function to check if the polygon geometry is valid and convert it to the correct format
|
127 |
def convert_to_ee_geometry(geometry):
|
128 |
-
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
else:
|
132 |
-
raise ValueError("
|
133 |
|
134 |
# Function to read points from CSV
|
135 |
def read_csv(file_path):
|
@@ -146,6 +207,18 @@ def read_kml(file_path):
|
|
146 |
gdf = gpd.read_file(file_path, driver='KML')
|
147 |
return gdf
|
148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
149 |
# Ask user whether they want to process 'Point' or 'Polygon' data (case-insensitive)
|
150 |
shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
|
151 |
|
@@ -174,7 +247,7 @@ if file_upload is not None:
|
|
174 |
st.stop() # Stop further processing if polygons are detected
|
175 |
|
176 |
# Processing the point data
|
177 |
-
with st.spinner('Processing
|
178 |
if locations_df is not None and not locations_df.empty:
|
179 |
# For GeoJSON data, the coordinates are in the geometry column
|
180 |
if 'geometry' in locations_df.columns:
|
@@ -231,7 +304,7 @@ if file_upload is not None:
|
|
231 |
st.stop() # Stop further processing if point data is detected
|
232 |
|
233 |
# Processing the polygon data
|
234 |
-
with st.spinner('Processing
|
235 |
if locations_df is not None and not locations_df.empty:
|
236 |
# Ensure the 'geometry' column exists in the dataframe
|
237 |
if 'geometry' not in locations_df.columns:
|
@@ -263,18 +336,6 @@ if file_upload is not None:
|
|
263 |
# Store the map in session_state
|
264 |
st.session_state.map_data = m
|
265 |
|
266 |
-
|
267 |
-
# Date Input for Start and End Dates
|
268 |
-
start_date = st.date_input("Start Date", value=pd.to_datetime('2020-01-01'))
|
269 |
-
end_date = st.date_input("End Date", value=pd.to_datetime('2020-12-31'))
|
270 |
-
|
271 |
-
# Convert start_date and end_date to string format for Earth Engine
|
272 |
-
start_date_str = start_date.strftime('%Y-%m-%d')
|
273 |
-
end_date_str = end_date.strftime('%Y-%m-%d')
|
274 |
-
|
275 |
-
# Aggregation period selection
|
276 |
-
aggregation_period = st.selectbox("Select Aggregation Period", ["Daily", "Weekly", "Monthly", "Yearly"], index=0)
|
277 |
-
|
278 |
# Initialize session state for storing results if not already done
|
279 |
if 'results' not in st.session_state:
|
280 |
st.session_state.results = []
|
@@ -318,14 +379,24 @@ def calculate_ndwi(image, geometry, reducer_choice):
|
|
318 |
ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI')
|
319 |
return ndwi
|
320 |
|
321 |
-
def calculate_custom_formula(image, geometry, custom_formula, reducer_choice, scale=30):
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
329 |
# Modify aggregation functions to return the correct time period and aggregated results
|
330 |
def aggregate_data_daily(collection):
|
331 |
# Extract day from the image date (using the exact date)
|
@@ -346,23 +417,28 @@ def aggregate_data_daily(collection):
|
|
346 |
return ee.ImageCollection(daily_images)
|
347 |
|
348 |
def aggregate_data_weekly(collection):
|
349 |
-
# Extract
|
350 |
-
collection = collection.map(lambda image: image.set(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
|
352 |
-
# Group images by week
|
353 |
-
grouped_by_week = collection.aggregate_array('week').distinct()
|
354 |
-
|
355 |
-
def calculate_weekly_mean(week):
|
356 |
-
weekly_collection = collection.filter(ee.Filter.eq('week', week))
|
357 |
-
weekly_mean = weekly_collection.mean()
|
358 |
-
return weekly_mean.set('week', week)
|
359 |
-
|
360 |
# Calculate the weekly mean for each week
|
361 |
weekly_images = ee.List(grouped_by_week.map(calculate_weekly_mean))
|
362 |
-
|
363 |
return ee.ImageCollection(weekly_images)
|
364 |
-
|
365 |
-
def aggregate_data_monthly(collection):
|
|
|
|
|
|
|
366 |
# Extract month and year from the image date
|
367 |
collection = collection.map(lambda image: image.set('month', ee.Date(image.get('system:time_start')).format('YYYY-MM')))
|
368 |
|
@@ -378,7 +454,7 @@ def aggregate_data_monthly(collection):
|
|
378 |
monthly_images = ee.List(grouped_by_month.map(calculate_monthly_mean))
|
379 |
|
380 |
return ee.ImageCollection(monthly_images)
|
381 |
-
|
382 |
def aggregate_data_yearly(collection):
|
383 |
# Extract year from the image date
|
384 |
collection = collection.map(lambda image: image.set('year', ee.Date(image.get('system:time_start')).format('YYYY')))
|
@@ -411,189 +487,236 @@ def calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_
|
|
411 |
else:
|
412 |
st.write("Please Select any one option...."+ index_choice.lower())
|
413 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
414 |
def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, index_choice, reducer_choice, shape_type, aggregation_period, custom_formula=""):
|
415 |
aggregated_results = []
|
416 |
-
|
417 |
-
# Check if the index_choice is 'custom formula' and the custom formula is empty
|
418 |
if index_choice.lower() == 'custom_formula' and not custom_formula:
|
419 |
st.error("Custom formula cannot be empty. Please provide a formula.")
|
420 |
-
return aggregated_results
|
421 |
|
422 |
-
# Initialize progress bar
|
423 |
total_steps = len(locations_df)
|
424 |
progress_bar = st.progress(0)
|
425 |
progress_text = st.empty()
|
426 |
-
|
427 |
with st.spinner('Processing data...'):
|
428 |
if shape_type.lower() == "point":
|
429 |
for idx, row in locations_df.iterrows():
|
430 |
-
# Check if the latitude and longitude columns exist and have values
|
431 |
latitude = row.get('latitude')
|
432 |
longitude = row.get('longitude')
|
433 |
-
|
434 |
if pd.isna(latitude) or pd.isna(longitude):
|
435 |
st.warning(f"Skipping location {idx} with missing latitude or longitude")
|
436 |
continue
|
437 |
-
|
438 |
location_name = row.get('name', f"Location_{idx}")
|
439 |
-
|
440 |
roi = ee.Geometry.Point([longitude, latitude])
|
441 |
-
|
442 |
collection = ee.ImageCollection(dataset_id) \
|
443 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
444 |
.filterBounds(roi)
|
445 |
-
|
446 |
# Aggregate data based on the selected period
|
447 |
if aggregation_period.lower() == 'daily':
|
448 |
collection = aggregate_data_daily(collection)
|
449 |
elif aggregation_period.lower() == 'weekly':
|
450 |
collection = aggregate_data_weekly(collection)
|
451 |
elif aggregation_period.lower() == 'monthly':
|
452 |
-
collection = aggregate_data_monthly(collection)
|
453 |
elif aggregation_period.lower() == 'yearly':
|
454 |
collection = aggregate_data_yearly(collection)
|
455 |
-
|
456 |
# Process each image in the collection
|
457 |
image_list = collection.toList(collection.size())
|
458 |
-
|
459 |
for i in range(image_list.size().getInfo()):
|
460 |
image = ee.Image(image_list.get(i))
|
461 |
-
|
462 |
if aggregation_period.lower() == 'daily':
|
463 |
timestamp = image.get('day')
|
|
|
|
|
464 |
elif aggregation_period.lower() == 'weekly':
|
465 |
-
timestamp = image.get('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
466 |
elif aggregation_period.lower() == 'monthly':
|
467 |
timestamp = image.get('month')
|
|
|
|
|
468 |
elif aggregation_period.lower() == 'yearly':
|
469 |
timestamp = image.get('year')
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
# Calculate the index for each period
|
474 |
index_image = calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_formula)
|
475 |
-
|
476 |
-
# Skip if index_image is None
|
477 |
-
if index_image is None:
|
478 |
-
st.warning(f"Index calculation failed for {location_name} on {date}. Skipping this entry.")
|
479 |
-
continue
|
480 |
|
481 |
-
# Reduce the region to get the aggregated value
|
482 |
try:
|
483 |
index_value = index_image.reduceRegion(
|
484 |
reducer=get_reducer(reducer_choice),
|
485 |
geometry=roi,
|
486 |
scale=30
|
487 |
).get(index_image.bandNames().get(0))
|
488 |
-
|
489 |
calculated_value = index_value.getInfo()
|
490 |
-
|
491 |
-
# Append the results if valid
|
492 |
if isinstance(calculated_value, (int, float)):
|
493 |
aggregated_results.append({
|
494 |
'Location Name': location_name,
|
495 |
'Latitude': latitude,
|
496 |
'Longitude': longitude,
|
497 |
-
|
|
|
|
|
498 |
'Calculated Value': calculated_value
|
499 |
})
|
500 |
else:
|
501 |
st.warning(f"Skipping invalid value for {location_name} on {date}")
|
502 |
except Exception as e:
|
503 |
st.error(f"Error retrieving value for {location_name}: {e}")
|
504 |
-
|
505 |
-
# Update progress bar
|
506 |
progress_percentage = (idx + 1) / total_steps
|
507 |
progress_bar.progress(progress_percentage)
|
508 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
509 |
-
|
510 |
elif shape_type.lower() == "polygon":
|
511 |
for idx, row in locations_df.iterrows():
|
512 |
polygon_name = row.get('name', f"Polygon_{idx}")
|
513 |
polygon_geometry = row.get('geometry')
|
514 |
-
|
515 |
location_name = polygon_name
|
516 |
-
|
517 |
try:
|
518 |
roi = convert_to_ee_geometry(polygon_geometry)
|
519 |
except ValueError as e:
|
520 |
st.warning(f"Skipping invalid polygon {polygon_name}: {e}")
|
521 |
continue
|
522 |
-
|
523 |
collection = ee.ImageCollection(dataset_id) \
|
524 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
525 |
.filterBounds(roi)
|
526 |
-
|
527 |
# Aggregate data based on the selected period
|
528 |
if aggregation_period.lower() == 'daily':
|
529 |
collection = aggregate_data_daily(collection)
|
530 |
elif aggregation_period.lower() == 'weekly':
|
531 |
collection = aggregate_data_weekly(collection)
|
532 |
elif aggregation_period.lower() == 'monthly':
|
533 |
-
collection = aggregate_data_monthly(collection)
|
534 |
elif aggregation_period.lower() == 'yearly':
|
535 |
collection = aggregate_data_yearly(collection)
|
536 |
-
|
537 |
# Process each image in the collection
|
538 |
image_list = collection.toList(collection.size())
|
539 |
-
|
540 |
for i in range(image_list.size().getInfo()):
|
541 |
image = ee.Image(image_list.get(i))
|
542 |
-
|
543 |
if aggregation_period.lower() == 'daily':
|
544 |
timestamp = image.get('day')
|
|
|
|
|
545 |
elif aggregation_period.lower() == 'weekly':
|
546 |
-
timestamp = image.get('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
547 |
elif aggregation_period.lower() == 'monthly':
|
548 |
timestamp = image.get('month')
|
|
|
|
|
549 |
elif aggregation_period.lower() == 'yearly':
|
550 |
timestamp = image.get('year')
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
# Calculate the index for each period
|
555 |
-
index_image = calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_formula)
|
556 |
|
557 |
-
|
558 |
-
if index_image is None:
|
559 |
-
st.warning(f"Index calculation failed for {location_name} on {date}. Skipping this entry.")
|
560 |
-
continue
|
561 |
|
562 |
-
# Reduce the region to get the aggregated value
|
563 |
try:
|
564 |
index_value = index_image.reduceRegion(
|
565 |
reducer=get_reducer(reducer_choice),
|
566 |
geometry=roi,
|
567 |
scale=30
|
568 |
).get(index_image.bandNames().get(0))
|
569 |
-
|
570 |
calculated_value = index_value.getInfo()
|
571 |
-
|
572 |
-
# Append the results if valid
|
573 |
if isinstance(calculated_value, (int, float)):
|
574 |
aggregated_results.append({
|
575 |
'Location Name': location_name,
|
576 |
-
|
|
|
|
|
577 |
'Calculated Value': calculated_value
|
578 |
})
|
579 |
else:
|
580 |
st.warning(f"Skipping invalid value for {location_name} on {date}")
|
581 |
except Exception as e:
|
582 |
st.error(f"Error retrieving value for {location_name}: {e}")
|
583 |
-
|
584 |
-
# Update progress bar
|
585 |
progress_percentage = (idx + 1) / total_steps
|
586 |
progress_bar.progress(progress_percentage)
|
587 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
588 |
-
|
589 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
590 |
|
591 |
# When the user clicks the process button, start the calculation
|
592 |
if st.button(f"Calculate ({index_choice})"):
|
593 |
if file_upload is not None:
|
594 |
-
|
595 |
-
if shape_type.lower() == "point":
|
596 |
-
# Process results for the selected aggregation period
|
597 |
results = process_aggregation(
|
598 |
locations_df,
|
599 |
start_date_str,
|
@@ -605,14 +728,10 @@ if st.button(f"Calculate ({index_choice})"):
|
|
605 |
aggregation_period,
|
606 |
custom_formula
|
607 |
)
|
608 |
-
|
609 |
-
# Display the results in a DataFrame
|
610 |
if results:
|
611 |
result_df = pd.DataFrame(results)
|
612 |
st.write(f"Processed Results Table ({aggregation_period}):")
|
613 |
st.dataframe(result_df)
|
614 |
-
|
615 |
-
# Provide a download button for the result CSV file
|
616 |
filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{aggregation_period.lower()}.csv"
|
617 |
st.download_button(
|
618 |
label="Download results as CSV",
|
@@ -620,15 +739,12 @@ if st.button(f"Calculate ({index_choice})"):
|
|
620 |
file_name=filename,
|
621 |
mime='text/csv'
|
622 |
)
|
623 |
-
|
624 |
-
# Once processing is complete, hide the spinner
|
625 |
-
st.spinner('') # This will stop the spinner
|
626 |
st.success('Processing complete!')
|
627 |
else:
|
628 |
st.warning("No results were generated.")
|
629 |
|
630 |
elif shape_type.lower() == "polygon":
|
631 |
-
# Process results for the selected aggregation period
|
632 |
results = process_aggregation(
|
633 |
locations_df,
|
634 |
start_date_str,
|
@@ -640,14 +756,10 @@ if st.button(f"Calculate ({index_choice})"):
|
|
640 |
aggregation_period,
|
641 |
custom_formula
|
642 |
)
|
643 |
-
|
644 |
-
# Display the results in a DataFrame
|
645 |
if results:
|
646 |
result_df = pd.DataFrame(results)
|
647 |
st.write(f"Processed Results Table ({aggregation_period}):")
|
648 |
st.dataframe(result_df)
|
649 |
-
|
650 |
-
# Provide a download button for the result CSV file
|
651 |
filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{aggregation_period.lower()}.csv"
|
652 |
st.download_button(
|
653 |
label="Download results as CSV",
|
@@ -655,12 +767,10 @@ if st.button(f"Calculate ({index_choice})"):
|
|
655 |
file_name=filename,
|
656 |
mime='text/csv'
|
657 |
)
|
658 |
-
|
659 |
-
st.spinner('') # This will stop the spinner
|
660 |
st.success('Processing complete!')
|
661 |
else:
|
662 |
st.warning("No results were generated.")
|
663 |
|
664 |
-
else:
|
665 |
-
st.warning("Please upload a file.")
|
666 |
-
|
|
|
7 |
from datetime import datetime
|
8 |
import leafmap.foliumap as leafmap
|
9 |
import re
|
10 |
+
from shapely.geometry import base
|
11 |
+
from lxml import etree
|
12 |
+
from xml.etree import ElementTree as ET
|
13 |
|
14 |
# Set up the page layout
|
15 |
st.set_page_config(layout="wide")
|
|
|
95 |
elif index_choice.lower() == 'average no₂':
|
96 |
st.write("Formula for Average NO₂: Average NO₂ = Mean(NO2 band)")
|
97 |
elif index_choice.lower() == 'custom formula':
|
98 |
+
custom_formula = st.text_input("Enter Custom Formula (e.g., B5,B4 for two bands or B3 for one band)")
|
99 |
# Check if custom formula is empty and show warning
|
100 |
if not custom_formula:
|
101 |
st.warning("Please enter a custom formula before proceeding.")
|
102 |
else:
|
103 |
+
# Check if the input contains a comma (indicating two bands)
|
104 |
+
if ',' in custom_formula:
|
105 |
+
# Split the input into two bands and strip whitespace
|
106 |
+
band1, band2 = [b.strip() for b in custom_formula.split(',', 1)]
|
107 |
+
st.write(f"Custom Formula: ({band1} - {band2}) / ({band1} + {band2})")
|
108 |
+
else:
|
109 |
+
# Single band case
|
110 |
+
band = custom_formula.strip()
|
111 |
+
st.write(f"Custom Formula: {band}")
|
112 |
|
113 |
# Function to get the corresponding reducer based on user input
|
114 |
def get_reducer(reducer_name):
|
|
|
134 |
index=0 # Default to 'mean'
|
135 |
)
|
136 |
|
|
|
137 |
def convert_to_ee_geometry(geometry):
|
138 |
+
# Handle Shapely geometry
|
139 |
+
if isinstance(geometry, base.BaseGeometry):
|
140 |
+
if geometry.is_valid:
|
141 |
+
geojson = geometry.__geo_interface__
|
142 |
+
print("Shapely GeoJSON:", geojson) # Debugging: Inspect the GeoJSON structure
|
143 |
+
return ee.Geometry(geojson)
|
144 |
+
else:
|
145 |
+
raise ValueError("Invalid geometry: The polygon geometry is not valid.")
|
146 |
+
|
147 |
+
# Handle GeoJSON input (string or dictionary)
|
148 |
+
elif isinstance(geometry, dict) or isinstance(geometry, str):
|
149 |
+
try:
|
150 |
+
if isinstance(geometry, str):
|
151 |
+
geometry = json.loads(geometry)
|
152 |
+
if 'type' in geometry and 'coordinates' in geometry:
|
153 |
+
print("GeoJSON Geometry:", geometry) # Debugging: Inspect the GeoJSON structure
|
154 |
+
return ee.Geometry(geometry)
|
155 |
+
else:
|
156 |
+
raise ValueError("GeoJSON format is invalid.")
|
157 |
+
except Exception as e:
|
158 |
+
raise ValueError(f"Error parsing GeoJSON: {e}")
|
159 |
+
|
160 |
+
# Handle KML input (string or file path)
|
161 |
+
elif isinstance(geometry, str) and geometry.lower().endswith(".kml"):
|
162 |
+
try:
|
163 |
+
# Parse the KML file
|
164 |
+
tree = ET.parse(geometry)
|
165 |
+
kml_root = tree.getroot()
|
166 |
+
|
167 |
+
# Extract coordinates from KML geometry (assuming it's a Polygon or MultiPolygon)
|
168 |
+
# KML coordinates are usually within the <coordinates> tag
|
169 |
+
kml_namespace = {'kml': 'http://www.opengis.net/kml/2.2'}
|
170 |
+
coordinates = kml_root.findall(".//kml:coordinates", kml_namespace)
|
171 |
+
|
172 |
+
if coordinates:
|
173 |
+
# Extract and format coordinates
|
174 |
+
coords_text = coordinates[0].text.strip()
|
175 |
+
coords = coords_text.split()
|
176 |
+
# Convert KML coordinates (comma-separated) into a list of tuples
|
177 |
+
coords = [tuple(map(float, coord.split(','))) for coord in coords]
|
178 |
+
geojson = {
|
179 |
+
"type": "Polygon", # Make sure the GeoJSON type is Polygon
|
180 |
+
"coordinates": [coords] # Wrap the coordinates in a list (required by GeoJSON format)
|
181 |
+
}
|
182 |
+
|
183 |
+
# Debugging: Inspect the KML-to-GeoJSON structure
|
184 |
+
print("KML GeoJSON:", geojson)
|
185 |
+
|
186 |
+
return ee.Geometry(geojson)
|
187 |
+
else:
|
188 |
+
raise ValueError("KML does not contain valid coordinates.")
|
189 |
+
except Exception as e:
|
190 |
+
raise ValueError(f"Error parsing KML: {e}")
|
191 |
+
|
192 |
else:
|
193 |
+
raise ValueError("Unsupported geometry input type. Supported types are Shapely, GeoJSON, and KML.")
|
194 |
|
195 |
# Function to read points from CSV
|
196 |
def read_csv(file_path):
|
|
|
207 |
gdf = gpd.read_file(file_path, driver='KML')
|
208 |
return gdf
|
209 |
|
210 |
+
|
211 |
+
# Date Input for Start and End Dates
|
212 |
+
start_date = st.date_input("Start Date", value=pd.to_datetime('2024-11-01'))
|
213 |
+
end_date = st.date_input("End Date", value=pd.to_datetime('2024-12-01'))
|
214 |
+
|
215 |
+
# Convert start_date and end_date to string format for Earth Engine
|
216 |
+
start_date_str = start_date.strftime('%Y-%m-%d')
|
217 |
+
end_date_str = end_date.strftime('%Y-%m-%d')
|
218 |
+
|
219 |
+
# Aggregation period selection
|
220 |
+
aggregation_period = st.selectbox("Select Aggregation Period", ["Daily", "Weekly", "Monthly", "Yearly"], index=0)
|
221 |
+
|
222 |
# Ask user whether they want to process 'Point' or 'Polygon' data (case-insensitive)
|
223 |
shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
|
224 |
|
|
|
247 |
st.stop() # Stop further processing if polygons are detected
|
248 |
|
249 |
# Processing the point data
|
250 |
+
with st.spinner('Processing Map...'):
|
251 |
if locations_df is not None and not locations_df.empty:
|
252 |
# For GeoJSON data, the coordinates are in the geometry column
|
253 |
if 'geometry' in locations_df.columns:
|
|
|
304 |
st.stop() # Stop further processing if point data is detected
|
305 |
|
306 |
# Processing the polygon data
|
307 |
+
with st.spinner('Processing Map...'):
|
308 |
if locations_df is not None and not locations_df.empty:
|
309 |
# Ensure the 'geometry' column exists in the dataframe
|
310 |
if 'geometry' not in locations_df.columns:
|
|
|
336 |
# Store the map in session_state
|
337 |
st.session_state.map_data = m
|
338 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
339 |
# Initialize session state for storing results if not already done
|
340 |
if 'results' not in st.session_state:
|
341 |
st.session_state.results = []
|
|
|
379 |
ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI')
|
380 |
return ndwi
|
381 |
|
382 |
+
def calculate_custom_formula(image, geometry, custom_formula, reducer_choice, scale=30):
|
383 |
+
try:
|
384 |
+
if "," in custom_formula:
|
385 |
+
band1, band2 = [b.strip() for b in custom_formula.split(",")]
|
386 |
+
band_names = image.bandNames().getInfo()
|
387 |
+
if band1 not in band_names or band2 not in band_names:
|
388 |
+
raise ValueError(f"One or both bands ({band1}, {band2}) do not exist in the image.")
|
389 |
+
result = image.normalizedDifference([band1, band2]).rename('custom_formula')
|
390 |
+
else:
|
391 |
+
band = custom_formula.strip()
|
392 |
+
band_names = image.bandNames().getInfo()
|
393 |
+
if band not in band_names:
|
394 |
+
raise ValueError(f"The band '{band}' does not exist in the image.")
|
395 |
+
result = image.select(band).rename('custom_formula')
|
396 |
+
return result
|
397 |
+
except Exception as e:
|
398 |
+
return ee.Image(0).rename('custom_formula').set('error', str(e))
|
399 |
+
|
400 |
# Modify aggregation functions to return the correct time period and aggregated results
|
401 |
def aggregate_data_daily(collection):
|
402 |
# Extract day from the image date (using the exact date)
|
|
|
417 |
return ee.ImageCollection(daily_images)
|
418 |
|
419 |
def aggregate_data_weekly(collection):
|
420 |
+
# Extract the start date of the week from the image date
|
421 |
+
collection = collection.map(lambda image: image.set(
|
422 |
+
'week_start', ee.Date(image.get('system:time_start'))
|
423 |
+
.advance(-ee.Date(image.get('system:time_start')).getRelative('day', 'week'), 'day')
|
424 |
+
))
|
425 |
+
# Group images by week start date
|
426 |
+
grouped_by_week = collection.aggregate_array('week_start').distinct()
|
427 |
+
|
428 |
+
def calculate_weekly_mean(week_start):
|
429 |
+
# Filter the collection by the specific week start date
|
430 |
+
weekly_collection = collection.filter(ee.Filter.eq('week_start', week_start))
|
431 |
+
weekly_mean = weekly_collection.mean() # Calculate mean for the week
|
432 |
+
return weekly_mean.set('week_start', week_start)
|
433 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
434 |
# Calculate the weekly mean for each week
|
435 |
weekly_images = ee.List(grouped_by_week.map(calculate_weekly_mean))
|
|
|
436 |
return ee.ImageCollection(weekly_images)
|
437 |
+
|
438 |
+
def aggregate_data_monthly(collection, start_date, end_date):
|
439 |
+
# Filter the collection for the specific date range
|
440 |
+
collection = collection.filterDate(start_date, end_date)
|
441 |
+
|
442 |
# Extract month and year from the image date
|
443 |
collection = collection.map(lambda image: image.set('month', ee.Date(image.get('system:time_start')).format('YYYY-MM')))
|
444 |
|
|
|
454 |
monthly_images = ee.List(grouped_by_month.map(calculate_monthly_mean))
|
455 |
|
456 |
return ee.ImageCollection(monthly_images)
|
457 |
+
|
458 |
def aggregate_data_yearly(collection):
|
459 |
# Extract year from the image date
|
460 |
collection = collection.map(lambda image: image.set('year', ee.Date(image.get('system:time_start')).format('YYYY')))
|
|
|
487 |
else:
|
488 |
st.write("Please Select any one option...."+ index_choice.lower())
|
489 |
|
490 |
+
def aggregate_data_weekly(collection):
|
491 |
+
def set_week_start(image):
|
492 |
+
# Get the image timestamp
|
493 |
+
date = ee.Date(image.get('system:time_start'))
|
494 |
+
# Calculate days since the start of the week (0 = Monday, 6 = Sunday)
|
495 |
+
days_since_week_start = date.getRelative('day', 'week')
|
496 |
+
# Convert to ee.Number and negate it to get the offset to the week start
|
497 |
+
offset = ee.Number(days_since_week_start).multiply(-1)
|
498 |
+
# Advance the date by the negative offset to get the week start
|
499 |
+
week_start = date.advance(offset, 'day')
|
500 |
+
return image.set('week_start', week_start.format('YYYY-MM-dd')) # Ensure string format
|
501 |
+
|
502 |
+
# Apply the week start calculation to each image
|
503 |
+
collection = collection.map(set_week_start)
|
504 |
+
|
505 |
+
# Group images by week start date
|
506 |
+
grouped_by_week = collection.aggregate_array('week_start').distinct()
|
507 |
+
|
508 |
+
def calculate_weekly_mean(week_start):
|
509 |
+
# Filter the collection by the specific week start date
|
510 |
+
weekly_collection = collection.filter(ee.Filter.eq('week_start', week_start))
|
511 |
+
weekly_mean = weekly_collection.mean() # Calculate mean for the week
|
512 |
+
return weekly_mean.set('week_start', week_start)
|
513 |
+
|
514 |
+
# Calculate the weekly mean for each week
|
515 |
+
weekly_images = ee.List(grouped_by_week.map(calculate_weekly_mean))
|
516 |
+
return ee.ImageCollection(weekly_images)
|
517 |
+
|
518 |
def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id, index_choice, reducer_choice, shape_type, aggregation_period, custom_formula=""):
|
519 |
aggregated_results = []
|
520 |
+
|
|
|
521 |
if index_choice.lower() == 'custom_formula' and not custom_formula:
|
522 |
st.error("Custom formula cannot be empty. Please provide a formula.")
|
523 |
+
return aggregated_results
|
524 |
|
|
|
525 |
total_steps = len(locations_df)
|
526 |
progress_bar = st.progress(0)
|
527 |
progress_text = st.empty()
|
528 |
+
|
529 |
with st.spinner('Processing data...'):
|
530 |
if shape_type.lower() == "point":
|
531 |
for idx, row in locations_df.iterrows():
|
|
|
532 |
latitude = row.get('latitude')
|
533 |
longitude = row.get('longitude')
|
|
|
534 |
if pd.isna(latitude) or pd.isna(longitude):
|
535 |
st.warning(f"Skipping location {idx} with missing latitude or longitude")
|
536 |
continue
|
537 |
+
|
538 |
location_name = row.get('name', f"Location_{idx}")
|
|
|
539 |
roi = ee.Geometry.Point([longitude, latitude])
|
540 |
+
|
541 |
collection = ee.ImageCollection(dataset_id) \
|
542 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
543 |
.filterBounds(roi)
|
544 |
+
|
545 |
# Aggregate data based on the selected period
|
546 |
if aggregation_period.lower() == 'daily':
|
547 |
collection = aggregate_data_daily(collection)
|
548 |
elif aggregation_period.lower() == 'weekly':
|
549 |
collection = aggregate_data_weekly(collection)
|
550 |
elif aggregation_period.lower() == 'monthly':
|
551 |
+
collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
|
552 |
elif aggregation_period.lower() == 'yearly':
|
553 |
collection = aggregate_data_yearly(collection)
|
554 |
+
|
555 |
# Process each image in the collection
|
556 |
image_list = collection.toList(collection.size())
|
557 |
+
processed_weeks = set() # Track processed weeks to avoid duplicates
|
558 |
for i in range(image_list.size().getInfo()):
|
559 |
image = ee.Image(image_list.get(i))
|
560 |
+
|
561 |
if aggregation_period.lower() == 'daily':
|
562 |
timestamp = image.get('day')
|
563 |
+
period_label = 'Date'
|
564 |
+
date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
|
565 |
elif aggregation_period.lower() == 'weekly':
|
566 |
+
timestamp = image.get('week_start')
|
567 |
+
period_label = 'Week'
|
568 |
+
date = ee.String(timestamp).getInfo() # Already formatted as YYYY-MM-dd
|
569 |
+
# Skip if week is outside the date range or already processed
|
570 |
+
if (pd.to_datetime(date) < pd.to_datetime(start_date_str) or
|
571 |
+
pd.to_datetime(date) > pd.to_datetime(end_date_str) or
|
572 |
+
date in processed_weeks):
|
573 |
+
continue
|
574 |
+
processed_weeks.add(date)
|
575 |
elif aggregation_period.lower() == 'monthly':
|
576 |
timestamp = image.get('month')
|
577 |
+
period_label = 'Month'
|
578 |
+
date = ee.Date(timestamp).format('YYYY-MM').getInfo()
|
579 |
elif aggregation_period.lower() == 'yearly':
|
580 |
timestamp = image.get('year')
|
581 |
+
period_label = 'Year'
|
582 |
+
date = ee.Date(timestamp).format('YYYY').getInfo()
|
583 |
+
|
|
|
584 |
index_image = calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_formula)
|
|
|
|
|
|
|
|
|
|
|
585 |
|
|
|
586 |
try:
|
587 |
index_value = index_image.reduceRegion(
|
588 |
reducer=get_reducer(reducer_choice),
|
589 |
geometry=roi,
|
590 |
scale=30
|
591 |
).get(index_image.bandNames().get(0))
|
592 |
+
|
593 |
calculated_value = index_value.getInfo()
|
594 |
+
|
|
|
595 |
if isinstance(calculated_value, (int, float)):
|
596 |
aggregated_results.append({
|
597 |
'Location Name': location_name,
|
598 |
'Latitude': latitude,
|
599 |
'Longitude': longitude,
|
600 |
+
period_label: date,
|
601 |
+
'Start Date': start_date_str,
|
602 |
+
'End Date': end_date_str,
|
603 |
'Calculated Value': calculated_value
|
604 |
})
|
605 |
else:
|
606 |
st.warning(f"Skipping invalid value for {location_name} on {date}")
|
607 |
except Exception as e:
|
608 |
st.error(f"Error retrieving value for {location_name}: {e}")
|
609 |
+
|
|
|
610 |
progress_percentage = (idx + 1) / total_steps
|
611 |
progress_bar.progress(progress_percentage)
|
612 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
613 |
+
|
614 |
elif shape_type.lower() == "polygon":
|
615 |
for idx, row in locations_df.iterrows():
|
616 |
polygon_name = row.get('name', f"Polygon_{idx}")
|
617 |
polygon_geometry = row.get('geometry')
|
|
|
618 |
location_name = polygon_name
|
619 |
+
|
620 |
try:
|
621 |
roi = convert_to_ee_geometry(polygon_geometry)
|
622 |
except ValueError as e:
|
623 |
st.warning(f"Skipping invalid polygon {polygon_name}: {e}")
|
624 |
continue
|
625 |
+
|
626 |
collection = ee.ImageCollection(dataset_id) \
|
627 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
628 |
.filterBounds(roi)
|
629 |
+
|
630 |
# Aggregate data based on the selected period
|
631 |
if aggregation_period.lower() == 'daily':
|
632 |
collection = aggregate_data_daily(collection)
|
633 |
elif aggregation_period.lower() == 'weekly':
|
634 |
collection = aggregate_data_weekly(collection)
|
635 |
elif aggregation_period.lower() == 'monthly':
|
636 |
+
collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
|
637 |
elif aggregation_period.lower() == 'yearly':
|
638 |
collection = aggregate_data_yearly(collection)
|
639 |
+
|
640 |
# Process each image in the collection
|
641 |
image_list = collection.toList(collection.size())
|
642 |
+
processed_weeks = set() # Track processed weeks to avoid duplicates
|
643 |
for i in range(image_list.size().getInfo()):
|
644 |
image = ee.Image(image_list.get(i))
|
645 |
+
|
646 |
if aggregation_period.lower() == 'daily':
|
647 |
timestamp = image.get('day')
|
648 |
+
period_label = 'Date'
|
649 |
+
date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
|
650 |
elif aggregation_period.lower() == 'weekly':
|
651 |
+
timestamp = image.get('week_start')
|
652 |
+
period_label = 'Week'
|
653 |
+
date = ee.String(timestamp).getInfo() # Already formatted as YYYY-MM-dd
|
654 |
+
# Skip if week is outside the date range or already processed
|
655 |
+
if (pd.to_datetime(date) < pd.to_datetime(start_date_str) or
|
656 |
+
pd.to_datetime(date) > pd.to_datetime(end_date_str) or
|
657 |
+
date in processed_weeks):
|
658 |
+
continue
|
659 |
+
processed_weeks.add(date)
|
660 |
elif aggregation_period.lower() == 'monthly':
|
661 |
timestamp = image.get('month')
|
662 |
+
period_label = 'Month'
|
663 |
+
date = ee.Date(timestamp).format('YYYY-MM').getInfo()
|
664 |
elif aggregation_period.lower() == 'yearly':
|
665 |
timestamp = image.get('year')
|
666 |
+
period_label = 'Year'
|
667 |
+
date = ee.Date(timestamp).format('YYYY').getInfo()
|
|
|
|
|
|
|
668 |
|
669 |
+
index_image = calculate_index_for_period(image, roi, index_choice, reducer_choice, custom_formula)
|
|
|
|
|
|
|
670 |
|
|
|
671 |
try:
|
672 |
index_value = index_image.reduceRegion(
|
673 |
reducer=get_reducer(reducer_choice),
|
674 |
geometry=roi,
|
675 |
scale=30
|
676 |
).get(index_image.bandNames().get(0))
|
677 |
+
|
678 |
calculated_value = index_value.getInfo()
|
679 |
+
|
|
|
680 |
if isinstance(calculated_value, (int, float)):
|
681 |
aggregated_results.append({
|
682 |
'Location Name': location_name,
|
683 |
+
period_label: date,
|
684 |
+
'Start Date': start_date_str,
|
685 |
+
'End Date': end_date_str,
|
686 |
'Calculated Value': calculated_value
|
687 |
})
|
688 |
else:
|
689 |
st.warning(f"Skipping invalid value for {location_name} on {date}")
|
690 |
except Exception as e:
|
691 |
st.error(f"Error retrieving value for {location_name}: {e}")
|
692 |
+
|
|
|
693 |
progress_percentage = (idx + 1) / total_steps
|
694 |
progress_bar.progress(progress_percentage)
|
695 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
696 |
+
|
697 |
+
if aggregated_results:
|
698 |
+
result_df = pd.DataFrame(aggregated_results)
|
699 |
+
if aggregation_period.lower() == 'daily':
|
700 |
+
aggregated_output = result_df.groupby('Location Name').agg({
|
701 |
+
'Latitude': 'first' if shape_type.lower() == 'point' else None,
|
702 |
+
'Longitude': 'first' if shape_type.lower() == 'point' else None,
|
703 |
+
'Start Date': 'first',
|
704 |
+
'End Date': 'first',
|
705 |
+
'Calculated Value': 'mean'
|
706 |
+
}).reset_index()
|
707 |
+
# Remove None columns (for polygons)
|
708 |
+
aggregated_output = aggregated_output[[col for col in aggregated_output.columns if col is not None]]
|
709 |
+
aggregated_output.rename(columns={'Calculated Value': 'Aggregated Value'}, inplace=True)
|
710 |
+
return aggregated_output.to_dict(orient='records')
|
711 |
+
else:
|
712 |
+
return result_df.to_dict(orient='records')
|
713 |
+
|
714 |
+
return []
|
715 |
|
716 |
# When the user clicks the process button, start the calculation
|
717 |
if st.button(f"Calculate ({index_choice})"):
|
718 |
if file_upload is not None:
|
719 |
+
if shape_type.lower() == "point":
|
|
|
|
|
720 |
results = process_aggregation(
|
721 |
locations_df,
|
722 |
start_date_str,
|
|
|
728 |
aggregation_period,
|
729 |
custom_formula
|
730 |
)
|
|
|
|
|
731 |
if results:
|
732 |
result_df = pd.DataFrame(results)
|
733 |
st.write(f"Processed Results Table ({aggregation_period}):")
|
734 |
st.dataframe(result_df)
|
|
|
|
|
735 |
filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{aggregation_period.lower()}.csv"
|
736 |
st.download_button(
|
737 |
label="Download results as CSV",
|
|
|
739 |
file_name=filename,
|
740 |
mime='text/csv'
|
741 |
)
|
742 |
+
st.spinner('')
|
|
|
|
|
743 |
st.success('Processing complete!')
|
744 |
else:
|
745 |
st.warning("No results were generated.")
|
746 |
|
747 |
elif shape_type.lower() == "polygon":
|
|
|
748 |
results = process_aggregation(
|
749 |
locations_df,
|
750 |
start_date_str,
|
|
|
756 |
aggregation_period,
|
757 |
custom_formula
|
758 |
)
|
|
|
|
|
759 |
if results:
|
760 |
result_df = pd.DataFrame(results)
|
761 |
st.write(f"Processed Results Table ({aggregation_period}):")
|
762 |
st.dataframe(result_df)
|
|
|
|
|
763 |
filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{aggregation_period.lower()}.csv"
|
764 |
st.download_button(
|
765 |
label="Download results as CSV",
|
|
|
767 |
file_name=filename,
|
768 |
mime='text/csv'
|
769 |
)
|
770 |
+
st.spinner('')
|
|
|
771 |
st.success('Processing complete!')
|
772 |
else:
|
773 |
st.warning("No results were generated.")
|
774 |
|
775 |
+
else:
|
776 |
+
st.warning("Please upload a file.")
|
|