这里只实现一个地图映射引擎
1.编译插件
pro文件:
TARGET = GD
QT += location-private positioning-private network
PLUGIN_TYPE = geoservices
PLUGIN_CLASS_NAME = GDProviderFactory
load(qt_plugin)
HEADERS += \
gdproviderfactory.h \
gdqgeotiledmap.h \
gdqgeotiledmappingmanagerengine.h \
gdqgeotiledmapreply.h \
gdqgeotilefetcher.h
SOURCES += \
gdproviderfactory.cpp \
gdqgeotiledmap.cpp \
gdqgeotiledmappingmanagerengine.cpp \
gdqgeotiledmapreply.cpp \
gdqgeotilefetcher.cpp
DISTFILES += \
README.md \
gd_plugin.json
插件类(也是qml调用的入口):
#ifndef GDPROVIDERFACTORY_H
#define GDPROVIDERFACTORY_H
#include <QObject>
#include <QtCore/QObject>
#include <QtLocation/QGeoServiceProviderFactory>
class GDProviderFactory : public QObject,public QGeoServiceProviderFactory
{
Q_OBJECT
Q_INTERFACES(QGeoServiceProviderFactory)
Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0"
FILE "gd_plugin.json")
public:
//只实现一个插件
QGeoMappingManagerEngine *createMappingManagerEngine(const QVariantMap ¶meters,
QGeoServiceProvider::Error *error,
QString *errorString) const;
signals:
};
#endif // GDPROVIDERFACTORY_H
#include "gdproviderfactory.h"
#include "gdqgeotiledmappingmanagerengine.h"
QGeoMappingManagerEngine *GDProviderFactory::createMappingManagerEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const
{
return new GDQGeoTiledMappingManagerEngine(parameters, error, errorString);
}
地图瓦片管理类、设置地图一些属性
#ifndef GDQGEOTILEDMAPPINGMANAGERENGINE_H
#define GDQGEOTILEDMAPPINGMANAGERENGINE_H
#include <QtLocation/QGeoServiceProvider>
#include <QtLocation/private/qgeotiledmappingmanagerengine_p.h>
class GDQGeoTiledMappingManagerEngine : public QGeoTiledMappingManagerEngine
{
Q_OBJECT
public:
GDQGeoTiledMappingManagerEngine(const QVariantMap ¶meters,
QGeoServiceProvider::Error *error,
QString *errorString);
QGeoMap *createMap() override;
private:
QString m_cacheDirectory;
};
#endif // GDQGEOTILEDMAPPINGMANAGERENGINE_H
#include "gdqgeotiledmappingmanagerengine.h"
#include "gdqgeotilefetcher.h"
#include "QtLocation/private/qgeotilespec_p.h"
#include "QtLocation/private/qgeofiletilecache_p.h"
#include "QtLocation/private/qgeocameracapabilities_p.h"
#include "gdqgeotiledmap.h"
GDQGeoTiledMappingManagerEngine::GDQGeoTiledMappingManagerEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString)
:QGeoTiledMappingManagerEngine()
{
Q_UNUSED(error);
Q_UNUSED(errorString);
//地图视角相关设置,对应到 QML Map 类型的属性
QGeoCameraCapabilities capabilities;
capabilities.setMinimumZoomLevel(1.96);
capabilities.setMaximumZoomLevel(20.88);
capabilities.setSupportsBearing(true);
capabilities.setSupportsTilting(true);
capabilities.setMinimumTilt(0);
capabilities.setMaximumTilt(80);
capabilities.setMinimumFieldOfView(20.0);
capabilities.setMaximumFieldOfView(120.0);
capabilities.setOverzoomEnabled(true);
setCameraCapabilities(capabilities);
//瓦片大小
int tile = parameters.value(QStringLiteral("amap.amap.tilesize"), 256).toInt();
setTileSize(QSize(tile, tile));
//支持地图样式
QList<QGeoMapType> types;
//QGeoCameraCapabilities cameraCapabilities;
types << QGeoMapType(QGeoMapType::StreetMap, tr("Road Map"), tr("Normal map view in daylight mode"), false,false, 1, "amap", capabilities);
types << QGeoMapType(QGeoMapType::TerrainMap, tr("Terrain"), tr("Terrain map view in daylight mode"), false, false, 2, "amap", capabilities);
types << QGeoMapType(QGeoMapType::SatelliteMapDay, tr("Satellite"), tr("Satellite map view in daylight mode"), false, false, 3, "amap", capabilities);
types << QGeoMapType(QGeoMapType::HybridMap, tr("Hybrid"), tr("Satellite map view with streets in daylight mode"), false, false, 4, "amap", capabilities);
setSupportedMapTypes(types);
//瓦片获取,默认接口是通过网络请求获取
GDQGeoTileFetcher *fetcher = new GDQGeoTileFetcher(parameters, this, tileSize());
setTileFetcher(fetcher);
if (parameters.contains(QStringLiteral("amap.cachefolder")))
m_cacheDirectory = parameters.value(QStringLiteral("amap.cachefolder")).toString().toLatin1();
else
m_cacheDirectory = QAbstractGeoTileCache::baseCacheDirectory() + QLatin1String("amap");
QAbstractGeoTileCache *tileCache = new QGeoFileTileCache(m_cacheDirectory);
tileCache->setMaxDiskUsage(100 * 1024 * 1024);
setTileCache(tileCache);
*error = QGeoServiceProvider::NoError;
errorString->clear();
}
QGeoMap *GDQGeoTiledMappingManagerEngine::createMap()
{
return new GDQGeoTiledMap(this);
}
瓦片获取类:
#ifndef GDQGEOTILEFETCHER_H
#define GDQGEOTILEFETCHER_H
#include <QtLocation/private/qgeotilefetcher_p.h>
#include "gdqgeotiledmappingmanagerengine.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QMutex>
class GDQGeoTileFetcher : public QGeoTileFetcher
{
Q_OBJECT
public:
GDQGeoTileFetcher(const QVariantMap ¶meters, GDQGeoTiledMappingManagerEngine *engine, const QSize &tileSize);
QGeoTiledMapReply *getTileImage(const QGeoTileSpec &spec);
private:
QString _getURL(int type, int x, int y, int zoom);
void _getSecAmapWords(int x, int y, QString &sec1, QString &sec2);
void _getSessionToken();
void _tryCorrectAmapVersions(QNetworkAccessManager *networkManager);
private slots:
void _networkReplyError(QNetworkReply::NetworkError error);
void _replyDestroyed();
void _amapVersionCompleted();
private:
QString m_apiKey;
QString m_signature;
QString m_client;
QString m_baseUri;
bool _amapVersionRetrieved;
QNetworkReply* _amapReply;
QMutex _amapVersionMutex;
QByteArray _userAgent;
QString _language;
// Amap version strings
QString _versionAmapMap;
QString _versionAmapSatellite;
QString _versionAmapLabels;
QString _versionAmapTerrain;
QString _secAmapWord;
QNetworkRequest netRequest;
QNetworkAccessManager *m_networkManager;
};
#endif // GDQGEOTILEFETCHER_H
#include "gdqgeotilefetcher.h"
#include "gdqgeotiledmapreply.h"
#include <QNetworkProxy>
#include <QDebug>
#include <QSize>
#include <QDir>
#include <QUrl>
#include <QUrlQuery>
#include <QTime>
#include <QtCore/QJsonDocument>
#include <math.h>
#include <map>
#include <QJsonObject>
GDQGeoTileFetcher::GDQGeoTileFetcher(const QVariantMap ¶meters, GDQGeoTiledMappingManagerEngine *engine, const QSize &tileSize)
:QGeoTileFetcher(engine)
{
if(parameters.contains(QStringLiteral("amap.maps.apikey")))
m_apiKey = parameters.value(QStringLiteral("amap.maps.apikey")).toString();
else
m_apiKey = parameters.value(QStringLiteral("amap.apikey")).toString();
m_signature = parameters.value(QStringLiteral("amap.maps.signature")).toString();
m_client = parameters.value(QStringLiteral("amap.maps.client")).toString();
m_baseUri = QStringLiteral("http://maps.amap.com/maps/api/staticmap");
if (parameters.contains(QStringLiteral("amap.useragent")))
_userAgent = parameters.value(QStringLiteral("amap.useragent")).toString().toLatin1();
else
_userAgent = "";
QStringList langs = QLocale::system().uiLanguages();
if (langs.length() > 0) {
_language = langs[0];
}
// Amap version strings
_versionAmapMap = "m@338000000";
_versionAmapSatellite = "198";
_versionAmapLabels = "h@336";
_versionAmapTerrain = "t@132,r@338000000";
_secAmapWord = "Galileo";
// _tryCorrectAmapVersions(m_networkManager);
// netRequest.setRawHeader("Referrer", "https://www.amap.com/maps/preview");
// netRequest.setRawHeader("Accept", "*/*");
// netRequest.setRawHeader("User-Agent", _userAgent);
/* 2017: support new Amap Maps Tile API (yet under development)
You have to be whitelisted to use the Tile API. I can't tell how to get whitelisted.
see https://developers.amap.com/maps/documentation/tile/
To use the new feature getUrl() and parsing the response has to be adapted. Maybe more than that...
*/
// _getSessionToken();
}
QGeoTiledMapReply *GDQGeoTileFetcher::getTileImage(const QGeoTileSpec &spec)
{
QString surl = _getURL(spec.mapId(), spec.x(), spec.y(), spec.zoom());
// qDebug()<<"_getURL:" << surl;
QUrl url(surl);
netRequest.setUrl(url);
QNetworkReply *netReply = m_networkManager->get(netRequest);
QGeoTiledMapReply *mapReply = new GDQGeoTiledMapReply(netReply, spec);
return mapReply;
}
QString GDQGeoTileFetcher::_getURL(int type, int x, int y, int zoom)
{
// qDebug() << "Type:" << type;
switch (type) {
case 0:
case 1: //Road Map
{
QString sec1 = ""; // after &x=...
QString sec2 = ""; // after &zoom=...
_getSecAmapWords(x, y, sec1, sec2);
return QString("http://wprd03.is.autonavi.com/appmaptile?style=7&x=%1&y=%2&z=%3").arg(x).arg(y).arg(zoom);
}
break;
case 2: //Satallite Map
{
QString sec1 = ""; // after &x=...
QString sec2 = ""; // after &zoom=...
_getSecAmapWords(x, y, sec1, sec2);
return QString("http://wprd03.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x=%1&y=%2&z=%3").arg(x).arg(y).arg(zoom);
}
break;
case 3: //Terrain Map
{
QString sec1 = ""; // after &x=...
QString sec2 = ""; // after &zoom=...
_getSecAmapWords(x, y, sec1, sec2);
return QString("http://wprd03.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=6&x=%1&y=%2&z=%3").arg(x).arg(y).arg(zoom);
}
break;
case 4: //Hybrid Map
{
QString sec1 = ""; // after &x=...
QString sec2 = ""; // after &zoom=...
_getSecAmapWords(x, y, sec1, sec2);
return QString("http://wprd03.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=8&x=%1&y=%2&z=%3").arg(x).arg(y).arg(zoom);
}
break;
}
return "";
}
void GDQGeoTileFetcher::_getSecAmapWords(int x, int y, QString &sec1, QString &sec2)
{
sec1 = ""; // after &x=...
sec2 = ""; // after &zoom=...
int seclen = ((x * 3) + y) % 8;
sec2 = _secAmapWord.left(seclen);
if (y >= 10000 && y < 100000) {
sec1 = "&s=";
}
}
void GDQGeoTileFetcher::_getSessionToken()
{
QUrl sessionUrl("https://www.amap.com/tile/v1/createSession");
QUrlQuery queryItems;
queryItems.addQueryItem("key", m_apiKey);
queryItems.addQueryItem("mapType", "roadmap");
queryItems.addQueryItem("language", _language);
queryItems.addQueryItem("region", "de");
sessionUrl.setQuery(queryItems);
netRequest.setUrl(sessionUrl);
QNetworkReply *sessionReply = m_networkManager->get(netRequest);
if (sessionReply->error() != QNetworkReply::NoError)
return;
QJsonDocument document = QJsonDocument::fromJson(sessionReply->readAll());
if (!document.isObject())
return;
QJsonObject object = document.object();
QJsonValue status = object.value(QStringLiteral("session"));
printf(status.toString().toLatin1().data());
}
void GDQGeoTileFetcher::_tryCorrectAmapVersions(QNetworkAccessManager *networkManager)
{
QMutexLocker locker(&_amapVersionMutex);
if (_amapVersionRetrieved) {
return;
}
_amapVersionRetrieved = true;
if(networkManager)
{
QNetworkRequest qheader;
QNetworkProxy proxy = networkManager->proxy();
QNetworkProxy tProxy;
tProxy.setType(QNetworkProxy::DefaultProxy);
networkManager->setProxy(tProxy);
QSslConfiguration conf = qheader.sslConfiguration();
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
qheader.setSslConfiguration(conf);
QString url = "http://maps.amap.com/maps/api/js?v=3.2&sensor=false";
qheader.setUrl(QUrl(url));
qheader.setRawHeader("User-Agent", _userAgent);
_amapReply = networkManager->get(qheader);
connect(_amapReply, &QNetworkReply::finished, this, &GDQGeoTileFetcher::_amapVersionCompleted);
connect(_amapReply, &QNetworkReply::destroyed, this, &GDQGeoTileFetcher::_replyDestroyed);
connect(_amapReply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
this, &GDQGeoTileFetcher::_networkReplyError);
networkManager->setProxy(proxy);
}
}
void GDQGeoTileFetcher::_networkReplyError(QNetworkReply::NetworkError error)
{
qWarning() << "Could not connect to amap maps. Error:" << error;
if(_amapReply)
{
_amapReply->deleteLater();
_amapReply = NULL;
}
}
void GDQGeoTileFetcher::_replyDestroyed()
{
_amapReply = NULL;
}
void GDQGeoTileFetcher::_amapVersionCompleted()
{
if (!_amapReply || (_amapReply->error() != QNetworkReply::NoError)) {
qDebug() << "Error collecting Amap maps version info";
return;
}
QString html = QString(_amapReply->readAll());
QRegExp reg("\"*https?://mt\\D?\\d..*/vt\\?lyrs=m@(\\d*)", Qt::CaseInsensitive);
if (reg.indexIn(html) != -1) {
QStringList gc = reg.capturedTexts();
_versionAmapMap = QString("m@%1").arg(gc[1]);
}
reg = QRegExp("\"*https?://khm\\D?\\d.amap.com/kh\\?v=(\\d*)", Qt::CaseInsensitive);
if (reg.indexIn(html) != -1) {
QStringList gc = reg.capturedTexts();
_versionAmapSatellite = gc[1];
}
reg = QRegExp("\"*https?://mt\\D?\\d..*/vt\\?lyrs=t@(\\d*),r@(\\d*)", Qt::CaseInsensitive);
if (reg.indexIn(html) != -1) {
QStringList gc = reg.capturedTexts();
_versionAmapTerrain = QString("t@%1,r@%2").arg(gc[1]).arg(gc[2]);
}
_amapReply->deleteLater();
_amapReply = NULL;
}
瓦片请求网络
#ifndef GDQGEOTILEDMAPREPLY_H
#define GDQGEOTILEDMAPREPLY_H
#include <QtNetwork/QNetworkReply>
#include <QtLocation/private/qgeotilespec_p.h>
#include <QtLocation/private/qgeotiledmapreply_p.h>
#include <QtCore/QPointer>
class GDQGeoTiledMapReply : public QGeoTiledMapReply
{
Q_OBJECT
public:
GDQGeoTiledMapReply(QNetworkReply *reply, const QGeoTileSpec &spec, QObject *parent = 0);
void abort();
QNetworkReply *networkReply() const;
private Q_SLOTS:
void networkFinished();
void networkError(QNetworkReply::NetworkError error);
private:
QPointer<QNetworkReply> m_reply;
};
#endif // GDQGEOTILEDMAPREPLY_H
#include "gdqgeotiledmapreply.h"
GDQGeoTiledMapReply::GDQGeoTiledMapReply(QNetworkReply *reply, const QGeoTileSpec &spec, QObject *parent)
:QGeoTiledMapReply(spec,parent),
m_reply(reply)
{
connect(m_reply,
SIGNAL(finished()),
this,
SLOT(networkFinished()));
connect(m_reply,
SIGNAL(error(QNetworkReply::NetworkError)),
this,
SLOT(networkError(QNetworkReply::NetworkError)));
}
void GDQGeoTiledMapReply::abort()
{
if (!m_reply)
return;
m_reply->abort();
}
QNetworkReply *GDQGeoTiledMapReply::networkReply() const
{
return m_reply;
}
void GDQGeoTiledMapReply::networkFinished()
{
if (!m_reply)
return;
if (m_reply->error() != QNetworkReply::NoError)
return;
setMapImageData(m_reply->readAll());
const int _mid = tileSpec().mapId();
if (_mid == 2)
setMapImageFormat("jpeg");
else
setMapImageFormat("png");
setFinished(true);
m_reply->deleteLater();
m_reply = 0;
}
void GDQGeoTiledMapReply::networkError(QNetworkReply::NetworkError error)
{
Q_UNUSED(error);
if (!m_reply)
return;
setFinished(true);
setCached(false);
m_reply->deleteLater();
m_reply = 0;
}
创造地图:
#ifndef GDQGEOTILEDMAP_H
#define GDQGEOTILEDMAP_H
#include "QtLocation/private/qgeotiledmap_p.h"
#include <QtGui/QImage>
#include <QtCore/QPointer>
#include "gdqgeotiledmappingmanagerengine.h"
class GDQGeoTiledMap : public QGeoTiledMap
{
public:
explicit GDQGeoTiledMap(GDQGeoTiledMappingManagerEngine *engine,QObject *parent = nullptr);
//QString getViewCopyright();
// void evaluateCopyrights(const QSet<QGeoTileSpec> &visibleTiles);
private:
QImage m_copyrightsSlab;
QString m_lastCopyrightsString;
QPointer<GDQGeoTiledMappingManagerEngine> m_engine;
};
#endif // GDQGEOTILEDMAP_H
#include "gdqgeotiledmap.h"
GDQGeoTiledMap::GDQGeoTiledMap(GDQGeoTiledMappingManagerEngine *engine, QObject *parent)
:QGeoTiledMap(engine, parent),
m_engine(engine)
{
}
二、qml调用插件
将生成的插件动态库放到C:\Qt5.15.2\5.15.2\msvc2019_64\plugins\geoservices目录下,qml直接调用。
import QtQuick 2.12
import QtQuick.Window 2.12
import QtPositioning 5.12
import QtLocation 5.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Map{
anchors.fill:parent
plugin: Plugin{name: "amap"}
}
}
插件json文件:
{
"Keys": ["amap"],
"Provider": "amap",
"Version": 200,
"Experimental": false,
"Features": [
"OnlineGeocodingFeature",
"ReverseGeocodingFeature",
"OnlineRoutingFeature",
"AlternativeRoutesFeature",
"OnlineMappingFeature",
"SearchSuggestionsFeature"
]
}