use crate::crs::CRS;
use crate::error::Result;
use crate::raster::{GeoTransform, Raster, RasterElement};
use gdal::cpl::CslStringList;
use gdal::raster::GdalType;
use gdal::spatial_ref::SpatialRef;
use gdal::{Dataset, DriverManager};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct GeoTiffOptions {
pub compression: String,
pub tile_size: usize,
pub cog: bool,
pub bigtiff: bool,
}
impl Default for GeoTiffOptions {
fn default() -> Self {
Self {
compression: "DEFLATE".to_string(),
tile_size: 256,
cog: false,
bigtiff: false,
}
}
}
pub fn read_geotiff<T, P>(path: P, band: Option<usize>) -> Result<Raster<T>>
where
T: RasterElement + GdalType,
P: AsRef<Path>,
{
let dataset = Dataset::open(path.as_ref())?;
let band_idx = band.unwrap_or(1);
let rasterband = dataset.rasterband(band_idx)?;
let (cols, rows) = dataset.raster_size();
let buffer = rasterband.read_as::<T>((0, 0), (cols, rows), (cols, rows), None)?;
let mut raster = Raster::from_vec(buffer.data().to_vec(), rows, cols)?;
if let Ok(gt) = dataset.geo_transform() {
raster.set_transform(GeoTransform::from_gdal(gt));
}
if let Ok(srs) = dataset.spatial_ref() {
if let Ok(wkt) = srs.to_wkt() {
let mut crs = CRS::from_wkt(wkt);
if let Ok(code) = srs.auth_code() {
crs = CRS::from_epsg(code as u32);
}
raster.set_crs(Some(crs));
}
}
if let Some(nodata) = rasterband.no_data_value() {
if let Some(nd) = num_traits::cast(nodata) {
raster.set_nodata(Some(nd));
}
}
Ok(raster)
}
pub fn write_geotiff<T, P>(
raster: &Raster<T>,
path: P,
options: Option<GeoTiffOptions>,
) -> Result<()>
where
T: RasterElement + GdalType,
P: AsRef<Path>,
{
let opts = options.unwrap_or_default();
let driver = DriverManager::get_driver_by_name("GTiff")?;
let (rows, cols) = raster.shape();
let mut create_options = vec![format!("COMPRESS={}", opts.compression)];
if opts.tile_size > 0 {
create_options.push("TILED=YES".to_string());
create_options.push(format!("BLOCKXSIZE={}", opts.tile_size));
create_options.push(format!("BLOCKYSIZE={}", opts.tile_size));
}
if opts.bigtiff {
create_options.push("BIGTIFF=YES".to_string());
}
let mut csl = CslStringList::new();
for opt in &create_options {
csl.add_string(opt)?;
}
let mut dataset =
driver.create_with_band_type_with_options::<T, _>(path.as_ref(), cols, rows, 1, &csl)?;
dataset.set_geo_transform(&raster.transform().to_gdal())?;
if let Some(crs) = raster.crs() {
if let Some(epsg) = crs.epsg() {
let srs = SpatialRef::from_epsg(epsg)?;
dataset.set_spatial_ref(&srs)?;
} else if let Some(wkt) = crs.wkt() {
let srs = SpatialRef::from_wkt(wkt)?;
dataset.set_spatial_ref(&srs)?;
}
}
let mut band = dataset.rasterband(1)?;
if let Some(nodata) = raster.nodata() {
if let Some(nd) = num_traits::cast(nodata) {
band.set_no_data_value(Some(nd))?;
}
}
let data: Vec<T> = raster.data().iter().copied().collect();
let mut buffer = gdal::raster::Buffer::new((cols, rows), data);
band.write((0, 0), (cols, rows), &mut buffer)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_write_read_roundtrip() {
let mut raster: Raster<f32> = Raster::new(100, 100);
raster.set_transform(GeoTransform::new(0.0, 100.0, 1.0, -1.0));
raster.set_crs(Some(CRS::from_epsg(4326)));
raster.set_nodata(Some(-9999.0));
for i in 0..100 {
for j in 0..100 {
raster.set(i, j, (i * 100 + j) as f32).unwrap();
}
}
let tmp = NamedTempFile::with_suffix(".tif").unwrap();
write_geotiff(&raster, tmp.path(), None).unwrap();
let loaded: Raster<f32> = read_geotiff(tmp.path(), None).unwrap();
assert_eq!(loaded.shape(), raster.shape());
assert_eq!(loaded.get(50, 50).unwrap(), raster.get(50, 50).unwrap());
}
}