File test_grid.cpp
File List > grid > tests > test_grid.cpp
Go to the documentation of this file
#include <gtest/gtest.h>
#include <cmath>
#include <limits>
#include <vector>
#include "grid/grid.hpp"
#include "grid/grid_ops.hpp"
// Helper to avoid brace-initialization inside GTest macros (MSVC preprocessor issue)
static Coordinate2D<size_t> coord(size_t x, size_t y) { return Coordinate2D<size_t>(x, y); }
// =============================================================================
// GeoTransform tests
// =============================================================================
TEST(GeoTransform, DefaultConstruction) {
GeoTransform t;
EXPECT_DOUBLE_EQ(t.x(), 0.0);
EXPECT_DOUBLE_EQ(t.y(), 0.0);
EXPECT_DOUBLE_EQ(t.dx(), 1.0);
EXPECT_DOUBLE_EQ(t.dy(), -1.0);
EXPECT_DOUBLE_EQ(t.rot_x(), 0.0);
EXPECT_DOUBLE_EQ(t.rot_y(), 0.0);
}
TEST(GeoTransform, ConstructionWithValues) {
GeoTransform t(100.0, 200.0, 0.5, -0.5);
EXPECT_DOUBLE_EQ(t.x(), 100.0);
EXPECT_DOUBLE_EQ(t.y(), 200.0);
EXPECT_DOUBLE_EQ(t.dx(), 0.5);
EXPECT_DOUBLE_EQ(t.dy(), -0.5);
}
TEST(GeoTransform, ConstructionFromCoordinate) {
Coordinate2D<double> upper_left(100.0, 200.0);
GeoTransform t(upper_left, 2.0);
EXPECT_DOUBLE_EQ(t.x(), 100.0);
EXPECT_DOUBLE_EQ(t.y(), 200.0);
EXPECT_DOUBLE_EQ(t.dx(), 2.0);
EXPECT_DOUBLE_EQ(t.dy(), -2.0);
}
TEST(GeoTransform, PixelToProjection) {
// Default transform: origin at (0,0), dx=1, dy=-1
GeoTransform t;
Coordinate2D<double> pixel(3.0, 4.0);
Coordinate2D<double> proj = t.pixel_to_projection(pixel);
EXPECT_DOUBLE_EQ(proj.x(), 3.0);
EXPECT_DOUBLE_EQ(proj.y(), -4.0);
}
TEST(GeoTransform, PixelToProjectionWithOffset) {
GeoTransform t(100.0, 200.0, 2.0, -2.0);
Coordinate2D<double> pixel(5.0, 3.0);
Coordinate2D<double> proj = t.pixel_to_projection(pixel);
EXPECT_DOUBLE_EQ(proj.x(), 100.0 + 5.0 * 2.0);
EXPECT_DOUBLE_EQ(proj.y(), 200.0 + 3.0 * (-2.0));
}
TEST(GeoTransform, ProjectionToPixel) {
GeoTransform t(100.0, 200.0, 2.0, -2.0);
Coordinate2D<double> proj(110.0, 194.0);
Coordinate2D<double> pixel = t.projection_to_pixel(proj);
EXPECT_NEAR(pixel.x(), 5.0, 1e-10);
EXPECT_NEAR(pixel.y(), 3.0, 1e-10);
}
TEST(GeoTransform, PixelProjectionRoundTrip) {
GeoTransform t(500.0, 1000.0, 0.25, -0.25);
Coordinate2D<double> original_pixel(10.5, 20.3);
Coordinate2D<double> proj = t.pixel_to_projection(original_pixel);
Coordinate2D<double> round_trip = t.projection_to_pixel(proj);
EXPECT_NEAR(round_trip.x(), original_pixel.x(), 1e-10);
EXPECT_NEAR(round_trip.y(), original_pixel.y(), 1e-10);
}
TEST(GeoTransform, WithNewResolution) {
GeoTransform t(100.0, 200.0, 1.0, -1.0);
GeoTransform t2 = t.with_new_resolution(2.0);
EXPECT_DOUBLE_EQ(t2.x(), 100.0);
EXPECT_DOUBLE_EQ(t2.y(), 200.0);
EXPECT_DOUBLE_EQ(t2.dx(), 2.0);
EXPECT_DOUBLE_EQ(t2.dy(), -2.0);
}
// =============================================================================
// Grid basic operations tests
// =============================================================================
TEST(Grid, ConstructionAndAccess) {
Grid<double> g(3, 2);
g[coord(0, 0)] = 1.0;
g[coord(1, 0)] = 2.0;
g[coord(2, 0)] = 3.0;
g[coord(0, 1)] = 4.0;
g[coord(1, 1)] = 5.0;
g[coord(2, 1)] = 6.0;
double v00 = g[coord(0, 0)];
double v10 = g[coord(1, 0)];
double v20 = g[coord(2, 0)];
double v01 = g[coord(0, 1)];
double v11 = g[coord(1, 1)];
double v21 = g[coord(2, 1)];
EXPECT_DOUBLE_EQ(v00, 1.0);
EXPECT_DOUBLE_EQ(v10, 2.0);
EXPECT_DOUBLE_EQ(v20, 3.0);
EXPECT_DOUBLE_EQ(v01, 4.0);
EXPECT_DOUBLE_EQ(v11, 5.0);
EXPECT_DOUBLE_EQ(v21, 6.0);
}
TEST(Grid, Fill) {
Grid<double> g(3, 3);
g.fill(42.0);
for (size_t i = 0; i < 3; i++) {
for (size_t j = 0; j < 3; j++) {
double val = g[coord(j, i)];
EXPECT_DOUBLE_EQ(val, 42.0);
}
}
}
TEST(Grid, MinMax) {
Grid<double> g(3, 2);
g[coord(0, 0)] = 3.0;
g[coord(1, 0)] = 1.0;
g[coord(2, 0)] = 5.0;
g[coord(0, 1)] = 2.0;
g[coord(1, 1)] = 8.0;
g[coord(2, 1)] = 4.0;
EXPECT_DOUBLE_EQ(g.min_value(), 1.0);
EXPECT_DOUBLE_EQ(g.max_value(), 8.0);
}
TEST(Grid, CopyFrom) {
Grid<double> a(2, 2);
a[coord(0, 0)] = 1.0;
a[coord(1, 0)] = 2.0;
a[coord(0, 1)] = 3.0;
a[coord(1, 1)] = 4.0;
Grid<double> b(2, 2);
b.copy_from(a);
double v00 = b[coord(0, 0)];
double v10 = b[coord(1, 0)];
double v01 = b[coord(0, 1)];
double v11 = b[coord(1, 1)];
EXPECT_DOUBLE_EQ(v00, 1.0);
EXPECT_DOUBLE_EQ(v10, 2.0);
EXPECT_DOUBLE_EQ(v01, 3.0);
EXPECT_DOUBLE_EQ(v11, 4.0);
}
TEST(Grid, InBounds) {
GridData gd(5, 3);
EXPECT_TRUE(gd.in_bounds(Coordinate2D<size_t>(0, 0)));
EXPECT_TRUE(gd.in_bounds(Coordinate2D<size_t>(4, 2)));
// size_t wraps so we can't test negative but can test overflow
EXPECT_FALSE(gd.in_bounds(Coordinate2D<size_t>(5, 0)));
EXPECT_FALSE(gd.in_bounds(Coordinate2D<size_t>(0, 3)));
}
TEST(Grid, FillFrom) {
Grid<double> big(5, 5);
big.fill(0.0);
Grid<double> small_grid(2, 2);
small_grid[coord(0, 0)] = 10.0;
small_grid[coord(1, 0)] = 20.0;
small_grid[coord(0, 1)] = 30.0;
small_grid[coord(1, 1)] = 40.0;
big.fill_from(small_grid, Coordinate2D<size_t>(1, 1));
double v00 = big[coord(0, 0)];
double v11 = big[coord(1, 1)];
double v21 = big[coord(2, 1)];
double v12 = big[coord(1, 2)];
double v22 = big[coord(2, 2)];
double v33 = big[coord(3, 3)];
EXPECT_DOUBLE_EQ(v00, 0.0);
EXPECT_DOUBLE_EQ(v11, 10.0);
EXPECT_DOUBLE_EQ(v21, 20.0);
EXPECT_DOUBLE_EQ(v12, 30.0);
EXPECT_DOUBLE_EQ(v22, 40.0);
EXPECT_DOUBLE_EQ(v33, 0.0);
}
// =============================================================================
// GeoGrid tests
// =============================================================================
TEST(GeoGrid, Extent) {
GeoGrid<double> grid(10, 5, GeoTransform(100.0, 200.0, 1.0, -1.0), GeoProjection());
auto ext = grid.extent();
EXPECT_DOUBLE_EQ(ext->minx, 100.0);
EXPECT_DOUBLE_EQ(ext->maxx, 110.0);
EXPECT_DOUBLE_EQ(ext->miny, 195.0);
EXPECT_DOUBLE_EQ(ext->maxy, 200.0);
}
// =============================================================================
// interpolate_value tests
// =============================================================================
TEST(InterpolateValue, CenterOfCell) {
// Create a 3x3 grid with known values
GeoGrid<double> grid(3, 3, GeoTransform(), GeoProjection());
grid[coord(0, 0)] = 1.0;
grid[coord(1, 0)] = 2.0;
grid[coord(2, 0)] = 3.0;
grid[coord(0, 1)] = 4.0;
grid[coord(1, 1)] = 5.0;
grid[coord(2, 1)] = 6.0;
grid[coord(0, 2)] = 7.0;
grid[coord(1, 2)] = 8.0;
grid[coord(2, 2)] = 9.0;
// At exact center of cell (1,1), the projection coord is (1.5, -1.5)
// which should give interpolated value = 5.0 (exact center of the cell)
double val = interpolate_value(grid, Coordinate2D<double>(1.5, -1.5));
EXPECT_NEAR(val, 5.0, 1e-10);
}
TEST(InterpolateValue, MidpointBetweenCells) {
GeoGrid<double> grid(3, 3, GeoTransform(), GeoProjection());
grid[coord(0, 0)] = 0.0;
grid[coord(1, 0)] = 10.0;
grid[coord(2, 0)] = 0.0;
grid[coord(0, 1)] = 0.0;
grid[coord(1, 1)] = 10.0;
grid[coord(2, 1)] = 0.0;
grid[coord(0, 2)] = 0.0;
grid[coord(1, 2)] = 0.0;
grid[coord(2, 2)] = 0.0;
// Between cells (0,0) and (1,0): projection coord x=1.0 is the midpoint
double val = interpolate_value(grid, Coordinate2D<double>(1.0, -0.5));
// This is at the boundary between cells - should be a finite number
EXPECT_TRUE(std::isfinite(val));
}
// =============================================================================
// GridGraph tests
// =============================================================================
TEST(GridGraph, ConstructionAndAccess) {
Grid<double> base(3, 3);
GridGraph<int> graph(base);
// Horizontal edges: (3-1)x3 = 2x3
EXPECT_EQ(graph.horizontal().width(), 2u);
EXPECT_EQ(graph.horizontal().height(), 3u);
// Vertical edges: 3x(3-1) = 3x2
EXPECT_EQ(graph.vertical().width(), 3u);
EXPECT_EQ(graph.vertical().height(), 2u);
// Set and read values via LineCoord2D
LineCoord2D<size_t> right_edge(Coordinate2D<size_t>(0, 0), Direction2D::RIGHT);
graph[right_edge] = 42;
EXPECT_EQ(graph[right_edge], 42);
LineCoord2D<size_t> down_edge(Coordinate2D<size_t>(0, 0), Direction2D::DOWN);
graph[down_edge] = 99;
EXPECT_EQ(graph[down_edge], 99);
}
TEST(GridGraph, InBounds) {
Grid<double> base(3, 3);
GridGraph<int> graph(base);
// Valid horizontal edge
EXPECT_TRUE(graph.in_bounds(LineCoord2D<size_t>(0, 0, Direction2D::RIGHT)));
EXPECT_TRUE(graph.in_bounds(LineCoord2D<size_t>(1, 2, Direction2D::RIGHT)));
// Out of bounds horizontal edge
EXPECT_FALSE(graph.in_bounds(LineCoord2D<size_t>(2, 0, Direction2D::RIGHT)));
// Valid vertical edge
EXPECT_TRUE(graph.in_bounds(LineCoord2D<size_t>(0, 0, Direction2D::DOWN)));
EXPECT_TRUE(graph.in_bounds(LineCoord2D<size_t>(2, 1, Direction2D::DOWN)));
// Out of bounds vertical edge
EXPECT_FALSE(graph.in_bounds(LineCoord2D<size_t>(0, 2, Direction2D::DOWN)));
}
// =============================================================================
// GeoGrid::pad tests
// =============================================================================
TEST(GeoGridPad, PadsWithValue) {
std::vector<std::vector<float>> data = {{1.0f, 2.0f}, {3.0f, 4.0f}};
GeoGrid<float> grid(data);
for (float pad_value : {0.0f, -1.0f}) {
GeoGrid<float> padded = grid.pad(pad_value);
EXPECT_EQ(padded.width(), 4u);
EXPECT_EQ(padded.height(), 4u);
for (size_t i = 0; i < padded.height(); i++) {
for (size_t j = 0; j < padded.width(); j++) {
const bool on_border =
i == 0 || i == padded.height() - 1 || j == 0 || j == padded.width() - 1;
const float actual = padded[coord(j, i)];
if (on_border) {
EXPECT_FLOAT_EQ(actual, pad_value);
} else {
EXPECT_FLOAT_EQ(actual, data[i - 1][j - 1]);
}
}
}
}
}
TEST(GeoGridPad, AdjustsGeoTransform) {
std::vector<std::vector<float>> data = {{0.5f, 0.6f}, {0.7f, 0.8f}};
GeoGrid<float> grid(data, GeoTransform(100.0, 200.0, 10.0, -10.0));
GeoGrid<float> padded = grid.pad(0.0f);
EXPECT_DOUBLE_EQ(padded.transform().x(), 100.0 - 10.0);
EXPECT_DOUBLE_EQ(padded.transform().y(), 200.0 + 10.0);
EXPECT_DOUBLE_EQ(padded.transform().dx(), 10.0);
EXPECT_DOUBLE_EQ(padded.transform().dy(), -10.0);
}
TEST(GeoGridPad, EmptyGrid) {
GeoGrid<float> grid(0, 0, GeoTransform(), GeoProjection());
GeoGrid<float> padded = grid.pad(0.0f);
EXPECT_EQ(padded.width(), 2u);
EXPECT_EQ(padded.height(), 2u);
}