MSU576 commited on
Commit
4f4bd53
·
verified ·
1 Parent(s): 6486dee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -122
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
- # Map snapshot export
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1072
  # =====================================================
1073
  def export_map_snapshot(m, width=800, height=600):
1074
- """Export geemap Map object to PNG snapshot (returns bytes)."""
1075
  try:
1076
- tmpfile = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
1077
- m.screenshot(filename=tmpfile.name, region=None, dimensions=(width, height))
1078
- with open(tmpfile.name, "rb") as f:
1079
- return f.read()
 
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 clay, elevation, seismic, flood occurrence, landcover, and NDVI."
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) as polygon on the map
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
- try:
1185
- dem = ee.Image("USGS/SRTMGL1_003"); dem_band_name = "elevation"
1186
- except Exception:
1187
- dem = None; dem_band_name = None
1188
 
1189
- # Soil
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 depth / clay band", options=bands, index=bands.index("b200") if "b200" in bands else 0)
1195
  except Exception:
1196
- try:
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 = None; seismic_band = None
1208
 
1209
- # Flood
1210
  try:
1211
  water = ee.Image("JRC/GSW1_4/GlobalSurfaceWater"); water_band = "occurrence"
1212
  except Exception:
1213
- water = None; water_band = None
1214
 
1215
- # Landcover
1216
  try:
1217
  landcover = ee.Image("ESA/WorldCover/v200"); lc_band = "Map"
1218
  except Exception:
1219
- landcover = None; lc_band = None
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
- # Render map + capture draw ROI (only once with st_folium)
1229
- # ----------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1230
  result = st_folium(m, width=800, height=600, returned_objects=["last_active_drawing"])
1231
- roi, coords, flat_coords = None, None, None
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 = saved.get("geometry")
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().replace(year=datetime.utcnow().year-2)).strftime("%Y-%m-%d")
1295
  ndvi_ts = safe_time_series(roi, ndvi_col, "NDVI", start, end)
1296
 
1297
- # Save results
 
 
 
 
 
 
 
 
 
 
 
 
1298
  active = st.session_state.get("active_site", 0)
1299
  if "sites" in st.session_state:
1300
  site = st.session_state["sites"][active]
1301
- if roi:
1302
- try:
1303
- site["ROI"] = roi.getInfo()
1304
- except Exception:
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
- # Map snapshot
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