From f538e05964211102707174c43d9d4e4d311d11e6 Mon Sep 17 00:00:00 2001
From: Pierre TILAK <pierre.tilak@aeronavics.com>
Date: Fri, 13 Sep 2019 11:53:17 +1200
Subject: [PATCH] Start reorganize QGCMapUrlEngine

---
 src/QtLocationPlugin/GoogleMapProvider.cpp | 222 +++++++++++++
 src/QtLocationPlugin/GoogleMapProvider.h   |  44 +++
 src/QtLocationPlugin/MapProvider.cpp       |  63 ++++
 src/QtLocationPlugin/MapProvider.h         |  37 +++
 src/QtLocationPlugin/QGCLocationPlugin.pri |   4 +
 src/QtLocationPlugin/QGCMapUrlEngine.cpp   | 507 ++++++++++-------------------
 src/QtLocationPlugin/QGCMapUrlEngine.h     |  40 +--
 7 files changed, 550 insertions(+), 367 deletions(-)
 create mode 100644 src/QtLocationPlugin/GoogleMapProvider.cpp
 create mode 100644 src/QtLocationPlugin/GoogleMapProvider.h
 create mode 100644 src/QtLocationPlugin/MapProvider.cpp
 create mode 100644 src/QtLocationPlugin/MapProvider.h

