From 47e94592ce0501a0485c6efff10d23dbaf37387f Mon Sep 17 00:00:00 2001 From: gaozheng Date: Mon, 29 Jun 2026 12:23:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(radar):=20=E8=A7=84=E8=8C=83=E5=8C=96=20.h?= =?UTF-8?q?ead=20=E8=A7=A3=E6=9E=90(=E7=BB=B4=E5=BA=A6/=E5=AD=97=E8=8A=82?= =?UTF-8?q?=E5=BA=8F/=E9=80=9A=E9=81=93=E5=81=8F=E7=A7=BB/=E6=B7=B1?= =?UTF-8?q?=E5=BA=A6=E9=97=B4=E8=B7=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/io/gpr/CMakeLists.txt | 2 +- src/io/gpr/NormalizedRadarReader.cpp | 67 +++++++++++++++++++ src/io/gpr/NormalizedRadarReader.hpp | 35 ++++++++++ tests/CMakeLists.txt | 2 + tests/io/gpr/test_normalized_radar_reader.cpp | 35 ++++++++++ 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/io/gpr/NormalizedRadarReader.cpp create mode 100644 src/io/gpr/NormalizedRadarReader.hpp create mode 100644 tests/io/gpr/test_normalized_radar_reader.cpp diff --git a/src/io/gpr/CMakeLists.txt b/src/io/gpr/CMakeLists.txt index 75bed4e..e552813 100644 --- a/src/io/gpr/CMakeLists.txt +++ b/src/io/gpr/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(geopro_io_gpr STATIC IprHeader.cpp IprbReader.cpp GprGeometry.cpp GprSurveyAssembler.cpp - GpsTrack.cpp) + GpsTrack.cpp NormalizedRadarReader.cpp) target_include_directories(geopro_io_gpr PUBLIC ${CMAKE_SOURCE_DIR}/src) target_compile_features(geopro_io_gpr PUBLIC cxx_std_17) # GprSurveyAssembler 返回 geopro::core::GprSurvey(头文件内联,仅需 include 解析)。 diff --git a/src/io/gpr/NormalizedRadarReader.cpp b/src/io/gpr/NormalizedRadarReader.cpp new file mode 100644 index 0000000..e8c86e2 --- /dev/null +++ b/src/io/gpr/NormalizedRadarReader.cpp @@ -0,0 +1,67 @@ +#include "io/gpr/NormalizedRadarReader.hpp" +#include +#include +#include +#include +namespace geopro::io::gpr { +namespace { +std::map parseKv(const std::string& text) { + std::map kv; + std::istringstream in(text); + std::string line; + while (std::getline(in, line)) { + const auto pos = line.find(':'); + if (pos == std::string::npos) continue; + std::string k = line.substr(0, pos), v = line.substr(pos + 1); + auto trim = [](std::string& s) { + const auto a = s.find_first_not_of(" \t\r\n"); + const auto b = s.find_last_not_of(" \t\r\n"); + s = (a == std::string::npos) ? "" : s.substr(a, b - a + 1); + }; + trim(k); trim(v); + kv[k] = v; + } + return kv; +} +int reqInt(const std::map& kv, const char* k) { + auto it = kv.find(k); + if (it == kv.end() || it->second.empty()) + throw std::runtime_error(std::string("规范化 .head 缺字段: ") + k); + return std::stoi(it->second); +} +double optD(const std::map& kv, const char* k, double dv) { + auto it = kv.find(k); + return (it == kv.end() || it->second.empty()) ? dv : std::stod(it->second); +} +} // namespace + +RadarHeader parseRadarHead(const std::string& headText) { + const auto kv = parseKv(headText); + RadarHeader h; + h.samples = reqInt(kv, "SAMPLES"); + h.channels = reqInt(kv, "NUMBER_OF_CH"); + h.lastTrace = reqInt(kv, "LAST_TRACE"); + if (h.channels <= 0 || h.lastTrace % h.channels != 0) + throw std::runtime_error("LAST_TRACE 不能被 NUMBER_OF_CH 整除"); + h.traces = static_cast(h.lastTrace / h.channels); + h.bits = static_cast(optD(kv, "BITS", 16)); + h.endianType = static_cast(optD(kv, "ENDIAN_TYPE", 1)); + h.distanceInterval = optD(kv, "DISTANCE_INTERVAL", 1.0); + h.timeWindowNs = optD(kv, "TIMEWINDOW", 0.0); + h.dielectric = optD(kv, "DIELECTRIC", 0.0); + auto it = kv.find("CH_X_OFFSETS"); + if (it != kv.end() && !it->second.empty()) { + std::istringstream os(it->second); + double v; + while (os >> v) h.chXOffsets.push_back(v); + } + return h; +} +double waveVelocityMperNs(const RadarHeader& h) { + return h.dielectric > 0.0 ? 0.2998 / std::sqrt(h.dielectric) : 0.1; +} +double depthSpacingZ(const RadarHeader& h) { + if (h.samples <= 1 || h.timeWindowNs <= 0.0) return 0.0; + return (h.timeWindowNs / (h.samples - 1)) * waveVelocityMperNs(h) / 2.0; +} +} // namespace geopro::io::gpr diff --git a/src/io/gpr/NormalizedRadarReader.hpp b/src/io/gpr/NormalizedRadarReader.hpp new file mode 100644 index 0000000..2556c18 --- /dev/null +++ b/src/io/gpr/NormalizedRadarReader.hpp @@ -0,0 +1,35 @@ +#ifndef GEOPRO_IO_GPR_NORMALIZED_RADAR_READER_HPP +#define GEOPRO_IO_GPR_NORMALIZED_RADAR_READER_HPP + +#include +#include + +namespace geopro::io::gpr { + +// 规范化雷达 .head(KEY:VALUE 文本头,每行一项)解析结果。 +struct RadarHeader { + int samples = 0; // N (SAMPLES) + int channels = 0; // M (NUMBER_OF_CH) + long lastTrace = 0; // 总扫描数=K*M (LAST_TRACE) + int traces = 0; // K = lastTrace/channels + int bits = 16; // 8/16/32 (BITS) + int endianType = 1; // 1 小端/2 大端 (ENDIAN_TYPE) + double distanceInterval = 1.0; // 道距 m (DISTANCE_INTERVAL) + double timeWindowNs = 0.0; // 时窗 ns (TIMEWINDOW) + double dielectric = 0.0; // 介电常数 (DIELECTRIC, 0=未知) + std::vector chXOffsets; // 通道横向偏移 m (CH_X_OFFSETS) +}; + +// 解析 KEY:VALUE 文本头。缺 SAMPLES/NUMBER_OF_CH/LAST_TRACE 任一 → std::runtime_error。 +// traces = lastTrace/channels(不整除抛错)。 +RadarHeader parseRadarHead(const std::string& headText); + +// 由 dielectric 求波速(m/ns): >0 时 0.2998/sqrt(eps),否则 0.1(默认)。 +double waveVelocityMperNs(const RadarHeader& h); + +// 深度采样间距(米): timeWindowNs/(samples-1) × 波速/2。samples<=1 → 0。 +double depthSpacingZ(const RadarHeader& h); + +} // namespace geopro::io::gpr + +#endif // GEOPRO_IO_GPR_NORMALIZED_RADAR_READER_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8aaa004..21edb38 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -226,6 +226,8 @@ target_sources(geopro_tests PRIVATE io/gpr/test_gpr_geometry.cpp) target_sources(geopro_tests PRIVATE io/gpr/test_gpr_survey_assembler.cpp) # GpsTrack:.gps 解析 + 经纬→局部米 + 沿轨迹里程插值/航向(G1 build-geo 基础,纯 C++17)。 target_sources(geopro_tests PRIVATE io/gpr/test_gps_track.cpp) +# NormalizedRadarReader:规范化 .head(KEY:VALUE) 解析(维度/字节序/通道偏移/波速/深度间距,纯 C++17)。 +target_sources(geopro_tests PRIVATE io/gpr/test_normalized_radar_reader.cpp) target_link_libraries(geopro_tests PRIVATE geopro_io_gpr) # Gpr3dvVolumeBridge(P2):gpr3dv 处理后立方体 → geopro 量化体(轴 X=道/Y=通道/Z=样本)。 diff --git a/tests/io/gpr/test_normalized_radar_reader.cpp b/tests/io/gpr/test_normalized_radar_reader.cpp new file mode 100644 index 0000000..e445350 --- /dev/null +++ b/tests/io/gpr/test_normalized_radar_reader.cpp @@ -0,0 +1,35 @@ +#include +#include "io/gpr/NormalizedRadarReader.hpp" +using namespace geopro::io::gpr; + +TEST(NormalizedRadarHead, ParsesCoreFieldsAndDerivesTraces) { + const std::string head = + "SAMPLES:516\nNUMBER_OF_CH:16\nLAST_TRACE:60448\nBITS:16\nENDIAN_TYPE:1\n" + "DISTANCE_INTERVAL:0.099194\nTIMEWINDOW:96.419553\nDIELECTRIC:\n" + "CH_X_OFFSETS:0.080 0.160 0.240 0.320 0.400 0.480 0.560 0.640 0.720 0.800 " + "0.880 0.960 1.040 1.120 1.200 1.280\n"; + const RadarHeader h = parseRadarHead(head); + EXPECT_EQ(h.samples, 516); + EXPECT_EQ(h.channels, 16); + EXPECT_EQ(h.lastTrace, 60448); + EXPECT_EQ(h.traces, 3778); // 60448/16 + EXPECT_EQ(h.bits, 16); + EXPECT_EQ(h.endianType, 1); + EXPECT_DOUBLE_EQ(h.distanceInterval, 0.099194); + ASSERT_EQ(h.chXOffsets.size(), 16u); + EXPECT_DOUBLE_EQ(h.chXOffsets.front(), 0.080); + EXPECT_DOUBLE_EQ(h.chXOffsets.back(), 1.280); +} + +TEST(NormalizedRadarHead, MissingRequiredFieldThrows) { + EXPECT_THROW(parseRadarHead("SAMPLES:516\nNUMBER_OF_CH:16\n"), std::runtime_error); +} + +TEST(NormalizedRadarHead, DepthSpacingUsesDefaultVelocityWhenNoDielectric) { + const std::string head = "SAMPLES:516\nNUMBER_OF_CH:16\nLAST_TRACE:32\n" + "TIMEWINDOW:96.419553\nDIELECTRIC:\n"; + const RadarHeader h = parseRadarHead(head); + EXPECT_NEAR(waveVelocityMperNs(h), 0.1, 1e-9); // 无介电 → 默认 0.1 + const double dz = depthSpacingZ(h); + EXPECT_NEAR(dz, (96.419553 / 515.0) * 0.1 / 2.0, 1e-9); +}