cell_map/cell_map.rs
1//! # `CellMap` implementation
2
3// ------------------------------------------------------------------------------------------------
4// IMPORTS
5// ------------------------------------------------------------------------------------------------
6
7use std::{
8 marker::PhantomData,
9 ops::{Index, IndexMut},
10 usize,
11};
12
13use nalgebra::{Affine2, Point2, Vector2};
14use ndarray::{s, Array2};
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16
17use crate::{
18 cell_map_file::CellMapFile,
19 extensions::Point2Ext,
20 iterators::{
21 layerers::Many,
22 slicers::{Cells, Line, Windows},
23 CellMapIter, CellMapIterMut,
24 },
25 map_metadata::CellMapMetadata,
26 Error, Layer,
27};
28
29// ------------------------------------------------------------------------------------------------
30// STRUCTS
31// ------------------------------------------------------------------------------------------------
32
33/// Provides a many-layer 2D map of cellular data.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(
36 try_from = "CellMapFile<L, T>",
37 into = "CellMapFile<L, T>",
38 bound = "T: Clone + Serialize + DeserializeOwned, L: Serialize + DeserializeOwned"
39)]
40pub struct CellMap<L, T>
41where
42 L: Layer,
43{
44 /// Stores each layer in the map as an [`ndarray::Array2<T>`].
45 ///
46 /// TODO:
47 /// When constgenerics is stabilised would be good to make this an array of `L::NUM_LAYERS`, to
48 /// avoid the vec allocation.
49 pub(crate) data: Vec<Array2<T>>,
50
51 /// Metadata associated with this map.
52 pub(crate) metadata: CellMapMetadata,
53
54 /// The original parameters supplied to `CellMap::new()`.
55 pub(crate) params: CellMapParams,
56
57 layer_type: PhantomData<L>,
58}
59
60/// Contains parameters required to construct a [`CellMap`]
61#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
62pub struct CellMapParams {
63 /// The size (resolution) of each cell in the map, in parent frame coordinates.
64 ///
65 /// # Default
66 ///
67 /// The default value is `[1.0, 1.0]`.
68 pub cell_size: Vector2<f64>,
69
70 /// The number of cells in the `x` and `y` directions.
71 ///
72 /// # Default
73 ///
74 /// The default value is [`Bounds::empty()`].
75 pub cell_bounds: Bounds,
76
77 /// The rotation of the map's Z axis about the parent Z axis in radians.
78 ///
79 /// # Default
80 ///
81 /// The default value is `0.0`.
82 pub rotation_in_parent_rad: f64,
83
84 /// The position of the origin of the map in the parent frame, in parent frame units.
85 ///
86 /// # Default
87 ///
88 /// The default value is `[0.0, 0.0]`.
89 pub position_in_parent: Vector2<f64>,
90
91 /// The precision to use when determining cell boundaries.
92 ///
93 /// This precision factor allows us to account for times when a cell position should fit into a
94 /// particular cell index, but due to floating point rounding does not. For example take a map
95 /// with a `cell_size = [0.1, 0.1]`, the cell index of the position `[0.7, 0.1]` should be `[7,
96 /// 1`], however the positions floating point index would be calculated as `[6.999999999999998,
97 /// 0.9999999999999999]`, which if `floor()`ed to fit into a `usize` would give the incorrect
98 /// index `[6, 0]`.
99 ///
100 /// When calculating cell index we therefore `floor` the floating point index unless it is
101 /// within `cell_size * cell_boundary_precision`, in which case we round up to the next cell.
102 /// Mutliplying by `cell_size` allows this value to be independent of the scale of the map.
103 ///
104 /// # Default
105 ///
106 /// The default value is `1e-10`.
107 pub cell_boundary_precision: f64,
108}
109
110/// Rectangular bounds describing the number of cells in each direction of the map.
111///
112/// These bounds are a half-open range, i.e. satisfied in the ranges:
113/// - $x_0 <= x < x_1$
114/// - $y_0 <= y < y_1$
115// NOTE: Range isn't uses since it's not Copy
116#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
117pub struct Bounds {
118 /// The bounds on the x axis, in the format (min, max),
119 pub x: (isize, isize),
120
121 /// The bounds on the y axis, in the format (min, max),
122 pub y: (isize, isize),
123}
124
125// ------------------------------------------------------------------------------------------------
126// IMPLS
127// ------------------------------------------------------------------------------------------------
128
129impl<L, T> CellMap<L, T>
130where
131 L: Layer,
132{
133 /// Creates a new map from the given data.
134 ///
135 /// If data is the wrong shape or has the wrong number of layers this function will return an
136 /// error.
137 pub fn new_from_data(params: CellMapParams, data: Vec<Array2<T>>) -> Result<Self, Error> {
138 if data.len() != L::NUM_LAYERS {
139 return Err(Error::WrongNumberOfLayers(L::NUM_LAYERS, data.len()));
140 }
141
142 if !data.is_empty() {
143 let layer_cells = (data[0].shape()[0], data[0].shape()[1]);
144
145 if layer_cells != params.cell_bounds.get_shape() {
146 return Err(Error::LayerWrongShape(
147 layer_cells,
148 params.cell_bounds.get_shape(),
149 ));
150 }
151 }
152
153 Ok(Self {
154 data,
155 metadata: params.into(),
156 params,
157 layer_type: PhantomData,
158 })
159 }
160
161 /// Returns the size of the cells in the map.
162 pub fn cell_size(&self) -> Vector2<f64> {
163 self.metadata.cell_size
164 }
165
166 /// Returns the number of cells in each direction of the map.
167 pub fn num_cells(&self) -> Vector2<usize> {
168 self.metadata.num_cells
169 }
170
171 /// Returns the bounds of this map
172 pub fn cell_bounds(&self) -> Bounds {
173 self.metadata.cell_bounds
174 }
175
176 /// Returns the parameters used to build this map.
177 pub fn params(&self) -> CellMapParams {
178 self.params
179 }
180
181 /// Gets the [`nalgebra::Affine2<f64>`] transformation between the map frame and the parent
182 /// frame.
183 pub fn to_parent(&self) -> Affine2<f64> {
184 self.metadata.to_parent
185 }
186
187 /// Moves this map relative to a new position and rotation relative to the parent frame.
188 ///
189 /// **Note:** This doesn't move the data relative to the map origin, the indexes into the map
190 /// remain the same, but the position of each cell in the map will change.
191 pub fn move_map(&mut self, position_in_parent: Vector2<f64>, rotation_in_parent_rad: f64) {
192 // Recalculate the map's to_parent affine
193 self.metadata.to_parent = CellMapMetadata::calc_to_parent(
194 position_in_parent,
195 rotation_in_parent_rad,
196 self.metadata.cell_size,
197 );
198
199 // Update the parameter values
200 self.params.position_in_parent = position_in_parent;
201 self.params.rotation_in_parent_rad = rotation_in_parent_rad;
202 }
203
204 /// Returns whether or not the given index is inside the map.
205 pub fn index_in_map(&self, index: Point2<usize>) -> bool {
206 self.metadata.is_in_map(index)
207 }
208
209 /// Returns whether or not the given parent-relative position is inside the map.
210 pub fn position_in_map(&self, position: Point2<f64>) -> bool {
211 self.index(position).is_some()
212 }
213
214 /// Get a reference to the value at the given layer and index. Returns `None` if the index is
215 /// outside the bounds of the map.
216 pub fn get(&self, layer: L, index: Point2<usize>) -> Option<&T> {
217 if self.index_in_map(index) {
218 Some(&self[(layer, index)])
219 } else {
220 None
221 }
222 }
223
224 /// Get a reference to the value at the given layer and index, without checking the bounds of
225 /// the map.
226 ///
227 /// # Safety
228 ///
229 /// This function will panic if `index` is outside the map.
230 pub unsafe fn get_unchecked(&self, layer: L, index: Point2<usize>) -> &T {
231 &self[(layer, index)]
232 }
233
234 /// Get a mutable reference to the value at the given layer and index. Returns `None` if the
235 /// index is outside the bounds of the map.
236 pub fn get_mut(&mut self, layer: L, index: Point2<usize>) -> Option<&mut T> {
237 if self.index_in_map(index) {
238 Some(&mut self[(layer, index)])
239 } else {
240 None
241 }
242 }
243
244 /// Get a mutable reference to the value at the given layer and index, without checking the
245 /// bounds of the map.
246 ///
247 /// # Safety
248 ///
249 /// This function will panic if `index` is outside the map.
250 pub unsafe fn get_mut_unchecked(&mut self, layer: L, index: Point2<usize>) -> &mut T {
251 &mut self[(layer, index)]
252 }
253
254 /// Set the given layer and index in the map to the given value. Returns an [`Error`] if the
255 /// index was outside the map.
256 pub fn set(&mut self, layer: L, index: Point2<usize>, value: T) -> Result<(), Error> {
257 if self.index_in_map(index) {
258 self[(layer, index)] = value;
259 Ok(())
260 } else {
261 Err(Error::IndexOutsideMap(index))
262 }
263 }
264
265 /// Set the given layer and index in the map to the given value, without checking if index is
266 /// the map.
267 ///
268 /// # Safety
269 ///
270 /// This function will panic if `index` is outside the map
271 pub unsafe fn set_unchecked(&mut self, layer: L, index: Point2<usize>, value: T) {
272 self[(layer, index)] = value;
273 }
274
275 /// Returns the position in the parent frame of the centre of the given cell index.
276 ///
277 /// Returns `None` if the given `index` is not inside the map.
278 pub fn position(&self, index: Point2<usize>) -> Option<Point2<f64>> {
279 self.metadata.position(index)
280 }
281
282 /// Returns the position in the parent frame of the centre of the given cell index, without
283 /// checking that the `index` is inside the map.
284 ///
285 /// # Safety
286 ///
287 /// This method won't panic if `index` is outside the map, but it's result can't be guaranteed
288 /// to be a position in the map.
289 pub fn position_unchecked(&self, index: Point2<usize>) -> Point2<f64> {
290 self.metadata.position_unchecked(index)
291 }
292
293 /// Get the cell index of the given poisition.
294 ///
295 /// Returns `None` if the given `position` is not inside the map.
296 pub fn index(&self, position: Point2<f64>) -> Option<Point2<usize>> {
297 self.metadata.index(position)
298 }
299
300 /// Get the cell index of the given poisition, without checking that the position is inside the
301 /// map.
302 ///
303 /// # Safety
304 ///
305 /// This function will not panic if `position` is outside the map, but use of the result to
306 /// index into the map is not guaranteed to be safe. It is possible for this function to return
307 /// a negative index value, which would indicate that the cell is outside the map.
308 pub unsafe fn index_unchecked(&self, position: Point2<f64>) -> Point2<isize> {
309 self.metadata.index_unchecked(position)
310 }
311
312 /// Returns an iterator over each cell in all layers of the map.
313 pub fn iter(&self) -> CellMapIter<'_, L, T, Many<L>, Cells> {
314 CellMapIter::<'_, L, T, Many<L>, Cells>::new_cells(self)
315 }
316
317 /// Returns a mutable iterator over each cell in all layers of the map.
318 pub fn iter_mut(&mut self) -> CellMapIterMut<'_, L, T, Many<L>, Cells> {
319 CellMapIterMut::<'_, L, T, Many<L>, Cells>::new_cells(self)
320 }
321
322 /// Returns an iterator over windows of cells in the map.
323 ///
324 /// The `semi_width` is half the size of the window in the x and y axes, not including
325 /// the central cell. E.g. to have a window which is in total 5x5, the `semi_window_size` needs
326 /// to be `Vector2::new(2, 2)`.
327 pub fn window_iter(
328 &self,
329 semi_width: Vector2<usize>,
330 ) -> Result<CellMapIter<'_, L, T, Many<L>, Windows>, Error> {
331 CellMapIter::<'_, L, T, Many<L>, Windows>::new_windows(self, semi_width)
332 }
333
334 /// Returns a mutable iterator over windows of cells in the map.
335 ///
336 /// The `semi_width` is half the size of the window in the x and y axes, not including
337 /// the central cell. E.g. to have a window which is in total 5x5, the `semi_window_size` needs
338 /// to be `Vector2::new(2, 2)`.
339 pub fn window_iter_mut(
340 &mut self,
341 semi_width: Vector2<usize>,
342 ) -> Result<CellMapIterMut<'_, L, T, Many<L>, Windows>, Error> {
343 CellMapIterMut::<'_, L, T, Many<L>, Windows>::new_windows(self, semi_width)
344 }
345
346 /// Returns an iterator over cells along the line joining `start_position` and
347 /// `end_position`, which are expressed as positions in the map's parent frame.
348 pub fn line_iter(
349 &self,
350 start_position: Point2<f64>,
351 end_position: Point2<f64>,
352 ) -> Result<CellMapIter<'_, L, T, Many<L>, Line>, Error> {
353 CellMapIter::<'_, L, T, Many<L>, Line>::new_line(self, start_position, end_position)
354 }
355
356 /// Returns a mutable iterator over cells along the line joining `start_position` and
357 /// `end_position`, which are expressed as positions in the map's parent frame.
358 pub fn line_iter_mut(
359 &mut self,
360 start_position: Point2<f64>,
361 end_position: Point2<f64>,
362 ) -> Result<CellMapIterMut<'_, L, T, Many<L>, Line>, Error> {
363 CellMapIterMut::<'_, L, T, Many<L>, Line>::new_line(self, start_position, end_position)
364 }
365}
366
367impl<L, T> CellMap<L, T>
368where
369 L: Layer + Serialize,
370 T: Clone + Serialize,
371{
372 /// Builds a new [`CellMapFile`] from the given map, which can be serialised or deserialised
373 /// using serde.
374 pub fn to_cell_map_file(&self) -> CellMapFile<L, T> {
375 CellMapFile::new(self)
376 }
377
378 /// Writes the map to the given path as a JSON file.
379 #[cfg(feature = "json")]
380 pub fn write_json<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), Error> {
381 let map_file = CellMapFile::new(&self);
382 map_file.write_json(path)
383 }
384}
385
386impl<L, T> CellMap<L, T>
387where
388 L: Layer + DeserializeOwned,
389 T: DeserializeOwned,
390{
391 /// Loads a map stored in JSON format at the given path.
392 #[cfg(feature = "json")]
393 pub fn from_json<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
394 let map_file = CellMapFile::from_json(path)?;
395 map_file.into_cell_map()
396 }
397}
398
399impl<L, T> CellMap<L, T>
400where
401 L: Layer,
402 T: Clone,
403{
404 /// Creates a new [`CellMap`] from the given params, filling each cell with `elem`.
405 pub fn new_from_elem(params: CellMapParams, elem: T) -> Self {
406 let data = vec![Array2::from_elem(params.cell_bounds.get_shape(), elem); L::NUM_LAYERS];
407
408 Self {
409 data,
410 metadata: params.into(),
411 params,
412 layer_type: PhantomData,
413 }
414 }
415}
416
417impl<L, T> CellMap<L, T>
418where
419 L: Layer,
420 T: Default + Clone,
421{
422 /// Creates a new [`CellMap`] from the given params, filling each cell with `T::default()`.
423 pub fn new(params: CellMapParams) -> Self {
424 let data =
425 vec![Array2::from_elem(params.cell_bounds.get_shape(), T::default()); L::NUM_LAYERS];
426
427 Self {
428 data,
429 metadata: params.into(),
430 params,
431 layer_type: PhantomData,
432 }
433 }
434
435 /// Resizes the map into the new bounds, filling any newly added cells with `T::default()`.
436 ///
437 /// Any cells that are in the map currently, which would be outside the new map, are removed.
438 // NOTE: It doesn't seem possible to resize an ndarray in place, so we have to allocate a new
439 // one.
440 pub fn resize(&mut self, new_bounds: Bounds) {
441 // Allocate new data
442 let mut data = vec![Array2::from_elem(new_bounds.get_shape(), T::default()); L::NUM_LAYERS];
443
444 // Get the slice describing the position of the old map inside the new map, based on the
445 // bounds. If there's no intersection then we can skip this step
446 if let Some(old_in_new) = new_bounds.get_slice_of_other(&self.metadata.cell_bounds) {
447 // Get the slice of new relative to old. Unwrap is ok sice we already know there's an
448 // intersection.
449 let new_in_old = self
450 .metadata
451 .cell_bounds
452 .get_slice_of_other(&new_bounds)
453 .unwrap();
454 for (new, old) in data.iter_mut().zip(self.data.iter()) {
455 new.slice_mut(s![
456 old_in_new.y.0..old_in_new.y.1,
457 old_in_new.x.0..old_in_new.x.1
458 ])
459 .assign(&old.slice(s![
460 new_in_old.y.0..new_in_old.y.1,
461 new_in_old.x.0..new_in_old.x.1
462 ]));
463 }
464 }
465
466 self.data = data;
467 self.metadata.cell_bounds = new_bounds;
468 self.params.cell_bounds = new_bounds;
469 self.metadata.num_cells = new_bounds.get_num_cells();
470 }
471
472 /// Merge `other` into self, resizing `self` so that `other` will be fully included in the map.
473 ///
474 /// Both maps should belong to the same parent frame, and `other.cell_size <= self.cell_size`.
475 /// For `other`s that have larger cells than `self` you should implement your own merge functon
476 /// based on this one that could for example use 2D linear interpolation.
477 ///
478 /// `func` is responsible for actually merging data in both `self` and `other` into a single
479 /// new value in `self`. The first argument is the value of the cell in `self`, while the
480 /// second argument will be the values from cells in `other` whose centres lie within the cell
481 /// in `self`.
482 pub fn merge<F: Fn(&T, &[T]) -> T>(&mut self, other: &CellMap<L, T>, func: F) {
483 // First get the bounds of `other` wrt `self`, which we have to do by accounting for the
484 // potential different alignment of `other` wrt `parent`. We do this by getting the corner
485 // points, transforming from `other` to `parent`, then from `parent` to `self`. We have to
486 // transform all corner points because rotation may lead to the corners being in different
487 // positions than when aligned to `other`.
488 let other_bounds = other.cell_bounds();
489 let corners_in_other = vec![
490 Point2::new(other_bounds.x.0, other_bounds.y.0).cast(),
491 Point2::new(other_bounds.x.1, other_bounds.y.0).cast() + Vector2::new(1.0, 0.0),
492 Point2::new(other_bounds.x.0, other_bounds.y.1).cast() + Vector2::new(0.0, 1.0),
493 Point2::new(other_bounds.x.1, other_bounds.y.1).cast() + Vector2::new(1.0, 1.0),
494 ];
495 let corners_in_parent: Vec<Point2<f64>> = corners_in_other
496 .iter()
497 .map(|c| other.to_parent().transform_point(c))
498 .collect();
499 let other_bl_parent = Point2::new(
500 corners_in_parent
501 .iter()
502 .min_by_key(|c| c.x.floor() as isize)
503 .unwrap()
504 .x
505 .floor(),
506 corners_in_parent
507 .iter()
508 .min_by_key(|c| c.y.floor() as isize)
509 .unwrap()
510 .y
511 .floor(),
512 );
513 let other_ur_parent = Point2::new(
514 corners_in_parent
515 .iter()
516 .max_by_key(|c| c.x.ceil() as isize)
517 .unwrap()
518 .x
519 .ceil(),
520 corners_in_parent
521 .iter()
522 .max_by_key(|c| c.y.ceil() as isize)
523 .unwrap()
524 .y
525 .ceil(),
526 );
527 let other_in_self =
528 Bounds::from_corner_positions(&self.metadata, other_bl_parent, other_ur_parent);
529 let store_offset = Vector2::new(
530 other_in_self.x.0.clamp(0, self.num_cells().x as isize) as usize,
531 other_in_self.y.0.clamp(0, self.num_cells().y as isize) as usize,
532 );
533
534 // Calculate the union of both bounds
535 let new_bounds = self.cell_bounds().union(&other_in_self);
536
537 // Resize self
538 self.resize(new_bounds);
539
540 // Get the index offset to go from an index into self to the 2D storage array (see store)
541 let store_slice_in_new = if let Some(slice) = new_bounds.get_slice_of_other(&other_in_self)
542 {
543 slice
544 } else {
545 unreachable!("Other was not inside self's new bounds");
546 };
547
548 // For each layer in the map
549 for layer in L::all() {
550 // Create a new array of size other_in_self, which will hold a copy of all items in
551 // other which fall into each cell in self.
552 let mut store: Array2<Vec<T>> = Array2::default(other_in_self.get_shape());
553
554 // For each cell in other get its position in parent, convert that to a cell index in
555 // self, and add that cell's value to the store
556 for ((_, pos), val) in other.iter().layer(layer.clone()).positioned() {
557 // The index of pos in self
558 if let Some(idx) = self.index(pos) {
559 // Get the index into the store array by subtracting the store offset
560 let store_idx = (idx.cast() - store_offset).map(|e| e as usize);
561
562 // Mutate the store vector by pushing val into it
563 if let Some(vec) = store.get_mut(store_idx.as_array2_index()) {
564 vec.push(val.clone());
565 } else {
566 unreachable!("Store index {} was invalid", store_idx);
567 }
568 } else {
569 // Point was outside the map, this shouldn't happen
570 unreachable!("Point in other ({}) was outside self during merge", pos);
571 }
572 }
573
574 // Iterate over the store and self, calling the merge function with the value in self
575 // and the values in the store
576 for (self_val, store_vec) in self.data[layer.to_index()]
577 .slice_mut(s![
578 store_slice_in_new.y.0..store_slice_in_new.y.1,
579 store_slice_in_new.x.0..store_slice_in_new.x.1,
580 ])
581 .iter_mut()
582 .zip(store.iter())
583 {
584 *self_val = func(self_val, store_vec.as_slice());
585 }
586 }
587 }
588}
589
590impl<L, T> Index<L> for CellMap<L, T>
591where
592 L: Layer,
593{
594 type Output = Array2<T>;
595
596 fn index(&self, index: L) -> &Self::Output {
597 &self.data[index.to_index()]
598 }
599}
600
601impl<L, T> IndexMut<L> for CellMap<L, T>
602where
603 L: Layer,
604{
605 fn index_mut(&mut self, index: L) -> &mut Self::Output {
606 &mut self.data[index.to_index()]
607 }
608}
609
610impl<L, T> Index<(L, Point2<usize>)> for CellMap<L, T>
611where
612 L: Layer,
613{
614 type Output = T;
615
616 fn index(&self, index: (L, Point2<usize>)) -> &Self::Output {
617 &self[index.0][(index.1.y, index.1.x)]
618 }
619}
620
621impl<L, T> IndexMut<(L, Point2<usize>)> for CellMap<L, T>
622where
623 L: Layer,
624{
625 fn index_mut(&mut self, index: (L, Point2<usize>)) -> &mut Self::Output {
626 &mut self[index.0][(index.1.y, index.1.x)]
627 }
628}
629
630impl Default for CellMapParams {
631 fn default() -> Self {
632 Self {
633 cell_size: Vector2::new(1.0, 1.0),
634 cell_bounds: Bounds::empty(),
635 cell_boundary_precision: 1e-10,
636 rotation_in_parent_rad: 0.0,
637 position_in_parent: Vector2::zeros(),
638 }
639 }
640}
641
642impl Bounds {
643 /// Creates a new empty (zero sized) bound
644 pub fn empty() -> Self {
645 Self {
646 x: (0, 0),
647 y: (0, 0),
648 }
649 }
650
651 /// Returns if the bounds are valid or not, i.e. if the minimum is larger than the maximum.
652 pub fn is_valid(&self) -> bool {
653 self.x.0 <= self.x.1 && self.y.0 <= self.y.1
654 }
655
656 /// Creates a new bound from the given max and min cell indices in the x and y axes.
657 ///
658 /// Must satisfy:
659 /// - $x_0 <= x_1$
660 /// - $y_0 <= y_1$
661 pub fn new(x: (isize, isize), y: (isize, isize)) -> Result<Self, Error> {
662 let bounds = Self { x, y };
663
664 if bounds.is_valid() {
665 Ok(bounds)
666 } else {
667 Err(Error::InvalidBounds(bounds))
668 }
669 }
670
671 /// Creates a new bound from the given opposing corners of the a rectangle.
672 ///
673 /// If the corners do not satisfy `all(bottom_left <= upper_right)` the bounds will be invalid
674 /// and an error is returned.
675 pub fn from_corners(
676 bottom_left: Point2<isize>,
677 upper_right: Point2<isize>,
678 ) -> Result<Self, Error> {
679 let bounds = Self {
680 x: (bottom_left.x, upper_right.x),
681 y: (bottom_left.y, upper_right.y),
682 };
683
684 if bounds.is_valid() {
685 Ok(bounds)
686 } else {
687 Err(Error::InvalidBounds(bounds))
688 }
689 }
690
691 /// Creates a new bound from the given opposing corners of the a rectangle, but the corners do
692 /// not have to be sorted in bottom_left, upper_right order.
693 ///
694 /// This function will automatically decide which points are provided such that the bounds will
695 /// be valid.
696 pub fn from_corners_unsorted(a: Point2<isize>, b: Point2<isize>) -> Self {
697 Self {
698 x: (a.x.min(b.x), a.x.max(b.x)),
699 y: (a.y.min(b.y), a.y.max(b.y)),
700 }
701 }
702
703 /// Creates a new bound from the given corner positions, which do not have to be in any order.
704 ///
705 /// The metadata parameter will be used to map from parent frame position into a map frame.
706 pub(crate) fn from_corner_positions(
707 metadata: &CellMapMetadata,
708 a: Point2<f64>,
709 b: Point2<f64>,
710 ) -> Self {
711 // Get the map-rel cell of each point
712 let cell_a = metadata.get_cell(a);
713 let cell_b = metadata.get_cell(b);
714
715 // Build the bounds
716 Self::from_corners_unsorted(cell_a, cell_b)
717 }
718
719 /// Converts this bounds into a pair of corners, the bottom left and upper right corners
720 /// respectively.
721 pub fn as_corners(&self) -> (Point2<isize>, Point2<isize>) {
722 (
723 Point2::new(self.x.0, self.y.0),
724 Point2::new(self.x.1, self.y.1),
725 )
726 }
727
728 /// Checks if the given point is inside the bounds
729 pub fn contains(&self, point: Point2<isize>) -> bool {
730 self.x.0 <= point.x && point.x < self.x.1 && self.y.0 <= point.y && point.y < self.y.1
731 }
732
733 /// Gets the value of the point as an index into an array bounded by this `Bounds`.
734 ///
735 /// If the point is outside the bounds `None` is returned
736 pub fn get_index(&self, point: Point2<isize>) -> Option<Point2<usize>> {
737 if self.contains(point) {
738 let unchecked = unsafe { self.get_index_unchecked(point) };
739
740 // Have already checked that the point is inside the bounds, no need to check again
741 Some(Point2::new(unchecked.x as usize, unchecked.y as usize))
742 } else {
743 None
744 }
745 }
746
747 /// Gets the value of the point as an index into an array bounded by this `Bounds`.
748 ///
749 /// # Safety
750 ///
751 /// This function will not panic if `point` is outside the map, but use of the result to
752 /// index into the map is not guaranteed to be safe. It is possible for this function to return
753 /// a negative index value, which would indicate that the cell is outside the map.
754 pub unsafe fn get_index_unchecked(&self, point: Point2<isize>) -> Point2<isize> {
755 Point2::new(point.x - self.x.0, point.y - self.y.0)
756 }
757
758 /// Gets the shape of this rectangle in a format that `ndarray` will accept.
759 ///
760 /// NOTE: shape order is (y, x), not (x, y).
761 pub fn get_shape(&self) -> (usize, usize) {
762 (
763 (self.y.1 - self.y.0) as usize,
764 (self.x.1 - self.x.0) as usize,
765 )
766 }
767
768 /// Gets the number of cells as a vector.
769 pub fn get_num_cells(&self) -> Vector2<usize> {
770 let shape = self.get_shape();
771 Vector2::new(shape.1, shape.0)
772 }
773
774 /// Gets the intersection of self with other, returning `None` if the two do not intersect.
775 pub fn intersect(&self, other: &Bounds) -> Option<Bounds> {
776 Bounds::new(
777 (self.x.0.max(other.x.0), self.x.1.min(other.x.1)),
778 (self.y.0.max(other.y.0), self.y.1.min(other.y.1)),
779 )
780 .ok()
781 }
782
783 /// Get the union of `self` with `other`, effectively this is the axis aligned bounding box of
784 /// `self` and `other`.
785 ///
786 /// If both bounds are empty this bound will be empty.
787 pub fn union(&self, other: &Bounds) -> Bounds {
788 Bounds::new(
789 (self.x.0.min(other.x.0), self.x.1.max(other.x.1)),
790 (self.y.0.min(other.y.0), self.y.1.max(other.y.1)),
791 )
792 .unwrap_or_default()
793 }
794
795 /// Gets the slice of other within self, cropping other so it fits within self.
796 ///
797 /// Note that slices are a pair of (min, max) half-open bounds that describe the slice into an
798 /// array, i.e. they are indices.
799 pub fn get_slice_of_other(&self, other: &Bounds) -> Option<Vector2<(usize, usize)>> {
800 // First get intersection of the two bounds in the origin frame
801 let intersect = self.intersect(other)?;
802
803 // Rebase the intersection to be a slice relative to the start of self, i.e. subtract the
804 // min bound on each axis from both min and max of the intersection
805 Some(Vector2::new(
806 (
807 (intersect.x.0 - self.x.0) as usize,
808 (intersect.x.1 - self.x.0) as usize,
809 ),
810 (
811 (intersect.y.0 - self.y.0) as usize,
812 (intersect.y.1 - self.y.0) as usize,
813 ),
814 ))
815 }
816}
817
818impl Default for Bounds {
819 fn default() -> Self {
820 Self::empty()
821 }
822}