详解QtLocation(三):QGeoMap源码解析
时间: 2018-12-23来源:OSCHINA
前景提要
「深度学习福利」大神带你进阶工程师,立即查看>>>
写在前面
本系列的 上一篇 已经提到了,打开QtLocation的源码工程,maps目录下组织着围绕QGeoMap这个核心类型的一个基本地图框架,其中QGeoTiledMap(继承自QGeoMap)是基于这个框架对瓦片地图的一个实现;而declarativemaps目录中的核心类型是QDeclarativeGeoMap,它对QGeoMap进行封装并注册为QML类型,并提供了地图要素(它这里称为MapItem)的渲染功能。从QDeclarativeGeoMap的名称我们可以看到QQuick1的QtDeclarative模块的烙印,但这个模块在QQuick2的时代已经过时了。
相信不少人在第一次看到Qt源码的时候很蛋疼,看到其中的Private类和诸如Q_D、Q_Q等宏时,一开始一头雾水;特别是你如果像我一样,一开始就直奔QtLocation模块的源码则会有更加深刻的体会。maps目录下几乎所有类型是私有的,这些类型的头文件末尾接着一个“_p”,很多类型还有一个同名的头文件,其末尾接着一个“_p_p”。每打开一个私有类的头文件都有一段醒目的注释: // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. //
这些私有类究竟有何作用呢?《 The Beauty of Qt 1: D-Pointer / Private Implementation 》这篇博文可以带我们一探究竟。从该博文摘取如下示例:
假如不使用D指针和Private类,那么我们可能会有如下的一个类型声明: class MyClass { public: MyClass(); ~MyClass(); private: int myVar; };
显然,这个头文件中的私有变量myVar被其他文件#include之后是会暴露的,很多时候我们不希望引用头文件的彼方对于我们私有的成员和可能的内部实现知道得太清楚;同时这里的声明过于明确,以至于后来如果我们希望增加某些私有变量,就必须重新定义这个头文件。
于是,我们换种方式,定义一个指针d_ptr指向Private类,然后用Q_DECLARE_PRIVATE宏来定义一些辅助函数和声明友元类: class MyClassPrivate; class MyClass { public: MyClass(); ~MyClass(); private: MyClassPrivate * const d_ptr; Q_DECLARE_PRIVATE(MyClass); };
简单地说,这样的好处在于 保证代码的二进制兼容性: 如果一个程序和某个库的某个版本动态链接,并且不需要重新编译,则称这个库是具有二进制兼容性。为了做到这一点,Qt中大量使用D指针和Private类的设计模式,不惜用近乎双倍的代码量,将私有变量包裹到一个Private类中,然后在公有类头文件中用一个私有的D指针指向对应的Private类。
然后,对这个Private类进行实现: class MyClassPrivate { public: MyClassPrivate(MyClass *parent); private: MyClass * const q_ptr; Q_DECLARE_PUBLIC(MyClass); int myVar; };
可以看到公有类中的d_ptr指向私有类,而私有类中的q_ptr则指向公有类;然后用两个宏去实现公有类和私有类之间的相互转换: #define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *> \ (qGetPtrHelper(d_ptr)); } \ inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *> \(qGetPtrHelper(d_ptr)); } \ friend class Class##Private; /////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define Q_DECLARE_PUBLIC(Class) \ inline Class* q_func() { return static_cast<Class *>(q_ptr); } \ inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \ friend class Class;
最后,进一步用两个宏对d_ptr和q_ptr这两个指针的访问方法进行简化: #define Q_D(Class) Class##Private * const d = d_func() #define Q_Q(Class) Class * const q = q_func()
如此一来,我们就可以在公有类中用Q_D宏去得到d这个私有类的指针,并用Q_Q宏去得到q这个公有类的指针了(注意:公有类中声明了Q_DECLARE_PRIVATE才可以使用Q_D宏,私有类中声明了Q_DECLARE_PUBLIC才可以使用Q_Q宏)。我们可称其为D-Q机制。
从QGeoMap窥探D-Q 设计之美
回到QtLocation,我们来看看QGeoMap类型。
QGeoMap公有类的定义在qgeomap_p.h文件中,私有类的定义在qgeomap_p_p.h文件中,它们两个的具体实现都在qgeomap.cpp文件中。QGeoMap继承自QObject,则继承了其对象树组织、信号槽以及D-Q机制。QGeoMap公有类定义如下(有省略,详见源码): class QGeoMappingManagerEngine; class QGeoMapPrivate; class QGeoCoordinate; class QSGNode; class QQuickWindow; class QGeoMapParameter; class QDeclarativeGeoMapItemBase; class Q_LOCATION_PRIVATE_EXPORT QGeoMap : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(QGeoMap) ... public: enum ItemType { NoItem = 0x0000, MapRectangle = 0x0001, MapCircle = 0x0002, MapPolyline = 0x0004, MapPolygon = 0x0008, MapQuickItem = 0x0010, CustomMapItem = 0x8000 }; Q_DECLARE_FLAGS(ItemTypes, ItemType) ... virtual ~QGeoMap(); void setViewportSize(const QSize& viewportSize); QSize viewportSize() const; int viewportWidth() const; int viewportHeight() const; QGeoCameraData cameraData() const; ... void setActiveMapType(const QGeoMapType mapType); const QGeoMapType activeMapType() const; ... const QGeoProjection &geoProjection() const; virtual void prefetchData(); virtual void clearData(); void addParameter(QGeoMapParameter *param); void removeParameter(QGeoMapParameter *param); void clearParameters(); ItemTypes supportedMapItemTypes() const; void addMapItem(QDeclarativeGeoMapItemBase *item); void removeMapItem(QDeclarativeGeoMapItemBase *item); void clearMapItems(); ... ... virtual bool setBearing(qreal bearing, const QGeoCoordinate &coordinate); virtual QGeoShape visibleRegion() const; ... virtual bool fitViewportToGeoRectangle(const QGeoRectangle &rectangle); ... protected: QGeoMap(QGeoMapPrivate &dd, QObject *parent = 0); void setCameraData(const QGeoCameraData &cameraData); ... virtual QSGNode *updateSceneGraph(QSGNode *node, QQuickWindow *window) = 0; Q_SIGNALS: void cameraDataChanged(const QGeoCameraData &cameraData); void sgNodeChanged(); void activeMapTypeChanged(); ... private: Q_DISABLE_COPY(QGeoMap) friend class QDeclarativeGeoMap; friend class QGeoMapPrivate; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoMap::ItemTypes)
QGeoMap 通过前向声明相关的Class们,组织起地图的基本框架:
1、地图几何要素类型ItemType,如 MapRectangle 、 MapCircle 、 MapPolyline 、 MapPolygon 和 MapQuickItem 等(注意:从这里可以看出QtLocation其实缺乏地理数据的互操作性,其定义的地图几何要素并不遵循 OGC® Standards 的 Simple Feature标准 ,这给GIS专业级的地图应用带来了不便;当然,同样也有非专业的东西最终成为了GIS标准的鲜活例子,那就是谷歌攻城狮们搞的 KML ,因为谷歌地球真的是太amazing了,——这需要QtLocation足够牛,并累积足够影响力才行,相信MapBox的贡献会增大其可能性),其对应类型皆以 QDeclarativeGeoMapItemBase 为基类,并提供 addMapItem 和 removeMapItem 等动态增删接口;2、地图类型 QGeoMapType ,如卫星地图、街道地图和地形图等等;3、镜头和视点,即这里的 QGeoCameraData ,类似于KML中的LookAt元素,插播KML的图类比说明一下:

