bhuvan / app.py
Zeel's picture
remove duplicate code
cc66d4e
import re
import json
import streamlit as st
import pandas as pd
import geopandas as gpd
import leafmap.foliumap as leafmap
from optree import tree_map
from shapely.ops import transform
import kml2geojson
from io import BytesIO, StringIO
import requests
def shape_3d_to_2d(shape):
if shape.has_z:
return transform(lambda x, y, z: (x, y), shape)
else:
return shape
def preprocess_gdf(gdf):
gdf = gdf.to_crs(epsg=7761) # epsg for Gujarat
gdf["geometry"] = gdf["geometry"].apply(shape_3d_to_2d)
gdf["geometry"] = gdf.buffer(0) # Fixes some invalid geometries
return gdf
def is_valid_polygon(geometry_gdf):
geometry = geometry_gdf.geometry.item()
return (geometry.type == "Polygon") and (not geometry.is_empty)
# wide streamlit display
st.set_page_config(layout="wide")
# Function
# Logo
# Logo
st.write(
f"""
<div style="display: flex; justify-content: space-between; align-items: center;">
<img src="https://huggingface.co/spaces/SustainabilityLabIITGN/NDVI_PERG/resolve/main/Final_IITGN-Logo-symmetric-Color.png" style="width: 10%; margin-right: auto;">
<img src="https://huggingface.co/spaces/SustainabilityLabIITGN/NDVI_PERG/resolve/main/IFS.jpg" style="width: 10%; margin-left: auto;">
</div>
""",
unsafe_allow_html=True,
)
# Title
# make title in center
# with cols[1]:
st.markdown(
f"""
<h1 style="text-align: center;">KML Viewer</h1>
""",
unsafe_allow_html=True,
)
file_url = st.query_params.get("file_url", None)
if not file_url:
file_url = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml", "shp"])
if not file_url:
st.warning(
"Please provide a KML or GeoJSON URL as a query parameter, e.g., `https://sustainabilitylabiitgn-bhuvan.hf.space/?file_url=<your_file_url>` or upload a file."
)
st.stop()
if ("file_url" in st.session_state) and ("input_gdf" in st.session_state) and (st.session_state.file_url == file_url):
# st.toast("Using cached data")
input_gdf = st.session_state.input_gdf
else:
st.session_state.file_url = file_url
print(file_url, type(file_url))
if isinstance(file_url, str):
if file_url.startswith("https://drive.google.com/file/d/"):
ID = file_url.replace("https://drive.google.com/file/d/", "").split("/")[0]
file_url = f"https://drive.google.com/uc?id={ID}"
elif file_url.startswith("https://drive.google.com/open?id="):
ID = file_url.replace("https://drive.google.com/open?id=", "")
file_url = f"https://drive.google.com/uc?id={ID}"
response = requests.get(file_url)
bytes_data = BytesIO(response.content)
string_data = response.text
else:
bytes_data = BytesIO(file_url.getvalue())
string_data = file_url.getvalue().decode("utf-8")
if string_data.startswith("<?xml"):
geojson = kml2geojson.convert(bytes_data)
features = geojson[0]["features"]
epsg = 4326
input_gdf = gpd.GeoDataFrame.from_features(features, crs=f"EPSG:{epsg}")
else:
input_gdf = gpd.read_file(bytes_data)
input_gdf = preprocess_gdf(input_gdf)
if len(input_gdf) > 1:
st.warning(f"Only the first polygon in the KML will be processed; all other geometries will be ignored.")
for i in range(len(input_gdf)):
geometry_gdf = input_gdf[input_gdf.index == i]
if is_valid_polygon(geometry_gdf):
break
else:
st.error(f"No polygon found inside KML. Please check the KML file.")
st.stop()
st.session_state.input_gdf = input_gdf
# st.toast("Data loaded and cached")
def format_fn(x):
return input_gdf.drop(columns=["geometry"]).loc[x].to_dict()
with st.expander("Advanced Controls", expanded=False):
# input_geometry_idx = st.selectbox("Select the geometry", input_gdf.index, format_func=format_fn)
map_type = st.radio(
"",
["Esri Satellite Map", "Google Hybrid Map (displays place names)", "Google Satellite Map"],
horizontal=True,
)
height = st.number_input("Map height (px)", 1, 10000, 600, 1)
# geometry_gdf = input_gdf[input_gdf.index == 0]
# def check_valid_geometry(geometry_gdf):
# geometry = geometry_gdf.geometry.item()
# if geometry.type != "Polygon":
# st.error(f"Selected geometry is of type '{geometry.type}'. Please provide a 'Polygon' geometry.")
# st.stop()
# check_valid_geometry(geometry_gdf)
m = leafmap.Map()
st.markdown(
"""
<style>
.stRadio [role=radiogroup]{
align-items: center;
justify-content: center;
}
</style>
""",
unsafe_allow_html=True,
)
if map_type == "Google Hybrid Map (displays place names)":
st.write(
"<h4><div style='text-align: center;'>Google Hybrid (displays place names)</div></h4>",
unsafe_allow_html=True,
)
m.add_basemap("HYBRID")
elif map_type == "Google Satellite Map":
st.write("<h4><div style='text-align: center;'>Google Satellite</div></h4>", unsafe_allow_html=True)
m.add_basemap("SATELLITE")
elif map_type == "Esri Satellite Map":
st.write("<h4><div style='text-align: center;'>Esri - 2024/10/10</div></h4>", unsafe_allow_html=True)
m.add_wms_layer(
"https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/WMTS/1.0.0/GoogleMapsCompatible/MapServer/tile/56450/{z}/{y}/{x}",
layers="0",
)
else:
st.error("Invalid map type")
st.stop()
m.add_gdf(
geometry_gdf.to_crs(epsg=4326),
layer_name="Geometry",
zoom_to_layer=True,
style_function=lambda x: {"color": "red", "fillOpacity": 0.0},
)
# Metrics
stats_df = pd.DataFrame(index=[0])
stats_df["Points"] = str(json.loads(geometry_gdf.to_crs(4326).to_json())["features"][0]["geometry"]["coordinates"])
stats_df["Centroid"] = geometry_gdf.centroid.to_crs(4326).item()
stats_df["Area (ha)"] = geometry_gdf.geometry.area.item() / 10000
stats_df["Perimeter (m)"] = geometry_gdf.geometry.length.item()
centroid_lon = stats_df["Centroid"].item().xy[0][0]
centroid_lat = stats_df["Centroid"].item().xy[1][0]
centroid_gdf = gpd.GeoDataFrame(geometry=[stats_df["Centroid"].item()], crs="EPSG:4326")
m.add_gdf(
centroid_gdf,
layer_name="Centroid",
style_function=lambda x: {"color": "blue", "fillOpacity": 0.0},
zoom_to_layer=False,
)
m.to_streamlit(height=height)
st.write("<h3><div style='text-align: center;'>Geometry Metrics</div></h3>", unsafe_allow_html=True)
# st.markdown(
# f"""| Metric | Value |
# | --- | --- |
# | Area (ha) | {stats_df['Area (ha)'].item():.2f} ha|
# | Perimeter (m) | {stats_df['Perimeter (m)'].item():.2f} m |"""
# unsafe_allow_html=True)
centroid_url = f"http://maps.google.com/maps?q={centroid_lat},{centroid_lon}&layer=satellite"
st.markdown(
f"""
<div style="display: flex; justify-content: center;">
<table>
<tr>
<th>Metric</th>
<th>Value</th>
</tr>
<td>Centroid</td>
<td>
({centroid_lon:.5f}, {centroid_lat:.5f})
<a href="{centroid_url}" target="_blank">
<button>View on Google Maps</button>
</a>
</td>
</tr>
<td>Area (ha)</td>
<td>{stats_df['Area (ha)'].item():.2f} ha</td>
</tr>
<tr>
<td>Perimeter (m)</td>
<td>{stats_df['Perimeter (m)'].item():.2f} m</td>
</tr>
</table>
</div>
""",
unsafe_allow_html=True,
)
print(stats_df["Points"].item())
print(type(stats_df["Points"].item()))
csv = stats_df.T.to_csv(index=True)
st.download_button("Download Geometry Metrics", csv, f"{file_url}_metrics.csv", "text/csv", use_container_width=True)
if isinstance(file_url, str):
st.markdown(
f"""
<div style="display: flex; justify-content: center;">
<a href="https://sustainabilitylabiitgn-ndvi-perg.hf.space?file_url={file_url}" target="_blank">
<button style="
background-color: #00EE00; /* Green background */
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
">
Click for NDVI Timeseries
</button>
</a>
</div>
""",
unsafe_allow_html=True,
)
# Add some space in the end
st.write(
"""
<style>
div.stButton > button {
margin: 10px;
}
</style>
""",
unsafe_allow_html=True,
)
# Add credits
st.write(
"""
<div style="display: flex; justify-content: center; align-items: center;">
<p style="text-align: left;">This tool is developed by <a href="https://sustainability-lab.github.io/">Sustainability Lab</a>, <a href="https://www.iitgn.ac.in/">IIT Gandhinagar</a> and supported by <a href="https://forests.gujarat.gov.in/">Gujarat Forest Department</a></p>""",
unsafe_allow_html=True,
)