use crate::io::results::SimulationResults;
use anyhow::{Context, Result};
use chrono::NaiveDateTime;
use netcdf::{self, FileMut};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
pub fn init_netcdf_output(
output_dir: PathBuf,
filename: &str,
_num_flowpaths: usize,
timesteps: Vec<f64>,
reference_time: &NaiveDateTime,
) -> Result<Arc<Mutex<FileMut>>> {
let mut file = netcdf::create(output_dir.join(filename))
.with_context(|| format!("Failed to create NetCDF file: {}", filename))?;
file.add_dimension("feature_id", 0)
.context("Failed to add feature_id dimension")?;
file.add_dimension("time", timesteps.len())
.context("Failed to add time dimension")?;
let mut time_var = file
.add_variable::<f64>("time", &["time"])
.context("Failed to add time variable")?;
time_var.put_attribute("_FillValue", -9999.0)?;
time_var.put_attribute("long_name", "valid output time")?;
time_var.put_attribute("standard_name", "time")?;
time_var.put_attribute(
"units",
format!(
"seconds since {}",
reference_time.format("%Y-%m-%d %H:%M:%S")
),
)?;
time_var.put_attribute("missing_value", -9999.0)?;
time_var
.put_values(×teps, ..)
.context("Failed to write time values")?;
let mut feature_var = file
.add_variable::<i64>("feature_id", &["feature_id"])
.context("Failed to add feature_id variable")?;
feature_var.put_attribute("long_name", "Segment ID")?;
let mut flow_var = file
.add_variable::<f32>("flow", &["feature_id", "time"])
.context("Failed to add flow variable")?;
flow_var.put_attribute("_FillValue", -9999.0f32)?;
flow_var.put_attribute("long_name", "Flow")?;
flow_var.put_attribute("units", "m3 s-1")?;
flow_var.put_attribute("missing_value", -9999.0f32)?;
let mut velocity_var = file
.add_variable::<f32>("velocity", &["feature_id", "time"])
.context("Failed to add velocity variable")?;
velocity_var.put_attribute("_FillValue", -9999.0f32)?;
velocity_var.put_attribute("long_name", "Velocity")?;
velocity_var.put_attribute("units", "m/s")?;
velocity_var.put_attribute("missing_value", -9999.0f32)?;
let mut depth_var = file
.add_variable::<f32>("depth", &["feature_id", "time"])
.context("Failed to add depth variable")?;
depth_var.put_attribute("_FillValue", -9999.0f32)?;
depth_var.put_attribute("long_name", "Depth")?;
depth_var.put_attribute("units", "m")?;
depth_var.put_attribute("missing_value", -9999.0f32)?;
file.add_attribute("TITLE", "OUTPUT FROM RS-ROUTE")?;
file.add_attribute(
"file_reference_time",
reference_time.format("%Y-%m-%d_%H:%M:%S").to_string(),
)?;
file.add_attribute("code_version", "")?;
let _ = file.add_variable::<f32>("type", &["feature_id"])?;
let _ = file.add_variable::<f32>("nudge", &["feature_id"])?;
Ok(Arc::new(Mutex::new(file)))
}
pub fn write_batch(
output_file: &Arc<Mutex<FileMut>>,
batch: &[Arc<SimulationResults>],
) -> Result<()> {
let mut file = output_file
.lock()
.map_err(|e| anyhow::anyhow!("Failed to acquire NetCDF file lock: {}", e))?;
let feature_var = file
.variable("feature_id")
.ok_or_else(|| anyhow::anyhow!("feature_id variable not found"))?;
let start_idx = feature_var.len();
let mut all_feature_ids = Vec::with_capacity(batch.len());
let mut all_flows = Vec::new();
let mut all_velocities = Vec::new();
let mut all_depths = Vec::new();
let expected_timesteps = file
.dimension("time")
.ok_or_else(|| anyhow::anyhow!("time dimension not found"))?
.len();
for results in batch {
all_feature_ids.push(results.feature_id);
let actual_timesteps = results.flow_data.len();
let downsampling = actual_timesteps / expected_timesteps;
for i in (downsampling - 1..actual_timesteps).step_by(downsampling) {
all_flows.push(results.flow_data[i]);
all_velocities.push(results.velocity_data[i]);
all_depths.push(results.depth_data[i]);
}
}
let mut feature_var = file
.variable_mut("feature_id")
.ok_or_else(|| anyhow::anyhow!("feature_id variable not found"))?;
feature_var
.put_values(&all_feature_ids, start_idx..)
.context("Failed to write feature_ids")?;
let flow_2d: Vec<Vec<f32>> = all_flows
.chunks(expected_timesteps)
.map(|chunk| chunk.to_vec())
.collect();
let mut flow_var = file
.variable_mut("flow")
.ok_or_else(|| anyhow::anyhow!("flow variable not found"))?;
for (i, row) in flow_2d.iter().enumerate() {
flow_var.put_values(row, (start_idx + i, ..))?;
}
let velocity_2d: Vec<Vec<f32>> = all_velocities
.chunks(expected_timesteps)
.map(|chunk| chunk.to_vec())
.collect();
let mut velocity_var = file
.variable_mut("velocity")
.ok_or_else(|| anyhow::anyhow!("velocity variable not found"))?;
for (i, row) in velocity_2d.iter().enumerate() {
velocity_var.put_values(row, (start_idx + i, ..))?;
}
let depth_2d: Vec<Vec<f32>> = all_depths
.chunks(expected_timesteps)
.map(|chunk| chunk.to_vec())
.collect();
let mut depth_var = file
.variable_mut("depth")
.ok_or_else(|| anyhow::anyhow!("depth variable not found"))?;
for (i, row) in depth_2d.iter().enumerate() {
depth_var.put_values(row, (start_idx + i, ..))?;
}
Ok(())
}
pub fn write_output(
output_file: &Arc<Mutex<FileMut>>,
results: &Arc<SimulationResults>,
) -> Result<()> {
let mut file = output_file
.lock()
.map_err(|e| anyhow::anyhow!("Failed to acquire NetCDF file lock: {}", e))?;
let expected_timesteps = file
.dimension("time")
.ok_or_else(|| anyhow::anyhow!("time dimension not found"))?;
let actual_timesteps = results.flow_data.len();
let downsampling = actual_timesteps / expected_timesteps.len();
let mut downsampled_flow_data = Vec::with_capacity(expected_timesteps.len());
let mut downsampled_velocity_data = Vec::with_capacity(expected_timesteps.len());
let mut downsampled_depth_data = Vec::with_capacity(expected_timesteps.len());
for i in (downsampling - 1..actual_timesteps).step_by(downsampling) {
downsampled_flow_data.push(results.flow_data[i]);
downsampled_velocity_data.push(results.velocity_data[i]);
downsampled_depth_data.push(results.depth_data[i]);
}
let mut feature_var = file
.variable_mut("feature_id")
.ok_or_else(|| anyhow::anyhow!("feature_id variable not found"))?;
let fidx = feature_var.len();
feature_var
.put_value(results.feature_id, fidx)
.context("Failed to write feature_id")?;
let mut flow_var = file
.variable_mut("flow")
.ok_or_else(|| anyhow::anyhow!("flow variable not found"))?;
flow_var
.put_values(&downsampled_flow_data, (fidx, ..))
.context("Failed to write flow data")?;
let mut velocity_var = file
.variable_mut("velocity")
.ok_or_else(|| anyhow::anyhow!("velocity variable not found"))?;
velocity_var
.put_values(&downsampled_velocity_data, (fidx, ..))
.context("Failed to write velocity data")?;
let mut depth_var = file
.variable_mut("depth")
.ok_or_else(|| anyhow::anyhow!("depth variable not found"))?;
depth_var
.put_values(&downsampled_depth_data, (fidx, ..))
.context("Failed to write depth data")?;
Ok(())
}