需求分析:
要获取一个地图,
需要ip
需要根据ip查询经纬度
根据经纬度查询地图
另外一条线是根据输入的地址 查询ip 根据查询到的ip查地图‘
最后,要渲染地图
上面这这些动作,要进行http查询:
为此要有三个QNetworkAccessManager对象:
QNetworkAccessManager *m_ipManager; // 用于ip管理
QNetworkAccessManager *m_locManager; // 用于查询位置
//网络管理对象 发送地图图片
QNetworkAccessManager *m_mapManager; //查询地图
//响应对象 接收地图图片
QNetworkReply *m_mapReply=NULL; //响应
所有的查询动作当然能顺序执行:
如先查询ip
等待获取IP返回结果
如果IP返回了,发起根据IP查询经纬度的请求
等待经纬度查询结果,如果获得的返回,则进一步根据返回的经纬度查询地图数据
等待地图数据查询结果,把结果写入文件
从文件中读取数据,渲染到weigt上
但是 如果其中某个环节网络断了 或者百度服务器宕机了,系统就会卡死。
按上面步骤,网络请求是同步顺序,执行来的,用户界面可能会阻塞,用户体验欠佳。
为此我们引入槽函数机制!让网络请求是异步进行,用户界面不会阻塞,这提供了更好的用户体验!
在构造函数中,添加如下代码:
Form1::Form1(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Form1)
{
ui->setupUi(this);
//new一个网络管理对象
m_ipManager = new QNetworkAccessManager(this); // net connection tool ,duty is to connect to the server
//declare connect relationship ,when receive the response ,call the slot function,response is finished signal
connect(m_ipManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onGetIp(QNetworkReply*)));
m_locManager=new QNetworkAccessManager(this); //net connection tool ,duty is to connect to the server for location
//declare connect relationship ,when receive the response ,call the slot function,response is finished signal
connect(m_locManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(onGetCurrentLoc(QNetworkReply*)));
m_mapManager=new QNetworkAccessManager(this); //net connection tool ,duty is to connect to the server for map query map
connect(m_mapManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(onSendMapRequest()));
}
和上面connect函数配套的也有三个槽函数,当网络请求有响应的时候,执行响应的槽函数。
配套的三个槽函数,在头文件中 declare如下图:
private slots:
//处理获取外网ip请求的 槽
void onGetIp(QNetworkReply*);
//根据ip获取经纬度 处理服务器响应内容
void onGetCurrentLoc(QNetworkReply*);
//处理服务器返回地图图片
void onSendMapRequest();
这样,所有网络查询与处理事异步进行的。
让我们从查询Ip地址起步:
首先通过QNetworkAccessManager 一个实例对象 m_ipManager发起一个request请求。
void Form1::getIp()
{
// by m_ipManager query ip
QUrl url("http://httpbin.org/ip"); //declare the url
QNetworkRequest request(url); //declare the request
m_ipManager->get(request); //send the request
}
这个请求的响应?
嗯嗯,当然在槽函数当中了~~~ 窗体的构造函数中不是“声明”了一个“连接”吗
connect(m_ipManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onGetIp(QNetworkReply*)));
当网络请求有相应的回来的时候,执行槽函数onGetIp,
在这个onGetIp函数中,我们取得服务端返回数据,从中解析出ip地址,进一步的, 我们会根据ip地址查询经纬度数据。
void Form1::onGetIp(QNetworkReply *reply)
{
//get the ip address,by ip value query the location
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsonObj = jsonDoc.object();
currentIp=jsonObj.value("origin").toString();
qDebug() << "Current IP:" << currentIp;
//by ip value query the location
getLocation();
}
void Form1::getLocation()
{
//by ip address ,query the location
QUrl url("http://api.map.baidu.com/location/ip?ip="+currentIp+"&ak="+ak);
QNetworkRequest request(url);
m_locManager->get(request);
}
上面查询经纬度数据的时候,用的是QNetworkAccessManager 一个实例对象 m_locManager,当这个对象发起request请求后,它的工作就算完成了。
结果?结果当然是另外一个槽函数来处理了。
connect(m_locManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(onGetCurrentLoc(QNetworkReply*)));
onGetCurrentLoc这个槽函数中,获取上一步request查询结果,从结果中解析出经纬度,根据经纬度的数据,调用查询地图数据的函数。
完整代码如下:
//json数据的一个样本如下
// {
// "address": "CN|辽宁省|大连市|None|None|100|75",
// "content": {
// "address_detail": {
// "province": "辽宁省",
// "city": "大连市",
// "district": "",
// "street": "",
// "street_number": "",
// "city_code": 167,
// "adcode": "210200"
// },
// "address": "辽宁省大连市",
// "point": {
// "x": "13539005.4",
// "y": "4682974.61"
// }
// },
// "status": 0
// }
void Form1::onGetCurrentLoc(QNetworkReply * reply)
{
//根据经纬度发起查询地图
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
//parse the json data,get the location data from the json data
QJsonObject jsonObj = jsonDoc.object();
QJsonObject contentObj = jsonObj.value("content").toObject();
QJsonObject pointObj = contentObj.value("point").toObject();
m_lng = pointObj.value("x").toString().toDouble();
m_lat = pointObj.value("y").toString().toDouble();
qDebug() << "经度:" << m_lng << "纬度:" << m_lat;
//根据经纬度发起查询地图请求
//发起查询地图请求
sendMapRequest();
}
到这一步,我们可以先测试一下:~~~~~~~~~~~~
看上去一切正常!
其中,sendMapRequest,当然是用一个对象发起一个请求,结果同样是在对应的槽函数当中处理
sendMapRequest完整代码如下:
void Form1::sendMapRequest()
{
//by location query the map, use m_mapManager
QString url="http://api.map.baidu.com/staticimage/v2?ak="+ak+"¢er="+QString::number(m_lng)+","+QString::number(m_lat)+"&width=1024&height=768&zoom="+QString::number(m_zoom);
QNetworkRequest request(url);
m_mapManager->get(request);
}
好了吗?且慢,网络查询获取的地图数据,也只能是应答对象reply,给出完成型号的时候,才能进行,就是说,构造函数中 connect(m_mapManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(onSendMapRequest())); 不是很合适,那就改造它,
构造函数相关代码改造为:
Form1::Form1(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Form1)
{
ui->setupUi(this);
//new一个网络管理对象
m_ipManager = new QNetworkAccessManager(this); // net connection tool ,duty is to connect to the server
//declare connect relationship ,when receive the response ,call the slot function,response is finished signal
connect(m_ipManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onGetIp(QNetworkReply*)));
m_locManager=new QNetworkAccessManager(this); //net connection tool ,duty is to connect to the server for location
//declare connect relationship ,when receive the response ,call the slot function,response is finished signal
connect(m_locManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(onGetCurrentLoc(QNetworkReply*)));
m_mapManager=new QNetworkAccessManager(this);
//注释掉下面的行 只有收到reply发出的准备好信号 我们才能读取数据,保存到文件当中
// connect(m_mapManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(onSendMapRequest()));
}
只有reply发出准备好可以读数据的信号后,才能,触发对应的槽函数:由于读取文件数据是个昂贵的资源,所以我们要
void MainWindow::sendMapRequest(){
//how
//断调前一次请求
if(m_mapReply!=NULL){
m_mapReply->disconnect();
//断掉事件连接
disconnect(m_mapReply,&QIODevice::readyRead,this,&MainWindow::onSendMapRequest);
}
//开始新的请求
//url
//请求地址
QString host= "http://api.map.baidu.com/staticimage/v2";
//请求参数
QString query_str=QString("ak=%1&width=512&height=256¢er=%2,%3&zoom=%4")
.arg(ak).arg(m_lng).arg(m_lat).arg(m_zoom);
QUrl url(host+"?"+query_str);
qDebug()<<host+"?"+query_str<<endl;
QNetworkRequest request(url);
//此处与前面的请求不同,等待服务器响应,
m_mapReply= m_mapManager->get(request);
//连接事件,处理服务器返回的 文件流
connect(m_mapReply,&QIODevice::readyRead,this,&MainWindow::onSendMapRequest);
}
获取地图数据是个相对比较昂贵的操作,假如原来有个正在处理的响应呢,当然应该断掉原来的响应啊。所有有了下面的代码:
//断调前一次请求
if(m_mapReply!=NULL){
m_mapReply->disconnect();
//断掉事件连接
disconnect(m_mapReply,&QIODevice::readyRead,this,&MainWindow::onSendMapRequest);
}
回顾上面的代码sendMapRequest中执行的动作为:
断开原有的http连接
断开reply相关的槽函数链接
通过http发起查询地图的连接
建了信号与槽函数间的联系,通过槽函数,处理数据,。
最后一步:如何把读取到的数据存放到文件中,且渲染到weight当中呢?
且听下回分解,明天见!
ps: 测试根据ip查询经纬度的数据:http://api.map.baidu.com/location/ip?ip=42.84.232.111&ak=***********
ps:问题1:qt6 处理图像的基本步骤:
在Qt6中处理图像的基本步骤通常涉及以下几个关键方面,以及所使用的核心工具类。以下是一个简要的描述:
基本步骤
-
创建图像对象:
首先,需要创建一个QImage
或QPixmap
对象来存储或处理图像。QImage
是一个类,用于处理图像数据,支持多种图像格式和色彩深度,包括灰度图像、彩色图像和索引图像。它提供了丰富的图像处理功能,如读取、写入、转换图像格式等。 -
读取图像数据:
使用QImage
的load()
函数从文件、数据流或其他源中读取图像数据。这个函数支持多种图像格式,如PNG、JPEG、BMP等。 -
处理图像:
通过QImage
提供的各种函数对图像进行处理,如缩放、裁剪、旋转、颜色转换等。Qt6还提供了更高级的图像处理功能,如滤镜效果、图像分析等,这些功能可能通过其他类库(如Qt Image Processing模块)实现。 -
显示图像:
将处理后的图像显示在界面上,通常可以通过将QImage
转换为QPixmap
(如果需要的话),然后使用QLabel
、QGraphicsView
等控件进行显示。 -
保存图像:
如果需要,可以将处理后的图像保存回文件或其他存储介质。使用QImage
的save()
函数可以实现这一功能。
用到的工具类
- QImage:用于处理图像数据,包括读取、写入、转换格式和进行基本的图像处理。
- QPixmap:与
QImage
类似,但优化了图像在屏幕上的显示性能。通常用于将图像显示在GUI控件中。 - QPainter:虽然主要用于绘图,但在图像处理过程中也可以用来在图像上绘制图形或文本,实现更复杂的图像处理效果。
- 其他Qt图像处理模块:Qt可能还提供了其他专门的图像处理模块,如Qt Image Processing,这些模块提供了更高级的图像处理功能。
综上所述,Qt6处理图像的基本步骤包括创建图像对象、读取图像数据、处理图像、显示图像和保存图像,而用到的核心工具类主要是QImage
和QPixmap
,辅以QPainter
和其他可能的Qt图像处理模块。
问题2:qt6 用QPainter绘制图像有哪些功能,具体步骤是什么
在Qt6中,QPainter
是用于在各种绘制设备(如窗口、图片等)上进行绘图的强大工具类。使用QPainter
绘制图像时,它提供了丰富的功能,包括但不限于:
-
基本图形绘制:可以绘制点、线、矩形、圆形、椭圆等基本图形,以及更复杂的形状如多边形、扇形等。
-
文本绘制:支持绘制文本,并可以设置文本的字体、颜色、位置等属性。
-
图像绘制:可以直接在绘图设备上绘制图像(如
QImage
、QPixmap
中的图像)。 -
变换:支持坐标变换,如平移、旋转、缩放等,以实现对图形的变换处理。
具体步骤通常包括:
-
创建
QPainter
对象:在绘图事件处理函数(如paintEvent
)中创建QPainter
对象,并指定绘图设备(如this
指针,表示在当前窗口上绘图)。 -
设置绘制属性:根据需要设置画笔(
QPen
)、画刷(QBrush
)、字体(QFont
)等属性。 -
进行绘图操作:使用
QPainter
提供的绘图函数进行绘图,如drawLine
、drawRect
、drawText
等。 -
结束绘图:绘图完成后,
QPainter
对象会自动销毁,或者可以显式调用其end()
方法来结束绘图操作(尽管在自动管理的上下文中通常不需要)。
通过这些步骤,开发者可以在Qt6应用程序中实现丰富的图形绘制功能。
问题3:QPainter 与 QImage QPixmap 的关系是什么
QPainter、QImage和QPixmap在Qt框架中各自扮演着重要的角色,并且它们之间存在着紧密的关系。以下是对这三者之间关系的详细阐述:
1. QPainter
- 功能:QPainter是Qt中用于在绘图设备上进行绘图的类。它提供了丰富的绘图函数,如绘制线条、形状、文本以及图像等。
- 与QImage和QPixmap的关系:QPainter可以作用于QImage和QPixmap上,进行图像的绘制和编辑。这意味着,你可以使用QPainter在QImage或QPixmap对象上绘制图形、文本或其他图像。
2. QImage
- 功能:QImage是Qt中用于处理图像数据的类。它提供了对图像像素的直接访问,允许进行图像的读取、写入、转换以及像素级别的操作。
- 与QPainter的关系:QImage是QPaintDevice的子类,因此QPainter可以直接在其上进行绘制。这使得QImage成为一个非常灵活的绘图表面,可以在其上绘制图形、文本或其他图像。
- 特点:QImage不依赖于底层硬件,提供了独立于平台的图像处理能力。它支持多种图像格式和色彩深度,并允许在多个线程中进行图像的读取和写入操作。
3. QPixmap
- 功能:QPixmap是Qt中专门用于在屏幕上显示图像优化的类。它针对屏幕显示进行了优化,能够提供更好的性能和显示效果。
- 与QPainter的关系:与QImage类似,QPixmap也是QPaintDevice的子类,因此QPainter同样可以在其上进行绘制。但是,由于QPixmap是为屏幕显示而优化的,因此它更适合在GUI应用程序中使用。
- 特点:QPixmap依赖于底层平台的绘制系统,因此在不同的操作系统上可能会有不同的显示效果。此外,由于它针对屏幕显示进行了优化,因此在处理大型图像或进行复杂图像处理时可能会比QImage慢。
4. 关系总结
- 绘图功能:QPainter是绘图工具,可以在QImage和QPixmap上进行绘图操作。
- 图像数据:QImage和QPixmap都是用于处理图像数据的类,但它们的用途和优化方向有所不同。QImage提供了像素级别的访问和操作,而QPixmap则优化了屏幕显示性能。
- 相互转换:QImage和QPixmap之间可以进行相互转换。可以使用QPixmap的
toImage()
函数将其转换为QImage,或者使用QImage的QPixmap::fromImage()
静态函数将其转换为QPixmap。这种转换使得开发者可以根据需要选择最适合当前任务的图像处理类。
综上所述,QPainter、QImage和QPixmap在Qt中共同构成了强大的图像处理和绘图功能体系。它们之间的关系使得开发者可以灵活地选择和使用这些类来满足不同的图像处理和绘图需求。
问题4:不是可以这样比喻 qt6中 QPainter 是画家,QPixmap 是画布 QPen 是画笔 QPainter 这个画家可以使用QPen 这个画笔 在 QPixmap 这个画布上绘制图形
这个比喻非常贴切且易于理解!在Qt6的上下文中,我们可以这样描述这三个类之间的关系:
-
QPainter 确实是“画家”,它负责执行绘制操作,将图形、文本或其他图像绘制到指定的绘图设备(如窗口、图像等)上。QPainter提供了丰富的绘图函数,使得开发者可以像画家一样在绘图设备上进行创作。
-
QPixmap 可以被看作是“画布”,它是专门为屏幕显示而优化的图像表示。QPixmap对象可以作为QPainter的绘图设备,让QPainter在其上进行绘制。就像画家在画布上作画一样,QPainter可以在QPixmap上绘制各种图形和图像。
-
QPen 则是“画笔”,它定义了绘制线条时的样式,包括颜色、宽度、线型(如实线、虚线等)和线帽样式等。通过设置QPen的属性,开发者可以控制QPainter绘制线条时的外观。虽然QPen主要用于绘制线条,但它也间接影响了其他需要线条的图形(如矩形边框、圆形轮廓等)的绘制效果。
因此,你的比喻“QPainter 是画家,QPixmap 是画布,QPen 是画笔,QPainter 这个画家可以使用QPen 这个画笔在QPixmap 这个画布上绘制图形”是非常准确的。这个比喻不仅直观地表达了这三个类之间的关系,还帮助开发者更好地理解它们在Qt6图像处理和绘图功能中的作用。
布局: