update app.py
Browse files
app.py
CHANGED
@@ -73,48 +73,24 @@ elif imagery_base == "MODIS":
|
|
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(
|
77 |
-
"Enter Custom Earth Engine Dataset ID (e.g., ee.Image('ACA/reef_habitat/v2_0') or ee.ImageCollection('AHN/AHN4'))",
|
78 |
-
value=""
|
79 |
-
)
|
80 |
if custom_dataset_id:
|
81 |
try:
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
is_image = True
|
93 |
-
except:
|
94 |
-
is_image = False
|
95 |
-
|
96 |
-
if is_image:
|
97 |
-
image = ee.Image(dataset_id)
|
98 |
-
band_names = image.bandNames().getInfo()
|
99 |
-
data = {
|
100 |
-
f"Custom Image: {dataset_id}": {
|
101 |
-
"sub_options": {dataset_id: f"Custom Image ({dataset_id})"},
|
102 |
-
"bands": {dataset_id: band_names}
|
103 |
-
}
|
104 |
}
|
105 |
-
|
106 |
-
|
107 |
-
collection = ee.ImageCollection(dataset_id)
|
108 |
-
band_names = collection.first().bandNames().getInfo()
|
109 |
-
data = {
|
110 |
-
f"Custom ImageCollection: {dataset_id}": {
|
111 |
-
"sub_options": {dataset_id: f"Custom ImageCollection ({dataset_id})"},
|
112 |
-
"bands": {dataset_id: band_names}
|
113 |
-
}
|
114 |
-
}
|
115 |
-
st.write(f"Fetched bands for {dataset_id} (ImageCollection): {', '.join(band_names)}")
|
116 |
except Exception as e:
|
117 |
-
st.error(f"Error fetching dataset: {str(e)}. Please check the dataset ID.")
|
118 |
data = {}
|
119 |
else:
|
120 |
st.warning("Please enter a custom dataset ID to proceed.")
|
@@ -138,25 +114,15 @@ if main_selection:
|
|
138 |
sub_options = data[main_selection]["sub_options"]
|
139 |
sub_selection = st.selectbox(f"Select Specific {imagery_base} Dataset ID", list(sub_options.keys()))
|
140 |
|
|
|
141 |
if sub_selection:
|
142 |
st.write(f"You selected: {main_selection} -> {sub_options[sub_selection]}")
|
143 |
st.write(f"Dataset ID: {sub_selection}")
|
144 |
-
dataset_id = sub_selection
|
145 |
|
146 |
# Earth Engine Index Calculator Section
|
147 |
st.header("Earth Engine Index Calculator")
|
148 |
|
149 |
-
# Define validate_formula function
|
150 |
-
def validate_formula(formula, selected_bands):
|
151 |
-
allowed_chars = set(" +-*/()0123456789.")
|
152 |
-
terms = re.findall(r'[a-zA-Z][a-zA-Z0-9_]*', formula)
|
153 |
-
invalid_terms = [term for term in terms if term not in selected_bands]
|
154 |
-
if invalid_terms:
|
155 |
-
return False, f"Invalid terms in formula: {', '.join(invalid_terms)}. Use only {', '.join(selected_bands)}."
|
156 |
-
if not all(char in allowed_chars or char in ''.join(selected_bands) for char in formula):
|
157 |
-
return False, "Formula contains invalid characters. Use only bands, numbers, and operators (+, -, *, /, ())"
|
158 |
-
return True, ""
|
159 |
-
|
160 |
# Load band information based on selected dataset
|
161 |
if main_selection and sub_selection:
|
162 |
dataset_bands = data[main_selection]["bands"].get(sub_selection, [])
|
@@ -170,27 +136,38 @@ if main_selection and sub_selection:
|
|
170 |
help=f"Select 1 or 2 bands from: {', '.join(dataset_bands)}"
|
171 |
)
|
172 |
|
173 |
-
# Ensure minimum 1
|
174 |
if len(selected_bands) < 1:
|
175 |
st.warning("Please select at least one band.")
|
176 |
st.stop()
|
177 |
|
178 |
# Show custom formula input if bands are selected
|
179 |
if selected_bands:
|
|
|
180 |
if len(selected_bands) == 1:
|
181 |
default_formula = f"{selected_bands[0]}"
|
182 |
example = f"'{selected_bands[0]} * 2' or '{selected_bands[0]} + 1'"
|
183 |
-
else:
|
184 |
default_formula = f"({selected_bands[0]} - {selected_bands[1]}) / ({selected_bands[0]} + {selected_bands[1]})"
|
185 |
example = f"'{selected_bands[0]} * {selected_bands[1]} / 2' or '({selected_bands[0]} - {selected_bands[1]}) / ({selected_bands[0]} + {selected_bands[1]})'"
|
186 |
|
187 |
custom_formula = st.text_input(
|
188 |
-
"Enter Custom Formula
|
189 |
value=default_formula,
|
190 |
help=f"Use only these bands: {', '.join(selected_bands)}. Examples: {example}"
|
191 |
)
|
192 |
|
193 |
# Validate the formula
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
is_valid, error_message = validate_formula(custom_formula, selected_bands)
|
195 |
if not is_valid:
|
196 |
st.error(error_message)
|
@@ -199,7 +176,10 @@ if main_selection and sub_selection:
|
|
199 |
st.warning("Please enter a custom formula to proceed.")
|
200 |
st.stop()
|
201 |
|
|
|
202 |
st.write(f"Custom Formula: {custom_formula}")
|
|
|
|
|
203 |
|
204 |
# Function to get the corresponding reducer based on user input
|
205 |
def get_reducer(reducer_name):
|
@@ -454,7 +434,7 @@ def calculate_custom_formula(image, geometry, selected_bands, custom_formula, re
|
|
454 |
except Exception as e:
|
455 |
st.error(f"Unexpected error: {e}")
|
456 |
return ee.Image(0).rename('custom_result').set('error', str(e))
|
457 |
-
|
458 |
# Function to calculate index for a period
|
459 |
def calculate_index_for_period(image, roi, selected_bands, custom_formula, reducer_choice):
|
460 |
return calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice)
|
@@ -514,30 +494,7 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
514 |
if not custom_formula:
|
515 |
st.error("Custom formula cannot be empty. Please provide a formula.")
|
516 |
return aggregated_results
|
517 |
-
|
518 |
-
# Initialize is_image and scale with default values
|
519 |
-
is_image = False # Default to False, will be updated based on dataset type
|
520 |
-
scale = 30 # Default scale, will be overridden for ee.Image if possible
|
521 |
|
522 |
-
# Check if dataset_id is an ee.Image or ee.ImageCollection
|
523 |
-
try:
|
524 |
-
dataset = ee.Image(dataset_id)
|
525 |
-
is_image = True
|
526 |
-
# Try to get the nominal scale from the dataset
|
527 |
-
try:
|
528 |
-
scale = dataset.projection().nominalScale().getInfo()
|
529 |
-
except:
|
530 |
-
st.warning(f"Could not determine nominal scale for {dataset_id}. Using default scale of 30m.")
|
531 |
-
scale = 30
|
532 |
-
except:
|
533 |
-
try:
|
534 |
-
dataset = ee.ImageCollection(dataset_id)
|
535 |
-
is_image = False
|
536 |
-
scale = 30 # Default scale for ImageCollections
|
537 |
-
except Exception as e:
|
538 |
-
st.error(f"Invalid dataset ID '{dataset_id}': {str(e)}. Must be a valid ee.Image or ee.ImageCollection.")
|
539 |
-
return aggregated_results
|
540 |
-
|
541 |
total_steps = len(locations_df)
|
542 |
progress_bar = st.progress(0)
|
543 |
progress_text = st.empty()
|
@@ -565,36 +522,7 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
565 |
collection = ee.ImageCollection(dataset_id) \
|
566 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
567 |
.filterBounds(roi)
|
568 |
-
|
569 |
-
if is_image:
|
570 |
-
# Handle any ee.Image dataset
|
571 |
-
image = dataset
|
572 |
-
index_image = calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice, scale=scale)
|
573 |
-
index_value = index_image.reduceRegion(
|
574 |
-
reducer=get_reducer(reducer_choice),
|
575 |
-
geometry=roi,
|
576 |
-
scale=scale # Use the dataset's native scale or default
|
577 |
-
).get('custom_result')
|
578 |
-
calculated_value = index_value.getInfo()
|
579 |
-
|
580 |
-
if isinstance(calculated_value, (int, float)):
|
581 |
-
aggregated_results.append({
|
582 |
-
'Location Name': location_name,
|
583 |
-
'Latitude': latitude,
|
584 |
-
'Longitude': longitude,
|
585 |
-
'Date': 'Static', # Static image has no time dimension
|
586 |
-
'Start Date': start_date_str,
|
587 |
-
'End Date': end_date_str,
|
588 |
-
'Calculated Value': calculated_value
|
589 |
-
})
|
590 |
-
else:
|
591 |
-
st.warning(f"Skipping invalid value for {location_name}")
|
592 |
-
else:
|
593 |
-
# Handle ee.ImageCollection (existing logic)
|
594 |
-
collection = dataset \
|
595 |
-
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
596 |
-
.filterBounds(roi)
|
597 |
-
|
598 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
599 |
collection = aggregate_data_custom(collection)
|
600 |
elif aggregation_period.lower() == 'weekly':
|
@@ -674,17 +602,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
674 |
except ValueError as e:
|
675 |
st.warning(f"Skipping invalid polygon {polygon_name}: {e}")
|
676 |
continue
|
677 |
-
|
678 |
-
if is_image:
|
679 |
-
# Handle any ee.Image dataset
|
680 |
-
image = dataset
|
681 |
-
index_image = calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice, scale=scale)
|
682 |
-
index_value = index_image.reduceRegion(
|
683 |
-
reducer=get_reducer(reducer_choice),
|
684 |
-
geometry=roi,
|
685 |
-
scale=scale
|
686 |
-
).get('custom_result')
|
687 |
-
calculated_value = index_value.getInfo()
|
688 |
|
689 |
collection = ee.ImageCollection(dataset_id) \
|
690 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
|
|
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.")
|
|
|
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:
|
119 |
st.write(f"You selected: {main_selection} -> {sub_options[sub_selection]}")
|
120 |
st.write(f"Dataset ID: {sub_selection}")
|
121 |
+
dataset_id = sub_selection # Use the key directly as the dataset ID
|
122 |
|
123 |
# Earth Engine Index Calculator Section
|
124 |
st.header("Earth Engine Index Calculator")
|
125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
# Load band information based on selected dataset
|
127 |
if main_selection and sub_selection:
|
128 |
dataset_bands = data[main_selection]["bands"].get(sub_selection, [])
|
|
|
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)
|
|
|
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):
|
|
|
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)
|
|
|
494 |
if not custom_formula:
|
495 |
st.error("Custom formula cannot be empty. Please provide a formula.")
|
496 |
return aggregated_results
|
|
|
|
|
|
|
|
|
497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
498 |
total_steps = len(locations_df)
|
499 |
progress_bar = st.progress(0)
|
500 |
progress_text = st.empty()
|
|
|
522 |
collection = ee.ImageCollection(dataset_id) \
|
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':
|
|
|
602 |
except ValueError as e:
|
603 |
st.warning(f"Skipping invalid polygon {polygon_name}: {e}")
|
604 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
605 |
|
606 |
collection = ee.ImageCollection(dataset_id) \
|
607 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|