Skip to content

Move endianness check into AsyncCursor constructor #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ It additionally exposes geospatial-specific TIFF tag metadata.

Download the following file for use in the tests.

```
```shell
aws s3 cp s3://naip-visualization/ny/2022/60cm/rgb/40073/m_4007307_sw_18_060_20220803.tif ./ --request-payer
```
32 changes: 26 additions & 6 deletions src/async_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use crate::error::{AiocogeoError, Result};

/// The asynchronous interface used to read COG files
///
/// This was derived from the Parquet `AsyncFileReader`:
/// https://docs.rs/parquet/latest/parquet/arrow/async_reader/trait.AsyncFileReader.html
/// This was derived from the Parquet
/// [`AsyncFileReader`](https://docs.rs/parquet/latest/parquet/arrow/async_reader/trait.AsyncFileReader.html)
///
/// Notes:
///
Expand Down Expand Up @@ -143,22 +143,42 @@ macro_rules! impl_read_byteorder {
}

impl AsyncCursor {
pub(crate) fn new(reader: Box<dyn AsyncFileReader>) -> Self {
/// Create a new AsyncCursor from a reader and endianness.
pub(crate) fn new(reader: Box<dyn AsyncFileReader>, endianness: Endianness) -> Self {
Self {
reader,
offset: 0,
endianness: Default::default(),
endianness,
}
}

pub(crate) fn set_endianness(&mut self, endianness: Endianness) {
self.endianness = endianness;
/// Create a new AsyncCursor for a TIFF file, automatically inferring endianness from the first
/// two bytes.
pub(crate) async fn try_open_tiff(reader: Box<dyn AsyncFileReader>) -> Result<Self> {
// Initialize with default endianness and then set later
let mut cursor = Self::new(reader, Default::default());
let magic_bytes = cursor.read(2).await;

// Should be b"II" for little endian or b"MM" for big endian
if magic_bytes == Bytes::from_static(b"II") {
cursor.endianness = Endianness::LittleEndian;
} else if magic_bytes == Bytes::from_static(b"MM") {
cursor.endianness = Endianness::BigEndian;
} else {
return Err(AiocogeoError::General(format!(
"unexpected magic bytes {magic_bytes:?}"
)));
};

Ok(cursor)
}

/// Consume self and return the underlying [`AsyncFileReader`].
pub(crate) fn into_inner(self) -> Box<dyn AsyncFileReader> {
self.reader
}

/// Read the given number of bytes, advancing the internal cursor state by the same amount.
pub(crate) async fn read(&mut self, length: usize) -> Bytes {
let range = self.offset as _..(self.offset + length) as _;
self.offset += length;
Expand Down
23 changes: 7 additions & 16 deletions src/cog.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use bytes::Bytes;

use crate::async_reader::{AsyncCursor, Endianness};
use crate::async_reader::AsyncCursor;
use crate::error::Result;
use crate::ifd::ImageFileDirectories;
use crate::AsyncFileReader;
Expand All @@ -13,17 +11,7 @@ pub struct COGReader {

impl COGReader {
pub async fn try_open(reader: Box<dyn AsyncFileReader>) -> Result<Self> {
let mut cursor = AsyncCursor::new(reader);
let magic_bytes = cursor.read(2).await;
// Should be b"II" for little endian or b"MM" for big endian
if magic_bytes == Bytes::from_static(b"II") {
cursor.set_endianness(Endianness::LittleEndian);
} else if magic_bytes == Bytes::from_static(b"MM") {
cursor.set_endianness(Endianness::BigEndian);
} else {
panic!("unexpected magic bytes {magic_bytes:?}");
}

let mut cursor = AsyncCursor::try_open_tiff(reader).await?;
let version = cursor.read_u16().await;

// Assert it's a standard non-big tiff
Expand Down Expand Up @@ -72,9 +60,12 @@ mod test {
let path = object_store::path::Path::parse("m_4007307_sw_18_060_20220803.tif").unwrap();
let store = Arc::new(LocalFileSystem::new_with_prefix(folder).unwrap());
let reader = ObjectReader::new(store, path);

let cog_reader = COGReader::try_open(Box::new(reader.clone())).await.unwrap();
let ifd = &cog_reader.ifds.as_ref()[4];
dbg!(ifd.compression);

let ifd = &cog_reader.ifds.as_ref()[1];
// dbg!(ifd.geotransform());
dbg!(ifd);
let tile = ifd.get_tile(0, 0, Box::new(reader)).await.unwrap();
std::fs::write("img.buf", tile).unwrap();
// dbg!(tile.len());
Expand Down
1 change: 1 addition & 0 deletions src/geo/affine.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Affine transformation values.
#[derive(Debug)]
pub struct AffineTransform(f64, f64, f64, f64, f64, f64);

Expand Down
111 changes: 65 additions & 46 deletions src/geo/geo_key_directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use tiff::decoder::ifd::Value;
use tiff::{TiffError, TiffResult};

/// Geospatial TIFF tag variants
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive, Eq, Hash)]
#[repr(u16)]
pub enum GeoKeyTag {
Expand Down Expand Up @@ -63,60 +64,75 @@ pub enum GeoKeyTag {
VerticalUnits = 4099,
}

/// http://docs.opengeospatial.org/is/19-008r4/19-008r4.html#_requirements_class_geokeydirectorytag
/// Metadata defined by the GeoTIFF standard.
///
/// <http://docs.opengeospatial.org/is/19-008r4/19-008r4.html#_requirements_class_geokeydirectorytag>
#[derive(Debug, Clone)]
pub struct GeoKeyDirectory {
model_type: Option<u16>,
raster_type: Option<u16>,
citation: Option<String>,
pub model_type: Option<u16>,
pub raster_type: Option<u16>,
pub citation: Option<String>,

geographic_type: Option<u16>,
geog_citation: Option<String>,
geog_geodetic_datum: Option<u16>,
geog_prime_meridian: Option<u16>,
geog_linear_units: Option<u16>,
geog_linear_unit_size: Option<f64>,
geog_angular_units: Option<u16>,
geog_angular_unit_size: Option<f64>,
geog_ellipsoid: Option<u16>,
geog_semi_major_axis: Option<f64>,
geog_semi_minor_axis: Option<f64>,
geog_inv_flattening: Option<f64>,
geog_azimuth_units: Option<u16>,
geog_prime_meridian_long: Option<f64>,
pub geographic_type: Option<u16>,
pub geog_citation: Option<String>,
pub geog_geodetic_datum: Option<u16>,

projected_type: Option<u16>,
proj_citation: Option<String>,
projection: Option<u16>,
proj_coord_trans: Option<u16>,
proj_linear_units: Option<u16>,
proj_linear_unit_size: Option<f64>,
proj_std_parallel1: Option<f64>,
proj_std_parallel2: Option<f64>,
proj_nat_origin_long: Option<f64>,
proj_nat_origin_lat: Option<f64>,
proj_false_easting: Option<f64>,
proj_false_northing: Option<f64>,
proj_false_origin_long: Option<f64>,
proj_false_origin_lat: Option<f64>,
proj_false_origin_easting: Option<f64>,
proj_false_origin_northing: Option<f64>,
proj_center_long: Option<f64>,
proj_center_lat: Option<f64>,
proj_center_easting: Option<f64>,
proj_center_northing: Option<f64>,
proj_scale_at_nat_origin: Option<f64>,
proj_scale_at_center: Option<f64>,
proj_azimuth_angle: Option<f64>,
proj_straight_vert_pole_long: Option<f64>,
/// This key is used to specify a Prime Meridian from the GeoTIFF CRS register or to indicate
/// that the Prime Meridian is user-defined. The default is Greenwich, England.
/// <https://docs.ogc.org/is/19-008r4/19-008r4.html#_requirements_class_primemeridiangeokey>
pub geog_prime_meridian: Option<u16>,

vertical: Option<u16>,
vertical_citation: Option<String>,
vertical_datum: Option<u16>,
vertical_units: Option<u16>,
pub geog_linear_units: Option<u16>,
pub geog_linear_unit_size: Option<f64>,
pub geog_angular_units: Option<u16>,
pub geog_angular_unit_size: Option<f64>,

/// This key is provided to specify an ellipsoid (or sphere) from the GeoTIFF CRS register or
/// to indicate that the ellipsoid (or sphere) is user-defined.
pub geog_ellipsoid: Option<u16>,
pub geog_semi_major_axis: Option<f64>,
pub geog_semi_minor_axis: Option<f64>,
pub geog_inv_flattening: Option<f64>,
pub geog_azimuth_units: Option<u16>,

/// This key allows definition of a user-defined Prime Meridian, the location of which is
/// defined by its longitude relative to the international reference meridian (for the earth
/// this is Greenwich).
pub geog_prime_meridian_long: Option<f64>,

pub projected_type: Option<u16>,
pub proj_citation: Option<String>,
pub projection: Option<u16>,
pub proj_coord_trans: Option<u16>,
pub proj_linear_units: Option<u16>,
pub proj_linear_unit_size: Option<f64>,
pub proj_std_parallel1: Option<f64>,
pub proj_std_parallel2: Option<f64>,
pub proj_nat_origin_long: Option<f64>,
pub proj_nat_origin_lat: Option<f64>,
pub proj_false_easting: Option<f64>,
pub proj_false_northing: Option<f64>,
pub proj_false_origin_long: Option<f64>,
pub proj_false_origin_lat: Option<f64>,
pub proj_false_origin_easting: Option<f64>,
pub proj_false_origin_northing: Option<f64>,
pub proj_center_long: Option<f64>,
pub proj_center_lat: Option<f64>,
pub proj_center_easting: Option<f64>,
pub proj_center_northing: Option<f64>,
pub proj_scale_at_nat_origin: Option<f64>,
pub proj_scale_at_center: Option<f64>,
pub proj_azimuth_angle: Option<f64>,
pub proj_straight_vert_pole_long: Option<f64>,

pub vertical: Option<u16>,
pub vertical_citation: Option<String>,
pub vertical_datum: Option<u16>,
pub vertical_units: Option<u16>,
}

impl GeoKeyDirectory {
/// Construct a new [`GeoKeyDirectory`] from tag values.
pub(crate) fn from_tags(mut tag_data: HashMap<GeoKeyTag, Value>) -> TiffResult<Self> {
let mut model_type = None;
let mut raster_type = None;
Expand Down Expand Up @@ -281,6 +297,9 @@ impl GeoKeyDirectory {
}

/// Return the EPSG code representing the crs of the image
///
/// This will return either [`GeoKeyDirectory::projected_type`] or
/// [`GeoKeyDirectory::geographic_type`].
pub fn epsg_code(&self) -> Option<u16> {
if let Some(projected_type) = self.projected_type {
Some(projected_type)
Expand Down
2 changes: 2 additions & 0 deletions src/geo/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Support for GeoTIFF files.

mod affine;
mod geo_key_directory;
mod partial_reads;
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![doc = include_str!("../README.md")]

mod async_reader;
mod cog;
mod decoder;
Expand Down