Skip to content
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

Coordinate Transformations #21

Merged
merged 15 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
545 changes: 545 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ name = "geotiff"
description = "A GeoTIFF library for Rust"
version = "0.0.2"
edition = "2021"
authors = ["Dominik Bucher <[email protected]>"]
authors = [
"Dominik Bucher <[email protected]>",
"Gunnar Schulze <[email protected]>"
]
repository = "https://github.com/georust/geotiff"

[dependencies]
delaunator = "1.0"
geo-index = "0.1"
gschulze marked this conversation as resolved.
Show resolved Hide resolved
geo-types = { version = "0.7" }
num_enum = "0.7"
num-traits = "0.2"
tiff = "0.9"

[dev-dependencies]
proj = "0.27"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
160 changes: 160 additions & 0 deletions src/coordinate_transform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use std::rc::Rc;

use geo_index::rtree::OwnedRTree;
use geo_types::Coord;
use tiff::{TiffError, TiffFormatError, TiffResult};

use crate::coordinate_transform::tie_points::Face;

mod affine_transform;
mod tie_point_and_pixel_scale;
mod tie_points;

const MODEL_TIE_POINT_TAG: &str = "ModelTiePointTag";
const MODEL_PIXEL_SCALE_TAG: &str = "ModelPixelScaleTag";
const MODEL_TRANSFORMATION_TAG: &str = "ModelTransformationTag";

#[derive(Debug)]
pub enum CoordinateTransform {
gschulze marked this conversation as resolved.
Show resolved Hide resolved
AffineTransform {
transform: [f64; 6],
inverse_transform: [f64; 6],
},
TiePointAndPixelScale {
raster_point: Coord,
model_point: Coord,
pixel_scale: Coord,
},
TiePoints {
raster_mesh: Rc<Vec<Face>>,
raster_index: OwnedRTree<f64>,
model_mesh: Rc<Vec<Face>>,
model_index: OwnedRTree<f64>,
},
}

impl CoordinateTransform {
pub(super) fn from_tag_data(
pixel_scale_data: Option<Vec<f64>>,
model_tie_points_data: Option<Vec<f64>>,
model_transformation_data: Option<Vec<f64>>,
) -> TiffResult<Self> {
let pixel_scale = pixel_scale_data
.map(|data| {
<[f64; 3]>::try_from(data).map_err(|_| {
TiffError::FormatError(TiffFormatError::Format(format!(
"Number values in {MODEL_PIXEL_SCALE_TAG} must be equal to 3"
)))
})
})
.transpose()?;
let tie_points = model_tie_points_data
.map(|data| {
let len = data.len();
if len == 0 {
return Err(TiffError::FormatError(TiffFormatError::Format(format!(
"Number of values in {MODEL_TIE_POINT_TAG} must be greater than 0"
))));
}

if len % 6 != 0 {
return Err(TiffError::FormatError(TiffFormatError::Format(format!(
"Number of values in {MODEL_TIE_POINT_TAG} must be divisible by 6"
))));
}

Ok(data)
})
.transpose()?;
let transformation_matrix = model_transformation_data
.map(|data| {
<[f64; 16]>::try_from(data).map_err(|_| {
TiffError::FormatError(TiffFormatError::Format(format!(
"Number of values in {MODEL_TRANSFORMATION_TAG} must be equal to 16"
)))
})
})
.transpose()?;

if let Some(transformation_matrix) = transformation_matrix {
if pixel_scale.is_some() {
return Err(TiffError::FormatError(TiffFormatError::Format(
format!("{MODEL_PIXEL_SCALE_TAG} must not be specified when {MODEL_TRANSFORMATION_TAG} is present"),
)));
}
if tie_points.is_some() {
return Err(TiffError::FormatError(TiffFormatError::Format(
format!("{MODEL_TIE_POINT_TAG} must not be specified when {MODEL_TRANSFORMATION_TAG} is present"),
)));
}

Self::from_transformation_matrix(transformation_matrix)
} else {
let Some(tie_points) = tie_points else {
return Err(TiffError::FormatError(TiffFormatError::Format(
format!("{MODEL_TIE_POINT_TAG} must be present when {MODEL_TRANSFORMATION_TAG} is missing"),
)));
};

if tie_points.len() == 6 {
let Some(pixel_scale) = pixel_scale else {
return Err(TiffError::FormatError(TiffFormatError::Format(
format!("{MODEL_PIXEL_SCALE_TAG} must be specified when {MODEL_TIE_POINT_TAG} contains 6 values"),
)));
};

Self::from_tie_point_and_pixel_scale(&tie_points, &pixel_scale)
} else {
Self::from_tie_points(&tie_points)
}
}
}

pub fn transform_to_model(&self, coord: &Coord) -> Coord {
match self {
CoordinateTransform::AffineTransform { transform, .. } => {
Self::transform_by_affine_transform(transform, coord)
}
CoordinateTransform::TiePointAndPixelScale {
raster_point,
model_point,
pixel_scale,
} => Self::transform_to_model_by_tie_point_and_pixel_scale(
raster_point,
model_point,
pixel_scale,
coord,
),
CoordinateTransform::TiePoints {
raster_index,
raster_mesh,
model_mesh,
..
} => Self::transform_by_tie_points(raster_index, raster_mesh, model_mesh, coord),
}
}

pub(super) fn transform_to_raster(&self, coord: &Coord) -> Coord {
match self {
CoordinateTransform::AffineTransform {
inverse_transform, ..
} => Self::transform_by_affine_transform(inverse_transform, coord),
CoordinateTransform::TiePointAndPixelScale {
raster_point,
model_point,
pixel_scale,
} => Self::transform_to_raster_by_tie_point_and_pixel_scale(
raster_point,
model_point,
pixel_scale,
coord,
),
CoordinateTransform::TiePoints {
model_index,
model_mesh,
raster_mesh,
..
} => Self::transform_by_tie_points(model_index, model_mesh, raster_mesh, coord),
}
}
}
45 changes: 45 additions & 0 deletions src/coordinate_transform/affine_transform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use geo_types::Coord;
use tiff::{TiffError, TiffFormatError, TiffResult};

