geopro/external/gpr3dviewer/TrajectoryCalculator.cpp

243 lines
9.6 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 "TrajectoryCalculator.h"
#include <algorithm>
#include <cmath>
namespace {
double planarDistance(const QVector3D &a, const QVector3D &b)
{
const double dx = static_cast<double>(a.x()) - b.x();
const double dy = static_cast<double>(a.y()) - b.y();
return std::sqrt(dx * dx + dy * dy);
}
QVector3D lerpPoint(const QVector3D &a, const QVector3D &b, double t)
{
return a * static_cast<float>(1.0 - t) + b * static_cast<float>(t);
}
QVector3D catmullRom(const QVector3D &p0, const QVector3D &p1, const QVector3D &p2, const QVector3D &p3, double t)
{
const float tf = static_cast<float>(t);
const float t2 = tf * tf;
const float t3 = t2 * tf;
return 0.5f * ((2.0f * p1) + (-p0 + p2) * tf + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 +
(-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
}
} // namespace
double TrajectoryCalculator::headingAt(int traceIdx, const QVector<QVector3D> &gpsPositions)
{
const int n = gpsPositions.size();
if (n <= 1) return 0.0;
if (traceIdx < n - 1) {
double deltaX = gpsPositions[traceIdx + 1].x() - gpsPositions[traceIdx].x(); // 北向变化
double deltaY = gpsPositions[traceIdx + 1].y() - gpsPositions[traceIdx].y(); // 东向变化
return std::atan2(deltaY, deltaX);
} else {
// 最后一点复用前一点方向
double deltaX = gpsPositions[traceIdx].x() - gpsPositions[traceIdx - 1].x();
double deltaY = gpsPositions[traceIdx].y() - gpsPositions[traceIdx - 1].y();
return std::atan2(deltaY, deltaX);
}
}
bool TrajectoryCalculator::computeTrajectories(SurveyLine &line, const SurveyGeometry &geom)
{
const QVector<QVector3D> &gps = line.gpsPositions;
const int nRtk = gps.size();
if (nRtk <= 0) return false;
const int chCount = geom.channelCount;
if (chCount <= 0) return false;
// 通道相对x偏移优先使用头文件CH_X_OFFSETS推导出的逐通道数组旧工程无数组时按首通道对称补齐。
QVector<double> chXRel = geom.channelXRel;
if (chXRel.size() != chCount) {
chXRel.resize(chCount);
const double lastXRel = -geom.ch1XRel;
for (int c = 0; c < chCount; ++c) {
chXRel[c] = (chCount > 1)
? geom.ch1XRel + (lastXRel - geom.ch1XRel) * c / (chCount - 1.0)
: geom.ch1XRel;
}
}
// 预分配 channelTrajectories
line.channelTrajectories.resize(chCount);
for (int c = 0; c < chCount; ++c) {
line.channelTrajectories[c].resize(nRtk);
}
// 遍历每个 RTK 点
for (int i = 0; i < nRtk; ++i) {
double thetaY = headingAt(i, gps);
double thetaX = thetaY + M_PI / 2.0;
// 旋转矩阵 R = [cos(theta_x), cos(theta_y); sin(theta_x), sin(theta_y)]
// 作用将设备相对坐标系x=右, y=前转换为绝对坐标系X=北, Y=东)
double r11 = std::cos(thetaX);
double r12 = std::cos(thetaY);
double r21 = std::sin(thetaX);
double r22 = std::sin(thetaY);
// RTK 相对天线中心的偏移向量(设备坐标系)
double rtkLocalX = geom.rtkOffsetX; // 横向偏移
double rtkLocalY = geom.rtkOffsetY; // 纵向偏移(前方为正)
// 转换为绝对坐标系偏移
double rtkAbsOffsetX = r11 * rtkLocalX + r12 * rtkLocalY;
double rtkAbsOffsetY = r21 * rtkLocalX + r22 * rtkLocalY;
// 天线中心绝对坐标 = RTK - 偏移量
double antX = gps[i].x() - rtkAbsOffsetX;
double antY = gps[i].y() - rtkAbsOffsetY;
double antZ = gps[i].z();
// 遍历每个通道
for (int c = 0; c < chCount; ++c) {
// 通道相对坐标(设备坐标系)
double chLocalX = chXRel[c];
double chLocalY = 0.0; // MATLAB 中 ch_y_rel = zeros(...)
// 转换为绝对坐标系偏移
double chAbsOffsetX = r11 * chLocalX + r12 * chLocalY;
double chAbsOffsetY = r21 * chLocalX + r22 * chLocalY;
// 通道绝对坐标
double chX = antX + chAbsOffsetX;
double chY = antY + chAbsOffsetY;
double chZ = antZ;
line.channelTrajectories[c][i] = QVector3D(static_cast<float>(chX),
static_cast<float>(chY),
static_cast<float>(chZ));
}
}
// 同步更新 GPRDataModel 中 traces 的 position
// 数据存储顺序trace0_ch0, trace0_ch1, ... trace0_chN, trace1_ch0, ...
// 即 globalIdx = traceInChannel * chCount + channel
const int tracesPerChannel = line.data.getTracesPerChannel();
for (int c = 0; c < chCount; ++c) {
for (int t = 0; t < tracesPerChannel && t < nRtk; ++t) {
int globalIdx = t * chCount + c;
if (globalIdx >= 0 && globalIdx < line.data.traces.size()) {
line.data.traces[globalIdx].position = line.channelTrajectories[c][t];
}
}
}
return true;
}
QVector<QVector3D> TrajectoryCalculator::resampleTrajectoryLinear(const QVector<QVector3D> &input, int targetCount)
{
QVector<QVector3D> output;
if (targetCount <= 0 || input.isEmpty()) return output;
if (input.size() == 1 || targetCount == 1) {
output.resize(targetCount);
std::fill(output.begin(), output.end(), input.first());
return output;
}
output.reserve(targetCount);
const double scale = static_cast<double>(input.size() - 1) / qMax(1, targetCount - 1);
for (int i = 0; i < targetCount; ++i) {
const double src = i * scale;
const int i0 = qBound(0, static_cast<int>(std::floor(src)), input.size() - 1);
const int i1 = qBound(0, i0 + 1, input.size() - 1);
output.append(lerpPoint(input[i0], input[i1], src - i0));
}
return output;
}
QVector<QVector3D> TrajectoryCalculator::resampleTrajectorySpline(const QVector<QVector3D> &input, int targetCount)
{
if (input.size() < 4) return resampleTrajectoryLinear(input, targetCount);
QVector<QVector3D> output;
if (targetCount <= 0) return output;
if (targetCount == 1) return QVector<QVector3D>{input.first()};
output.reserve(targetCount);
const double scale = static_cast<double>(input.size() - 1) / qMax(1, targetCount - 1);
for (int i = 0; i < targetCount; ++i) {
const double src = i * scale;
const int i1 = qBound(0, static_cast<int>(std::floor(src)), input.size() - 1);
const int i2 = qBound(0, i1 + 1, input.size() - 1);
const int i0 = qBound(0, i1 - 1, input.size() - 1);
const int i3 = qBound(0, i2 + 1, input.size() - 1);
output.append(catmullRom(input[i0], input[i1], input[i2], input[i3], src - i1));
}
return output;
}
TrajectoryFilterResult TrajectoryCalculator::filterAndInterpolateTrajectory(const QVector<QVector3D> &input,
const TrajectoryFilterOptions &options,
int targetCount)
{
TrajectoryFilterResult result;
const int n = input.size();
result.keepMask = QVector<bool>(n, true);
result.distanceOutlierMask = QVector<bool>(n, false);
result.speedOutlierMask = QVector<bool>(n, false);
result.angleOutlierMask = QVector<bool>(n, false);
if (n <= 0 || targetCount <= 0) return result;
for (int i = 1; i < n; ++i) {
const double dist = planarDistance(input[i - 1], input[i]);
if (options.distanceFilterEnabled && dist > options.maxSegmentDistanceM) {
result.distanceOutlierMask[i] = true;
result.keepMask[i] = false;
}
if (options.speedFilterEnabled && options.traceIntervalSec > 1e-9 && dist / options.traceIntervalSec > options.maxSpeedMps) {
result.speedOutlierMask[i] = true;
result.keepMask[i] = false;
}
}
if (options.angleFilterEnabled) {
const double thresholdRad = options.maxTurnAngleDeg * M_PI / 180.0;
for (int i = 1; i + 1 < n; ++i) {
const double d0 = planarDistance(input[i - 1], input[i]);
const double d1 = planarDistance(input[i], input[i + 1]);
if (d0 < 1e-6 || d1 < 1e-6) continue;
const double ax = input[i].x() - input[i - 1].x();
const double ay = input[i].y() - input[i - 1].y();
const double bx = input[i + 1].x() - input[i].x();
const double by = input[i + 1].y() - input[i].y();
const double cosv = std::clamp((ax * bx + ay * by) / (d0 * d1), -1.0, 1.0);
const double turn = std::acos(cosv);
if (turn > thresholdRad) {
result.angleOutlierMask[i] = true;
result.keepMask[i] = false;
}
}
}
if (options.preserveEndpoints && n > 0) {
result.keepMask[0] = true;
result.keepMask[n - 1] = true;
}
QVector<QVector3D> kept;
kept.reserve(n);
for (int i = 0; i < n; ++i) {
if (result.keepMask.value(i, true)) kept.append(input[i]);
}
if (kept.size() < 2) {
kept = input;
result.warnings.append(QStringLiteral("过滤后有效轨迹点过少,已使用原始轨迹插值。"));
}
if (options.interpolationMode == TrajectoryFilterOptions::InterpolationMode::Spline) {
if (kept.size() < 4) result.warnings.append(QStringLiteral("样条插值点数不足,已回退到线性插值。"));
result.outputPositions = resampleTrajectorySpline(kept, targetCount);
} else {
result.outputPositions = resampleTrajectoryLinear(kept, targetCount);
}
return result;
}