Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qplacemanagerenginemapbox.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 Mapbox, Inc.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
9
10#include <QtCore/QUrlQuery>
11#include <QtCore/QXmlStreamReader>
12#include <QtCore/QRegularExpression>
13#include <QtNetwork/QNetworkAccessManager>
14#include <QtNetwork/QNetworkRequest>
15#include <QtNetwork/QNetworkReply>
16#include <QtPositioning/QGeoCircle>
17#include <QtLocation/QPlaceCategory>
18#include <QtLocation/QPlaceSearchRequest>
19#include <QtLocation/private/unsupportedreplies_p.h>
20
21#include <QtCore/QElapsedTimer>
22
23namespace {
24
25// https://www.mapbox.com/api-documentation/#poi-categories
26static const QStringList categories = QStringList()
27 << QStringLiteral("bakery")
28 << QStringLiteral("bank")
29 << QStringLiteral("bar")
30 << QStringLiteral("cafe")
31 << QStringLiteral("church")
32 << QStringLiteral("cinema")
33 << QStringLiteral("coffee")
34 << QStringLiteral("concert")
35 << QStringLiteral("fast food")
36 << QStringLiteral("finance")
37 << QStringLiteral("gallery")
38 << QStringLiteral("historic")
39 << QStringLiteral("hotel")
40 << QStringLiteral("landmark")
41 << QStringLiteral("museum")
42 << QStringLiteral("music")
43 << QStringLiteral("park")
44 << QStringLiteral("pizza")
45 << QStringLiteral("restaurant")
46 << QStringLiteral("retail")
47 << QStringLiteral("school")
48 << QStringLiteral("shop")
49 << QStringLiteral("tea")
50 << QStringLiteral("theater")
51 << QStringLiteral("university");
52
53} // namespace
54
55// Mapbox API does not provide support for paginated place queries. This
56// implementation is a wrapper around its Geocoding service:
57// https://www.mapbox.com/api-documentation/#geocoding
58QPlaceManagerEngineMapbox::QPlaceManagerEngineMapbox(const QVariantMap &parameters, QGeoServiceProvider::Error *error, QString *errorString)
59 : QPlaceManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this))
60{
61 if (parameters.contains(QStringLiteral("mapbox.useragent")))
62 m_userAgent = parameters.value(QStringLiteral("mapbox.useragent")).toString().toLatin1();
63 else
64 m_userAgent = mapboxDefaultUserAgent;
65
66 m_accessToken = parameters.value(QStringLiteral("mapbox.access_token")).toString();
67
68 m_isEnterprise = parameters.value(QStringLiteral("mapbox.enterprise")).toBool();
69 m_urlPrefix = m_isEnterprise ? mapboxGeocodingEnterpriseApiPath : mapboxGeocodingApiPath;
70
71 *error = QGeoServiceProvider::NoError;
72 errorString->clear();
73}
74
78
79QPlaceSearchReply *QPlaceManagerEngineMapbox::search(const QPlaceSearchRequest &request)
80{
81 return qobject_cast<QPlaceSearchReply *>(doSearch(request, PlaceSearchType::CompleteSearch));
82}
83
85{
86 return qobject_cast<QPlaceSearchSuggestionReply *>(doSearch(request, PlaceSearchType::SuggestionSearch));
87}
88
89QPlaceReply *QPlaceManagerEngineMapbox::doSearch(const QPlaceSearchRequest &request, PlaceSearchType searchType)
90{
91 const QGeoShape searchArea = request.searchArea();
92 const QString searchTerm = request.searchTerm();
93 const QString recommendationId = request.recommendationId();
94 const QList<QPlaceCategory> placeCategories = request.categories();
95
96 bool invalidRequest = false;
97
98 // QLocation::DeviceVisibility is not allowed for non-enterprise accounts.
99 if (!m_isEnterprise)
100 invalidRequest |= request.visibilityScope().testFlag(QLocation::DeviceVisibility);
101
102 // Must provide either a search term, categories or recommendation.
103 invalidRequest |= searchTerm.isEmpty() && placeCategories.isEmpty() && recommendationId.isEmpty();
104
105 // Category search must not provide recommendation, and vice-versa.
106 invalidRequest |= searchTerm.isEmpty() && !placeCategories.isEmpty() && !recommendationId.isEmpty();
107
108 if (invalidRequest) {
109 QPlaceReply *reply;
110 if (searchType == PlaceSearchType::CompleteSearch)
111 reply = new QPlaceSearchReplyMapbox(request, 0, this);
112 else
113 reply = new QPlaceSearchSuggestionReplyMapbox(0, this);
114
115 connect(reply, &QPlaceReply::finished,
116 this, &QPlaceManagerEngineMapbox::onReplyFinished);
117 connect(reply, &QPlaceReply::errorOccurred,
118 this, &QPlaceManagerEngineMapbox::onReplyError);
119
120 QMetaObject::invokeMethod(reply, "setError", Qt::QueuedConnection,
121 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
122 Q_ARG(QString, "Invalid request."));
123
124 return reply;
125 }
126
127 QString queryString;
128 if (!searchTerm.isEmpty()) {
129 queryString = searchTerm;
130 } else if (!recommendationId.isEmpty()) {
131 queryString = recommendationId;
132 } else {
133 QStringList similarIds;
134 for (const QPlaceCategory &placeCategory : placeCategories)
135 similarIds.append(placeCategory.categoryId());
136 queryString = similarIds.join(QLatin1Char(','));
137 }
138 queryString.append(QStringLiteral(".json"));
139
140 // https://www.mapbox.com/api-documentation/#request-format
141 QUrl requestUrl(m_urlPrefix + queryString);
142
143 QUrlQuery queryItems;
144 queryItems.addQueryItem(QStringLiteral("access_token"), m_accessToken);
145
146 // XXX: Investigate situations where we need to filter by 'country'.
147
148 QStringList languageCodes;
149 for (const QLocale& locale: std::as_const(m_locales)) {
150 // Returns the language and country of this locale as a string of the
151 // form "language_country", where language is a lowercase, two-letter
152 // ISO 639 language code, and country is an uppercase, two- or
153 // three-letter ISO 3166 country code.
154
155 if (locale.language() == QLocale::C)
156 continue;
157
158 const QString languageCode = locale.name().section(QLatin1Char('_'), 0, 0);
159 if (!languageCodes.contains(languageCode))
160 languageCodes.append(languageCode);
161 }
162
163 if (!languageCodes.isEmpty())
164 queryItems.addQueryItem(QStringLiteral("language"), languageCodes.join(QLatin1Char(',')));
165
166 if (searchArea.type() != QGeoShape::UnknownType) {
167 const QGeoCoordinate center = searchArea.center();
168 queryItems.addQueryItem(QStringLiteral("proximity"),
169 QString::number(center.longitude()) + QLatin1Char(',') + QString::number(center.latitude()));
170 }
171
172 queryItems.addQueryItem(QStringLiteral("type"), QStringLiteral("poi"));
173
174 // XXX: Investigate situations where 'autocomplete' should be disabled.
175
176 QGeoRectangle boundingBox = searchArea.boundingGeoRectangle();
177 if (!boundingBox.isEmpty()) {
178 queryItems.addQueryItem(QStringLiteral("bbox"),
179 QString::number(boundingBox.topLeft().longitude()) + QLatin1Char(',') +
180 QString::number(boundingBox.bottomRight().latitude()) + QLatin1Char(',') +
181 QString::number(boundingBox.bottomRight().longitude()) + QLatin1Char(',') +
182 QString::number(boundingBox.topLeft().latitude()));
183 }
184
185 if (request.limit() > 0)
186 queryItems.addQueryItem(QStringLiteral("limit"), QString::number(request.limit()));
187
188 // XXX: Investigate searchContext() use cases.
189
190 requestUrl.setQuery(queryItems);
191
192 QNetworkRequest networkRequest(requestUrl);
193 networkRequest.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent);
194
195 QNetworkReply *networkReply = m_networkManager->get(networkRequest);
196 QPlaceReply *reply;
197 if (searchType == PlaceSearchType::CompleteSearch)
198 reply = new QPlaceSearchReplyMapbox(request, networkReply, this);
199 else
200 reply = new QPlaceSearchSuggestionReplyMapbox(networkReply, this);
201
202 connect(reply, &QPlaceReply::finished,
203 this, &QPlaceManagerEngineMapbox::onReplyFinished);
204 connect(reply, &QPlaceReply::errorOccurred,
205 this, &QPlaceManagerEngineMapbox::onReplyError);
206
207 return reply;
208}
209
211{
212 if (m_categories.isEmpty()) {
213 for (const QString &categoryId : categories) {
214 QPlaceCategory category;
215 category.setName(QMapboxCommon::mapboxNameForCategory(categoryId));
216 category.setCategoryId(categoryId);
217 category.setVisibility(QLocation::PublicVisibility);
218 m_categories[categoryId] = category;
219 }
220 }
221
222 QPlaceCategoriesReplyMapbox *reply = new QPlaceCategoriesReplyMapbox(this);
223 connect(reply, &QPlaceReply::finished,
224 this, &QPlaceManagerEngineMapbox::onReplyFinished);
225 connect(reply, &QPlaceReply::errorOccurred,
226 this, &QPlaceManagerEngineMapbox::onReplyError);
227
228 // Queue a future finished() emission from the reply.
229 QMetaObject::invokeMethod(reply, "finish", Qt::QueuedConnection);
230
231 return reply;
232}
233
234QString QPlaceManagerEngineMapbox::parentCategoryId(const QString &categoryId) const
235{
236 Q_UNUSED(categoryId);
237
238 // Only a single category level.
239 return QString();
240}
241
243{
244 // Only a single category level.
245 if (categoryId.isEmpty())
246 return m_categories.keys();
247
248 return QStringList();
249}
250
251QPlaceCategory QPlaceManagerEngineMapbox::category(const QString &categoryId) const
252{
253 return m_categories.value(categoryId);
254}
255
257{
258 // Only a single category level.
259 if (parentId.isEmpty())
260 return m_categories.values();
261
262 return QList<QPlaceCategory>();
263}
264
266{
267 return m_locales;
268}
269
270void QPlaceManagerEngineMapbox::setLocales(const QList<QLocale> &locales)
271{
272 m_locales = locales;
273}
274
275void QPlaceManagerEngineMapbox::onReplyFinished()
276{
277 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
278 if (reply)
279 emit finished(reply);
280}
281
282void QPlaceManagerEngineMapbox::onReplyError(QPlaceReply::Error errorCode, const QString &errorString)
283{
284 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
285 if (reply)
286 emit errorOccurred(reply, errorCode, errorString);
287}
QPlaceReply * initializeCategories() override
Initializes the categories of the manager engine.
QStringList childCategoryIds(const QString &categoryId) const override
Returns the child category identifiers of the category corresponding to categoryId.
QList< QLocale > locales() const override
Returns a list of preferred locales.
QPlaceSearchReply * search(const QPlaceSearchRequest &) override
Searches for places according to the parameters specified in request.
QList< QPlaceCategory > childCategories(const QString &parentId) const override
Returns a list of categories that are children of the category corresponding to parentId.
QString parentCategoryId(const QString &categoryId) const override
Returns the parent category identifier of the category corresponding to categoryId.
void setLocales(const QList< QLocale > &locales) override
Set the list of preferred locales.
QPlaceCategory category(const QString &categoryId) const override
Returns the category corresponding to the given categoryId.
QPlaceSearchSuggestionReply * searchSuggestions(const QPlaceSearchRequest &) override
Requests a set of search term suggestions according to the parameters specified in request.