Update app.py
Browse files
app.py
CHANGED
|
@@ -1062,21 +1062,88 @@ import streamlit as st
|
|
| 1062 |
import geemap.foliumap as geemap
|
| 1063 |
import ee
|
| 1064 |
import matplotlib.pyplot as plt
|
| 1065 |
-
from datetime import datetime
|
| 1066 |
-
import tempfile
|
| 1067 |
from streamlit_folium import st_folium
|
| 1068 |
-
import folium
|
| 1069 |
|
| 1070 |
# =====================================================
|
| 1071 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1072 |
# =====================================================
|
| 1073 |
def export_map_snapshot(m, width=800, height=600):
|
| 1074 |
-
"""
|
| 1075 |
try:
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
|
|
|
| 1080 |
except Exception as e:
|
| 1081 |
st.warning(f"Map snapshot failed: {e}")
|
| 1082 |
return None
|
|
@@ -1088,85 +1155,19 @@ def locator_page():
|
|
| 1088 |
st.title("🌍 GeoMate Interactive Earth Explorer")
|
| 1089 |
st.markdown(
|
| 1090 |
"Draw a polygon (or rectangle) on the map using the drawing tool. "
|
| 1091 |
-
"Then press **Compute Summaries** to compute soil
|
| 1092 |
)
|
| 1093 |
|
| 1094 |
-
#
|
| 1095 |
-
# EE Auth
|
| 1096 |
-
# ----------------------------
|
| 1097 |
-
EARTHENGINE_TOKEN = os.getenv("EARTHENGINE_TOKEN")
|
| 1098 |
-
SERVICE_ACCOUNT = os.getenv("SERVICE_ACCOUNT")
|
| 1099 |
-
|
| 1100 |
-
def initialize_ee():
|
| 1101 |
-
if "ee_initialized" in st.session_state and st.session_state["ee_initialized"]:
|
| 1102 |
-
return True
|
| 1103 |
-
if EARTHENGINE_TOKEN and SERVICE_ACCOUNT:
|
| 1104 |
-
try:
|
| 1105 |
-
creds = ee.ServiceAccountCredentials(email=SERVICE_ACCOUNT, key_data=EARTHENGINE_TOKEN)
|
| 1106 |
-
ee.Initialize(creds)
|
| 1107 |
-
st.session_state["ee_initialized"] = True
|
| 1108 |
-
return True
|
| 1109 |
-
except Exception as e:
|
| 1110 |
-
st.warning(f"Service account init failed: {e}, falling back...")
|
| 1111 |
-
try:
|
| 1112 |
-
ee.Initialize()
|
| 1113 |
-
st.session_state["ee_initialized"] = True
|
| 1114 |
-
return True
|
| 1115 |
-
except Exception:
|
| 1116 |
-
try:
|
| 1117 |
-
ee.Authenticate()
|
| 1118 |
-
ee.Initialize()
|
| 1119 |
-
st.session_state["ee_initialized"] = True
|
| 1120 |
-
return True
|
| 1121 |
-
except Exception as e:
|
| 1122 |
-
st.error(f"Earth Engine auth failed: {e}")
|
| 1123 |
-
return False
|
| 1124 |
-
|
| 1125 |
if not initialize_ee():
|
| 1126 |
st.stop()
|
| 1127 |
|
| 1128 |
-
#
|
| 1129 |
-
# Safe reducers
|
| 1130 |
-
# ----------------------------
|
| 1131 |
-
def safe_get_reduce(region, image, band, scale=1000, default=None, max_pixels=int(1e7)):
|
| 1132 |
-
try:
|
| 1133 |
-
rr = image.reduceRegion(ee.Reducer.mean(), region, scale=scale, maxPixels=max_pixels)
|
| 1134 |
-
val = rr.get(band)
|
| 1135 |
-
return float(val.getInfo()) if val else default
|
| 1136 |
-
except Exception:
|
| 1137 |
-
return default
|
| 1138 |
-
|
| 1139 |
-
def safe_reduce_histogram(region, image, band, scale=1000, max_pixels=int(1e7)):
|
| 1140 |
-
try:
|
| 1141 |
-
rr = image.reduceRegion(ee.Reducer.frequencyHistogram(), region, scale=scale, maxPixels=max_pixels)
|
| 1142 |
-
hist = rr.get(band)
|
| 1143 |
-
return hist.getInfo() if hist else {}
|
| 1144 |
-
except Exception:
|
| 1145 |
-
return {}
|
| 1146 |
-
|
| 1147 |
-
def safe_time_series(region, collection, band, start, end, reducer=ee.Reducer.mean(), scale=1000, max_pixels=int(1e7)):
|
| 1148 |
-
try:
|
| 1149 |
-
def per_image(img):
|
| 1150 |
-
date = img.date().format("YYYY-MM-dd")
|
| 1151 |
-
val = img.reduceRegion(reducer, region, scale=scale, maxPixels=max_pixels).get(band)
|
| 1152 |
-
return ee.Feature(None, {"date": date, "val": val})
|
| 1153 |
-
feats = collection.filterDate(start, end).map(per_image).filter(ee.Filter.notNull(["val"])).getInfo()
|
| 1154 |
-
pts = []
|
| 1155 |
-
for f in feats.get("features", []):
|
| 1156 |
-
p = f.get("properties", {})
|
| 1157 |
-
if p.get("val") is not None:
|
| 1158 |
-
pts.append((p.get("date"), float(p.get("val"))))
|
| 1159 |
-
return pts
|
| 1160 |
-
except Exception:
|
| 1161 |
-
return []
|
| 1162 |
-
|
| 1163 |
-
# ----------------------------
|
| 1164 |
-
# Map setup
|
| 1165 |
-
# ----------------------------
|
| 1166 |
m = geemap.Map(center=[28.0, 72.0], zoom=5, plugin_Draw=True, draw_export=True, locate_control=True)
|
| 1167 |
|
| 1168 |
-
# Restore ROI (if available)
|
| 1169 |
if "roi_geojson" in st.session_state:
|
|
|
|
| 1170 |
try:
|
| 1171 |
saved = st.session_state["roi_geojson"]
|
| 1172 |
folium.GeoJson(saved, name="Saved ROI",
|
|
@@ -1174,61 +1175,59 @@ def locator_page():
|
|
| 1174 |
except Exception as e:
|
| 1175 |
st.warning(f"Could not re-add saved ROI: {e}")
|
| 1176 |
|
| 1177 |
-
#
|
| 1178 |
-
# Datasets (DEM, Soil, Seismic, Flood, Landcover, NDVI)
|
| 1179 |
-
# ----------------------------
|
| 1180 |
-
# DEM
|
| 1181 |
try:
|
| 1182 |
dem = ee.Image("NASA/NASADEM_HGT/001"); dem_band_name = "elevation"
|
| 1183 |
except Exception:
|
| 1184 |
-
|
| 1185 |
-
dem = ee.Image("USGS/SRTMGL1_003"); dem_band_name = "elevation"
|
| 1186 |
-
except Exception:
|
| 1187 |
-
dem = None; dem_band_name = None
|
| 1188 |
|
| 1189 |
-
|
| 1190 |
-
soil_img = None; chosen_soil_band = None
|
| 1191 |
try:
|
| 1192 |
soil_img = ee.Image("OpenLandMap/SOL/SOL_CLAY-WFRACTION_USDA-3A1A1A_M/v02")
|
| 1193 |
bands = soil_img.bandNames().getInfo()
|
| 1194 |
-
chosen_soil_band = st.selectbox("Select soil
|
| 1195 |
except Exception:
|
| 1196 |
-
|
| 1197 |
-
soil_img = ee.Image("projects/soilgrids-isric/clay_mean")
|
| 1198 |
-
bands = soil_img.bandNames().getInfo()
|
| 1199 |
-
chosen_soil_band = st.selectbox("Select soil depth (SoilGrids)", options=bands, index=0)
|
| 1200 |
-
except Exception:
|
| 1201 |
-
soil_img = None; chosen_soil_band = None
|
| 1202 |
|
| 1203 |
-
# Seismic
|
| 1204 |
try:
|
| 1205 |
seismic_img = ee.Image("SEDAC/GSHAPSeismicHazard"); seismic_band = "gshap"
|
| 1206 |
except Exception:
|
| 1207 |
-
seismic_img
|
| 1208 |
|
| 1209 |
-
# Flood
|
| 1210 |
try:
|
| 1211 |
water = ee.Image("JRC/GSW1_4/GlobalSurfaceWater"); water_band = "occurrence"
|
| 1212 |
except Exception:
|
| 1213 |
-
water
|
| 1214 |
|
| 1215 |
-
# Landcover
|
| 1216 |
try:
|
| 1217 |
landcover = ee.Image("ESA/WorldCover/v200"); lc_band = "Map"
|
| 1218 |
except Exception:
|
| 1219 |
-
landcover
|
| 1220 |
|
| 1221 |
-
# NDVI
|
| 1222 |
try:
|
| 1223 |
ndvi_col = ee.ImageCollection("MODIS/061/MOD13A2").select("NDVI")
|
| 1224 |
except Exception:
|
| 1225 |
ndvi_col = None
|
| 1226 |
|
| 1227 |
-
#
|
| 1228 |
-
|
| 1229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1230 |
result = st_folium(m, width=800, height=600, returned_objects=["last_active_drawing"])
|
| 1231 |
-
roi,
|
| 1232 |
|
| 1233 |
if result and "last_active_drawing" in result and result["last_active_drawing"]:
|
| 1234 |
feat = result["last_active_drawing"]
|
|
@@ -1251,9 +1250,8 @@ def locator_page():
|
|
| 1251 |
st.error(f"Failed to convert geometry: {e}")
|
| 1252 |
|
| 1253 |
if roi is None and "roi_geojson" in st.session_state:
|
| 1254 |
-
saved = st.session_state["roi_geojson"]
|
| 1255 |
try:
|
| 1256 |
-
geom =
|
| 1257 |
if geom:
|
| 1258 |
roi = ee.Geometry(geom)
|
| 1259 |
coords = geom.get("coordinates", None)
|
|
@@ -1274,9 +1272,7 @@ def locator_page():
|
|
| 1274 |
st.markdown("### 📍 ROI Coordinates (Lat, Lon)")
|
| 1275 |
st.write(st.session_state["roi_coords"])
|
| 1276 |
|
| 1277 |
-
#
|
| 1278 |
-
# Compute summaries
|
| 1279 |
-
# ----------------------------
|
| 1280 |
if st.button("Compute Summaries"):
|
| 1281 |
if roi is None:
|
| 1282 |
st.error("⚠️ No ROI found. Please draw first.")
|
|
@@ -1291,32 +1287,47 @@ def locator_page():
|
|
| 1291 |
ndvi_ts = []
|
| 1292 |
if ndvi_col:
|
| 1293 |
end = datetime.utcnow().strftime("%Y-%m-%d")
|
| 1294 |
-
start = (datetime.utcnow()
|
| 1295 |
ndvi_ts = safe_time_series(roi, ndvi_col, "NDVI", start, end)
|
| 1296 |
|
| 1297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1298 |
active = st.session_state.get("active_site", 0)
|
| 1299 |
if "sites" in st.session_state:
|
| 1300 |
site = st.session_state["sites"][active]
|
| 1301 |
-
|
| 1302 |
-
|
| 1303 |
-
|
| 1304 |
-
|
| 1305 |
-
site["ROI"] = "Not available"
|
| 1306 |
site["Soil Profile"] = f"{soil_val} ({chosen_soil_band})" if soil_val else "N/A"
|
| 1307 |
site["Topo Data"] = f"{elev_val} m" if elev_val else "N/A"
|
| 1308 |
site["Seismic Data"] = seismic_val if seismic_val else "N/A"
|
| 1309 |
site["Flood Data"] = flood_val if flood_val else "N/A"
|
| 1310 |
site["Environmental Data"] = {"Landcover": lc_stats, "NDVI": ndvi_ts}
|
|
|
|
| 1311 |
|
| 1312 |
st.session_state["soil_json"] = {
|
| 1313 |
"Soil": soil_val, "Soil Band": chosen_soil_band,
|
| 1314 |
"Elevation": elev_val, "Seismic": seismic_val,
|
| 1315 |
"Flood": flood_val, "Landcover Stats": lc_stats,
|
| 1316 |
-
"NDVI TS": ndvi_ts
|
|
|
|
|
|
|
|
|
|
| 1317 |
}
|
| 1318 |
|
| 1319 |
-
#
|
| 1320 |
map_bytes = export_map_snapshot(m)
|
| 1321 |
if map_bytes:
|
| 1322 |
st.session_state["last_map_snapshot"] = map_bytes
|
|
@@ -1330,13 +1341,28 @@ def locator_page():
|
|
| 1330 |
st.write(f"**Elevation:** {elev_val}")
|
| 1331 |
st.write(f"**Seismic:** {seismic_val}")
|
| 1332 |
st.write(f"**Flood:** {flood_val}")
|
|
|
|
| 1333 |
st.json(lc_stats)
|
|
|
|
| 1334 |
if ndvi_ts:
|
| 1335 |
d, v = zip(*ndvi_ts)
|
| 1336 |
fig, ax = plt.subplots()
|
| 1337 |
ax.plot(d, v, marker="o"); ax.set_title("NDVI"); ax.set_xlabel("Date")
|
| 1338 |
st.pyplot(fig)
|
| 1339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1340 |
# GeoMate Ask (RAG) — simple chat with memory per site and auto-extract numeric values
|
| 1341 |
import re, json, pickle
|
| 1342 |
import streamlit as st
|
|
|
|
| 1062 |
import geemap.foliumap as geemap
|
| 1063 |
import ee
|
| 1064 |
import matplotlib.pyplot as plt
|
| 1065 |
+
from datetime import datetime, timedelta
|
|
|
|
| 1066 |
from streamlit_folium import st_folium
|
|
|
|
| 1067 |
|
| 1068 |
# =====================================================
|
| 1069 |
+
# EE Init Helper
|
| 1070 |
+
# =====================================================
|
| 1071 |
+
def initialize_ee():
|
| 1072 |
+
EARTHENGINE_TOKEN = os.getenv("EARTHENGINE_TOKEN")
|
| 1073 |
+
SERVICE_ACCOUNT = os.getenv("SERVICE_ACCOUNT")
|
| 1074 |
+
|
| 1075 |
+
if "ee_initialized" in st.session_state and st.session_state["ee_initialized"]:
|
| 1076 |
+
return True
|
| 1077 |
+
|
| 1078 |
+
if EARTHENGINE_TOKEN and SERVICE_ACCOUNT:
|
| 1079 |
+
try:
|
| 1080 |
+
creds = ee.ServiceAccountCredentials(email=SERVICE_ACCOUNT, key_data=EARTHENGINE_TOKEN)
|
| 1081 |
+
ee.Initialize(creds)
|
| 1082 |
+
st.session_state["ee_initialized"] = True
|
| 1083 |
+
return True
|
| 1084 |
+
except Exception as e:
|
| 1085 |
+
st.warning(f"Service account init failed: {e}, falling back...")
|
| 1086 |
+
|
| 1087 |
+
try:
|
| 1088 |
+
ee.Initialize()
|
| 1089 |
+
st.session_state["ee_initialized"] = True
|
| 1090 |
+
return True
|
| 1091 |
+
except Exception:
|
| 1092 |
+
try:
|
| 1093 |
+
ee.Authenticate()
|
| 1094 |
+
ee.Initialize()
|
| 1095 |
+
st.session_state["ee_initialized"] = True
|
| 1096 |
+
return True
|
| 1097 |
+
except Exception as e:
|
| 1098 |
+
st.error(f"Earth Engine auth failed: {e}")
|
| 1099 |
+
return False
|
| 1100 |
+
|
| 1101 |
+
# =====================================================
|
| 1102 |
+
# Safe reducers
|
| 1103 |
+
# =====================================================
|
| 1104 |
+
def safe_get_reduce(region, image, band, scale=1000, default=None, max_pixels=int(1e7)):
|
| 1105 |
+
try:
|
| 1106 |
+
rr = image.reduceRegion(ee.Reducer.mean(), region, scale=scale, maxPixels=max_pixels)
|
| 1107 |
+
val = rr.get(band)
|
| 1108 |
+
return float(val.getInfo()) if val else default
|
| 1109 |
+
except Exception:
|
| 1110 |
+
return default
|
| 1111 |
+
|
| 1112 |
+
def safe_reduce_histogram(region, image, band, scale=1000, max_pixels=int(1e7)):
|
| 1113 |
+
try:
|
| 1114 |
+
rr = image.reduceRegion(ee.Reducer.frequencyHistogram(), region, scale=scale, maxPixels=max_pixels)
|
| 1115 |
+
hist = rr.get(band)
|
| 1116 |
+
return hist.getInfo() if hist else {}
|
| 1117 |
+
except Exception:
|
| 1118 |
+
return {}
|
| 1119 |
+
|
| 1120 |
+
def safe_time_series(region, collection, band, start, end, reducer=ee.Reducer.mean(), scale=1000, max_pixels=int(1e7)):
|
| 1121 |
+
try:
|
| 1122 |
+
def per_image(img):
|
| 1123 |
+
date = img.date().format("YYYY-MM-dd")
|
| 1124 |
+
val = img.reduceRegion(reducer, region, scale=scale, maxPixels=max_pixels).get(band)
|
| 1125 |
+
return ee.Feature(None, {"date": date, "val": val})
|
| 1126 |
+
feats = collection.filterDate(start, end).map(per_image).filter(ee.Filter.notNull(["val"])).getInfo()
|
| 1127 |
+
pts = []
|
| 1128 |
+
for f in feats.get("features", []):
|
| 1129 |
+
p = f.get("properties", {})
|
| 1130 |
+
if p.get("val") is not None:
|
| 1131 |
+
pts.append((p.get("date"), float(p.get("val"))))
|
| 1132 |
+
return pts
|
| 1133 |
+
except Exception:
|
| 1134 |
+
return []
|
| 1135 |
+
|
| 1136 |
+
# =====================================================
|
| 1137 |
+
# Map snapshot (in-memory, no disk bloat)
|
| 1138 |
# =====================================================
|
| 1139 |
def export_map_snapshot(m, width=800, height=600):
|
| 1140 |
+
"""Return PNG snapshot bytes of geemap Map."""
|
| 1141 |
try:
|
| 1142 |
+
from io import BytesIO
|
| 1143 |
+
buf = BytesIO()
|
| 1144 |
+
m.screenshot(filename=None, region=None, dimensions=(width, height), out_file=buf)
|
| 1145 |
+
buf.seek(0)
|
| 1146 |
+
return buf.read()
|
| 1147 |
except Exception as e:
|
| 1148 |
st.warning(f"Map snapshot failed: {e}")
|
| 1149 |
return None
|
|
|
|
| 1155 |
st.title("🌍 GeoMate Interactive Earth Explorer")
|
| 1156 |
st.markdown(
|
| 1157 |
"Draw a polygon (or rectangle) on the map using the drawing tool. "
|
| 1158 |
+
"Then press **Compute Summaries** to compute soil, elevation, seismic, flood, landcover, NDVI, and atmospheric data."
|
| 1159 |
)
|
| 1160 |
|
| 1161 |
+
# --- Auth
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
if not initialize_ee():
|
| 1163 |
st.stop()
|
| 1164 |
|
| 1165 |
+
# --- Map setup
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1166 |
m = geemap.Map(center=[28.0, 72.0], zoom=5, plugin_Draw=True, draw_export=True, locate_control=True)
|
| 1167 |
|
| 1168 |
+
# Restore ROI (if available)
|
| 1169 |
if "roi_geojson" in st.session_state:
|
| 1170 |
+
import folium
|
| 1171 |
try:
|
| 1172 |
saved = st.session_state["roi_geojson"]
|
| 1173 |
folium.GeoJson(saved, name="Saved ROI",
|
|
|
|
| 1175 |
except Exception as e:
|
| 1176 |
st.warning(f"Could not re-add saved ROI: {e}")
|
| 1177 |
|
| 1178 |
+
# --- Datasets
|
|
|
|
|
|
|
|
|
|
| 1179 |
try:
|
| 1180 |
dem = ee.Image("NASA/NASADEM_HGT/001"); dem_band_name = "elevation"
|
| 1181 |
except Exception:
|
| 1182 |
+
dem, dem_band_name = None, None
|
|
|
|
|
|
|
|
|
|
| 1183 |
|
| 1184 |
+
soil_img, chosen_soil_band = None, None
|
|
|
|
| 1185 |
try:
|
| 1186 |
soil_img = ee.Image("OpenLandMap/SOL/SOL_CLAY-WFRACTION_USDA-3A1A1A_M/v02")
|
| 1187 |
bands = soil_img.bandNames().getInfo()
|
| 1188 |
+
chosen_soil_band = st.selectbox("Select soil clay band", options=bands, index=bands.index("b200") if "b200" in bands else 0)
|
| 1189 |
except Exception:
|
| 1190 |
+
soil_img, chosen_soil_band = None, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1191 |
|
|
|
|
| 1192 |
try:
|
| 1193 |
seismic_img = ee.Image("SEDAC/GSHAPSeismicHazard"); seismic_band = "gshap"
|
| 1194 |
except Exception:
|
| 1195 |
+
seismic_img, seismic_band = None, None
|
| 1196 |
|
|
|
|
| 1197 |
try:
|
| 1198 |
water = ee.Image("JRC/GSW1_4/GlobalSurfaceWater"); water_band = "occurrence"
|
| 1199 |
except Exception:
|
| 1200 |
+
water, water_band = None, None
|
| 1201 |
|
|
|
|
| 1202 |
try:
|
| 1203 |
landcover = ee.Image("ESA/WorldCover/v200"); lc_band = "Map"
|
| 1204 |
except Exception:
|
| 1205 |
+
landcover, lc_band = None, None
|
| 1206 |
|
|
|
|
| 1207 |
try:
|
| 1208 |
ndvi_col = ee.ImageCollection("MODIS/061/MOD13A2").select("NDVI")
|
| 1209 |
except Exception:
|
| 1210 |
ndvi_col = None
|
| 1211 |
|
| 1212 |
+
# Atmospheric datasets
|
| 1213 |
+
try:
|
| 1214 |
+
precip_col = ee.ImageCollection("UCSB-CHG/CHIRPS/DAILY").select("precipitation")
|
| 1215 |
+
except Exception:
|
| 1216 |
+
precip_col = None
|
| 1217 |
+
|
| 1218 |
+
try:
|
| 1219 |
+
temp_col = ee.ImageCollection("MODIS/061/MOD11A2").select("LST_Day_1km")
|
| 1220 |
+
except Exception:
|
| 1221 |
+
temp_col = None
|
| 1222 |
+
|
| 1223 |
+
try:
|
| 1224 |
+
pm25_img = ee.ImageCollection("COPERNICUS/S5P/OFFL/L3_AER_AI").select("absorbing_aerosol_index").mean()
|
| 1225 |
+
except Exception:
|
| 1226 |
+
pm25_img = None
|
| 1227 |
+
|
| 1228 |
+
# --- Render & capture ROI
|
| 1229 |
result = st_folium(m, width=800, height=600, returned_objects=["last_active_drawing"])
|
| 1230 |
+
roi, flat_coords = None, None
|
| 1231 |
|
| 1232 |
if result and "last_active_drawing" in result and result["last_active_drawing"]:
|
| 1233 |
feat = result["last_active_drawing"]
|
|
|
|
| 1250 |
st.error(f"Failed to convert geometry: {e}")
|
| 1251 |
|
| 1252 |
if roi is None and "roi_geojson" in st.session_state:
|
|
|
|
| 1253 |
try:
|
| 1254 |
+
geom = st.session_state["roi_geojson"].get("geometry")
|
| 1255 |
if geom:
|
| 1256 |
roi = ee.Geometry(geom)
|
| 1257 |
coords = geom.get("coordinates", None)
|
|
|
|
| 1272 |
st.markdown("### 📍 ROI Coordinates (Lat, Lon)")
|
| 1273 |
st.write(st.session_state["roi_coords"])
|
| 1274 |
|
| 1275 |
+
# --- Compute summaries
|
|
|
|
|
|
|
| 1276 |
if st.button("Compute Summaries"):
|
| 1277 |
if roi is None:
|
| 1278 |
st.error("⚠️ No ROI found. Please draw first.")
|
|
|
|
| 1287 |
ndvi_ts = []
|
| 1288 |
if ndvi_col:
|
| 1289 |
end = datetime.utcnow().strftime("%Y-%m-%d")
|
| 1290 |
+
start = (datetime.utcnow() - timedelta(days=365*2)).strftime("%Y-%m-%d")
|
| 1291 |
ndvi_ts = safe_time_series(roi, ndvi_col, "NDVI", start, end)
|
| 1292 |
|
| 1293 |
+
precip_ts, temp_ts, pm25_val = [], [], None
|
| 1294 |
+
if precip_col:
|
| 1295 |
+
end = datetime.utcnow().strftime("%Y-%m-%d")
|
| 1296 |
+
start = (datetime.utcnow() - timedelta(days=365)).strftime("%Y-%m-%d")
|
| 1297 |
+
precip_ts = safe_time_series(roi, precip_col, "precipitation", start, end, scale=5000)
|
| 1298 |
+
if temp_col:
|
| 1299 |
+
end = datetime.utcnow().strftime("%Y-%m-%d")
|
| 1300 |
+
start = (datetime.utcnow() - timedelta(days=365)).strftime("%Y-%m-%d")
|
| 1301 |
+
temp_ts = safe_time_series(roi, temp_col, "LST_Day_1km", start, end, scale=1000)
|
| 1302 |
+
if pm25_img:
|
| 1303 |
+
pm25_val = safe_get_reduce(roi, pm25_img, "absorbing_aerosol_index", 10000)
|
| 1304 |
+
|
| 1305 |
+
# Save to site
|
| 1306 |
active = st.session_state.get("active_site", 0)
|
| 1307 |
if "sites" in st.session_state:
|
| 1308 |
site = st.session_state["sites"][active]
|
| 1309 |
+
try:
|
| 1310 |
+
site["ROI"] = roi.getInfo()
|
| 1311 |
+
except Exception:
|
| 1312 |
+
site["ROI"] = "Not available"
|
|
|
|
| 1313 |
site["Soil Profile"] = f"{soil_val} ({chosen_soil_band})" if soil_val else "N/A"
|
| 1314 |
site["Topo Data"] = f"{elev_val} m" if elev_val else "N/A"
|
| 1315 |
site["Seismic Data"] = seismic_val if seismic_val else "N/A"
|
| 1316 |
site["Flood Data"] = flood_val if flood_val else "N/A"
|
| 1317 |
site["Environmental Data"] = {"Landcover": lc_stats, "NDVI": ndvi_ts}
|
| 1318 |
+
site["Atmospheric Data"] = {"Precipitation": precip_ts, "Temperature": temp_ts, "PM2.5": pm25_val}
|
| 1319 |
|
| 1320 |
st.session_state["soil_json"] = {
|
| 1321 |
"Soil": soil_val, "Soil Band": chosen_soil_band,
|
| 1322 |
"Elevation": elev_val, "Seismic": seismic_val,
|
| 1323 |
"Flood": flood_val, "Landcover Stats": lc_stats,
|
| 1324 |
+
"NDVI TS": ndvi_ts,
|
| 1325 |
+
"Precipitation TS": precip_ts,
|
| 1326 |
+
"Temperature TS": temp_ts,
|
| 1327 |
+
"PM2.5": pm25_val
|
| 1328 |
}
|
| 1329 |
|
| 1330 |
+
# Snapshot
|
| 1331 |
map_bytes = export_map_snapshot(m)
|
| 1332 |
if map_bytes:
|
| 1333 |
st.session_state["last_map_snapshot"] = map_bytes
|
|
|
|
| 1341 |
st.write(f"**Elevation:** {elev_val}")
|
| 1342 |
st.write(f"**Seismic:** {seismic_val}")
|
| 1343 |
st.write(f"**Flood:** {flood_val}")
|
| 1344 |
+
st.write(f"**PM2.5 Index:** {pm25_val}")
|
| 1345 |
st.json(lc_stats)
|
| 1346 |
+
|
| 1347 |
if ndvi_ts:
|
| 1348 |
d, v = zip(*ndvi_ts)
|
| 1349 |
fig, ax = plt.subplots()
|
| 1350 |
ax.plot(d, v, marker="o"); ax.set_title("NDVI"); ax.set_xlabel("Date")
|
| 1351 |
st.pyplot(fig)
|
| 1352 |
|
| 1353 |
+
if precip_ts:
|
| 1354 |
+
d, v = zip(*precip_ts)
|
| 1355 |
+
fig, ax = plt.subplots()
|
| 1356 |
+
ax.plot(d, v, marker="o", color="blue"); ax.set_title("Precipitation (mm)"); ax.set_xlabel("Date")
|
| 1357 |
+
st.pyplot(fig)
|
| 1358 |
+
|
| 1359 |
+
if temp_ts:
|
| 1360 |
+
d, v = zip(*temp_ts)
|
| 1361 |
+
fig, ax = plt.subplots()
|
| 1362 |
+
ax.plot(d, v, marker="o", color="red"); ax.set_title("LST Temperature (K)"); ax.set_xlabel("Date")
|
| 1363 |
+
st.pyplot(fig)
|
| 1364 |
+
|
| 1365 |
+
|
| 1366 |
# GeoMate Ask (RAG) — simple chat with memory per site and auto-extract numeric values
|
| 1367 |
import re, json, pickle
|
| 1368 |
import streamlit as st
|