一般就是三种方法
1.opengl,vtk这种从零自己画,网上也可能有半成品,大多是付费的。
2.重写qwt3d,07年就停止更新了,画出来类似opengl,需要自己修改参数,参数修改不对很难搞,对于经纬高某一个数数值跨度小不太友好。我这边需求数据就不太符合,所以很难搞,直接换成q3d了

3.直接用q3d,耗费资源比较大,方便点,但是只有曲面图,散点图和柱形图。除了不能拟合个线,其他都挺好用。

照着这个大佬的配置一下Am_Jun
重写qwt3d
track3d.h
cpp
#ifndef TRACK3D_H
#define TRACK3D_H
#include <QTimer>
#include <qwt3d_surfaceplot.h>
#include <qwt3d_function.h>
#include <QMouseEvent>
using namespace Qwt3D;
class Rosenbrock:public Function{ // 定义Rosenbrock类,继承自Function类
public:
Rosenbrock(SurfacePlot & pw):Function(pw){} // 构造函数,初始化SurfacePlot对象和Function对象
Rosenbrock(){} // 无参构造函数
double operator()(double x, double y)//需要自行更改这个函数,以x,y轴的数值确定z值,坑的很
{
// return 2*x*y;//z轴初始范围与该函数相关
return 9 * (y - 32.6);
}
};
class track3D:public Qwt3D::SurfacePlot
{
public:
// 构造函数 初始化三维图表
track3D();
// 初始化Rosenbrock函数表面(用于定义坐标系底图)
//void rosenbrockinit();
// 添加航迹线
bool addLine(QString linename,int linewidth);
// 添加标记点(实现为仅保留单点的特殊线段)
bool addPoint(QString pointName, int color,int lineWidth);
// 更新指定标记点的坐标(保持固定长度)
bool updatePointData(QString pointName,Qwt3D::Triple point);
// 删除指定航迹线
bool deleteLine(QString linename);
// 清除所有航迹线
bool deleteAllLine();
// 向指定航迹线添加数据点
bool addData(QString linename,Qwt3D::Triple point);
// 设置当前坐标范围极值
virtual void setCurMaxMin();
// 重新绘制图表
void replot();
// 将QColor转换为Qwt3D的RGBA格式
Qwt3D::RGBA colorToRGBA(const QColor& qtColor);
protected:
virtual void keyPressEvent(QKeyEvent *);
private:
bool first = 0;
// 三维图表实例(当前类本身)
Qwt3D::SurfacePlot plot;
//地图 管理航迹 航迹线管理器(键:线名称,值:线对象指针)
QMap<QString,Qwt3D::Line3D*> mLine;
//航迹 管理大小 航迹线长度记录(用于动态截断)
QMap<QString,int> mLineSize;
QVector<Line3D> testData;
//颜色索引(循环使用预设颜色)
int colorindex;
// 预设颜色列表
QVector<QColor> baseColors = {QColor(Qt::red), QColor(Qt::blue), QColor(Qt::black),QColor(Qt::green), /* 其他颜色 */};
// 当前坐标轴范围临时变量
double myCurxMin = 10000;
double myCurxMax = -10000;
double myCuryMin = 10000;
double myCuryMax = -10000;
// 初始坐标轴范围(经度/纬度)
double myxMin = 10000;
double myxMax = -10000;
double myyMin = 10000;
double myyMax = -10000;
// 单条航迹最大数据点数
int m_limit = 20000;
// 默认线宽
int defaultWidth = 5;
//设置显示盒子
Rosenbrock *m_rosenbrock;
};
#endif // TRACK3D_H
track3d.cpp
cpp
#include "track3d.h"
#include <QDebug>
track3D::track3D()
{
// 设置图表的标题
setTitle("GPS");
// 设置绘图样式为线条三维样式
setPlotStyle(Qwt3D::LINE3D_STYLE);
//自动缩放坐标轴
//coordinates()->setAutoScale(0);
//设置隔离线数目
setIsolines(5);
// 创建一个Rosenbrock函数对象,并将其与当前SurfacePlot对象关联
m_rosenbrock = new Rosenbrock(*this);
// 设置网格的分辨率为10x10
m_rosenbrock->setMesh(41,31);
// 设置函数的定义域为x,y都在0到100之间
m_rosenbrock->setDomain(myxMin,myxMax,myyMin,myyMax);
m_rosenbrock->setMinZ(0);
m_rosenbrock->setMaxZ(50);
m_rosenbrock->create();
// 设置坐标轴的网格线显示,仅在左侧、背面和底面显示
// coordinates()->setGridLines(true,false,Qwt3D::LEFT|Qwt3D::BACK|Qwt3D::FLOOR);
// 为每个坐标轴设置主刻度和次刻度数量
for (unsigned i=0; i != coordinates()->axes.size(); ++i)
{
coordinates()->axes[i].setMajors(5); // 主刻度数量
coordinates()->axes[i].setMinors(1); // 次刻度数量
}
// 设置各坐标轴的标签
coordinates()->axes[X1].setLabelString("经度/°");
coordinates()->axes[Y1].setLabelString("纬度/°");
coordinates()->axes[Z1].setLabelString("高度/m");
// 设置坐标轴样式为盒子样式
setCoordinateStyle(BOX);
// 设置图表的平移、旋转、缩放、缩小比例以及坐标轴的主刻度和副刻度数目
setShift(1,0,0);
setRotation(30,0,15);
setShift(0, 0, 0); // 将图表移动到中心
setZoom(0.9);
// 更新数据,并更新图表
replot();
}
//void track3D::rosenbrockinit()
//{
// //更新坐标轴样式
// Rosenbrock rosenbrock(*this);
// rosenbrock.setDomain(myxMin,myxMax,myyMin,myyMax);
// rosenbrock.setMinZ(myzMin);
// rosenbrock.setMaxZ(myzMax);
// rosenbrock.create();
//}
void track3D::keyPressEvent(QKeyEvent * e) // 定义键盘事件处理函数
{
int c = e->key(); // 获取用户按下的键
if(e->key() == Qt::Key_Up) // 判断用户按下的键是否为向上键
{
setShift(1,0,0); // 设置图形在三维空间中的位置,参数为x,y,z轴方向上的偏移量
setRotation(30,0,15); // 设置图形在三维空间中的旋转角度,参数为x,y,z轴方向上的旋转角度
setScale(1,1,1); // 设置图形缩放比例
setShift(0.15,0,0); // 重新设置图形的偏移量,使其在x轴正方向上有一定的移动
setZoom(0.9); // 缩小视图镜头
}
else if(c == 65) // 判断用户按下的键是否为字符'A'
{
setScale(xScale(),yScale(),zScale() * 1.2); // 增加z方向上的缩放比例
}
else if(c == 68) // 判断用户按下的键是否为字符'D'
{
setScale(xScale() * 1.2,yScale(),zScale()); // 增加x方向上的缩放比例
}
else if(c == 83) // 判断用户按下的键是否为字符'S'
{
setScale(xScale(),yScale() * 1.2,zScale()); // 增加y方向上的缩放比例
}
}
//添加轨迹线
bool track3D::addLine(QString linename,int linewidth)
{
//如果没有,则进行添加
if(!mLine.contains(linename))
{
Qwt3D::Line3D _l3d;
Qwt3D::Line3D *myLine1 = dynamic_cast<Qwt3D::Line3D *>(this->addEnrichment(_l3d));
myLine1->configure(linewidth,true);
colorindex = colorindex % 2;
myLine1->setLineColor(colorToRGBA(baseColors[colorindex])); //设置默认颜色为红色
colorindex++;
mLine.insert(linename,myLine1);
return true;
}
return false;
}
bool track3D::addPoint(QString pointName,int color, int lineWidth)
{
//实现方法:按照正常线段添加曲线 更新时只保留一个点位
//如果没有,则进行添加
if(!mLine.contains(pointName))
{
Qwt3D::Line3D _l3d;
Qwt3D::Line3D *myLine1 = dynamic_cast<Qwt3D::Line3D *>(this->addEnrichment(_l3d));
myLine1->configure(lineWidth,true);
myLine1->setLineColor(colorToRGBA(baseColors[color]));
mLine.insert(pointName,myLine1);
return true;
}
return false;
}
//更新标识线-动态绘制时每次调用该函数使得线段长度固定
bool track3D::updatePointData(QString pointName, Triple point)
{
//更新点的坐标
if(mLine.contains(pointName)){
mLineSize[pointName] += 1;
auto it = mLine.find(pointName);
if(mLineSize[pointName] > m_limit){
//删除原来的数据
it.value()->clear();
mLineSize[pointName] = 0;
}
//更新点
it.value()->add(point);
}
}
//删除单挑轨迹线
bool track3D::deleteLine(QString linename)
{
if(mLine.contains(linename)) //如果包含
{
auto it = mLine.find(linename);
it.value()->clear();
this->degrade(it.value());
mLine.remove(linename);
return true;
}
return false;
}
//删除所有轨迹线
bool track3D::deleteAllLine()
{
for(auto line:mLine)
{
line->clear(); //移除所有曲线数据
this->degrade(line);
}
mLine.clear(); //移除所有曲线
return true;
}
bool track3D::addData(QString linename, Triple point)
{
if(mLine.contains(linename)) {
// 独立判断各坐标轴极值
if (point.x < myCurxMin) { myCurxMin = point.x;}
if (point.x > myCurxMax) { myCurxMax = point.x;}
if (point.y < myCuryMin) { myCuryMin = point.y;}
if (point.y > myCuryMax) { myCuryMax = point.y;}
setCurMaxMin(); // 只有当极值变化时才更新坐标范围
replot(); // 刷新图表
auto it = mLine.find(linename);
it.value()->add(point);
return true;
} else {
if(addLine(linename,defaultWidth)) return true;
return false;
}
}
void track3D::setCurMaxMin()
{
myxMax = qMax(myxMax,myCurxMax);
myxMin = qMin(myxMin,myCurxMin);
myyMax = qMax(myyMax,myCuryMax);
myyMin = qMin(myyMin,myCuryMin);
//myzMax = qMax(myzMax,myCurzMax);
//myzMin = qMin(myzMin,myCurzMin);
// myxMax = myxMax + 1;
// myxMin = myxMin - 1;
// myyMax = myyMax + 1;
// myyMin = myyMin - 1;
// myzMax = myzMax + 1;
// myzMin = myzMin - 1;
qDebug() << "myxMax" << myxMax << "myxMin" << myxMin
<< "myyMax" << myyMax << "myyMin" << myyMin;
//<< "myzMax" << myzMax << "myzMin" << myzMin;
// double _yScale = myxMax / myzMax;
// _yScale = (myxMax / myzMax) > _yScale? (myyMax / myzMax): _yScale;
// setScale(1,1,_yScale);
m_rosenbrock->setDomain(myxMin - 0.001,myxMax + 0.001,myyMin - 0.001,myyMax + 0.001);
//m_rosenbrock->setMinZ(0);
//m_rosenbrock->setMaxZ(50);
m_rosenbrock->create();
// 为每个坐标轴设置主刻度和次刻度数量
for (unsigned i = 0; i != coordinates()->axes.size(); ++i)
{
coordinates()->axes[i].setMajors(5); // 主刻度数量
coordinates()->axes[i].setMinors(1); // 次刻度数量
}
// 设置各坐标轴的标签
coordinates()->axes[X1].setLabelString("经度/°");
coordinates()->axes[Y1].setLabelString("纬度/°");
coordinates()->axes[Z1].setLabelString("高度/m");
// 设置坐标轴样式为盒子样式
//setCoordinateStyle(BOX);
}
// 数据更新与重绘
void track3D::replot()
{
std::vector<Line3D> lineContain;
for(auto line:mLine)
{
lineContain.push_back(*line);
//方法1 单独为线段更新
//updateData(*line);
//updateGL();
}
//存在两条以上曲线时,必须使用此方法更新数据(参数为vector<Line3D>)
updateData(lineContain);
updateGL();
}
//qColor 2 RGBA
RGBA track3D::colorToRGBA(const QColor &qtColor)
{
Qwt3D::RGBA qwt3DColor;
qwt3DColor.r = qtColor.redF(); // 获取红色分量并转换为0.0到1.0的范围
qwt3DColor.g = qtColor.greenF(); // 获取绿色分量并转换为0.0到1.0的范围
qwt3DColor.b = qtColor.blueF(); // 获取蓝色分量并转换为0.0到1.0的范围
qwt3DColor.a = qtColor.alphaF(); // 获取透明度分量并转换为0.0到1.0的范围
return qwt3DColor;
}
使用示例
cpp
track3D *m_track = nullptr;
//初始化航迹图并插入
m_track = new track3D();
ui->tabWidget->insertTab(1, m_track, "3D Track"); // 我插入到tab里的,可以直接创建个界面中
//插入线条
if (m_track->addLine("理论轨迹", 2)) {
for (int i = 1;i < m_data.length() - 1;i ++) {
Triple point(m_data.at(i).Longitude, m_data.at(i).Latitude, m_data.at(i).Altitude);
m_track->addData("理论轨迹", point);
qDebug() << m_data.at(i).Longitude << " " << m_data.at(i).Latitude << " " << m_data.at(i).Altitude;
}
m_track->replot();
}
照着这个大佬配置长沙红胖子Qt
直接使用q3d
q3dsurfacewidget.h
cpp
#ifndef Q3DSURFACEWIDGET_H
#define Q3DSURFACEWIDGET_H
#include <QMainWindow>
#include <Q3dscatter>
#include <q3dsurface.h>
#include <q3dtheme.h>
#include <qsurface3dseries.h>
#include <qvector3d.h>
#include <QtDataVisualization>
#include <qabstract3dinputhandler.h>
#include <qmap.h>
using namespace QtDataVisualization;
QT_BEGIN_NAMESPACE
namespace Ui { class Q3dSurfaceWidget; }
QT_END_NAMESPACE
class Q3dSurfaceWidget : public QMainWindow
{
Q_OBJECT
public:
Q3dSurfaceWidget(QWidget *parent = nullptr);
~Q3dSurfaceWidget();
void drawPoint(QString linename,QVector<QVector3D> points);
private:
Ui::Q3dSurfaceWidget *ui;
Q3DScatter *m_3Dgraph;
void initControl();
//理论数据
QtDataVisualization::QScatter3DSeries *m_Theory;//散点类型
//实际数据
QtDataVisualization::QScatter3DSeries *m_Actual;//散点类型
//数据代理
QScatterDataProxy *proxy_Theory = nullptr;
QScatterDataProxy *proxy_Actual = nullptr;
};
#endif // Q3DSURFACEWIDGET_H
q3dsurfacewidget.cpp
cpp
#include "q3dsurfacewidget.h"
#include "ui_q3dsurfacewidget.h"
#include "QSplitter"
#include <QDebug>
#include <QDateTime>
Q3dSurfaceWidget::Q3dSurfaceWidget(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Q3dSurfaceWidget)
{
ui->setupUi(this);
this->setWindowTitle("UDP_Reciver");
initControl();
QSplitter *splitter = new QSplitter(Qt::Horizontal);
splitter->addWidget(ui->widget);
this->setCentralWidget(splitter);
}
Q3dSurfaceWidget::~Q3dSurfaceWidget()
{
delete ui;
}
void Q3dSurfaceWidget::initControl()
{
m_3Dgraph = new Q3DScatter();
ui->widget = QWidget::createWindowContainer(m_3Dgraph);
proxy_Theory = new QScatterDataProxy(); //数据代理
proxy_Actual = new QScatterDataProxy(); //数据代理
m_Theory = new QScatter3DSeries(proxy_Theory);//创建序列
m_Theory->setMeshSmooth(true);
m_3Dgraph->addSeries(m_Theory);
m_Actual = new QScatter3DSeries(proxy_Actual);//创建序列
m_Actual->setMeshSmooth(true);
m_3Dgraph->addSeries(m_Actual);
//创建坐标轴
m_3Dgraph->axisX()->setTitle("经度");
m_3Dgraph->axisX()->setTitleVisible(true);
m_3Dgraph->axisX()->setRange(0,100);
m_3Dgraph->axisY()->setTitle("海拔");
m_3Dgraph->axisY()->setTitleVisible(true);
m_3Dgraph->axisY()->setRange(0,100);
m_3Dgraph->axisZ()->setTitle("纬度");
m_3Dgraph->axisZ()->setTitleVisible(true);
m_3Dgraph->axisZ()->setRange(0,100);
m_3Dgraph->activeTheme()->setLabelBackgroundEnabled(false);
m_3Dgraph->activeTheme()->setBackgroundColor(QColor(90,90,90));//设置背景色
m_Theory->setMesh(QAbstract3DSeries::MeshMinimal);//数据点为圆球
m_Theory->setSingleHighlightColor(QColor(0,0,255));//设置点选中时的高亮颜色
m_Theory->setBaseColor(QColor(0,255,255));//设置点的颜色
m_Theory->setItemSize(0.05);//设置点的大小
m_Actual->setMesh(QAbstract3DSeries::MeshMinimal);//数据点为圆球
m_Actual->setSingleHighlightColor(QColor(0,0,0));//设置点选中时的高亮颜色
m_Actual->setBaseColor(QColor(255,0,255));//设置点的颜色
m_Actual->setItemSize(0.05);//设置点的大小
}
void Q3dSurfaceWidget::drawPoint(QString linename,QVector<QVector3D> points)
{
if (points.isEmpty()) return; // 如果点集合为空,直接返回
// 计算所有点的最小值和最大值
float minX = points[0].x(), minY = points[0].y(), minZ = points[0].z();
float maxX = points[0].x(), maxY = points[0].y(), maxZ = points[0].z();
for (const auto &point : points) {
minX = qMin(minX, point.x());
minY = qMin(minY, point.y());
minZ = qMin(minZ, point.z());
maxX = qMax(maxX, point.x());
maxY = qMax(maxY, point.y());
maxZ = qMax(maxZ, point.z());
}
// 动态调整坐标轴范围
m_3Dgraph->axisX()->setRange(minX - 1, maxX + 1);
m_3Dgraph->axisY()->setRange(minY - 1, maxY + 1);
m_3Dgraph->axisZ()->setRange(minZ - 1, maxZ + 1);
if(linename == "理论")
{
QScatterDataArray *dataArray = new QScatterDataArray();
dataArray->resize(points.count());
QScatterDataItem *ptrToDataArray = &dataArray->first();
for (int i = 0;i < points.count();i++ )
{
ptrToDataArray->setPosition(points[i]);
ptrToDataArray++;
}
m_Theory->dataProxy()->resetArray(dataArray);//更新散点
}
if(linename == "实际")
{
QScatterDataArray *dataArray = new QScatterDataArray();
dataArray->resize(points.count());
QScatterDataItem *ptrToDataArray = &dataArray->first();
for (int i = 0;i < points.count();i++ )
{
ptrToDataArray->setPosition(points[i]);
ptrToDataArray++;
}
m_Actual->dataProxy()->resetArray(dataArray);//更新散点
}
}
使用示例
cpp
Q3dSurfaceWidget *m_track = nullptr;
//初始化航迹图并插入
m_track = new Q3dSurfaceWidget();
ui->tabWidget->insertTab(1, m_track, "3D Track"); // 在第二个位置(索引为1)插入标签页
//我这边用定时器每十毫秒加个点,也能用for
//初始化时钟
if(timer_show == nullptr)
{
timer_show = new QTimer();
//开始跑
connect(timer_show, &QTimer::timeout, [=]() {
if(i < m_data.length() -1)
{
if(isTheory == 1)
{
m_points_2.append(QVector3D(m_data.at(i).TargetLongitude,m_data.at(i).TargetAltitude,m_data.at(i).TargetLatitude));
m_track->drawPoint("理论",m_points_2);
}
if(isActual == 1)
{
m_points.append(QVector3D(m_data.at(i).Longitude,m_data.at(i).Altitude,m_data.at(i).Latitude));
m_track->drawPoint("实际",m_points);
}
i++;
}
});
timer_show->start(10);
}
计算经纬高两个点之间的距离
cpp
//计算经纬高距离
//Haversine公式 然后当成三角形计算斜边
QVector<datacsv> m_data;
struct datacsv
{
//时间戳
QString TimeStamp;
//经度
double Longitude;
//纬度
double Latitude;
//海拔
double Altitude;
//目标经度
double TargetLongitude;
//目标纬度
double TargetLatitude;
//目标海拔
double TargetAltitude;
};
double a = pow(sin((m_data.at(i).Latitude - m_data.at(i).TargetLatitude) / 100000 / 2 * 3.1415926535 / 180) ,2) + cos(m_data.at(i).Latitude / 100000 * 3.1415926535 / 180) * cos(m_data.at(i).TargetLatitude / 100000 * 3.1415926535 / 180) * pow(sin((m_data.at(i).Longitude - m_data.at(i).TargetLongitude) / 100000 / 2 * 3.1415926535 / 180) ,2);
double d = 2 * m_R * atan(sqrt(a / (1 - a)));
double d_2 = abs(m_data.at(i).Altitude - m_data.at(i).TargetAltitude);
double result = sqrt(d * d + d_2 * d_2);
//restlt就是最后结果