use crate::{Coord, Coord2d};
use alloc::borrow::Cow;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LineString<'a, T: Coord> {
coords: Cow<'a, [T]>,
}
pub type LineString2<'a, C = f64> = LineString<'a, [C; 2]>;
pub type LineString3<'a, C = f64> = LineString<'a, [C; 3]>;
impl<'a, T: Coord> LineString<'a, T> {
pub fn new() -> Self {
Self {
coords: Cow::Borrowed(&[]),
}
}
pub fn from_raw(coords: Cow<'a, [T]>) -> Self {
Self { coords }
}
pub fn raw_coords(&self) -> &[T] {
self.as_ref()
}
pub fn iter(&self) -> Iter<T> {
Iter {
slice: &self.coords,
pos: 0,
close: false,
}
}
pub fn iter_closed(&self) -> Iter<T> {
Iter {
slice: &self.coords,
pos: 0,
close: true,
}
}
pub fn len(&self) -> usize {
self.coords.len()
}
pub fn is_empty(&self) -> bool {
self.coords.is_empty()
}
pub fn push(&mut self, coord: T) {
self.coords.to_mut().push(coord);
}
pub fn clear(&mut self) {
self.coords.to_mut().clear();
}
pub fn transform<T2: Coord>(&self, f: impl Fn(&T) -> T2) -> LineString<T2> {
LineString {
coords: self.coords.iter().map(f).collect(),
}
}
pub fn transform_inplace(&mut self, mut f: impl FnMut(&T) -> T) {
self.coords.to_mut().iter_mut().for_each(|c| {
*c = f(c);
});
}
pub fn reverse_inplace(&mut self) {
let len = self.coords.len();
if len > 0 {
let data = self.coords.to_mut();
for i in 0..data.len() / 2 {
data.swap(i, len - (i + 1));
}
}
}
pub fn reverse_ring_inplace(&mut self) {
let len = self.coords.len();
if len > 1 {
let data = self.coords.to_mut();
for i in 1..(data.len() + 1) / 2 {
data.swap(i, len - i);
}
}
}
}
impl<'a, T: Coord2d> LineString<'a, T> {
pub fn is_ccw(&self) -> bool {
self.signed_ring_area() > 0.0
}
pub fn is_cw(&self) -> bool {
self.signed_ring_area() < 0.0
}
pub fn ring_area(&self) -> f64 {
self.signed_ring_area().abs()
}
pub fn signed_ring_area(&self) -> f64 {
if self.is_empty() {
return 0.0;
}
let mut area = 0.0;
let mut ring_iter = self.iter_closed();
let mut prev = ring_iter.next().unwrap().xy();
for coord in ring_iter {
let xy = coord.xy();
area += (prev.0 * xy.1) - (prev.1 * xy.0);
prev = xy;
}
area / 2.0
}
}
impl<T: Coord> AsRef<[T]> for LineString<'_, T> {
fn as_ref(&self) -> &[T] {
self.coords.as_ref()
}
}
impl<'a, T: Coord> IntoIterator for &'a LineString<'_, T> {
type Item = T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct Iter<'a, T: Coord> {
slice: &'a [T],
pos: usize,
close: bool,
}
impl<'a, T: Coord> Iterator for Iter<'a, T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let v = if self.pos < self.slice.len() {
Some(self.slice[self.pos].clone())
} else if self.close && !self.slice.is_empty() && self.pos == self.slice.len() {
Some(self.slice[0].clone())
} else {
None
};
self.pos += 1;
v
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
#[test]
fn test_line_basic() {
let mut line: LineString2 = LineString2::from_raw(
(0..4)
.map(|v| [v as f64, v as f64])
.collect::<Vec<[f64; 2]>>()
.into(),
);
assert_eq!(line.len(), 4);
assert!(!line.is_empty());
for (i, coord) in line.iter().enumerate() {
match i {
0 => assert_eq!(coord, [0., 0.]),
1 => assert_eq!(coord, [1., 1.]),
2 => assert_eq!(coord, [2., 2.]),
3 => assert_eq!(coord, [3., 3.]),
_ => unreachable!(),
}
}
line.clear();
assert_eq!(line.len(), 0);
assert!(line.is_empty());
line.push([0., 1.]);
assert_eq!(line.len(), 1);
for _c in &line {}
}
#[test]
fn test_line_empty() {
let line: LineString2 = LineString2::new();
assert_eq!(line.len(), 0);
assert!(line.is_empty());
assert_eq!(line.iter().count(), 0);
}
#[test]
fn test_line_empty_iter_closed() {
let line: LineString2 = LineString2::new();
assert_eq!(line.iter_closed().count(), 0);
}
#[test]
fn test_line_close() {
let line: LineString2 = LineString2::from_raw(
(0..3)
.map(|v| [v as f64, v as f64])
.collect::<Vec<[f64; 2]>>()
.into(),
);
assert_eq!(line.len(), 3);
assert!(!line.is_empty());
for (i, coord) in line.iter_closed().enumerate() {
match i {
0 => assert_eq!(coord, [0., 0.]),
1 => assert_eq!(coord, [1., 1.]),
2 => assert_eq!(coord, [2., 2.]),
3 => assert_eq!(coord, [0., 0.]),
_ => unreachable!(),
}
}
}
#[test]
fn test_transform() {
{
let line = LineString2::from_raw(vec![[0., 0.], [5., 0.], [5., 5.], [0., 5.]].into());
let new_line = line.transform(|[x, y]| [x + 2., y + 1.]);
assert_eq!(
new_line.raw_coords(),
[[2., 1.], [7., 1.], [7., 6.], [2., 6.]]
);
}
{
let mut line =
LineString2::from_raw([[0., 0.], [5., 0.], [5., 5.], [0., 5.]][..].into());
line.transform_inplace(|[x, y]| [x + 2., y + 1.]);
assert_eq!(line.raw_coords(), [[2., 1.], [7., 1.], [7., 6.], [2., 6.]]);
}
}
#[test]
fn test_winding_order() {
let line =
LineString2::from_raw(vec![[0.0, 0.0], [3.0, 0.0], [3.0, 3.0], [0.0, 3.0]].into());
assert!(line.is_ccw());
assert!(!line.is_cw());
let line =
LineString2::from_raw(vec![[0.0, 0.0], [0.0, 3.0], [3.0, 3.0], [3.0, 0.0]].into());
assert!(!line.is_ccw());
assert!(line.is_cw());
let line = LineString2::from_raw(vec![[0.0, 0.0], [0.0, 0.0]].into());
assert!(!line.is_ccw());
assert!(!line.is_cw());
}
#[test]
fn test_reverse() {
let mut line = LineString2::from_raw(
vec![
[0.0, 0.0],
[3.0, 0.0],
[3.0, 1.0],
[3.0, 3.0],
[1.0, 3.0],
[0.0, 3.0],
]
.into(),
);
line.reverse_inplace();
assert_eq!(
line.raw_coords(),
vec![
[0.0, 3.0],
[1.0, 3.0],
[3.0, 3.0],
[3.0, 1.0],
[3.0, 0.0],
[0.0, 0.0]
]
);
let mut line = LineString2::from_raw(
vec![[0.0, 0.0], [3.0, 0.0], [6.0, 0.0], [6.0, 3.0], [3.0, 3.0]].into(),
);
line.reverse_inplace();
assert_eq!(
line.raw_coords(),
vec![[3.0, 3.0], [6.0, 3.0], [6.0, 0.0], [3.0, 0.0], [0.0, 0.0]]
);
}
#[test]
fn test_ring_reverse() {
let mut line = LineString2::from_raw(
vec![
[0.0, 0.0],
[3.0, 0.0],
[3.0, 1.0],
[3.0, 3.0],
[1.0, 3.0],
[0.0, 3.0],
]
.into(),
);
line.reverse_ring_inplace();
assert_eq!(
line.raw_coords(),
vec![
[0.0, 0.0],
[0.0, 3.0],
[1.0, 3.0],
[3.0, 3.0],
[3.0, 1.0],
[3.0, 0.0]
]
);
let mut line = LineString2::from_raw(
vec![[0.0, 0.0], [3.0, 0.0], [6.0, 0.0], [6.0, 3.0], [3.0, 3.0]].into(),
);
line.reverse_ring_inplace();
assert_eq!(
line.raw_coords(),
vec![[0.0, 0.0], [3.0, 3.0], [6.0, 3.0], [6.0, 0.0], [3.0, 0.0]]
);
}
}