use anyhow::{Context, Result, anyhow};
use std::path::Path;
use surtgis_algorithms::terrain::HillshadeParams;
use surtgis_core::io::read_geotiff;
use surtgis_relief::{
ColorScheme, RayShadeParams, ReliefBuilder, SunSample, WaterParams, ambient_shade,
detect_water, ray_shade, sphere_shade,
};
#[allow(clippy::too_many_arguments)]
pub fn handle(
input: &Path,
output: &Path,
colormap: &str,
sun_azimuth: f64,
sun_altitude: f64,
shadows: bool,
soft: usize,
ambient: bool,
water: bool,
z_factor: f64,
radius: usize,
) -> Result<()> {
let scheme = parse_scheme(colormap)?;
let dem = read_geotiff::<f64, _>(input, None)
.with_context(|| format!("read DEM: {}", input.display()))?;
let sphere_params = HillshadeParams {
azimuth: sun_azimuth,
altitude: sun_altitude,
z_factor,
normalized: true,
};
let sphere = sphere_shade(&dem, sphere_params).context("sphere_shade failed")?;
let mut builder = ReliefBuilder::new(&dem)
.base_colormap(scheme)
.add_shade(sphere, 0.6);
if shadows {
let params = if soft > 1 {
let low = (sun_altitude - 5.0).max(0.5);
let high = (sun_altitude + 5.0).min(89.0);
RayShadeParams::with_soft_shadow_altitude(sun_azimuth, low, high, soft)
} else {
RayShadeParams {
suns: vec![SunSample::new(sun_azimuth, sun_altitude)],
radius: 0, }
};
let params = RayShadeParams {
suns: params.suns,
radius: cast_radius(&dem),
};
let shadow = ray_shade(&dem, ¶ms).context("ray_shade failed")?;
builder = builder.add_shadow(shadow, 0.7);
}
if ambient {
let ao = ambient_shade(&dem, radius).context("ambient_shade failed")?;
builder = builder.add_ambient(ao, 0.3);
}
if water {
let mask = detect_water(&dem, &WaterParams::default()).context("detect_water failed")?;
builder = builder.add_water(mask, ColorScheme::Water);
}
let img = builder.render().context("composite render failed")?;
if let Some(parent) = output.parent()
&& !parent.as_os_str().is_empty()
&& !parent.exists()
{
std::fs::create_dir_all(parent)
.with_context(|| format!("create output dir: {}", parent.display()))?;
}
img.save_png(output)
.with_context(|| format!("write PNG: {}", output.display()))?;
tracing::info!("wrote {}", output.display());
Ok(())
}
fn parse_scheme(s: &str) -> Result<ColorScheme> {
Ok(match s.to_ascii_lowercase().as_str() {
"terrain" => ColorScheme::Terrain,
"divergent" => ColorScheme::Divergent,
"grayscale" | "greyscale" => ColorScheme::Grayscale,
"ndvi" => ColorScheme::Ndvi,
"blue-white-red" | "bwr" => ColorScheme::BlueWhiteRed,
"geomorphons" => ColorScheme::Geomorphons,
"water" => ColorScheme::Water,
"accumulation" => ColorScheme::Accumulation,
other => {
return Err(anyhow!(
"unknown colormap '{other}'. Valid: terrain, divergent, grayscale, ndvi, bwr, geomorphons, water, accumulation"
));
}
})
}
fn cast_radius(dem: &surtgis_core::raster::Raster<f64>) -> usize {
let (rows, cols) = dem.shape();
rows.max(cols)
}