surtgis 0.10.0

High-performance geospatial analysis CLI
//! Handler for landscape ecology subcommands.

use anyhow::{Context, Result};
use std::collections::HashSet;
use std::time::Instant;

use surtgis_algorithms::landscape::{
    class_metrics, label_patches, landscape_metrics, patch_metrics, patches_to_csv,
};

use crate::commands::LandscapeCommands;
use crate::helpers::{done, parse_connectivity, read_dem, write_result_i32};

pub fn handle(algorithm: LandscapeCommands, compress: bool) -> Result<()> {
    match algorithm {
        LandscapeCommands::LabelPatches {
            input,
            output,
            connectivity,
        } => {
            let conn = parse_connectivity(connectivity)?;
            let raster = read_dem(&input)?;
            let start = Instant::now();
            let (labels, num_patches) =
                label_patches(&raster, conn).context("Failed to label patches")?;
            let elapsed = start.elapsed();
            write_result_i32(&labels, &output, compress)?;
            println!("{} patches found", num_patches);
            done("Label patches", &output, elapsed);
        }

        LandscapeCommands::PatchMetrics {
            input,
            output,
            connectivity,
        } => {
            let conn = parse_connectivity(connectivity)?;
            let raster = read_dem(&input)?;
            let start = Instant::now();
            let (labels, num_patches) =
                label_patches(&raster, conn).context("Failed to label patches")?;
            let patches = patch_metrics(&raster, &labels, num_patches)
                .context("Failed to compute patch metrics")?;
            let elapsed = start.elapsed();
            let csv = patches_to_csv(&patches);
            std::fs::write(&output, &csv).context("Failed to write CSV")?;
            println!(
                "{} patches, {} classes",
                patches.len(),
                patches
                    .iter()
                    .map(|p| p.class)
                    .collect::<HashSet<_>>()
                    .len()
            );
            done("Patch metrics", &output, elapsed);
        }

        LandscapeCommands::ClassMetrics {
            input,
            connectivity,
        } => {
            let conn = parse_connectivity(connectivity)?;
            let raster = read_dem(&input)?;
            let start = Instant::now();
            let (labels, num_patches) =
                label_patches(&raster, conn).context("Failed to label patches")?;
            let patches = patch_metrics(&raster, &labels, num_patches)
                .context("Failed to compute patch metrics")?;
            let cm = class_metrics(&raster, &patches).context("Failed to compute class metrics")?;
            let elapsed = start.elapsed();

            println!(
                "{:<10} {:>10} {:>10} {:>8} {:>12} {:>8} {:>10}",
                "Class", "Area(m\u{b2})", "Proportion", "Patches", "MeanArea", "AI", "Cohesion"
            );
            println!("{}", "-".repeat(78));
            for c in &cm {
                println!(
                    "{:<10} {:>10.1} {:>10.4} {:>8} {:>12.1} {:>8.1} {:>10.1}",
                    c.class,
                    c.area_m2,
                    c.proportion,
                    c.num_patches,
                    c.mean_patch_area_m2,
                    c.ai,
                    c.cohesion
                );
            }
            println!("\n  Processing time: {:.2?}", elapsed);
        }

        LandscapeCommands::LandscapeMetrics { input } => {
            let raster = read_dem(&input)?;
            let start = Instant::now();
            let lm = landscape_metrics(&raster).context("Failed to compute landscape metrics")?;
            let elapsed = start.elapsed();

            println!("Landscape Metrics:");
            println!("  SHDI (Shannon):  {:.4}", lm.shdi);
            println!("  SIDI (Simpson):  {:.4}", lm.sidi);
            println!("  Classes:         {}", lm.num_classes);
            println!("  Total cells:     {}", lm.total_cells);
            println!("  Total area:      {:.1} m\u{b2}", lm.total_area_m2);
            println!("  Processing time: {:.2?}", elapsed);
        }

        LandscapeCommands::Analyze {
            input,
            output_labels,
            output_csv,
            connectivity,
        } => {
            let conn = parse_connectivity(connectivity)?;
            let raster = read_dem(&input)?;
            let start = Instant::now();

            // 1. Label patches
            let (labels, num_patches) =
                label_patches(&raster, conn).context("Failed to label patches")?;
            println!("Patches: {}", num_patches);

            // 2. Patch metrics
            let patches = patch_metrics(&raster, &labels, num_patches)
                .context("Failed to compute patch metrics")?;

            // 3. Class metrics
            let cm = class_metrics(&raster, &patches).context("Failed to compute class metrics")?;

            println!(
                "\n{:<10} {:>10} {:>10} {:>8} {:>8} {:>10}",
                "Class", "Proportion", "Patches", "AI", "Cohesion", "MeanArea"
            );
            println!("{}", "-".repeat(66));
            for c in &cm {
                println!(
                    "{:<10} {:>10.4} {:>8} {:>8.1} {:>10.1} {:>10.1}",
                    c.class, c.proportion, c.num_patches, c.ai, c.cohesion, c.mean_patch_area_m2
                );
            }

            // 4. Landscape metrics
            let lm = landscape_metrics(&raster).context("Failed to compute landscape metrics")?;
            println!(
                "\nLandscape: SHDI={:.4}  SIDI={:.4}  classes={}",
                lm.shdi, lm.sidi, lm.num_classes
            );

            // 5. Write outputs
            if let Some(ref lp) = output_labels {
                write_result_i32(&labels, lp, compress)?;
                println!("Labels saved to: {}", lp.display());
            }
            if let Some(ref cp) = output_csv {
                let csv = patches_to_csv(&patches);
                std::fs::write(cp, &csv).context("Failed to write CSV")?;
                println!("Patch CSV saved to: {}", cp.display());
            }

            let elapsed = start.elapsed();
            println!("\n  Processing time: {:.2?}", elapsed);
        }
    }

    Ok(())
}