File test_contour_ops.cpp
File List > contour > tests > test_contour_ops.cpp
Go to the documentation of this file
#include <gtest/gtest.h>
#include <cmath>
#include <vector>
#include "contour/contour.hpp"
#include "contour/contour_gen.hpp"
#include "polyline/polyline.hpp"
// =============================================================================
// join_contours tests (max_dist: 2.0 avoids auto-closing merged straight
// chains; 1.0 for a single unit segment so endpoints are not snapped shut)
// =============================================================================
TEST(ContourOps, JoinContoursAppend) {
// Two contours at the same height that can be joined end-to-end
// a: (0,0) -> (1,0) b: (1,0) -> (2,0)
// a.back = (1,0), b.front = (1,0) -> should append b to a
std::vector<Coordinate2D<double>> pts_a = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0)};
std::vector<Coordinate2D<double>> pts_b = {Coordinate2D<double>(1.0, 0.0),
Coordinate2D<double>(2.0, 0.0)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts_a)));
contours.emplace_back(Contour(10.0, std::move(pts_b)));
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
EXPECT_EQ(joined.size(), 1);
EXPECT_EQ(joined[0].points().size(), 3);
EXPECT_DOUBLE_EQ(joined[0].points()[0].x(), 0.0);
EXPECT_DOUBLE_EQ(joined[0].points()[1].x(), 1.0);
EXPECT_DOUBLE_EQ(joined[0].points()[2].x(), 2.0);
}
TEST(ContourOps, JoinContoursPrepend) {
// a: (1,0) -> (2,0) b: (0,0) -> (1,0)
// a.front = (1,0), b.back = (1,0) -> should prepend b to a
std::vector<Coordinate2D<double>> pts_a = {Coordinate2D<double>(1.0, 0.0),
Coordinate2D<double>(2.0, 0.0)};
std::vector<Coordinate2D<double>> pts_b = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts_a)));
contours.emplace_back(Contour(10.0, std::move(pts_b)));
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
EXPECT_EQ(joined.size(), 1);
EXPECT_EQ(joined[0].points().size(), 3);
EXPECT_DOUBLE_EQ(joined[0].points().front().x(), 0.0);
EXPECT_DOUBLE_EQ(joined[0].points().back().x(), 2.0);
}
TEST(ContourOps, JoinContoursNoJoin) {
// Two contours that are far apart - should not join
std::vector<Coordinate2D<double>> pts_a = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0)};
std::vector<Coordinate2D<double>> pts_b = {Coordinate2D<double>(100.0, 0.0),
Coordinate2D<double>(101.0, 0.0)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts_a)));
contours.emplace_back(Contour(10.0, std::move(pts_b)));
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
EXPECT_EQ(joined.size(), 2);
}
TEST(ContourOps, JoinContoursEmpty) {
std::vector<Contour> contours;
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
EXPECT_EQ(joined.size(), 0);
}
TEST(ContourOps, JoinContoursSingle) {
std::vector<Coordinate2D<double>> pts = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts)));
std::vector<Contour> joined = join_contours(std::move(contours), 1.0);
EXPECT_EQ(joined.size(), 1);
EXPECT_EQ(joined[0].points().size(), 2);
}
TEST(ContourOps, JoinContoursClosesNearLoop) {
// A single contour whose own front and back are close together (within
// max_dist) should be snapped into a closed loop.
std::vector<Coordinate2D<double>> pts = {
Coordinate2D<double>(0.0, 0.0), Coordinate2D<double>(10.0, 0.0),
Coordinate2D<double>(10.0, 10.0), Coordinate2D<double>(0.0, 10.0),
Coordinate2D<double>(0.5, 0.5)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts)));
EXPECT_FALSE(contours[0].is_loop());
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
ASSERT_EQ(joined.size(), 1);
EXPECT_TRUE(joined[0].is_loop());
EXPECT_DOUBLE_EQ(joined[0].points().front().x(), joined[0].points().back().x());
EXPECT_DOUBLE_EQ(joined[0].points().front().y(), joined[0].points().back().y());
}
TEST(ContourOps, JoinContoursLeavesFarEndpointsOpen) {
// A single contour whose own front and back are farther apart than max_dist
// should be left open.
std::vector<Coordinate2D<double>> pts = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(50.0, 0.0),
Coordinate2D<double>(50.0, 50.0)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts)));
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
ASSERT_EQ(joined.size(), 1);
EXPECT_FALSE(joined[0].is_loop());
EXPECT_EQ(joined[0].points().size(), 3);
}
TEST(ContourOps, JoinContoursPrefersCrossJoinOverSelfClose) {
// Contour a's back is closer to contour b's front (1.0 away) than to its
// own front (5.0 away), so a should join with b instead of self-closing.
std::vector<Coordinate2D<double>> pts_a = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(5.0, 0.0)};
std::vector<Coordinate2D<double>> pts_b = {Coordinate2D<double>(6.0, 0.0),
Coordinate2D<double>(10.0, 0.0)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts_a)));
contours.emplace_back(Contour(10.0, std::move(pts_b)));
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
ASSERT_EQ(joined.size(), 1);
EXPECT_FALSE(joined[0].is_loop());
EXPECT_EQ(joined[0].points().size(), 4);
}
TEST(ContourOps, JoinContoursChain) {
// Three contours forming a chain
std::vector<Coordinate2D<double>> pts_a = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0)};
std::vector<Coordinate2D<double>> pts_b = {Coordinate2D<double>(1.0, 0.0),
Coordinate2D<double>(2.0, 0.0)};
std::vector<Coordinate2D<double>> pts_c = {Coordinate2D<double>(2.0, 0.0),
Coordinate2D<double>(3.0, 0.0)};
std::vector<Contour> contours;
contours.emplace_back(Contour(10.0, std::move(pts_a)));
contours.emplace_back(Contour(10.0, std::move(pts_b)));
contours.emplace_back(Contour(10.0, std::move(pts_c)));
std::vector<Contour> joined = join_contours(std::move(contours), 2.0);
EXPECT_EQ(joined.size(), 1);
EXPECT_DOUBLE_EQ(joined[0].points().front().x(), 0.0);
EXPECT_DOUBLE_EQ(joined[0].points().back().x(), 3.0);
}
// =============================================================================
// trim_contours tests
// =============================================================================
TEST(ContourOps, TrimContoursAllInside) {
std::vector<Coordinate2D<double>> pts = {
Coordinate2D<double>(1.0, 1.0), Coordinate2D<double>(2.0, 1.0),
Coordinate2D<double>(3.0, 1.0), Coordinate2D<double>(4.0, 1.0)};
Contour c(10.0, std::move(pts));
std::vector<Contour> contours = {std::move(c)};
Extent2D bounds{0.0, 5.0, 0.0, 5.0};
std::vector<Contour> trimmed = trim_contours(contours, bounds);
EXPECT_EQ(trimmed.size(), 1);
EXPECT_EQ(trimmed[0].points().size(), 4);
}
TEST(ContourOps, TrimContoursAllOutside) {
std::vector<Coordinate2D<double>> pts = {Coordinate2D<double>(10.0, 10.0),
Coordinate2D<double>(11.0, 10.0),
Coordinate2D<double>(12.0, 10.0)};
Contour c(10.0, std::move(pts));
std::vector<Contour> contours = {std::move(c)};
Extent2D bounds{0.0, 5.0, 0.0, 5.0};
std::vector<Contour> trimmed = trim_contours(contours, bounds);
EXPECT_EQ(trimmed.size(), 0);
}
TEST(ContourOps, TrimContoursSplit) {
// Contour crosses boundary: inside, outside, inside -> should become 2 contours
std::vector<Coordinate2D<double>> pts = {
Coordinate2D<double>(1.0, 1.0), Coordinate2D<double>(2.0, 1.0),
Coordinate2D<double>(6.0, 1.0), // outside
Coordinate2D<double>(3.0, 1.0), Coordinate2D<double>(4.0, 1.0)};
Contour c(10.0, std::move(pts));
std::vector<Contour> contours = {std::move(c)};
Extent2D bounds{0.0, 5.0, 0.0, 5.0};
std::vector<Contour> trimmed = trim_contours(contours, bounds);
EXPECT_EQ(trimmed.size(), 2);
EXPECT_EQ(trimmed[0].points().size(), 2);
EXPECT_EQ(trimmed[1].points().size(), 2);
}
TEST(ContourOps, TrimContoursEmpty) {
std::vector<Contour> contours;
Extent2D bounds{0.0, 5.0, 0.0, 5.0};
std::vector<Contour> trimmed = trim_contours(contours, bounds);
EXPECT_EQ(trimmed.size(), 0);
}
// =============================================================================
// Contour::from_polyline / to_polyline tests
// =============================================================================
TEST(ContourOps, ContourToPolyline) {
std::vector<Coordinate2D<double>> pts = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0),
Coordinate2D<double>(2.0, 0.0)};
Contour contour(15.0, std::move(pts));
// Create a config that maps height 15.0 to "normal" (interval 5.0)
ContourConfigs configs({{"normal", ContourConfig{5.0, 1, RGBColor(0, 0, 0, 0), 0.14}}});
Polyline poly = contour.to_polyline(configs);
EXPECT_EQ(poly.name, "15.000000");
EXPECT_EQ(poly.vertices.size(), 3);
EXPECT_EQ(poly.layer, "101_Contour");
}
TEST(ContourOps, ContourFromPolyline) {
Polyline poly;
poly.name = "25.5";
poly.layer = "test";
poly.vertices = {Coordinate2D<double>(0.0, 0.0), Coordinate2D<double>(1.0, 1.0),
Coordinate2D<double>(2.0, 2.0)};
Contour c = Contour::from_polyline(poly);
EXPECT_DOUBLE_EQ(c.height(), 25.5);
EXPECT_EQ(c.points().size(), 3);
}
TEST(ContourOps, ContourFromPolylineInvalidName) {
Polyline poly;
poly.name = "not_a_number";
poly.layer = "test";
poly.vertices = {Coordinate2D<double>(0.0, 0.0)};
Contour c = Contour::from_polyline(poly);
// Should default to 0 height and empty points
EXPECT_DOUBLE_EQ(c.height(), 0.0);
EXPECT_EQ(c.points().size(), 0);
}
// =============================================================================
// Contour::push_back tests
// =============================================================================
TEST(ContourOps, PushBackCreatesLoop) {
std::vector<Coordinate2D<double>> pts = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0),
Coordinate2D<double>(1.0, 1.0)};
Contour c(10.0, std::move(pts));
EXPECT_FALSE(c.is_loop());
c.push_back(Coordinate2D<double>(0.0, 0.0));
EXPECT_TRUE(c.is_loop());
}
TEST(ContourOps, PushBackNotLoop) {
std::vector<Coordinate2D<double>> pts = {Coordinate2D<double>(0.0, 0.0),
Coordinate2D<double>(1.0, 0.0)};
Contour c(10.0, std::move(pts));
EXPECT_FALSE(c.is_loop());
c.push_back(Coordinate2D<double>(2.0, 0.0));
EXPECT_FALSE(c.is_loop());
}