"""
GeoSight Dashboard - Streamlit Application
Interactive web interface for satellite imagery analysis.
"""
import streamlit as st
import pandas as pd
import numpy as np
import folium
from streamlit_folium import st_folium
from datetime import datetime, timedelta
import httpx
import os
# Page configuration
st.set_page_config(
page_title="GeoSight - Satellite Imagery Analysis",
page_icon="π°οΈ",
layout="wide",
initial_sidebar_state="expanded",
)
# Custom CSS
st.markdown("""
<style>
.main-header {
font-size: 2.5rem;
font-weight: bold;
color: #1E3A5F;
margin-bottom: 0;
}
.sub-header {
font-size: 1.2rem;
color: #666;
margin-top: 0;
}
.metric-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
border-radius: 10px;
color: white;
}
.stButton>button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 5px;
padding: 10px 25px;
}
</style>
""", unsafe_allow_html=True)
# API URL
API_URL = os.environ.get("GEOSIGHT_API_URL", "http://localhost:8000")
def main():
# Header
st.markdown('<p class="main-header">π°οΈ GeoSight</p>', unsafe_allow_html=True)
st.markdown('<p class="sub-header">Satellite Imagery Analysis Platform</p>', unsafe_allow_html=True)
st.divider()
# Sidebar
with st.sidebar:
st.image("https://via.placeholder.com/200x80?text=GeoSight", use_column_width=True)
st.header("π Location Settings")
location_method = st.radio(
"Select location by:",
["π Search", "π Coordinates", "πΊοΈ Click on Map"]
)
if location_method == "π Search":
location_name = st.text_input("Location Name", "Mumbai, India")
latitude, longitude = None, None
elif location_method == "π Coordinates":
col1, col2 = st.columns(2)
with col1:
latitude = st.number_input("Latitude", -90.0, 90.0, 19.076)
with col2:
longitude = st.number_input("Longitude", -180.0, 180.0, 72.877)
location_name = None
else:
latitude, longitude = 19.076, 72.877
location_name = None
st.header("π
Date Range")
col1, col2 = st.columns(2)
with col1:
start_date = st.date_input(
"Start Date",
datetime.now() - timedelta(days=30)
)
with col2:
end_date = st.date_input(
"End Date",
datetime.now()
)
st.header("βοΈ Analysis Parameters")
radius_km = st.slider("Analysis Radius (km)", 1, 50, 10)
st.divider()
st.caption("Powered by Sentinel-2 & Landsat")
# Main content tabs
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"πΊοΈ Map View",
"πΏ Vegetation (NDVI)",
"ποΈ Land Cover",
"π Change Detection",
"π Reports"
])
# Tab 1: Map View
with tab1:
st.header("Interactive Map")
col1, col2 = st.columns([2, 1])
with col1:
# Create map
m = folium.Map(
location=[latitude or 19.076, longitude or 72.877],
zoom_start=10,
tiles="OpenStreetMap"
)
# Add satellite tile layer
folium.TileLayer(
tiles="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
attr="Esri",
name="Satellite",
).add_to(m)
# Add marker
folium.Marker(
[latitude or 19.076, longitude or 72.877],
popup="Analysis Location",
icon=folium.Icon(color="red", icon="info-sign"),
).add_to(m)
# Add circle for radius
folium.Circle(
[latitude or 19.076, longitude or 72.877],
radius=radius_km * 1000,
color="blue",
fill=True,
fillOpacity=0.1,
).add_to(m)
folium.LayerControl().add_to(m)
map_data = st_folium(m, width=700, height=500)
with col2:
st.subheader("π Quick Stats")
st.metric("Analysis Area", f"{(radius_km * 2) ** 2:.1f} kmΒ²")
st.metric("Date Range", f"{(end_date - start_date).days} days")
if st.button("π Search Imagery", use_container_width=True):
with st.spinner("Searching..."):
st.success("Found 12 cloud-free images!")
# Tab 2: NDVI Analysis
with tab2:
st.header("πΏ Vegetation Analysis (NDVI)")
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("NDVI Map")
if st.button("Calculate NDVI", key="ndvi_btn"):
with st.spinner("Processing satellite imagery..."):
# Generate demo NDVI data
demo_ndvi = generate_demo_ndvi()
st.image(demo_ndvi, caption="NDVI Visualization", use_column_width=True)
with col2:
st.subheader("Statistics")
# Demo statistics
st.metric("Mean NDVI", "0.45", "+0.05")
st.metric("Healthy Vegetation", "62%", "+3%")
st.metric("Sparse Vegetation", "25%", "-2%")
# Legend
st.subheader("Legend")
st.markdown("""
- π’ **> 0.6**: Dense, healthy vegetation
- π‘ **0.3 - 0.6**: Moderate vegetation
- π **0.1 - 0.3**: Sparse vegetation
- π΄ **< 0.1**: Non-vegetation
""")
# Tab 3: Land Cover
with tab3:
st.header("ποΈ Land Cover Classification")
col1, col2 = st.columns([1, 1])
with col1:
if st.button("Classify Land Cover", key="lc_btn"):
with st.spinner("Running ML classification..."):
demo_lc = generate_demo_landcover()
st.image(demo_lc, caption="Land Cover Map", use_column_width=True)
with col2:
st.subheader("Classification Results")
# Demo pie chart
import plotly.express as px
data = {
"Class": ["Forest", "Urban", "Cropland", "Water", "Barren"],
"Percentage": [35, 25, 20, 12, 8]
}
fig = px.pie(data, values="Percentage", names="Class", hole=0.4)
fig.update_layout(height=300)
st.plotly_chart(fig, use_container_width=True)
# Tab 4: Change Detection
with tab4:
st.header("π Change Detection")
col1, col2, col3 = st.columns(3)
with col1:
st.subheader("Before")
st.image(generate_demo_rgb(seed=42), caption=f"Image: {start_date}")
with col2:
st.subheader("After")
st.image(generate_demo_rgb(seed=43), caption=f"Image: {end_date}")
with col3:
st.subheader("Changes")
if st.button("Detect Changes", key="cd_btn"):
st.image(generate_demo_change(), caption="Change Map")
st.subheader("Change Statistics")
col1, col2, col3 = st.columns(3)
col1.metric("Changed Area", "2.3 kmΒ²", "15%")
col2.metric("New Construction", "0.8 kmΒ²")
col3.metric("Vegetation Loss", "0.5 kmΒ²")
# Tab 5: Reports
with tab5:
st.header("π Generate Report")
report_title = st.text_input("Report Title", f"Analysis Report - {location_name or 'Custom Location'}")
analyses = st.multiselect(
"Include Analyses",
["NDVI", "NDWI", "Land Cover", "Change Detection"],
default=["NDVI", "Land Cover"]
)
report_format = st.selectbox("Output Format", ["PDF", "HTML", "Markdown"])
if st.button("Generate Report", key="report_btn"):
with st.spinner("Generating comprehensive report..."):
st.success("Report generated successfully!")
st.download_button(
label="π₯ Download Report",
data="Demo report content",
file_name=f"geosight_report_{datetime.now().strftime('%Y%m%d')}.md",
mime="text/markdown"
)
def generate_demo_ndvi():
"""Generate demo NDVI visualization."""
np.random.seed(42)
size = 256
x = np.linspace(0, 4 * np.pi, size)
y = np.linspace(0, 4 * np.pi, size)
xx, yy = np.meshgrid(x, y)
ndvi = 0.4 + 0.3 * np.sin(xx) * np.cos(yy) + 0.1 * np.random.randn(size, size)
ndvi = np.clip(ndvi, -1, 1)
# Convert to RGB colormap
rgb = np.zeros((size, size, 3), dtype=np.uint8)
rgb[:, :, 1] = ((ndvi + 1) / 2 * 255).astype(np.uint8) # Green channel
rgb[:, :, 0] = ((1 - (ndvi + 1) / 2) * 100).astype(np.uint8) # Red channel
return rgb
def generate_demo_landcover():
"""Generate demo land cover visualization."""
np.random.seed(42)
size = 256
colors = {
0: [34, 139, 34], # Forest - green
1: [220, 20, 60], # Urban - red
2: [255, 215, 0], # Cropland - gold
3: [65, 105, 225], # Water - blue
4: [222, 184, 135], # Barren - tan
}
# Create smooth classification
x = np.linspace(0, 4, size)
y = np.linspace(0, 4, size)
xx, yy = np.meshgrid(x, y)
noise = np.sin(xx) * np.cos(yy) + 0.3 * np.random.randn(size, size)
classification = ((noise - noise.min()) / (noise.max() - noise.min()) * 4).astype(int)
rgb = np.zeros((size, size, 3), dtype=np.uint8)
for class_id, color in colors.items():
mask = classification == class_id
rgb[mask] = color
return rgb
def generate_demo_rgb(seed=42):
"""Generate demo RGB satellite image."""
np.random.seed(seed)
size = 256
rgb = np.zeros((size, size, 3), dtype=np.uint8)
rgb[:, :, 0] = 100 + np.random.randint(0, 50, (size, size))
rgb[:, :, 1] = 120 + np.random.randint(0, 60, (size, size))
rgb[:, :, 2] = 80 + np.random.randint(0, 40, (size, size))
return rgb
def generate_demo_change():
"""Generate demo change detection visualization."""
np.random.seed(123)
size = 256
rgb = np.ones((size, size, 3), dtype=np.uint8) * 255
# Add some red (decrease) and green (increase) patches
for _ in range(10):
x, y = np.random.randint(20, size-20, 2)
r = np.random.randint(10, 30)
yy, xx = np.ogrid[:size, :size]
mask = (xx - x)**2 + (yy - y)**2 < r**2
if np.random.random() > 0.5:
rgb[mask, 0] = 255
rgb[mask, 1] = 100
rgb[mask, 2] = 100
else:
rgb[mask, 0] = 100
rgb[mask, 1] = 255
rgb[mask, 2] = 100
return rgb
if __name__ == "__main__":
main()