diff --git a/README.md b/README.md index 834b3b0..2ca5eca 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/src/async_reader.rs b/src/async_reader.rs index d03e902..e9bc975 100644 --- a/src/async_reader.rs +++ b/src/async_reader.rs @@ -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: /// @@ -143,22 +143,42 @@ macro_rules! impl_read_byteorder { } impl AsyncCursor { - pub(crate) fn new(reader: Box) -> Self { + /// Create a new AsyncCursor from a reader and endianness. + pub(crate) fn new(reader: Box, 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) -> Result { + // 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 { 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; diff --git a/src/cog.rs b/src/cog.rs index abb2416..8faa22e 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -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; @@ -13,17 +11,7 @@ pub struct COGReader { impl COGReader { pub async fn try_open(reader: Box) -> Result { - 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 @@ -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()); diff --git a/src/geo/affine.rs b/src/geo/affine.rs index 875d134..78f7ac6 100644 --- a/src/geo/affine.rs +++ b/src/geo/affine.rs @@ -1,3 +1,4 @@ +/// Affine transformation values. #[derive(Debug)] pub struct AffineTransform(f64, f64, f64, f64, f64, f64); diff --git a/src/geo/geo_key_directory.rs b/src/geo/geo_key_directory.rs index 3094310..0a13a5b 100644 --- a/src/geo/geo_key_directory.rs +++ b/src/geo/geo_key_directory.rs @@ -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 { @@ -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. +/// +/// #[derive(Debug, Clone)] pub struct GeoKeyDirectory { - model_type: Option, - raster_type: Option, - citation: Option, + pub model_type: Option, + pub raster_type: Option, + pub citation: Option, - geographic_type: Option, - geog_citation: Option, - geog_geodetic_datum: Option, - geog_prime_meridian: Option, - geog_linear_units: Option, - geog_linear_unit_size: Option, - geog_angular_units: Option, - geog_angular_unit_size: Option, - geog_ellipsoid: Option, - geog_semi_major_axis: Option, - geog_semi_minor_axis: Option, - geog_inv_flattening: Option, - geog_azimuth_units: Option, - geog_prime_meridian_long: Option, + pub geographic_type: Option, + pub geog_citation: Option, + pub geog_geodetic_datum: Option, - projected_type: Option, - proj_citation: Option, - projection: Option, - proj_coord_trans: Option, - proj_linear_units: Option, - proj_linear_unit_size: Option, - proj_std_parallel1: Option, - proj_std_parallel2: Option, - proj_nat_origin_long: Option, - proj_nat_origin_lat: Option, - proj_false_easting: Option, - proj_false_northing: Option, - proj_false_origin_long: Option, - proj_false_origin_lat: Option, - proj_false_origin_easting: Option, - proj_false_origin_northing: Option, - proj_center_long: Option, - proj_center_lat: Option, - proj_center_easting: Option, - proj_center_northing: Option, - proj_scale_at_nat_origin: Option, - proj_scale_at_center: Option, - proj_azimuth_angle: Option, - proj_straight_vert_pole_long: Option, + /// 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. + /// + pub geog_prime_meridian: Option, - vertical: Option, - vertical_citation: Option, - vertical_datum: Option, - vertical_units: Option, + pub geog_linear_units: Option, + pub geog_linear_unit_size: Option, + pub geog_angular_units: Option, + pub geog_angular_unit_size: Option, + + /// 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, + pub geog_semi_major_axis: Option, + pub geog_semi_minor_axis: Option, + pub geog_inv_flattening: Option, + pub geog_azimuth_units: Option, + + /// 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, + + pub projected_type: Option, + pub proj_citation: Option, + pub projection: Option, + pub proj_coord_trans: Option, + pub proj_linear_units: Option, + pub proj_linear_unit_size: Option, + pub proj_std_parallel1: Option, + pub proj_std_parallel2: Option, + pub proj_nat_origin_long: Option, + pub proj_nat_origin_lat: Option, + pub proj_false_easting: Option, + pub proj_false_northing: Option, + pub proj_false_origin_long: Option, + pub proj_false_origin_lat: Option, + pub proj_false_origin_easting: Option, + pub proj_false_origin_northing: Option, + pub proj_center_long: Option, + pub proj_center_lat: Option, + pub proj_center_easting: Option, + pub proj_center_northing: Option, + pub proj_scale_at_nat_origin: Option, + pub proj_scale_at_center: Option, + pub proj_azimuth_angle: Option, + pub proj_straight_vert_pole_long: Option, + + pub vertical: Option, + pub vertical_citation: Option, + pub vertical_datum: Option, + pub vertical_units: Option, } impl GeoKeyDirectory { + /// Construct a new [`GeoKeyDirectory`] from tag values. pub(crate) fn from_tags(mut tag_data: HashMap) -> TiffResult { let mut model_type = None; let mut raster_type = None; @@ -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 { if let Some(projected_type) = self.projected_type { Some(projected_type) diff --git a/src/geo/mod.rs b/src/geo/mod.rs index 8ad7c9d..fb68407 100644 --- a/src/geo/mod.rs +++ b/src/geo/mod.rs @@ -1,3 +1,5 @@ +//! Support for GeoTIFF files. + mod affine; mod geo_key_directory; mod partial_reads; diff --git a/src/lib.rs b/src/lib.rs index f4e9774..f0c3f21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + mod async_reader; mod cog; mod decoder;