Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -143,31 +143,51 @@ def calculate_custom_formula(image, geometry, selected_bands, custom_formula, re
|
|
143 |
return ee.Image(0).rename('custom_result').set('error', str(e))
|
144 |
|
145 |
# Aggregation functions
|
146 |
-
def
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
daily_mean = daily_collection.mean()
|
152 |
-
return daily_mean.set('
|
|
|
153 |
daily_images = ee.List(grouped_by_day.map(calculate_daily_mean))
|
154 |
return ee.ImageCollection(daily_images)
|
155 |
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
def aggregate_data_monthly(collection, start_date, end_date):
|
173 |
collection = collection.filterDate(start_date, end_date)
|
@@ -180,6 +200,7 @@ def aggregate_data_monthly(collection, start_date, end_date):
|
|
180 |
monthly_images = ee.List(grouped_by_month.map(calculate_monthly_mean))
|
181 |
return ee.ImageCollection(monthly_images)
|
182 |
|
|
|
183 |
def aggregate_data_yearly(collection):
|
184 |
collection = collection.map(lambda image: image.set('year', ee.Date(image.get('system:time_start')).format('YYYY')))
|
185 |
grouped_by_year = collection.aggregate_array('year').distinct()
|
@@ -190,6 +211,7 @@ def aggregate_data_yearly(collection):
|
|
190 |
yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
|
191 |
return ee.ImageCollection(yearly_images)
|
192 |
|
|
|
193 |
# Worker function for processing a single geometry
|
194 |
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):
|
195 |
if shape_type.lower() == "point":
|
@@ -220,11 +242,13 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
220 |
collection = ee.ImageCollection(dataset_id) \
|
221 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
222 |
.filterBounds(roi)
|
223 |
-
|
224 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
225 |
collection = aggregate_data_custom(collection)
|
|
|
|
|
226 |
elif aggregation_period.lower() == 'weekly':
|
227 |
-
collection = aggregate_data_weekly(collection)
|
228 |
elif aggregation_period.lower() == 'monthly':
|
229 |
collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
|
230 |
elif aggregation_period.lower() == 'yearly':
|
@@ -234,13 +258,16 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
234 |
image_list = collection.toList(collection.size())
|
235 |
processed_weeks = set()
|
236 |
aggregated_results = []
|
237 |
-
|
238 |
for i in range(image_list.size().getInfo()):
|
239 |
image = ee.Image(image_list.get(i))
|
240 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
241 |
timestamp = image.get('day')
|
242 |
period_label = 'Date'
|
243 |
date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
|
|
|
|
|
|
|
|
|
244 |
elif aggregation_period.lower() == 'weekly':
|
245 |
timestamp = image.get('week_start')
|
246 |
period_label = 'Week'
|
@@ -281,16 +308,15 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
281 |
aggregated_results.append(result)
|
282 |
except Exception as e:
|
283 |
st.error(f"Error retrieving value for {location_name}: {e}")
|
284 |
-
|
285 |
return aggregated_results
|
286 |
|
|
|
287 |
# Main processing function
|
288 |
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):
|
289 |
aggregated_results = []
|
290 |
total_steps = len(locations_df)
|
291 |
progress_bar = st.progress(0)
|
292 |
progress_text = st.empty()
|
293 |
-
|
294 |
start_time = time.time() # Start timing the process
|
295 |
with ThreadPoolExecutor(max_workers=10) as executor:
|
296 |
futures = []
|
@@ -312,7 +338,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
312 |
include_boundary
|
313 |
)
|
314 |
futures.append(future)
|
315 |
-
|
316 |
completed = 0
|
317 |
for future in as_completed(futures):
|
318 |
result = future.result()
|
@@ -322,11 +347,9 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
322 |
progress_percentage = completed / total_steps
|
323 |
progress_bar.progress(progress_percentage)
|
324 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
325 |
-
|
326 |
# End timing the process
|
327 |
end_time = time.time()
|
328 |
processing_time = end_time - start_time # Calculate total processing time
|
329 |
-
|
330 |
if aggregated_results:
|
331 |
result_df = pd.DataFrame(aggregated_results)
|
332 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
@@ -351,7 +374,6 @@ imagery_base = st.selectbox("Select Imagery Base", ["Sentinel", "Landsat", "MODI
|
|
351 |
|
352 |
# Initialize data as an empty dictionary
|
353 |
data = {}
|
354 |
-
|
355 |
if imagery_base == "Sentinel":
|
356 |
dataset_file = "sentinel_datasets.json"
|
357 |
try:
|
@@ -397,18 +419,14 @@ elif imagery_base == "Custom Input":
|
|
397 |
else:
|
398 |
st.warning("Please enter a custom dataset ID to proceed.")
|
399 |
data = {}
|
400 |
-
|
401 |
if not data:
|
402 |
st.error("No valid dataset available. Please check your inputs.")
|
403 |
st.stop()
|
404 |
|
405 |
st.markdown("<hr><h5><b>{}</b></h5>".format(imagery_base), unsafe_allow_html=True)
|
406 |
-
|
407 |
main_selection = st.selectbox(f"Select {imagery_base} Dataset Category", list(data.keys()))
|
408 |
-
|
409 |
sub_selection = None
|
410 |
dataset_id = None
|
411 |
-
|
412 |
if main_selection:
|
413 |
sub_options = data[main_selection]["sub_options"]
|
414 |
sub_selection = st.selectbox(f"Select Specific {imagery_base} Dataset ID", list(sub_options.keys()))
|
@@ -418,7 +436,6 @@ if main_selection:
|
|
418 |
dataset_id = sub_selection
|
419 |
|
420 |
st.markdown("<hr><h5><b>Earth Engine Index Calculator</b></h5>", unsafe_allow_html=True)
|
421 |
-
|
422 |
if main_selection and sub_selection:
|
423 |
dataset_bands = data[main_selection]["bands"].get(sub_selection, [])
|
424 |
st.write(f"Available Bands for {sub_options[sub_selection]}: {', '.join(dataset_bands)}")
|
@@ -428,11 +445,9 @@ if main_selection and sub_selection:
|
|
428 |
default=[dataset_bands[0]] if dataset_bands else [],
|
429 |
help=f"Select 1 or 2 bands from: {', '.join(dataset_bands)}"
|
430 |
)
|
431 |
-
|
432 |
if len(selected_bands) < 1:
|
433 |
st.warning("Please select at least one band.")
|
434 |
st.stop()
|
435 |
-
|
436 |
if selected_bands:
|
437 |
if len(selected_bands) == 1:
|
438 |
default_formula = f"{selected_bands[0]}"
|
@@ -445,7 +460,6 @@ if main_selection and sub_selection:
|
|
445 |
value=default_formula,
|
446 |
help=f"Use only these bands: {', '.join(selected_bands)}. Examples: {example}"
|
447 |
)
|
448 |
-
|
449 |
def validate_formula(formula, selected_bands):
|
450 |
allowed_chars = set(" +-*/()0123456789.")
|
451 |
terms = re.findall(r'[a-zA-Z][a-zA-Z0-9_]*', formula)
|
@@ -455,7 +469,6 @@ if main_selection and sub_selection:
|
|
455 |
if not all(char in allowed_chars or char in ''.join(selected_bands) for char in formula):
|
456 |
return False, "Formula contains invalid characters. Use only bands, numbers, and operators (+, -, *, /, ())"
|
457 |
return True, ""
|
458 |
-
|
459 |
is_valid, error_message = validate_formula(custom_formula, selected_bands)
|
460 |
if not is_valid:
|
461 |
st.error(error_message)
|
@@ -463,7 +476,6 @@ if main_selection and sub_selection:
|
|
463 |
elif not custom_formula:
|
464 |
st.warning("Please enter a custom formula to proceed.")
|
465 |
st.stop()
|
466 |
-
|
467 |
st.write(f"Custom Formula: {custom_formula}")
|
468 |
|
469 |
reducer_choice = st.selectbox(
|
@@ -478,8 +490,8 @@ start_date_str = start_date.strftime('%Y-%m-%d')
|
|
478 |
end_date_str = end_date.strftime('%Y-%m-%d')
|
479 |
|
480 |
aggregation_period = st.selectbox(
|
481 |
-
"Select Aggregation Period (e.g, Custom(Start Date to End Date) , Weekly , Monthly , Yearly)",
|
482 |
-
["Custom (Start Date to End Date)", "Weekly", "Monthly", "Yearly"],
|
483 |
index=0
|
484 |
)
|
485 |
|
@@ -487,7 +499,6 @@ shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", [
|
|
487 |
|
488 |
kernel_size = None
|
489 |
include_boundary = None
|
490 |
-
|
491 |
if shape_type.lower() == "point":
|
492 |
kernel_size = st.selectbox(
|
493 |
"Select Calculation Area(e.g, Point , 3x3 Kernel , 5x5 Kernel)",
|
@@ -512,14 +523,11 @@ if file_upload is not None:
|
|
512 |
if file_upload.name.endswith('.csv'):
|
513 |
# Read the CSV file
|
514 |
locations_df = pd.read_csv(file_upload)
|
515 |
-
|
516 |
# Show the first few rows to help user identify columns
|
517 |
st.write("Preview of your uploaded data (first 5 rows):")
|
518 |
st.dataframe(locations_df.head())
|
519 |
-
|
520 |
# Get all column names from the uploaded file
|
521 |
all_columns = locations_df.columns.tolist()
|
522 |
-
|
523 |
# Let user select latitude and longitude columns from dropdown
|
524 |
col1, col2 = st.columns(2)
|
525 |
with col1:
|
@@ -536,18 +544,15 @@ if file_upload is not None:
|
|
536 |
index=all_columns.index('longitude') if 'longitude' in all_columns else 0,
|
537 |
help="Select the column containing longitude values"
|
538 |
)
|
539 |
-
|
540 |
# Validate the selected columns contain numeric data
|
541 |
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]):
|
542 |
st.error("Error: Selected Latitude and Longitude columns must contain numeric values")
|
543 |
st.stop()
|
544 |
-
|
545 |
# Rename the selected columns to standard names for processing
|
546 |
locations_df = locations_df.rename(columns={
|
547 |
original_lat_col: 'latitude',
|
548 |
original_lon_col: 'longitude'
|
549 |
})
|
550 |
-
|
551 |
elif file_upload.name.endswith('.geojson'):
|
552 |
locations_df = gpd.read_file(file_upload)
|
553 |
if 'geometry' in locations_df.columns:
|
@@ -583,7 +588,6 @@ if file_upload is not None:
|
|
583 |
original_lon_col = 'longitude'
|
584 |
except Exception as e:
|
585 |
st.error(f"Error parsing KML file: {str(e)}")
|
586 |
-
|
587 |
# Display map for points if we have valid data
|
588 |
if not locations_df.empty and 'latitude' in locations_df.columns and 'longitude' in locations_df.columns:
|
589 |
m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10)
|
@@ -595,7 +599,6 @@ if file_upload is not None:
|
|
595 |
m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name'))
|
596 |
st.write("Map of Uploaded Points:")
|
597 |
m.to_streamlit()
|
598 |
-
|
599 |
elif shape_type.lower() == "polygon":
|
600 |
if file_upload.name.endswith('.csv'):
|
601 |
st.error("CSV upload not supported for polygons. Please upload a GeoJSON or KML file.")
|
@@ -625,7 +628,6 @@ if file_upload is not None:
|
|
625 |
locations_df = gpd.GeoDataFrame(polygons, geometry=gpd.GeoSeries.from_wkt([p['geometry'] for p in polygons]), crs="EPSG:4326")
|
626 |
except Exception as e:
|
627 |
st.error(f"Error parsing KML file: {str(e)}")
|
628 |
-
|
629 |
# Display map for polygons if we have valid data
|
630 |
if not locations_df.empty and 'geometry' in locations_df.columns:
|
631 |
centroid_lat = locations_df.geometry.centroid.y.mean()
|
@@ -658,12 +660,10 @@ if st.button(f"Calculate {custom_formula}"):
|
|
658 |
kernel_size,
|
659 |
include_boundary
|
660 |
)
|
661 |
-
|
662 |
if results:
|
663 |
result_df = pd.DataFrame(results)
|
664 |
st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
|
665 |
st.dataframe(result_df)
|
666 |
-
|
667 |
filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
|
668 |
st.download_button(
|
669 |
label="Download results as CSV",
|
@@ -671,12 +671,10 @@ if st.button(f"Calculate {custom_formula}"):
|
|
671 |
file_name=filename,
|
672 |
mime='text/csv'
|
673 |
)
|
674 |
-
|
675 |
st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
|
676 |
else:
|
677 |
st.warning("No results were generated. Check your inputs or formula.")
|
678 |
st.info(f"Total processing time: {processing_time:.2f} seconds.")
|
679 |
-
|
680 |
except Exception as e:
|
681 |
st.error(f"An error occurred during processing: {str(e)}")
|
682 |
else:
|
|
|
143 |
return ee.Image(0).rename('custom_result').set('error', str(e))
|
144 |
|
145 |
# Aggregation functions
|
146 |
+
def aggregate_data_daily(collection):
|
147 |
+
"""
|
148 |
+
Aggregates data on a daily basis.
|
149 |
+
"""
|
150 |
+
def set_day_start(image):
|
151 |
+
date = ee.Date(image.get('system:time_start'))
|
152 |
+
day_start = date.format('YYYY-MM-dd')
|
153 |
+
return image.set('day_start', day_start)
|
154 |
+
|
155 |
+
collection = collection.map(set_day_start)
|
156 |
+
grouped_by_day = collection.aggregate_array('day_start').distinct()
|
157 |
+
|
158 |
+
def calculate_daily_mean(day_start):
|
159 |
+
daily_collection = collection.filter(ee.Filter.eq('day_start', day_start))
|
160 |
daily_mean = daily_collection.mean()
|
161 |
+
return daily_mean.set('day_start', day_start)
|
162 |
+
|
163 |
daily_images = ee.List(grouped_by_day.map(calculate_daily_mean))
|
164 |
return ee.ImageCollection(daily_images)
|
165 |
|
166 |
+
|
167 |
+
def aggregate_data_weekly(collection, start_date_str, end_date_str):
|
168 |
+
"""
|
169 |
+
Aggregates data on a weekly basis, starting from the exact start date provided by the user.
|
170 |
+
"""
|
171 |
+
start_date = ee.Date(start_date_str)
|
172 |
+
end_date = ee.Date(end_date_str)
|
173 |
+
|
174 |
+
# Calculate the number of weeks between the start and end dates
|
175 |
+
days_diff = end_date.difference(start_date, 'day')
|
176 |
+
num_weeks = days_diff.divide(7).ceil().getInfo() # Total number of weeks
|
177 |
+
|
178 |
+
weekly_images = []
|
179 |
+
for week in range(num_weeks):
|
180 |
+
week_start = start_date.advance(week * 7, 'day') # Start of the week
|
181 |
+
week_end = week_start.advance(7, 'day') # End of the week
|
182 |
+
|
183 |
+
weekly_collection = collection.filterDate(week_start, week_end)
|
184 |
+
if weekly_collection.size().getInfo() > 0:
|
185 |
+
weekly_mean = weekly_collection.mean()
|
186 |
+
weekly_mean = weekly_mean.set('week_start', week_start.format('YYYY-MM-dd'))
|
187 |
+
weekly_images.append(weekly_mean)
|
188 |
+
|
189 |
+
return ee.ImageCollection.fromImages(weekly_images)
|
190 |
+
|
191 |
|
192 |
def aggregate_data_monthly(collection, start_date, end_date):
|
193 |
collection = collection.filterDate(start_date, end_date)
|
|
|
200 |
monthly_images = ee.List(grouped_by_month.map(calculate_monthly_mean))
|
201 |
return ee.ImageCollection(monthly_images)
|
202 |
|
203 |
+
|
204 |
def aggregate_data_yearly(collection):
|
205 |
collection = collection.map(lambda image: image.set('year', ee.Date(image.get('system:time_start')).format('YYYY')))
|
206 |
grouped_by_year = collection.aggregate_array('year').distinct()
|
|
|
211 |
yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
|
212 |
return ee.ImageCollection(yearly_images)
|
213 |
|
214 |
+
|
215 |
# Worker function for processing a single geometry
|
216 |
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):
|
217 |
if shape_type.lower() == "point":
|
|
|
242 |
collection = ee.ImageCollection(dataset_id) \
|
243 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
244 |
.filterBounds(roi)
|
245 |
+
|
246 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
247 |
collection = aggregate_data_custom(collection)
|
248 |
+
elif aggregation_period.lower() == 'daily':
|
249 |
+
collection = aggregate_data_daily(collection)
|
250 |
elif aggregation_period.lower() == 'weekly':
|
251 |
+
collection = aggregate_data_weekly(collection, start_date_str, end_date_str)
|
252 |
elif aggregation_period.lower() == 'monthly':
|
253 |
collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
|
254 |
elif aggregation_period.lower() == 'yearly':
|
|
|
258 |
image_list = collection.toList(collection.size())
|
259 |
processed_weeks = set()
|
260 |
aggregated_results = []
|
|
|
261 |
for i in range(image_list.size().getInfo()):
|
262 |
image = ee.Image(image_list.get(i))
|
263 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
264 |
timestamp = image.get('day')
|
265 |
period_label = 'Date'
|
266 |
date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
|
267 |
+
elif aggregation_period.lower() == 'daily':
|
268 |
+
timestamp = image.get('day_start')
|
269 |
+
period_label = 'Date'
|
270 |
+
date = ee.String(timestamp).getInfo()
|
271 |
elif aggregation_period.lower() == 'weekly':
|
272 |
timestamp = image.get('week_start')
|
273 |
period_label = 'Week'
|
|
|
308 |
aggregated_results.append(result)
|
309 |
except Exception as e:
|
310 |
st.error(f"Error retrieving value for {location_name}: {e}")
|
|
|
311 |
return aggregated_results
|
312 |
|
313 |
+
|
314 |
# Main processing function
|
315 |
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):
|
316 |
aggregated_results = []
|
317 |
total_steps = len(locations_df)
|
318 |
progress_bar = st.progress(0)
|
319 |
progress_text = st.empty()
|
|
|
320 |
start_time = time.time() # Start timing the process
|
321 |
with ThreadPoolExecutor(max_workers=10) as executor:
|
322 |
futures = []
|
|
|
338 |
include_boundary
|
339 |
)
|
340 |
futures.append(future)
|
|
|
341 |
completed = 0
|
342 |
for future in as_completed(futures):
|
343 |
result = future.result()
|
|
|
347 |
progress_percentage = completed / total_steps
|
348 |
progress_bar.progress(progress_percentage)
|
349 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
|
|
350 |
# End timing the process
|
351 |
end_time = time.time()
|
352 |
processing_time = end_time - start_time # Calculate total processing time
|
|
|
353 |
if aggregated_results:
|
354 |
result_df = pd.DataFrame(aggregated_results)
|
355 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
|
|
374 |
|
375 |
# Initialize data as an empty dictionary
|
376 |
data = {}
|
|
|
377 |
if imagery_base == "Sentinel":
|
378 |
dataset_file = "sentinel_datasets.json"
|
379 |
try:
|
|
|
419 |
else:
|
420 |
st.warning("Please enter a custom dataset ID to proceed.")
|
421 |
data = {}
|
|
|
422 |
if not data:
|
423 |
st.error("No valid dataset available. Please check your inputs.")
|
424 |
st.stop()
|
425 |
|
426 |
st.markdown("<hr><h5><b>{}</b></h5>".format(imagery_base), unsafe_allow_html=True)
|
|
|
427 |
main_selection = st.selectbox(f"Select {imagery_base} Dataset Category", list(data.keys()))
|
|
|
428 |
sub_selection = None
|
429 |
dataset_id = None
|
|
|
430 |
if main_selection:
|
431 |
sub_options = data[main_selection]["sub_options"]
|
432 |
sub_selection = st.selectbox(f"Select Specific {imagery_base} Dataset ID", list(sub_options.keys()))
|
|
|
436 |
dataset_id = sub_selection
|
437 |
|
438 |
st.markdown("<hr><h5><b>Earth Engine Index Calculator</b></h5>", unsafe_allow_html=True)
|
|
|
439 |
if main_selection and sub_selection:
|
440 |
dataset_bands = data[main_selection]["bands"].get(sub_selection, [])
|
441 |
st.write(f"Available Bands for {sub_options[sub_selection]}: {', '.join(dataset_bands)}")
|
|
|
445 |
default=[dataset_bands[0]] if dataset_bands else [],
|
446 |
help=f"Select 1 or 2 bands from: {', '.join(dataset_bands)}"
|
447 |
)
|
|
|
448 |
if len(selected_bands) < 1:
|
449 |
st.warning("Please select at least one band.")
|
450 |
st.stop()
|
|
|
451 |
if selected_bands:
|
452 |
if len(selected_bands) == 1:
|
453 |
default_formula = f"{selected_bands[0]}"
|
|
|
460 |
value=default_formula,
|
461 |
help=f"Use only these bands: {', '.join(selected_bands)}. Examples: {example}"
|
462 |
)
|
|
|
463 |
def validate_formula(formula, selected_bands):
|
464 |
allowed_chars = set(" +-*/()0123456789.")
|
465 |
terms = re.findall(r'[a-zA-Z][a-zA-Z0-9_]*', formula)
|
|
|
469 |
if not all(char in allowed_chars or char in ''.join(selected_bands) for char in formula):
|
470 |
return False, "Formula contains invalid characters. Use only bands, numbers, and operators (+, -, *, /, ())"
|
471 |
return True, ""
|
|
|
472 |
is_valid, error_message = validate_formula(custom_formula, selected_bands)
|
473 |
if not is_valid:
|
474 |
st.error(error_message)
|
|
|
476 |
elif not custom_formula:
|
477 |
st.warning("Please enter a custom formula to proceed.")
|
478 |
st.stop()
|
|
|
479 |
st.write(f"Custom Formula: {custom_formula}")
|
480 |
|
481 |
reducer_choice = st.selectbox(
|
|
|
490 |
end_date_str = end_date.strftime('%Y-%m-%d')
|
491 |
|
492 |
aggregation_period = st.selectbox(
|
493 |
+
"Select Aggregation Period (e.g, Custom(Start Date to End Date) , Daily , Weekly , Monthly , Yearly)",
|
494 |
+
["Custom (Start Date to End Date)", "Daily", "Weekly", "Monthly", "Yearly"],
|
495 |
index=0
|
496 |
)
|
497 |
|
|
|
499 |
|
500 |
kernel_size = None
|
501 |
include_boundary = None
|
|
|
502 |
if shape_type.lower() == "point":
|
503 |
kernel_size = st.selectbox(
|
504 |
"Select Calculation Area(e.g, Point , 3x3 Kernel , 5x5 Kernel)",
|
|
|
523 |
if file_upload.name.endswith('.csv'):
|
524 |
# Read the CSV file
|
525 |
locations_df = pd.read_csv(file_upload)
|
|
|
526 |
# Show the first few rows to help user identify columns
|
527 |
st.write("Preview of your uploaded data (first 5 rows):")
|
528 |
st.dataframe(locations_df.head())
|
|
|
529 |
# Get all column names from the uploaded file
|
530 |
all_columns = locations_df.columns.tolist()
|
|
|
531 |
# Let user select latitude and longitude columns from dropdown
|
532 |
col1, col2 = st.columns(2)
|
533 |
with col1:
|
|
|
544 |
index=all_columns.index('longitude') if 'longitude' in all_columns else 0,
|
545 |
help="Select the column containing longitude values"
|
546 |
)
|
|
|
547 |
# Validate the selected columns contain numeric data
|
548 |
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]):
|
549 |
st.error("Error: Selected Latitude and Longitude columns must contain numeric values")
|
550 |
st.stop()
|
|
|
551 |
# Rename the selected columns to standard names for processing
|
552 |
locations_df = locations_df.rename(columns={
|
553 |
original_lat_col: 'latitude',
|
554 |
original_lon_col: 'longitude'
|
555 |
})
|
|
|
556 |
elif file_upload.name.endswith('.geojson'):
|
557 |
locations_df = gpd.read_file(file_upload)
|
558 |
if 'geometry' in locations_df.columns:
|
|
|
588 |
original_lon_col = 'longitude'
|
589 |
except Exception as e:
|
590 |
st.error(f"Error parsing KML file: {str(e)}")
|
|
|
591 |
# Display map for points if we have valid data
|
592 |
if not locations_df.empty and 'latitude' in locations_df.columns and 'longitude' in locations_df.columns:
|
593 |
m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10)
|
|
|
599 |
m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name'))
|
600 |
st.write("Map of Uploaded Points:")
|
601 |
m.to_streamlit()
|
|
|
602 |
elif shape_type.lower() == "polygon":
|
603 |
if file_upload.name.endswith('.csv'):
|
604 |
st.error("CSV upload not supported for polygons. Please upload a GeoJSON or KML file.")
|
|
|
628 |
locations_df = gpd.GeoDataFrame(polygons, geometry=gpd.GeoSeries.from_wkt([p['geometry'] for p in polygons]), crs="EPSG:4326")
|
629 |
except Exception as e:
|
630 |
st.error(f"Error parsing KML file: {str(e)}")
|
|
|
631 |
# Display map for polygons if we have valid data
|
632 |
if not locations_df.empty and 'geometry' in locations_df.columns:
|
633 |
centroid_lat = locations_df.geometry.centroid.y.mean()
|
|
|
660 |
kernel_size,
|
661 |
include_boundary
|
662 |
)
|
|
|
663 |
if results:
|
664 |
result_df = pd.DataFrame(results)
|
665 |
st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
|
666 |
st.dataframe(result_df)
|
|
|
667 |
filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
|
668 |
st.download_button(
|
669 |
label="Download results as CSV",
|
|
|
671 |
file_name=filename,
|
672 |
mime='text/csv'
|
673 |
)
|
|
|
674 |
st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
|
675 |
else:
|
676 |
st.warning("No results were generated. Check your inputs or formula.")
|
677 |
st.info(f"Total processing time: {processing_time:.2f} seconds.")
|
|
|
678 |
except Exception as e:
|
679 |
st.error(f"An error occurred during processing: {str(e)}")
|
680 |
else:
|