File test_grid_ops.cpp
File List > grid > tests > test_grid_ops.cpp
Go to the documentation of this file
#include <gtest/gtest.h>
#include <cmath>
#include <limits>
#include "grid/grid.hpp"
#include "grid/grid_ops.hpp"
#include "utilities/progress_tracker.hpp"
// Helper class to create test grids
class TestGrid : public GeoGrid<double> {
public:
explicit TestGrid(const std::vector<std::vector<double>>& data)
: GeoGrid<double>(data[0].size(), data.size(), GeoTransform(), GeoProjection()) {
for (size_t i = 0; i < data.size(); i++) {
for (size_t j = 0; j < data[0].size(); j++) {
(*this)[{j, i}] = data[i][j];
}
}
}
};
// Test downsample function with MEAN method
TEST(GridOps, DownsampleMean) {
std::vector<std::vector<double>> data = {{1.0, 2.0, 3.0, 4.0},
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0},
{13.0, 14.0, 15.0, 16.0}};
TestGrid grid(data);
GeoGrid<double> downsampled = downsample(grid, 2, ProgressTracker(), DownsampleMethod::MEAN);
EXPECT_EQ(downsampled.width(), 2);
EXPECT_EQ(downsampled.height(), 2);
// First block: (1+2+5+6)/4 = 3.5
double val00 = downsampled[{0, 0}];
EXPECT_NEAR(val00, 3.5, 1e-10);
// Second block: (3+4+7+8)/4 = 5.5
double val10 = downsampled[{1, 0}];
EXPECT_NEAR(val10, 5.5, 1e-10);
// Third block: (9+10+13+14)/4 = 11.5
double val01 = downsampled[{0, 1}];
EXPECT_NEAR(val01, 11.5, 1e-10);
// Fourth block: (11+12+15+16)/4 = 13.5
double val11 = downsampled[{1, 1}];
EXPECT_NEAR(val11, 13.5, 1e-10);
}
// Test downsample function with MEDIAN method
TEST(GridOps, DownsampleMedian) {
std::vector<std::vector<double>> data = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}};
TestGrid grid(data);
GeoGrid<double> downsampled = downsample(grid, 2, ProgressTracker(), DownsampleMethod::MEDIAN);
EXPECT_EQ(downsampled.width(), 2);
EXPECT_EQ(downsampled.height(), 2);
// Block (0,0): {1,2,4,5} -> sorted: [1,2,4,5], size=4 (even), median is (2+4)/2 = 3.0
double median_val00 = downsampled[{0, 0}];
EXPECT_NEAR(median_val00, 3.0, 1e-10);
// Block (1,0): {3,6} -> sorted: [3,6], size=2 (even), median is (3+6)/2 = 4.5
double median_val10 = downsampled[{1, 0}];
EXPECT_NEAR(median_val10, 4.5, 1e-10);
// Block (0,1): {7,8} -> sorted: [7,8], size=2 (even), median is (7+8)/2 = 7.5
double median_val01 = downsampled[{0, 1}];
EXPECT_NEAR(median_val01, 7.5, 1e-10);
// Block (1,1): {9} -> sorted: [9], size=1 (odd), median is 9.0
double median_val11 = downsampled[{1, 1}];
EXPECT_NEAR(median_val11, 9.0, 1e-10);
}
// Test downsample with odd dimensions
TEST(GridOps, DownsampleOddDimensions) {
std::vector<std::vector<double>> data = {{1.0, 2.0, 3.0, 4.0, 5.0}, {6.0, 7.0, 8.0, 9.0, 10.0}};
TestGrid grid(data);
GeoGrid<double> downsampled = downsample(grid, 2, ProgressTracker(), DownsampleMethod::MEAN);
// Should round up: ceil(5/2) = 3, ceil(2/2) = 1
EXPECT_EQ(downsampled.width(), 3);
EXPECT_EQ(downsampled.height(), 1);
}
// Test remove_outliers function
TEST(GridOps, RemoveOutliers) {
// Create a grid with an outlier
std::vector<std::vector<double>> data = {{10.0, 10.0, 10.0},
{10.0, 100.0, 10.0}, // Outlier in the middle
{10.0, 10.0, 10.0}};
TestGrid grid(data);
remove_outliers(grid, ProgressTracker(), 1.0);
for (size_t i = 0; i < grid.height(); i++) {
for (size_t j = 0; j < grid.width(); j++) {
double val = grid[{j, i}];
EXPECT_DOUBLE_EQ(val, 10.0);
}
}
}
// Test remove_outliers with no outliers
TEST(GridOps, RemoveOutliersNoOutliers) {
std::vector<std::vector<double>> data = {
{10.0, 10.0, 10.0}, {10.0, 10.0, 10.0}, {10.0, 10.0, 10.0}};
TestGrid grid(data);
remove_outliers(grid, ProgressTracker(), 1.0);
// All values should remain the same
for (size_t i = 0; i < grid.height(); i++) {
for (size_t j = 0; j < grid.width(); j++) {
double val = grid[{j, i}];
EXPECT_DOUBLE_EQ(val, 10.0);
}
}
}
// Test interpolate_holes function
TEST(GridOps, InterpolateHoles) {
// Create a grid with a hole (NaN or large value)
std::vector<std::vector<double>> data = {
{10.0, 10.0, 10.0},
{10.0, std::numeric_limits<double>::max(), 10.0}, // Hole in the middle
{10.0, 10.0, 10.0}};
TestGrid grid(data);
interpolate_holes(grid, ProgressTracker());
for (size_t i = 0; i < grid.height(); i++) {
for (size_t j = 0; j < grid.width(); j++) {
double val = grid[{j, i}];
EXPECT_DOUBLE_EQ(val, 10.0);
}
}
}
// Test interpolate_holes with multiple holes
TEST(GridOps, InterpolateHolesMultiple) {
std::vector<std::vector<double>> data = {
{10.0, std::numeric_limits<double>::max(), 20.0},
{std::numeric_limits<double>::max(), 15.0, std::numeric_limits<double>::max()},
{30.0, 25.0, 35.0}};
TestGrid grid(data);
interpolate_holes(grid, ProgressTracker());
// Grid layout (grid[{j, i}]):
// {0,0}=10.0 {1,0}=HOLE {2,0}=20.0
// {0,1}=HOLE {1,1}=15.0 {2,1}=HOLE
// {0,2}=30.0 {1,2}=25.0 {2,2}=35.0
// Hole at {1, 0}: neighbors DOWN=15.0@1, LEFT=10.0@1, RIGHT=20.0@1
// weighted_average = 15.0/1 + 10.0/1 + 20.0/1 = 45.0
// total_weight = 1/1 + 1/1 + 1/1 = 3.0
// result = 45.0 / 3.0 = 15.0
double val_10 = grid[{1, 0}];
EXPECT_NEAR(val_10, 15.0, 1e-9);
// Hole at {0, 1}: neighbors UP=10.0@1, DOWN=30.0@1, RIGHT=15.0@1
// weighted_average = 10.0/1 + 30.0/1 + 15.0/1 = 55.0
// total_weight = 1/1 + 1/1 + 1/1 = 3.0
// result = 55.0 / 3.0 = 18.333...
double val_01 = grid[{0, 1}];
EXPECT_NEAR(val_01, 55.0 / 3.0, 1e-9);
// Hole at {2, 1}: neighbors UP=20.0@1, DOWN=35.0@1, LEFT=15.0@1
// weighted_average = 20.0/1 + 35.0/1 + 15.0/1 = 70.0
// total_weight = 1/1 + 1/1 + 1/1 = 3.0
// result = 70.0 / 3.0 = 23.333...
double val_21 = grid[{2, 1}];
EXPECT_NEAR(val_21, 70.0 / 3.0, 1e-9);
// Non-hole values should remain unchanged
double val_00 = grid[{0, 0}];
double val_20 = grid[{2, 0}];
double val_11 = grid[{1, 1}];
double val_02 = grid[{0, 2}];
double val_12 = grid[{1, 2}];
double val_22 = grid[{2, 2}];
EXPECT_DOUBLE_EQ(val_00, 10.0);
EXPECT_DOUBLE_EQ(val_20, 20.0);
EXPECT_DOUBLE_EQ(val_11, 15.0);
EXPECT_DOUBLE_EQ(val_02, 30.0);
EXPECT_DOUBLE_EQ(val_12, 25.0);
EXPECT_DOUBLE_EQ(val_22, 35.0);
}
// Test interpolate_holes with isolated hole (no neighbors)
TEST(GridOps, InterpolateHolesIsolated) {
std::vector<std::vector<double>> data = {
{std::numeric_limits<double>::max(), std::numeric_limits<double>::max(),
std::numeric_limits<double>::max()},
{std::numeric_limits<double>::max(), std::numeric_limits<double>::max(),
std::numeric_limits<double>::max()},
{std::numeric_limits<double>::max(), std::numeric_limits<double>::max(),
std::numeric_limits<double>::max()}};
TestGrid grid(data);
interpolate_holes(grid, ProgressTracker());
// All should be set to 0 (no neighbors to interpolate from)
for (size_t i = 0; i < grid.height(); i++) {
for (size_t j = 0; j < grid.width(); j++) {
double val = grid[{j, i}];
EXPECT_DOUBLE_EQ(val, 0.0);
}
}
}
// Test has_value function
TEST(GridOps, HasValue) {
EXPECT_TRUE(has_value(10.0));
EXPECT_TRUE(has_value(0.0));
EXPECT_TRUE(has_value(-10.0));
EXPECT_TRUE(has_value(1e5));
EXPECT_TRUE(has_value(1e6));
EXPECT_TRUE(has_value(1e7));
EXPECT_TRUE(has_value(1e100)); // Any finite value should be valid
EXPECT_FALSE(has_value(std::numeric_limits<double>::infinity()));
EXPECT_FALSE(has_value(-std::numeric_limits<double>::infinity()));
EXPECT_FALSE(has_value(std::numeric_limits<double>::quiet_NaN()));
EXPECT_FALSE(has_value(std::numeric_limits<double>::max())); // Sentinel value represents a hole
EXPECT_FALSE(
has_value(-std::numeric_limits<double>::max())); // Negative max also represents a hole
}