use std::cmp::Ordering;
use std::collections::BinaryHeap;
use num_traits::Float;
use types::{Point, LineString};
#[derive(PartialEq, Debug)]
struct VScore<T>
where T: Float
{
area: T,
current: usize,
left: usize,
right: usize,
}
impl<T> Ord for VScore<T>
where T: Float
{
fn cmp(&self, other: &VScore<T>) -> Ordering {
other.area.partial_cmp(&self.area).unwrap()
}
}
impl<T> PartialOrd for VScore<T>
where T: Float
{
fn partial_cmp(&self, other: &VScore<T>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> Eq for VScore<T> where T: Float {}
fn visvalingam<T>(orig: &[Point<T>], epsilon: &T) -> Vec<Point<T>>
where T: Float
{
if orig.len() < 3 || orig.is_empty() {
return orig.to_vec();
}
let max = orig.len();
let mut adjacent: Vec<(_)> = (0..orig.len())
.map(|i| {
if i == 0 {
(-1_i32, 1_i32)
} else {
((i - 1) as i32, (i + 1) as i32)
}
})
.collect();
let mut pq = BinaryHeap::new();
for (i, win) in orig.windows(3).enumerate() {
pq.push(VScore {
area: area(win.first().unwrap(), &win[1], win.last().unwrap()),
current: i + 1,
left: i,
right: i + 2,
});
}
loop {
let smallest = match pq.pop() {
None => break,
Some(ref x) if x.area > *epsilon => continue,
Some(s) => s,
};
let (left, right) = adjacent[smallest.current];
if left as i32 != smallest.left as i32 || right as i32 != smallest.right as i32 {
continue;
}
let (ll, _) = adjacent[left as usize];
let (_, rr) = adjacent[right as usize];
adjacent[left as usize] = (ll, right);
adjacent[right as usize] = (left, rr);
adjacent[smallest.current as usize] = (0, 0);
let choices = [(ll, left, right), (left, right, rr)];
for &(ai, current_point, bi) in &choices {
if ai as usize >= max || bi as usize >= max {
continue;
}
let new_left = Point::new(orig[ai as usize].x(), orig[ai as usize].y());
let new_current = Point::new(orig[current_point as usize].x(),
orig[current_point as usize].y());
let new_right = Point::new(orig[bi as usize].x(), orig[bi as usize].y());
pq.push(VScore {
area: area(&new_left, &new_current, &new_right),
current: current_point as usize,
left: ai as usize,
right: bi as usize,
});
}
}
orig.iter()
.zip(adjacent.iter())
.filter_map(|(tup, adj)| { if *adj != (0, 0) { Some(*tup) } else { None } })
.collect::<Vec<Point<T>>>()
}
fn area<T>(p1: &Point<T>, p2: &Point<T>, p3: &Point<T>) -> T
where T: Float
{
((p1.x() - p3.x()) * (p2.y() - p3.y()) - (p2.x() - p3.x()) * (p1.y() - p3.y())).abs() /
(T::one() + T::one())
}
pub trait SimplifyVW<T, Epsilon = T> {
fn simplifyvw(&self, epsilon: &T) -> Self where T: Float;
}
impl<T> SimplifyVW<T> for LineString<T>
where T: Float
{
fn simplifyvw(&self, epsilon: &T) -> LineString<T> {
LineString(visvalingam(&self.0, epsilon))
}
}
#[cfg(test)]
mod test {
use types::Point;
use super::visvalingam;
#[test]
fn visvalingam_test() {
let points = vec![(5.0, 2.0), (3.0, 8.0), (6.0, 20.0), (7.0, 25.0), (10.0, 10.0)];
let points_ls: Vec<_> = points.iter().map(|e| Point::new(e.0, e.1)).collect();
let correct = vec![(5.0, 2.0), (7.0, 25.0), (10.0, 10.0)];
let correct_ls: Vec<_> = correct.iter().map(|e| Point::new(e.0, e.1)).collect();
let simplified = visvalingam(&points_ls, &30.);
assert_eq!(simplified, correct_ls);
}
#[test]
fn visvalingam_test_long() {
let points = include!("test_fixtures/vw_orig.rs");
let points_ls: Vec<_> = points.iter().map(|e| Point::new(e[0], e[1])).collect();
let correct = include!("test_fixtures/vw_simplified.rs");
let correct_ls: Vec<_> = correct.iter().map(|e| Point::new(e[0], e[1])).collect();
let simplified = visvalingam(&points_ls, &0.0005);
assert_eq!(simplified, correct_ls);
}
#[test]
fn visvalingam_test_empty_linestring() {
let vec = Vec::new();
let compare = Vec::new();
let simplified = visvalingam(&vec, &1.0);
assert_eq!(simplified, compare);
}
#[test]
fn visvalingam_test_two_point_linestring() {
let mut vec = Vec::new();
vec.push(Point::new(0.0, 0.0));
vec.push(Point::new(27.8, 0.1));
let mut compare = Vec::new();
compare.push(Point::new(0.0, 0.0));
compare.push(Point::new(27.8, 0.1));
let simplified = visvalingam(&vec, &1.0);
assert_eq!(simplified, compare);
}
}