use crate::error::TiffError;
use gdal::raster::Buffer;
use gdal::raster::{GdalDataType, GdalType, RasterizeOptions, rasterize};
use gdal::vector::Geometry;
use gdal::vector::LayerAccess;
use gdal::{Dataset, DriverManager};
use gdal::spatial_ref::{CoordTransform, SpatialRef};
use gdal::raster::RasterCreationOptions;
use ndarray::s;
use ndarray::{Array3, Axis};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct RasterInfo {
pub width: usize,
pub height: usize,
pub bands: usize,
pub geo_transform: [f64; 6],
pub projection: String,
pub nodata: Option<f64>,
pub original_type: GdalDataType,
}
impl RasterInfo {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, TiffError> {
let path_str = path.as_ref().to_string_lossy().to_string();
let dataset = Dataset::open(&path)?;
let geo_transform = dataset.geo_transform()?;
let projection = dataset.projection();
let (width, height) = dataset.raster_size();
let bands = dataset.raster_count();
if bands < 1 {
return Err(TiffError::BandMissing(path_str, 1));
}
let first_band = dataset.rasterband(1)?;
let nodata = first_band.no_data_value();
let original_type = first_band.band_type();
Ok(Self {
width,
height,
bands,
geo_transform,
projection,
nodata,
original_type,
})
}
pub fn bounds(&self) -> (f64, f64, f64, f64) {
let gt = self.geo_transform;
let w = self.width as f64;
let h = self.height as f64;
let x0 = gt[0];
let y0 = gt[3];
let x1 = gt[0] + w * gt[1] + h * gt[2];
let y1 = gt[3] + w * gt[4] + h * gt[5];
let min_x = x0.min(x1);
let max_x = x0.max(x1);
let min_y = y0.min(y1);
let max_y = y0.max(y1);
(min_x, min_y, max_x, max_y)
}
pub fn res(&self) -> (f64, f64) {
(self.geo_transform[1].abs(), self.geo_transform[5].abs())
}
pub fn pixel_to_geo(&self, col: usize, row: usize) -> (f64, f64) {
let gt = self.geo_transform;
let x = gt[0] + (col as f64) * gt[1] + (row as f64) * gt[2];
let y = gt[3] + (col as f64) * gt[4] + (row as f64) * gt[5];
(x, y)
}
pub fn geo_to_pixel(&self, x: f64, y: f64) -> (isize, isize) {
let gt = self.geo_transform;
let det = gt[1] * gt[5] - gt[2] * gt[4];
if det.abs() < 1e-10 {
let col = ((x - gt[0]) / gt[1]).floor() as isize;
let row = ((y - gt[3]) / gt[5]).floor() as isize;
(col, row)
} else {
let dx = x - gt[0];
let dy = y - gt[3];
let col = ((gt[5] * dx - gt[2] * dy) / det).floor() as isize;
let row = ((-gt[4] * dx + gt[1] * dy) / det).floor() as isize;
(col, row)
}
}
pub fn bounds_to_window(
&self,
bounds: (f64, f64, f64, f64),
) -> Result<(usize, usize, usize, usize), TiffError> {
let (min_x, min_y, max_x, max_y) = bounds;
let (col1, row1) = self.geo_to_pixel(min_x, max_y); let (col2, row2) = self.geo_to_pixel(max_x, min_y);
use std::cmp::{max, min};
let x_start = min(col1, col2);
let y_start = min(row1, row2);
let x_end = max(col1, col2);
let y_end = max(row1, row2);
let x_off = x_start.max(0).min(self.width as isize) as usize;
let y_off = y_start.max(0).min(self.height as isize) as usize;
let x_end_clamped = x_end.max(0).min(self.width as isize) as usize;
let y_end_clamped = y_end.max(0).min(self.height as isize) as usize;
let width = x_end_clamped.saturating_sub(x_off);
let height = y_end_clamped.saturating_sub(y_off);
if width == 0 || height == 0 {
return Err(TiffError::InvalidCropBounds(format!(
"地理范围与栅格不重叠: bounds=({}, {}, {}, {}), 像素范围: x[{}~{}], y[{}~{}]",
min_x, min_y, max_x, max_y, x_start, x_end, y_start, y_end
)));
}
Ok((x_off, y_off, width, height))
}
}
#[derive(Debug, Clone)]
pub struct GeoTiff {
pub data: Array3<f64>, pub geo_transform: [f64; 6], pub projection: String, pub nodata: Option<f64>, pub original_type: GdalDataType, }
impl GeoTiff {
pub fn info<P: AsRef<Path>>(path: P) -> Result<RasterInfo, TiffError> {
RasterInfo::from_file(path)
}
pub fn read<P: AsRef<Path>>(path: P) -> Result<Self, TiffError> {
let path_str = path.as_ref().to_string_lossy().to_string();
let dataset = Dataset::open(&path)?;
let geo_transform = dataset.geo_transform()?;
let projection = dataset.projection();
let raster_count = dataset.raster_count();
if raster_count < 1 {
return Err(TiffError::BandMissing(path_str, 1));
}
let (width, height) = dataset.raster_size();
let first_band = dataset.rasterband(1)?;
let mut nodata = first_band.no_data_value();
let original_type = first_band.band_type();
if nodata.is_none() {
nodata = Some(f64::NAN);
}
let mut flat_data: Vec<f64> = Vec::with_capacity(raster_count * height * width);
for i in 1..=raster_count {
let band = dataset.rasterband(i)?;
let buffer = band.read_as::<f64>(
(0, 0), (width, height), (width, height), None, )?;
flat_data.extend_from_slice(buffer.data());
}
let data = Array3::from_shape_vec((raster_count, height, width), flat_data)?;
Ok(Self {
data,
geo_transform,
projection,
nodata,
original_type,
})
}
pub fn read_window<P: AsRef<Path>>(
path: P,
x_off: usize,
y_off: usize,
width: usize,
height: usize,
) -> Result<Self, TiffError> {
let path_str = path.as_ref().to_string_lossy().to_string();
let dataset = Dataset::open(&path)?;
let geo_transform = dataset.geo_transform()?;
let projection = dataset.projection();
let raster_count = dataset.raster_count();
if raster_count < 1 {
return Err(TiffError::BandMissing(path_str.clone(), 1));
}
let (raster_width, raster_height) = dataset.raster_size();
if x_off + width > raster_width || y_off + height > raster_height {
return Err(TiffError::InvalidCropBounds(format!(
"窗口超出栅格范围: 窗口({}, {}, {}, {}), 栅格尺寸({}, {})",
x_off, y_off, width, height, raster_width, raster_height
)));
}
let first_band = dataset.rasterband(1)?;
let mut nodata = first_band.no_data_value();
let original_type = first_band.band_type();
if nodata.is_none() {
nodata = Some(f64::NAN);
}
let mut flat_data: Vec<f64> = Vec::with_capacity(raster_count * height * width);
for i in 1..=raster_count {
let band = dataset.rasterband(i)?;
let buffer = band.read_as::<f64>(
(x_off as isize, y_off as isize), (width, height), (width, height), None, )?;
flat_data.extend_from_slice(buffer.data());
}
let data = Array3::from_shape_vec((raster_count, height, width), flat_data)?;
let new_geo_transform = [
geo_transform[0]
+ (x_off as f64) * geo_transform[1]
+ (y_off as f64) * geo_transform[2],
geo_transform[1],
geo_transform[2],
geo_transform[3]
+ (x_off as f64) * geo_transform[4]
+ (y_off as f64) * geo_transform[5],
geo_transform[4],
geo_transform[5],
];
Ok(Self {
data,
geo_transform: new_geo_transform,
projection,
nodata,
original_type,
})
}
pub fn read_bounds<P: AsRef<Path>>(
path: P,
bounds: (f64, f64, f64, f64), ) -> Result<Self, TiffError> {
let info = RasterInfo::from_file(&path)?;
let (x_off, y_off, width, height) = info.bounds_to_window(bounds)?;
Self::read_window(path, x_off, y_off, width, height)
}
pub fn read_by_vector<P: AsRef<Path>, Q: AsRef<Path>>(
raster_path: P,
vector_path: Q,
apply_mask: bool,
) -> Result<Self, TiffError> {
let vector_path_str = vector_path.as_ref().to_string_lossy().to_string();
let vector_ds = Dataset::open(&vector_path)?;
let mut layer = vector_ds.layer(0)?;
let extent = layer.get_extent()?;
let info = RasterInfo::from_file(&raster_path)?;
if let Some(vector_srs) = layer.spatial_ref() {
if let Ok(vector_wkt) = vector_srs.to_wkt() {
if !info.projection.is_empty()
&& !vector_wkt.is_empty()
&& info.projection != vector_wkt
{
eprintln!("警告: 矢量投影与栅格投影可能不一致!建议先进行重投影。");
}
}
}
let bounds = (extent.MinX, extent.MinY, extent.MaxX, extent.MaxY);
let (x_off, y_off, width, height) = info.bounds_to_window(bounds)?;
println!(
"矢量窗口读取: Path={}, x={}, y={}, w={}, h={}",
vector_path_str, x_off, y_off, width, height
);
let mut result = Self::read_window(&raster_path, x_off, y_off, width, height)?;
if apply_mask {
let no_data_val = result.nodata.unwrap_or(-9999.0);
result.nodata = Some(no_data_val);
let driver = DriverManager::get_driver_by_name("MEM")?;
let mut mask_ds = driver.create_with_band_type::<u8, _>("", width, height, 1)?;
mask_ds.set_geo_transform(&result.geo_transform)?;
mask_ds.set_projection(&result.projection)?;
let options = RasterizeOptions {
all_touched: true,
merge_algorithm: gdal::raster::MergeAlgorithm::Replace,
..Default::default()
};
let geometries: Vec<Geometry> = layer
.features()
.filter_map(|f| f.geometry().cloned())
.collect();
if geometries.is_empty() {
return Err(TiffError::Gdal(gdal::errors::GdalError::NullPointer {
method_name: "rasterize",
msg: "矢量文件中没有有效的几何体".into(),
}));
}
rasterize(&mut mask_ds, &[1], &geometries, &[1.0], Some(options))?;
let mask_band = mask_ds.rasterband(1)?;
let mask_data =
mask_band.read_as::<u8>((0, 0), (width, height), (width, height), None)?;
let mask_slice = mask_data.data();
for mut band_view in result.data.outer_iter_mut() {
if let Some(slice) = band_view.as_slice_mut() {
for (pixel, &m) in slice.iter_mut().zip(mask_slice.iter()) {
if m == 0 {
*pixel = no_data_val;
}
}
} else {
for (pixel, &m) in band_view.iter_mut().zip(mask_slice.iter()) {
if m == 0 {
*pixel = no_data_val;
}
}
}
}
}
Ok(result)
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<(), TiffError> {
let path = path.as_ref();
macro_rules! resolve_nodata {
($t:ty, $default:expr) => {{
if let Some(nd) = self.nodata {
if nd.is_finite() {
(Some(nd as $t), Some(nd))
} else {
(Some($default), Some($default as f64))
}
} else {
(None, None)
}
}};
}
match self.original_type {
GdalDataType::UInt8 => {
let (fill, meta) = resolve_nodata!(u8, u8::MAX);
self.write_impl(path, fill, meta, |v| v as u8)
}
GdalDataType::UInt16 => {
let (fill, meta) = resolve_nodata!(u16, u16::MAX);
self.write_impl(path, fill, meta, |v| v as u16)
}
GdalDataType::Int16 => {
let (fill, meta) = resolve_nodata!(i16, i16::MIN);
self.write_impl(path, fill, meta, |v| v as i16)
}
GdalDataType::UInt32 => {
let (fill, meta) = resolve_nodata!(u32, u32::MAX);
self.write_impl(path, fill, meta, |v| v as u32)
}
GdalDataType::Int32 => {
let (fill, meta) = resolve_nodata!(i32, i32::MIN);
self.write_impl(path, fill, meta, |v| v as i32)
}
GdalDataType::Float32 => {
let (fill, meta) = if let Some(val) = self.nodata {
(Some(val as f32), Some(val))
} else {
(None, None)
};
self.write_impl(path, fill, meta, |v| v as f32)
}
_ => {
self.write_impl(path, self.nodata, self.nodata, |v| v)
}
}
}
fn write_impl<T: GdalType + Copy + PartialEq>(
&self,
path: &Path,
fill_value: Option<T>, metadata_value: Option<f64>, convert: impl Fn(f64) -> T,
) -> Result<(), TiffError> {
let (bands, height, width) = self.data.dim();
let driver = DriverManager::get_driver_by_name("GTiff")?;
let mut options = RasterCreationOptions::default();
options.set_name_value("COMPRESS", "LZW")?;
options.set_name_value("PREDICTOR", "2")?;
options.set_name_value("TILED", "YES")?;
options.set_name_value("BIGTIFF", "IF_SAFER")?;
let mut dataset = driver.create_with_band_type::<T, _>(
path, width as usize,
height as usize,
bands as usize,
)?;
dataset.set_geo_transform(&self.geo_transform)?;
dataset.set_projection(&self.projection)?;
for i in 0..bands {
let mut band = dataset.rasterband(i + 1)?;
if let Some(val) = metadata_value {
band.set_no_data_value(Some(val))?;
}
let band_view = self.data.index_axis(Axis(0), i);
let src_nodata = self.nodata;
let map_pixel = |v: &f64| -> T {
let v = *v;
let is_nodata = if let Some(snd) = src_nodata {
if snd.is_nan() { v.is_nan() } else { v == snd }
} else {
false
};
if is_nodata {
if let Some(fill) = fill_value {
return fill;
}
}
convert(v)
};
let band_vec: Vec<T> = if let Some(slice) = band_view.as_slice() {
slice.iter().map(map_pixel).collect()
} else {
band_view.iter().map(map_pixel).collect()
};
let mut buffer = Buffer::new(
(width, height), band_vec, );
band.write((0, 0), (width as usize, height as usize), &mut buffer)?;
}
dataset.flush_cache()?;
Ok(())
}
pub fn crop(
&self,
x_off: usize,
y_off: usize,
width: usize,
height: usize,
) -> Result<Self, TiffError> {
let (_bands, max_h, max_w) = self.data.dim();
if x_off + width > max_w || y_off + height > max_h {
let msg = format!(
"裁剪参数超出范围: x_off={}, y_off={}, width={}, height={}, max_w={}, max_h={}",
x_off, y_off, width, height, max_w, max_h
);
return Err(TiffError::InvalidCropBounds(msg));
}
let new_data = self
.data
.slice(s![.., y_off..y_off + height, x_off..x_off + width])
.to_owned();
let mut new_gt = self.geo_transform;
let x_shift = x_off as f64;
let y_shift = y_off as f64;
new_gt[0] = self.geo_transform[0]
+ (x_shift * self.geo_transform[1])
+ (y_shift * self.geo_transform[2]);
new_gt[3] = self.geo_transform[3]
+ (x_shift * self.geo_transform[4])
+ (y_shift * self.geo_transform[5]);
Ok(Self {
data: new_data,
geo_transform: new_gt,
projection: self.projection.clone(), nodata: self.nodata,
original_type: self.original_type,
})
}
pub fn crop_by_vector<P: AsRef<Path>>(
&self,
vector_path: P,
apply_mask: bool,
) -> Result<Self, TiffError> {
let path_str = vector_path.as_ref().to_string_lossy().to_string();
let dataset = Dataset::open(&vector_path).map_err(TiffError::Gdal)?;
let mut layer = dataset.layer(0).map_err(TiffError::Gdal)?;
let raster_srs = self.projection.clone();
if let Some(vector_srs) = layer.spatial_ref() {
if let Ok(vector_wkt) = vector_srs.to_wkt() {
if !raster_srs.is_empty() && !vector_wkt.is_empty() && raster_srs != vector_wkt {
eprintln!("警告: 矢量投影与栅格投影可能不一致!建议先进行重投影。");
}
}
}
let extent = layer.get_extent().map_err(TiffError::Gdal)?;
let (x_off, y_off, width, height) = self.compute_crop_window(extent, self.geo_transform)?;
println!(
"矢量裁剪: Path={}, x={}, y={}, w={}, h={}",
path_str, x_off, y_off, width, height
);
let mut cropped_tif = self.crop(x_off, y_off, width, height)?;
if apply_mask {
let no_data_val = cropped_tif.nodata.unwrap_or(-9999.0);
cropped_tif.nodata = Some(no_data_val);
let driver = DriverManager::get_driver_by_name("MEM").map_err(TiffError::Gdal)?;
let mut mask_ds = driver
.create_with_band_type::<u8, _>("", width, height, 1)
.map_err(TiffError::Gdal)?;
mask_ds
.set_geo_transform(&cropped_tif.geo_transform)
.map_err(TiffError::Gdal)?;
mask_ds
.set_projection(&cropped_tif.projection)
.map_err(TiffError::Gdal)?;
let options = RasterizeOptions {
all_touched: true, merge_algorithm: gdal::raster::MergeAlgorithm::Replace,
..Default::default()
};
let geometries: Vec<Geometry> = layer
.features()
.filter_map(|f| f.geometry().cloned())
.collect();
if geometries.is_empty() {
return Err(TiffError::Gdal(gdal::errors::GdalError::NullPointer {
method_name: "rasterize",
msg: "矢量文件中没有有效的几何体".into(),
}));
}
rasterize(&mut mask_ds, &[1], &geometries, &[1.0], Some(options))?;
let mask_band = mask_ds.rasterband(1).map_err(TiffError::Gdal)?;
let mask_data = mask_band
.read_as::<u8>((0, 0), (width, height), (width, height), None)
.map_err(TiffError::Gdal)?;
let mask_slice = mask_data.data();
for mut band_view in cropped_tif.data.outer_iter_mut() {
if let Some(slice) = band_view.as_slice_mut() {
for (pixel, &m) in slice.iter_mut().zip(mask_slice.iter()) {
if m == 0 {
*pixel = no_data_val;
}
}
} else {
for (pixel, &m) in band_view.iter_mut().zip(mask_slice.iter()) {
if m == 0 {
*pixel = no_data_val;
}
}
}
}
}
Ok(cropped_tif)
}
fn compute_crop_window(
&self,
extent: gdal::vector::Envelope,
geo_transform: [f64; 6],
) -> Result<(usize, usize, usize, usize), TiffError> {
let px1 = ((extent.MinX - geo_transform[0]) / geo_transform[1] + 0.5) as isize;
let px2 = ((extent.MaxX - geo_transform[0]) / geo_transform[1] + 0.5) as isize;
let py1 = ((extent.MinY - geo_transform[3]) / geo_transform[5] + 0.5) as isize;
let py2 = ((extent.MaxY - geo_transform[3]) / geo_transform[5] + 0.5) as isize;
use std::cmp::{max, min};
let x_start = min(px1, px2);
let y_start = min(py1, py2);
let x_end = max(px1, px2);
let y_end = max(py1, py2);
let (_, h, w) = self.data.dim();
let x_off = x_start.max(0).min(w as isize) as usize;
let y_off = y_start.max(0).min(h as isize) as usize;
let x_end_clamped = x_end.max(0).min(w as isize) as usize;
let y_end_clamped = y_end.max(0).min(h as isize) as usize;
let width = x_end_clamped.saturating_sub(x_off); let height = y_end_clamped.saturating_sub(y_off);
if width == 0 || height == 0 {
return Err(TiffError::InvalidCropBounds(format!(
"矢量范围与图像不重叠或无效。矢量像素范围: x[{}~{}], y[{}~{}]",
x_start, x_end, y_start, y_end
)));
}
Ok((x_off, y_off, width, height))
}
pub fn reproject(&self, target_epsg: i32) -> Result<GeoTiff, TiffError> {
let mut src_srs = SpatialRef::from_wkt(&self.projection)?;
let mut target_srs = SpatialRef::from_epsg(target_epsg as u32)?;
src_srs
.set_axis_mapping_strategy(gdal::spatial_ref::AxisMappingStrategy::TraditionalGisOrder);
target_srs
.set_axis_mapping_strategy(gdal::spatial_ref::AxisMappingStrategy::TraditionalGisOrder);
if let Ok(src_epsg) = src_srs.auth_code() {
if src_epsg == target_epsg {
if let Some(auth_name) = src_srs.auth_name() {
if auth_name == "EPSG" {
println!("源投影与目标投影一致 (EPSG:{}), 跳过转换。", target_epsg);
return Ok(self.clone());
}
}
}
}
let transform = CoordTransform::new(&src_srs, &target_srs)?;
let (new_width, new_height, new_gt) = self.compute_reprojected_bounds(&transform)?;
let (bands, h, w) = self.data.dim();
let driver = DriverManager::get_driver_by_name("MEM")?;
let mut src_ds = driver.create_with_band_type::<f64, _>("", w, h, bands)?;
src_ds.set_geo_transform(&self.geo_transform)?;
src_ds.set_projection(&self.projection)?;
for b in 0..bands {
let mut band = src_ds.rasterband(b + 1)?;
if let Some(nd) = self.nodata {
band.set_no_data_value(Some(nd))?;
}
let data_vec: Vec<f64> = self
.data
.index_axis(ndarray::Axis(0), b)
.iter()
.cloned()
.collect();
let mut buffer = Buffer::new((w, h), data_vec);
band.write((0, 0), (w, h), &mut buffer)?;
}
let mut dst_ds =
driver.create_with_band_type::<f64, _>("", new_width, new_height, bands)?;
dst_ds.set_geo_transform(&new_gt)?;
let target_wkt = target_srs.to_wkt()?;
dst_ds.set_projection(&target_wkt)?;
for b in 1..=bands {
let mut band = dst_ds.rasterband(b)?;
if let Some(nd) = self.nodata {
band.set_no_data_value(Some(nd))?;
band.fill(nd, Some(0.0))?;
} else {
band.fill(0.0, Some(0.0))?;
}
}
gdal::raster::reproject(&src_ds, &dst_ds)?;
let mut new_flat_data = Vec::with_capacity(bands * new_height * new_width);
for b in 1..=bands {
let band = dst_ds.rasterband(b)?;
let buffer = band.read_as::<f64>(
(0, 0),
(new_width, new_height),
(new_width, new_height),
None,
)?;
new_flat_data.extend_from_slice(buffer.data());
}
let new_data = Array3::from_shape_vec((bands, new_height, new_width), new_flat_data)
.map_err(|e| TiffError::InvalidCropBounds(e.to_string()))?;
Ok(GeoTiff {
data: new_data,
geo_transform: new_gt,
projection: target_wkt,
nodata: self.nodata,
original_type: self.original_type,
})
}
fn compute_reprojected_bounds(
&self,
transform: &CoordTransform,
) -> Result<(usize, usize, [f64; 6]), TiffError> {
let (_, h, w) = self.data.dim();
let gt = self.geo_transform;
let corners_pixel = vec![
(0.0, 0.0), (w as f64, 0.0), (w as f64, h as f64), (0.0, h as f64), ];
let mut xs = Vec::new();
let mut ys = Vec::new();
for (px, py) in corners_pixel {
let (x, y) = self.pixel_to_projected(px, py, gt, transform)?;
xs.push(x);
ys.push(y);
}
let min_x = xs.iter().cloned().fold(f64::INFINITY, f64::min);
let max_x = xs.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let min_y = ys.iter().cloned().fold(f64::INFINITY, f64::min);
let max_y = ys.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let cx = w as f64 / 2.0;
let cy = h as f64 / 2.0;
let p0 = self.pixel_to_projected(cx, cy, gt, transform)?;
let p1 = self.pixel_to_projected(cx + 1.0, cy, gt, transform)?; let p2 = self.pixel_to_projected(cx, cy + 1.0, gt, transform)?;
let res_x = ((p1.0 - p0.0).powi(2) + (p1.1 - p0.1).powi(2)).sqrt();
let res_y = ((p2.0 - p0.0).powi(2) + (p2.1 - p0.1).powi(2)).sqrt();
let width_geo = max_x - min_x;
let height_geo = max_y - min_y;
let new_w = (width_geo / res_x).ceil() as usize;
let new_h = (height_geo / res_y).ceil() as usize;
let new_gt = [min_x, res_x, 0.0, max_y, 0.0, -res_y];
Ok((new_w, new_h, new_gt))
}
fn pixel_to_projected(
&self,
px: f64,
py: f64,
gt: [f64; 6],
transform: &CoordTransform,
) -> Result<(f64, f64), TiffError> {
let geo_x = gt[0] + px * gt[1] + py * gt[2];
let geo_y = gt[3] + px * gt[4] + py * gt[5];
let mut x = [geo_x];
let mut y = [geo_y];
let mut z = [0.0];
transform.transform_coords(&mut x, &mut y, &mut z)?;
Ok((x[0], y[0]))
}
}
#[test]
fn test_reproject() -> Result<(), TiffError> {
let p = std::path::Path::new("./data/Hawaiin.tif");
let tif = GeoTiff::read(p)?;
println!("tif_nodata: {:?}", tif.nodata);
let target_epsg = 32604;
println!("正在开始重投影至 EPSG:{}...", target_epsg);
let tif_projected = tif.reproject(target_epsg as i32)?;
println!("重投影完成!新尺寸: {:?}", tif_projected.data.dim());
println!("新投影 WKT: {}", &tif_projected.projection[..100]);
let path = std::path::Path::new("./data/Hawaiin_UTM4N.tif");
tif_projected.write(path)?;
println!("结果已保存至: {}", path.display());
Ok(())
}