4、地图投影,即 QGeoProjection 为抽象基类,QtLocation中只继承此类实现了 QGeoProjectionWebMercator 类型(网络墨卡托投影);5、地图参数 QGeoMapParameter 以及 addParameter 和 removeParameter 等动态增删接口,便利地提供了一种动态地从前端将自定义参数传递到后端的机制,这很有用;6、其他如视口viewport(屏幕坐标)、可视地理范围visibleRegion(经纬坐标)等相关属性和接口,不一而足。
QGeoMap 对应的 QGeoMapPrivate 定义如下(有省略,详见源码): class QGeoMappingManagerEngine; class QGeoMap; class QGeoMapParameter; class QDeclarativeGeoMapItemBase; class Q_LOCATION_PRIVATE_EXPORT QGeoMapPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QGeoMap) public: QGeoMapPrivate(QGeoMappingManagerEngine *engine, QGeoProjection *geoProjection); virtual ~QGeoMapPrivate(); const QGeoProjection *geoProjection() const; ... ... protected: /* Hooks into the actual map implementations */ virtual void addParameter(QGeoMapParameter *param); virtual void removeParameter(QGeoMapParameter *param); virtual QGeoMap::ItemTypes supportedMapItemTypes() const; virtual void addMapItem(QDeclarativeGeoMapItemBase *item); virtual void removeMapItem(QDeclarativeGeoMapItemBase *item); ... virtual void changeViewportSize(const QSize &size) = 0; // called by QGeoMap::setSize() virtual void changeCameraData(const QGeoCameraData &oldCameraData) = 0; // called by QGeoMap::setCameraData() virtual void changeActiveMapType(const QGeoMapType mapType) = 0; // called by QGeoMap::setActiveMapType() virtual double mapWidth() const; virtual double mapHeight() const; ... virtual double maximumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const; protected: QSize m_viewportSize; QGeoProjection *m_geoProjection; QPointer<QGeoMappingManagerEngine> m_engine; QGeoCameraData m_cameraData; QGeoMapType m_activeMapType; QList<QGeoMapParameter *> m_mapParameters; QList<QDeclarativeGeoMapItemBase *> m_mapItems; ... mutable double m_maximumViewportLatitude = 0; };
对比可见其D-Q机制遵循我 写在前面 的内容,但却不见d_ptr和d_ptr指针,这是为何呢?因为只要你的公有类继承于QObject,对应的私有类继承于QObjectPrivate,再分别声明了Q_DECLARE_PRIVATE和Q_DECLARE_PUBLIC,就可以通过Q_D和Q_Q从基类中的d_ptr和d_ptr指针强制转换为当前子类的d_ptr和d_ptr指针。如此一来,在公有类中调用私有类的具体实现就游刃有余了,例如: void QGeoMap::setViewportSize(const QSize& size) { Q_D(QGeoMap); if (size == d->m_viewportSize) return; d->m_viewportSize = size; d->m_geoProjection->setViewportSize(size); d->changeViewportSize(size); }

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

热门排行