diff --git a/src/QtLocationPlugin/GoogleMapProvider.cpp b/src/QtLocationPlugin/GoogleMapProvider.cpp
new file mode 100644
index 0000000..ef8eb3b
--- /dev/null
+++ b/src/QtLocationPlugin/GoogleMapProvider.cpp
@@ -0,0 +1,222 @@
+#include "GoogleMapProvider.h"
+#if defined(DEBUG_GOOGLE_MAPS)
+#include <QFile>
+#include <QStandardPaths>
+#endif
+#include "QGCMapEngine.h"
+
+#define AVERAGE_GOOGLE_STREET_MAP   4913
+
+GoogleMapProvider::GoogleMapProvider(QObject* parent)
+    : MapProvider(QString("https://www.google.com/maps/preview"),
+                  QString("png"), quint32(AVERAGE_GOOGLE_STREET_MAP), parent),
+      _googleVersionRetrieved(false), _googleReply(nullptr) {
+
+    // Google version strings
+    _versionGoogleMap       = "m@354000000";
+    _versionGoogleSatellite = "692";
+    _versionGoogleLabels    = "h@336";
+    _versionGoogleTerrain   = "t@354,r@354000000";
+    _secGoogleWord          = "Galileo";
+
+}
+
+quint32 GoogleMapProvider::getAverageSize(){
+    return quint32(AVERAGE_GOOGLE_STREET_MAP);
+}
+
+GoogleMapProvider::~GoogleMapProvider() {
+    if (_googleReply)
+        _googleReply->deleteLater();
+}
+
+//-----------------------------------------------------------------------------
+void GoogleMapProvider::_getSecGoogleWords(int x, int y, QString& sec1,
+                                    QString& sec2) {
+    sec1       = ""; // after &x=...
+    sec2       = ""; // after &zoom=...
+    int seclen = ((x * 3) + y) % 8;
+    sec2       = _secGoogleWord.left(seclen);
+    if (y >= 10000 && y < 100000) {
+        sec1 = "&s=";
+    }
+}
+
+//-----------------------------------------------------------------------------
+void GoogleMapProvider::_networkReplyError(QNetworkReply::NetworkError error) {
+    qWarning() << "Could not connect to google maps. Error:" << error;
+    if (_googleReply) {
+        _googleReply->deleteLater();
+        _googleReply = nullptr;
+    }
+}
+//-----------------------------------------------------------------------------
+void GoogleMapProvider::_replyDestroyed() { _googleReply = nullptr; }
+
+void GoogleMapProvider::_googleVersionCompleted() {
+    if (!_googleReply || (_googleReply->error() != QNetworkReply::NoError)) {
+        qDebug() << "Error collecting Google maps version info";
+        return;
+    }
+    QString html = QString(_googleReply->readAll());
+
+#if defined(DEBUG_GOOGLE_MAPS)
+    QString filename =
+        QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+    filename += "/google.output";
+    QFile file(filename);
+    if (file.open(QIODevice::ReadWrite)) {
+        QTextStream stream(&file);
+        stream << html << endl;
+    }
+#endif
+
+    QRegExp reg("\"*https?://mt\\D?\\d..*/vt\\?lyrs=m@(\\d*)",
+                Qt::CaseInsensitive);
+    if (reg.indexIn(html) != -1) {
+        QStringList gc    = reg.capturedTexts();
+        _versionGoogleMap = QString("m@%1").arg(gc[1]);
+    }
+    reg = QRegExp("\"*https?://khm\\D?\\d.googleapis.com/kh\\?v=(\\d*)",
+                  Qt::CaseInsensitive);
+    if (reg.indexIn(html) != -1) {
+        QStringList gc          = reg.capturedTexts();
+        _versionGoogleSatellite = 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();
+        _versionGoogleTerrain = QString("t@%1,r@%2").arg(gc[1]).arg(gc[2]);
+    }
+    _googleReply->deleteLater();
+    _googleReply = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+void GoogleMapProvider::_tryCorrectGoogleVersions(
+    QNetworkAccessManager* networkManager) {
+    QMutexLocker locker(&_googleVersionMutex);
+    if (_googleVersionRetrieved) {
+        return;
+    }
+    _googleVersionRetrieved = 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.google.com/maps/api/js?v=3.2&sensor=false";
+        qheader.setUrl(QUrl(url));
+        QByteArray ua;
+        ua.append(getQGCMapEngine()->userAgent());
+        qheader.setRawHeader("User-Agent", ua);
+        _googleReply = networkManager->get(qheader);
+        connect(_googleReply, &QNetworkReply::finished, this,
+                &GoogleMapProvider::_googleVersionCompleted);
+        connect(_googleReply, &QNetworkReply::destroyed, this,
+                &GoogleMapProvider::_replyDestroyed);
+        connect(
+            _googleReply,
+            static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(
+                &QNetworkReply::error),
+            this, &GoogleMapProvider::_networkReplyError);
+        networkManager->setProxy(proxy);
+    }
+}
+//
+//-----------------------------------------------------------------------------
+QString GoogleMapProvider::_getURL(int x, int y, int zoom,
+                              QNetworkAccessManager* networkManager) {
+    // http://mt1.google.com/vt/lyrs=m
+    QString server  = "mt";
+    QString request = "vt";
+    QString sec1    = ""; // after &x=...
+    QString sec2    = ""; // after &zoom=...
+    _getSecGoogleWords(x, y, sec1, sec2);
+    _tryCorrectGoogleVersions(networkManager);
+    return QString(
+               "http://%1%2.google.com/%3/lyrs=%4&hl=%5&x=%6%7&y=%8&z=%9&s=%10")
+        .arg(server)
+        .arg(_getServerNum(x, y, 4))
+        .arg(request)
+        .arg(_versionGoogleMap)
+        .arg(_language)
+        .arg(x)
+        .arg(sec1)
+        .arg(y)
+        .arg(zoom)
+        .arg(sec2);
+//} break;
+//case GoogleSatellite: {
+//    // http://mt1.google.com/vt/lyrs=s
+//    QString server  = "khm";
+//    QString request = "kh";
+//    QString sec1    = ""; // after &x=...
+//    QString sec2    = ""; // after &zoom=...
+//    _getSecGoogleWords(x, y, sec1, sec2);
+//    _tryCorrectGoogleVersions(networkManager);
+//    return QString(
+//               "http://%1%2.google.com/%3/v=%4&hl=%5&x=%6%7&y=%8&z=%9&s=%10")
+//        .arg(server)
+//        .arg(_getServerNum(x, y, 4))
+//        .arg(request)
+//        .arg(_versionGoogleSatellite)
+//        .arg(_language)
+//        .arg(x)
+//        .arg(sec1)
+//        .arg(y)
+//        .arg(zoom)
+//        .arg(sec2);
+//} break;
+//case GoogleLabels: {
+//    QString server  = "mts";
+//    QString request = "vt";
+//    QString sec1    = ""; // after &x=...
+//    QString sec2    = ""; // after &zoom=...
+//    _getSecGoogleWords(x, y, sec1, sec2);
+//    _tryCorrectGoogleVersions(networkManager);
+//    return QString(
+//               "http://%1%2.google.com/%3/lyrs=%4&hl=%5&x=%6%7&y=%8&z=%9&s=%10")
+//        .arg(server)
+//        .arg(_getServerNum(x, y, 4))
+//        .arg(request)
+//        .arg(_versionGoogleLabels)
+//        .arg(_language)
+//        .arg(x)
+//        .arg(sec1)
+//        .arg(y)
+//        .arg(zoom)
+//        .arg(sec2);
+//} break;
+//case GoogleTerrain: {
+//    QString server  = "mt";
+//    QString request = "vt";
+//    QString sec1    = ""; // after &x=...
+//    QString sec2    = ""; // after &zoom=...
+//    _getSecGoogleWords(x, y, sec1, sec2);
+//    _tryCorrectGoogleVersions(networkManager);
+//    return QString(
+//               "http://%1%2.google.com/%3/v=%4&hl=%5&x=%6%7&y=%8&z=%9&s=%10")
+//        .arg(server)
+//        .arg(_getServerNum(x, y, 4))
+//        .arg(request)
+//        .arg(_versionGoogleTerrain)
+//        .arg(_language)
+//        .arg(x)
+//        .arg(sec1)
+//        .arg(y)
+//        .arg(zoom)
+//        .arg(sec2);
+//} break;
+//default:
+//    qWarning("Unknown map id %d\n", type);
+//    break;
+//}
+//return {};
+}
+
diff --git a/src/QtLocationPlugin/GoogleMapProvider.h b/src/QtLocationPlugin/GoogleMapProvider.h
new file mode 100644
index 0000000..a97e3d4
--- /dev/null
+++ b/src/QtLocationPlugin/GoogleMapProvider.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "MapProvider.h"
+
+#include <QByteArray>
+#include <QNetworkProxy>
+#include <QNetworkReply>
+#include <QString>
+#include <QPoint>
+#include <QMutex>
+
+class GoogleMapProvider : public MapProvider {
+    Q_OBJECT
+  public:
+    GoogleMapProvider(QObject* parent);
+
+    ~GoogleMapProvider();
+
+    static quint32 getAverageSize();
+    // Google Specific private slots
+  private slots:
+    void _networkReplyError(QNetworkReply::NetworkError error);
+    void _googleVersionCompleted();
+    void _replyDestroyed();
+
+  private:
+    // Define the url to Request
+    QString _getURL(int x, int y, int zoom,
+                    QNetworkAccessManager* networkManager) ;
+
+    // Google Specific private methods
+    void _getSecGoogleWords(int x, int y, QString& sec1, QString& sec2);
+    void _tryCorrectGoogleVersions(QNetworkAccessManager* networkManager);
+
+    // Google Specific attributes
+    bool           _googleVersionRetrieved;
+    QNetworkReply* _googleReply;
+    QMutex         _googleVersionMutex;
+    QString        _versionGoogleMap;
+    QString        _versionGoogleSatellite;
+    QString        _versionGoogleLabels;
+    QString        _versionGoogleTerrain;
+    QString        _secGoogleWord;
+};
diff --git a/src/QtLocationPlugin/MapProvider.cpp b/src/QtLocationPlugin/MapProvider.cpp
new file mode 100644
index 0000000..ba4b81b
--- /dev/null
+++ b/src/QtLocationPlugin/MapProvider.cpp
@@ -0,0 +1,63 @@
+#include "MapProvider.h"
+
+MapProvider::MapProvider(QString referrer, QString imageFormat,
+                         quint32 averageSize, QObject* parent)
+    : QObject(parent), _referrer(referrer), _imageFormat(imageFormat),
+      _averageSize(averageSize) {
+    QStringList langs = QLocale::system().uiLanguages();
+    if (langs.length() > 0) {
+        _language = langs[0];
+    }
+}
+
+QNetworkRequest MapProvider::getTileURL(int x, int y, int zoom,
+                                        QNetworkAccessManager* networkManager) {
+    //-- Build URL
+    QNetworkRequest request;
+    QString         url = _getURL(x, y, zoom, networkManager);
+    if (url.isEmpty()) {
+        return request;
+    }
+    request.setUrl(QUrl(url));
+    request.setRawHeader("Accept", "*/*");
+    request.setRawHeader("Referrer", _referrer.toUtf8());
+    request.setRawHeader("User-Agent", _userAgent);
+    return request;
+}
+
+QString MapProvider::getImageFormat(const QByteArray& image) {
+    QString format;
+    if (image.size() > 2) {
+        if (image.startsWith(reinterpret_cast<const char*>(pngSignature)))
+            format = "png";
+        else if (image.startsWith(reinterpret_cast<const char*>(jpegSignature)))
+            format = "jpg";
+        else if (image.startsWith(reinterpret_cast<const char*>(gifSignature)))
+            format = "gif";
+        else {
+            return _imageFormat;
+        }
+    }
+    return format;
+}
+
+QString MapProvider::_tileXYToQuadKey(int tileX, int tileY, int levelOfDetail) {
+    QString quadKey;
+    for (int i = levelOfDetail; i > 0; i--) {
+        char digit = '0';
+        int  mask  = 1 << (i - 1);
+        if ((tileX & mask) != 0) {
+            digit++;
+        }
+        if ((tileY & mask) != 0) {
+            digit++;
+            digit++;
+        }
+        quadKey.append(digit);
+    }
+    return quadKey;
+}
+
+int MapProvider::_getServerNum(int x, int y, int max) {
+    return (x + 2 * y) % max;
+}
diff --git a/src/QtLocationPlugin/MapProvider.h b/src/QtLocationPlugin/MapProvider.h
new file mode 100644
index 0000000..b40ad5b
--- /dev/null
+++ b/src/QtLocationPlugin/MapProvider.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <QByteArray>
+#include <QNetworkProxy>
+#include <QNetworkReply>
+#include <QString>
+
+static const unsigned char pngSignature[]   = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00};
+static const unsigned char jpegSignature[]  = {0xFF, 0xD8, 0xFF, 0x00};
+static const unsigned char gifSignature[]   = {0x47, 0x49, 0x46, 0x38, 0x00};
+
+class MapProvider : public QObject {
+    Q_OBJECT
+  public:
+    MapProvider(QString referrer, QString imageFormat, quint32 averageSize,
+                QObject* parent = nullptr);
+
+    QNetworkRequest getTileURL(int x, int y, int zoom,
+                               QNetworkAccessManager* networkManager);
+
+    QString getImageFormat(const QByteArray& image);
+
+  protected:
+    QString _tileXYToQuadKey            (int tileX, int tileY, int levelOfDetail);
+    int     _getServerNum               (int x, int y, int max);
+
+    // Define Referrer for Request RawHeader
+    QString _referrer;
+    QString _imageFormat;
+    quint32 _averageSize;
+    QByteArray      _userAgent;
+    QString         _language;
+
+    // Define the url to Request
+    virtual QString _getURL(int x, int y, int zoom,
+                            QNetworkAccessManager* networkManager) = 0;
+};
diff --git a/src/QtLocationPlugin/QGCLocationPlugin.pri b/src/QtLocationPlugin/QGCLocationPlugin.pri
index fd31ddd..4df3b05 100644
--- a/src/QtLocationPlugin/QGCLocationPlugin.pri
+++ b/src/QtLocationPlugin/QGCLocationPlugin.pri
@@ -22,6 +22,8 @@ HEADERS += \
     $$PWD/QGeoServiceProviderPluginQGC.h \
     $$PWD/QGeoTileFetcherQGC.h \
     $$PWD/QGeoTiledMappingManagerEngineQGC.h \
