use crate::db;
use ndarray::{Array2};
use num_traits::{Num, FromPrimitive, ToPrimitive};
#[derive(Clone)]
pub struct DensityMap<F, Z> {
pub dimension: db::Rect<F>,
pub data: Array2<Z>,
}
impl<F, Z> DensityMap<F, Z>
where F: Copy
{
pub fn get_data(self) -> Array2<Z> {
self.data
}
pub fn get_data_ref(&self) -> &Array2<Z> {
&self.data
}
pub fn get_data_ref_mut(&mut self) -> &mut Array2<Z> {
&mut self.data
}
pub fn dimension(&self) -> db::Rect<F> {
self.dimension
}
fn num_bins(&self) -> (usize, usize) {
self.data.dim()
}
}
impl<F, Z> DensityMap<F, Z>
where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
Z: Copy + std::ops::Div<F, Output=Z> {
pub fn density_at(&self, p: db::Point<F>) -> Z {
self.get_density_at(p).expect("Point `p` is outside of the defined region.")
}
pub fn get_density_at(&self, p: db::Point<F>) -> Option<Z> {
self.get_value_at(p)
.map(|v| v / self.bin_area())
}
}
impl<F, Z> DensityMap<F, Z>
where F: Copy + Num + FromPrimitive + ToPrimitive + PartialOrd,
Z: Copy {
pub fn bin_dimension(&self) -> (F, F) {
let (w, h) = self.num_bins();
let bin_width = self.dimension.width() / F::from_usize(w).unwrap();
let bin_height = self.dimension.height() / F::from_usize(h).unwrap();
(bin_width, bin_height)
}
pub fn bin_area(&self) -> F {
let (w, h) = self.bin_dimension();
w * h
}
pub fn coordinates_to_indices(&self, p: db::Point<F>) -> (usize, usize) {
let r = self.dimension;
assert!(r.contains_point(p), "Point is not inside boundary.");
let (w, h) = self.data.dim();
let x = (p.x - r.lower_left.x) * F::from_usize(w).unwrap() / r.width();
let y = (p.y - r.lower_left.y) * F::from_usize(h).unwrap() / r.height();
(x.to_usize().unwrap(),
y.to_usize().unwrap())
}
pub fn value_at(&self, p: db::Point<F>) -> Z {
self.get_value_at(p).expect("Point `p` is out of the defined region.")
}
pub fn get_value_at(&self, p: db::Point<F>) -> Option<Z> {
if self.dimension.contains_point(p) {
let (x, y) = self.coordinates_to_indices(p);
Some(self.data[[x, y]])
} else {
None
}
}
}
impl<F, Z> DensityMap<F, Z>
where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
Z: Num + std::ops::AddAssign + Copy + Clone + std::ops::Mul<F, Output=Z> {
pub fn new(r: db::Rect<F>, (w, h): (usize, usize)) -> Self {
Self {
dimension: r,
data: Array2::zeros((w, h)),
}
}
pub fn from_data(r: db::Rect<F>, data: Array2<Z>) -> Self {
Self {
dimension: r,
data,
}
}
pub fn clear(&mut self) {
self.data.iter_mut().for_each(|x| *x = Z::zero());
}
fn bin_lower_left_corner(&self, (x, y): (usize, usize)) -> db::Point<F> {
let (bin_width, bin_height) = self.bin_dimension();
let (x, y) = (F::from_usize(x).unwrap(), F::from_usize(y).unwrap());
self.dimension.lower_left() + db::Point::new(x * bin_width, y * bin_height)
}
pub fn bin_center(&self, (x, y): (usize, usize)) -> db::Point<F> {
let (bin_width, bin_height) = self.bin_dimension();
let _2 = F::one() + F::one();
let bin_center = db::Point::new(bin_width / _2, bin_height / _2);
self.bin_lower_left_corner((x, y)) + bin_center
}
pub fn get_bin_shape(&self, (x, y): (usize, usize)) -> db::Rect<F> {
let start = self.bin_lower_left_corner((x, y));
let (bin_width, bin_height) = self.bin_dimension();
let end = start + db::Point::new(bin_width, bin_height);
db::Rect::new(start, end)
}
pub fn draw_rect(&mut self, r: &db::Rect<F>, value: Z) {
if let Some(r) = r.intersection(&self.dimension)
{
let (xstart, ystart) = self.coordinates_to_indices(r.lower_left);
let (xend, yend) = self.coordinates_to_indices(r.upper_right);
let xend = xend.min(self.data.dim().0-1);
let yend = yend.min(self.data.dim().1-1);
let bin_area = self.bin_area();
for x in xstart..xend+1 {
for y in ystart..yend+1 {
if x == xstart || x == xend || y == ystart || y == yend {
let bin_shape = self.get_bin_shape((x, y));
let overlap_area = bin_shape.intersection(&r)
.map(|r| r.width() * r.height())
.unwrap_or(F::zero());
self.data[[x, y]] += value * overlap_area;
} else {
self.data[[x, y]] += value * bin_area;
}
}
}
}
}
}
impl<F, Z> DensityMap<F, Z>
where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
Z: Num + std::ops::AddAssign + Copy + Clone + std::ops::Mul<F, Output=Z> + FromPrimitive {
pub fn downsample(&self, reduction_factor: usize) -> Self {
assert!(reduction_factor >= 1);
let (w, h) = (self.data.shape()[0], self.data.shape()[1]);
assert_eq!(w % reduction_factor, 0, "Dimension must be divisible by the reduction factor.");
assert_eq!(h % reduction_factor, 0, "Dimension must be divisible by the reduction factor.");
let w_new = w / reduction_factor;
let h_new = h / reduction_factor;
let mut new_data = Array2::zeros((w_new, h_new));
for x in 0..w {
let x_new = x / reduction_factor;
for y in 0..h {
let y_new = y / reduction_factor;
new_data[[x_new, y_new]] += self.data[[x, y]];
}
}
Self::from_data(self.dimension, new_data)
}
}
#[test]
fn test_coordinates_to_indices() {
let c: DensityMap<_, f64> = DensityMap::new(db::Rect::new((0.0, 0.0),
(10.0, 20.0)), (10, 20));
assert_eq!(c.coordinates_to_indices(db::Point::new(0.0, 0.0)), (0, 0));
assert_eq!(c.coordinates_to_indices(db::Point::new(10.0, 20.0)), (10, 20));
assert_eq!(c.coordinates_to_indices(db::Point::new(0.5, 0.5)), (0, 0));
}
#[test]
fn test_bin() {
let c: DensityMap<_, f64> = DensityMap::new(db::Rect::new((0.0, 0.0),
(10.0, 20.0)), (10, 10));
assert_eq!(c.num_bins(), (10, 10));
assert_eq!(c.get_bin_shape((0, 0)), db::Rect::new((0.0, 0.0), (1.0, 2.0)));
}
#[test]
fn test_draw_rect() {
use db::traits::DoubledOrientedArea;
let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
(10.0, 10.0)), (10, 10));
let r = db::Rect::new((1.0, 1.0), (2.0, 3.0));
c.draw_rect(&r, 1.0);
assert_eq!(c.data[[0, 0]], 0.0);
assert_eq!(c.data[[1, 1]], 1.0);
assert_eq!(c.data[[2, 3]], 0.0);
let sum: f64 = c.data.iter().sum();
assert_eq!(
2.0 * sum,
r.area_doubled_oriented()
);
}
#[test]
fn test_draw_rect_with_partial_bins() {
use db::traits::DoubledOrientedArea;
let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
(10.0, 10.0)), (10, 10));
let r = db::Rect::new((1.5, 1.5), (5.25, 6.0));
c.draw_rect(&r, 1.0);
assert_eq!(c.data[[0, 0]], 0.0);
assert_eq!(c.data[[1, 1]], 0.25);
assert_eq!(c.data[[1, 2]], 0.5);
assert_eq!(c.data[[2, 2]], 1.0);
let sum: f64 = c.data.iter().sum();
assert_eq!(
2.0 * sum,
r.area_doubled_oriented()
);
}
#[test]
fn test_draw_oversize_rect() {
let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
(10.0, 10.0)), (10, 10));
let r = db::Rect::new((-1.0, -1.0), (11.0, 11.0));
c.draw_rect(&r, 1.0);
assert_eq!(c.data[[0, 0]], 1.0);
assert_eq!(c.data[[9, 9]], 1.0);
}
#[test]
fn test_draw_oversize_rect_1x1() {
let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
(10.0, 10.0)), (1, 1));
let r = db::Rect::new((-1.0, -1.0), (11.0, 11.0));
c.draw_rect(&r, 1.0);
assert_eq!(c.density_at((0.0, 0.0).into()), 1.0);
assert_eq!(c.data[[0, 0]], 100.0);
}