use crate::coordinate_transform::CoordinateTransform;

impl CoordinateTransform {
pub fn from_transformation_matrix(transformation_matrix: [f64; 16]) -> TiffResult<Self> {
let transform = [
transformation_matrix[0],
transformation_matrix[1],
transformation_matrix[3],
transformation_matrix[4],
transformation_matrix[5],
transformation_matrix[7],
];

let det = transform[0] * transform[4] - transform[1] * transform[3];
if det.abs() < 0.000000000000001 {
return Err(TiffError::FormatError(TiffFormatError::Format(
"Provided transformation matrix is not invertible".into(),
)));
}

let inverse_transform = [
transform[4] / det,
-transform[1] / det,
(transform[1] * transform[5] - transform[2] * transform[4]) / det,
-transform[3] / det,
transform[0] / det,
(-transform[0] * transform[5] + transform[2] * transform[3]) / det,
];

Ok(CoordinateTransform::AffineTransform {
transform,
inverse_transform,
})
}

pub(super) fn transform_by_affine_transform(transform: &[f64; 6], coord: &Coord) -> Coord {
Coord {
x: coord.x * transform[0] + coord.y * transform[1] + transform[2],
y: coord.x * transform[3] + coord.y * transform[4] + transform[5],
}
}
}
50 changes: 50 additions & 0 deletions src/coordinate_transform/tie_point_and_pixel_scale.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use geo_types::Coord;
use tiff::TiffResult;

use crate::coordinate_transform::CoordinateTransform;

impl CoordinateTransform {
pub(super) fn from_tie_point_and_pixel_scale(
tie_points: &[f64],
pixel_scale: &[f64],
) -> TiffResult<Self> {
Ok(CoordinateTransform::TiePointAndPixelScale {
raster_point: Coord {
x: tie_points[0],
y: tie_points[1],
},
model_point: Coord {
x: tie_points[3],
y: tie_points[4],
},
pixel_scale: Coord {
x: pixel_scale[0],
y: pixel_scale[1],
},
})
}

pub(super) fn transform_to_model_by_tie_point_and_pixel_scale(
raster_point: &Coord,
model_point: &Coord,
pixel_scale: &Coord,
coord: &Coord,
) -> Coord {
Coord {
x: (coord.x - raster_point.x) * pixel_scale.x + model_point.x,
y: (coord.y - raster_point.y) * -pixel_scale.y + model_point.y,
}
}

pub(super) fn transform_to_raster_by_tie_point_and_pixel_scale(
raster_point: &Coord,
model_point: &Coord,
pixel_scale: &Coord,
coord: &Coord,
) -> Coord {
Coord {
x: (coord.x - model_point.x) / pixel_scale.x + raster_point.x,
y: (coord.y - model_point.y) / -pixel_scale.y + raster_point.y,
}
}
}
Loading