+    $$PWD/MapProvider.h \
+    $$PWD/GoogleMapProvider.h \
 
 SOURCES += \
     $$PWD/QGCMapEngine.cpp \
@@ -34,6 +36,8 @@ SOURCES += \
     $$PWD/QGeoServiceProviderPluginQGC.cpp \
     $$PWD/QGeoTileFetcherQGC.cpp \
     $$PWD/QGeoTiledMappingManagerEngineQGC.cpp \
+    $$PWD/MapProvider.cpp \
+    $$PWD/GoogleMapProvider.cpp \
 
 OTHER_FILES += \
     $$PWD/qgc_maps_plugin.json
diff --git a/src/QtLocationPlugin/QGCMapUrlEngine.cpp b/src/QtLocationPlugin/QGCMapUrlEngine.cpp
index 56e622b..d4321d1 100644
--- a/src/QtLocationPlugin/QGCMapUrlEngine.cpp
+++ b/src/QtLocationPlugin/QGCMapUrlEngine.cpp
@@ -28,47 +28,32 @@
 #include <QString>
 #include <QByteArray>
 
-#if defined(DEBUG_GOOGLE_MAPS)
-#include <QFile>
-#include <QStandardPaths>
-#endif
 
-static const unsigned char pngSignature[]   = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00};
-static const unsigned char jpegSignature[]  = {0xFF, 0xD8, 0xFF, 0x00};
-static const unsigned char gifSignature[]   = {0x47, 0x49, 0x46, 0x38, 0x00};
+#define AVERAGE_GOOGLE_TERRAIN_MAP  19391
+#define AVERAGE_BING_STREET_MAP     1297
+#define AVERAGE_BING_SAT_MAP        19597
+#define AVERAGE_GOOGLE_SAT_MAP      56887
+#define AVERAGE_MAPBOX_SAT_MAP      15739
+#define AVERAGE_MAPBOX_STREET_MAP   5648
+#define AVERAGE_TILE_SIZE           13652
+#define AVERAGE_AIRMAP_ELEV_SIZE    2786
 
 //-----------------------------------------------------------------------------
 UrlFactory::UrlFactory()
     : _timeout(5 * 1000)
