use std::fs::File;
use std::io::BufWriter;
use std::path::Path;
use ndarray::Array2;
use tiff::encoder::colortype::Gray32Float;
use tiff::encoder::compression::DeflateLevel;
use tiff::encoder::{Compression, TiffEncoder};
use tiff::tags::Tag;
use crate::crs::CRS;
use crate::error::{Error, Result};
use crate::raster::GeoTransform;
pub struct StripWriterConfig {
pub rows: usize,
pub cols: usize,
pub transform: GeoTransform,
pub crs: Option<CRS>,
pub nodata: Option<f64>,
pub compress: bool,
pub rows_per_strip: u32,
}
pub fn write_geotiff_streaming<F>(
path: &Path,
config: &StripWriterConfig,
mut produce_strip: F,
) -> Result<()>
where
F: FnMut(usize, usize) -> Result<Array2<f64>>,
{
let tmp_path = path.with_extension("tmp");
let file = BufWriter::new(File::create(&tmp_path)?);
let compression = if config.compress {
Compression::Deflate(DeflateLevel::Balanced)
} else {
Compression::Uncompressed
};
let mut encoder = TiffEncoder::new(file)
.map_err(|e| Error::Other(format!("TIFF encoder error: {}", e)))?
.with_compression(compression);
let mut image = encoder
.new_image::<Gray32Float>(config.cols as u32, config.rows as u32)
.map_err(|e| Error::Other(format!("Cannot create TIFF image: {}", e)))?;
image
.rows_per_strip(config.rows_per_strip)
.map_err(|e| Error::Other(format!("Cannot set rows_per_strip: {}", e)))?;
let gt = &config.transform;
let scale = vec![gt.pixel_width, gt.pixel_height.abs(), 0.0];
image
.encoder()
.write_tag(Tag::Unknown(33550), scale.as_slice())
.map_err(|e| Error::Other(format!("Cannot write scale tag: {}", e)))?;
let tiepoint = vec![0.0, 0.0, 0.0, gt.origin_x, gt.origin_y, 0.0];
image
.encoder()
.write_tag(Tag::Unknown(33922), tiepoint.as_slice())
.map_err(|e| Error::Other(format!("Cannot write tiepoint tag: {}", e)))?;
let geokeys: Vec<u16> = if let Some(ref crs) = config.crs {
if let Some(epsg) = crs.epsg() {
if epsg == 4326 {
vec![
1,
1,
0,
3, 1024,
0,
1,
2, 1025,
0,
1,
1, 2048,
0,
1,
epsg as u16, ]
} else {
vec![
1,
1,
0,
3, 1024,
0,
1,
1, 1025,
0,
1,
1, 3072,
0,
1,
epsg as u16, ]
}
} else {
vec![
1, 1, 0, 2, 1024, 0, 1, 1, 1025, 0, 1, 1, ]
}
} else {
vec![
1, 1, 0, 2, 1024, 0, 1, 1, 1025, 0, 1, 1, ]
};
image
.encoder()
.write_tag(Tag::Unknown(34735), geokeys.as_slice())
.map_err(|e| Error::Other(format!("Cannot write geokey tag: {}", e)))?;
if let Some(nd) = config.nodata {
let nodata_str = if nd.is_nan() {
"nan\0".to_string()
} else {
format!("{}\0", nd)
};
image
.encoder()
.write_tag(Tag::Unknown(42113), nodata_str.as_bytes())
.map_err(|e| Error::Other(format!("Cannot write nodata tag: {}", e)))?;
}
let rps = config.rows_per_strip as usize;
let num_strips = (config.rows + rps - 1) / rps;
let mut all_data: Vec<f32> = Vec::with_capacity(config.rows * config.cols);
for strip_idx in 0..num_strips {
let strip_rows = if strip_idx == num_strips - 1 {
config.rows - strip_idx * rps
} else {
rps
};
let data = produce_strip(strip_idx, strip_rows)?;
all_data.extend(data.iter().map(|&v| v as f32));
}
image
.write_data(&all_data)
.map_err(|e| Error::Other(format!("Cannot write image data: {}", e)))?;
std::fs::rename(&tmp_path, path)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::strip_reader::StripReader;
#[test]
fn test_write_and_read_back() {
let path = std::path::Path::new("/tmp/surtgis_strip_writer_test.tif");
let rows = 100;
let cols = 50;
let config = StripWriterConfig {
rows,
cols,
transform: GeoTransform::new(100.0, 200.0, 10.0, -10.0),
crs: Some(CRS::from_epsg(32719)),
nodata: Some(-9999.0),
compress: false,
rows_per_strip: 32,
};
write_geotiff_streaming(path, &config, |_strip_idx, strip_rows| {
let mut data = Array2::<f64>::zeros((strip_rows, cols));
for r in 0..strip_rows {
for c in 0..cols {
data[[r, c]] = (r * 1000 + c) as f64;
}
}
Ok(data)
})
.unwrap();
let reader = StripReader::open(path).unwrap();
assert_eq!(reader.rows(), rows);
assert_eq!(reader.cols(), cols);
let gt = reader.transform();
assert!((gt.origin_x - 100.0).abs() < 1e-6);
assert!((gt.origin_y - 200.0).abs() < 1e-6);
assert!(reader.crs().is_some());
assert_eq!(reader.crs().unwrap().epsg(), Some(32719));
let _ = std::fs::remove_file(path);
}
}