Skip to content
Open
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ version = "0.0.2"
edition = "2021"
authors = [
"Dominik Bucher <dobucher@ethz.ch>",
"Gunnar Schulze <gunnar.schulze@gmail.com>"
"Gunnar Schulze <gunnar.schulze@gmail.com>",
]
repository = "https://github.com/georust/geotiff"

Expand All @@ -15,7 +15,7 @@ geo-index = { version = "0.1", optional = true }
geo-types = { version = "0.7" }
num_enum = "0.7"
num-traits = "0.2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're not using num_traits::FromPrimitive anymore, I guess this dependency can be removed?

Suggested change
num-traits = "0.2"

tiff = "0.9"
tiff = "0.9.1"

[dev-dependencies]
proj = "0.27"
Expand Down
94 changes: 63 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
//! A [GeoTIFF](https://www.ogc.org/standard/geotiff) library for Rust
use std::any::type_name;
use std::io::{Read, Seek};

use geo_types::{Coord, Rect};
use num_traits::FromPrimitive;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::tags::Tag;
use tiff::TiffResult;
Expand All @@ -19,18 +17,19 @@ mod decoder_ext;
mod geo_key_directory;
mod raster_data;

macro_rules! unwrap_primitive_type {
($result: expr, $actual: ty, $expected: ty) => {
$result
.ok_or_else(|| {
format!(
"Cannot represent {} as {}",
type_name::<$actual>(),
type_name::<$expected>()
)
})
.unwrap()
};
/// The raster value type that can be read from a GeoTIFF.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RasterDataType {
U8,
U16,
U32,
U64,
F32,
F64,
I8,
I16,
I32,
I64,
}

/// The basic GeoTIFF struct. This includes any metadata as well as the actual raster data.
Expand Down Expand Up @@ -85,6 +84,22 @@ impl GeoTiff {
})
}

/// Returns the sample type of the raster data.
pub fn sample_type(&self) -> RasterDataType {
match &self.raster_data {
RasterData::U8(_) => RasterDataType::U8,
RasterData::U16(_) => RasterDataType::U16,
RasterData::U32(_) => RasterDataType::U32,
RasterData::U64(_) => RasterDataType::U64,
RasterData::F32(_) => RasterDataType::F32,
RasterData::F64(_) => RasterDataType::F64,
RasterData::I8(_) => RasterDataType::I8,
RasterData::I16(_) => RasterDataType::I16,
RasterData::I32(_) => RasterDataType::I32,
RasterData::I64(_) => RasterDataType::I64,
}
}

