use crate::data_structures::PriorityQueue;
use alloc::vec::Vec;
use core::f64::consts::SQRT_2;
use libm::{fmax, fmin, sqrt};
use s2json::{GetXY, MValueCompatible, NewXYM};
#[derive(Debug, Default, Copy, MValueCompatible, Clone, PartialEq)]
pub struct PolyLabelMetadata {
pub distance: f64,
}
impl PolyLabelMetadata {
pub fn new(distance: f64) -> Self {
PolyLabelMetadata { distance }
}
}
#[derive(Debug, Default, Clone)]
pub struct PolyLabelCell {
pub x: f64,
pub y: f64,
pub h: f64,
pub d: f64,
pub max: f64,
}
pub fn polylabels<P: GetXY, R: NewXYM<PolyLabelMetadata>>(
polygons: &[Vec<Vec<P>>],
precision: Option<f64>,
) -> Vec<R> {
polygons.iter().map(|polygon| polylabel(polygon, precision)).collect()
}
pub fn polylabel<P: GetXY, R: NewXYM<PolyLabelMetadata>>(
polygon: &Vec<Vec<P>>,
precision: Option<f64>,
) -> R {
let precision = precision.unwrap_or(1.0);
let mut min_x = f64::MAX;
let mut min_y = f64::MAX;
let mut max_x = f64::MIN;
let mut max_y = f64::MIN;
if polygon.is_empty() || polygon[0].is_empty() {
return R::new_xym(0.0, 0.0, PolyLabelMetadata::default());
}
for point in &polygon[0] {
let (x, y) = point.xy();
if x < min_x {
min_x = x;
}
if y < min_y {
min_y = y;
}
if x > max_x {
max_x = x;
}
if y > max_y {
max_y = y;
}
}
let width = max_x - min_x;
let height = max_y - min_y;
let cell_size = fmax(precision, fmin(width, height));
if cell_size == precision {
return R::new_xym(min_x, min_y, PolyLabelMetadata::default());
}
let mut cell_queue =
PriorityQueue::<PolyLabelCell>::new(|a: &PolyLabelCell, b: &PolyLabelCell| {
b.max.partial_cmp(&a.max).unwrap_or(core::cmp::Ordering::Equal)
});
let mut best_cell = get_centroid_cell(polygon);
let bbox_cell = build_cell(min_x + width / 2., min_y + height / 2., 0., polygon);
if bbox_cell.d > best_cell.d {
best_cell = bbox_cell;
}
let potentially_queue =
|x: f64,
y: f64,
h: f64,
best_cell: &mut PolyLabelCell,
cell_queue: &mut PriorityQueue<PolyLabelCell>| {
let cell = build_cell(x, y, h, polygon);
if cell.max > best_cell.d + precision {
cell_queue.push(cell.clone());
}
if cell.d > best_cell.d {
*best_cell = cell;
}
};
let mut h = cell_size / 2.;
let mut x = min_x;
while x < max_x {
let mut y = min_y;
while y < max_y {
potentially_queue(x + h, y + h, h, &mut best_cell, &mut cell_queue);
y += cell_size;
}
x += cell_size;
}
loop {
let cell = cell_queue.pop();
if cell.is_none() {
break;
}
let PolyLabelCell { max, x, y, h: ch, .. } = &cell.unwrap();
if max - best_cell.d <= precision {
break;
}
h = ch / 2.;
potentially_queue(x - h, y - h, h, &mut best_cell, &mut cell_queue);
potentially_queue(x + h, y - h, h, &mut best_cell, &mut cell_queue);
potentially_queue(x - h, y + h, h, &mut best_cell, &mut cell_queue);
potentially_queue(x + h, y + h, h, &mut best_cell, &mut cell_queue);
}
R::new_xym(best_cell.x, best_cell.y, PolyLabelMetadata::default())
}
fn build_cell<P: GetXY>(x: f64, y: f64, h: f64, polygon: &Vec<Vec<P>>) -> PolyLabelCell {
let d = point_to_polygon_dist(x, y, polygon);
PolyLabelCell { x, y, h, d, max: d + h * SQRT_2 }
}
fn point_to_polygon_dist<P: GetXY>(x: f64, y: f64, polygon: &Vec<Vec<P>>) -> f64 {
let mut inside = false;
let mut min_dist_sq = f64::MAX;
for ring in polygon {
let len = ring.len();
let mut j = len - 1;
for i in 0..len {
let a = &ring[i];
let b = &ring[j];
if (a.y() > y) != (b.y() > y)
&& x < ((b.x() - a.x()) * (y - a.y())) / (b.y() - a.y()) + a.x()
{
inside = !inside;
}
min_dist_sq = fmin(min_dist_sq, get_seg_dist_sq(x, y, a, b));
j = i; }
}
if min_dist_sq == 0. { 0. } else { (if inside { 1. } else { -1. }) * sqrt(min_dist_sq) }
}
fn get_centroid_cell<P: GetXY>(polygon: &Vec<Vec<P>>) -> PolyLabelCell {
let mut area = 0.;
let mut x = 0.;
let mut y = 0.;
let points = &polygon[0];
let len = points.len();
let mut j = len - 1;
for i in 0..len {
let a = &points[i];
let b = &points[j];
let f = a.x() * b.y() - b.x() * a.y();
x += (a.x() + b.x()) * f;
y += (a.y() + b.y()) * f;
area += f * 3.;
j = i; }
let centroid = build_cell(x / area, y / area, 0., polygon);
if area == 0. || centroid.d < 0. {
build_cell(points[0].x(), points[0].y(), 0., polygon)
} else {
centroid
}
}
fn get_seg_dist_sq<P: GetXY>(px: f64, py: f64, a: &P, b: &P) -> f64 {
let mut x = a.x();
let mut y = a.y();
let mut dx = b.x() - x;
let mut dy = b.y() - y;
if dx != 0. || dy != 0. {
let t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
if t > 1. {
x = b.x();
y = b.y();
} else if t > 0. {
x += dx * t;
y += dy * t;
}
}
dx = px - x;
dy = py - y;
dx * dx + dy * dy
}