Spaces:
Running
Running
update app.py
Browse files
app.py
CHANGED
@@ -4,7 +4,7 @@ import ee
|
|
4 |
import os
|
5 |
import pandas as pd
|
6 |
import geopandas as gpd
|
7 |
-
from datetime import datetime
|
8 |
import leafmap.foliumap as leafmap
|
9 |
import re
|
10 |
from shapely.geometry import base
|
@@ -143,7 +143,7 @@ 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 |
collection = collection.map(lambda image: image.set('day', ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')))
|
148 |
grouped_by_day = collection.aggregate_array('day').distinct()
|
149 |
def calculate_daily_mean(day):
|
@@ -153,12 +153,12 @@ def aggregate_data_daily(collection):
|
|
153 |
daily_images = ee.List(grouped_by_day.map(calculate_daily_mean))
|
154 |
return ee.ImageCollection(daily_images)
|
155 |
|
156 |
-
def aggregate_data_weekly(collection
|
157 |
def set_week_start(image):
|
158 |
date = ee.Date(image.get('system:time_start'))
|
159 |
-
|
160 |
-
|
161 |
-
week_start =
|
162 |
return image.set('week_start', week_start.format('YYYY-MM-dd'))
|
163 |
collection = collection.map(set_week_start)
|
164 |
grouped_by_week = collection.aggregate_array('week_start').distinct()
|
@@ -191,7 +191,7 @@ def aggregate_data_yearly(collection):
|
|
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, kernel_size=None, include_boundary=None):
|
195 |
if shape_type.lower() == "point":
|
196 |
latitude = row.get('latitude')
|
197 |
longitude = row.get('longitude')
|
@@ -221,10 +221,10 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
221 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
222 |
.filterBounds(roi)
|
223 |
|
224 |
-
if aggregation_period.lower() == '
|
225 |
-
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':
|
@@ -232,11 +232,12 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
232 |
|
233 |
# Process each image in the collection
|
234 |
image_list = collection.toList(collection.size())
|
235 |
-
|
236 |
aggregated_results = []
|
|
|
237 |
for i in range(image_list.size().getInfo()):
|
238 |
image = ee.Image(image_list.get(i))
|
239 |
-
if aggregation_period.lower() == '
|
240 |
timestamp = image.get('day')
|
241 |
period_label = 'Date'
|
242 |
date = ee.Date(timestamp).format('YYYY-MM-dd').getInfo()
|
@@ -244,9 +245,11 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
244 |
timestamp = image.get('week_start')
|
245 |
period_label = 'Week'
|
246 |
date = ee.String(timestamp).getInfo()
|
247 |
-
if date
|
|
|
|
|
248 |
continue
|
249 |
-
|
250 |
elif aggregation_period.lower() == 'monthly':
|
251 |
timestamp = image.get('month')
|
252 |
period_label = 'Month'
|
@@ -273,19 +276,21 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
273 |
'Calculated Value': calculated_value
|
274 |
}
|
275 |
if shape_type.lower() == 'point':
|
276 |
-
result[
|
277 |
-
result[
|
278 |
aggregated_results.append(result)
|
279 |
except Exception as e:
|
280 |
st.error(f"Error retrieving value for {location_name}: {e}")
|
|
|
281 |
return aggregated_results
|
282 |
|
283 |
# Main processing function
|
284 |
-
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):
|
285 |
aggregated_results = []
|
286 |
total_steps = len(locations_df)
|
287 |
progress_bar = st.progress(0)
|
288 |
progress_text = st.empty()
|
|
|
289 |
start_time = time.time() # Start timing the process
|
290 |
with ThreadPoolExecutor(max_workers=10) as executor:
|
291 |
futures = []
|
@@ -301,10 +306,13 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
301 |
shape_type,
|
302 |
aggregation_period,
|
303 |
custom_formula,
|
|
|
|
|
304 |
kernel_size,
|
305 |
include_boundary
|
306 |
)
|
307 |
futures.append(future)
|
|
|
308 |
completed = 0
|
309 |
for future in as_completed(futures):
|
310 |
result = future.result()
|
@@ -314,9 +322,11 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
314 |
progress_percentage = completed / total_steps
|
315 |
progress_bar.progress(progress_percentage)
|
316 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
|
|
317 |
# End timing the process
|
318 |
end_time = time.time()
|
319 |
processing_time = end_time - start_time # Calculate total processing time
|
|
|
320 |
if aggregated_results:
|
321 |
result_df = pd.DataFrame(aggregated_results)
|
322 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
@@ -326,11 +336,11 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
326 |
'Calculated Value': 'mean'
|
327 |
}
|
328 |
if shape_type.lower() == 'point':
|
329 |
-
agg_dict[
|
330 |
-
agg_dict[
|
331 |
aggregated_output = result_df.groupby('Location Name').agg(agg_dict).reset_index()
|
332 |
aggregated_output.rename(columns={'Calculated Value': 'Aggregated Value'}, inplace=True)
|
333 |
-
return aggregated_output.to_dict(orient='records'), processing_time
|
334 |
else:
|
335 |
return result_df.to_dict(orient='records'), processing_time
|
336 |
return [], processing_time
|
@@ -341,6 +351,7 @@ imagery_base = st.selectbox("Select Imagery Base", ["Sentinel", "Landsat", "MODI
|
|
341 |
|
342 |
# Initialize data as an empty dictionary
|
343 |
data = {}
|
|
|
344 |
if imagery_base == "Sentinel":
|
345 |
dataset_file = "sentinel_datasets.json"
|
346 |
try:
|
@@ -392,9 +403,12 @@ if not data:
|
|
392 |
st.stop()
|
393 |
|
394 |
st.markdown("<hr><h5><b>{}</b></h5>".format(imagery_base), unsafe_allow_html=True)
|
|
|
395 |
main_selection = st.selectbox(f"Select {imagery_base} Dataset Category", list(data.keys()))
|
|
|
396 |
sub_selection = None
|
397 |
dataset_id = None
|
|
|
398 |
if main_selection:
|
399 |
sub_options = data[main_selection]["sub_options"]
|
400 |
sub_selection = st.selectbox(f"Select Specific {imagery_base} Dataset ID", list(sub_options.keys()))
|
@@ -404,6 +418,7 @@ if main_selection:
|
|
404 |
dataset_id = sub_selection
|
405 |
|
406 |
st.markdown("<hr><h5><b>Earth Engine Index Calculator</b></h5>", unsafe_allow_html=True)
|
|
|
407 |
if main_selection and sub_selection:
|
408 |
dataset_bands = data[main_selection]["bands"].get(sub_selection, [])
|
409 |
st.write(f"Available Bands for {sub_options[sub_selection]}: {', '.join(dataset_bands)}")
|
@@ -413,9 +428,11 @@ if main_selection and sub_selection:
|
|
413 |
default=[dataset_bands[0]] if dataset_bands else [],
|
414 |
help=f"Select 1 or 2 bands from: {', '.join(dataset_bands)}"
|
415 |
)
|
|
|
416 |
if len(selected_bands) < 1:
|
417 |
st.warning("Please select at least one band.")
|
418 |
st.stop()
|
|
|
419 |
if selected_bands:
|
420 |
if len(selected_bands) == 1:
|
421 |
default_formula = f"{selected_bands[0]}"
|
@@ -428,6 +445,7 @@ if main_selection and sub_selection:
|
|
428 |
value=default_formula,
|
429 |
help=f"Use only these bands: {', '.join(selected_bands)}. Examples: {example}"
|
430 |
)
|
|
|
431 |
def validate_formula(formula, selected_bands):
|
432 |
allowed_chars = set(" +-*/()0123456789.")
|
433 |
terms = re.findall(r'[a-zA-Z][a-zA-Z0-9_]*', formula)
|
@@ -437,6 +455,7 @@ if main_selection and sub_selection:
|
|
437 |
if not all(char in allowed_chars or char in ''.join(selected_bands) for char in formula):
|
438 |
return False, "Formula contains invalid characters. Use only bands, numbers, and operators (+, -, *, /, ())"
|
439 |
return True, ""
|
|
|
440 |
is_valid, error_message = validate_formula(custom_formula, selected_bands)
|
441 |
if not is_valid:
|
442 |
st.error(error_message)
|
@@ -444,6 +463,7 @@ if main_selection and sub_selection:
|
|
444 |
elif not custom_formula:
|
445 |
st.warning("Please enter a custom formula to proceed.")
|
446 |
st.stop()
|
|
|
447 |
st.write(f"Custom Formula: {custom_formula}")
|
448 |
|
449 |
reducer_choice = st.selectbox(
|
@@ -458,12 +478,13 @@ start_date_str = start_date.strftime('%Y-%m-%d')
|
|
458 |
end_date_str = end_date.strftime('%Y-%m-%d')
|
459 |
|
460 |
aggregation_period = st.selectbox(
|
461 |
-
"Select Aggregation Period (e.g,
|
462 |
-
["
|
463 |
index=0
|
464 |
)
|
465 |
|
466 |
shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
|
|
|
467 |
kernel_size = None
|
468 |
include_boundary = None
|
469 |
|
@@ -483,26 +504,57 @@ elif shape_type.lower() == "polygon":
|
|
483 |
|
484 |
file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"])
|
485 |
locations_df = pd.DataFrame()
|
|
|
|
|
486 |
|
487 |
if file_upload is not None:
|
488 |
if shape_type.lower() == "point":
|
489 |
if file_upload.name.endswith('.csv'):
|
|
|
490 |
locations_df = pd.read_csv(file_upload)
|
491 |
-
|
|
|
|
|
492 |
st.dataframe(locations_df.head())
|
|
|
|
|
493 |
all_columns = locations_df.columns.tolist()
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
498 |
st.stop()
|
499 |
-
|
500 |
-
|
|
|
|
|
|
|
|
|
|
|
501 |
elif file_upload.name.endswith('.geojson'):
|
502 |
locations_df = gpd.read_file(file_upload)
|
503 |
if 'geometry' in locations_df.columns:
|
504 |
locations_df['latitude'] = locations_df['geometry'].y
|
505 |
locations_df['longitude'] = locations_df['geometry'].x
|
|
|
|
|
506 |
else:
|
507 |
st.error("GeoJSON file doesn't contain geometry column")
|
508 |
st.stop()
|
@@ -527,8 +579,12 @@ if file_upload is not None:
|
|
527 |
locations_df = gpd.GeoDataFrame(points, geometry=gpd.GeoSeries.from_wkt([p['geometry'] for p in points]), crs="EPSG:4326")
|
528 |
locations_df['latitude'] = locations_df['geometry'].y
|
529 |
locations_df['longitude'] = locations_df['geometry'].x
|
|
|
|
|
530 |
except Exception as e:
|
531 |
st.error(f"Error parsing KML file: {str(e)}")
|
|
|
|
|
532 |
if not locations_df.empty and 'latitude' in locations_df.columns and 'longitude' in locations_df.columns:
|
533 |
m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10)
|
534 |
for _, row in locations_df.iterrows():
|
@@ -539,7 +595,7 @@ if file_upload is not None:
|
|
539 |
m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name'))
|
540 |
st.write("Map of Uploaded Points:")
|
541 |
m.to_streamlit()
|
542 |
-
|
543 |
elif shape_type.lower() == "polygon":
|
544 |
if file_upload.name.endswith('.csv'):
|
545 |
st.error("CSV upload not supported for polygons. Please upload a GeoJSON or KML file.")
|
@@ -569,6 +625,8 @@ if file_upload is not None:
|
|
569 |
locations_df = gpd.GeoDataFrame(polygons, geometry=gpd.GeoSeries.from_wkt([p['geometry'] for p in polygons]), crs="EPSG:4326")
|
570 |
except Exception as e:
|
571 |
st.error(f"Error parsing KML file: {str(e)}")
|
|
|
|
|
572 |
if not locations_df.empty and 'geometry' in locations_df.columns:
|
573 |
centroid_lat = locations_df.geometry.centroid.y.mean()
|
574 |
centroid_lon = locations_df.geometry.centroid.x.mean()
|
@@ -594,14 +652,18 @@ if st.button(f"Calculate {custom_formula}"):
|
|
594 |
reducer_choice,
|
595 |
shape_type,
|
596 |
aggregation_period,
|
|
|
|
|
597 |
custom_formula,
|
598 |
kernel_size,
|
599 |
include_boundary
|
600 |
)
|
|
|
601 |
if results:
|
602 |
result_df = pd.DataFrame(results)
|
603 |
st.write(f"Processed Results Table ({aggregation_period}) for Formula: {custom_formula}")
|
604 |
st.dataframe(result_df)
|
|
|
605 |
filename = f"{main_selection}_{dataset_id}_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}_{aggregation_period.lower()}.csv"
|
606 |
st.download_button(
|
607 |
label="Download results as CSV",
|
@@ -609,10 +671,12 @@ if st.button(f"Calculate {custom_formula}"):
|
|
609 |
file_name=filename,
|
610 |
mime='text/csv'
|
611 |
)
|
|
|
612 |
st.success(f"Processing complete! Total processing time: {processing_time:.2f} seconds.")
|
613 |
else:
|
614 |
st.warning("No results were generated. Check your inputs or formula.")
|
615 |
st.info(f"Total processing time: {processing_time:.2f} seconds.")
|
|
|
616 |
except Exception as e:
|
617 |
st.error(f"An error occurred during processing: {str(e)}")
|
618 |
else:
|
|
|
4 |
import os
|
5 |
import pandas as pd
|
6 |
import geopandas as gpd
|
7 |
+
from datetime import datetime
|
8 |
import leafmap.foliumap as leafmap
|
9 |
import re
|
10 |
from shapely.geometry import base
|
|
|
143 |
return ee.Image(0).rename('custom_result').set('error', str(e))
|
144 |
|
145 |
# Aggregation functions
|
146 |
+
def aggregate_data_custom(collection):
|
147 |
collection = collection.map(lambda image: image.set('day', ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')))
|
148 |
grouped_by_day = collection.aggregate_array('day').distinct()
|
149 |
def calculate_daily_mean(day):
|
|
|
153 |
daily_images = ee.List(grouped_by_day.map(calculate_daily_mean))
|
154 |
return ee.ImageCollection(daily_images)
|
155 |
|
156 |
+
def aggregate_data_weekly(collection):
|
157 |
def set_week_start(image):
|
158 |
date = ee.Date(image.get('system:time_start'))
|
159 |
+
days_since_week_start = date.getRelative('day', 'week')
|
160 |
+
offset = ee.Number(days_since_week_start).multiply(-1)
|
161 |
+
week_start = date.advance(offset, 'day')
|
162 |
return image.set('week_start', week_start.format('YYYY-MM-dd'))
|
163 |
collection = collection.map(set_week_start)
|
164 |
grouped_by_week = collection.aggregate_array('week_start').distinct()
|
|
|
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":
|
196 |
latitude = row.get('latitude')
|
197 |
longitude = row.get('longitude')
|
|
|
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':
|
|
|
232 |
|
233 |
# Process each image in the collection
|
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()
|
|
|
245 |
timestamp = image.get('week_start')
|
246 |
period_label = 'Week'
|
247 |
date = ee.String(timestamp).getInfo()
|
248 |
+
if (pd.to_datetime(date) < pd.to_datetime(start_date_str) or
|
249 |
+
pd.to_datetime(date) > pd.to_datetime(end_date_str) or
|
250 |
+
date in processed_weeks):
|
251 |
continue
|
252 |
+
processed_weeks.add(date)
|
253 |
elif aggregation_period.lower() == 'monthly':
|
254 |
timestamp = image.get('month')
|
255 |
period_label = 'Month'
|
|
|
276 |
'Calculated Value': calculated_value
|
277 |
}
|
278 |
if shape_type.lower() == 'point':
|
279 |
+
result[original_lat_col] = latitude # Use original column name
|
280 |
+
result[original_lon_col] = longitude # Use original column name
|
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 = []
|
|
|
306 |
shape_type,
|
307 |
aggregation_period,
|
308 |
custom_formula,
|
309 |
+
original_lat_col,
|
310 |
+
original_lon_col,
|
311 |
kernel_size,
|
312 |
include_boundary
|
313 |
)
|
314 |
futures.append(future)
|
315 |
+
|
316 |
completed = 0
|
317 |
for future in as_completed(futures):
|
318 |
result = future.result()
|
|
|
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)':
|
|
|
336 |
'Calculated Value': 'mean'
|
337 |
}
|
338 |
if shape_type.lower() == 'point':
|
339 |
+
agg_dict[original_lat_col] = 'first'
|
340 |
+
agg_dict[original_lon_col] = 'first'
|
341 |
aggregated_output = result_df.groupby('Location Name').agg(agg_dict).reset_index()
|
342 |
aggregated_output.rename(columns={'Calculated Value': 'Aggregated Value'}, inplace=True)
|
343 |
+
return aggregated_output.to_dict(orient='records'), processing_time
|
344 |
else:
|
345 |
return result_df.to_dict(orient='records'), processing_time
|
346 |
return [], processing_time
|
|
|
351 |
|
352 |
# Initialize data as an empty dictionary
|
353 |
data = {}
|
354 |
+
|
355 |
if imagery_base == "Sentinel":
|
356 |
dataset_file = "sentinel_datasets.json"
|
357 |
try:
|
|
|
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 |
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 |
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 |
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 |
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 |
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 |
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 |
|
486 |
shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"])
|
487 |
+
|
488 |
kernel_size = None
|
489 |
include_boundary = None
|
490 |
|
|
|
504 |
|
505 |
file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"])
|
506 |
locations_df = pd.DataFrame()
|
507 |
+
original_lat_col = None
|
508 |
+
original_lon_col = None
|
509 |
|
510 |
if file_upload is not None:
|
511 |
if shape_type.lower() == "point":
|
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:
|
526 |
+
original_lat_col = st.selectbox(
|
527 |
+
"Select Latitude Column",
|
528 |
+
options=all_columns,
|
529 |
+
index=all_columns.index('latitude') if 'latitude' in all_columns else 0,
|
530 |
+
help="Select the column containing latitude values"
|
531 |
+
)
|
532 |
+
with col2:
|
533 |
+
original_lon_col = st.selectbox(
|
534 |
+
"Select Longitude Column",
|
535 |
+
options=all_columns,
|
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:
|
554 |
locations_df['latitude'] = locations_df['geometry'].y
|
555 |
locations_df['longitude'] = locations_df['geometry'].x
|
556 |
+
original_lat_col = 'latitude'
|
557 |
+
original_lon_col = 'longitude'
|
558 |
else:
|
559 |
st.error("GeoJSON file doesn't contain geometry column")
|
560 |
st.stop()
|
|
|
579 |
locations_df = gpd.GeoDataFrame(points, geometry=gpd.GeoSeries.from_wkt([p['geometry'] for p in points]), crs="EPSG:4326")
|
580 |
locations_df['latitude'] = locations_df['geometry'].y
|
581 |
locations_df['longitude'] = locations_df['geometry'].x
|
582 |
+
original_lat_col = 'latitude'
|
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)
|
590 |
for _, row in locations_df.iterrows():
|
|
|
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 |
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()
|
632 |
centroid_lon = locations_df.geometry.centroid.x.mean()
|
|
|
652 |
reducer_choice,
|
653 |
shape_type,
|
654 |
aggregation_period,
|
655 |
+
original_lat_col,
|
656 |
+
original_lon_col,
|
657 |
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 |
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:
|