/// Returns the extent of the image in model space.
pub fn model_extent(&self) -> Rect {
let offset = self.raster_offset();
Expand All @@ -109,25 +124,42 @@ impl GeoTiff {

/// Returns the value at the given location for the specified sample.
/// The coordinates are in model space.
pub fn get_value_at<T: FromPrimitive + 'static>(
&self,
coord: &Coord,
sample: usize,
) -> Option<T> {
pub fn get_value_at(&self, coord: &Coord, sample: usize) -> Option<RasterValue> {
let index = self.compute_index(coord, sample)?;
let value = self.raster_data.get_value(index);
Some(value)
}

Some(match &self.raster_data {
RasterData::U8(data) => unwrap_primitive_type!(T::from_u8(data[index]), u8, T),
RasterData::U16(data) => unwrap_primitive_type!(T::from_u16(data[index]), u16, T),
RasterData::U32(data) => unwrap_primitive_type!(T::from_u32(data[index]), u32, T),
RasterData::U64(data) => unwrap_primitive_type!(T::from_u64(data[index]), u64, T),
RasterData::F32(data) => unwrap_primitive_type!(T::from_f32(data[index]), f32, T),
RasterData::F64(data) => unwrap_primitive_type!(T::from_f64(data[index]), f64, T),
RasterData::I8(data) => unwrap_primitive_type!(T::from_i8(data[index]), i8, T),
RasterData::I16(data) => unwrap_primitive_type!(T::from_i16(data[index]), i16, T),
RasterData::I32(data) => unwrap_primitive_type!(T::from_i32(data[index]), i32, T),
RasterData::I64(data) => unwrap_primitive_type!(T::from_i64(data[index]), i64, T),
})
/// Returns the value at the given pixel coordinates for the specified sample.
/// The coordinates are in pixel space (0-based).
pub fn get_value_at_pixel(&self, x: usize, y: usize, sample: usize) -> Option<RasterValue> {
let GeoTiff {
raster_width,
raster_height,
num_samples,
..
} = self;

// Check bounds
if x >= *raster_width || y >= *raster_height {
return None;
}

// Check sample bounds
if sample >= *num_samples {
panic!(
"sample out of bounds: the number of samples is {} but the sample is {}",
num_samples, sample
);
}

// Compute index directly from pixel coordinates
let index = (y * raster_width + x) * num_samples + sample;

// Get the value from the appropriate data array
let value = self.raster_data.get_value(index);

Some(value)
}

fn compute_index(&self, coord: &Coord, sample: usize) -> Option<usize> {
Expand Down
104 changes: 103 additions & 1 deletion src/raster_data.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,94 @@
use std::fmt;
use std::fmt::{Debug, Formatter};

pub(super) enum RasterData {

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RasterValue {
U8(u8),
U16(u16),
U32(u32),
U64(u64),
F32(f32),
F64(f64),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
}

impl RasterValue {
pub fn as_u8(&self) -> Option<u8> {
match self {
RasterValue::U8(value) => Some(*value),
_ => None,
}
}

pub fn as_u16(&self) -> Option<u16> {
match self {
RasterValue::U16(value) => Some(*value),
_ => None,
}
}

pub fn as_u32(&self) -> Option<u32> {
match self {
RasterValue::U32(value) => Some(*value),
_ => None,
}
}

pub fn as_u64(&self) -> Option<u64> {
match self {
RasterValue::U64(value) => Some(*value),
_ => None,
}
}

pub fn as_f32(&self) -> Option<f32> {
match self {
RasterValue::F32(value) => Some(*value),
_ => None,
}
}

pub fn as_f64(&self) -> Option<f64> {
match self {
RasterValue::F64(value) => Some(*value),
_ => None,
}
}

pub fn as_i8(&self) -> Option<i8> {
match self {
RasterValue::I8(value) => Some(*value),
_ => None,
}
}

pub fn as_i16(&self) -> Option<i16> {
match self {
RasterValue::I16(value) => Some(*value),
_ => None,
}
}

pub fn as_i32(&self) -> Option<i32> {
match self {
RasterValue::I32(value) => Some(*value),
_ => None,
}
}

pub fn as_i64(&self) -> Option<i64> {
match self {
RasterValue::I64(value) => Some(*value),
_ => None,
}
}
}

pub enum RasterData {
U8(Vec<u8>),
U16(Vec<u16>),
U32(Vec<u32>),
Expand Down Expand Up @@ -50,4 +137,19 @@ impl RasterData {
RasterData::I64(data) => data.len(),
}
}

pub fn get_value(&self, index: usize) -> RasterValue {
match self {
RasterData::U8(data) => RasterValue::U8(data[index]),
RasterData::U16(data) => RasterValue::U16(data[index]),
RasterData::U32(data) => RasterValue::U32(data[index]),
RasterData::U64(data) => RasterValue::U64(data[index]),
RasterData::F32(data) => RasterValue::F32(data[index]),
RasterData::F64(data) => RasterValue::F64(data[index]),
RasterData::I8(data) => RasterValue::I8(data[index]),
RasterData::I16(data) => RasterValue::I16(data[index]),
RasterData::I32(data) => RasterValue::I32(data[index]),
RasterData::I64(data) => RasterValue::I64(data[index]),
}
}
}
60 changes: 36 additions & 24 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ fn test_load_marbles() {
)
);
assert_eq!(
geotiff.get_value_at::<u8>(&Coord { x: 761.0, y: 599.0 }, 0),
geotiff
.get_value_at(&Coord { x: 761.0, y: 599.0 }, 0)
.map(|v| v.as_u8().unwrap()),
Some(147)
);
assert_eq!(
geotiff.get_value_at::<u8>(&Coord { x: 761.0, y: 599.0 }, 1),
geotiff
.get_value_at(&Coord { x: 761.0, y: 599.0 }, 1)
.map(|v| v.as_u8().unwrap()),
Some(128)
);
assert_eq!(
geotiff.get_value_at::<u8>(&Coord { x: 761.0, y: 599.0 }, 2),
geotiff
.get_value_at(&Coord { x: 761.0, y: 599.0 }, 2)
.map(|v| v.as_u8().unwrap()),
Some(165)
);
}
Expand All @@ -58,33 +64,39 @@ fn test_load_zh_dem_25() {
)
);
assert_eq!(
geotiff.get_value_at::<i16>(
&Coord {
x: 677575.0,
y: 253000.0
},
0
),
geotiff
.get_value_at(
&Coord {
x: 677575.0,
y: 253000.0
},
0
)
.map(|v| v.as_i16().unwrap()),
Some(551)
);
assert_eq!(
geotiff.get_value_at::<i16>(
&Coord {
x: 679250.0,
y: 251875.0
},
0
),
geotiff
.get_value_at(
&Coord {
x: 679250.0,
y: 251875.0
},
0
)
.map(|v| v.as_i16().unwrap()),
Some(530)
);
assert_eq!(
geotiff.get_value_at::<i16>(
&Coord {
x: 685700.0,
y: 249450.0
},
0
),
geotiff
.get_value_at(
&Coord {
x: 685700.0,
y: 249450.0
},
0
)
.map(|v| v.as_i16().unwrap()),
Some(587)
);

Expand Down
Loading
Loading