QTransform使用心得(三)--电子地图开发中的应用 目录1 墨卡托投影2 QTransform2.1 缩放正面例子2.2 先平移再缩放再平移正面例子代码与效果解说2.3 先缩放再平移(反面例子)总结归纳我们平日使用某德、某度地图都要做缩放、平移操作。本文介绍使用Qt开发电子地图并实现缩放、漫游效果的理论基础。1 墨卡托投影我们都有剥桔子皮的经历。把球形的皮剥下来是无法将其绝对展平的。同样用二维平面刻画地球表面将不可避免的带来失真。从大航海时代以来许多数学家、地图学家都在寻找一种将3维球面投影到2维平面的方法尽量将失真带来的影响降低到最小。今天的某歌、某德、某度地图都使用墨卡托投影或者其变种。在墨卡托投影里每个经纬度都与一个二维平面的(x,y)坐标一一对应。以天安门为例上面的单位是米。我们常用的电子地图就是把各个线条如国界线、道路、河流等、点城市等的墨卡托坐标缩放到屏幕上展示的。2 QTransform二维平面上任何一个平移、伸缩、旋转的操作或者其组合都可以用3×3矩阵表述。在Qt里由专用的类QTransform来描述这个3×3矩阵。接下来我们举3个例子然后从这3个例子里归纳漫游、缩放的实现步骤。为了简化问题这里我们不使用地球作为绘制对象而是使用一个2000*2000的图案把它投影到一个1000*1000的窗口里。2.1 缩放正面例子代码#include mainwindow.h #include QDebug #include QPainter MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { //框一个2000乘2000的框 m_path.addRect(0, 0, 2000, 2000); //在第一象限画个十字 m_path.moveTo(1250, 500); m_path.lineTo(1750, 500); m_path.moveTo(1500, 250); m_path.lineTo(1500, 750); //窗体固定1000x1000 setFixedSize(1000, 1000); } MainWindow::~MainWindow() { } void MainWindow::paintEvent(QPaintEvent *event) { QTransform trans; qDebug()trans; trans.scale(0.5,0.5); QPainter qp(this); qp.setTransform(trans); QPen pen; pen.setWidth(10); pen.setColor(Qt::red); qp.setPen(pen); qp.drawPath(m_path); }上图左边显示图案全貌红色正方形边框第一象限中央有一个红色十字。从右边的代码可以看到我们定义了QTransform并调用scale(0.5,0.5)把原始图像缩小1/2所以2000*2000的原始图像才能装进1000*1000的窗体里面。上面是最简单的缩放示例只调用scale方法。这样看到的是图案全貌。接下来我们想看一下某个局部的情况见下面的例子。2.2 先平移再缩放再平移正面例子接下来我们还是想把原始图案缩小1/2但是还要让红色十字位于窗体中心。代码与效果代码#include mainwindow.h #include QDebug #include QPainter namespace { const int cnst_iWndSize 1000; } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { //框一个2000乘2000的框 m_path.addRect(0, 0, 2000, 2000); //在第一象限画个十字 m_path.moveTo(1250, 500); m_path.lineTo(1750, 500); m_path.moveTo(1500, 250); m_path.lineTo(1500, 750); //窗体固定1000x1000 setFixedSize(cnst_iWndSize, cnst_iWndSize); } MainWindow::~MainWindow() { } void MainWindow::paintEvent(QPaintEvent *event) { QTransform trans; qDebug()trans; //最后把原点移动到窗体中央 trans.translate(cnst_iWndSize/2, cnst_iWndSize/2); qDebug()trans; //然后缩放 trans.scale(0.5,0.5); qDebug()trans; //首先把十字(1500, 500) 移动到窗体原点(0, 0) trans.translate(0 - 1500, 0 - 500); qDebug()trans; QPainter qp(this); qp.setTransform(trans); QPen pen; pen.setWidth(10); pen.setColor(Qt::red); qp.setPen(pen); qp.drawPath(m_path); }解说在前面QTransform使用心得一--解析QTransform的用法_qt qtransform 根据坐标对如何反算-CSDN博客我们说过QTransform的一系列操作是倒序。如果你先平移再缩放那么就要先调用QTransform::scale然后再调用QTransform::translate。在本示例里面我们先把十字中心平移到(0,0)的位置。由于十字的坐标是(1500, 500)所以平移的量就是(-1500, -500)。然后缩放scale(0.5,0.5)最后再把原点(0,0)平移到窗体中心(500,500) trans.translate(cnst_iWndSize/2, cnst_iWndSize/2);还有不要忘记上面的操作在代码里是反序的。经过第一步把十字中心平移到(0,0)的位置图案仍然是2000*2000只是位置变了。第二步缩放。记住缩放以(0,0)为不动点。所以缩放前后十字中心的位置还是(0,0)只是它周围图案离自己的距离减少50%。第三步还是平移。经过了第二步图案尺寸已经符合窗体大小了。但是十字中心不在窗体中心(500,500)。所以再操作一次 trans.translate(cnst_iWndSize/2, cnst_iWndSize/2)完成。2.3 先缩放再平移(反面例子)读者很容易走入一个误区我能不能直接把十字移动到窗口中央(500,500)然后缩放不可以。前面说了缩放以(0,0)为不动点。如果你先把十字移动到窗口中央(500,500)随后缩放将再一次改变十字的坐标反而不对了。看下面的例子#include mainwindow.h #include QDebug #include QPainter namespace { const int cnst_iWndSize 1000; } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { //框一个2000乘2000的框 m_path.addRect(0, 0, 2000, 2000); //在第一象限画个十字 m_path.moveTo(1250, 500); m_path.lineTo(1750, 500); m_path.moveTo(1500, 250); m_path.lineTo(1500, 750); //窗体固定1000x1000 setFixedSize(cnst_iWndSize, cnst_iWndSize); } MainWindow::~MainWindow() { } void MainWindow::paintEvent(QPaintEvent *event) { QTransform trans; qDebug()trans; //然后缩放 trans.scale(0.5,0.5); qDebug()trans; //把十字(1500, 500) 移动到窗体中心(500, 500) trans.translate(cnst_iWndSize/2 - 1500, cnst_iWndSize/2 - 500); qDebug()trans; QPainter qp(this); qp.setTransform(trans); QPen pen; pen.setWidth(10); pen.setColor(Qt::red); qp.setPen(pen); qp.drawPath(m_path); }总结归纳在绘制电子地图时我们通常要定义一个参考点--在本示例里面就是十字中心。我们既要知道参考点的绝对坐标墨卡托坐标墨卡托坐标与经纬度之间有现成的转换公式所以知道经纬度也可以也要知道参考点在窗体里的坐标。只要知道了这两者就能确定从墨卡托坐标系到窗体坐标系的转换矩阵(QTransform)。然后使用QPainter::setTransform()方法。最后使用QPainter绘制以墨卡托坐标为单位的原始图元线条、多边形等即可不必再针对每个图元都做一次墨卡托到屏幕的变换。具体到如何确定QTransform的问题上1 先把参考点移动到(0,0)点2 缩放3 再把参考点从(0,0)移动到对应的屏幕坐标。比方说你以北京为参考点。首先定义QTransform,然后调用QTransform::translate()把北京的墨卡托坐标减去。然后根据你需要的比例尺做缩放。最后再一次调用QTransform::translate()把北京从窗体的(0,0)移动到你想要它出现的屏幕坐标上。在实际的电子地图操作里软件启动后每个城市在窗体里都会有一个固定位置所以你作为软件开发者肯定知道起始位置。这个起始位置就是第1步里平移量的相反数。你开始漫游就会改变城市在屏幕的坐标。这个改变量你是知道的(通过QWidget::mouseMoveEvent获取)。改变后的屏幕坐标你当然也就知道这个新的屏幕坐标就是你在第三步里的移动目的位置。