怎样快速开发一个中国地图组件?
大家好,最近,感觉到地图组件受到了广泛的关注。这是因为通过它可以直观的了解到数据的地理分布,那么怎样利用Qt快速开发一个地图组件呢?
说到用Qt开发地图组件,大家最先想到的就是利用图片和热点的方式实现。比如,利用贴图的方式把中国地图的图片显示出来,然后在省会的位置标注一个圆形图标,当用户单击这个图标的时候,可以弹出相关的信息或者进行进一步的操作(比如下钻)。但是利用图片的方式存在这么几个问题。
1、利用Qt显示多幅图片的时候,会导致CPU使用率偏高,尤其是尺寸稍大的图片。
2、必须为每个省会进行标注,操作繁琐。
其实,有另外一个思路,就是利用QGraphics Scene-View-Item机制来实现。比如,把各个省份当成一个图元,通过读取svg地图中的数据创建各省的图元,当单击各省的时候图元可以发出消息(消息中带有省份信息),应用截获到消息后可以进行进一步的处理。
下面介绍具体步骤。
第一步 下载含有中国地图的svg文件,并放到指定目录。该目录应与程序中设定的目录一致。
第二步 设计、实现地图图元类QExtMapItem,该图元使用svg文件中的数据。
为了节省篇幅,此处仅列出QExtMapItem的部分关键接口。该类派生自QGraphicsObject,目的是为了能重写悬浮事件,以便发出悬浮消息供应用层使用。其中setRect()接口用来设置图元的位置、尺寸信息。setPath()用来设置图元的形状信息,该信息来自svg地图。setInformation()用来设置图元的业务数据,以便应用层可以进行相关处理,比如直接弹出界面显示该信息或者利用该信息到数据库中查询后进行展示。
// src/mapitem/qext_mapitem.h
class QExtMapItem : public QGraphicsObject {
public:
QExtMapItem(QGraphicsItem *parent = nullptr);
~QExtMapItem(){;}
void setRect(const QRectF &rct);
QRectF rect() const {
return m_rect;
}
void setPath(const QPainterPath &path) {
m_path = path;
}
QPainterPath path() const {
return m_path;
}
void setInformation(const QString &strInfo) {
m_strInfo = strInfo;
}
QString information() const {
return m_strInfo;
}
Q_SIGNALS:
void sig_clicked();
protected:
virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
...
};
QExtMapItem的实现代码如下。
// src/mapitem/qext_mapitem.cpp
QExtMapItem::QExtMapItem(QGraphicsItem *parent) :
QGraphicsObject(parent), m_bHover(false) {
setAcceptHoverEvents(true);
setFlags(ItemIsSelectable);
}
void QExtMapItem::setRect(const QRectF &rct) {
m_rect = rct;
}
void QExtMapItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) {
if (hover()){
painter->setBrush(m_hoverBrush);
}
else {
painter->setBrush(m_staticBrush);
}
painter->drawPath(m_path);
return;
}
QRectF QExtMapItem::boundingRect() const {
return m_rect;
}
QPainterPath QExtMapItem::shape() const {
return m_path;
}
void QExtMapItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
QGraphicsObject::hoverEnterEvent(event);
setHover(true);
}
void QExtMapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
QGraphicsObject::hoverLeaveEvent(event);
setHover(false);
}
void QExtMapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsObject::mousePressEvent(event);
}
void QExtMapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsObject::mouseReleaseEvent(event);
emit sig_clicked();
}
第三步 编写CParseSvg解析svg文件,构建地图图元。
为了进行解耦,设计CParseSvg类用来解析svg文件。解析svg的代码并不复杂,需要注意的是本案例采用了两遍解析,第一遍计算地图左上角位置,第二遍才构建真正的地图图元。第一遍解析的目的在于将地图偏移至期望的位置。本案例利用QDom进行解析。因篇幅所限,不在此展示CParseSvg的实现。地图的样例如下,其中path对应某个省/自治区的地图数据。案例代码中带有参考的地图样例。
第四步,编写自定义视图类CCustomView ,用来模拟对业务信息的处理,比如单击地图上某个省份时,弹出相关信息。视图类的关键代码如下。
// src/mapitem/customview.cpp
CCustomView::CCustomView(QWidget* parent) :
...
initialize();
}
void CCustomView::initialize() {
QGraphicsScene * pScene = new QGraphicsScene();
setScene(pScene);
m_backgroundColor = Qt::darkBlue;
setBackgroundBrush(m_backgroundColor);}
void CCustomView::slot_itemClicked() {
QExtMapItem *pItem = dynamic_cast(sender());
if (NULL == pItem) {
return;
}
QMessageBox::information(this, "", pItem->information());
}
第五步,在main()函数中构建视图对象,解析svg文件,生成地图。
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QDialog dialog;
//dialog.setWindowFlags(Qt::CustomizeWindowHint |Qt::FramelessWindowHint);
dialog.setMouseTracking(true);
// 构建视图
CCustomView* pView = new CCustomView(&dialog);
CParseSvg parseSvg;
{
QString strPath = QCoreApplication::applicationDirPath();
strPath = ns_train::getPath(strPath);
if (!strPath.endsWith("/")) {
strPath += "/";
}
QString strSvgFileName = strPath + "chinaLow.svg"; // 程序运行目录下的svg文件
qDebug() << strSvgFileName;
parseSvg.parseSvgFile(strSvgFileName, pView);
QString strTitle = QString::fromLocal8Bit("全国地图");
parseSvg.addTitle(strTitle, pView);
}
QRectF rctView = pView->rect();
// 将视图添加到布局
gridLayout->addWidget(pView, 0, 0);
dialog.exec();
return 0;
}
第六步 构建程序,把svg地图放在程序运行目录,启动程序。