feat(radar): 规范化 .head 解析(维度/字节序/通道偏移/深度间距)
This commit is contained in:
parent
abd3027610
commit
47e94592ce
|
|
@ -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 解析)。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
#include "io/gpr/NormalizedRadarReader.hpp"
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
namespace geopro::io::gpr {
|
||||
namespace {
|
||||
std::map<std::string, std::string> parseKv(const std::string& text) {
|
||||
std::map<std::string, std::string> 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<std::string, std::string>& 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<std::string, std::string>& 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<int>(h.lastTrace / h.channels);
|
||||
h.bits = static_cast<int>(optD(kv, "BITS", 16));
|
||||
h.endianType = static_cast<int>(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
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef GEOPRO_IO_GPR_NORMALIZED_RADAR_READER_HPP
|
||||
#define GEOPRO_IO_GPR_NORMALIZED_RADAR_READER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<double> 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
|
||||
|
|
@ -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=样本)。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
#include <gtest/gtest.h>
|
||||
#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);
|
||||
}
|
||||
Loading…
Reference in New Issue