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