-#ifndef QGC_NO_GOOGLE_MAPS
-    , _googleVersionRetrieved(false)
-    , _googleReply(nullptr)
-#endif
 {
-    QStringList langs = QLocale::system().uiLanguages();
-    if (langs.length() > 0) {
-        _language = langs[0];
-    }
+
+    // BingMaps
+    //_versionBingMaps             = "563";
 
 #ifndef QGC_NO_GOOGLE_MAPS
-    // Google version strings
-    _versionGoogleMap            = "m@354000000";
-    _versionGoogleSatellite      = "692";
-    _versionGoogleLabels         = "h@336";
-    _versionGoogleTerrain        = "t@354,r@354000000";
-    _secGoogleWord               = "Galileo";
+    _googleMapProvider = new GoogleMapProvider(this);
 #endif
-    // BingMaps
-    _versionBingMaps             = "563";
 }
 
 //-----------------------------------------------------------------------------
 UrlFactory::~UrlFactory()
 {
-#ifndef QGC_NO_GOOGLE_MAPS
-    if(_googleReply)
-        _googleReply->deleteLater();
-#endif
 }
 
 
@@ -76,157 +61,143 @@ UrlFactory::~UrlFactory()
 QString
 UrlFactory::getImageFormat(MapType type, const QByteArray& image)
 {
-    QString format;
-    if(image.size() > 2)
-    {
-        if (image.startsWith(reinterpret_cast<const char*>(pngSignature)))
-            format = "png";
-        else if (image.startsWith(reinterpret_cast<const char*>(jpegSignature)))
-            format = "jpg";
-        else if (image.startsWith(reinterpret_cast<const char*>(gifSignature)))
-            format = "gif";
-        else {
-            switch (type) {
-                case GoogleMap:
-                case GoogleLabels:
-                case GoogleTerrain:
-                case GoogleHybrid:
-                case BingMap:
-                case StatkartTopo:
-                    format = "png";
-                    break;
-                case EniroTopo:
-                    format = "png";
-                    break;
-                /*
-                case MapQuestMap:
-                case MapQuestSat:
-                case OpenStreetMap:
-                */
-                case MapboxStreets:
-                case MapboxLight:
-                case MapboxDark:
-                case MapboxSatellite:
-                case MapboxHybrid:
-                case MapboxWheatPaste:
-                case MapboxStreetsBasic:
-                case MapboxComic:
-                case MapboxOutdoors:
-                case MapboxRunBikeHike:
-                case MapboxPencil:
-                case MapboxPirates:
-                case MapboxEmerald:
-                case MapboxHighContrast:
-                case GoogleSatellite:
-                case BingSatellite:
-                case BingHybrid:
-                    format = "jpg";
-                    break;
-                case AirmapElevation:
-                    format = "bin";
-                    break;
-                case VWorldStreet :
-                    format = "png";
-                    break;
-                case VWorldSatellite :
-                    format = "jpg";
-                    break;
-                default:
-                    qWarning("UrlFactory::getImageFormat() Unknown map id %d", type);
-                    break;
-            }
-        }
-    }
-    return format;
+    Q_UNUSED(type);
+    return _googleMapProvider->getImageFormat(image);
+    //QString format;
+    //if(image.size() > 2)
+    //{
+    //    if (image.startsWith(reinterpret_cast<const char*>(pngSignature)))
+    //        format = "png";
+    //    else if (image.startsWith(reinterpret_cast<const char*>(jpegSignature)))
+    //        format = "jpg";
+    //    else if (image.startsWith(reinterpret_cast<const char*>(gifSignature)))
+    //        format = "gif";
+    //    else {
+    //        switch (type) {
+    //            case GoogleMap:
+    //            case GoogleLabels:
+    //            case GoogleTerrain:
+    //            case GoogleHybrid:
+    //            case BingMap:
+    //            case StatkartTopo:
+    //                format = "png";
+    //                break;
+    //            case EniroTopo:
+    //                format = "png";
+    //                break;
+    //            /*
+    //            case MapQuestMap:
+    //            case MapQuestSat:
+    //            case OpenStreetMap:
+    //            */
+    //            case MapboxStreets:
+    //            case MapboxLight:
+    //            case MapboxDark:
+    //            case MapboxSatellite:
+    //            case MapboxHybrid:
+    //            case MapboxWheatPaste:
+    //            case MapboxStreetsBasic:
+    //            case MapboxComic:
+    //            case MapboxOutdoors:
+    //            case MapboxRunBikeHike:
+    //            case MapboxPencil:
+    //            case MapboxPirates:
+    //            case MapboxEmerald:
+    //            case MapboxHighContrast:
+    //            case GoogleSatellite:
+    //            case BingSatellite:
+    //            case BingHybrid:
+    //                format = "jpg";
+    //                break;
+    //            case AirmapElevation:
+    //                format = "bin";
+    //                break;
+    //            case VWorldStreet :
+    //                format = "png";
+    //                break;
+    //            case VWorldSatellite :
+    //                format = "jpg";
+    //                break;
+    //            default:
+    //                qWarning("UrlFactory::getImageFormat() Unknown map id %d", type);
+    //                break;
+    //        }
+    //    }
+    //}
+    //return format;
 }
 
 //-----------------------------------------------------------------------------
 QNetworkRequest
 UrlFactory::getTileURL(MapType type, int x, int y, int zoom, QNetworkAccessManager* networkManager)
 {
-    //-- Build URL
-    QNetworkRequest request;
-    QString url = _getURL(type, x, y, zoom, networkManager);
-    if(url.isEmpty()) {
-        return request;
-    }
-    request.setUrl(QUrl(url));
-    request.setRawHeader("Accept", "*/*");
-    switch (type) {
-#ifndef QGC_NO_GOOGLE_MAPS
-        case GoogleMap:
-        case GoogleSatellite:
-        case GoogleLabels:
-        case GoogleTerrain:
-        case GoogleHybrid:
-            request.setRawHeader("Referrer", "https://www.google.com/maps/preview");
-            break;
-#endif
-        case BingHybrid:
-        case BingMap:
-        case BingSatellite:
-            request.setRawHeader("Referrer", "https://www.bing.com/maps/");
-            break;
-        case StatkartTopo:
-            request.setRawHeader("Referrer", "https://www.norgeskart.no/");
-            break;
-        case EniroTopo:
-            request.setRawHeader("Referrer", "https://www.eniro.se/");
-            break;
-        /*
-        case OpenStreetMapSurfer:
-        case OpenStreetMapSurferTerrain:
-            request.setRawHeader("Referrer", "http://www.mapsurfer.net/");
-            break;
-        case OpenStreetMap:
-        case OpenStreetOsm:
-            request.setRawHeader("Referrer", "https://www.openstreetmap.org/");
-            break;
-        */
-
-        case EsriWorldStreet:
-        case EsriWorldSatellite:
-        case EsriTerrain: {
-                QByteArray token = qgcApp()->toolbox()->settingsManager()->appSettings()->esriToken()->rawValue().toString().toLatin1();
-                request.setRawHeader("User-Agent", QByteArrayLiteral("Qt Location based application"));
-                request.setRawHeader("User-Token", token);
-            }
-            return request;
-
-        case AirmapElevation:
-            request.setRawHeader("Referrer", "https://api.airmap.com/");
-            break;
-
-        default:
-            break;
-    }
-    request.setRawHeader("User-Agent", _userAgent);
-    return request;
+    Q_UNUSED(type);
+    return _googleMapProvider->getTileURL(x,y,zoom,networkManager);
+    ////-- Build URL
+    //QNetworkRequest request;
+    //QString url = _getURL(type, x, y, zoom, networkManager);
+    //if(url.isEmpty()) {
+    //    return request;
+    //}
+    //request.setUrl(QUrl(url));
+    //request.setRawHeader("Accept", "*/*");
+    //switch (type) {
+//    //    case GoogleMap:
+//    //    case GoogleSatellite:
+//    //    case GoogleLabels:
+//    //    case GoogleTerrain:
+//    //    case GoogleHybrid:
+//    //        request.setRawHeader("Referrer", "https://www.google.com/maps/preview");
+//    //        break;
+    //    case BingHybrid:
+    //    case BingMap:
+    //    case BingSatellite:
+    //        request.setRawHeader("Referrer", "https://www.bing.com/maps/");
+    //        break;
+    //    case StatkartTopo:
+    //        request.setRawHeader("Referrer", "https://www.norgeskart.no/");
+    //        break;
+    //    case EniroTopo:
+    //        request.setRawHeader("Referrer", "https://www.eniro.se/");
+    //        break;
+    //    /*
+    //    case OpenStreetMapSurfer:
+    //    case OpenStreetMapSurferTerrain:
+    //        request.setRawHeader("Referrer", "http://www.mapsurfer.net/");
+    //        break;
+    //    case OpenStreetMap:
+    //    case OpenStreetOsm:
+    //        request.setRawHeader("Referrer", "https://www.openstreetmap.org/");
+    //        break;
+    //    */
+
+    //    case EsriWorldStreet:
+    //    case EsriWorldSatellite:
+    //    case EsriTerrain: {
+    //            QByteArray token = qgcApp()->toolbox()->settingsManager()->appSettings()->esriToken()->rawValue().toString().toLatin1();
+    //            request.setRawHeader("User-Agent", QByteArrayLiteral("Qt Location based application"));
+    //            request.setRawHeader("User-Token", token);
+    //        }
+    //        return request;
+
+    //    case AirmapElevation:
+    //        request.setRawHeader("Referrer", "https://api.airmap.com/");
+    //        break;
+
+    //    default:
+    //        break;
+    //}
+    //request.setRawHeader("User-Agent", _userAgent);
+    //return request;
 }
 
 //-----------------------------------------------------------------------------
