#include "io/gpr/GprSurveyAssembler.hpp" #include #include #include #include #include #include using namespace geopro::io::gpr; namespace { void writeText(const std::string& p, const std::string& s) { std::ofstream f(p); f << s; } void writeI16(const std::string& p, const std::vector& v) { std::ofstream f(p, std::ios::binary); f.write(reinterpret_cast(v.data()), static_cast(v.size() * sizeof(int16_t))); } // samples=2, lastTrace=1 -> traces=2; CHANNELS 仅供头解析,不影响装配。 const char* HDR = "SAMPLES: 2\nLAST TRACE: 1\nCHANNELS: 2\nTIMEWINDOW: 4.0\n" "SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n"; } // namespace TEST(GprSurveyAssembler, AssemblesTwoChannels) { auto d = (std::filesystem::temp_directory_path() / "gpr_asm").string(); std::filesystem::create_directories(d); // 通道 A(横偏 1.0, 值 10,11, 12,13), 通道 B(横偏 0.0, 值 20..23)。 // B 的 Y 更小, 用于验证按 Y 升序重排。 writeText(d + "/A.iprh", HDR); writeI16(d + "/A.iprb", {10, 11, 12, 13}); // [trace*samples+s] writeText(d + "/B.iprh", HDR); writeI16(d + "/B.iprb", {20, 21, 22, 23}); writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); // A->1.0, B->0.0 auto s = assembleGprSurvey({d + "/A.iprb", d + "/B.iprb"}, d + "/x.ord"); EXPECT_EQ(s.samples, 2); EXPECT_EQ(s.ntraces, 2); EXPECT_NEAR(s.x0, 0.0, 1e-9); EXPECT_NEAR(s.z0, 0.0, 1e-9); EXPECT_NEAR(s.dx, 0.05, 1e-9); // depthOfSample(1,h) = 1e8 * (1 * 4/(2-1) * 1e-9) / 2 = 0.2 EXPECT_NEAR(s.dz, 0.2, 1e-6); ASSERT_EQ(s.channelY.size(), 2u); EXPECT_NEAR(s.channelY[0], 0.0, 1e-9); // 升序: B(0.0) 在前 EXPECT_NEAR(s.channelY[1], 1.0, 1e-9); // A(1.0) 在后 // 升序后通道0=B, 通道1=A EXPECT_NEAR(s.at(0, 0, 0), 20.0, 1e-9); // B 的 (t0,s0) EXPECT_NEAR(s.at(0, 1, 1), 23.0, 1e-9); // B 的 (t1,s1) EXPECT_NEAR(s.at(1, 0, 0), 10.0, 1e-9); // A 的 (t0,s0) EXPECT_NEAR(s.at(1, 1, 1), 13.0, 1e-9); // A 的 (t1,s1) std::filesystem::remove_all(d); } TEST(GprSurveyAssembler, ThrowsWhenChannelCountMismatch) { auto d = (std::filesystem::temp_directory_path() / "gpr_asm_mismatch").string(); std::filesystem::create_directories(d); writeText(d + "/A.iprh", HDR); writeI16(d + "/A.iprb", {10, 11, 12, 13}); // .ord 含 2 个有效通道, 但只传 1 个 iprb 路径 -> 抛错。 writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); EXPECT_THROW(assembleGprSurvey({d + "/A.iprb"}, d + "/x.ord"), std::runtime_error); std::filesystem::remove_all(d); } TEST(GprSurveyAssembler, SlabMatchesFullForRange) { auto d = (std::filesystem::temp_directory_path() / "gpr_asm_slab").string(); std::filesystem::create_directories(d); // 两通道各 4 道 (lastTrace=3), samples=2。 const char* HDR4 = "SAMPLES: 2\nLAST TRACE: 3\nCHANNELS: 2\nTIMEWINDOW: 4.0\n" "SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n"; writeText(d + "/A.iprh", HDR4); writeI16(d + "/A.iprb", {10, 11, 12, 13, 14, 15, 16, 17}); // 4 道 writeText(d + "/B.iprh", HDR4); writeI16(d + "/B.iprb", {20, 21, 22, 23, 24, 25, 26, 27}); // 4 道 writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); // A->1.0, B->0.0 auto full = assembleGprSurvey({d + "/A.iprb", d + "/B.iprb"}, d + "/x.ord"); auto slab = assembleGprSurveySlab({d + "/A.iprb", d + "/B.iprb"}, d + "/x.ord", 1, 3); // 道 [1,3) EXPECT_EQ(slab.ntraces, 2); EXPECT_EQ(slab.samples, full.samples); EXPECT_NEAR(slab.dx, full.dx, 1e-9); EXPECT_NEAR(slab.dz, full.dz, 1e-9); EXPECT_NEAR(slab.z0, full.z0, 1e-9); EXPECT_NEAR(slab.x0, full.x0 + 1 * full.dx, 1e-9); // x0 对齐到 t0 ASSERT_EQ(slab.channelY.size(), full.channelY.size()); for (std::size_t c = 0; c < slab.channelY.size(); ++c) { EXPECT_NEAR(slab.channelY[c], full.channelY[c], 1e-9); } // 值对拍: slab(c,t,s) == full(c, t+1, s) for (int c = 0; c < static_cast(slab.channelY.size()); ++c) for (int t = 0; t < slab.ntraces; ++t) for (int s = 0; s < slab.samples; ++s) EXPECT_NEAR(slab.at(c, t, s), full.at(c, t + 1, s), 1e-9); std::filesystem::remove_all(d); } TEST(GprSurveyAssembler, SlabOutOfRangeThrows) { auto d = (std::filesystem::temp_directory_path() / "gpr_asm_slab_oob").string(); std::filesystem::create_directories(d); const char* HDR4 = "SAMPLES: 2\nLAST TRACE: 3\nCHANNELS: 2\nTIMEWINDOW: 4.0\n" "SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n"; writeText(d + "/A.iprh", HDR4); writeI16(d + "/A.iprb", {10, 11, 12, 13, 14, 15, 16, 17}); // 4 道 writeText(d + "/B.iprh", HDR4); writeI16(d + "/B.iprb", {20, 21, 22, 23, 24, 25, 26, 27}); // 4 道 writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); const std::vector paths = {d + "/A.iprb", d + "/B.iprb"}; // t1 超总道数 (总道数=4)。 EXPECT_THROW(assembleGprSurveySlab(paths, d + "/x.ord", 2, 5), std::runtime_error); // t0 > t1。 EXPECT_THROW(assembleGprSurveySlab(paths, d + "/x.ord", 3, 1), std::runtime_error); std::filesystem::remove_all(d); } TEST(GprSurveyAssembler, SlabThrowsWhenChannelCountMismatch) { auto d = (std::filesystem::temp_directory_path() / "gpr_asm_slab_cm").string(); std::filesystem::create_directories(d); const char* HDR4 = "SAMPLES: 2\nLAST TRACE: 3\nCHANNELS: 2\nTIMEWINDOW: 4.0\n" "SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n"; writeText(d + "/A.iprh", HDR4); writeI16(d + "/A.iprb", {10, 11, 12, 13, 14, 15, 16, 17}); writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); // 2 有效通道 // 只传 1 个 iprb 路径 -> 抛错。 EXPECT_THROW(assembleGprSurveySlab({d + "/A.iprb"}, d + "/x.ord", 1, 3), std::runtime_error); std::filesystem::remove_all(d); } TEST(GprSurveyAssembler, AlignsTracesToMinimum) { auto d = (std::filesystem::temp_directory_path() / "gpr_asm_align").string(); std::filesystem::create_directories(d); // 通道 A: traces=2 (lastTrace=1); 通道 B: traces=3 (lastTrace=2)。 // ntraces 应对齐为 min=2。 const char* HDR3 = "SAMPLES: 2\nLAST TRACE: 2\nCHANNELS: 2\nTIMEWINDOW: 4.0\n" "SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n"; writeText(d + "/A.iprh", HDR); writeI16(d + "/A.iprb", {10, 11, 12, 13}); // 2 道 writeText(d + "/B.iprh", HDR3); writeI16(d + "/B.iprb", {20, 21, 22, 23, 24, 25}); // 3 道 writeText(d + "/x.ord", "0 0.0 -1.5 1\n1 1.0 -1.5 1\n"); // A->0.0, B->1.0 auto s = assembleGprSurvey({d + "/A.iprb", d + "/B.iprb"}, d + "/x.ord"); EXPECT_EQ(s.ntraces, 2); EXPECT_EQ(s.samples, 2); // A(Y=0.0)=通道0, B(Y=1.0)=通道1; B 的第3道(t=2)被对齐丢弃。 EXPECT_NEAR(s.at(1, 1, 0), 22.0, 1e-9); // B 的 (t1,s0) std::filesystem::remove_all(d); }