use alloc::vec;
use libm::pow;
use s2json::{MValue, VectorGeometry, VectorLineString, VectorPoint};
pub trait SimplifyVectorGeometry<M: Clone + Default = MValue> {
fn build_sq_dists(&mut self, tolerance: f64, maxzoom: Option<u8>);
fn simplify(&mut self, tolerance: f64, zoom: u8, maxzoom: Option<u8>);
}
impl<M: Clone + Default> SimplifyVectorGeometry<M> for VectorGeometry<M> {
fn build_sq_dists(&mut self, tolerance: f64, maxzoom: Option<u8>) {
build_sq_dists(self, tolerance, maxzoom);
}
fn simplify(&mut self, tolerance: f64, zoom: u8, maxzoom: Option<u8>) {
simplify(self, tolerance, zoom, maxzoom);
}
}
pub fn build_sq_dists<M: Clone + Default>(
geometry: &mut VectorGeometry<M>,
tolerance: f64,
maxzoom: Option<u8>,
) {
let maxzoom = maxzoom.unwrap_or(16);
let tol = pow(tolerance / (1 << maxzoom) as f64, 2.);
match geometry {
VectorGeometry::LineString(geo) => {
let coords = &mut geo.coordinates;
build_sq_dist(coords, 0, coords.len() - 1, tol);
}
VectorGeometry::Polygon(geo) | VectorGeometry::MultiLineString(geo) => {
let coords = &mut geo.coordinates;
coords.iter_mut().for_each(|line| build_sq_dist(line, 0, line.len() - 1, tol));
}
VectorGeometry::MultiPolygon(geo) => {
let coords = &mut geo.coordinates;
coords.iter_mut().for_each(|polygon| {
polygon.iter_mut().for_each(|line| build_sq_dist(line, 0, line.len() - 1, tol))
});
}
_ => {}
}
}
fn build_sq_dist<M: Clone + Default>(
coords: &mut VectorLineString<M>,
first: usize,
last: usize,
sq_tolerance: f64,
) {
coords[first].t = Some(1.);
_build_sq_dist(coords, first, last, sq_tolerance);
coords[last].t = Some(1.);
}
fn _build_sq_dist<M: Clone + Default>(
coords: &mut VectorLineString<M>,
first: usize,
last: usize,
sq_tolerance: f64,
) {
let mid = (last - first) >> 1;
let mut max_sq_dist = sq_tolerance;
let mut min_pos_to_mid = last - first;
let mut index: Option<usize> = None;
let VectorPoint { x: ax, y: ay, .. } = coords[first];
let VectorPoint { x: bx, y: by, .. } = coords[last];
let mut i = first;
while i < last {
let VectorPoint { x, y, .. } = coords[i];
let d = get_sq_seg_dist(x, y, ax, ay, bx, by);
if d > max_sq_dist {
index = Some(i);
max_sq_dist = d;
} else if d == max_sq_dist {
let pos_to_mid = isize::abs(i as isize - mid as isize) as usize;
if pos_to_mid < min_pos_to_mid {
index = Some(i);
min_pos_to_mid = pos_to_mid;
}
}
i += 1;
}
if max_sq_dist > sq_tolerance
&& let Some(index) = index
{
if index - first > 1 {
_build_sq_dist(coords, first, index, sq_tolerance);
}
coords[index].t = Some(max_sq_dist);
if last - index > 1 {
_build_sq_dist(coords, index, last, sq_tolerance);
}
}
}
fn get_sq_seg_dist(ps: f64, pt: f64, s: f64, t: f64, bs: f64, bt: f64) -> f64 {
let mut s = s;
let mut t = t;
let mut ds = bs - s;
let mut dt = bt - t;
if ds != 0. || dt != 0. {
let m = ((ps - s) * ds + (pt - t) * dt) / (ds * ds + dt * dt);
if m > 1. {
s = bs;
t = bt;
} else if m > 0. {
s += ds * m;
t += dt * m;
}
}
ds = ps - s;
dt = pt - t;
ds * ds + dt * dt
}
pub fn simplify<M: Clone + Default>(
geometry: &mut VectorGeometry<M>,
tolerance: f64,
zoom: u8,
maxzoom: Option<u8>,
) {
let maxzoom = maxzoom.unwrap_or(16);
let zoom_tol = if zoom >= maxzoom { 0. } else { tolerance / (1 << (zoom as u64)) as f64 };
match geometry {
VectorGeometry::LineString(geo) => {
geo.coordinates = simplify_line(&geo.coordinates, zoom_tol, false, false);
}
VectorGeometry::Polygon(geo) | VectorGeometry::MultiLineString(geo) => {
geo.coordinates = geo
.coordinates
.iter()
.map(|line| simplify_line(line, zoom_tol, false, false))
.collect();
}
VectorGeometry::MultiPolygon(geo) => {
geo.coordinates = geo
.coordinates
.iter()
.map(|polygon| {
polygon.iter().map(|line| simplify_line(line, zoom_tol, true, true)).collect()
})
.collect();
}
_ => (),
}
}
fn simplify_line<M: Clone + Default>(
line: &VectorLineString<M>,
tolerance: f64,
is_polygon: bool,
is_outer: bool,
) -> VectorLineString<M> {
let sq_tolerance = tolerance * tolerance;
let size = line.len() as f64;
if tolerance > 0. && size < (if is_polygon { sq_tolerance } else { tolerance }) {
return line.clone();
}
let mut ring: VectorLineString<M> = vec![];
for point in line {
if tolerance == 0. || (point.t.unwrap_or(0.0)) > sq_tolerance {
ring.push(point.clone());
}
}
if is_polygon {
rewind(&mut ring, is_outer);
}
ring
}
pub fn rewind<M: Clone + Default>(ring: &mut VectorLineString<M>, clockwise: bool) {
let len = ring.len();
let mut area: f64 = 0.;
let mut i = 0;
let mut j = len - 2;
while i < len {
area += (ring[i].x - ring[j].x) * (ring[i].y + ring[j].y);
j = i;
i += 2;
}
i = 0;
if (area > 0.) == clockwise {
let len_half = len / 2;
while i < len_half {
ring.swap(i, len - i - 1);
i += 1;
}
}
}