301 lines
11 KiB
C++
301 lines
11 KiB
C++
#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);
|
||
// 二进制指针强转short(Windows平台原生小端序,雷达标准字节序)
|
||
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;
|
||
}
|
||
|