-#ifndef QGC_NO_GOOGLE_MAPS
-void
-UrlFactory::_getSecGoogleWords(int x, int y, QString &sec1, QString &sec2)
-{
-    sec1 = ""; // after &x=...
-    sec2 = ""; // after &zoom=...
-    int seclen = ((x * 3) + y) % 8;
-    sec2 = _secGoogleWord.left(seclen);
-    if (y >= 10000 && y < 100000) {
-        sec1 = "&s=";
-    }
-}
-#endif
-
-//-----------------------------------------------------------------------------
+#if 0
 QString
 UrlFactory::_getURL(MapType type, int x, int y, int zoom, QNetworkAccessManager* networkManager)
 {
     switch (type) {
-#ifdef QGC_NO_GOOGLE_MAPS
     Q_UNUSED(networkManager);
-#else
     case GoogleMap:
     {
         // http://mt1.google.com/vt/lyrs=m
@@ -273,7 +244,6 @@ UrlFactory::_getURL(MapType type, int x, int y, int zoom, QNetworkAccessManager*
         return QString("http://%1%2.google.com/%3/v=%4&hl=%5&x=%6%7&y=%8&z=%9&s=%10").arg(server).arg(_getServerNum(x, y, 4)).arg(request).arg(_versionGoogleTerrain).arg(_language).arg(x).arg(sec1).arg(y).arg(zoom).arg(sec2);
     }
     break;
-#endif
     case StatkartTopo:
     {
         return QString("http://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo4&zoom=%1&x=%2&y=%3").arg(zoom).arg(x).arg(y);
@@ -476,181 +446,54 @@ UrlFactory::_getURL(MapType type, int x, int y, int zoom, QNetworkAccessManager*
     return {};
 }
 
-//-----------------------------------------------------------------------------
-QString
-UrlFactory::_tileXYToQuadKey(int tileX, int tileY, int levelOfDetail)
-{
-    QString quadKey;
-    for (int i = levelOfDetail; i > 0; i--) {
-        char digit = '0';
-        int mask   = 1 << (i - 1);
-        if ((tileX & mask) != 0) {
-            digit++;
-        }
-        if ((tileY & mask) != 0) {
-            digit++;
-            digit++;
-        }
-        quadKey.append(digit);
-    }
-    return quadKey;
-}
-
-//-----------------------------------------------------------------------------
-int
-UrlFactory::_getServerNum(int x, int y, int max)
-{
-    return (x + 2 * y) % max;
-}
-
-//-----------------------------------------------------------------------------
-#ifndef QGC_NO_GOOGLE_MAPS
-void
-UrlFactory::_networkReplyError(QNetworkReply::NetworkError error)
-{
-    qWarning() << "Could not connect to google maps. Error:" << error;
-    if(_googleReply)
-    {
-        _googleReply->deleteLater();
-        _googleReply = nullptr;
-    }
-}
-#endif
-//-----------------------------------------------------------------------------
-#ifndef QGC_NO_GOOGLE_MAPS
-void
-UrlFactory::_replyDestroyed()
-{
-    _googleReply = nullptr;
-}
-#endif
 
 //-----------------------------------------------------------------------------
-#ifndef QGC_NO_GOOGLE_MAPS
-void
-UrlFactory::_googleVersionCompleted()
-{
-    if (!_googleReply || (_googleReply->error() != QNetworkReply::NoError)) {
-        qDebug() << "Error collecting Google maps version info";
-        return;
-    }
-    QString html = QString(_googleReply->readAll());
 
-#if defined(DEBUG_GOOGLE_MAPS)
-    QString filename = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
-    filename += "/google.output";
-    QFile file(filename);
-    if (file.open(QIODevice::ReadWrite))
-    {
-        QTextStream stream( &file );
-        stream << html << endl;
-    }
-#endif
-
-    QRegExp reg("\"*https?://mt\\D?\\d..*/vt\\?lyrs=m@(\\d*)", Qt::CaseInsensitive);
-    if (reg.indexIn(html) != -1) {
-        QStringList gc = reg.capturedTexts();
-        _versionGoogleMap = QString("m@%1").arg(gc[1]);
-    }
-    reg = QRegExp("\"*https?://khm\\D?\\d.googleapis.com/kh\\?v=(\\d*)", Qt::CaseInsensitive);
-    if (reg.indexIn(html) != -1) {
-        QStringList gc = reg.capturedTexts();
-        _versionGoogleSatellite = 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();
-        _versionGoogleTerrain = QString("t@%1,r@%2").arg(gc[1]).arg(gc[2]);
-    }
-    _googleReply->deleteLater();
-    _googleReply = nullptr;
-}
-#endif
-
-//-----------------------------------------------------------------------------
-#ifndef QGC_NO_GOOGLE_MAPS
-void
-UrlFactory::_tryCorrectGoogleVersions(QNetworkAccessManager* networkManager)
-{
-    QMutexLocker locker(&_googleVersionMutex);
-    if (_googleVersionRetrieved) {
-        return;
-    }
-    _googleVersionRetrieved = 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.google.com/maps/api/js?v=3.2&sensor=false";
-        qheader.setUrl(QUrl(url));
-        QByteArray ua;
-        ua.append(getQGCMapEngine()->userAgent());
-        qheader.setRawHeader("User-Agent", ua);
-        _googleReply = networkManager->get(qheader);
-        connect(_googleReply, &QNetworkReply::finished, this, &UrlFactory::_googleVersionCompleted);
-        connect(_googleReply, &QNetworkReply::destroyed, this, &UrlFactory::_replyDestroyed);
-        connect(_googleReply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
-                this, &UrlFactory::_networkReplyError);
-        networkManager->setProxy(proxy);
-    }
-}
 #endif
 
-#define AVERAGE_GOOGLE_STREET_MAP   4913
-#define AVERAGE_GOOGLE_TERRAIN_MAP  19391
-#define AVERAGE_BING_STREET_MAP     1297
-#define AVERAGE_BING_SAT_MAP        19597
-#define AVERAGE_GOOGLE_SAT_MAP      56887
-#define AVERAGE_MAPBOX_SAT_MAP      15739
-#define AVERAGE_MAPBOX_STREET_MAP   5648
-#define AVERAGE_TILE_SIZE           13652
-#define AVERAGE_AIRMAP_ELEV_SIZE    2786
 
 //-----------------------------------------------------------------------------
 quint32
 UrlFactory::averageSizeForType(MapType type)
 {
-    switch (type) {
-    case GoogleMap:
-        return AVERAGE_GOOGLE_STREET_MAP;
-    case BingMap:
-        return AVERAGE_BING_STREET_MAP;
-    case GoogleSatellite:
-        return AVERAGE_GOOGLE_SAT_MAP;
-    case MapboxSatellite:
-        return AVERAGE_MAPBOX_SAT_MAP;
-    case BingHybrid:
-    case BingSatellite:
-        return AVERAGE_BING_SAT_MAP;
-    case GoogleTerrain:
-        return AVERAGE_GOOGLE_TERRAIN_MAP;
-    case MapboxStreets:
-    case MapboxStreetsBasic:
-    case MapboxRunBikeHike:
-        return AVERAGE_MAPBOX_STREET_MAP;
-    case AirmapElevation:
-        return AVERAGE_AIRMAP_ELEV_SIZE;
-    case GoogleLabels:
-    case MapboxDark:
-    case MapboxLight:
-    case MapboxOutdoors:
-    case MapboxPencil:
-    case OpenStreetMap:
-    case GoogleHybrid:
-    case MapboxComic:
-    case MapboxEmerald:
-    case MapboxHighContrast:
-    case MapboxHybrid:
-    case MapboxPirates:
-    case MapboxWheatPaste:
-    default:
-        break;
-    }
-    return AVERAGE_TILE_SIZE;
+    Q_UNUSED(type);
+    return GoogleMapProvider::getAverageSize();
+
+//    switch (type) {
+//    case GoogleMap:
+//        return AVERAGE_GOOGLE_STREET_MAP;
+//    case BingMap:
+//        return AVERAGE_BING_STREET_MAP;
+//    case GoogleSatellite:
+//        return AVERAGE_GOOGLE_SAT_MAP;
+//    case MapboxSatellite:
+//        return AVERAGE_MAPBOX_SAT_MAP;
+//    case BingHybrid:
+//    case BingSatellite:
+//        return AVERAGE_BING_SAT_MAP;
+//    case GoogleTerrain:
+//        return AVERAGE_GOOGLE_TERRAIN_MAP;
+//    case MapboxStreets:
+//    case MapboxStreetsBasic:
+//    case MapboxRunBikeHike:
+//        return AVERAGE_MAPBOX_STREET_MAP;
+//    case AirmapElevation:
+//        return AVERAGE_AIRMAP_ELEV_SIZE;
+//    case GoogleLabels:
+//    case MapboxDark:
+//    case MapboxLight:
+//    case MapboxOutdoors:
+//    case MapboxPencil:
+//    case OpenStreetMap:
+//    case GoogleHybrid:
+//    case MapboxComic:
+//    case MapboxEmerald:
+//    case MapboxHighContrast:
+//    case MapboxHybrid:
+//    case MapboxPirates:
+//    case MapboxWheatPaste:
+//    default:
+//        break;
+//    }
+//    return AVERAGE_TILE_SIZE;
 }
diff --git a/src/QtLocationPlugin/QGCMapUrlEngine.h b/src/QtLocationPlugin/QGCMapUrlEngine.h
index a4ba347..ac20da0 100644
--- a/src/QtLocationPlugin/QGCMapUrlEngine.h
+++ b/src/QtLocationPlugin/QGCMapUrlEngine.h
@@ -16,12 +16,7 @@
 #ifndef QGC_MAP_URL_ENGINE_H
 #define QGC_MAP_URL_ENGINE_H
 
-#include <QString>
-#include <QPoint>
-#include <QByteArray>
-#include <QNetworkProxy>
-#include <QNetworkReply>
-#include <QMutex>
+#include "GoogleMapProvider.h"
 
 #define MAX_MAP_ZOOM (20.0)
 
@@ -90,41 +85,16 @@ public:
 
     static quint32  averageSizeForType  (MapType type);
 
-private slots:
-#ifndef QGC_NO_GOOGLE_MAPS
-    void    _networkReplyError          (QNetworkReply::NetworkError error);
-    void    _googleVersionCompleted     ();
-    void    _replyDestroyed             ();
-#endif
 
-private:
-    QString _getURL                     (MapType type, int x, int y, int zoom, QNetworkAccessManager* networkManager);
-    QString _tileXYToQuadKey            (int tileX, int tileY, int levelOfDetail);
-    int     _getServerNum               (int x, int y, int max);
-#ifndef QGC_NO_GOOGLE_MAPS
-    void    _getSecGoogleWords          (int x, int y, QString& sec1, QString& sec2);
-    void    _tryCorrectGoogleVersions   (QNetworkAccessManager* networkManager);
-#endif
+//private:
+//    QString _getURL                     (MapType type, int x, int y, int zoom, QNetworkAccessManager* networkManager);
 
 private:
     int             _timeout;
-    QByteArray      _userAgent;
-    QString         _language;
-
-    // Google version strings
-#ifndef QGC_NO_GOOGLE_MAPS
-    bool            _googleVersionRetrieved;
-    QNetworkReply*  _googleReply;
-    QMutex          _googleVersionMutex;
-    QString         _versionGoogleMap;
-    QString         _versionGoogleSatellite;
-    QString         _versionGoogleLabels;
-    QString         _versionGoogleTerrain;
-    QString         _secGoogleWord;
-#endif
 
     // BingMaps
-    QString         _versionBingMaps;
+    //QString         _versionBingMaps;
+    GoogleMapProvider*   _googleMapProvider;
 
 };