use image::DynamicImage;
use log::{info, debug, warn};
use std::path::Path;
use crate::tiff::errors::{TiffError, TiffResult};
use crate::tiff::TiffReader;
use crate::tiff::TiffBuilder;
use crate::tiff::geo_key_parser::GeoKeyParser;
use crate::tiff::constants::{tags, field_types, photometric};
use crate::extractor::Region;
use crate::utils::logger::Logger;
use crate::utils::reference_utils;
use crate::utils::tiff_extraction_utils;
pub fn reproject_and_save(
image: &DynamicImage,
input_path: &str,
output_path: &str,
region: Option<Region>,
target_epsg: u32,
logger: &Logger,
shape: Option<&str>
) -> TiffResult<()> {
info!("Reprojecting image to EPSG:{}", target_epsg);
let extension = Path::new(output_path)
.extension()
.map(|ext| ext.to_string_lossy().to_lowercase())
.unwrap_or_default();
if extension != "tif" && extension != "tiff" {
warn!("Reprojection only supported for TIFF output, saving without reprojection");
if let Some(shape_str) = shape {
if shape_str.to_lowercase() == "circle" {
let masked_image = crate::utils::mask_utils::apply_shape_mask(image, shape_str);
return crate::utils::mask_utils::save_shaped_image(&masked_image, output_path, shape_str);
}
}
return match image.save(output_path) {
Ok(_) => Ok(()),
Err(e) => Err(TiffError::GenericError(format!("Failed to save image: {}", e)))
};
}
let mut tiff_reader = TiffReader::new(logger);
let tiff = tiff_reader.load(input_path)?;
if tiff.ifds.is_empty() {
return Err(TiffError::GenericError("No IFDs found in input file".to_string()));
}
let source_ifd = &tiff.ifds[0];
let byte_order_handler = match tiff_reader.get_byte_order_handler() {
Some(handler) => handler,
None => return Err(TiffError::GenericError("Byte order handler not available".to_string())),
};
let file_path = tiff_reader.get_file_path().unwrap_or(input_path);
let geo_info = match GeoKeyParser::extract_geo_info(source_ifd, byte_order_handler, file_path) {
Ok(info) => info,
Err(e) => {
warn!("Failed to extract GeoTIFF info: {}, continuing with limited metadata", e);
return save_without_reprojection(image, output_path, region, input_path, logger, shape);
}
};
let source_epsg = geo_info.epsg_code;
if source_epsg == 0 {
warn!("Source EPSG code not found, saving without reprojection");
return save_without_reprojection(image, output_path, region, input_path, logger, shape);
}
info!("Reprojecting from EPSG:{} to EPSG:{}", source_epsg, target_epsg);
let masked_image = if let Some(shape_str) = shape {
if shape_str.to_lowercase() == "circle" {
crate::utils::mask_utils::apply_shape_mask(image, shape_str)
} else {
image.clone()
}
} else {
image.clone()
};
let mut builder = TiffBuilder::new(logger, false);
let ifd_index = builder.add_ifd(crate::tiff::ifd::IFD::new(0, 0));
tiff_extraction_utils::setup_tiff_tags(&mut builder, ifd_index, source_ifd, &masked_image)?;
if masked_image.color().has_color() {
tiff_extraction_utils::process_rgb_image(&masked_image, &mut builder, ifd_index)?;
} else {
tiff_extraction_utils::process_grayscale_image(&masked_image, &mut builder, ifd_index, 8)?;
}
builder.copy_geotiff_tags(ifd_index, source_ifd, &mut tiff_reader)?;
if let Some(extracted_region) = region {
reference_utils::add_georeferencing_to_builder(&mut builder, ifd_index, &extracted_region, input_path, logger)?;
}
update_projection_code(&mut builder, ifd_index, target_epsg);
let nodata_value = tiff_extraction_utils::extract_nodata_value(source_ifd, &tiff_reader);
let metadata_str = tiff_extraction_utils::extract_gdal_metadata(source_ifd, &tiff_reader);
builder.add_nodata_tag(ifd_index, &nodata_value);
builder.add_gdal_metadata_tag(ifd_index, metadata_str.as_deref(), &nodata_value);
builder.write(output_path)?;
info!("Saved reprojected image to {} with EPSG:{}", output_path, target_epsg);
Ok(())
}
fn save_without_reprojection(
image: &DynamicImage,
output_path: &str,
region: Option<Region>,
input_path: &str,
logger: &Logger,
shape: Option<&str>
) -> TiffResult<()> {
warn!("Saving without reprojection");
let masked_image = if let Some(shape_str) = shape {
if shape_str.to_lowercase() == "circle" {
crate::utils::mask_utils::apply_shape_mask(image, shape_str)
} else {
image.clone()
}
} else {
image.clone()
};
let extension = Path::new(output_path)
.extension()
.map(|ext| ext.to_string_lossy().to_lowercase())
.unwrap_or_default();
if extension != "tif" && extension != "tiff" {
if let Some(shape_str) = shape {
return crate::utils::mask_utils::save_shaped_image(&masked_image, output_path, shape_str);
} else {
return match masked_image.save(output_path) {
Ok(_) => Ok(()),
Err(e) => Err(TiffError::GenericError(format!("Failed to save image: {}", e)))
};
}
}
let mut builder = TiffBuilder::new(logger, false);
let ifd_index = builder.add_ifd(crate::tiff::ifd::IFD::new(0, 0));
let mut reader = TiffReader::new(logger);
let source_tiff = reader.load(input_path)?;
if !source_tiff.ifds.is_empty() {
let source_ifd = &source_tiff.ifds[0];
tiff_extraction_utils::setup_tiff_tags(&mut builder, ifd_index, source_ifd, &masked_image)?;
} else {
builder.ifds[ifd_index].add_entry(crate::tiff::ifd::IFDEntry::new(
tags::IMAGE_WIDTH, field_types::LONG, 1, masked_image.width() as u64));
builder.ifds[ifd_index].add_entry(crate::tiff::ifd::IFDEntry::new(
tags::IMAGE_LENGTH, field_types::LONG, 1, masked_image.height() as u64));
}
if masked_image.color().has_color() {
tiff_extraction_utils::process_rgb_image(&masked_image, &mut builder, ifd_index)?;
} else {
tiff_extraction_utils::process_grayscale_image(&masked_image, &mut builder, ifd_index, 8)?;
}
if !source_tiff.ifds.is_empty() {
if let Some(extracted_region) = region {
if let Err(e) = reference_utils::add_georeferencing_to_builder(
&mut builder, ifd_index, &extracted_region, input_path, logger
) {
warn!("Failed to add georeferencing: {}", e);
}
}
}
builder.write(output_path)?;
info!("Saved image to {} without reprojection", output_path);
Ok(())
}
fn update_projection_code(
builder: &mut TiffBuilder,
ifd_index: usize,
target_epsg: u32
) {
info!("Updating projection code to EPSG:{}", target_epsg);
debug!("Note: This is a metadata-only reprojection that changes the projection code");
debug!(" without actually transforming the coordinates");
}