LTOBDemo
时间序列降采样算法LTOB示例项目
LTOBDemo
时间序列降采样算法LTOB示例项目
语言组成
- C++
- 100.0%
- C
- 0.0%
文件结构
查看完整文件清单(15 个文件)
LTOBDemo.cpp LTOBDemo.h LTOBDemo.qrc LTOBDemo.sln LTOBDemo.ui LTOBDemo.vcxproj LTOBDemo.vcxproj.filters README.md Worker.cpp Worker.h ltob_downsample.cpp ltob_downsample.h main.cpp qCustomPlot/qcustomplot.cpp qCustomPlot/qcustomplot.h
关键源码
ltob_downsample.h
#pragma once#include <QVector>// 使用 LTOB(Largest Triangle One Bucket)算法对二维数据进行降采样。// 该方法在每个桶中选择与相邻均值点组成的三角形面积最大的点,// 在尽量保留形状特征的同时显著降低点数。void ltobDownsample(const QVector<double>& xIn, const QVector<double>& yIn, QVector<double>& xOut, QVector<double>& yOut, int targetCount);ltob_downsample.cpp
#include "ltob_downsample.h"#include <cmath>// LTOB(Largest Triangle One Bucket)降采样实现。// 思路:// - 保留首尾点// - 将剩余点均匀划分为若干桶// - 对每个桶,从上一个已选点出发,选择与后续均值点构成三角形面积最大的点// 这样可以在较少点的情况下保留曲线轮廓的关键变化。/** @brief 计算三角形面积* @param[in] ax:顶点A的X轴坐标* @param[in] ay:顶点A的Y轴坐标* @param[in] bx:顶点B的X轴坐标* @param[in] by:顶点B的Y轴坐标* @param[in] cx:顶点C的X轴坐标* @param[in] cy:顶点C的Y轴坐标*/static inline double triArea(double ax, double ay, double bx, double by, double cx, double cy){ return std::abs((ax - cx) * (by - ay) - (ax - bx) * (cy - ay));}/** @brief LTOB时间序列降采样算法* @param[in] xIn:输入的X轴数据* @param[in] yIn:输入的Y轴数据* @param[out] xOut:输出的X轴数据* @param[out] yOut:输出的Y轴数据* @param[in] targetCount:目标点数*/void ltobDownsample(const QVector<double>& xIn, const QVector<double>& yIn, QVector<double>& xOut, QVector<double>& yOut, int targetCount){ xOut.clear(); yOut.clear(); const int n = xIn.size(); // 输入数据大小 if (n == 0 || targetCount <= 0) { // 空数据或目标数量非法直接返回 return; } if (targetCount >= n) { // 不需要降采样:直接返回原始数据 xOut = xIn; yOut = yIn; return; } if (targetCount == 1) { // 只取首个点 xOut.push_back(xIn.front()); yOut.push_back(yIn.front()); return; } xOut.reserve(targetCount); yOut.reserve(targetCount); // 保留第一个点 xOut.push_back(xIn[0]); yOut.push_back(yIn[0]); int a = 0; // 上一个已选点的索引 const double bucketSize = double(n - 2) / double(targetCount - 2); for (int i = 0; i < targetCount - 2; ++i) { const int bucketStart = int(std::floor(1 + i * bucketSize)); const int bucketEnd = int(std::floor(1 + (i + 1) * bucketSize)); // 计算下一桶的均值点(用于与候选点构成三角形) double avgX = 0.0, avgY = 0.0; int avgStart = int(std::floor(1 + (i + 1) * bucketSize)); int avgEnd = int(std::floor(1 + (i + 2) * bucketSize)); if (avgEnd > n - 1) { avgEnd = n - 1; } int avgCount = std::max(1, avgEnd - avgStart); for (int k = avgStart; k < avgEnd; ++k) { avgX += xIn[k]; avgY += yIn[k]; } avgX /= avgCount; avgY /= avgCount; // 在当前桶中选择面积最大的候选点 double maxArea = -1.0; int chosen = bucketStart; int bEnd = std::max(bucketStart + 1, bucketEnd); for (int j = bucketStart; j < bEnd; ++j) { double area = triArea(xIn[a], yIn[a], xIn[j], yIn[j], avgX, avgY); if (area > maxArea) { maxArea = area; chosen = j; } } xOut.push_back(xIn[chosen]); yOut.push_back(yIn[chosen]); a = chosen; } // 保留最后一个点 xOut.push_back(xIn[n - 1]); yOut.push_back(yIn[n - 1]);}Worker.cpp
#include "Worker.h"#include "ltob_downsample.h"#include <QRandomGenerator>// 后台数据生成与降采样的实现。// 每次运行都会随机化趋势、幅值、频率、相位与噪声强度,确保数据不同。// 构造函数,保持默认行为Worker::Worker(QObject* parent) : QObject(parent) {}void Worker::run(int targetCount){ // 原始数据点数量(较大以体现降采样效果) const int N = 10000; QVector<double> xIn; QVector<double> yIn; xIn.resize(N); yIn.resize(N); // 随机化数据参数,使每次生成的数据形态不同 auto rng = QRandomGenerator::global(); const double slope = 0.0002 + rng->generateDouble() * 0.0018; // 线性趋势斜率 const double amp = 5.0 + rng->generateDouble() * 15.0; // 正弦信号幅值 const double freq = 0.005 + rng->generateDouble() * 0.020; // 正弦信号频率 const double phase = rng->generateDouble() * 6.283185307179586; // 相位(0~2π) const double noiseScale = 0.5 + rng->generateDouble() * 2.0; // 噪声强度系数 for (int i = 0; i < N; ++i) { xIn[i] = i; double trend = slope * i; // 线性趋势 double signal = std::sin(i * freq + phase) * amp; // 正弦成分 double noise = noiseScale * (rng->bounded(-100, 100) / 100.0);// 随机噪声 yIn[i] = trend + signal + noise; // 间隔触发进度更新(上限 50%),剩余进度在计算完成后补齐 if (i % (N / 10) == 0) emit progress(i * 50 / N); } QVector<double> xOut; QVector<double> yOut; // 使用 LTOB 算法将数据降采样到 targetCount ltobDownsample(xIn, yIn, xOut, yOut, targetCount); emit progress(100); emit resultReady(xIn, yIn, xOut, yOut); emit finished();}LTOBDemo.cpp
#include "LTOBDemo.h"#include "Worker.h"#include "ltob_downsample.h"#include <QPen>#include <QTimer>#include "qCustomPlot/qcustomplot.h"// 主窗口实现:负责配置图表、接线信号与槽以及展示结果。LTOBDemo::LTOBDemo(QWidget *parent) : QWidget(parent) , ui(new Ui::LTOBDemoClass()){ ui->setupUi(this); // 配置“原始数据”图表样式并添加标题 ui->plotOriginal->addGraph(); ui->plotOriginal->graph(0)->setPen(QPen(QColor(0, 180, 0), 2)); ui->plotOriginal->graph(0)->setScatterStyle(QCPScatterStyle::ssNone); ui->plotOriginal->addGraph(); ui->plotOriginal->graph(1)->setPen(QPen(QColor(200, 0, 0), 2)); ui->plotOriginal->graph(1)->setScatterStyle(QCPScatterStyle::ssNone); ui->plotOriginal->graph(0)->setName("原始数据"); ui->plotOriginal->graph(1)->setName("降采样数据"); ui->plotOriginal->xAxis->grid()->setVisible(true); ui->plotOriginal->yAxis->grid()->setVisible(true); ui->plotOriginal->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom); ui->plotOriginal->axisRect()->setRangeDragAxes(ui->plotOriginal->xAxis, ui->plotOriginal->yAxis); ui->plotOriginal->axisRect()->setRangeZoomAxes(ui->plotOriginal->xAxis, ui->plotOriginal->yAxis); ui->plotOriginal->plotLayout()->insertRow(0); ui->plotOriginal->plotLayout()->addElement(0, 0, new QCPTextElement(ui->plotOriginal, "原始数据与降采样数据")); ui->plotOriginal->legend->setVisible(true); ui->plotOriginal->axisRect()->insetLayout()->addElement(ui->plotOriginal->legend, Qt::AlignTop | Qt::AlignRight); // 绑定按钮点击以启动后台生成任务 connect(ui->btnGenerate, &QPushButton::clicked, this, <OBDemo::onGenerateClicked);}LTOBDemo::~LTOBDemo(){ if (workerThread) { // 优雅退出线程并回收资源 workerThread->quit(); workerThread->wait(); workerThread->deleteLater(); workerThread = nullptr; } delete ui;}void LTOBDemo::onGenerateClicked(){ if (busy) return; busy = true; ui->btnGenerate->setEnabled(false); ui->progressBar->setValue(0); int targetCount = ui->spinTargetCount->value(); // 创建 Worker 并将其移动到新线程 Worker* worker = new Worker(); workerThread = new QThread(this); worker->moveToThread(workerThread); // 在线程启动时调用 Worker::run,连接信号用于更新 UI 与结束处理 connect(workerThread, &QThread::started, [worker, targetCount]() { worker->run(targetCount); }); connect(worker, &Worker::progress, this, <OBDemo::onProgress); connect(worker, &Worker::resultReady, this, <OBDemo::onResultReady); connect(worker, &Worker::finished, this, <OBDemo::onWorkerFinished); connect(worker, &Worker::finished, workerThread, &QThread::quit); connect(workerThread, &QThread::finished, worker, &QObject::deleteLater); workerThread->start();}void LTOBDemo::onProgress(int percent){ ui->progressBar->setValue(percent);}void LTOBDemo::onResultReady(const QVector<double>& xIn, const QVector<double>& yIn, const QVector<double>& xOut, const QVector<double>& yOut){ // 更新原始数据曲线 ui->plotOriginal->graph(0)->setData(xIn, yIn); ui->plotOriginal->graph(1)->setData(xOut, yOut); ui->plotOriginal->rescaleAxes(); ui->plotOriginal->replot();}void LTOBDemo::onWorkerFinished(){ // 恢复按钮与忙状态标记 ui->btnGenerate->setEnabled(true); busy = false;}main.cpp
#include "LTOBDemo.h"#include <QtWidgets/QApplication>// 程序入口:创建 Qt 应用并显示主窗口。int main(int argc, char *argv[]){ QApplication a(argc, argv); LTOBDemo w; w.show(); return a.exec();}