use float_cmp::approx_eq;
use getset::CopyGetters;
use ndarray::{Array2, ShapeBuilder};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::num::Wrapping;
use std::path::Path;
use std::{fmt::Display, io::Write};
use crate::errors::GridMapError;
use crate::structures::simbox::SimBox;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DataOrder {
#[default]
RowMajor,
ColumnMajor,
}
#[derive(Debug, Clone, CopyGetters)]
pub struct GridMap<
RawValue: Default + Clone + std::fmt::Debug,
VisValue: Display,
Converter: Fn(&RawValue) -> VisValue,
> {
#[getset(get_copy = "pub")]
span_x: (f32, f32),
#[getset(get_copy = "pub")]
span_y: (f32, f32),
#[getset(get_copy = "pub")]
tile_dim: (f32, f32),
values: Array2<RawValue>,
converter: Converter,
}
impl<
RawValue: Default + Clone + std::fmt::Debug,
VisValue: Display,
Converter: Fn(&RawValue) -> VisValue,
> GridMap<RawValue, VisValue, Converter>
{
pub fn new(
span_x: (f32, f32),
span_y: (f32, f32),
tile_dim: (f32, f32),
converter: Converter,
) -> Result<GridMap<RawValue, VisValue, Converter>, GridMapError> {
let values = Array2::<RawValue>::default((
Self::get_len(span_x, tile_dim.0)?,
Self::get_len(span_y, tile_dim.1)?,
));
Ok(GridMap {
span_x,
span_y,
tile_dim,
values,
converter,
})
}
fn get_len(span: (f32, f32), tile: f32) -> Result<usize, GridMapError> {
let diff = span.1 - span.0;
if diff < 0.0 {
return Err(GridMapError::InvalidSpan);
}
if tile > diff || tile == 0.0 {
return Err(GridMapError::InvalidGridTile);
}
Ok((diff / tile).round() as usize + 1)
}
pub fn from_box(
simbox: &SimBox,
tile_dim: (f32, f32),
converter: Converter,
) -> Result<GridMap<RawValue, VisValue, Converter>, GridMapError> {
let span_x = (0.0, simbox.x);
let span_y = (0.0, simbox.y);
GridMap::new(span_x, span_y, tile_dim, converter)
}
pub fn from_file(
filename: impl AsRef<Path>,
converter: Converter,
split: &[char],
parser: impl Fn(&str) -> Option<RawValue>,
comments: &[&str],
) -> Result<GridMap<RawValue, VisValue, Converter>, GridMapError> {
let file = File::open(&filename)
.map_err(|_| GridMapError::FileNotFound(Box::from(filename.as_ref())))?;
let reader = BufReader::new(file);
let mut x_coords = Vec::new();
let mut y_coords = Vec::new();
let mut values = Vec::new();
let mut data_order = DataOrder::RowMajor;
for line in reader.lines() {
let line =
line.map_err(|_| GridMapError::CouldNotReadLine(Box::from(filename.as_ref())))?;
if line.trim().is_empty() {
continue;
}
if comments.iter().any(|char| line.starts_with(char)) {
continue;
}
let split: Vec<&str> = line
.split(split)
.filter(|substring| !substring.trim().is_empty())
.collect();
let x = Self::parse_coordinate(&split, 0, &line, &filename)?;
let y = Self::parse_coordinate(&split, 1, &line, &filename)?;
let z = match split.get(2) {
Some(x) => parser(x).ok_or(GridMapError::CouldNotParseLine(
line.to_owned(),
Box::from(filename.as_ref()),
))?,
None => parser(" ").ok_or(GridMapError::CouldNotParseLine(
line.to_owned(),
Box::from(filename.as_ref()),
))?,
};
values.push(z);
x_coords.push(x);
y_coords.push(y);
if y_coords.len() == 2 && y_coords[0] == y_coords[1] {
data_order = DataOrder::ColumnMajor;
}
}
if x_coords.len() < 2 || y_coords.len() < 2 {
return Err(GridMapError::EmptyGridMap);
}
let (span_x, span_y, (tile_x, tile_y)) =
Self::validate_and_get_span(&x_coords, &y_coords, data_order)?;
GridMap::from_vec(
span_x,
span_y,
(tile_x, tile_y),
values,
data_order,
converter,
)
}
#[allow(clippy::type_complexity)]
fn validate_and_get_span(
x_coords: &[f32],
y_coords: &[f32],
data_order: DataOrder,
) -> Result<((f32, f32), (f32, f32), (f32, f32)), GridMapError> {
let (fast_changing_coords, slow_changing_coords) = match data_order {
DataOrder::ColumnMajor => (x_coords, y_coords),
DataOrder::RowMajor => (y_coords, x_coords),
};
let (min_s, second_s, max_s, block_size) =
Self::validate_slow_changing_coords(slow_changing_coords)?;
let (min_f, second_f, max_f) =
Self::validate_fast_changing_coords(fast_changing_coords, block_size)?;
Self::check_order_increasing(
fast_changing_coords,
slow_changing_coords,
data_order,
block_size,
)?;
let tile_f = second_f - min_f;
let tile_s = second_s - min_s;
match data_order {
DataOrder::ColumnMajor => Ok(((min_f, max_f), (min_s, max_s), (tile_f, tile_s))),
DataOrder::RowMajor => Ok(((min_s, max_s), (min_f, max_f), (tile_s, tile_f))),
}
}
fn validate_slow_changing_coords(
slow_changing_coords: &[f32],
) -> Result<(f32, f32, f32, usize), GridMapError> {
let mut previous_slow = *slow_changing_coords.first().expect(
"FATAL GROAN ERROR | GridMap::validate_slow_changing_coords | First coordinate value should exist.",
);
let mut counter = 0;
let mut block_size = None;
let (mut min_s, mut second_s, mut max_s) = (f32::MAX, f32::MAX, f32::MIN);
Self::update_min_max(previous_slow, &mut min_s, &mut second_s, &mut max_s);
for &s in slow_changing_coords.iter().skip(1) {
counter += 1;
if !approx_eq!(f32, s, previous_slow) {
if previous_slow > s {
return Err(GridMapError::NotIncreasing(
s.to_string(),
previous_slow.to_string(),
));
}
match block_size {
Some(block) => match counter.cmp(&block) {
std::cmp::Ordering::Less => {
return Err(GridMapError::InvalidCoordinates(s.to_string()));
}
std::cmp::Ordering::Greater => {
panic!("FATAL GROAN ERROR | GridMap::validate_slow_changing_coords | Invalid slow-changing coordinate detected but this should have been handled elsewhere.");
}
std::cmp::Ordering::Equal => {
previous_slow = s;
Self::update_min_max(
previous_slow,
&mut min_s,
&mut second_s,
&mut max_s,
);
}
},
None => {
block_size = Some(counter);
previous_slow = s;
Self::update_min_max(previous_slow, &mut min_s, &mut second_s, &mut max_s);
}
}
counter = 0;
continue;
}
if block_size.is_some() && counter >= block_size.unwrap() {
return Err(GridMapError::InvalidCoordinates(s.to_string()));
}
}
let unwrapped_block_size = block_size
.ok_or_else(|| GridMapError::InvalidCoordinates(previous_slow.to_string()))?;
Ok((min_s, second_s, max_s, unwrapped_block_size))
}
fn validate_fast_changing_coords(
fast_changing_coords: &[f32],
block_size: usize,
) -> Result<(f32, f32, f32), GridMapError> {
let (mut min_f, mut second_f, mut max_f) = (f32::MAX, f32::MAX, f32::MIN);
for i in 0..block_size {
let coord_i = *fast_changing_coords.get(i).expect(
"FATAL GROAN ERROR | GridMap::validate_fast_changing_coords | Access to fast-changing coordinates is out of range. (1)",
);
Self::update_min_max(coord_i, &mut min_f, &mut second_f, &mut max_f);
let mut j = i + block_size;
while j < fast_changing_coords.len() {
let coord_j = fast_changing_coords.get(j).expect(
"FATAL GROAN ERROR | GridMap::validate_fast_changing_coords | Access to fast-changing coordinates is out of range. (2)",
);
if !approx_eq!(f32, coord_i, *coord_j) {
return Err(GridMapError::InvalidCoordinates(coord_j.to_string()));
}
j += block_size;
}
}
Ok((min_f, second_f, max_f))
}
fn check_order_increasing(
fast_changing_coords: &[f32],
slow_changing_coords: &[f32],
data_order: DataOrder,
block_size: usize,
) -> Result<(), GridMapError> {
for i in 0..(block_size - 1) {
let coord1 = fast_changing_coords.get(i).unwrap();
let coord2 = fast_changing_coords.get(i + 1).unwrap();
if coord1 > coord2 {
return Err(GridMapError::NotIncreasing(
coord2.to_string(),
coord1.to_string(),
));
}
if approx_eq!(f32, *coord1, *coord2) {
let slow_changing = slow_changing_coords.get(i).expect(
"FATAL GROAN ERROR | GridMap::check_order_increasing | Could not find slow-changing coordinate which should exist.",
);
let (x, y) = match data_order {
DataOrder::ColumnMajor => (coord1, slow_changing),
DataOrder::RowMajor => (slow_changing, coord1),
};
return Err(GridMapError::PointDefinedMultipleTimes(
x.to_string(),
y.to_string(),
));
}
}
Ok(())
}
fn update_min_max(current: f32, min: &mut f32, second_min: &mut f32, max: &mut f32) {
if current < *min {
*second_min = *min;
*min = current;
} else if current < *second_min {
*second_min = current;
}
if current > *max {
*max = current;
}
}
pub fn from_vec(
span_x: (f32, f32),
span_y: (f32, f32),
tile_dim: (f32, f32),
values: Vec<RawValue>,
data_order: DataOrder,
converter: Converter,
) -> Result<GridMap<RawValue, VisValue, Converter>, GridMapError> {
let len_x = Self::get_len(span_x, tile_dim.0)?;
let len_y = Self::get_len(span_y, tile_dim.1)?;
let n_values = values.len();
if len_x * len_y != n_values {
return Err(GridMapError::InvalidMapDimensions(len_x * len_y, n_values));
}
let values = match data_order {
DataOrder::RowMajor => Array2::<RawValue>::from_shape_vec((len_x, len_y), values),
DataOrder::ColumnMajor => {
Array2::<RawValue>::from_shape_vec((len_x, len_y).f(), values)
}
}
.expect("FATAL GROAN ERROR | GridMap::from_vec | Could not construct 2D array of values.");
Ok(GridMap {
span_x,
span_y,
tile_dim,
values,
converter,
})
}
pub fn clear(&mut self) {
self.values
.iter_mut()
.for_each(|x| *x = RawValue::default())
}
#[inline(always)]
pub fn n_tiles(&self) -> usize {
self.values.len()
}
#[inline(always)]
pub fn n_tiles_x(&self) -> usize {
self.values.nrows()
}
#[inline(always)]
pub fn n_tiles_y(&self) -> usize {
self.values.ncols()
}
#[inline(always)]
pub fn get_tile(&self, x: f32, y: f32) -> Option<(f32, f32)> {
if !self.is_inside(x, y) {
return None;
}
Some((self.index2x(self.x2index(x)), self.index2y(self.y2index(y))))
}
#[inline(always)]
pub fn is_inside(&self, x: f32, y: f32) -> bool {
self.get_at(x, y).is_some()
}
#[inline(always)]
pub fn get_at(&self, x: f32, y: f32) -> Option<&RawValue> {
self.values.get((self.x2index(x), self.y2index(y)))
}
#[inline(always)]
pub fn get_mut_at(&mut self, x: f32, y: f32) -> Option<&mut RawValue> {
let x2i = self.x2index(x);
let y2i = self.y2index(y);
self.values.get_mut((x2i, y2i))
}
#[inline(always)]
pub fn get_at_convert(&self, x: f32, y: f32) -> Option<VisValue> {
Some((self.converter)(self.get_at(x, y)?))
}
pub fn write_map_raw(&self, writer: &mut impl Write) -> Result<(), GridMapError> {
self.extract_raw().try_for_each(|x| {
writeln!(writer, "{:10.6} {:10.6} {:?}", x.0, x.1, x.2)
.map_err(|_| GridMapError::CouldNotWrite)
})?;
Ok(())
}
pub fn write_map_raw_column_major(&self, writer: &mut impl Write) -> Result<(), GridMapError> {
self.extract_raw_column_major().try_for_each(|x| {
writeln!(writer, "{:10.6} {:10.6} {:?}", x.0, x.1, x.2)
.map_err(|_| GridMapError::CouldNotWrite)
})?;
Ok(())
}
pub fn write_map(&self, writer: &mut impl Write) -> Result<(), GridMapError> {
self.extract_convert().try_for_each(|x| {
writeln!(writer, "{:10.6} {:10.6} {}", x.0, x.1, x.2)
.map_err(|_| GridMapError::CouldNotWrite)
})?;
Ok(())
}
pub fn write_map_column_major(&self, writer: &mut impl Write) -> Result<(), GridMapError> {
self.extract_convert_column_major().try_for_each(|x| {
writeln!(writer, "{:10.6} {:10.6} {}", x.0, x.1, x.2)
.map_err(|_| GridMapError::CouldNotWrite)
})?;
Ok(())
}
pub fn extract_raw(&self) -> impl Iterator<Item = (f32, f32, &RawValue)> + '_ {
(0..self.n_tiles_x())
.flat_map(move |x| {
let x_coord = self.index2x(x);
(0..self.n_tiles_y()).map(move |y| {
let y_coord = self.index2y(y);
(x_coord, y_coord, self.get_at(x_coord, y_coord)
.expect("FATAL GROAN ERROR | GridMap::extract_raw | Value at target coordinates must exist."))
})
}
)
}
pub fn extract_raw_column_major(&self) -> impl Iterator<Item = (f32, f32, &RawValue)> + '_ {
(0..self.n_tiles_y())
.flat_map(move |y| {
let y_coord = self.index2y(y);
(0..self.n_tiles_x()).map(move |x| {
let x_coord = self.index2y(x);
(x_coord, y_coord, self.get_at(x_coord, y_coord)
.expect("FATAL GROAN ERROR | GridMap::extract_raw_column_major | Value at target coordinates must exist."))
})
}
)
}
pub fn extract_convert(&self) -> impl Iterator<Item = (f32, f32, VisValue)> + '_ {
self.extract_raw()
.map(|x| (x.0, x.1, (self.converter)(x.2)))
}
pub fn extract_convert_column_major(&self) -> impl Iterator<Item = (f32, f32, VisValue)> + '_ {
self.extract_raw_column_major()
.map(|x| (x.0, x.1, (self.converter)(x.2)))
}
#[inline(always)]
fn x2index(&self, x: f32) -> usize {
Wrapping(((x - self.span_x.0) / self.tile_dim.0).round() as isize).0 as usize
}
#[inline(always)]
fn y2index(&self, y: f32) -> usize {
Wrapping(((y - self.span_y.0) / self.tile_dim.1).round() as isize).0 as usize
}
#[inline(always)]
fn index2x(&self, index: usize) -> f32 {
(index as f32 * self.tile_dim.0) + self.span_x.0
}
#[inline(always)]
fn index2y(&self, index: usize) -> f32 {
(index as f32 * self.tile_dim.1) + self.span_y.0
}
fn parse_coordinate(
split: &[&str],
index: usize,
line: &str,
filename: impl AsRef<Path>,
) -> Result<f32, GridMapError> {
split
.get(index)
.ok_or_else(|| {
GridMapError::CouldNotParseLine(line.to_owned(), Box::from(filename.as_ref()))
})
.and_then(|s| {
s.trim().parse::<f32>().map_err(|_| {
GridMapError::CouldNotParseLine(line.to_owned(), Box::from(filename.as_ref()))
})
})
}
}
#[cfg(test)]
mod tests {
use std::io::BufWriter;
use float_cmp::assert_approx_eq;
use super::*;
#[allow(clippy::ptr_arg)]
fn sum(vec: &Vec<f32>) -> f32 {
vec.iter().sum()
}
#[test]
fn new() {
let gridmap = GridMap::new((-2.0, 7.0), (3.0, 6.0), (0.15, 0.20), sum).unwrap();
assert_eq!(gridmap.span_x, (-2.0, 7.0));
assert_eq!(gridmap.span_y, (3.0, 6.0));
assert_eq!(gridmap.tile_dim, (0.15, 0.20));
assert_eq!(gridmap.n_tiles(), 61 * 16);
assert_eq!(gridmap.n_tiles_x(), 61);
assert_eq!(gridmap.n_tiles_y(), 16);
assert_eq!(gridmap.values[(5, 8)], Vec::<f32>::new());
}
#[test]
fn new_failures() {
assert!(matches!(
GridMap::<Vec<f32>, f32, fn(&Vec<f32>) -> f32>::new(
(2.0, -2.0),
(3.0, 6.0),
(0.15, 0.20),
sum
),
Err(GridMapError::InvalidSpan)
));
assert!(matches!(
GridMap::<Vec<f32>, f32, fn(&Vec<f32>) -> f32>::new(
(-2.0, 7.0),
(3.0, -6.0),
(0.15, 0.20),
sum
),
Err(GridMapError::InvalidSpan)
));
assert!(matches!(
GridMap::<Vec<f32>, f32, fn(&Vec<f32>) -> f32>::new(
(-2.0, 7.0),
(3.0, 6.0),
(0.0, 0.20),
sum
),
Err(GridMapError::InvalidGridTile)
));
assert!(matches!(
GridMap::<Vec<f32>, f32, fn(&Vec<f32>) -> f32>::new(
(-2.0, 7.0),
(3.0, 6.0),
(0.15, 0.0),
sum
),
Err(GridMapError::InvalidGridTile)
));
assert!(matches!(
GridMap::<Vec<f32>, f32, fn(&Vec<f32>) -> f32>::new(
(-2.0, 7.0),
(3.0, 6.0),
(10.5, 0.20),
sum
),
Err(GridMapError::InvalidGridTile)
));
assert!(matches!(
GridMap::<Vec<f32>, f32, fn(&Vec<f32>) -> f32>::new(
(-2.0, 7.0),
(3.0, 6.0),
(0.15, 3.10),
sum
),
Err(GridMapError::InvalidGridTile)
));
}
#[test]
fn from_box() {
let simbox = SimBox::from([10.0, 20.0, 14.0]);
let gridmap = GridMap::from_box(&simbox, (0.15, 0.20), sum).unwrap();
assert_eq!(gridmap.span_x, (0.0, 10.0));
assert_eq!(gridmap.span_y, (0.0, 20.0));
assert_eq!(gridmap.tile_dim, (0.15, 0.20));
assert_eq!(gridmap.n_tiles(), 68 * 101);
assert_eq!(gridmap.n_tiles_x(), 68);
assert_eq!(gridmap.n_tiles_y(), 101);
assert_eq!(gridmap.values[(5, 8)], Vec::<f32>::new());
}
#[test]
fn from_vec_column_major() {
let values = vec![1, 2, 3, 4, 5, 6, 7, 8];
let gridmap = GridMap::from_vec(
(1.0, 2.0),
(1.0, 2.5),
(1.0, 0.5),
values,
DataOrder::ColumnMajor,
usize::to_owned,
)
.unwrap();
assert_eq!(gridmap.n_tiles_x(), 2);
assert_eq!(gridmap.n_tiles_y(), 4);
assert_eq!(gridmap.n_tiles(), 8);
assert_eq!(*gridmap.get_at(1.0, 1.0).unwrap(), 1);
assert_eq!(*gridmap.get_at(2.0, 1.0).unwrap(), 2);
assert_eq!(*gridmap.get_at(1.0, 1.5).unwrap(), 3);
assert_eq!(*gridmap.get_at(2.0, 1.5).unwrap(), 4);
assert_eq!(*gridmap.get_at(1.0, 2.0).unwrap(), 5);
assert_eq!(*gridmap.get_at(2.0, 2.0).unwrap(), 6);
assert_eq!(*gridmap.get_at(1.0, 2.5).unwrap(), 7);
assert_eq!(*gridmap.get_at(2.0, 2.5).unwrap(), 8);
}
#[test]
fn from_vec_row_major() {
let values = vec![1, 2, 3, 4, 5, 6, 7, 8];
let gridmap = GridMap::from_vec(
(1.0, 2.0),
(1.0, 2.5),
(1.0, 0.5),
values,
DataOrder::RowMajor,
usize::to_owned,
)
.unwrap();
assert_eq!(gridmap.n_tiles_x(), 2);
assert_eq!(gridmap.n_tiles_y(), 4);
assert_eq!(gridmap.n_tiles(), 8);
assert_eq!(*gridmap.get_at(1.0, 1.0).unwrap(), 1);
assert_eq!(*gridmap.get_at(2.0, 1.0).unwrap(), 5);
assert_eq!(*gridmap.get_at(1.0, 1.5).unwrap(), 2);
assert_eq!(*gridmap.get_at(2.0, 1.5).unwrap(), 6);
assert_eq!(*gridmap.get_at(1.0, 2.0).unwrap(), 3);
assert_eq!(*gridmap.get_at(2.0, 2.0).unwrap(), 7);
assert_eq!(*gridmap.get_at(1.0, 2.5).unwrap(), 4);
assert_eq!(*gridmap.get_at(2.0, 2.5).unwrap(), 8);
}
#[allow(clippy::ptr_arg)]
fn sum_usize(vec: &Vec<usize>) -> usize {
vec.iter().sum()
}
fn parse_vec(string: &str) -> Option<Vec<usize>> {
let split: Vec<&str> = string.split_whitespace().collect();
Some(
split
.into_iter()
.map(|x| x.parse::<usize>().unwrap())
.collect::<Vec<usize>>(),
)
}
#[test]
fn from_file_column_major() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_column_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
assert_eq!(*gridmap.get_at(0.0, 0.0).unwrap(), vec![10]);
assert_eq!(*gridmap.get_at(1.0, 0.0).unwrap(), vec![5, 4]);
assert_eq!(*gridmap.get_at(2.0, 0.0).unwrap(), vec![43, 23, 21]);
assert_eq!(*gridmap.get_at(0.0, 1.0).unwrap(), vec![4, 8, 12]);
assert_eq!(*gridmap.get_at(1.0, 1.0).unwrap(), vec![2, 5]);
assert_eq!(*gridmap.get_at(2.0, 1.0).unwrap(), Vec::<usize>::new());
}
#[test]
fn from_file_column_major_not_increasing() {
match GridMap::from_file(
"test_files/gridmaps/map_column_major_decreasing.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::NotIncreasing(x, y)) => {
assert_eq!(x, String::from("1"));
assert_eq!(y, String::from("2"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_column_major_not_fully_increasing() {
match GridMap::from_file(
"test_files/gridmaps/map_column_major_not_fully_increasing.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::NotIncreasing(x, y)) => {
assert_eq!(x, String::from("0"));
assert_eq!(y, String::from("1"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_column_major_x_inconsistency() {
match GridMap::from_file(
"test_files/gridmaps/map_column_major_x_inconsistency.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::InvalidCoordinates(x)) => {
assert_eq!(x, String::from("2"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_column_major_y_inconsistency() {
match GridMap::from_file(
"test_files/gridmaps/map_column_major_y_inconsistency.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::InvalidCoordinates(x)) => {
assert_eq!(x, String::from("3.5"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_column_major_redefinition() {
match GridMap::from_file(
"test_files/gridmaps/map_column_major_redefinition.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::PointDefinedMultipleTimes(x, y)) => {
assert_eq!(x, String::from("0"));
assert_eq!(y, String::from("0"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_row_major() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_row_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
assert_eq!(*gridmap.get_at(0.0, 0.0).unwrap(), vec![10]);
assert_eq!(*gridmap.get_at(1.0, 0.0).unwrap(), vec![5, 4]);
assert_eq!(*gridmap.get_at(2.0, 0.0).unwrap(), vec![43, 23, 21]);
assert_eq!(*gridmap.get_at(0.0, 1.0).unwrap(), vec![4, 8, 12]);
assert_eq!(*gridmap.get_at(1.0, 1.0).unwrap(), vec![2, 5]);
assert_eq!(*gridmap.get_at(2.0, 1.0).unwrap(), Vec::<usize>::new());
}
#[test]
fn from_file_row_major_not_increasing() {
match GridMap::from_file(
"test_files/gridmaps/map_row_major_decreasing.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::NotIncreasing(x, y)) => {
assert_eq!(x, String::from("1"));
assert_eq!(y, String::from("2"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_row_major_x_inconsistency() {
match GridMap::from_file(
"test_files/gridmaps/map_row_major_x_inconsistency.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::InvalidCoordinates(x)) => {
assert_eq!(x, String::from("1"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_row_major_y_inconsistency() {
match GridMap::from_file(
"test_files/gridmaps/map_row_major_y_inconsistency.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::InvalidCoordinates(x)) => {
assert_eq!(x, String::from("0"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn from_file_row_major_redefinition() {
match GridMap::from_file(
"test_files/gridmaps/map_row_major_redefinition.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::PointDefinedMultipleTimes(x, y)) => {
assert_eq!(x, String::from("0"));
assert_eq!(y, String::from("1"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn all_coordinates_same() {
match GridMap::from_file(
"test_files/gridmaps/all_coordinates_same.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
) {
Ok(_) => panic!("Should have failed, but succeeded."),
Err(GridMapError::InvalidCoordinates(x)) => {
assert_eq!(x, String::from("0"));
}
Err(e) => panic!("Invalid error type returned: '{}'", e),
}
}
#[test]
fn clear() {
let values = vec![1, 2, 3, 4, 5, 6, 7, 8];
let mut gridmap = GridMap::from_vec(
(1.0, 2.0),
(1.0, 2.5),
(1.0, 0.5),
values,
DataOrder::ColumnMajor,
usize::to_owned,
)
.unwrap();
gridmap.clear();
assert!(gridmap.values.iter().all(|&x| x == 0));
let mut gridmap = GridMap::from_file(
"test_files/gridmaps/map_column_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
gridmap.clear();
assert!(gridmap.values.iter().all(|x| x.is_empty()));
}
#[test]
fn coord2index() {
let gridmap = GridMap::new((-2.0, 7.0), (3.0, 6.0), (0.15, 0.20), sum).unwrap();
assert_eq!(gridmap.x2index(-2.0), 0);
assert_approx_eq!(f32, gridmap.index2x(0), -2.0);
assert_eq!(gridmap.x2index(-2.07), 0);
assert_eq!(gridmap.x2index(-2.08), usize::MAX);
assert_eq!(gridmap.x2index(-2.32), usize::MAX - 1);
assert_eq!(gridmap.x2index(7.0), 60);
assert_approx_eq!(f32, gridmap.index2x(60), 7.0);
assert_eq!(gridmap.x2index(7.07), 60);
assert_eq!(gridmap.x2index(7.08), 61);
assert_eq!(gridmap.x2index(7.32), 62);
assert_eq!(gridmap.y2index(3.0), 0);
assert_approx_eq!(f32, gridmap.index2y(0), 3.0);
assert_eq!(gridmap.y2index(2.91), 0);
assert_eq!(gridmap.y2index(2.89), usize::MAX);
assert_eq!(gridmap.y2index(2.58), usize::MAX - 1);
assert_eq!(gridmap.y2index(6.0), 15);
assert_approx_eq!(f32, gridmap.index2y(15), 6.0);
assert_eq!(gridmap.y2index(6.09), 15);
assert_eq!(gridmap.y2index(6.11), 16);
assert_eq!(gridmap.y2index(6.42), 17);
}
#[test]
fn get_at() {
let mut gridmap = GridMap::new((-2.0, 7.0), (3.0, 6.0), (0.15, 0.20), sum).unwrap();
let val = gridmap.get_mut_at(0.45, 3.57).unwrap();
val.push(10.4);
val.push(2.8);
let val = gridmap.get_at(0.45, 3.57).unwrap();
assert_eq!(val.len(), 2);
assert_approx_eq!(f32, val[0], 10.4);
assert_approx_eq!(f32, val[1], 2.8);
assert_approx_eq!(f32, gridmap.get_at_convert(0.39, 3.52).unwrap(), 13.2);
assert_eq!(gridmap.get_at(0.56, 3.57).unwrap().len(), 0);
assert_eq!(gridmap.get_at(0.45, 3.42).unwrap().len(), 0);
}
#[test]
fn extract_raw() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_row_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let vec = gridmap
.extract_raw()
.collect::<Vec<(f32, f32, &Vec<usize>)>>();
assert_eq!(vec[0], (0.0, 0.0, &vec![10]));
assert_eq!(vec[1], (0.0, 1.0, &vec![4, 8, 12]));
assert_eq!(vec[2], (1.0, 0.0, &vec![5, 4]));
assert_eq!(vec[3], (1.0, 1.0, &vec![2, 5]));
assert_eq!(vec[4], (2.0, 0.0, &vec![43, 23, 21]));
assert_eq!(vec[5], (2.0, 1.0, &vec![]));
}
#[test]
fn extract_raw_column_major() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_column_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let vec = gridmap
.extract_raw_column_major()
.collect::<Vec<(f32, f32, &Vec<usize>)>>();
assert_eq!(vec[0], (0.0, 0.0, &vec![10]));
assert_eq!(vec[1], (1.0, 0.0, &vec![5, 4]));
assert_eq!(vec[2], (2.0, 0.0, &vec![43, 23, 21]));
assert_eq!(vec[3], (0.0, 1.0, &vec![4, 8, 12]));
assert_eq!(vec[4], (1.0, 1.0, &vec![2, 5]));
assert_eq!(vec[5], (2.0, 1.0, &vec![]));
}
#[test]
fn extract_convert() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_column_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let vec = gridmap
.extract_convert()
.collect::<Vec<(f32, f32, usize)>>();
assert_eq!(vec[0], (0.0, 0.0, 10));
assert_eq!(vec[1], (0.0, 1.0, 24));
assert_eq!(vec[2], (1.0, 0.0, 9));
assert_eq!(vec[3], (1.0, 1.0, 7));
assert_eq!(vec[4], (2.0, 0.0, 87));
assert_eq!(vec[5], (2.0, 1.0, 0));
}
#[test]
fn extract_convert_column_major() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_row_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let vec = gridmap
.extract_convert_column_major()
.collect::<Vec<(f32, f32, usize)>>();
assert_eq!(vec[0], (0.0, 0.0, 10));
assert_eq!(vec[1], (1.0, 0.0, 9));
assert_eq!(vec[2], (2.0, 0.0, 87));
assert_eq!(vec[3], (0.0, 1.0, 24));
assert_eq!(vec[4], (1.0, 1.0, 7));
assert_eq!(vec[5], (2.0, 1.0, 0));
}
#[test]
fn write_raw() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_row_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let mut output = BufWriter::new(Vec::new());
gridmap.write_map_raw(&mut output).unwrap();
let output_string = String::from_utf8(output.into_inner().unwrap()).unwrap();
let expected = " 0.000000 0.000000 [10]\n 0.000000 1.000000 [4, 8, 12]\n 1.000000 0.000000 [5, 4]\n 1.000000 1.000000 [2, 5]\n 2.000000 0.000000 [43, 23, 21]\n 2.000000 1.000000 []\n";
assert_eq!(output_string, expected);
}
#[test]
fn write_raw_column_major() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_column_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let mut output = BufWriter::new(Vec::new());
gridmap.write_map_raw_column_major(&mut output).unwrap();
let output_string = String::from_utf8(output.into_inner().unwrap()).unwrap();
let expected = " 0.000000 0.000000 [10]\n 1.000000 0.000000 [5, 4]\n 2.000000 0.000000 [43, 23, 21]\n 0.000000 1.000000 [4, 8, 12]\n 1.000000 1.000000 [2, 5]\n 2.000000 1.000000 []\n";
assert_eq!(output_string, expected);
}
#[test]
fn write() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_column_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let mut output = BufWriter::new(Vec::new());
gridmap.write_map(&mut output).unwrap();
let output_string = String::from_utf8(output.into_inner().unwrap()).unwrap();
let expected = " 0.000000 0.000000 10\n 0.000000 1.000000 24\n 1.000000 0.000000 9\n 1.000000 1.000000 7\n 2.000000 0.000000 87\n 2.000000 1.000000 0\n";
assert_eq!(output_string, expected);
}
#[test]
fn write_column_major() {
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_row_major.dat",
sum_usize,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let mut output = BufWriter::new(Vec::new());
gridmap.write_map_column_major(&mut output).unwrap();
let output_string = String::from_utf8(output.into_inner().unwrap()).unwrap();
let expected = " 0.000000 0.000000 10\n 1.000000 0.000000 9\n 2.000000 0.000000 87\n 0.000000 1.000000 24\n 1.000000 1.000000 7\n 2.000000 1.000000 0\n";
assert_eq!(output_string, expected);
}
#[test]
fn is_inside() {
let gridmap = GridMap::new((-2.0, 7.0), (3.0, 6.0), (0.15, 0.20), sum).unwrap();
assert!(gridmap.is_inside(-1.8, 4.5));
assert!(gridmap.is_inside(-2.05, 3.09));
assert!(gridmap.is_inside(7.05, 6.09));
assert!(!gridmap.is_inside(7.11, 4.5));
assert!(!gridmap.is_inside(0.11, 2.8));
}
#[test]
fn get_tile() {
let gridmap = GridMap::new((-2.0, 7.0), (3.0, 6.0), (0.15, 0.20), sum).unwrap();
assert_eq!(gridmap.get_tile(-1.8, 4.5).unwrap(), (-1.85, 4.6));
assert_eq!(gridmap.get_tile(5.8, 4.2).unwrap(), (5.8, 4.2));
assert_eq!(gridmap.get_tile(5.7843, 4.12374).unwrap(), (5.8, 4.2));
assert!(gridmap.get_tile(7.11, 4.5).is_none());
assert!(gridmap.get_tile(0.11, 2.8).is_none());
}
#[test]
fn using_closure_as_converter() {
let number = 17;
let sum_and_add_number = move |raw: &Vec<usize>| raw.iter().sum::<usize>() + number;
let gridmap = GridMap::from_file(
"test_files/gridmaps/map_row_major.dat",
sum_and_add_number,
&['|'],
parse_vec,
&["@", "#"],
)
.unwrap();
let vec = gridmap
.extract_convert()
.collect::<Vec<(f32, f32, usize)>>();
assert_eq!(vec[0], (0.0, 0.0, 27));
assert_eq!(vec[1], (0.0, 1.0, 41));
assert_eq!(vec[2], (1.0, 0.0, 26));
assert_eq!(vec[3], (1.0, 1.0, 24));
assert_eq!(vec[4], (2.0, 0.0, 104));
assert_eq!(vec[5], (2.0, 1.0, 17));
}
}