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