use std::cmp::min;
use geo::algorithm::area::Area;
use geo::algorithm::simplify_vw::SimplifyVw;
use geo_types::{Coord, LineString, Polygon, Triangle};
fn is_closed(ls: &[Coord<f64>]) -> bool {
if ls.len() < 2 {
false
} else {
ls.first() == ls.last()
}
}
pub(crate) fn smoothen_h3_coordinates(in_coords: &[Coord<f64>]) -> Vec<Coord<f64>> {
let closed = is_closed(in_coords);
let mut out = Vec::with_capacity(in_coords.len() + if closed { 2 } else { 0 });
if in_coords.len() >= 3 {
if !closed {
out.push(*in_coords.first().unwrap());
}
let apply_window = |c1: &Coord<f64>, c2: &Coord<f64>| Coord {
x: 0.5_f64.mul_add(c1.x, 0.5 * c2.x),
y: 0.5_f64.mul_add(c1.y, 0.5 * c2.y),
};
in_coords.windows(2).for_each(|window| {
out.push(apply_window(&window[0], &window[1]));
});
if closed {
out.push(apply_window(&in_coords[in_coords.len() - 1], &in_coords[0]));
let rotation_n = min(out.len(), 4);
out.rotate_right(rotation_n);
} else {
out.push(*in_coords.last().unwrap());
}
} else {
out = in_coords.to_vec();
}
if in_coords.len() >= 3 {
let out_ls = LineString::from(out);
let hexagon_corner_area =
Triangle::from([in_coords[0], in_coords[1], in_coords[2]]).unsigned_area();
out_ls.simplify_vw(&(hexagon_corner_area * 0.75)).0
} else {
out
}
}
pub fn smoothen_h3_linked_polygon(in_poly: &Polygon<f64>) -> Polygon<f64> {
Polygon::new(
LineString::from(smoothen_h3_coordinates(&in_poly.exterior().0)),
in_poly
.interiors()
.iter()
.map(|ring| LineString::from(smoothen_h3_coordinates(&ring.0)))
.collect(),
)
}
#[cfg(test)]
mod tests {
use geo::algorithm::coords_iter::CoordsIter;
use geo_types::Coord;
use crate::algorithm::smoothen_h3_linked_polygon;
use crate::{H3Cell, ToLinkedPolygons};
#[test]
fn smooth_donut_linked_polygon() {
let ring = H3Cell::from_coordinate(Coord::from((23.3, 12.3)), 6)
.unwrap()
.grid_ring_unsafe(4)
.unwrap();
let polygons = ring.to_linked_polygons(false).unwrap();
assert_eq!(polygons.len(), 1);
let smoothed = smoothen_h3_linked_polygon(&polygons[0]);
assert!(smoothed.exterior().coords_count() < 10);
assert_eq!(smoothed.interiors().len(), 1);
assert!(smoothed.interiors()[0].coords_count() < 10);
}
}