summaryrefslogtreecommitdiff
path: root/src/plugins/geoservices/mapbox/qplacemanagerenginemapbox.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/geoservices/mapbox/qplacemanagerenginemapbox.cpp')
-rw-r--r--src/plugins/geoservices/mapbox/qplacemanagerenginemapbox.cpp318
1 files changed, 318 insertions, 0 deletions
diff --git a/src/plugins/geoservices/mapbox/qplacemanagerenginemapbox.cpp b/src/plugins/geoservices/mapbox/qplacemanagerenginemapbox.cpp
new file mode 100644
index 00000000..56678a45
--- /dev/null
+++ b/src/plugins/geoservices/mapbox/qplacemanagerenginemapbox.cpp
@@ -0,0 +1,318 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Mapbox, Inc.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtFoo module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qplacemanagerenginemapbox.h"
+#include "qplacesearchreplymapbox.h"
+#include "qplacesearchsuggestionreplymapbox.h"
+#include "qplacecategoriesreplymapbox.h"
+#include "qmapboxcommon.h"
+
+#include <QtCore/QUrlQuery>
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QRegularExpression>
+#include <QtNetwork/QNetworkAccessManager>
+#include <QtNetwork/QNetworkRequest>
+#include <QtNetwork/QNetworkReply>
+#include <QtPositioning/QGeoCircle>
+#include <QtLocation/private/unsupportedreplies_p.h>
+
+#include <QtCore/QElapsedTimer>
+
+namespace {
+
+// https://www.mapbox.com/api-documentation/#poi-categories
+static const QStringList categories = QStringList()
+ << QStringLiteral("bakery")
+ << QStringLiteral("bank")
+ << QStringLiteral("bar")
+ << QStringLiteral("cafe")
+ << QStringLiteral("church")
+ << QStringLiteral("cinema")
+ << QStringLiteral("coffee")
+ << QStringLiteral("concert")
+ << QStringLiteral("fast food")
+ << QStringLiteral("finance")
+ << QStringLiteral("gallery")
+ << QStringLiteral("historic")
+ << QStringLiteral("hotel")
+ << QStringLiteral("landmark")
+ << QStringLiteral("museum")
+ << QStringLiteral("music")
+ << QStringLiteral("park")
+ << QStringLiteral("pizza")
+ << QStringLiteral("restaurant")
+ << QStringLiteral("retail")
+ << QStringLiteral("school")
+ << QStringLiteral("shop")
+ << QStringLiteral("tea")
+ << QStringLiteral("theater")
+ << QStringLiteral("university");
+
+} // namespace
+
+// Mapbox API does not provide support for paginated place queries. This
+// implementation is a wrapper around its Geocoding service:
+// https://www.mapbox.com/api-documentation/#geocoding
+QPlaceManagerEngineMapbox::QPlaceManagerEngineMapbox(const QVariantMap &parameters, QGeoServiceProvider::Error *error, QString *errorString)
+ : QPlaceManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this))
+{
+ if (parameters.contains(QStringLiteral("mapbox.useragent")))
+ m_userAgent = parameters.value(QStringLiteral("mapbox.useragent")).toString().toLatin1();
+ else
+ m_userAgent = mapboxDefaultUserAgent;
+
+ m_accessToken = parameters.value(QStringLiteral("mapbox.access_token")).toString();
+
+ m_isEnterprise = parameters.value(QStringLiteral("mapbox.enterprise")).toBool();
+ m_urlPrefix = m_isEnterprise ? mapboxGeocodingEnterpriseApiPath : mapboxGeocodingApiPath;
+
+ *error = QGeoServiceProvider::NoError;
+ errorString->clear();
+}
+
+QPlaceManagerEngineMapbox::~QPlaceManagerEngineMapbox()
+{
+}
+
+QPlaceSearchReply *QPlaceManagerEngineMapbox::search(const QPlaceSearchRequest &request)
+{
+ return qobject_cast<QPlaceSearchReply *>(doSearch(request, PlaceSearchType::CompleteSearch));
+}
+
+QPlaceSearchSuggestionReply *QPlaceManagerEngineMapbox::searchSuggestions(const QPlaceSearchRequest &request)
+{
+ return qobject_cast<QPlaceSearchSuggestionReply *>(doSearch(request, PlaceSearchType::SuggestionSearch));
+}
+
+QPlaceReply *QPlaceManagerEngineMapbox::doSearch(const QPlaceSearchRequest &request, PlaceSearchType searchType)
+{
+ const QGeoShape searchArea = request.searchArea();
+ const QString searchTerm = request.searchTerm();
+ const QString recommendationId = request.recommendationId();
+ const QList<QPlaceCategory> placeCategories = request.categories();
+
+ bool invalidRequest = false;
+
+ // QLocation::DeviceVisibility is not allowed for non-enterprise accounts.
+ if (!m_isEnterprise)
+ invalidRequest |= request.visibilityScope().testFlag(QLocation::DeviceVisibility);
+
+ // Must provide either a search term, categories or recommendation.
+ invalidRequest |= searchTerm.isEmpty() && placeCategories.isEmpty() && recommendationId.isEmpty();
+
+ // Category search must not provide recommendation, and vice-versa.
+ invalidRequest |= searchTerm.isEmpty() && !placeCategories.isEmpty() && !recommendationId.isEmpty();
+
+ if (invalidRequest) {
+ QPlaceReply *reply;
+ if (searchType == PlaceSearchType::CompleteSearch)
+ reply = new QPlaceSearchReplyMapbox(request, 0, this);
+ else
+ reply = new QPlaceSearchSuggestionReplyMapbox(0, this);
+
+ connect(reply, &QPlaceReply::finished, this, &QPlaceManagerEngineMapbox::onReplyFinished);
+ connect(reply, QOverload<QPlaceReply::Error, const QString &>::of(&QPlaceReply::error),
+ this, &QPlaceManagerEngineMapbox::onReplyError);
+
+ QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection,
+ Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
+ Q_ARG(QString, "Invalid request."));
+
+ return reply;
+ }
+
+ QString queryString;
+ if (!searchTerm.isEmpty()) {
+ queryString = searchTerm;
+ } else if (!recommendationId.isEmpty()) {
+ queryString = recommendationId;
+ } else {
+ QStringList similarIds;
+ for (const QPlaceCategory &placeCategory : placeCategories)
+ similarIds.append(placeCategory.categoryId());
+ queryString = similarIds.join(QLatin1Char(','));
+ }
+ queryString.append(QStringLiteral(".json"));
+
+ // https://www.mapbox.com/api-documentation/#request-format
+ QUrl requestUrl(m_urlPrefix + queryString);
+
+ QUrlQuery queryItems;
+ queryItems.addQueryItem(QStringLiteral("access_token"), m_accessToken);
+
+ // XXX: Investigate situations where we need to filter by 'country'.
+
+ QStringList languageCodes;
+ for (const QLocale& locale: qAsConst(m_locales)) {
+ // Returns the language and country of this locale as a string of the
+ // form "language_country", where language is a lowercase, two-letter
+ // ISO 639 language code, and country is an uppercase, two- or
+ // three-letter ISO 3166 country code.
+
+ if (locale.language() == QLocale::C)
+ continue;
+
+ const QString languageCode = locale.name().section(QLatin1Char('_'), 0, 0);
+ if (!languageCodes.contains(languageCode))
+ languageCodes.append(languageCode);
+ }
+
+ if (!languageCodes.isEmpty())
+ queryItems.addQueryItem(QStringLiteral("language"), languageCodes.join(QLatin1Char(',')));
+
+ if (searchArea.type() != QGeoShape::UnknownType) {
+ const QGeoCoordinate center = searchArea.center();
+ queryItems.addQueryItem(QStringLiteral("proximity"),
+ QString::number(center.longitude()) + QLatin1Char(',') + QString::number(center.latitude()));
+ }
+
+ queryItems.addQueryItem(QStringLiteral("type"), QStringLiteral("poi"));
+
+ // XXX: Investigate situations where 'autocomplete' should be disabled.
+
+ QGeoRectangle boundingBox = searchArea.boundingGeoRectangle();
+ if (!boundingBox.isEmpty()) {
+ queryItems.addQueryItem(QStringLiteral("bbox"),
+ QString::number(boundingBox.topLeft().longitude()) + QLatin1Char(',') +
+ QString::number(boundingBox.bottomRight().latitude()) + QLatin1Char(',') +
+ QString::number(boundingBox.bottomRight().longitude()) + QLatin1Char(',') +
+ QString::number(boundingBox.topLeft().latitude()));
+ }
+
+ if (request.limit() > 0)
+ queryItems.addQueryItem(QStringLiteral("limit"), QString::number(request.limit()));
+
+ // XXX: Investigate searchContext() use cases.
+
+ requestUrl.setQuery(queryItems);
+
+ QNetworkRequest networkRequest(requestUrl);
+ networkRequest.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent);
+
+ QNetworkReply *networkReply = m_networkManager->get(networkRequest);
+ QPlaceReply *reply;
+ if (searchType == PlaceSearchType::CompleteSearch)
+ reply = new QPlaceSearchReplyMapbox(request, networkReply, this);
+ else
+ reply = new QPlaceSearchSuggestionReplyMapbox(networkReply, this);
+
+ connect(reply, &QPlaceReply::finished, this, &QPlaceManagerEngineMapbox::onReplyFinished);
+ connect(reply, QOverload<QPlaceReply::Error, const QString &>::of(&QPlaceReply::error),
+ this, &QPlaceManagerEngineMapbox::onReplyError);
+
+ return reply;
+}
+
+QPlaceReply *QPlaceManagerEngineMapbox::initializeCategories()
+{
+ if (m_categories.isEmpty()) {
+ for (const QString &categoryId : categories) {
+ QPlaceCategory category;
+ category.setName(QMapboxCommon::mapboxNameForCategory(categoryId));
+ category.setCategoryId(categoryId);
+ category.setVisibility(QLocation::PublicVisibility);
+ m_categories[categoryId] = category;
+ }
+ }
+
+ QPlaceCategoriesReplyMapbox *reply = new QPlaceCategoriesReplyMapbox(this);
+ connect(reply, &QPlaceReply::finished, this, &QPlaceManagerEngineMapbox::onReplyFinished);
+ connect(reply, QOverload<QPlaceReply::Error, const QString &>::of(&QPlaceReply::error),
+ this, &QPlaceManagerEngineMapbox::onReplyError);
+
+ // Queue a future finished() emission from the reply.
+ QMetaObject::invokeMethod(reply, "finish", Qt::QueuedConnection);
+
+ return reply;
+}
+
+QString QPlaceManagerEngineMapbox::parentCategoryId(const QString &categoryId) const
+{
+ Q_UNUSED(categoryId)
+
+ // Only a single category level.
+ return QString();
+}
+
+QStringList QPlaceManagerEngineMapbox::childCategoryIds(const QString &categoryId) const
+{
+ // Only a single category level.
+ if (categoryId.isEmpty())
+ return m_categories.keys();
+
+ return QStringList();
+}
+
+QPlaceCategory QPlaceManagerEngineMapbox::category(const QString &categoryId) const
+{
+ return m_categories.value(categoryId);
+}
+
+QList<QPlaceCategory> QPlaceManagerEngineMapbox::childCategories(const QString &parentId) const
+{
+ // Only a single category level.
+ if (parentId.isEmpty())
+ return m_categories.values();
+
+ return QList<QPlaceCategory>();
+}
+
+QList<QLocale> QPlaceManagerEngineMapbox::locales() const
+{
+ return m_locales;
+}
+
+void QPlaceManagerEngineMapbox::setLocales(const QList<QLocale> &locales)
+{
+ m_locales = locales;
+}
+
+void QPlaceManagerEngineMapbox::onReplyFinished()
+{
+ QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
+ if (reply)
+ emit finished(reply);
+}
+
+void QPlaceManagerEngineMapbox::onReplyError(QPlaceReply::Error errorCode, const QString &errorString)
+{
+ QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
+ if (reply)
+ emit error(reply, errorCode, errorString);
+}