use byteorder::{LittleEndian, WriteBytesExt};
use geodesy::authoring::*;
use core::f64;
use log::{info, trace};
use std::fs::File;
use std::io::{BufWriter, Seek, Write};
use std::path::Path;
use std::time;
const HEADER_SIZE: usize = 6 * size_of::<f64>() + 2 * size_of::<u64>();
use std::path::PathBuf;
use clap::{Command, arg};
fn cli() -> Command {
Command::new("unigrid")
.about("Handling Rust Geodesy Unigrids")
.subcommand_required(true)
.arg_required_else_help(true)
.allow_external_subcommands(false)
.subcommand(
Command::new("add")
.about("Add grids to geodesy/unigrid.grids")
.arg_required_else_help(true)
.arg(arg!(<PATH> ... "Grids to add"))
.arg(arg!(--force "Let new grids shadow older ones with the same name")),
)
.subcommand(
Command::new("list")
.about("List contents of unigrid in ./geodesy")
.arg(arg!(--verbose "Show additional details")),
)
.subcommand(Command::new("paths").about("Show unigrid search paths"))
.subcommand(Command::new("vacuum").about("Remove shadowed gridfiles (unimplemented)"))
}
fn main() -> Result<(), anyhow::Error> {
env_logger::init();
log::trace!("This is geodesy-grids");
let matches = cli().get_matches();
let ctx = Plain::new();
match matches.subcommand() {
Some(("list", sub_matches)) => {
let index: PathBuf = "geodesy".into();
let index = geodesy::grd::read_unigrid_index(&[index])?;
let grids = index[0].keys();
let verbose = sub_matches.get_flag("verbose");
for grid in grids {
let subgrids = &index[0][grid].subgrids;
let n = subgrids.len();
let header = &index[0][grid].header;
println!("{grid} [{n}]");
if verbose {
println!(" {header:?}");
for subgrid in subgrids {
let header = &subgrid.header;
println!(" {header:?}");
}
println!();
}
}
Ok(())
}
Some(("paths", _sub_matches)) => {
let paths = ctx.get_paths();
println!("{paths:?}");
Ok(())
}
Some(("vacuum", _sub_matches)) => {
println!("Vacuuming, i.e. removing shadowed files, is not implemented yet");
Ok(())
}
Some(("add", sub_matches)) => {
let paths = sub_matches
.get_many::<String>("PATH")
.into_iter()
.flatten()
.collect::<Vec<_>>();
let force = sub_matches.get_flag("force");
add_grid_files_to_unigrid(&paths, force)?;
Ok(())
}
_ => unreachable!(), }
}
fn add_grid_files_to_unigrid(args: &[&String], force: bool) -> Result<(), Error> {
let args = args.iter();
let unifile = File::options()
.append(true)
.create(true)
.open("geodesy/unigrid.grids")?;
let mut uniwriter = BufWriter::new(unifile);
let mut uniindex = File::options()
.append(true)
.create(true)
.open("geodesy/unigrid.index")?;
let index: PathBuf = "geodesy".into();
let index = geodesy::grd::read_unigrid_index(&[index])?;
let grids: Vec<_> = index[0].keys().cloned().collect();
'arg: for arg in args {
info!("Handling input file `{arg}`");
let start = time::Instant::now();
let grid_path = Path::new(arg);
let grid_id = grid_path.file_stem().unwrap().to_str().unwrap().to_owned();
if grids.contains(&grid_id) && !force {
println!(
"Adding {grid_id} would shadow existing element. Specify '--force' if this is intended."
);
continue;
}
let grid = geodesy::grd::read_grid(arg)?;
let header = &grid.header;
let records = header.rows * header.cols * header.bands;
let duration = start.elapsed();
trace!("Read {records} records in {duration:?}");
for subgrid in &grid.subgrids {
if subgrid.subgrids.is_empty() {
continue;
}
let sub_id = subgrid.name.as_str();
eprintln!(
"Skipping the file {grid_path:?}: Its subgrid {sub_id:?}, contains recursive subgrids, which is not supported"
);
continue 'arg;
}
let offset = write_single_grid_to_unigrid(&mut uniwriter, &grid)?;
writeln!(uniindex, "{grid_id} 0 {offset} {grid_path:?}")?;
for (i, subgrid) in grid.subgrids.iter().enumerate() {
let offset = write_single_grid_to_unigrid(&mut uniwriter, subgrid)?;
let corrected_index = i + 1;
writeln!(
uniindex,
"{grid_id} {corrected_index} {offset} {grid_path:?}"
)?;
}
let duration = start.elapsed();
trace!("Done in {duration:?}");
}
Ok(())
}
fn write_single_grid_to_unigrid(
uniwriter: &mut BufWriter<File>,
grid: &BaseGrid,
) -> Result<u64, Error> {
let offset = uniwriter.seek(std::io::SeekFrom::End(0))?;
let header = &grid.header;
let records = header.rows * header.cols * header.bands;
uniwriter.write_f64::<LittleEndian>(header.lat_n)?;
uniwriter.write_f64::<LittleEndian>(header.lat_s)?;
uniwriter.write_f64::<LittleEndian>(header.lon_w)?;
uniwriter.write_f64::<LittleEndian>(header.lon_e)?;
uniwriter.write_f64::<LittleEndian>(header.dlat)?;
uniwriter.write_f64::<LittleEndian>(header.dlon)?;
uniwriter.write_u64::<LittleEndian>(header.bands as u64)?;
uniwriter.write_u64::<LittleEndian>(offset + HEADER_SIZE as u64)?;
if let GridSource::Internal { values } = &grid.grid {
let len = values.len();
let rows = header.rows;
let cols = header.cols;
let bands = header.bands;
let grid_id = &grid.name;
if records != len {
info!("{grid_id}, {records}, {len}, {rows}, {cols}, {bands}");
return Err(Error::General("Mismatch between header info and grid size"));
}
for element in values.iter() {
uniwriter.write_f32::<LittleEndian>(*element)?;
}
} else {
return Err(Error::General("Unexpected grid type"));
}
Ok(offset)
}