1pub mod crs;
28pub mod error;
29pub mod geokeys;
30pub mod transform;
31
32#[cfg(feature = "cog")]
33pub mod cog;
34
35pub use error::{Error, Result};
36
37#[cfg(feature = "local")]
38use crs::CrsInfo;
39#[cfg(feature = "local")]
40use geokeys::GeoKeyDirectory;
41#[cfg(feature = "local")]
42use ndarray::ArrayD;
43#[cfg(feature = "local")]
44use std::collections::HashSet;
45#[cfg(feature = "local")]
46use std::path::Path;
47#[cfg(feature = "local")]
48use tiff_reader::{OpenOptions as TiffOpenOptions, TagValue, TiffFile, TiffSample};
49#[cfg(feature = "local")]
50use transform::GeoTransform;
51
52#[cfg(feature = "local")]
53use geotiff_core::tags::{
54 TAG_GDAL_NODATA, TAG_GEO_ASCII_PARAMS, TAG_GEO_DOUBLE_PARAMS, TAG_GEO_KEY_DIRECTORY,
55 TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, TAG_MODEL_TRANSFORMATION, TAG_NEW_SUBFILE_TYPE,
56 TAG_SUBFILE_TYPE,
57};
58
59#[cfg(feature = "local")]
61pub struct GeoTiffFile {
62 tiff: TiffFile,
63 geo_metadata: GeoMetadata,
64 crs: CrsInfo,
65 geokeys: GeoKeyDirectory,
66 transform: Option<GeoTransform>,
67 base_ifd_index: usize,
68 overview_ifds: Vec<GeoImageIfd>,
69}
70
71#[cfg(feature = "local")]
72#[derive(Debug, Clone)]
73struct GeoImageIfd {
74 top_level_ifd_index: Option<usize>,
75 ifd: tiff_reader::Ifd,
76}
77
78#[cfg(feature = "local")]
79pub use tiff_reader::OpenOptions as GeoTiffOpenOptions;
80
81pub use geotiff_core::GeoMetadata;
82
83#[cfg(feature = "local")]
84impl GeoTiffFile {
85 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
87 Self::open_with_options(path, TiffOpenOptions::default())
88 }
89
90 pub fn open_with_options<P: AsRef<Path>>(path: P, options: GeoTiffOpenOptions) -> Result<Self> {
92 let tiff = TiffFile::open_with_options(path, options)?;
93 Self::from_tiff(tiff)
94 }
95
96 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
98 Self::from_bytes_with_options(data, TiffOpenOptions::default())
99 }
100
101 pub fn from_bytes_with_options(data: Vec<u8>, options: GeoTiffOpenOptions) -> Result<Self> {
103 let tiff = TiffFile::from_bytes_with_options(data, options)?;
104 Self::from_tiff(tiff)
105 }
106
107 pub(crate) fn from_tiff(tiff: TiffFile) -> Result<Self> {
108 let metadata_ifd_index = find_metadata_ifd_index(tiff.ifds())?;
109 let metadata_ifd = tiff.ifd(metadata_ifd_index)?;
110 let geokeys = parse_geokey_directory(metadata_ifd)?;
111 let crs = CrsInfo::from_geokeys(&geokeys);
112 let epsg = crs.epsg();
113 let tiepoints = parse_tiepoints(metadata_ifd);
114 let pixel_scale = parse_fixed_len_double_tag::<3>(
115 metadata_ifd
116 .tag(TAG_MODEL_PIXEL_SCALE)
117 .map(|tag| &tag.value),
118 );
119 let transformation = parse_fixed_len_double_tag::<16>(
120 metadata_ifd
121 .tag(TAG_MODEL_TRANSFORMATION)
122 .map(|tag| &tag.value),
123 );
124 let transform = transformation
125 .as_ref()
126 .map(GeoTransform::from_transformation_matrix)
127 .or_else(|| {
128 let tiepoint = tiepoints.first()?;
129 let scale = pixel_scale.as_ref()?;
130 Some(GeoTransform::from_tiepoint_and_scale_with_raster_type(
131 tiepoint,
132 scale,
133 crs.raster_type_enum(),
134 ))
135 });
136 let base_ifd_index = find_base_ifd_index(tiff.ifds(), metadata_ifd_index);
137 let base_ifd = tiff.ifd(base_ifd_index)?;
138 let overview_ifds =
139 collect_overview_ifds(&tiff, base_ifd, base_ifd_index, metadata_ifd_index)?;
140 let geo_bounds = transform
141 .as_ref()
142 .map(|gt| gt.bounds(base_ifd.width(), base_ifd.height()));
143
144 let geo_metadata = GeoMetadata {
145 epsg,
146 tiepoints,
147 pixel_scale,
148 transformation,
149 nodata: parse_nodata(metadata_ifd),
150 band_count: base_ifd.samples_per_pixel() as u32,
151 width: base_ifd.width(),
152 height: base_ifd.height(),
153 geo_bounds,
154 };
155
156 Ok(Self {
157 tiff,
158 geo_metadata,
159 crs,
160 geokeys,
161 transform,
162 base_ifd_index,
163 overview_ifds,
164 })
165 }
166
167 pub fn tiff(&self) -> &TiffFile {
169 &self.tiff
170 }
171
172 pub fn metadata(&self) -> &GeoMetadata {
174 &self.geo_metadata
175 }
176
177 pub fn epsg(&self) -> Option<u32> {
179 self.geo_metadata.epsg
180 }
181
182 pub fn crs(&self) -> &CrsInfo {
184 &self.crs
185 }
186
187 pub fn geokeys(&self) -> &GeoKeyDirectory {
189 &self.geokeys
190 }
191
192 pub fn transform(&self) -> Option<&GeoTransform> {
194 self.transform.as_ref()
195 }
196
197 pub fn geo_bounds(&self) -> Option<[f64; 4]> {
199 self.geo_metadata.geo_bounds
200 }
201
202 pub fn pixel_to_geo(&self, col: f64, row: f64) -> Option<(f64, f64)> {
204 self.transform
205 .map(|transform| transform.pixel_to_geo(col, row))
206 }
207
208 pub fn geo_to_pixel(&self, x: f64, y: f64) -> Option<(f64, f64)> {
210 self.transform
211 .and_then(|transform| transform.geo_to_pixel(x, y))
212 }
213
214 pub fn width(&self) -> u32 {
216 self.geo_metadata.width
217 }
218
219 pub fn height(&self) -> u32 {
221 self.geo_metadata.height
222 }
223
224 pub fn band_count(&self) -> u32 {
226 self.geo_metadata.band_count
227 }
228
229 pub fn nodata(&self) -> Option<&str> {
231 self.geo_metadata.nodata.as_deref()
232 }
233
234 pub fn overview_count(&self) -> usize {
236 self.overview_ifds.len()
237 }
238
239 pub fn overview_ifd_index(&self, overview_index: usize) -> Result<usize> {
244 self.overview_ifds
245 .get(overview_index)
246 .ok_or(Error::OverviewNotFound(overview_index))?
247 .top_level_ifd_index
248 .ok_or(Error::OverviewHasNoTopLevelIfdIndex(overview_index))
249 }
250
251 pub fn overview_ifd(&self, overview_index: usize) -> Result<&tiff_reader::Ifd> {
253 self.overview_ifds
254 .get(overview_index)
255 .map(|overview| &overview.ifd)
256 .ok_or(Error::OverviewNotFound(overview_index))
257 }
258
259 pub fn base_ifd_index(&self) -> usize {
261 self.base_ifd_index
262 }
263
264 pub fn read_raster<T: TiffSample>(&self) -> Result<ArrayD<T>> {
266 self.tiff
267 .read_image::<T>(self.base_ifd_index)
268 .map_err(Into::into)
269 }
270
271 pub fn read_window<T: TiffSample>(
273 &self,
274 row_off: usize,
275 col_off: usize,
276 rows: usize,
277 cols: usize,
278 ) -> Result<ArrayD<T>> {
279 self.tiff
280 .read_window::<T>(self.base_ifd_index, row_off, col_off, rows, cols)
281 .map_err(Into::into)
282 }
283
284 pub fn read_band<T: TiffSample>(&self, band_index: usize) -> Result<ArrayD<T>> {
287 self.tiff
288 .read_band::<T>(self.base_ifd_index, band_index)
289 .map_err(Into::into)
290 }
291
292 pub fn read_band_window<T: TiffSample>(
295 &self,
296 band_index: usize,
297 row_off: usize,
298 col_off: usize,
299 rows: usize,
300 cols: usize,
301 ) -> Result<ArrayD<T>> {
302 self.tiff
303 .read_band_window::<T>(
304 self.base_ifd_index,
305 band_index,
306 row_off,
307 col_off,
308 rows,
309 cols,
310 )
311 .map_err(Into::into)
312 }
313
314 pub fn read_decoded_raster<T: TiffSample>(&self) -> Result<ArrayD<T>> {
316 self.tiff
317 .read_decoded_image::<T>(self.base_ifd_index)
318 .map_err(Into::into)
319 }
320
321 pub fn read_decoded_window<T: TiffSample>(
323 &self,
324 row_off: usize,
325 col_off: usize,
326 rows: usize,
327 cols: usize,
328 ) -> Result<ArrayD<T>> {
329 self.tiff
330 .read_decoded_window::<T>(self.base_ifd_index, row_off, col_off, rows, cols)
331 .map_err(Into::into)
332 }
333
334 pub fn read_raster_samples<T: TiffSample>(&self) -> Result<ArrayD<T>> {
338 self.tiff
339 .read_image_samples::<T>(self.base_ifd_index)
340 .map_err(Into::into)
341 }
342
343 pub fn read_window_samples<T: TiffSample>(
347 &self,
348 row_off: usize,
349 col_off: usize,
350 rows: usize,
351 cols: usize,
352 ) -> Result<ArrayD<T>> {
353 self.tiff
354 .read_window_samples::<T>(self.base_ifd_index, row_off, col_off, rows, cols)
355 .map_err(Into::into)
356 }
357
358 pub fn read_overview<T: TiffSample>(&self, overview_index: usize) -> Result<ArrayD<T>> {
360 let overview = self
361 .overview_ifds
362 .get(overview_index)
363 .ok_or(Error::OverviewNotFound(overview_index))?;
364 self.tiff
365 .read_image_from_ifd::<T>(&overview.ifd)
366 .map_err(Into::into)
367 }
368
369 pub fn read_decoded_overview<T: TiffSample>(&self, overview_index: usize) -> Result<ArrayD<T>> {
371 let overview = self
372 .overview_ifds
373 .get(overview_index)
374 .ok_or(Error::OverviewNotFound(overview_index))?;
375 self.tiff
376 .read_decoded_image_from_ifd::<T>(&overview.ifd)
377 .map_err(Into::into)
378 }
379
380 pub fn read_overview_samples<T: TiffSample>(&self, overview_index: usize) -> Result<ArrayD<T>> {
384 let overview = self
385 .overview_ifds
386 .get(overview_index)
387 .ok_or(Error::OverviewNotFound(overview_index))?;
388 self.tiff
389 .read_image_samples_from_ifd::<T>(&overview.ifd)
390 .map_err(Into::into)
391 }
392
393 pub fn read_overview_band<T: TiffSample>(
396 &self,
397 overview_index: usize,
398 band_index: usize,
399 ) -> Result<ArrayD<T>> {
400 let overview = self
401 .overview_ifds
402 .get(overview_index)
403 .ok_or(Error::OverviewNotFound(overview_index))?;
404 self.tiff
405 .read_band_from_ifd::<T>(&overview.ifd, band_index)
406 .map_err(Into::into)
407 }
408
409 pub fn read_overview_window<T: TiffSample>(
411 &self,
412 overview_index: usize,
413 row_off: usize,
414 col_off: usize,
415 rows: usize,
416 cols: usize,
417 ) -> Result<ArrayD<T>> {
418 let overview = self
419 .overview_ifds
420 .get(overview_index)
421 .ok_or(Error::OverviewNotFound(overview_index))?;
422 self.tiff
423 .read_window_from_ifd::<T>(&overview.ifd, row_off, col_off, rows, cols)
424 .map_err(Into::into)
425 }
426
427 pub fn read_decoded_overview_window<T: TiffSample>(
429 &self,
430 overview_index: usize,
431 row_off: usize,
432 col_off: usize,
433 rows: usize,
434 cols: usize,
435 ) -> Result<ArrayD<T>> {
436 let overview = self
437 .overview_ifds
438 .get(overview_index)
439 .ok_or(Error::OverviewNotFound(overview_index))?;
440 self.tiff
441 .read_decoded_window_from_ifd::<T>(&overview.ifd, row_off, col_off, rows, cols)
442 .map_err(Into::into)
443 }
444
445 pub fn read_overview_band_window<T: TiffSample>(
448 &self,
449 overview_index: usize,
450 band_index: usize,
451 row_off: usize,
452 col_off: usize,
453 rows: usize,
454 cols: usize,
455 ) -> Result<ArrayD<T>> {
456 let overview = self
457 .overview_ifds
458 .get(overview_index)
459 .ok_or(Error::OverviewNotFound(overview_index))?;
460 self.tiff
461 .read_band_window_from_ifd::<T>(&overview.ifd, band_index, row_off, col_off, rows, cols)
462 .map_err(Into::into)
463 }
464
465 pub fn read_overview_window_samples<T: TiffSample>(
469 &self,
470 overview_index: usize,
471 row_off: usize,
472 col_off: usize,
473 rows: usize,
474 cols: usize,
475 ) -> Result<ArrayD<T>> {
476 let overview = self
477 .overview_ifds
478 .get(overview_index)
479 .ok_or(Error::OverviewNotFound(overview_index))?;
480 self.tiff
481 .read_window_samples_from_ifd::<T>(&overview.ifd, row_off, col_off, rows, cols)
482 .map_err(Into::into)
483 }
484}
485
486#[cfg(feature = "local")]
487fn is_overview_ifd(base: &tiff_reader::Ifd, candidate: &tiff_reader::Ifd) -> bool {
488 let smaller = candidate.width() < base.width() || candidate.height() < base.height();
489 if !smaller {
490 return false;
491 }
492
493 let same_layout = candidate.samples_per_pixel() == base.samples_per_pixel()
494 && candidate.bits_per_sample() == base.bits_per_sample()
495 && candidate.sample_format() == base.sample_format()
496 && candidate.photometric_interpretation() == base.photometric_interpretation();
497 if !same_layout {
498 return false;
499 }
500
501 has_reduced_resolution_flag(candidate)
502 || (candidate.tag(TAG_NEW_SUBFILE_TYPE).is_none()
503 && candidate.tag(TAG_SUBFILE_TYPE).is_none())
504}
505
506#[cfg(feature = "local")]
507fn collect_overview_ifds(
508 tiff: &TiffFile,
509 base_ifd: &tiff_reader::Ifd,
510 base_ifd_index: usize,
511 metadata_ifd_index: usize,
512) -> Result<Vec<GeoImageIfd>> {
513 let mut overviews: Vec<GeoImageIfd> = tiff
514 .ifds()
515 .iter()
516 .enumerate()
517 .filter(|(index, candidate)| {
518 *index != base_ifd_index
519 && *index != metadata_ifd_index
520 && is_overview_ifd(base_ifd, candidate)
521 })
522 .map(|(index, candidate)| GeoImageIfd {
523 top_level_ifd_index: Some(index),
524 ifd: candidate.clone(),
525 })
526 .collect();
527
528 if let Some(offsets) = base_ifd.sub_ifd_offsets() {
529 let mut seen_offsets = HashSet::new();
530 collect_subifd_overviews(tiff, base_ifd, &offsets, &mut seen_offsets, &mut overviews)?;
531 }
532
533 overviews.sort_by(|lhs, rhs| {
534 rhs.ifd
535 .width()
536 .cmp(&lhs.ifd.width())
537 .then_with(|| rhs.ifd.height().cmp(&lhs.ifd.height()))
538 .then_with(|| lhs.top_level_ifd_index.cmp(&rhs.top_level_ifd_index))
539 });
540
541 Ok(overviews)
542}
543
544#[cfg(feature = "local")]
545fn collect_subifd_overviews(
546 tiff: &TiffFile,
547 base_ifd: &tiff_reader::Ifd,
548 offsets: &[u64],
549 seen_offsets: &mut HashSet<u64>,
550 overviews: &mut Vec<GeoImageIfd>,
551) -> Result<()> {
552 for &offset in offsets {
553 if !seen_offsets.insert(offset) {
554 continue;
555 }
556
557 let candidate = tiff.read_ifd_at_offset(offset)?;
558 if is_overview_ifd(base_ifd, &candidate) {
559 overviews.push(GeoImageIfd {
560 top_level_ifd_index: None,
561 ifd: candidate.clone(),
562 });
563 }
564 if let Some(child_offsets) = candidate.sub_ifd_offsets() {
565 collect_subifd_overviews(tiff, base_ifd, &child_offsets, seen_offsets, overviews)?;
566 }
567 }
568 Ok(())
569}
570
571#[cfg(feature = "local")]
572fn find_metadata_ifd_index(ifds: &[tiff_reader::Ifd]) -> Result<usize> {
573 ifds.iter()
574 .position(|ifd| ifd.tag(TAG_GEO_KEY_DIRECTORY).is_some())
575 .or_else(|| ifds.iter().position(has_model_georeferencing))
576 .ok_or(Error::NotGeoTiff)
577}
578
579#[cfg(feature = "local")]
580fn has_model_georeferencing(ifd: &tiff_reader::Ifd) -> bool {
581 ifd.tag(TAG_MODEL_TRANSFORMATION).is_some()
582 || (ifd.tag(TAG_MODEL_TIEPOINT).is_some() && ifd.tag(TAG_MODEL_PIXEL_SCALE).is_some())
583}
584
585#[cfg(feature = "local")]
586fn find_base_ifd_index(ifds: &[tiff_reader::Ifd], metadata_ifd_index: usize) -> usize {
587 let metadata_ifd = &ifds[metadata_ifd_index];
588 if !has_reduced_resolution_flag(metadata_ifd) {
589 return metadata_ifd_index;
590 }
591
592 ifds.iter()
593 .enumerate()
594 .skip(metadata_ifd_index + 1)
595 .find_map(|(index, ifd)| (!has_reduced_resolution_flag(ifd)).then_some(index))
596 .unwrap_or(metadata_ifd_index)
597}
598
599#[cfg(feature = "local")]
600fn has_reduced_resolution_flag(ifd: &tiff_reader::Ifd) -> bool {
601 ifd.tag(TAG_NEW_SUBFILE_TYPE)
602 .and_then(|tag| tag.value.as_u64())
603 .map(|flags| flags & 0x1 != 0)
604 .or_else(|| {
605 ifd.tag(TAG_SUBFILE_TYPE)
606 .and_then(|tag| tag.value.as_u16())
607 .map(|value| value == 2)
608 })
609 .unwrap_or(false)
610}
611
612#[cfg(feature = "local")]
613fn parse_geokey_directory(ifd: &tiff_reader::Ifd) -> Result<GeoKeyDirectory> {
614 let Some(directory) = ifd
615 .tag(TAG_GEO_KEY_DIRECTORY)
616 .and_then(|tag| match &tag.value {
617 TagValue::Short(values) => Some(values.as_slice()),
618 _ => None,
619 })
620 else {
621 return Ok(GeoKeyDirectory::new());
622 };
623 let double_params = ifd
624 .tag(TAG_GEO_DOUBLE_PARAMS)
625 .and_then(|tag| tag.value.as_f64_vec())
626 .unwrap_or_default();
627 let ascii_params = ifd
628 .tag(TAG_GEO_ASCII_PARAMS)
629 .and_then(|tag| tag.value.as_str())
630 .unwrap_or("");
631 GeoKeyDirectory::parse(directory, &double_params, ascii_params)
632 .ok_or(Error::InvalidGeoKeyDirectory)
633}
634
635#[cfg(feature = "local")]
636fn parse_fixed_len_double_tag<const N: usize>(value: Option<&TagValue>) -> Option<[f64; N]> {
637 let values = value.and_then(TagValue::as_f64_vec)?;
638 if values.len() < N {
639 return None;
640 }
641 let mut out = [0.0; N];
642 out.copy_from_slice(&values[..N]);
643 Some(out)
644}
645
646#[cfg(feature = "local")]
647fn parse_tiepoints(ifd: &tiff_reader::Ifd) -> Vec<[f64; 6]> {
648 let values = ifd
649 .tag(TAG_MODEL_TIEPOINT)
650 .and_then(|tag| tag.value.as_f64_vec())
651 .unwrap_or_default();
652 values
653 .chunks_exact(6)
654 .map(|chunk| [chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5]])
655 .collect()
656}
657
658#[cfg(feature = "local")]
659fn parse_nodata(ifd: &tiff_reader::Ifd) -> Option<String> {
660 ifd.tag(TAG_GDAL_NODATA)
661 .and_then(|tag| tag.value.as_str())
662 .map(ToOwned::to_owned)
663}
664
665#[cfg(test)]
666#[cfg(feature = "local")]
667mod tests {
668 use super::GeoTiffFile;
669
670 #[derive(Clone)]
671 struct TestIfdSpec {
672 entries: Vec<(u16, u16, u32, Vec<u8>)>,
673 image_data: Vec<u8>,
674 }
675
676 fn le_u16(value: u16) -> [u8; 2] {
677 value.to_le_bytes()
678 }
679
680 fn le_u32(value: u32) -> [u8; 4] {
681 value.to_le_bytes()
682 }
683
684 fn le_f64(value: f64) -> [u8; 8] {
685 value.to_le_bytes()
686 }
687
688 fn inline_short(value: u16) -> Vec<u8> {
689 let mut bytes = [0u8; 4];
690 bytes[..2].copy_from_slice(&le_u16(value));
691 bytes.to_vec()
692 }
693
694 #[allow(clippy::too_many_arguments)]
695 fn build_lerc2_header_v2(
696 width: u32,
697 height: u32,
698 valid_pixel_count: u32,
699 image_type: i32,
700 max_z_error: f64,
701 z_min: f64,
702 z_max: f64,
703 payload_len: usize,
704 ) -> Vec<u8> {
705 let blob_size = 58 + 4 + payload_len;
706 let mut bytes = Vec::with_capacity(blob_size);
707 bytes.extend_from_slice(b"Lerc2 ");
708 bytes.extend_from_slice(&2i32.to_le_bytes());
709 bytes.extend_from_slice(&height.to_le_bytes());
710 bytes.extend_from_slice(&width.to_le_bytes());
711 bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
712 bytes.extend_from_slice(&8i32.to_le_bytes());
713 bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
714 bytes.extend_from_slice(&image_type.to_le_bytes());
715 bytes.extend_from_slice(&max_z_error.to_le_bytes());
716 bytes.extend_from_slice(&z_min.to_le_bytes());
717 bytes.extend_from_slice(&z_max.to_le_bytes());
718 bytes
719 }
720
721 fn build_classic_tiff(ifds: &[TestIfdSpec]) -> Vec<u8> {
722 let mut ifd_offsets = Vec::with_capacity(ifds.len());
723 let mut cursor = 8usize;
724 for ifd in ifds {
725 ifd_offsets.push(cursor as u32);
726 let deferred_len: usize = ifd
727 .entries
728 .iter()
729 .filter(|(tag, _, _, value)| *tag != 273 && value.len() > 4)
730 .map(|(_, _, _, value)| value.len())
731 .sum();
732 cursor += 2 + ifd.entries.len() * 12 + 4 + ifd.image_data.len() + deferred_len;
733 }
734
735 let mut bytes = Vec::with_capacity(cursor);
736 bytes.extend_from_slice(b"II");
737 bytes.extend_from_slice(&le_u16(42));
738 bytes.extend_from_slice(&le_u32(ifd_offsets.first().copied().unwrap_or(0)));
739
740 for (ifd_index, ifd) in ifds.iter().enumerate() {
741 let ifd_offset = ifd_offsets[ifd_index] as usize;
742 debug_assert_eq!(bytes.len(), ifd_offset);
743
744 let ifd_size = 2 + ifd.entries.len() * 12 + 4;
745 let mut next_data_offset = ifd_offset + ifd_size;
746 let image_offset = next_data_offset as u32;
747 next_data_offset += ifd.image_data.len();
748
749 bytes.extend_from_slice(&le_u16(ifd.entries.len() as u16));
750 let mut deferred = Vec::new();
751 for (tag, ty, count, value) in &ifd.entries {
752 bytes.extend_from_slice(&le_u16(*tag));
753 bytes.extend_from_slice(&le_u16(*ty));
754 bytes.extend_from_slice(&le_u32(*count));
755 if *tag == 273 {
756 bytes.extend_from_slice(&le_u32(image_offset));
757 } else if value.len() <= 4 {
758 let mut inline = [0u8; 4];
759 inline[..value.len()].copy_from_slice(value);
760 bytes.extend_from_slice(&inline);
761 } else {
762 bytes.extend_from_slice(&le_u32(next_data_offset as u32));
763 next_data_offset += value.len();
764 deferred.push(value.clone());
765 }
766 }
767
768 let next_ifd_offset = ifd_offsets.get(ifd_index + 1).copied().unwrap_or(0);
769 bytes.extend_from_slice(&le_u32(next_ifd_offset));
770 bytes.extend_from_slice(&ifd.image_data);
771 for value in deferred {
772 bytes.extend_from_slice(&value);
773 }
774 debug_assert_eq!(bytes.len(), next_data_offset);
775 }
776
777 bytes
778 }
779
780 fn build_simple_geotiff(pixel_is_point: bool) -> Vec<u8> {
781 let image_data = vec![10u8, 20, 30, 40];
782 let tiepoints = [0.0, 0.0, 0.0, 100.0, 200.0, 0.0];
783 let scales = [2.0, 2.0, 0.0];
784 let geo_keys = if pixel_is_point {
785 vec![
786 1, 1, 0, 3, 1024, 0, 1, 2, 1025, 0, 1, 2, 2048, 0, 1, 4326, ]
791 } else {
792 vec![
793 1, 1, 0, 2, 1024, 0, 1, 2, 2048, 0, 1, 4326, ]
797 };
798 let nodata = b"-9999\0".to_vec();
799
800 build_classic_tiff(&[TestIfdSpec {
801 image_data,
802 entries: vec![
803 (256u16, 4u16, 1u32, le_u32(2).to_vec()),
804 (257u16, 4u16, 1u32, le_u32(2).to_vec()),
805 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
806 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
807 (273u16, 4u16, 1u32, vec![]),
808 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
809 (278u16, 4u16, 1u32, le_u32(2).to_vec()),
810 (279u16, 4u16, 1u32, le_u32(4).to_vec()),
811 (
812 33550u16,
813 12u16,
814 3u32,
815 scales.iter().flat_map(|value| le_f64(*value)).collect(),
816 ),
817 (
818 33922u16,
819 12u16,
820 6u32,
821 tiepoints.iter().flat_map(|value| le_f64(*value)).collect(),
822 ),
823 (
824 34735u16,
825 3u16,
826 geo_keys.len() as u32,
827 geo_keys.iter().flat_map(|value| le_u16(*value)).collect(),
828 ),
829 (42113u16, 2u16, nodata.len() as u32, nodata),
830 ],
831 }])
832 }
833
834 fn build_simple_lerc_geotiff() -> Vec<u8> {
835 let tiepoints = [0.0, 0.0, 0.0, 100.0, 200.0, 0.0];
836 let scales = [2.0, 2.0, 0.0];
837 let geo_keys = vec![
838 1, 1, 0, 2, 1024, 0, 1, 2, 2048, 0, 1, 4326, ];
842
843 let mut image_data = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
844 image_data.extend_from_slice(&0u32.to_le_bytes());
845 image_data.push(1);
846 for value in [1.0f32, 2.0, 3.0, 4.0] {
847 image_data.extend_from_slice(&value.to_le_bytes());
848 }
849 let image_len = image_data.len() as u32;
850
851 build_classic_tiff(&[TestIfdSpec {
852 image_data,
853 entries: vec![
854 (256u16, 4u16, 1u32, le_u32(2).to_vec()),
855 (257u16, 4u16, 1u32, le_u32(2).to_vec()),
856 (258u16, 3u16, 1u32, inline_short(32)),
857 (259u16, 3u16, 1u32, inline_short(34887)),
858 (273u16, 4u16, 1u32, vec![]),
859 (277u16, 3u16, 1u32, inline_short(1)),
860 (278u16, 4u16, 1u32, le_u32(2).to_vec()),
861 (279u16, 4u16, 1u32, le_u32(image_len).to_vec()),
862 (339u16, 3u16, 1u32, inline_short(3)),
863 (
864 33550u16,
865 12u16,
866 3u32,
867 scales.iter().flat_map(|value| le_f64(*value)).collect(),
868 ),
869 (
870 33922u16,
871 12u16,
872 6u32,
873 tiepoints.iter().flat_map(|value| le_f64(*value)).collect(),
874 ),
875 (
876 34735u16,
877 3u16,
878 geo_keys.len() as u32,
879 geo_keys.iter().flat_map(|value| le_u16(*value)).collect(),
880 ),
881 ],
882 }])
883 }
884
885 fn overwrite_classic_inline_long_tag(bytes: &mut [u8], tag_code: u16, value: u32) {
886 let entry_count = u16::from_le_bytes([bytes[8], bytes[9]]) as usize;
887 let mut offset = 10usize;
888 for _ in 0..entry_count {
889 let code = u16::from_le_bytes([bytes[offset], bytes[offset + 1]]);
890 if code == tag_code {
891 bytes[offset + 8..offset + 12].copy_from_slice(&le_u32(value));
892 return;
893 }
894 offset += 12;
895 }
896 panic!("tag {tag_code} not found in classic TIFF");
897 }
898
899 fn overwrite_first_ifd_next_pointer(bytes: &mut [u8], value: u32) {
900 let entry_count = u16::from_le_bytes([bytes[8], bytes[9]]) as usize;
901 let pointer_offset = 10 + entry_count * 12;
902 bytes[pointer_offset..pointer_offset + 4].copy_from_slice(&le_u32(value));
903 }
904
905 fn overwrite_classic_inline_long_tag_at(
906 bytes: &mut [u8],
907 ifd_offset: usize,
908 tag_code: u16,
909 value: u32,
910 ) {
911 let entry_count = u16::from_le_bytes([bytes[ifd_offset], bytes[ifd_offset + 1]]) as usize;
912 let mut offset = ifd_offset + 2;
913 for _ in 0..entry_count {
914 let code = u16::from_le_bytes([bytes[offset], bytes[offset + 1]]);
915 if code == tag_code {
916 bytes[offset + 8..offset + 12].copy_from_slice(&le_u32(value));
917 return;
918 }
919 offset += 12;
920 }
921 panic!("tag {tag_code} not found in classic TIFF at offset {ifd_offset}");
922 }
923
924 fn first_ifd_next_pointer(bytes: &[u8]) -> u32 {
925 let entry_count = u16::from_le_bytes([bytes[8], bytes[9]]) as usize;
926 let pointer_offset = 10 + entry_count * 12;
927 u32::from_le_bytes([
928 bytes[pointer_offset],
929 bytes[pointer_offset + 1],
930 bytes[pointer_offset + 2],
931 bytes[pointer_offset + 3],
932 ])
933 }
934
935 fn ifd_next_pointer(bytes: &[u8], ifd_offset: usize) -> u32 {
936 let entry_count = u16::from_le_bytes([bytes[ifd_offset], bytes[ifd_offset + 1]]) as usize;
937 let pointer_offset = ifd_offset + 2 + entry_count * 12;
938 u32::from_le_bytes([
939 bytes[pointer_offset],
940 bytes[pointer_offset + 1],
941 bytes[pointer_offset + 2],
942 bytes[pointer_offset + 3],
943 ])
944 }
945
946 fn build_geotiff_with_overview() -> Vec<u8> {
947 let base = TestIfdSpec {
948 image_data: vec![10u8, 20, 30, 40],
949 entries: vec![
950 (256u16, 4u16, 1u32, le_u32(2).to_vec()),
951 (257u16, 4u16, 1u32, le_u32(2).to_vec()),
952 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
953 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
954 (273u16, 4u16, 1u32, vec![]),
955 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
956 (278u16, 4u16, 1u32, le_u32(2).to_vec()),
957 (279u16, 4u16, 1u32, le_u32(4).to_vec()),
958 (
959 33550u16,
960 12u16,
961 3u32,
962 [2.0, 2.0, 0.0]
963 .iter()
964 .flat_map(|value| le_f64(*value))
965 .collect(),
966 ),
967 (
968 33922u16,
969 12u16,
970 6u32,
971 [0.0, 0.0, 0.0, 100.0, 200.0, 0.0]
972 .iter()
973 .flat_map(|value| le_f64(*value))
974 .collect(),
975 ),
976 (
977 34735u16,
978 3u16,
979 12u32,
980 [1u16, 1, 0, 2, 1024, 0, 1, 2, 2048, 0, 1, 4326]
981 .iter()
982 .flat_map(|value| le_u16(*value))
983 .collect(),
984 ),
985 ],
986 };
987 let overview = TestIfdSpec {
988 image_data: vec![99u8],
989 entries: vec![
990 (254u16, 4u16, 1u32, le_u32(1).to_vec()),
991 (256u16, 4u16, 1u32, le_u32(1).to_vec()),
992 (257u16, 4u16, 1u32, le_u32(1).to_vec()),
993 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
994 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
995 (273u16, 4u16, 1u32, vec![]),
996 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
997 (278u16, 4u16, 1u32, le_u32(1).to_vec()),
998 (279u16, 4u16, 1u32, le_u32(1).to_vec()),
999 ],
1000 };
1001
1002 build_classic_tiff(&[base, overview])
1003 }
1004
1005 fn build_geotiff_with_subifd_overview() -> Vec<u8> {
1006 let base = TestIfdSpec {
1007 image_data: vec![10u8, 20, 30, 40],
1008 entries: vec![
1009 (256u16, 4u16, 1u32, le_u32(2).to_vec()),
1010 (257u16, 4u16, 1u32, le_u32(2).to_vec()),
1011 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1012 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1013 (273u16, 4u16, 1u32, vec![]),
1014 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1015 (278u16, 4u16, 1u32, le_u32(2).to_vec()),
1016 (279u16, 4u16, 1u32, le_u32(4).to_vec()),
1017 (330u16, 4u16, 1u32, le_u32(0).to_vec()),
1018 (
1019 33550u16,
1020 12u16,
1021 3u32,
1022 [2.0, 2.0, 0.0]
1023 .iter()
1024 .flat_map(|value| le_f64(*value))
1025 .collect(),
1026 ),
1027 (
1028 33922u16,
1029 12u16,
1030 6u32,
1031 [0.0, 0.0, 0.0, 100.0, 200.0, 0.0]
1032 .iter()
1033 .flat_map(|value| le_f64(*value))
1034 .collect(),
1035 ),
1036 (
1037 34735u16,
1038 3u16,
1039 12u32,
1040 [1u16, 1, 0, 2, 1024, 0, 1, 2, 2048, 0, 1, 4326]
1041 .iter()
1042 .flat_map(|value| le_u16(*value))
1043 .collect(),
1044 ),
1045 ],
1046 };
1047 let overview = TestIfdSpec {
1048 image_data: vec![99u8],
1049 entries: vec![
1050 (254u16, 4u16, 1u32, le_u32(1).to_vec()),
1051 (256u16, 4u16, 1u32, le_u32(1).to_vec()),
1052 (257u16, 4u16, 1u32, le_u32(1).to_vec()),
1053 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1054 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1055 (273u16, 4u16, 1u32, vec![]),
1056 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1057 (278u16, 4u16, 1u32, le_u32(1).to_vec()),
1058 (279u16, 4u16, 1u32, le_u32(1).to_vec()),
1059 ],
1060 };
1061
1062 let mut bytes = build_classic_tiff(&[base, overview]);
1063 let child_ifd_offset = first_ifd_next_pointer(&bytes);
1064 overwrite_classic_inline_long_tag(&mut bytes, 330, child_ifd_offset);
1065 overwrite_first_ifd_next_pointer(&mut bytes, 0);
1066 bytes
1067 }
1068
1069 fn build_geotiff_with_nested_subifd_overviews() -> Vec<u8> {
1070 let base = TestIfdSpec {
1071 image_data: (1u8..=16).collect(),
1072 entries: vec![
1073 (256u16, 4u16, 1u32, le_u32(4).to_vec()),
1074 (257u16, 4u16, 1u32, le_u32(4).to_vec()),
1075 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1076 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1077 (273u16, 4u16, 1u32, vec![]),
1078 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1079 (278u16, 4u16, 1u32, le_u32(4).to_vec()),
1080 (279u16, 4u16, 1u32, le_u32(16).to_vec()),
1081 (330u16, 4u16, 1u32, le_u32(0).to_vec()),
1082 (
1083 33550u16,
1084 12u16,
1085 3u32,
1086 [2.0, 2.0, 0.0]
1087 .iter()
1088 .flat_map(|value| le_f64(*value))
1089 .collect(),
1090 ),
1091 (
1092 33922u16,
1093 12u16,
1094 6u32,
1095 [0.0, 0.0, 0.0, 100.0, 200.0, 0.0]
1096 .iter()
1097 .flat_map(|value| le_f64(*value))
1098 .collect(),
1099 ),
1100 (
1101 34735u16,
1102 3u16,
1103 12u32,
1104 [1u16, 1, 0, 2, 1024, 0, 1, 2, 2048, 0, 1, 4326]
1105 .iter()
1106 .flat_map(|value| le_u16(*value))
1107 .collect(),
1108 ),
1109 ],
1110 };
1111 let overview = TestIfdSpec {
1112 image_data: vec![50u8, 60, 70, 80],
1113 entries: vec![
1114 (254u16, 4u16, 1u32, le_u32(1).to_vec()),
1115 (256u16, 4u16, 1u32, le_u32(2).to_vec()),
1116 (257u16, 4u16, 1u32, le_u32(2).to_vec()),
1117 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1118 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1119 (273u16, 4u16, 1u32, vec![]),
1120 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1121 (278u16, 4u16, 1u32, le_u32(2).to_vec()),
1122 (279u16, 4u16, 1u32, le_u32(4).to_vec()),
1123 (330u16, 4u16, 1u32, le_u32(0).to_vec()),
1124 ],
1125 };
1126 let nested = TestIfdSpec {
1127 image_data: vec![99u8],
1128 entries: vec![
1129 (254u16, 4u16, 1u32, le_u32(1).to_vec()),
1130 (256u16, 4u16, 1u32, le_u32(1).to_vec()),
1131 (257u16, 4u16, 1u32, le_u32(1).to_vec()),
1132 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1133 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1134 (273u16, 4u16, 1u32, vec![]),
1135 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1136 (278u16, 4u16, 1u32, le_u32(1).to_vec()),
1137 (279u16, 4u16, 1u32, le_u32(1).to_vec()),
1138 ],
1139 };
1140
1141 let mut bytes = build_classic_tiff(&[base, overview, nested]);
1142 let child_ifd_offset = first_ifd_next_pointer(&bytes);
1143 let grandchild_ifd_offset = ifd_next_pointer(&bytes, child_ifd_offset as usize);
1144 overwrite_classic_inline_long_tag(&mut bytes, 330, child_ifd_offset);
1145 overwrite_classic_inline_long_tag_at(
1146 &mut bytes,
1147 child_ifd_offset as usize,
1148 330,
1149 grandchild_ifd_offset,
1150 );
1151 overwrite_first_ifd_next_pointer(&mut bytes, 0);
1152 bytes
1153 }
1154
1155 fn build_cog_like_geotiff_with_ghost_ifd() -> Vec<u8> {
1156 let geo_keys = [1u16, 1, 0, 2, 1024, 0, 1, 2, 2048, 0, 1, 4326];
1157 let ghost = TestIfdSpec {
1158 image_data: vec![0u8],
1159 entries: vec![
1160 (254u16, 4u16, 1u32, le_u32(1).to_vec()),
1161 (256u16, 4u16, 1u32, le_u32(1).to_vec()),
1162 (257u16, 4u16, 1u32, le_u32(1).to_vec()),
1163 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1164 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1165 (273u16, 4u16, 1u32, vec![]),
1166 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1167 (278u16, 4u16, 1u32, le_u32(1).to_vec()),
1168 (279u16, 4u16, 1u32, le_u32(1).to_vec()),
1169 (
1170 33550u16,
1171 12u16,
1172 3u32,
1173 [2.0, 2.0, 0.0]
1174 .iter()
1175 .flat_map(|value| le_f64(*value))
1176 .collect(),
1177 ),
1178 (
1179 33922u16,
1180 12u16,
1181 6u32,
1182 [0.0, 0.0, 0.0, 100.0, 200.0, 0.0]
1183 .iter()
1184 .flat_map(|value| le_f64(*value))
1185 .collect(),
1186 ),
1187 (
1188 34735u16,
1189 3u16,
1190 geo_keys.len() as u32,
1191 geo_keys.iter().flat_map(|value| le_u16(*value)).collect(),
1192 ),
1193 ],
1194 };
1195 let overview = TestIfdSpec {
1196 image_data: vec![50u8, 60, 70, 80],
1197 entries: vec![
1198 (254u16, 4u16, 1u32, le_u32(1).to_vec()),
1199 (256u16, 4u16, 1u32, le_u32(2).to_vec()),
1200 (257u16, 4u16, 1u32, le_u32(2).to_vec()),
1201 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1202 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1203 (273u16, 4u16, 1u32, vec![]),
1204 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1205 (278u16, 4u16, 1u32, le_u32(2).to_vec()),
1206 (279u16, 4u16, 1u32, le_u32(4).to_vec()),
1207 ],
1208 };
1209 let base = TestIfdSpec {
1210 image_data: (1u8..=16).collect(),
1211 entries: vec![
1212 (256u16, 4u16, 1u32, le_u32(4).to_vec()),
1213 (257u16, 4u16, 1u32, le_u32(4).to_vec()),
1214 (258u16, 3u16, 1u32, [8, 0, 0, 0].to_vec()),
1215 (259u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1216 (273u16, 4u16, 1u32, vec![]),
1217 (277u16, 3u16, 1u32, [1, 0, 0, 0].to_vec()),
1218 (278u16, 4u16, 1u32, le_u32(4).to_vec()),
1219 (279u16, 4u16, 1u32, le_u32(16).to_vec()),
1220 ],
1221 };
1222
1223 build_classic_tiff(&[ghost, overview, base])
1224 }
1225
1226 #[test]
1227 fn parses_geotiff_metadata_and_reads_raster() {
1228 let file = GeoTiffFile::from_bytes(build_simple_geotiff(false)).unwrap();
1229 assert_eq!(file.epsg(), Some(4326));
1230 assert_eq!(file.width(), 2);
1231 assert_eq!(file.height(), 2);
1232 assert_eq!(file.band_count(), 1);
1233 assert_eq!(file.nodata(), Some("-9999"));
1234 assert_eq!(file.geo_bounds(), Some([100.0, 196.0, 104.0, 200.0]));
1235
1236 let raster = file.read_raster::<u8>().unwrap();
1237 assert_eq!(raster.shape(), &[2, 2]);
1238 let (values, offset) = raster.into_raw_vec_and_offset();
1239 assert_eq!(offset, Some(0));
1240 assert_eq!(values, vec![10, 20, 30, 40]);
1241 }
1242
1243 #[test]
1244 fn parses_geotiff_metadata_and_reads_lerc_raster() {
1245 let file = GeoTiffFile::from_bytes(build_simple_lerc_geotiff()).unwrap();
1246 assert_eq!(file.epsg(), Some(4326));
1247 assert_eq!(file.width(), 2);
1248 assert_eq!(file.height(), 2);
1249
1250 let raster = file.read_raster::<f32>().unwrap();
1251 assert_eq!(raster.shape(), &[2, 2]);
1252 let (values, offset) = raster.into_raw_vec_and_offset();
1253 assert_eq!(offset, Some(0));
1254 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
1255 }
1256
1257 #[test]
1258 fn pixel_is_point_metadata_shifts_bounds_to_outer_edges() {
1259 let file = GeoTiffFile::from_bytes(build_simple_geotiff(true)).unwrap();
1260 assert_eq!(file.geo_bounds(), Some([99.0, 197.0, 103.0, 201.0]));
1261
1262 let transform = file.transform().unwrap();
1263 let (center_x, center_y) = transform.pixel_to_geo(0.5, 0.5);
1264 assert_eq!((center_x, center_y), (100.0, 200.0));
1265 }
1266
1267 #[test]
1268 fn discovers_reduced_resolution_overviews() {
1269 let file = GeoTiffFile::from_bytes(build_geotiff_with_overview()).unwrap();
1270 assert_eq!(file.overview_count(), 1);
1271 assert_eq!(file.overview_ifd_index(0).unwrap(), 1);
1272
1273 let overview = file.read_overview::<u8>(0).unwrap();
1274 assert_eq!(overview.shape(), &[1, 1]);
1275 let (values, offset) = overview.into_raw_vec_and_offset();
1276 assert_eq!(offset, Some(0));
1277 assert_eq!(values, vec![99]);
1278 }
1279
1280 #[test]
1281 fn discovers_and_reads_subifd_overviews() {
1282 let file = GeoTiffFile::from_bytes(build_geotiff_with_subifd_overview()).unwrap();
1283 assert_eq!(file.overview_count(), 1);
1284 assert!(matches!(
1285 file.overview_ifd_index(0).unwrap_err(),
1286 crate::error::Error::OverviewHasNoTopLevelIfdIndex(0)
1287 ));
1288 assert_eq!(file.overview_ifd(0).unwrap().width(), 1);
1289 assert_eq!(file.overview_ifd(0).unwrap().height(), 1);
1290
1291 let overview = file.read_overview::<u8>(0).unwrap();
1292 assert_eq!(overview.shape(), &[1, 1]);
1293 let (values, offset) = overview.into_raw_vec_and_offset();
1294 assert_eq!(offset, Some(0));
1295 assert_eq!(values, vec![99]);
1296 }
1297
1298 #[test]
1299 fn discovers_nested_subifd_overviews() {
1300 let file = GeoTiffFile::from_bytes(build_geotiff_with_nested_subifd_overviews()).unwrap();
1301 assert_eq!(file.overview_count(), 2);
1302 assert_eq!(file.overview_ifd(0).unwrap().width(), 2);
1303 assert_eq!(file.overview_ifd(1).unwrap().width(), 1);
1304 assert!(matches!(
1305 file.overview_ifd_index(0).unwrap_err(),
1306 crate::error::Error::OverviewHasNoTopLevelIfdIndex(0)
1307 ));
1308 assert!(matches!(
1309 file.overview_ifd_index(1).unwrap_err(),
1310 crate::error::Error::OverviewHasNoTopLevelIfdIndex(1)
1311 ));
1312
1313 let first = file.read_overview::<u8>(0).unwrap();
1314 assert_eq!(first.shape(), &[2, 2]);
1315 let second = file.read_overview::<u8>(1).unwrap();
1316 assert_eq!(second.shape(), &[1, 1]);
1317 assert_eq!(second[[0, 0]], 99);
1318 }
1319
1320 #[test]
1321 fn reads_base_raster_window() {
1322 let file = GeoTiffFile::from_bytes(build_simple_geotiff(false)).unwrap();
1323 let window = file.read_window::<u8>(1, 0, 1, 2).unwrap();
1324 assert_eq!(window.shape(), &[1, 2]);
1325 let (values, offset) = window.into_raw_vec_and_offset();
1326 assert_eq!(offset, Some(0));
1327 assert_eq!(values, vec![30, 40]);
1328 }
1329
1330 #[test]
1331 fn reads_overview_window() {
1332 let file = GeoTiffFile::from_bytes(build_geotiff_with_overview()).unwrap();
1333 let window = file.read_overview_window::<u8>(0, 0, 0, 1, 1).unwrap();
1334 assert_eq!(window.shape(), &[1, 1]);
1335 let (values, offset) = window.into_raw_vec_and_offset();
1336 assert_eq!(offset, Some(0));
1337 assert_eq!(values, vec![99]);
1338 }
1339
1340 #[test]
1341 fn prefers_non_ghost_base_ifd_for_cog_like_layouts() {
1342 let file = GeoTiffFile::from_bytes(build_cog_like_geotiff_with_ghost_ifd()).unwrap();
1343 assert_eq!(file.base_ifd_index(), 2);
1344 assert_eq!(file.width(), 4);
1345 assert_eq!(file.height(), 4);
1346 assert_eq!(file.geo_bounds(), Some([100.0, 192.0, 108.0, 200.0]));
1347 assert_eq!(file.overview_count(), 1);
1348 assert_eq!(file.overview_ifd_index(0).unwrap(), 1);
1349
1350 let base = file.read_raster::<u8>().unwrap();
1351 assert_eq!(base.shape(), &[4, 4]);
1352 let (values, offset) = base.into_raw_vec_and_offset();
1353 assert_eq!(offset, Some(0));
1354 assert_eq!(values, (1u8..=16).collect::<Vec<_>>());
1355
1356 let overview = file.read_overview::<u8>(0).unwrap();
1357 assert_eq!(overview.shape(), &[2, 2]);
1358 let (values, offset) = overview.into_raw_vec_and_offset();
1359 assert_eq!(offset, Some(0));
1360 assert_eq!(values, vec![50, 60, 70, 80]);
1361 }
1362
1363 #[test]
1364 fn rejects_zero_rows_per_strip_without_panicking() {
1365 let mut bytes = build_simple_geotiff(false);
1366 overwrite_classic_inline_long_tag(&mut bytes, 278, 0);
1367
1368 let file = GeoTiffFile::from_bytes(bytes).unwrap();
1369 assert_eq!(file.epsg(), Some(4326));
1370
1371 let error = file.tiff().read_image_bytes(0).unwrap_err();
1372 assert!(error.to_string().contains("RowsPerStrip"));
1373 }
1374}