#include "Rd3Parser.h" #include "PerformanceLogger.h" #include #include #include #include #include #include #include #include #include /** * @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(model.tracesPerChannel * model.header.distanceInc); } else { model.totalDistance = static_cast(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(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(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; }