1use crate::db;
10use ndarray::{Array2};
11use num_traits::{Num, FromPrimitive, ToPrimitive};
12
13#[derive(Clone)]
48pub struct DensityMap<F, Z> {
49 pub dimension: db::Rect<F>,
51 pub data: Array2<Z>,
53}
54
55impl<F, Z> DensityMap<F, Z>
56 where F: Copy
57{
58 pub fn get_data(self) -> Array2<Z> {
60 self.data
61 }
62
63 pub fn get_data_ref(&self) -> &Array2<Z> {
65 &self.data
66 }
67
68 pub fn get_data_ref_mut(&mut self) -> &mut Array2<Z> {
70 &mut self.data
71 }
72
73 pub fn dimension(&self) -> db::Rect<F> {
75 self.dimension
76 }
77
78 fn num_bins(&self) -> (usize, usize) {
80 self.data.dim()
81 }
82}
83
84impl<F, Z> DensityMap<F, Z>
85 where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
86 Z: Copy + std::ops::Div<F, Output=Z> {
87 pub fn density_at(&self, p: db::Point<F>) -> Z {
93 self.get_density_at(p).expect("Point `p` is outside of the defined region.")
94 }
95
96
97 pub fn get_density_at(&self, p: db::Point<F>) -> Option<Z> {
102 self.get_value_at(p)
103 .map(|v| v / self.bin_area())
104 }
105}
106
107impl<F, Z> DensityMap<F, Z>
108 where F: Copy + Num + FromPrimitive + ToPrimitive + PartialOrd,
109 Z: Copy {
110 pub fn bin_dimension(&self) -> (F, F) {
112 let (w, h) = self.num_bins();
113 let bin_width = self.dimension.width() / F::from_usize(w).unwrap();
114 let bin_height = self.dimension.height() / F::from_usize(h).unwrap();
115 (bin_width, bin_height)
116 }
117
118 pub fn bin_area(&self) -> F {
120 let (w, h) = self.bin_dimension();
121 w * h
122 }
123
124 pub fn coordinates_to_indices(&self, p: db::Point<F>) -> (usize, usize) {
126 let r = self.dimension;
127 assert!(r.contains_point(p), "Point is not inside boundary.");
128 let (w, h) = self.data.dim();
129 let x = (p.x - r.lower_left.x) * F::from_usize(w).unwrap() / r.width();
130 let y = (p.y - r.lower_left.y) * F::from_usize(h).unwrap() / r.height();
131 (x.to_usize().unwrap(),
132 y.to_usize().unwrap())
133 }
134
135 pub fn value_at(&self, p: db::Point<F>) -> Z {
142 self.get_value_at(p).expect("Point `p` is out of the defined region.")
143 }
144
145 pub fn get_value_at(&self, p: db::Point<F>) -> Option<Z> {
149 if self.dimension.contains_point(p) {
150 let (x, y) = self.coordinates_to_indices(p);
151 Some(self.data[[x, y]])
152 } else {
153 None
154 }
155 }
156}
157
158impl<F, Z> DensityMap<F, Z>
159 where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
160 Z: Num + std::ops::AddAssign + Copy + Clone + std::ops::Mul<F, Output=Z> {
161
162 pub fn new(r: db::Rect<F>, (w, h): (usize, usize)) -> Self {
165 Self {
166 dimension: r,
167 data: Array2::zeros((w, h)),
168 }
169 }
170
171 pub fn from_data(r: db::Rect<F>, data: Array2<Z>) -> Self {
174 Self {
175 dimension: r,
176 data,
177 }
178 }
179
180 pub fn clear(&mut self) {
182 self.data.iter_mut().for_each(|x| *x = Z::zero());
183 }
184
185 fn bin_lower_left_corner(&self, (x, y): (usize, usize)) -> db::Point<F> {
187 let (bin_width, bin_height) = self.bin_dimension();
188 let (x, y) = (F::from_usize(x).unwrap(), F::from_usize(y).unwrap());
190 self.dimension.lower_left() + db::Point::new(x * bin_width, y * bin_height)
191 }
192
193 pub fn bin_center(&self, (x, y): (usize, usize)) -> db::Point<F> {
195 let (bin_width, bin_height) = self.bin_dimension();
196 let _2 = F::one() + F::one();
197 let bin_center = db::Point::new(bin_width / _2, bin_height / _2);
198 self.bin_lower_left_corner((x, y)) + bin_center
199 }
200
201 pub fn get_bin_shape(&self, (x, y): (usize, usize)) -> db::Rect<F> {
203
204 let start = self.bin_lower_left_corner((x, y));
206 let (bin_width, bin_height) = self.bin_dimension();
208 let end = start + db::Point::new(bin_width, bin_height);
209
210 db::Rect::new(start, end)
211 }
212
213 pub fn draw_rect(&mut self, r: &db::Rect<F>, value: Z) {
217
218 if let Some(r) = r.intersection(&self.dimension)
220 {
221
222 let (xstart, ystart) = self.coordinates_to_indices(r.lower_left);
224 let (xend, yend) = self.coordinates_to_indices(r.upper_right);
225
226 let xend = xend.min(self.data.dim().0-1);
227 let yend = yend.min(self.data.dim().1-1);
228
229 let bin_area = self.bin_area();
230 for x in xstart..xend+1 {
232 for y in ystart..yend+1 {
233 if x == xstart || x == xend || y == ystart || y == yend {
234 let bin_shape = self.get_bin_shape((x, y));
237 let overlap_area = bin_shape.intersection(&r)
238 .map(|r| r.width() * r.height())
239 .unwrap_or(F::zero());
240 self.data[[x, y]] += value * overlap_area;
241 } else {
242 self.data[[x, y]] += value * bin_area;
243 }
244 }
245 }
246 }
247 }
248}
249
250
251impl<F, Z> DensityMap<F, Z>
252 where F: Copy + Num + PartialOrd + FromPrimitive + ToPrimitive,
253 Z: Num + std::ops::AddAssign + Copy + Clone + std::ops::Mul<F, Output=Z> + FromPrimitive {
254
255 pub fn downsample(&self, reduction_factor: usize) -> Self {
260 assert!(reduction_factor >= 1);
261 let (w, h) = (self.data.shape()[0], self.data.shape()[1]);
262 assert_eq!(w % reduction_factor, 0, "Dimension must be divisible by the reduction factor.");
263 assert_eq!(h % reduction_factor, 0, "Dimension must be divisible by the reduction factor.");
264
265 let w_new = w / reduction_factor;
266 let h_new = h / reduction_factor;
267
268 let mut new_data = Array2::zeros((w_new, h_new));
269
270 for x in 0..w {
271 let x_new = x / reduction_factor;
272 for y in 0..h {
273 let y_new = y / reduction_factor;
274
275 new_data[[x_new, y_new]] += self.data[[x, y]];
276 }
277 }
278
279 Self::from_data(self.dimension, new_data)
285 }
286}
287
288 #[test]
289fn test_coordinates_to_indices() {
290 let c: DensityMap<_, f64> = DensityMap::new(db::Rect::new((0.0, 0.0),
291 (10.0, 20.0)), (10, 20));
292 assert_eq!(c.coordinates_to_indices(db::Point::new(0.0, 0.0)), (0, 0));
293 assert_eq!(c.coordinates_to_indices(db::Point::new(10.0, 20.0)), (10, 20));
294 assert_eq!(c.coordinates_to_indices(db::Point::new(0.5, 0.5)), (0, 0));
295}
296
297#[test]
298fn test_bin() {
299 let c: DensityMap<_, f64> = DensityMap::new(db::Rect::new((0.0, 0.0),
300 (10.0, 20.0)), (10, 10));
301 assert_eq!(c.num_bins(), (10, 10));
302 assert_eq!(c.get_bin_shape((0, 0)), db::Rect::new((0.0, 0.0), (1.0, 2.0)));
303}
304
305#[test]
306fn test_draw_rect() {
307
308 use db::traits::DoubledOrientedArea;
309
310 let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
311 (10.0, 10.0)), (10, 10));
312 let r = db::Rect::new((1.0, 1.0), (2.0, 3.0));
313 c.draw_rect(&r, 1.0);
314 assert_eq!(c.data[[0, 0]], 0.0);
315 assert_eq!(c.data[[1, 1]], 1.0);
316 assert_eq!(c.data[[2, 3]], 0.0);
317
318 let sum: f64 = c.data.iter().sum();
319 assert_eq!(
320 2.0 * sum,
321 r.area_doubled_oriented()
322 );
323}
324
325#[test]
326fn test_draw_rect_with_partial_bins() {
327
328 use db::traits::DoubledOrientedArea;
329
330 let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
331 (10.0, 10.0)), (10, 10));
332 let r = db::Rect::new((1.5, 1.5), (5.25, 6.0));
333 c.draw_rect(&r, 1.0);
334 assert_eq!(c.data[[0, 0]], 0.0);
335 assert_eq!(c.data[[1, 1]], 0.25);
336 assert_eq!(c.data[[1, 2]], 0.5);
337 assert_eq!(c.data[[2, 2]], 1.0);
338
339 let sum: f64 = c.data.iter().sum();
341 assert_eq!(
342 2.0 * sum,
343 r.area_doubled_oriented()
344 );
345}
346
347
348#[test]
349fn test_draw_oversize_rect() {
350 let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
351 (10.0, 10.0)), (10, 10));
352 let r = db::Rect::new((-1.0, -1.0), (11.0, 11.0));
353 c.draw_rect(&r, 1.0);
354 assert_eq!(c.data[[0, 0]], 1.0);
355 assert_eq!(c.data[[9, 9]], 1.0);
356
357}
358
359#[test]
360fn test_draw_oversize_rect_1x1() {
361 let mut c = DensityMap::new(db::Rect::new((0.0, 0.0),
362 (10.0, 10.0)), (1, 1));
363 let r = db::Rect::new((-1.0, -1.0), (11.0, 11.0));
364 c.draw_rect(&r, 1.0);
365 assert_eq!(c.density_at((0.0, 0.0).into()), 1.0);
366 assert_eq!(c.data[[0, 0]], 100.0);
367
368}