use geo::AffineTransform;
use crate::coords;
use crate::error::Result;
fn apply(bytes: &[u8], transform: AffineTransform<f64>) -> Result<Vec<u8>> {
coords::map_coords(bytes, &mut |p| {
let moved = transform.apply(geo_types::Coord { x: p.x, y: p.y });
p.x = moved.x;
p.y = moved.y;
})
}
pub fn st_rotate(bytes: &[u8], radians: f64) -> Result<Vec<u8>> {
st_rotate_xy(bytes, radians, 0.0, 0.0)
}
pub fn st_rotate_xy(bytes: &[u8], radians: f64, x0: f64, y0: f64) -> Result<Vec<u8>> {
let (sin, cos) = radians.sin_cos();
let transform = AffineTransform::new(
cos,
-sin,
x0 - x0 * cos + y0 * sin,
sin,
cos,
y0 - x0 * sin - y0 * cos,
);
apply(bytes, transform)
}
pub fn st_translate(bytes: &[u8], dx: f64, dy: f64) -> Result<Vec<u8>> {
apply(bytes, AffineTransform::translate(dx, dy))
}
pub fn st_scale(bytes: &[u8], xfactor: f64, yfactor: f64) -> Result<Vec<u8>> {
apply(
bytes,
AffineTransform::scale(xfactor, yfactor, geo_types::coord! { x: 0.0, y: 0.0 }),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::functions::io::{st_as_text, st_geom_from_text};
fn g(wkt: &str) -> Vec<u8> {
st_geom_from_text(wkt, None).unwrap()
}
fn text(blob: &[u8]) -> String {
st_as_text(blob).unwrap()
}
#[test]
fn rotate_about_origin_and_custom_point() {
let rotated = st_rotate(&g("POINT(1 0)"), std::f64::consts::FRAC_PI_2).unwrap();
let wkt = text(&rotated);
assert!(wkt.starts_with("POINT(") && wkt.contains(" 1)"), "{wkt}");
let rotated = st_rotate_xy(&g("POINT(4 5)"), std::f64::consts::PI, 5.0, 5.0).unwrap();
let wkt = text(&rotated);
assert!(
wkt.starts_with("POINT(5.999") || wkt.starts_with("POINT(6 5"),
"{wkt}"
);
}
#[test]
fn translate_and_scale() {
assert_eq!(
text(&st_translate(&g("LINESTRING(0 0,1 1)"), 10.0, -2.0).unwrap()),
"LINESTRING(10 -2,11 -1)"
);
assert_eq!(
text(&st_scale(&g("POINT(2 3)"), 2.0, 3.0).unwrap()),
"POINT(4 9)"
);
}
fn point_z() -> Vec<u8> {
let mut v = vec![0x01];
v.extend_from_slice(&1001u32.to_le_bytes());
for value in [1.0f64, 2.0, 3.0] {
v.extend_from_slice(&value.to_le_bytes());
}
v
}
#[test]
fn z_rides_through_every_transform() {
use crate::functions::{rtree, threed};
let cases: Vec<(&str, Vec<u8>, f64, f64)> = vec![
(
"translate",
st_translate(&point_z(), 10.0, 20.0).unwrap(),
11.0,
22.0,
),
("scale", st_scale(&point_z(), 2.0, 3.0).unwrap(), 2.0, 6.0),
(
"rotate",
st_rotate(&point_z(), std::f64::consts::FRAC_PI_2).unwrap(),
-2.0,
1.0,
),
(
"rotate_xy",
st_rotate_xy(&point_z(), std::f64::consts::FRAC_PI_2, 5.0, 5.0).unwrap(),
8.0,
1.0,
),
];
for (name, out, want_x, want_y) in cases {
let (x, y) = (
rtree::st_min_x(&out).unwrap().unwrap(),
rtree::st_min_y(&out).unwrap().unwrap(),
);
assert!((x - want_x).abs() < 1e-9, "{name}: x = {x}");
assert!((y - want_y).abs() < 1e-9, "{name}: y = {y}");
assert_eq!(threed::st_z(&out).unwrap(), Some(3.0), "{name}");
assert_eq!(threed::st_coord_dim(&out).unwrap(), 3, "{name}");
}
}
#[test]
fn surface_collections_transform() {
use crate::functions::{rtree, surface, threed};
let moved = st_translate(&surface::fixtures::cube(6), 1000.0, 2000.0).unwrap();
assert_eq!(surface::st_num_patches(&moved).unwrap(), Some(6));
assert_eq!(surface::is_closed(&moved).unwrap(), Some(true));
assert_eq!(rtree::st_min_x(&moved).unwrap(), Some(1000.0));
assert_eq!(threed::st_zmax(&moved).unwrap(), Some(1.0));
let scaled = st_scale(&surface::fixtures::cube(6), 2.0, 2.0).unwrap();
assert_eq!(rtree::st_max_x(&scaled).unwrap(), Some(2.0));
assert_eq!(threed::st_zmax(&scaled).unwrap(), Some(1.0));
}
}