#![allow(clippy::panic, clippy::expect_used)]
use oxigdal_algorithms::error::AlgorithmError;
use oxigdal_algorithms::vector::{
JoinStyle, OffsetOptions, offset_linestring, offset_polygon_rings,
};
fn miter_opts() -> OffsetOptions {
OffsetOptions {
join_style: JoinStyle::Miter,
miter_limit: 10.0,
simplify_tolerance: None,
}
}
fn bevel_opts() -> OffsetOptions {
OffsetOptions {
join_style: JoinStyle::Bevel,
miter_limit: 10.0,
simplify_tolerance: None,
}
}
fn shoelace_area(ring: &[(f64, f64)]) -> f64 {
let n = ring.len();
let mut sum = 0.0_f64;
for i in 0..n - 1 {
sum += ring[i].0 * ring[i + 1].1;
sum -= ring[i + 1].0 * ring[i].1;
}
sum.abs() / 2.0
}
#[test]
fn test_offset_horizontal_line_left_positive() {
let coords = vec![(0.0_f64, 0.0_f64), (10.0, 0.0)];
let result = offset_linestring(&coords, 1.0, &miter_opts())
.expect("offset of horizontal line should succeed");
assert_eq!(
result.coords.len(),
2,
"simple 2-point line should yield 2-point offset"
);
assert!(!result.was_simplified);
for &(x, y) in &result.coords {
assert!(x.is_finite() && y.is_finite(), "coords must be finite");
assert!(
(y - 1.0).abs() < 1e-10,
"left offset of eastward line should be at y=1, got y={y}"
);
}
assert!((result.coords[0].0 - 0.0).abs() < 1e-10);
assert!((result.coords[1].0 - 10.0).abs() < 1e-10);
}
#[test]
fn test_offset_horizontal_line_right_negative() {
let coords = vec![(0.0_f64, 0.0_f64), (10.0, 0.0)];
let result = offset_linestring(&coords, -1.0, &miter_opts()).expect("offset should succeed");
assert_eq!(result.coords.len(), 2);
for &(_, y) in &result.coords {
assert!(
(y - (-1.0)).abs() < 1e-10,
"right offset of eastward line should be at y=-1, got y={y}"
);
}
}
#[test]
fn test_offset_zero_distance_returns_input() {
let coords = vec![(0.0_f64, 0.0_f64), (5.0, 3.0), (10.0, 0.0)];
let result = offset_linestring(&coords, 0.0, &miter_opts())
.expect("zero-distance offset should succeed");
assert_eq!(
result.coords, coords,
"zero distance must return identical coords"
);
assert!(!result.was_simplified);
}
#[test]
fn test_offset_right_angle_miter_within_limit() {
let coords = vec![(0.0_f64, 10.0_f64), (0.0, 0.0), (10.0, 0.0)];
let result = offset_linestring(&coords, 1.0, &miter_opts())
.expect("miter L-shape offset should succeed");
for &(x, y) in &result.coords {
assert!(
x.is_finite() && y.is_finite(),
"NaN/Inf in miter output: ({x}, {y})"
);
}
assert!(
result.coords.len() >= 3,
"miter L-shape should have ≥ 3 points, got {}",
result.coords.len()
);
}
#[test]
fn test_offset_right_angle_bevel_style() {
let coords = vec![(0.0_f64, 10.0_f64), (0.0, 0.0), (10.0, 0.0)];
let result = offset_linestring(&coords, 1.0, &bevel_opts())
.expect("bevel L-shape offset should succeed");
assert_eq!(
result.coords.len(),
4,
"bevel at a single interior vertex should yield 4 points, got {}",
result.coords.len()
);
for &(x, y) in &result.coords {
assert!(x.is_finite() && y.is_finite());
}
}
#[test]
fn test_offset_miter_limit_clamps_sharp_angle() {
let coords = vec![(0.0_f64, 0.0_f64), (10.0, 0.0), (20.0, 0.001)];
let opts = OffsetOptions {
join_style: JoinStyle::Miter,
miter_limit: 2.0,
simplify_tolerance: None,
};
let result = offset_linestring(&coords, 1.0, &opts).expect("clamped miter should succeed");
for &(x, y) in &result.coords {
assert!(x.is_finite(), "x must be finite, got {x}");
assert!(y.is_finite(), "y must be finite, got {y}");
}
assert!(result.coords.len() >= 3, "need ≥ 3 output points");
}
#[test]
fn test_offset_insufficient_vertices_errors() {
let coords = vec![(0.0_f64, 0.0_f64)];
let result = offset_linestring(&coords, 1.0, &miter_opts());
match result {
Err(AlgorithmError::InsufficientData { operation, .. }) => {
assert_eq!(operation, "offset_linestring");
}
other => panic!("expected InsufficientData, got {other:?}"),
}
}
#[test]
fn test_offset_polygon_ring_square() {
let ring = vec![
(0.0_f64, 0.0_f64),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0), ];
let opts = OffsetOptions {
join_style: JoinStyle::Miter,
miter_limit: 10.0,
simplify_tolerance: None,
};
let result = offset_polygon_rings(std::slice::from_ref(&ring), 1.0, &opts)
.expect("polygon ring offset should succeed");
assert_eq!(result.len(), 1, "one ring in → one ring out");
let out = &result[0];
for &(x, y) in out {
assert!(
x.is_finite() && y.is_finite(),
"non-finite coord in ring output"
);
}
assert_eq!(
out.first(),
out.last(),
"output ring should be closed (first == last)"
);
let original_area = shoelace_area(&ring); let expanded_area = shoelace_area(out);
assert!(
expanded_area > original_area,
"offset +1 should expand area beyond {original_area}, got {expanded_area}"
);
}