1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
//! TIFF/GeoTIFF structure analysis command
//!
//! This module implements the command for analyzing and displaying
//! the structure of TIFF and GeoTIFF files.
use clap::ArgMatches;
use log::{debug, info};
use crate::commands::command_traits::Command;
use crate::tiff::TiffReader;
use crate::tiff::errors::{TiffResult, TiffError};
use crate::utils::logger::Logger;
use crate::tiff::{is_geotiff_tag, get_tag_name, get_projected_cs_description};
use crate::tiff::geo_key_parser::GeoKeyParser;
use crate::utils::tiff_code_translators::compression_code_to_name;
use crate::compression::CompressionFactory;
use crate::tiff::ifd::IFD;
use crate::tiff::constants::{tags, geo_keys};
use crate::tiff::types::TIFF;
/// Command for analyzing TIFF file structure
pub struct AnalyzeCommand<'a> {
/// Path to the input file
input_file: String,
/// Whether to enable verbose output
verbose: bool,
/// Logger for recording operations
logger: &'a Logger,
}
impl<'a> AnalyzeCommand<'a> {
/// Create a new analyze command
///
/// # Arguments
/// * `args` - CLI argument matches from clap
/// * `logger` - Logger for recording operations
///
/// # Returns
/// A new AnalyzeCommand instance or an error
pub fn new(args: &ArgMatches, logger: &'a Logger) -> TiffResult<Self> {
let input_file = args.get_one::<String>("input")
.ok_or_else(|| TiffError::GenericError("Missing input file".to_string()))?
.clone();
let verbose = args.get_flag("verbose");
Ok(AnalyzeCommand {
input_file,
verbose,
logger,
})
}
/// Display basic TIFF information
///
/// Shows the TIFF format (standard or BigTIFF) and number of IFDs.
///
/// # Arguments
/// * `tiff` - The TIFF structure to analyze
fn display_tiff_summary(&self, tiff: &TIFF) {
info!("TIFF Analysis Results:");
info!(" Format: {}", if tiff.is_big_tiff { "BigTIFF" } else { "TIFF" });
info!(" Number of IFDs: {}", tiff.ifd_count());
}
/// Display basic IFD information
///
/// Shows IFD offset, number of entries, dimensions, and samples per pixel.
///
/// # Arguments
/// * `ifd` - The IFD to analyze
/// * `index` - Index of the IFD in the TIFF file
fn display_ifd_summary(&self, ifd: &IFD, index: usize) {
info!("\nIFD #{} (offset: {})", index, ifd.offset);
info!(" Number of entries: {}", ifd.entries.len());
if let Some((width, height)) = ifd.get_dimensions() {
info!(" Dimensions: {}x{}", width, height);
} else {
info!(" Dimensions: Not available");
}
info!(" Samples per pixel: {}", ifd.get_samples_per_pixel());
}
/// Display compression information
///
/// Shows the compression method used and whether it's supported for extraction.
///
/// # Arguments
/// * `ifd` - The IFD to analyze for compression info
fn display_compression_info(&self, ifd: &IFD) {
if let Some(entry) = ifd.get_entry(tags::COMPRESSION) {
info!(" Compression: {} ({})",
entry.value_offset,
compression_code_to_name(entry.value_offset));
// Show if the compression method is supported
match CompressionFactory::create_handler(entry.value_offset) {
Ok(_) => info!(" (Compression supported for extraction)"),
Err(_) => info!(" (Compression not supported for extraction)"),
}
}
}
/// Display subfile type information
///
/// Shows the NewSubfileType tag value and interprets any relevant flags.
///
/// # Arguments
/// * `ifd` - The IFD to analyze for subfile type info
fn display_subfile_type(&self, ifd: &IFD) {
if let Some(entry) = ifd.get_entry(tags::NEW_SUBFILE_TYPE) {
info!(" NewSubfileType: {}", entry.value_offset);
if entry.value_offset & 1 == 1 {
info!(" (Reduced resolution version)");
}
}
}
/// Check if IFD has GeoTIFF tags and display them
///
/// Lists all GeoTIFF tags found in the IFD.
///
/// # Arguments
/// * `ifd` - The IFD to analyze for GeoTIFF tags
///
/// # Returns
/// Boolean indicating whether GeoTIFF tags were found
fn display_geotiff_tags(&self, ifd: &IFD) -> bool {
let has_geotiff = ifd.entries.iter().any(|entry| is_geotiff_tag(entry.tag));
if has_geotiff {
info!(" GeoTIFF tags found:");
for entry in &ifd.entries {
if is_geotiff_tag(entry.tag) {
info!(" Tag {} ({}): count={}, value/offset={}",
entry.tag, get_tag_name(entry.tag), entry.count, entry.value_offset);
}
}
}
has_geotiff
}
/// Display detailed GeoTIFF information using the GeoKeyParser
///
/// Extracts and displays detailed GeoTIFF metadata including
/// pixel scale, tiepoints, GeoKey directory, and PROJ.4 string.
///
/// # Arguments
/// * `reader` - TIFF reader for accessing tag data
/// * `ifd` - The IFD containing GeoTIFF information
fn display_geotiff_details(&self, reader: &TiffReader, ifd: &IFD) {
if let Some(byte_order_handler) = reader.get_byte_order_handler() {
let file_path = reader.get_file_path().unwrap_or(&self.input_file);
// We need to pass the Box<dyn ByteOrderHandler> directly
self.display_pixel_scale(ifd, byte_order_handler, file_path);
self.display_tiepoint(ifd, byte_order_handler, file_path);
self.display_geokey_directory(ifd, byte_order_handler, file_path);
self.display_proj_string(ifd, byte_order_handler, file_path);
}
}
/// Display pixel scale information
///
/// Shows the pixel dimensions in map units from ModelPixelScale tag.
///
/// # Arguments
/// * `ifd` - The IFD containing GeoTIFF information
/// * `byte_order_handler` - Handler for interpreting byte order
/// * `file_path` - Path to the TIFF file
fn display_pixel_scale(&self, ifd: &IFD,
byte_order_handler: &Box<dyn crate::io::byte_order::ByteOrderHandler>,
file_path: &str) {
if let Ok(pixel_scale) = GeoKeyParser::read_model_pixel_scale_values(ifd, byte_order_handler, file_path) {
if pixel_scale.len() >= 3 {
info!(" Pixel Size: X={:.6} Y={:.6} meters (Z={:.6})",
pixel_scale[0], pixel_scale[1], pixel_scale[2]);
}
}
}
/// Display tiepoint information
///
/// Shows the relationship between raster and map coordinates
/// from ModelTiepoint tag.
///
/// # Arguments
/// * `ifd` - The IFD containing GeoTIFF information
/// * `byte_order_handler` - Handler for interpreting byte order
/// * `file_path` - Path to the TIFF file
fn display_tiepoint(&self, ifd: &IFD,
byte_order_handler: &Box<dyn crate::io::byte_order::ByteOrderHandler>,
file_path: &str) {
if let Ok(tiepoint) = GeoKeyParser::read_model_tiepoint_values(ifd, byte_order_handler, file_path) {
if tiepoint.len() >= 6 {
info!(" Tiepoint: Raster({:.1},{:.1},{:.1}) → Map({:.6},{:.6},{:.6})",
tiepoint[0], tiepoint[1], tiepoint[2],
tiepoint[3], tiepoint[4], tiepoint[5]);
}
}
}
/// Display GeoKey directory information
///
/// Shows the GeoKey directory entries and provides additional
/// information for specific keys like ProjectedCSTypeGeoKey.
///
/// # Arguments
/// * `ifd` - The IFD containing GeoTIFF information
/// * `byte_order_handler` - Handler for interpreting byte order
/// * `file_path` - Path to the TIFF file
fn display_geokey_directory(&self, ifd: &IFD,
byte_order_handler: &Box<dyn crate::io::byte_order::ByteOrderHandler>,
file_path: &str) {
if let Ok(geo_key_data) = GeoKeyParser::format_geo_keys(ifd, byte_order_handler, file_path) {
if !geo_key_data.is_empty() {
info!(" GeoKey Directory:");
for (key_id, key_name, tiff_tag_location, count, value_offset, value_str) in &geo_key_data {
info!(" Key {} ({}): Location={}, Count={}, Value={}",
key_id, key_name, tiff_tag_location, count, value_str);
// Add extra information for certain keys
if *key_id == geo_keys::PROJECTED_CS_TYPE && *tiff_tag_location == 0 {
let code = *value_offset as u16;
info!(" → {}", get_projected_cs_description(code));
}
}
}
}
}
/// Display the PROJ.4 string
///
/// Generates and displays a PROJ.4 compatible projection string
/// based on the GeoTIFF information.
///
/// # Arguments
/// * `ifd` - The IFD containing GeoTIFF information
/// * `byte_order_handler` - Handler for interpreting byte order
/// * `file_path` - Path to the TIFF file
fn display_proj_string(&self, ifd: &IFD,
byte_order_handler: &Box<dyn crate::io::byte_order::ByteOrderHandler>,
file_path: &str) {
if let Ok(geo_info) = GeoKeyParser::extract_geo_info(ifd, byte_order_handler, file_path) {
let proj_string = GeoKeyParser::format_projection_string(&geo_info);
info!(" PROJ.4 String:");
info!(" {}", proj_string);
}
}
/// Display a summary of the first few tags
///
/// Shows detailed information for a subset of tags to avoid
/// overwhelming output for large IFDs.
///
/// # Arguments
/// * `ifd` - The IFD to summarize
fn display_tag_summary(&self, ifd: &IFD) {
let max_tags = 10;
info!(" First {} tags:", ifd.entries.len().min(max_tags));
for (j, entry) in ifd.entries.iter().take(max_tags).enumerate() {
debug!(" {}: Tag {} (type: {}, count: {}, value/offset: {})",
j, entry.tag, entry.field_type, entry.count, entry.value_offset);
}
if ifd.entries.len() > max_tags {
info!(" ... ({} more tags)", ifd.entries.len() - max_tags);
}
}
}
impl<'a> Command for AnalyzeCommand<'a> {
fn execute(&self) -> TiffResult<()> {
info!("Analyzing file: {}", self.input_file);
if self.verbose {
debug!("Verbose mode enabled");
}
// Create and use TIFF reader
let mut reader = TiffReader::new(self.logger);
let tiff = reader.load(&self.input_file)?;
// Display basic TIFF information
self.display_tiff_summary(&tiff);
// Variable to track if any GeoTIFF tags were found
let mut has_geotiff_tags = false;
// Process each IFD
for (i, ifd) in tiff.ifds.iter().enumerate() {
// Display basic IFD info
self.display_ifd_summary(ifd, i);
// Display compression info
self.display_compression_info(ifd);
// Display subfile type
self.display_subfile_type(ifd);
// Check and display GeoTIFF tags
let ifd_has_geotiff = self.display_geotiff_tags(ifd);
if ifd_has_geotiff {
has_geotiff_tags = true;
// Display detailed GeoTIFF info
self.display_geotiff_details(&reader, ifd);
}
// Display tag summary
self.display_tag_summary(ifd);
}
debug!("Analysis completed successfully");
self.logger.log("Analysis completed successfully")?;
Ok(())
}
}