Update app.py
Browse files
app.py
CHANGED
|
@@ -164,8 +164,9 @@ def soil_recognizer_page():
|
|
| 164 |
st.write(f"{cls}: {score:.2%}")
|
| 165 |
|
| 166 |
if st.button("Save to site"):
|
| 167 |
-
|
| 168 |
-
|
|
|
|
| 169 |
save_active_site(site)
|
| 170 |
st.success("Saved prediction to active site memory.")
|
| 171 |
|
|
@@ -595,43 +596,49 @@ def build_full_geotech_pdf(site: Dict[str, Any], filename: str, include_map_imag
|
|
| 595 |
if "sites" not in st.session_state:
|
| 596 |
# initialize with a default site
|
| 597 |
st.session_state["sites"] = [{
|
| 598 |
-
"
|
|
|
|
|
|
|
| 599 |
"Soil Recognizer Confidence": None,
|
| 600 |
-
"Site
|
| 601 |
-
"
|
| 602 |
-
"Site ID": 0,
|
| 603 |
-
"Coordinates":"",
|
| 604 |
"lat": None,
|
| 605 |
"lon": None,
|
| 606 |
-
"Project Description":"",
|
| 607 |
-
|
| 608 |
-
"
|
| 609 |
-
"
|
| 610 |
-
"
|
|
|
|
|
|
|
| 611 |
"Field Investigation": [],
|
| 612 |
"Laboratory Results": [],
|
| 613 |
"GSD": None,
|
| 614 |
"USCS": None,
|
| 615 |
"AASHTO": None,
|
| 616 |
"GI": None,
|
|
|
|
| 617 |
"Load Bearing Capacity": None,
|
| 618 |
"Skin Shear Strength": None,
|
| 619 |
"Relative Compaction": None,
|
| 620 |
"Rate of Consolidation": None,
|
| 621 |
-
"Nature of Construction": None
|
| 622 |
-
|
| 623 |
-
"
|
| 624 |
-
"
|
| 625 |
-
"
|
| 626 |
-
"
|
|
|
|
|
|
|
| 627 |
"map_snapshot": None,
|
|
|
|
| 628 |
"chat_history": [],
|
| 629 |
"classifier_inputs": {},
|
| 630 |
"classifier_decision": None,
|
| 631 |
"report_convo_state": 0,
|
| 632 |
"report_missing_fields": [],
|
| 633 |
"report_answers": {}
|
| 634 |
-
|
| 635 |
|
| 636 |
if "active_site" not in st.session_state:
|
| 637 |
st.session_state["active_site"] = 0
|
|
@@ -706,31 +713,39 @@ with st.sidebar:
|
|
| 706 |
"Site Name": new_site_name.strip(),
|
| 707 |
"Project Name": "Project - " + new_site_name.strip(),
|
| 708 |
"Site ID": idx,
|
| 709 |
-
"
|
|
|
|
|
|
|
| 710 |
"lat": None,
|
| 711 |
"lon": None,
|
| 712 |
-
"Project Description":"",
|
| 713 |
-
|
| 714 |
-
"
|
| 715 |
-
"
|
| 716 |
-
"
|
|
|
|
|
|
|
| 717 |
"Field Investigation": [],
|
| 718 |
"Laboratory Results": [],
|
| 719 |
"GSD": None,
|
| 720 |
"USCS": None,
|
| 721 |
"AASHTO": None,
|
| 722 |
"GI": None,
|
|
|
|
| 723 |
"Load Bearing Capacity": None,
|
| 724 |
"Skin Shear Strength": None,
|
| 725 |
"Relative Compaction": None,
|
| 726 |
"Rate of Consolidation": None,
|
| 727 |
-
"Nature of Construction": None
|
| 728 |
-
|
| 729 |
-
"
|
| 730 |
-
"
|
| 731 |
-
"
|
| 732 |
-
"
|
|
|
|
|
|
|
| 733 |
"map_snapshot": None,
|
|
|
|
| 734 |
"chat_history": [],
|
| 735 |
"classifier_inputs": {},
|
| 736 |
"classifier_decision": None,
|
|
@@ -738,6 +753,7 @@ with st.sidebar:
|
|
| 738 |
"report_missing_fields": [],
|
| 739 |
"report_answers": {}
|
| 740 |
})
|
|
|
|
| 741 |
st.success(f"Site '{new_site_name.strip()}' created.")
|
| 742 |
st.session_state["active_site"] = idx
|
| 743 |
st.rerun()
|
|
@@ -993,47 +1009,125 @@ def ocr_page():
|
|
| 993 |
def locator_page():
|
| 994 |
st.header("🌍 Locator — Select Area of Interest")
|
| 995 |
site = st.session_state["sites"][st.session_state["active_site"]]
|
|
|
|
| 996 |
st.markdown("You can enter coordinates manually or draw/upload a GeoJSON boundary (draw-mode not available in this minimal example).")
|
|
|
|
| 997 |
lat = st.number_input("Latitude", value=site.get("lat") or 0.0, format="%.6f", key="locator_lat")
|
| 998 |
lon = st.number_input("Longitude", value=site.get("lon") or 0.0, format="%.6f", key="locator_lon")
|
| 999 |
site["lat"] = lat; site["lon"] = lon
|
|
|
|
| 1000 |
if st.button("Fetch Earth Data (EE)"):
|
| 1001 |
if not EE_AVAILABLE:
|
| 1002 |
-
st.error("Earth Engine/Geemap not available in this environment. Please add geemap & earthengine-api to requirements.")
|
| 1003 |
else:
|
| 1004 |
try:
|
| 1005 |
-
# Initialize
|
| 1006 |
if not ee.data._credentials:
|
| 1007 |
-
|
| 1008 |
-
ee_key_json
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
| 1024 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1025 |
m.add_basemap("SATELLITE")
|
| 1026 |
-
|
| 1027 |
-
|
| 1028 |
-
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1037 |
except Exception as e:
|
| 1038 |
st.error(f"Earth Engine error: {e}")
|
| 1039 |
|
|
@@ -1182,7 +1276,7 @@ page = st.session_state["page"]
|
|
| 1182 |
selected = option_menu(
|
| 1183 |
None,
|
| 1184 |
["Home","Soil recognizer","Classifier","GSD","OCR","Locator","RAG","Reports"],
|
| 1185 |
-
icons=["house","
|
| 1186 |
menu_icon="cast",
|
| 1187 |
default_index=["Home","Soil recognizer","Classifier","GSD","OCR","Locator","RAG","Reports"].index(page) if page in ["Home","Soil recognizer","Classifier","GSD","OCR","Locator","RAG","Reports"] else 0,
|
| 1188 |
orientation="horizontal",
|
|
|
|
| 164 |
st.write(f"{cls}: {score:.2%}")
|
| 165 |
|
| 166 |
if st.button("Save to site"):
|
| 167 |
+
# Save predicted soil class into Soil Profile field
|
| 168 |
+
st.session_state["sites"][st.session_state["active_site"]]["Soil Class"] = predicted_class
|
| 169 |
+
st.session_state["sites"][st.session_state["active_site"]]"Soil Recognizer Confidence"] = confidence_scores[predicted_class]
|
| 170 |
save_active_site(site)
|
| 171 |
st.success("Saved prediction to active site memory.")
|
| 172 |
|
|
|
|
| 596 |
if "sites" not in st.session_state:
|
| 597 |
# initialize with a default site
|
| 598 |
st.session_state["sites"] = [{
|
| 599 |
+
"Site Name":"home",
|
| 600 |
+
"Project Name": "Project ",
|
| 601 |
+
"Soil Class": None,
|
| 602 |
"Soil Recognizer Confidence": None,
|
| 603 |
+
"Site ID": idx,
|
| 604 |
+
"Coordinates": "",
|
|
|
|
|
|
|
| 605 |
"lat": None,
|
| 606 |
"lon": None,
|
| 607 |
+
"Project Description": "",
|
| 608 |
+
# Site Characterization
|
| 609 |
+
"Topography": None, # manual / descriptive topo entry
|
| 610 |
+
"Drainage": None, # manual drainage notes
|
| 611 |
+
"Current Land Use": None, # can be linked to Environmental Data
|
| 612 |
+
"Regional Geology": None, # manual geology notes
|
| 613 |
+
# Investigations & Lab
|
| 614 |
"Field Investigation": [],
|
| 615 |
"Laboratory Results": [],
|
| 616 |
"GSD": None,
|
| 617 |
"USCS": None,
|
| 618 |
"AASHTO": None,
|
| 619 |
"GI": None,
|
| 620 |
+
# Geotechnical Parameters
|
| 621 |
"Load Bearing Capacity": None,
|
| 622 |
"Skin Shear Strength": None,
|
| 623 |
"Relative Compaction": None,
|
| 624 |
"Rate of Consolidation": None,
|
| 625 |
+
"Nature of Construction": None
|
| 626 |
+
# Earth Engine Data
|
| 627 |
+
"Soil Profile": None, # SoilGrids (clay/sand/silt etc.)
|
| 628 |
+
"Flood Data": None, # JRC Global Surface Water
|
| 629 |
+
"Seismic Data": None, # GSHAP Seismic hazard
|
| 630 |
+
"Environmental Data": None, # Landcover, forest loss, urban
|
| 631 |
+
"Topo Data": None, # SRTM DEM elevation
|
| 632 |
+
# Map & Visualization
|
| 633 |
"map_snapshot": None,
|
| 634 |
+
# AI / Reporting
|
| 635 |
"chat_history": [],
|
| 636 |
"classifier_inputs": {},
|
| 637 |
"classifier_decision": None,
|
| 638 |
"report_convo_state": 0,
|
| 639 |
"report_missing_fields": [],
|
| 640 |
"report_answers": {}
|
| 641 |
+
})
|
| 642 |
|
| 643 |
if "active_site" not in st.session_state:
|
| 644 |
st.session_state["active_site"] = 0
|
|
|
|
| 713 |
"Site Name": new_site_name.strip(),
|
| 714 |
"Project Name": "Project - " + new_site_name.strip(),
|
| 715 |
"Site ID": idx,
|
| 716 |
+
"Soil Class": None,
|
| 717 |
+
"Soil Recognizer Confidence": None,
|
| 718 |
+
"Coordinates": "",
|
| 719 |
"lat": None,
|
| 720 |
"lon": None,
|
| 721 |
+
"Project Description": "",
|
| 722 |
+
# Site Characterization
|
| 723 |
+
"Topography": None, # manual / descriptive topo entry
|
| 724 |
+
"Drainage": None, # manual drainage notes
|
| 725 |
+
"Current Land Use": None, # can be linked to Environmental Data
|
| 726 |
+
"Regional Geology": None, # manual geology notes
|
| 727 |
+
# Investigations & Lab
|
| 728 |
"Field Investigation": [],
|
| 729 |
"Laboratory Results": [],
|
| 730 |
"GSD": None,
|
| 731 |
"USCS": None,
|
| 732 |
"AASHTO": None,
|
| 733 |
"GI": None,
|
| 734 |
+
# Geotechnical Parameters
|
| 735 |
"Load Bearing Capacity": None,
|
| 736 |
"Skin Shear Strength": None,
|
| 737 |
"Relative Compaction": None,
|
| 738 |
"Rate of Consolidation": None,
|
| 739 |
+
"Nature of Construction": None
|
| 740 |
+
# Earth Engine Data
|
| 741 |
+
"Soil Profile": None, # SoilGrids (clay/sand/silt etc.)
|
| 742 |
+
"Flood Data": None, # JRC Global Surface Water
|
| 743 |
+
"Seismic Data": None, # GSHAP Seismic hazard
|
| 744 |
+
"Environmental Data": None, # Landcover, forest loss, urban
|
| 745 |
+
"Topo Data": None, # SRTM DEM elevation
|
| 746 |
+
# Map & Visualization
|
| 747 |
"map_snapshot": None,
|
| 748 |
+
# AI / Reporting
|
| 749 |
"chat_history": [],
|
| 750 |
"classifier_inputs": {},
|
| 751 |
"classifier_decision": None,
|
|
|
|
| 753 |
"report_missing_fields": [],
|
| 754 |
"report_answers": {}
|
| 755 |
})
|
| 756 |
+
|
| 757 |
st.success(f"Site '{new_site_name.strip()}' created.")
|
| 758 |
st.session_state["active_site"] = idx
|
| 759 |
st.rerun()
|
|
|
|
| 1009 |
def locator_page():
|
| 1010 |
st.header("🌍 Locator — Select Area of Interest")
|
| 1011 |
site = st.session_state["sites"][st.session_state["active_site"]]
|
| 1012 |
+
|
| 1013 |
st.markdown("You can enter coordinates manually or draw/upload a GeoJSON boundary (draw-mode not available in this minimal example).")
|
| 1014 |
+
|
| 1015 |
lat = st.number_input("Latitude", value=site.get("lat") or 0.0, format="%.6f", key="locator_lat")
|
| 1016 |
lon = st.number_input("Longitude", value=site.get("lon") or 0.0, format="%.6f", key="locator_lon")
|
| 1017 |
site["lat"] = lat; site["lon"] = lon
|
| 1018 |
+
|
| 1019 |
if st.button("Fetch Earth Data (EE)"):
|
| 1020 |
if not EE_AVAILABLE:
|
| 1021 |
+
st.error("Earth Engine/Geemap not available in this environment. Please add `geemap` & `earthengine-api` to requirements.txt.")
|
| 1022 |
else:
|
| 1023 |
try:
|
| 1024 |
+
# Initialize Earth Engine with service account if not already
|
| 1025 |
if not ee.data._credentials:
|
| 1026 |
+
ee_key_json = os.environ.get("EARTHENGINE_TOKEN")
|
| 1027 |
+
if ee_key_json:
|
| 1028 |
+
token_dict = json.loads(ee_key_json)
|
| 1029 |
+
ee.Initialize(credentials=ee.ServiceAccountCredentials(
|
| 1030 |
+
email=token_dict["client_email"],
|
| 1031 |
+
key_data=json.dumps(token_dict)
|
| 1032 |
+
))
|
| 1033 |
+
else:
|
| 1034 |
+
st.error("No Earth Engine credentials found.")
|
| 1035 |
+
return
|
| 1036 |
+
|
| 1037 |
+
st.info("Querying Earth Engine datasets... (this may take a few seconds)")
|
| 1038 |
+
|
| 1039 |
+
# ----------------------------
|
| 1040 |
+
# DEM / Topography (SRTM)
|
| 1041 |
+
# ----------------------------
|
| 1042 |
+
dem = ee.Image("USGS/SRTMGL1_003")
|
| 1043 |
+
elevation = dem.sample(region=ee.Geometry.Point([lon, lat]), scale=30).first().get("elevation").getInfo()
|
| 1044 |
+
site["Topo Data"] = f"Elevation: {elevation:.1f} m (from SRTM DEM)"
|
| 1045 |
+
|
| 1046 |
+
# ----------------------------
|
| 1047 |
+
# Flood (JRC Global Surface Water)
|
| 1048 |
+
# ----------------------------
|
| 1049 |
+
gsw = ee.Image("JRC/GSW1_4/GlobalSurfaceWater").select("occurrence")
|
| 1050 |
+
flood_val = gsw.sample(region=ee.Geometry.Point([lon, lat]), scale=30).first().get("occurrence").getInfo()
|
| 1051 |
+
site["Flood Data"] = f"Flood occurrence probability: {flood_val:.1f}%"
|
| 1052 |
+
|
| 1053 |
+
# ----------------------------
|
| 1054 |
+
# Soil (SoilGrids250m from ISRIC)
|
| 1055 |
+
# ----------------------------
|
| 1056 |
+
soil = ee.Image("ISRIC/SoilGrids/2017/original").select("clay_0-5cm_mean")
|
| 1057 |
+
soil_val = soil.sample(region=ee.Geometry.Point([lon, lat]), scale=250).first().get("clay_0-5cm_mean").getInfo()
|
| 1058 |
+
site["Soil Profile"] = f"Surface clay content: {soil_val:.1f} g/kg (SoilGrids)"
|
| 1059 |
+
|
| 1060 |
+
# ----------------------------
|
| 1061 |
+
# Seismic (GSHAP Global Seismic Hazard)
|
| 1062 |
+
# ----------------------------
|
| 1063 |
+
seismic = ee.Image("SEDAC/GSHAPSeismicHazard")
|
| 1064 |
+
seis_val = seismic.sample(region=ee.Geometry.Point([lon, lat]), scale=1000).first().get("gshap").getInfo()
|
| 1065 |
+
site["Seismic Data"] = f"Seismic hazard (PGA, g): {seis_val:.3f}"
|
| 1066 |
+
|
| 1067 |
+
# ----------------------------
|
| 1068 |
+
# Environmental (Landcover, Deforestation, Urbanization)
|
| 1069 |
+
# ----------------------------
|
| 1070 |
+
|
| 1071 |
+
# 1. ESA WorldCover 2020 landcover class
|
| 1072 |
+
landcover = ee.ImageCollection("ESA/WorldCover/v200").first()
|
| 1073 |
+
lc_val = landcover.sample(region=ee.Geometry.Point([lon, lat]), scale=10).first().get("Map").getInfo()
|
| 1074 |
+
landcover_dict = {
|
| 1075 |
+
10: "Tree Cover",
|
| 1076 |
+
20: "Shrubland",
|
| 1077 |
+
30: "Grassland",
|
| 1078 |
+
40: "Cropland",
|
| 1079 |
+
50: "Built-up",
|
| 1080 |
+
60: "Bare / Sparse vegetation",
|
| 1081 |
+
70: "Snow and Ice",
|
| 1082 |
+
80: "Permanent Water",
|
| 1083 |
+
90: "Herbaceous Wetland",
|
| 1084 |
+
95: "Mangroves",
|
| 1085 |
+
100: "Moss & Lichen"
|
| 1086 |
+
}
|
| 1087 |
+
lc_class = landcover_dict.get(lc_val, "Unknown")
|
| 1088 |
+
|
| 1089 |
+
# 2. Hansen Forest Loss (2001–2021)
|
| 1090 |
+
forest = ee.Image("UMD/hansen/global_forest_change_2021_v1_9").select("loss")
|
| 1091 |
+
forest_loss = forest.sample(region=ee.Geometry.Point([lon, lat]), scale=30).first().get("loss").getInfo()
|
| 1092 |
+
forest_status = "Recent forest loss detected" if forest_loss == 1 else "No forest loss detected"
|
| 1093 |
+
|
| 1094 |
+
# 3. Urban footprint (GHSL built-up)
|
| 1095 |
+
urban = ee.Image("JRC/GHSL/P2016/BUILT_LDSMT_GLOBE_V1").select("built")
|
| 1096 |
+
urban_val = urban.sample(region=ee.Geometry.Point([lon, lat]), scale=250).first().get("built").getInfo()
|
| 1097 |
+
urban_status = "Urban area" if urban_val == 1 else "Non-urban area"
|
| 1098 |
+
|
| 1099 |
+
# Combine environmental info
|
| 1100 |
+
site["Environmental Data"] = (
|
| 1101 |
+
f"Landcover: {lc_class}; "
|
| 1102 |
+
f"{forest_status};
|
| 1103 |
+
f"{urban_status}."
|
| 1104 |
+
)
|
| 1105 |
+
|
| 1106 |
+
|
| 1107 |
+
# ----------------------------
|
| 1108 |
+
# Map Display
|
| 1109 |
+
# ----------------------------
|
| 1110 |
+
m = geemap.Map(center=[lat, lon], zoom=8)
|
| 1111 |
m.add_basemap("SATELLITE")
|
| 1112 |
+
|
| 1113 |
+
# Add layers with visualization params
|
| 1114 |
+
m.addLayer(dem, {"min": 0, "max": 3000, "palette": ["blue", "green", "brown", "white"]}, "DEM")
|
| 1115 |
+
m.addLayer(gsw, {"min": 0, "max": 100, "palette": ["white", "blue"]}, "Flood Occurrence")
|
| 1116 |
+
m.addLayer(soil, {"min": 0, "max": 600, "palette": ["yellow", "brown", "red"]}, "Soil Clay (0-5cm)")
|
| 1117 |
+
m.addLayer(seismic, {"min": 0, "max": 1, "palette": ["green", "yellow", "red"]}, "Seismic Hazard")
|
| 1118 |
+
m.addLayer(landcover, {"min": 10, "max": 100, "palette": ["006400","ffbb22","ffff4c","f096ff","fa0000","b4b4b4","f0f0f0","0064c8","0096a0","00cf75","fae6a0"]}, "Landcover")
|
| 1119 |
+
m.addLayer(forest, {"palette":["white","green"]}, "Forest Loss")
|
| 1120 |
+
m.addLayer(urban, {"min":0,"max":1,"palette":["white","black"]}, "Urban Footprint")
|
| 1121 |
+
|
| 1122 |
+
m.addLayerControl()
|
| 1123 |
+
|
| 1124 |
+
# Save to /tmp and embed in Streamlit
|
| 1125 |
+
map_file = "/tmp/locator_map.html"
|
| 1126 |
+
m.to_html(map_file)
|
| 1127 |
+
st.components.v1.html(open(map_file, "r").read(), height=600)
|
| 1128 |
+
|
| 1129 |
+
st.success("✅ Earth Engine data fetched and saved to site!")
|
| 1130 |
+
|
| 1131 |
except Exception as e:
|
| 1132 |
st.error(f"Earth Engine error: {e}")
|
| 1133 |
|
|
|
|
| 1276 |
selected = option_menu(
|
| 1277 |
None,
|
| 1278 |
["Home","Soil recognizer","Classifier","GSD","OCR","Locator","RAG","Reports"],
|
| 1279 |
+
icons=["house","chart","journal-code","bar-chart","camera","geo-alt","robot","file-earmark-text"],
|
| 1280 |
menu_icon="cast",
|
| 1281 |
default_index=["Home","Soil recognizer","Classifier","GSD","OCR","Locator","RAG","Reports"].index(page) if page in ["Home","Soil recognizer","Classifier","GSD","OCR","Locator","RAG","Reports"] else 0,
|
| 1282 |
orientation="horizontal",
|