geopro/external/gpr3dviewer/Rd3Parser.cpp

301 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Rd3Parser.h"
#include "PerformanceLogger.h"
#include <QFile>
#include <QTextStream>
#include <QDataStream>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>
#include <QStringList>
#include <QVector3D>
/**
* @brief 加载整套Mala Mira系列雷达RD3数据rad头文件 + rd3纯二进制波形
* @param radFilePath .rad文本配置头文件路径
* @param model 输出GPR全局数据模型
* @return true 加载解析成功false 失败
* @note 存储结构:.rad存储全部仪器/测线参数;.rd3无文件头从头到尾全是short16振幅采样
*/
bool Rd3Parser::loadFromRad(const QString &radFilePath, GPRDataModel &model) {
SCOPED_PERF_TIMER("Parser", "Rd3Parser::loadFromRad");
// 清空旧数据与头部信息
model.clear();
model.header = GPRDataModel::Header{};
// 第一步解析rad文本头填充全部采集参数
if (!parseRadHeader(radFilePath, model.header)) {
qDebug() << "Error: Failed to parse .rad header file:" << radFilePath;
return false;
}
// 自动匹配同目录同名二进制数据文件 .rd3
QFileInfo radInfo(radFilePath);
QString binaryPath = radInfo.absolutePath() + "/" + radInfo.completeBaseName() + ".rd3";
if (!QFile::exists(binaryPath)) {
qDebug() << "Error: Matching binary file not found at:" << binaryPath;
return false;
}
// 推算后仍无有效道数则失败
if (model.header.numTraces <= 0) {
qDebug() << "Error: Unable to determine numTraces from header";
return false;
}
// 第二步:读取无头部的纯二进制波形
return loadRd3Binary(binaryPath, model);
}
/**
* @brief 解析RAD文本头文件提取雷达采集关键参数存入Header结构体
* @param radFilePath rad文本文件路径
* @param header 待填充头部参数结构体
* @return 解析成功返回true
*/
bool Rd3Parser::parseRadHeader(const QString &radFilePath, GPRDataModel::Header &header) {
SCOPED_PERF_TIMER("Parser", "Rd3Parser::parseRadHeader");
QFile file(radFilePath);
// 只读文本模式打开
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Error: Cannot open rad file for read";
return false;
}
QTextStream in(&file);
// 逐行读取键值对 KEY: VALUE 或 KEY=VALUE
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.isEmpty()) continue;
int sepPos = line.indexOf(':');
if (sepPos == -1) sepPos = line.indexOf('=');
if (sepPos == -1) continue;
QString key = line.left(sepPos).trimmed();
QString value = line.mid(sepPos + 1).trimmed();
// 原始字符串参数全存入map备用
header.rawParams[key] = value;
// 识别核心业务参数并类型转换赋值
if (key == "SAMPLES") {
header.samplesPerTrace = extractInt(value);
} else if (key == "LAST TRACE") {
header.numTraces = extractInt(value);
} else if (key == "TIMEWINDOW") {
header.timeWindowNs = extractDouble(value);
} else if (key == "DISTANCE INTERVAL") {
header.distanceInc = extractDouble(value);
} else if (key == "ANTENNAS") {
header.antennaFreq = extractDouble(value);
} else if (key == "ANTENNAS") {
header.antennaType = value;
} else if (key == "DATE") {
header.date = value;
} else if (key == "TIME") {
header.timeStr = value;
} else if (key == "NUMBER_OF_CH") {
header.numberOfChannels = extractInt(value);
} else if (key == "CH_X_OFFSETS") {
// 多通道天线水平X偏移量
QStringList offsets = value.split(' ', Qt::SkipEmptyParts);
for (const QString &offset : offsets) {
header.chXOffsets.append(offset.toFloat());
}
} else if (key == "CH_Y_OFFSETS") {
// 多通道天线行进Y偏移量
QStringList offsets = value.split(' ', Qt::SkipEmptyParts);
for (const QString &offset : offsets) {
header.chYOffsets.append(offset.toFloat());
}
} else if (key == "UNITS") {
header.units = value;
} else if (key == "START POSITION") {
header.startPosition = extractDouble(value);
} else if (key == "STOP POSITION") {
header.stopPosition = extractDouble(value);
} else if (key == "TIME INTERVAL") {
header.timeIntervalNs = extractDouble(value);
}
}
file.close();
// 基础合法性校验单道采样数必须大于0总道数允许从二进制文件推算
if (header.samplesPerTrace <= 0) {
qDebug() << "Error: Invalid SAMPLES value in rad file";
return false;
}
// 设置地层电磁波默认速度 0.1 m/ns空气近似值后续界面可手动修改
header.waveVelocity = 0.1;
qDebug() << "==== RAD Header Parse Complete ===="
<< "\nSamples per trace:" << header.samplesPerTrace
<< "\nTotal traces:" << header.numTraces
<< "\nTime window(ns):" << header.timeWindowNs
<< "\nChannel count:" << header.numberOfChannels
<< "\nX offset array size:" << header.chXOffsets.size()
<< "\nY offset array size:" << header.chYOffsets.size();
return true;
}
/**
* @brief 读取纯RD3二进制数据无任何文件头全连续short16振幅
* @param rd3FilePath rd3波形文件路径
* @param model 绑定头部参数填充完整traces波形数组
* @return 读取成功true
* @detail 存储格式:
* 道0采样0,道0采样1...道0采样N | 道1采样0...道1采样N | ...循环所有通道所有道
* 数据类型:小端序 short 16位有符号整型
*/
bool Rd3Parser::loadRd3Binary(const QString &rd3FilePath, GPRDataModel &model) {
SCOPED_PERF_TIMER("Parser", "Rd3Parser::loadRd3Binary");
QFile file(rd3FilePath);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Error: Open rd3 binary failed:" << rd3FilePath;
return false;
}
const int samplesPerTrace = model.header.samplesPerTrace;
const int totalTraceCount = model.header.numTraces;
// ========== 关键修正rd3无头部偏移直接从文件0位置开始读波形 ==========
const qint64 dataStartOffset = 0;
file.seek(dataStartOffset);
// 理论完整字节大小校验
const qint64 singleTraceByteSize = samplesPerTrace * sizeof(short);
const qint64 fullExpectedBytes = totalTraceCount * singleTraceByteSize;
const qint64 realFileBytes = file.size();
if (realFileBytes < fullExpectedBytes) {
qDebug() << "Warning: RD3 file size smaller than theoretical data size!"
<< "Expected:" << fullExpectedBytes << "Actual:" << realFileBytes;
}
// 预分配内存容器,减少动态扩容开销
model.traces.reserve(totalTraceCount);
// 单道读取缓冲区
QByteArray traceBuffer;
traceBuffer.resize(singleTraceByteSize);
// 通道数量兜底
const int channelCnt = model.header.numberOfChannels > 0 ? model.header.numberOfChannels : 1;
model.channels = channelCnt;
// 每通道独立道数量
model.tracesPerChannel = totalTraceCount / channelCnt;
// 计算整条测线总行进距离
if (model.header.distanceInc > 1e-6) {
model.totalDistance = static_cast<float>(model.tracesPerChannel * model.header.distanceInc);
} else {
model.totalDistance = static_cast<float>(model.header.stopPosition - model.header.startPosition);
}
// 循环逐道读取二进制波形
for (int traceGlobalIdx = 0; traceGlobalIdx < totalTraceCount; ++traceGlobalIdx) {
if (file.atEnd()) {
qDebug() << "Warning: File ended early at global trace index" << traceGlobalIdx;
break;
}
// 读取一整道所有采样字节
qint64 readBytes = file.read(traceBuffer.data(), traceBuffer.size());
if (readBytes != traceBuffer.size()) {
qDebug() << "Warning: Trace" << traceGlobalIdx << "incomplete byte read";
break;
}
RadarTrace oneTrace;
oneTrace.amplitudes.resize(samplesPerTrace);
// 二进制指针强转shortWindows平台原生小端序雷达标准字节序
const short* rawShortBuf = reinterpret_cast<const short*>(traceBuffer.constData());
// 填充单道振幅值
for (int s = 0; s < samplesPerTrace; s++) {
oneTrace.amplitudes[s] = rawShortBuf[s];
}
// 分配所属通道、通道内道序号
int chNo = traceGlobalIdx % channelCnt;
int traceInChIdx = traceGlobalIdx / channelCnt;
oneTrace.channelNumber = chNo;
// 计算空间三维坐标 X(天线横向偏移) Y(行进里程) Z=0地面平面
float xOff = 0.0f;
float yOff = 0.0f;
if (chNo < model.header.chXOffsets.size()) xOff = model.header.chXOffsets[chNo];
if (chNo < model.header.chYOffsets.size()) yOff = model.header.chYOffsets[chNo];
float lineDist = static_cast<float>(model.header.startPosition + traceInChIdx * model.header.distanceInc);
oneTrace.position = QVector3D(xOff, lineDist - yOff, 0.0f);
model.traces.append(std::move(oneTrace));
}
file.close();
if (model.traces.isEmpty()) {
qDebug() << "Error: No valid traces loaded from rd3";
return false;
}
qDebug() << "RD3 Binary Load OK, total valid traces:" << model.traces.size();
return true;
}
/**
* @brief 安全字符串转double数值优先提取字符串开头的数字转换失败返回0
* @param value 原始字符串200 MHz shielded
* @return 浮点结果
*/
double Rd3Parser::extractDouble(const QString& value) {
bool ok = false;
double res = 0.0;
// 1. 遍历字符串,截取开头连续的数字/小数点部分
int numLength = 0;
while (numLength < value.length()) {
QChar ch = value.at(numLength);
// 只保留 数字 和 小数点(支持小数,如 200.5 MHz
if (ch.isDigit() || ch == '.') {
numLength++;
}
else {
// 遇到非数字/小数点,停止截取
break;
}
}
// 2. 截取有效数字字符串并转换
if (numLength > 0) {
QString numStr = value.left(numLength);
res = numStr.toDouble(&ok);
}
// 3. 转换成功返回数值失败返回0.0
return ok ? res : 0.0;
}
/**
* @brief 安全字符串转int整型转换失败返回0
* @param value 原始rad文件字符串数值
* @return 整型结果
*/
int Rd3Parser::extractInt(const QString &value) {
bool ok = false;
int res = value.toInt(&ok);
return ok ? res : 0;
}