use crate::error::{Error, Result};
use crate::raster::{GeoTransform, Raster, RasterElement};
use std::fs::File;
use std::io::Cursor;
use std::path::Path;
use tiff::decoder::{Decoder, DecodingResult, Limits};
use tiff::encoder::colortype::Gray32Float;
use tiff::encoder::compression::DeflateLevel;
use tiff::encoder::{Compression, TiffEncoder};
use tiff::tags::Tag;
#[derive(Debug, Clone)]
pub struct GeoTiffOptions {
pub compression: String,
}
impl Default for GeoTiffOptions {
fn default() -> Self {
Self {
compression: "NONE".to_string(),
}
}
}
pub fn read_geotiff<T, P>(path: P, band: Option<usize>) -> Result<Raster<T>>
where
T: RasterElement,
P: AsRef<Path>,
{
let file = File::open(path.as_ref())?;
decode_geotiff(file, band)
}
pub fn read_geotiff_from_buffer<T>(data: &[u8], band: Option<usize>) -> Result<Raster<T>>
where
T: RasterElement,
{
let cursor = Cursor::new(data);
decode_geotiff(cursor, band)
}
fn decode_geotiff<T, R>(reader: R, _band: Option<usize>) -> Result<Raster<T>>
where
T: RasterElement,
R: std::io::Read + std::io::Seek,
{
let mut decoder = Decoder::new(reader)
.map_err(|e| Error::Other(format!("TIFF decode error: {}", e)))?
.with_limits(Limits::unlimited());
let (width, height) = decoder.dimensions()
.map_err(|e| Error::Other(format!("Cannot read dimensions: {}", e)))?;
let rows = height as usize;
let cols = width as usize;
let result = decoder.read_image()
.map_err(|e| Error::Other(format!("Cannot read image data: {}", e)))?;
let data: Vec<T> = match result {
DecodingResult::F32(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
DecodingResult::F64(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
DecodingResult::U8(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
DecodingResult::U16(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
DecodingResult::U32(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
DecodingResult::I8(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
DecodingResult::I16(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
DecodingResult::I32(buf) => buf
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(T::default_nodata()))
.collect(),
_ => return Err(Error::UnsupportedDataType("Unsupported TIFF pixel format".to_string())),
};
if data.len() != rows * cols {
return Err(Error::InvalidDimensions {
width: cols,
height: rows,
});
}
let mut raster = Raster::from_vec(data, rows, cols)?;
if let Ok(transform) = read_geotransform(&mut decoder) {
raster.set_transform(transform);
}
if let Some(crs) = read_crs(&mut decoder) {
raster.set_crs(Some(crs));
}
Ok(raster)
}
fn read_crs<R: std::io::Read + std::io::Seek>(
decoder: &mut Decoder<R>,
) -> Option<crate::crs::CRS> {
let geokeys = decoder.get_tag_u16_vec(Tag::Unknown(34735)).ok()?;
if geokeys.len() < 4 {
return None;
}
let num_keys = geokeys[3] as usize;
for i in 0..num_keys {
let base = 4 + i * 4;
if base + 3 >= geokeys.len() {
break;
}
let key_id = geokeys[base];
let value = geokeys[base + 3];
if (key_id == 3072 || key_id == 2048) && value > 0 {
return Some(crate::crs::CRS::from_epsg(value as u32));
}
}
None
}
fn read_geotransform<R: std::io::Read + std::io::Seek>(
decoder: &mut Decoder<R>,
) -> Result<GeoTransform> {
let scale_tag = Tag::Unknown(33550);
let tiepoint_tag = Tag::Unknown(33922);
let scale = decoder
.get_tag_f64_vec(scale_tag)
.map_err(|_| Error::Other("No pixel scale tag".into()))?;
let tiepoint = decoder
.get_tag_f64_vec(tiepoint_tag)
.map_err(|_| Error::Other("No tiepoint tag".into()))?;
if scale.len() >= 2 && tiepoint.len() >= 6 {
let origin_x = tiepoint[3] - tiepoint[0] * scale[0];
let origin_y = tiepoint[4] + tiepoint[1] * scale[1];
let pixel_width = scale[0];
let pixel_height = -scale[1];
return Ok(GeoTransform::new(origin_x, origin_y, pixel_width, pixel_height));
}
Err(Error::Other("Cannot determine geotransform".into()))
}
pub fn write_geotiff<T, P>(
raster: &Raster<T>,
path: P,
options: Option<GeoTiffOptions>,
) -> Result<()>
where
T: RasterElement,
P: AsRef<Path>,
{
let compress = options
.as_ref()
.map(|o| o.compression.to_lowercase() != "none")
.unwrap_or(false);
let final_path = path.as_ref();
let tmp_path = final_path.with_extension("tmp");
let file = File::create(&tmp_path)?;
encode_geotiff(raster, file, compress)?;
std::fs::rename(&tmp_path, final_path)?;
Ok(())
}
pub fn write_geotiff_to_buffer<T>(
raster: &Raster<T>,
options: Option<GeoTiffOptions>,
) -> Result<Vec<u8>>
where
T: RasterElement,
{
let compress = options
.as_ref()
.map(|o| o.compression.to_lowercase() != "none")
.unwrap_or(false);
let mut buf = Vec::new();
encode_geotiff(raster, Cursor::new(&mut buf), compress)?;
Ok(buf)
}
fn encode_geotiff<T, W>(raster: &Raster<T>, writer: W, compress: bool) -> Result<()>
where
T: RasterElement,
W: std::io::Write + std::io::Seek,
{
let compression = if compress {
Compression::Deflate(DeflateLevel::Balanced)
} else {
Compression::Uncompressed
};
let mut encoder = TiffEncoder::new(writer)
.map_err(|e| Error::Other(format!("TIFF encoder error: {}", e)))?
.with_compression(compression);
let (rows, cols) = raster.shape();
let data: Vec<f32> = raster
.data()
.iter()
.map(|&v| num_traits::cast(v).unwrap_or(f32::NAN))
.collect();
let mut image = encoder
.new_image::<Gray32Float>(cols as u32, rows as u32)
.map_err(|e| Error::Other(format!("Cannot create TIFF image: {}", e)))?;
let gt = raster.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(crs) = raster.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)))?;
image
.write_data(&data)
.map_err(|e| Error::Other(format!("Cannot write image data: {}", e)))?;
Ok(())
}