feat(io/gpr): 新增 .iprh 头解析器(纯 C++17,零 Qt/VTK)

- IprHeader/parseIprHeader:按行解析 KEY: value,支持含空格键名
  (LAST TRACE/SOIL VELOCITY/DISTANCE INTERVAL)
- SOIL VELOCITY 由 m/µs 统一换算为 m/s 存储(×1e6)
- 缺 SAMPLES/LAST TRACE/CHANNELS 任一抛 std::runtime_error
- CMake 接线:src/io(gpr) 静态库 geopro_io_gpr + tests 链接
- TDD:2 个新用例,全测试套件 100% 通过
This commit is contained in:
gaozheng 2026-06-23 09:45:13 +08:00
parent b509795ffd
commit c395921ca8
7 changed files with 117 additions and 0 deletions

View File

@ -9,6 +9,7 @@
# add_subdirectory(controller) #
#
add_subdirectory(core)
add_subdirectory(io)
add_subdirectory(data)
add_subdirectory(net)
add_subdirectory(render)

1
src/io/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_subdirectory(gpr)

View File

@ -0,0 +1,6 @@
add_library(geopro_io_gpr STATIC IprHeader.cpp)
target_include_directories(geopro_io_gpr PUBLIC ${CMAKE_SOURCE_DIR}/src)
target_compile_features(geopro_io_gpr PUBLIC cxx_std_17)
# C++17 Qt/VTK AUTOMOC/UIC/RCC
set_target_properties(geopro_io_gpr PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)

64
src/io/gpr/IprHeader.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "io/gpr/IprHeader.hpp"
#include <cctype>
#include <sstream>
#include <stdexcept>
namespace geopro::io::gpr {
namespace {
// SOIL VELOCITY 原文单位 m/µs本层统一换算成 m/s 存储。
constexpr double kSoilVelocityToMetersPerSecond = 1e6;
std::string trim(const std::string& s) {
std::size_t b = 0;
std::size_t e = s.size();
while (b < e && std::isspace(static_cast<unsigned char>(s[b]))) ++b;
while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1]))) --e;
return s.substr(b, e - b);
}
} // namespace
IprHeader parseIprHeader(const std::string& text) {
IprHeader header;
bool hasSamples = false;
bool hasLastTrace = false;
bool hasChannels = false;
std::istringstream stream(text);
std::string line;
while (std::getline(stream, line)) {
const std::size_t colon = line.find(':');
if (colon == std::string::npos) continue;
const std::string key = trim(line.substr(0, colon));
const std::string value = trim(line.substr(colon + 1));
if (value.empty()) continue;
if (key == "SAMPLES") {
header.samples = std::stoi(value);
hasSamples = true;
} else if (key == "LAST TRACE") {
header.lastTrace = std::stol(value);
hasLastTrace = true;
} else if (key == "CHANNELS") {
header.channels = std::stoi(value);
hasChannels = true;
} else if (key == "TIMEWINDOW") {
header.timeWindowNs = std::stod(value);
} else if (key == "SOIL VELOCITY") {
header.soilVelocity = std::stod(value) * kSoilVelocityToMetersPerSecond;
} else if (key == "DISTANCE INTERVAL") {
header.distanceInterval = std::stod(value);
}
}
if (!hasSamples || !hasLastTrace || !hasChannels) {
throw std::runtime_error("parseIprHeader: missing required field(s) (SAMPLES/LAST TRACE/CHANNELS)");
}
return header;
}
} // namespace geopro::io::gpr

23
src/io/gpr/IprHeader.hpp Normal file
View File

@ -0,0 +1,23 @@
#ifndef GEOPRO_IO_GPR_IPRHEADER_HPP
#define GEOPRO_IO_GPR_IPRHEADER_HPP
#include <string>
namespace geopro::io::gpr {
// 探地雷达 .iprh 文本头的关键字段(纯解析,零 Qt/VTK 依赖)。
struct IprHeader {
int samples = 0; // 每道采样点数 (SAMPLES)
long lastTrace = 0; // 最后一道索引 (LAST TRACE);道数 = lastTrace+1
int channels = 0; // 通道数 (CHANNELS)
double timeWindowNs = 0; // 时窗 ns (TIMEWINDOW)
double soilVelocity = 0; // 土速,统一存 m/s原文 SOIL VELOCITY 单位 m/µs读入后 ×1e6
double distanceInterval = 0; // 道距 m (DISTANCE INTERVAL)
};
// 解析 .iprh 头文本。samples/lastTrace/channels 缺任一抛 std::runtime_error。
IprHeader parseIprHeader(const std::string& text);
} // namespace geopro::io::gpr
#endif // GEOPRO_IO_GPR_IPRHEADER_HPP

View File

@ -168,4 +168,8 @@ target_sources(geopro_tests PRIVATE controller/test_workbench_nav_controller.cpp
target_sources(geopro_tests PRIVATE controller/test_vtk_scene_controller.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_controller Qt6::Test)
# io/gpr .iprh C++17 Qt/VTK
target_sources(geopro_tests PRIVATE io/gpr/test_ipr_header.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_io_gpr)
add_subdirectory(spike) # spike S3: banded contour

View File

@ -0,0 +1,18 @@
#include "io/gpr/IprHeader.hpp"
#include <gtest/gtest.h>
using geopro::io::gpr::parseIprHeader;
TEST(IprHeader, ParsesKeyFieldsFromRealSample) {
const std::string t =
"SAMPLES: 821\nLAST TRACE: 45305\nCHANNELS: 14\n"
"TIMEWINDOW: 160.352\nSOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.049084\n";
auto h = parseIprHeader(t);
EXPECT_EQ(h.samples, 821);
EXPECT_EQ(h.lastTrace, 45305);
EXPECT_EQ(h.channels, 14);
EXPECT_DOUBLE_EQ(h.timeWindowNs, 160.352);
EXPECT_DOUBLE_EQ(h.soilVelocity, 1e8); // 100 m/µs → 1e8 m/s
EXPECT_NEAR(h.distanceInterval, 0.049084, 1e-9);
}
TEST(IprHeader, ThrowsOnMissingSamples) {
EXPECT_THROW(parseIprHeader("CHANNELS: 14\n"), std::runtime_error);
}