"""
Land cover classification tool.
"""
from typing import Optional, Literal
import numpy as np
import structlog
from geosight.tools.geocoding import resolve_location
from geosight.services.sentinel_hub import SentinelHubService
from geosight.models.land_cover import LandCoverClassifier, LAND_COVER_CLASSES
from geosight.utils.visualization import create_classification_map, array_to_base64_png
from geosight.utils.geo import create_bbox_from_point
logger = structlog.get_logger(__name__)
async def detect_land_cover(
date: str,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
location_name: Optional[str] = None,
radius_km: float = 10.0,
model: Literal["eurosat", "deepglobe", "custom"] = "eurosat",
) -> dict:
"""Classify land cover using machine learning."""
try:
lat, lon, display_name = await resolve_location(
location_name=location_name,
latitude=latitude,
longitude=longitude,
)
logger.info("detecting_land_cover", location=display_name, date=date)
bbox = create_bbox_from_point(lat, lon, radius_km)
# Generate classification
classifier = LandCoverClassifier(model_name=model)
size = min(int(radius_km * 20), 512)
dummy_image = np.random.rand(size, size, 3)
classification, confidence = await classifier.classify(dummy_image)
# Calculate statistics
stats = calculate_land_cover_stats(classification, radius_km)
# Generate visualization
class_colors = {i: info["color"] for i, info in LAND_COVER_CLASSES.items()}
colored_map = create_classification_map(classification, class_colors)
image_base64 = array_to_base64_png(colored_map)
# Build summary
sorted_classes = sorted(
stats["class_percentages"].items(),
key=lambda x: x[1],
reverse=True,
)
class_lines = []
for class_name, percentage in sorted_classes:
if percentage > 0.5:
area = percentage / 100 * stats["total_area_km2"]
class_lines.append(f" • {class_name}: {percentage:.1f}% ({area:.2f} km²)")
summary = f"""🗺️ **Land Cover Classification**
📍 **Location:** {display_name}
📅 **Date:** {date}
📐 **Area:** {stats['total_area_km2']:.1f} km²
🤖 **Model:** {model.upper()}
**Land Cover Distribution:**
{chr(10).join(class_lines)}
**Dominant Class:** {sorted_classes[0][0]} ({sorted_classes[0][1]:.1f}%)
"""
return {
"summary": summary,
"statistics": stats,
"image_base64": image_base64,
"image_mime_type": "image/png",
"location": {"name": display_name, "latitude": lat, "longitude": lon},
}
except ValueError as e:
return {"summary": f"❌ **Error:** {str(e)}", "error": str(e)}
except Exception as e:
logger.error("land_cover_error", error=str(e), exc_info=True)
return {"summary": f"❌ **Classification failed:** {str(e)}", "error": str(e)}
def calculate_land_cover_stats(classification: np.ndarray, radius_km: float) -> dict:
"""Calculate land cover statistics."""
total_pixels = classification.size
total_area = (radius_km * 2) ** 2
class_percentages = {}
class_areas = {}
for class_id, info in LAND_COVER_CLASSES.items():
count = np.sum(classification == class_id)
percentage = count / total_pixels * 100
area = percentage / 100 * total_area
class_percentages[info["name"]] = float(percentage)
class_areas[info["name"]] = float(area)
return {
"total_area_km2": float(total_area),
"total_pixels": int(total_pixels),
"class_percentages": class_percentages,
"class_areas_km2": class_areas,
"num_classes_present": sum(1 for p in class_percentages.values() if p > 0),
}