Spaces:
Running
Running
update app.py
Browse files
app.py
CHANGED
@@ -6,6 +6,7 @@ import pandas as pd
|
|
6 |
import geopandas as gpd
|
7 |
from datetime import datetime
|
8 |
import leafmap.foliumap as leafmap
|
|
|
9 |
|
10 |
# Set up the page layout
|
11 |
st.set_page_config(layout="wide")
|
@@ -191,7 +192,7 @@ def calculate_custom_formula(image, geometry, formula, scale=30):
|
|
191 |
image (ee.Image): The input image.
|
192 |
geometry (ee.Geometry): The region of interest (ROI) geometry.
|
193 |
formula (str): A string representing the mathematical formula to apply to the image.
|
194 |
-
scale (int): The scale for the reduceRegion operation (default is 30).
|
195 |
|
196 |
Returns:
|
197 |
ee.Dictionary: The result of applying the custom formula in the region.
|
@@ -210,62 +211,84 @@ def calculate_custom_formula(image, geometry, formula, scale=30):
|
|
210 |
scale=scale
|
211 |
)
|
212 |
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
result = calculate_ndwi(mean_image, roi)
|
233 |
-
elif index_choice.lower() == 'average no₂':
|
234 |
-
if 'NO2' in mean_image.bandNames().getInfo():
|
235 |
-
result = calculate_avg_no2_sentinel5p(mean_image, roi)
|
236 |
-
else:
|
237 |
-
st.warning(f"No NO2 band found for {location_name}. Please use Sentinel-5P for NO₂ data.")
|
238 |
-
elif index_choice.lower() == 'custom formula' and custom_formula:
|
239 |
-
result = calculate_custom_formula(mean_image, roi, custom_formula)
|
240 |
-
|
241 |
-
if result is not None:
|
242 |
-
# Extract the calculated value from the result
|
243 |
-
if isinstance(result, ee.Number):
|
244 |
-
calculated_value = result.getInfo() # For ee.Number, getInfo() is used to get the value
|
245 |
-
elif isinstance(result, ee.Dictionary):
|
246 |
-
calculated_value = result.get('CustomResult', None).getInfo() # For ee.Dictionary, use get() to access 'CustomResult' and then getInfo()
|
247 |
-
|
248 |
-
if calculated_value is not None:
|
249 |
-
daily_results.append({
|
250 |
-
'Location Name': location_name,
|
251 |
-
'Latitude': latitude,
|
252 |
-
'Longitude': longitude,
|
253 |
-
'Date': day,
|
254 |
-
'Calculated Value': calculated_value
|
255 |
-
})
|
256 |
-
|
257 |
-
return daily_results
|
258 |
|
259 |
-
# Process each point for
|
260 |
-
|
261 |
-
|
262 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
locations_df = read_csv(file_upload)
|
264 |
-
elif
|
265 |
locations_df = read_geojson(file_upload)
|
266 |
-
elif
|
267 |
locations_df = read_kml(file_upload)
|
268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
for idx, row in locations_df.iterrows():
|
270 |
latitude = row['latitude']
|
271 |
longitude = row['longitude']
|
@@ -278,29 +301,144 @@ if file_upload is not None:
|
|
278 |
# Define the region of interest (ROI)
|
279 |
roi = ee.Geometry.Point([longitude, latitude])
|
280 |
|
281 |
-
# Load Sentinel-2 image collection
|
282 |
collection = ee.ImageCollection(sub_options[sub_selection]) \
|
283 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
284 |
.filterBounds(roi)
|
285 |
|
286 |
-
|
287 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
288 |
|
289 |
-
|
290 |
-
|
291 |
-
|
|
|
|
|
292 |
|
293 |
-
|
294 |
-
|
|
|
295 |
|
296 |
-
|
297 |
-
|
|
|
298 |
|
299 |
-
|
|
|
|
|
|
|
|
|
300 |
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
import geopandas as gpd
|
7 |
from datetime import datetime
|
8 |
import leafmap.foliumap as leafmap
|
9 |
+
import re
|
10 |
|
11 |
# Set up the page layout
|
12 |
st.set_page_config(layout="wide")
|
|
|
192 |
image (ee.Image): The input image.
|
193 |
geometry (ee.Geometry): The region of interest (ROI) geometry.
|
194 |
formula (str): A string representing the mathematical formula to apply to the image.
|
195 |
+
scale (int): The scale for the reduceRegion operation (default is 30).
|
196 |
|
197 |
Returns:
|
198 |
ee.Dictionary: The result of applying the custom formula in the region.
|
|
|
211 |
scale=scale
|
212 |
)
|
213 |
|
214 |
+
# Return the result
|
215 |
+
return result
|
216 |
+
|
217 |
+
|
218 |
+
# Function to get the most recent image from the collection
|
219 |
+
def get_most_recent_image(image_collection):
|
220 |
+
image = image_collection.sort('system:time_start', False).first()
|
221 |
+
return image
|
222 |
+
|
223 |
+
# Function to handle the custom formula choice and calculation
|
224 |
+
def process_custom_formula(image, roi, custom_formula):
|
225 |
+
if custom_formula:
|
226 |
+
result = calculate_custom_formula(image, roi, custom_formula)
|
227 |
+
if result:
|
228 |
+
return result.getInfo()
|
229 |
+
return None
|
230 |
+
|
231 |
+
locations_df = None # Initialize locations_df to None
|
232 |
+
polygons_df = None # Ensure polygons_df is initialized at the beginning
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
|
234 |
+
# Process each point (with additional checks for file validity)
|
235 |
+
# Check the shape type and assign polygons_df only for Polygon data
|
236 |
+
if file_upload:
|
237 |
+
# locations_df = None # Initialize locations_df to None
|
238 |
+
# polygons_df = None # Ensure polygons_df is initialized at the beginning
|
239 |
+
|
240 |
+
file_extension = os.path.splitext(file_upload.name)[1].lower()
|
241 |
+
|
242 |
+
# Read file based on shape type
|
243 |
+
if shape_type == 'Point':
|
244 |
+
if file_extension == '.csv':
|
245 |
locations_df = read_csv(file_upload)
|
246 |
+
elif file_extension == '.geojson':
|
247 |
locations_df = read_geojson(file_upload)
|
248 |
+
elif file_extension == '.kml':
|
249 |
locations_df = read_kml(file_upload)
|
250 |
+
else:
|
251 |
+
st.error("Unsupported file type. Please upload a CSV, GeoJSON, or KML file for points.")
|
252 |
+
elif shape_type == 'Polygon':
|
253 |
+
if file_extension == '.geojson':
|
254 |
+
polygons_df = read_geojson(file_upload)
|
255 |
+
elif file_extension == '.kml':
|
256 |
+
polygons_df = read_kml(file_upload)
|
257 |
+
else:
|
258 |
+
st.error("Unsupported file type. Please upload a GeoJSON or KML file for polygons.")
|
259 |
+
|
260 |
+
|
261 |
+
if locations_df is not None and not locations_df.empty:
|
262 |
+
# Ensure the necessary columns exist in the dataframe
|
263 |
+
if 'latitude' not in locations_df.columns or 'longitude' not in locations_df.columns:
|
264 |
+
st.error("Uploaded file is missing required 'latitude' or 'longitude' columns.")
|
265 |
+
else:
|
266 |
+
# Display a preview of the points data
|
267 |
+
st.write("Preview of the uploaded points data:")
|
268 |
+
st.dataframe(locations_df.head())
|
269 |
+
|
270 |
+
# Create a LeafMap object to display the points
|
271 |
+
m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10)
|
272 |
+
|
273 |
+
# Add points to the map using a loop
|
274 |
+
for _, row in locations_df.iterrows():
|
275 |
+
latitude = row['latitude']
|
276 |
+
longitude = row['longitude']
|
277 |
+
|
278 |
+
# Check if latitude or longitude are NaN and skip if they are
|
279 |
+
if pd.isna(latitude) or pd.isna(longitude):
|
280 |
+
continue # Skip this row and move to the next one
|
281 |
+
|
282 |
+
m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name'))
|
283 |
+
|
284 |
+
# Display map
|
285 |
+
st.write("Map of Uploaded Points:")
|
286 |
+
m.to_streamlit()
|
287 |
+
|
288 |
+
# Store the map in session_state
|
289 |
+
st.session_state.map_data = m
|
290 |
+
|
291 |
+
# Process each point for index calculation
|
292 |
for idx, row in locations_df.iterrows():
|
293 |
latitude = row['latitude']
|
294 |
longitude = row['longitude']
|
|
|
301 |
# Define the region of interest (ROI)
|
302 |
roi = ee.Geometry.Point([longitude, latitude])
|
303 |
|
304 |
+
# Load Sentinel-2 image collection
|
305 |
collection = ee.ImageCollection(sub_options[sub_selection]) \
|
306 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
307 |
.filterBounds(roi)
|
308 |
|
309 |
+
image = get_most_recent_image(collection)
|
310 |
+
if not image:
|
311 |
+
st.warning(f"No images found for {location_name}.")
|
312 |
+
else:
|
313 |
+
st.write(f"Found images for {location_name}.")
|
314 |
+
# Perform the calculation based on user selection
|
315 |
+
# Perform the calculation based on user selection
|
316 |
+
result = None
|
317 |
+
if index_choice == 'NDVI':
|
318 |
+
result = calculate_ndvi(image, roi)
|
319 |
+
elif index_choice == 'NDWI':
|
320 |
+
result = calculate_ndwi(image, roi)
|
321 |
+
elif index_choice == 'Average NO₂':
|
322 |
+
if 'NO2' in image.bandNames().getInfo():
|
323 |
+
result = calculate_avg_no2_sentinel5p(image, roi)
|
324 |
+
else:
|
325 |
+
st.warning(f"No NO2 band found for {location_name}. Please use Sentinel-5P for NO₂ data.")
|
326 |
+
elif index_choice.lower() == 'custom formula' and custom_formula:
|
327 |
+
result = process_custom_formula(image, roi, custom_formula)
|
328 |
+
|
329 |
+
# Validate result before using getInfo
|
330 |
+
if result is not None:
|
331 |
+
calculated_value = None # Initialize the calculated_value as None
|
332 |
+
|
333 |
+
# Check if the result is a dictionary
|
334 |
+
if isinstance(result, dict):
|
335 |
+
# Extract the value using the appropriate key (adjust the key name as needed)
|
336 |
+
calculated_value = result.get('CustomResult', None) # Replace 'CustomResult' if using NDVI, NDWI, etc.
|
337 |
+
else:
|
338 |
+
try:
|
339 |
+
# If it's an Earth Engine object, get the value using getInfo
|
340 |
+
calculated_value = result.getInfo()
|
341 |
+
except Exception as e:
|
342 |
+
st.error(f"Error getting result info: {e}")
|
343 |
+
|
344 |
+
# If a valid calculated_value is found, append the result to session_state
|
345 |
+
if calculated_value is not None:
|
346 |
+
st.session_state.results.append({
|
347 |
+
'Location Name': location_name,
|
348 |
+
'Latitude': latitude,
|
349 |
+
'Longitude': longitude,
|
350 |
+
'Calculated Value': calculated_value
|
351 |
+
})
|
352 |
+
else:
|
353 |
+
st.warning(f"No value calculated for {location_name}.")
|
354 |
+
else:
|
355 |
+
st.warning(f"No value calculated for {location_name}.")
|
356 |
+
|
357 |
+
|
358 |
+
# Check if polygons_df is populated for polygons
|
359 |
+
if polygons_df is not None:
|
360 |
+
st.write("Preview of the uploaded polygons data:")
|
361 |
+
st.dataframe(polygons_df.head())
|
362 |
+
|
363 |
+
m = leafmap.Map(center=[polygons_df.geometry.centroid.y.mean(), polygons_df.geometry.centroid.x.mean()], zoom=10)
|
364 |
|
365 |
+
for _, row in polygons_df.iterrows():
|
366 |
+
polygon = row['geometry']
|
367 |
+
if polygon.is_valid:
|
368 |
+
gdf = gpd.GeoDataFrame([row], geometry=[polygon], crs=polygons_df.crs)
|
369 |
+
m.add_gdf(gdf=gdf, layer_name=row.get('name', 'Unnamed Polygon'))
|
370 |
|
371 |
+
st.write("Map of Uploaded Polygons:")
|
372 |
+
m.to_streamlit()
|
373 |
+
st.session_state.map_data = m
|
374 |
|
375 |
+
for idx, row in polygons_df.iterrows():
|
376 |
+
polygon = row['geometry']
|
377 |
+
location_name = row.get('name', f"Polygon_{idx}")
|
378 |
|
379 |
+
try:
|
380 |
+
roi = convert_to_ee_geometry(polygon)
|
381 |
+
except ValueError as e:
|
382 |
+
st.error(str(e))
|
383 |
+
continue
|
384 |
|
385 |
+
collection = ee.ImageCollection(sub_options[sub_selection]) \
|
386 |
+
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
387 |
+
.filterBounds(roi)
|
388 |
+
|
389 |
+
image = get_most_recent_image(collection)
|
390 |
+
|
391 |
+
if not image:
|
392 |
+
st.warning(f"No images found for {location_name}.")
|
393 |
+
else:
|
394 |
+
st.write(f"Found an image for {location_name}.")
|
395 |
+
result = None
|
396 |
+
if index_choice.lower() == 'ndvi':
|
397 |
+
result = calculate_ndvi(image, roi)
|
398 |
+
elif index_choice.lower() == 'ndwi':
|
399 |
+
result = calculate_ndwi(image, roi)
|
400 |
+
elif index_choice.lower() == 'average no₂':
|
401 |
+
if 'NO2' in image.bandNames().getInfo():
|
402 |
+
result = calculate_avg_no2_sentinel5p(image, roi)
|
403 |
+
else:
|
404 |
+
st.warning(f"No NO2 band found for {location_name}. Please use Sentinel-5P for NO₂ data.")
|
405 |
+
elif index_choice.lower() == 'custom formula' and custom_formula:
|
406 |
+
result = process_custom_formula(image, roi, custom_formula)
|
407 |
+
|
408 |
+
if result is not None:
|
409 |
+
# Initialize the calculated_value as None
|
410 |
+
calculated_value = None
|
411 |
+
|
412 |
+
# Check if the result is a dictionary (e.g., custom formula result)
|
413 |
+
if isinstance(result, dict) and 'CustomResult' in result:
|
414 |
+
calculated_value = result['CustomResult'] # Extract the numeric value from the dictionary
|
415 |
+
# If the result is a numeric value (e.g., NDVI, NDWI, or NO2)
|
416 |
+
elif isinstance(result, (int, float)):
|
417 |
+
calculated_value = result
|
418 |
+
|
419 |
+
# If a valid calculated_value is found, append the result to session_state
|
420 |
+
if calculated_value is not None:
|
421 |
+
st.session_state.results.append({
|
422 |
+
'Location Name': location_name,
|
423 |
+
'Calculated Value': calculated_value
|
424 |
+
})
|
425 |
+
|
426 |
+
# After processing, show the results
|
427 |
+
if st.session_state.results:
|
428 |
+
result_df = pd.DataFrame(st.session_state.results)
|
429 |
+
|
430 |
+
if shape_type.lower() == 'point':
|
431 |
+
st.write("Processed Results Table (Points):")
|
432 |
+
st.dataframe(result_df[['Location Name', 'Latitude', 'Longitude', 'Calculated Value']])
|
433 |
+
else:
|
434 |
+
st.write("Processed Results Table (Polygons):")
|
435 |
+
st.dataframe(result_df[['Location Name', 'Calculated Value']])
|
436 |
+
|
437 |
+
filename = f"{main_selection}_{sub_selection}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{shape_type}.csv"
|
438 |
+
|
439 |
+
st.download_button(
|
440 |
+
label="Download results as CSV",
|
441 |
+
data=result_df.to_csv(index=False).encode('utf-8'),
|
442 |
+
file_name=filename,
|
443 |
+
mime='text/csv'
|
444 |
+
)
|