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