use crate::error::{Result, VrtError};
use oxigdal_core::types::{GeoTransform, NoDataValue, RasterDataType};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SourceFilename {
pub path: PathBuf,
pub relative_to_vrt: bool,
pub shared: bool,
}
impl SourceFilename {
pub fn new<P: AsRef<Path>>(path: P, relative_to_vrt: bool) -> Self {
Self {
path: path.as_ref().to_path_buf(),
relative_to_vrt,
shared: false,
}
}
pub fn absolute<P: AsRef<Path>>(path: P) -> Self {
Self::new(path, false)
}
pub fn relative<P: AsRef<Path>>(path: P) -> Self {
Self::new(path, true)
}
pub fn with_shared(mut self, shared: bool) -> Self {
self.shared = shared;
self
}
pub fn resolve<P: AsRef<Path>>(&self, vrt_path: P) -> Result<PathBuf> {
if self.relative_to_vrt {
let vrt_dir = vrt_path.as_ref().parent().ok_or_else(|| {
VrtError::path_resolution(
self.path.display().to_string(),
"VRT path has no parent directory",
)
})?;
Ok(vrt_dir.join(&self.path))
} else {
Ok(self.path.clone())
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct PixelRect {
pub x_off: u64,
pub y_off: u64,
pub x_size: u64,
pub y_size: u64,
}
impl PixelRect {
pub fn new(x_off: u64, y_off: u64, x_size: u64, y_size: u64) -> Self {
Self {
x_off,
y_off,
x_size,
y_size,
}
}
pub fn is_valid(&self) -> bool {
self.x_size > 0 && self.y_size > 0
}
pub fn contains(&self, x: u64, y: u64) -> bool {
x >= self.x_off
&& x < self.x_off.saturating_add(self.x_size)
&& y >= self.y_off
&& y < self.y_off.saturating_add(self.y_size)
}
pub fn intersect(&self, other: &Self) -> Option<Self> {
let x1 = self.x_off.max(other.x_off);
let y1 = self.y_off.max(other.y_off);
let x2 = (self.x_off + self.x_size).min(other.x_off + other.x_size);
let y2 = (self.y_off + self.y_size).min(other.y_off + other.y_size);
if x2 > x1 && y2 > y1 {
Some(Self::new(x1, y1, x2 - x1, y2 - y1))
} else {
None
}
}
pub fn intersects(&self, other: &Self) -> bool {
self.intersect(other).is_some()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SourceWindow {
pub src_rect: PixelRect,
pub dst_rect: PixelRect,
}
impl SourceWindow {
pub fn new(src_rect: PixelRect, dst_rect: PixelRect) -> Self {
Self { src_rect, dst_rect }
}
pub fn identity(width: u64, height: u64) -> Self {
let rect = PixelRect::new(0, 0, width, height);
Self::new(rect, rect)
}
pub fn validate(&self) -> Result<()> {
if !self.src_rect.is_valid() {
return Err(VrtError::invalid_window("Source rectangle is invalid"));
}
if !self.dst_rect.is_valid() {
return Err(VrtError::invalid_window("Destination rectangle is invalid"));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VrtSource {
pub filename: SourceFilename,
pub source_band: usize,
pub window: Option<SourceWindow>,
pub nodata: Option<NoDataValue>,
pub data_type: Option<RasterDataType>,
pub properties: Option<SourceProperties>,
}
impl VrtSource {
pub fn new(filename: SourceFilename, source_band: usize) -> Self {
Self {
filename,
source_band,
window: None,
nodata: None,
data_type: None,
properties: None,
}
}
pub fn simple<P: AsRef<Path>>(path: P, band: usize) -> Self {
Self::new(SourceFilename::absolute(path), band)
}
pub fn with_window(mut self, window: SourceWindow) -> Self {
self.window = Some(window);
self
}
pub fn with_nodata(mut self, nodata: NoDataValue) -> Self {
self.nodata = Some(nodata);
self
}
pub fn with_data_type(mut self, data_type: RasterDataType) -> Self {
self.data_type = Some(data_type);
self
}
pub fn with_properties(mut self, properties: SourceProperties) -> Self {
self.properties = Some(properties);
self
}
pub fn validate(&self) -> Result<()> {
if self.source_band == 0 {
return Err(VrtError::invalid_source("Source band must be >= 1"));
}
if let Some(ref window) = self.window {
window.validate()?;
}
Ok(())
}
pub fn dst_rect(&self) -> Option<PixelRect> {
self.window.as_ref().map(|w| w.dst_rect)
}
pub fn src_rect(&self) -> Option<PixelRect> {
self.window.as_ref().map(|w| w.src_rect)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SourceProperties {
pub width: u64,
pub height: u64,
pub band_count: usize,
pub data_type: RasterDataType,
pub geo_transform: Option<GeoTransform>,
pub nodata: NoDataValue,
}
impl SourceProperties {
pub fn new(width: u64, height: u64, band_count: usize, data_type: RasterDataType) -> Self {
Self {
width,
height,
band_count,
data_type,
geo_transform: None,
nodata: NoDataValue::None,
}
}
pub fn with_geo_transform(mut self, geo_transform: GeoTransform) -> Self {
self.geo_transform = Some(geo_transform);
self
}
pub fn with_nodata(mut self, nodata: NoDataValue) -> Self {
self.nodata = nodata;
self
}
pub fn validate_band(&self, band: usize) -> Result<()> {
if band == 0 || band > self.band_count {
return Err(VrtError::band_out_of_range(band, self.band_count));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_source_filename() {
let filename = SourceFilename::absolute("/path/to/file.tif");
assert_eq!(filename.path, PathBuf::from("/path/to/file.tif"));
assert!(!filename.relative_to_vrt);
let filename = SourceFilename::relative("data/file.tif");
assert!(filename.relative_to_vrt);
}
#[test]
fn test_pixel_rect() {
let rect = PixelRect::new(10, 20, 100, 200);
assert!(rect.is_valid());
assert!(rect.contains(10, 20));
assert!(rect.contains(50, 100));
assert!(!rect.contains(5, 20));
assert!(!rect.contains(200, 20));
}
#[test]
fn test_rect_intersection() {
let rect1 = PixelRect::new(0, 0, 100, 100);
let rect2 = PixelRect::new(50, 50, 100, 100);
let intersection = rect1.intersect(&rect2);
assert!(intersection.is_some());
let inter = intersection.expect("Should have intersection");
assert_eq!(inter.x_off, 50);
assert_eq!(inter.y_off, 50);
assert_eq!(inter.x_size, 50);
assert_eq!(inter.y_size, 50);
let rect3 = PixelRect::new(200, 200, 100, 100);
assert!(rect1.intersect(&rect3).is_none());
}
#[test]
fn test_source_window() {
let src_rect = PixelRect::new(0, 0, 512, 512);
let dst_rect = PixelRect::new(100, 100, 512, 512);
let window = SourceWindow::new(src_rect, dst_rect);
assert!(window.validate().is_ok());
}
#[test]
fn test_vrt_source() {
let source = VrtSource::simple("/path/to/file.tif", 1);
assert_eq!(source.source_band, 1);
assert!(source.validate().is_ok());
let invalid_source = VrtSource::simple("/path/to/file.tif", 0);
assert!(invalid_source.validate().is_err());
}
#[test]
fn test_source_properties() {
let props = SourceProperties::new(512, 512, 3, RasterDataType::UInt8);
assert_eq!(props.width, 512);
assert_eq!(props.band_count, 3);
assert!(props.validate_band(1).is_ok());
assert!(props.validate_band(3).is_ok());
assert!(props.validate_band(0).is_err());
assert!(props.validate_band(4).is_err());
}
}