|
import streamlit as st |
|
import json |
|
import ee |
|
import os |
|
import pandas as pd |
|
import geopandas as gpd |
|
from datetime import datetime |
|
import leafmap.foliumap as leafmap |
|
import re |
|
|
|
|
|
st.set_page_config(layout="wide") |
|
|
|
|
|
m = st.markdown( |
|
""" |
|
<style> |
|
div.stButton > button:first-child { |
|
background-color: #006400; |
|
color:#ffffff; |
|
} |
|
</style>""", |
|
unsafe_allow_html=True, |
|
) |
|
|
|
|
|
st.write( |
|
f""" |
|
<div style="display: flex; justify-content: space-between; align-items: center;"> |
|
<img src="https://huggingface.co/spaces/YashMK89/GEE_Calculator/resolve/main/ISRO_Logo.png" style="width: 20%; margin-right: auto;"> |
|
<img src="https://huggingface.co/spaces/YashMK89/GEE_Calculator/resolve/main/SAC_Logo.png" style="width: 20%; margin-left: auto;"> |
|
</div> |
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
|
|
|
|
st.markdown( |
|
f""" |
|
<h1 style="text-align: center;">Precision Analysis for Vegetation, Water, and Air Quality</h1> |
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
st.write("<h2><div style='text-align: center;'>User Inputs</div></h2>", unsafe_allow_html=True) |
|
|
|
|
|
earthengine_credentials = os.environ.get("EE_Authentication") |
|
|
|
|
|
os.makedirs(os.path.expanduser("~/.config/earthengine/"), exist_ok=True) |
|
with open(os.path.expanduser("~/.config/earthengine/credentials"), "w") as f: |
|
f.write(earthengine_credentials) |
|
|
|
ee.Initialize(project='ee-yashsacisro24') |
|
|
|
|
|
with open("sentinel_datasets.json") as f: |
|
data = json.load(f) |
|
|
|
|
|
st.title("Sentinel Dataset") |
|
|
|
|
|
main_selection = st.selectbox("Select Sentinel Dataset Category", list(data.keys())) |
|
|
|
if main_selection: |
|
sub_options = data[main_selection]["sub_options"] |
|
sub_selection = st.selectbox("Select Specific Dataset ID", list(sub_options.keys())) |
|
|
|
|
|
st.header("Earth Engine Index Calculator") |
|
|
|
|
|
index_choice = st.selectbox("Select an Index or Enter Custom Formula", ['NDVI', 'NDWI', 'Average NO₂', 'Custom Formula']) |
|
|
|
|
|
custom_formula = "" |
|
|
|
|
|
if index_choice.lower() == 'ndvi': |
|
st.write("Formula for NDVI: NDVI = (B8 - B4) / (B8 + B4)") |
|
elif index_choice.lower() == 'ndwi': |
|
st.write("Formula for NDWI: NDWI = (B3 - B8) / (B3 + B8)") |
|
elif index_choice.lower() == 'average no₂': |
|
st.write("Formula for Average NO₂: Average NO₂ = Mean(NO2 band)") |
|
elif index_choice.lower() == 'custom formula': |
|
custom_formula = st.text_input("Enter Custom Formula (e.g., '(B5 - B4) / (B5 + B4)')") |
|
st.write(f"Custom Formula: {custom_formula}") |
|
|
|
|
|
reducer_choice = st.selectbox( |
|
"Select Reducer", |
|
['mean', 'sum', 'median', 'min', 'max', 'count'], |
|
index=0 |
|
) |
|
|
|
|
|
def convert_to_ee_geometry(geometry): |
|
if geometry.is_valid: |
|
geojson = geometry.__geo_interface__ |
|
return ee.Geometry(geojson) |
|
else: |
|
raise ValueError("Invalid geometry: The polygon geometry is not valid.") |
|
|
|
|
|
def read_csv(file_path): |
|
df = pd.read_csv(file_path) |
|
return df |
|
|
|
|
|
def read_geojson(file_path): |
|
gdf = gpd.read_file(file_path) |
|
return gdf |
|
|
|
|
|
def read_kml(file_path): |
|
gdf = gpd.read_file(file_path, driver='KML') |
|
return gdf |
|
|
|
|
|
shape_type = st.selectbox("Do you want to process 'Point' or 'Polygon' data?", ["Point", "Polygon"]) |
|
|
|
|
|
file_upload = st.file_uploader(f"Upload your {shape_type} data (CSV, GeoJSON, KML)", type=["csv", "geojson", "kml"]) |
|
|
|
|
|
start_date = st.date_input("Start Date", value=pd.to_datetime('2020-01-01')) |
|
end_date = st.date_input("End Date", value=pd.to_datetime('2020-12-31')) |
|
|
|
|
|
start_date_str = start_date.strftime('%Y-%m-%d') |
|
end_date_str = end_date.strftime('%Y-%m-%d') |
|
|
|
|
|
if 'results' not in st.session_state: |
|
st.session_state.results = [] |
|
if 'last_params' not in st.session_state: |
|
st.session_state.last_params = {} |
|
if 'map_data' not in st.session_state: |
|
st.session_state.map_data = None |
|
|
|
|
|
def parameters_changed(): |
|
return ( |
|
st.session_state.last_params.get('main_selection') != main_selection or |
|
st.session_state.last_params.get('sub_selection') != sub_selection or |
|
st.session_state.last_params.get('index_choice') != index_choice or |
|
st.session_state.last_params.get('start_date_str') != start_date_str or |
|
st.session_state.last_params.get('end_date_str') != end_date_str or |
|
st.session_state.last_params.get('shape_type') != shape_type or |
|
st.session_state.last_params.get('file_upload') != file_upload |
|
) |
|
|
|
|
|
if parameters_changed(): |
|
st.session_state.results = [] |
|
st.session_state.last_params = { |
|
'main_selection': main_selection, |
|
'sub_selection': sub_selection, |
|
'index_choice': index_choice, |
|
'start_date_str': start_date_str, |
|
'end_date_str': end_date_str, |
|
'shape_type': shape_type, |
|
'file_upload': file_upload |
|
} |
|
|
|
|
|
def get_reducer(reducer_name): |
|
""" |
|
Map user-friendly reducer names to Earth Engine reducer objects. |
|
|
|
Args: |
|
reducer_name (str): The name of the reducer (e.g., 'mean', 'sum', 'median'). |
|
|
|
Returns: |
|
ee.Reducer: The corresponding Earth Engine reducer. |
|
""" |
|
reducers = { |
|
'mean': ee.Reducer.mean(), |
|
'sum': ee.Reducer.sum(), |
|
'median': ee.Reducer.median(), |
|
'min': ee.Reducer.min(), |
|
'max': ee.Reducer.max(), |
|
'count': ee.Reducer.count(), |
|
} |
|
|
|
|
|
return reducers.get(reducer_name.lower(), ee.Reducer.mean()) |
|
|
|
|
|
def calculate_ndvi(image, geometry): |
|
ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI') |
|
|
|
|
|
result = ndvi.reduceRegion( |
|
reducer=get_reducer(reducer_choice), |
|
geometry=geometry, |
|
scale=30 |
|
) |
|
|
|
|
|
result_value = result.get('NDVI') |
|
try: |
|
calculated_value = result_value.getInfo() |
|
st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}") |
|
except Exception as e: |
|
st.error(f"Error retrieving NDVI result: {e}") |
|
|
|
return result_value |
|
|
|
|
|
|
|
def calculate_ndwi(image, geometry): |
|
ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI') |
|
result = ndwi.reduceRegion( |
|
reducer=get_reducer(reducer_choice), |
|
geometry=geometry, |
|
scale=30 |
|
) |
|
|
|
|
|
result_value = result.get('NDWI') |
|
try: |
|
calculated_value = result_value.getInfo() |
|
st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}") |
|
except Exception as e: |
|
st.error(f"Error retrieving NDVI result: {e}") |
|
|
|
return result_value |
|
|
|
|
|
def calculate_avg_no2_sentinel5p(image, geometry): |
|
no2 = image.select('NO2').reduceRegion( |
|
reducer=get_reducer(reducer_choice), |
|
geometry=geometry, |
|
scale=1000 |
|
) |
|
|
|
|
|
result_value = result.get('NDVI') |
|
try: |
|
calculated_value = result_value.getInfo() |
|
st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}") |
|
except Exception as e: |
|
st.error(f"Error retrieving NDVI result: {e}") |
|
|
|
return result_value |
|
|
|
|
|
def calculate_custom_formula(image, geometry, formula, scale=30): |
|
""" |
|
Calculate a custom formula on an image and return the result for a given geometry, |
|
using a user-specified reducer. |
|
""" |
|
|
|
band_names = image.bandNames().getInfo() |
|
band_dict = {band: image.select(band) for band in band_names} |
|
|
|
|
|
result_image = image.expression(formula, band_dict).rename('CustomResult') |
|
|
|
|
|
result = result_image.reduceRegion( |
|
reducer=get_reducer(reducer_choice), |
|
geometry=geometry, |
|
scale=scale |
|
) |
|
|
|
|
|
result_value = result.get('CustomResult') |
|
try: |
|
calculated_value = result_value.getInfo() |
|
st.write(f"NDVI calculation using {reducer_choice}: {calculated_value}") |
|
except Exception as e: |
|
st.error(f"Error retrieving NDVI result: {e}") |
|
|
|
return result_value |
|
|
|
|
|
def get_most_recent_image(image_collection): |
|
image = image_collection.sort('system:time_start', False).first() |
|
return image |
|
|
|
|
|
|
|
def process_custom_formula(image, geometry, formula): |
|
return calculate_custom_formula(image, geometry, formula) |
|
|
|
locations_df = None |
|
polygons_df = None |
|
|
|
|
|
|
|
if file_upload: |
|
|
|
|
|
|
|
file_extension = os.path.splitext(file_upload.name)[1].lower() |
|
|
|
|
|
if shape_type == 'Point': |
|
if file_extension == '.csv': |
|
locations_df = read_csv(file_upload) |
|
elif file_extension == '.geojson': |
|
locations_df = read_geojson(file_upload) |
|
elif file_extension == '.kml': |
|
locations_df = read_kml(file_upload) |
|
else: |
|
st.error("Unsupported file type. Please upload a CSV, GeoJSON, or KML file for points.") |
|
elif shape_type == 'Polygon': |
|
if file_extension == '.geojson': |
|
polygons_df = read_geojson(file_upload) |
|
elif file_extension == '.kml': |
|
polygons_df = read_kml(file_upload) |
|
else: |
|
st.error("Unsupported file type. Please upload a GeoJSON or KML file for polygons.") |
|
|
|
|
|
if locations_df is not None and not locations_df.empty: |
|
|
|
if 'latitude' not in locations_df.columns or 'longitude' not in locations_df.columns: |
|
st.error("Uploaded file is missing required 'latitude' or 'longitude' columns.") |
|
else: |
|
|
|
st.write("Preview of the uploaded points data:") |
|
st.dataframe(locations_df.head()) |
|
|
|
|
|
m = leafmap.Map(center=[locations_df['latitude'].mean(), locations_df['longitude'].mean()], zoom=10) |
|
|
|
|
|
for _, row in locations_df.iterrows(): |
|
latitude = row['latitude'] |
|
longitude = row['longitude'] |
|
|
|
|
|
if pd.isna(latitude) or pd.isna(longitude): |
|
continue |
|
|
|
m.add_marker(location=[latitude, longitude], popup=row.get('name', 'No Name')) |
|
|
|
|
|
st.write("Map of Uploaded Points:") |
|
m.to_streamlit() |
|
|
|
|
|
st.session_state.map_data = m |
|
|
|
|
|
for idx, row in locations_df.iterrows(): |
|
latitude = row['latitude'] |
|
longitude = row['longitude'] |
|
location_name = row.get('name', f"Location_{idx}") |
|
|
|
|
|
if pd.isna(latitude) or pd.isna(longitude): |
|
continue |
|
|
|
|
|
roi = ee.Geometry.Point([longitude, latitude]) |
|
|
|
|
|
collection = ee.ImageCollection(sub_options[sub_selection]) \ |
|
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \ |
|
.filterBounds(roi) |
|
|
|
image = get_most_recent_image(collection) |
|
if not image: |
|
st.warning(f"No images found for {location_name}.") |
|
else: |
|
st.write(f"Found images for {location_name}.") |
|
|
|
|
|
result = None |
|
if index_choice == 'NDVI': |
|
result = calculate_ndvi(image, roi) |
|
elif index_choice == 'NDWI': |
|
result = calculate_ndwi(image, roi) |
|
elif index_choice == 'Average NO₂': |
|
if 'NO2' in image.bandNames().getInfo(): |
|
result = calculate_avg_no2_sentinel5p(image, roi) |
|
else: |
|
st.warning(f"No NO2 band found for {location_name}. Please use Sentinel-5P for NO₂ data.") |
|
elif index_choice.lower() == 'custom formula' and custom_formula: |
|
result = process_custom_formula(image, roi, custom_formula) |
|
|
|
|
|
if result is not None: |
|
calculated_value = None |
|
|
|
|
|
if isinstance(result, dict): |
|
|
|
calculated_value = result.get('CustomResult', None) |
|
else: |
|
try: |
|
|
|
calculated_value = result.getInfo() |
|
except Exception as e: |
|
st.error(f"Error getting result info: {e}") |
|
|
|
|
|
if calculated_value is not None: |
|
st.session_state.results.append({ |
|
'Location Name': location_name, |
|
'Latitude': latitude, |
|
'Longitude': longitude, |
|
'Calculated Value': calculated_value |
|
}) |
|
else: |
|
st.warning(f"No value calculated for {location_name}.") |
|
else: |
|
st.warning(f"No value calculated for {location_name}.") |
|
|
|
|
|
|
|
if polygons_df is not None: |
|
st.write("Preview of the uploaded polygons data:") |
|
st.dataframe(polygons_df.head()) |
|
|
|
m = leafmap.Map(center=[polygons_df.geometry.centroid.y.mean(), polygons_df.geometry.centroid.x.mean()], zoom=10) |
|
|
|
for _, row in polygons_df.iterrows(): |
|
polygon = row['geometry'] |
|
if polygon.is_valid: |
|
gdf = gpd.GeoDataFrame([row], geometry=[polygon], crs=polygons_df.crs) |
|
m.add_gdf(gdf=gdf, layer_name=row.get('name', 'Unnamed Polygon')) |
|
|
|
st.write("Map of Uploaded Polygons:") |
|
m.to_streamlit() |
|
st.session_state.map_data = m |
|
|
|
for idx, row in polygons_df.iterrows(): |
|
polygon = row['geometry'] |
|
location_name = row.get('name', f"Polygon_{idx}") |
|
|
|
try: |
|
roi = convert_to_ee_geometry(polygon) |
|
except ValueError as e: |
|
st.error(str(e)) |
|
continue |
|
|
|
collection = ee.ImageCollection(sub_options[sub_selection]) \ |
|
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \ |
|
.filterBounds(roi) |
|
|
|
image = get_most_recent_image(collection) |
|
|
|
if not image: |
|
st.warning(f"No images found for {location_name}.") |
|
else: |
|
st.write(f"Found an image for {location_name}.") |
|
result = None |
|
if index_choice.lower() == 'ndvi': |
|
result = calculate_ndvi(image, roi) |
|
elif index_choice.lower() == 'ndwi': |
|
result = calculate_ndwi(image, roi) |
|
elif index_choice.lower() == 'average no₂': |
|
if 'NO2' in image.bandNames().getInfo(): |
|
result = calculate_avg_no2_sentinel5p(image, roi) |
|
else: |
|
st.warning(f"No NO2 band found for {location_name}. Please use Sentinel-5P for NO₂ data.") |
|
elif index_choice.lower() == 'custom formula' and custom_formula: |
|
result = process_custom_formula(image, roi, custom_formula) |
|
|
|
if result is not None: |
|
|
|
calculated_value = None |
|
|
|
|
|
if isinstance(result, dict) and 'CustomResult' in result: |
|
calculated_value = result['CustomResult'] |
|
|
|
elif isinstance(result, (int, float)): |
|
calculated_value = result |
|
|
|
|
|
if calculated_value is not None: |
|
st.session_state.results.append({ |
|
'Location Name': location_name, |
|
'Calculated Value': calculated_value |
|
}) |
|
|
|
|
|
if st.session_state.results: |
|
result_df = pd.DataFrame(st.session_state.results) |
|
|
|
if shape_type.lower() == 'point': |
|
st.write("Processed Results Table (Points):") |
|
st.dataframe(result_df[['Location Name', 'Latitude', 'Longitude', 'Calculated Value']]) |
|
else: |
|
st.write("Processed Results Table (Polygons):") |
|
st.dataframe(result_df[['Location Name', 'Calculated Value']]) |
|
|
|
filename = f"{main_selection}_{sub_selection}_{start_date.strftime('%Y/%m/%d')}_{end_date.strftime('%Y/%m/%d')}_{shape_type}.csv" |
|
|
|
st.download_button( |
|
label="Download results as CSV", |
|
data=result_df.to_csv(index=False).encode('utf-8'), |
|
file_name=filename, |
|
mime='text/csv' |
|
) |
|
|