6#include "placesv2/qplacecategoriesreplyhere.h"
7#include "placesv2/qplacecontentreplyimpl.h"
8#include "placesv2/qplacesearchsuggestionreplyimpl.h"
9#include "placesv2/qplacesearchreplyhere.h"
10#include "placesv2/qplacedetailsreplyimpl.h"
16#include <QCoreApplication>
17#include <QtCore/QFile>
18#include <QtCore/QJsonArray>
19#include <QtCore/QJsonDocument>
20#include <QtCore/QJsonObject>
21#include <QtCore/QRegularExpression>
22#include <QtCore/QStandardPaths>
23#include <QtCore/QUrlQuery>
24#include <QtNetwork/QNetworkProxy>
25#include <QtNetwork/QNetworkProxyFactory>
27#include <QtLocation/QPlace>
28#include <QtLocation/QPlaceContentRequest>
29#include <QtLocation/QPlaceDetailsReply>
30#include <QtLocation/QPlaceIcon>
31#include <QtLocation/QPlaceSearchRequest>
32#include <QtPositioning/QGeoCircle>
44 "administrative-areas-buildings\0"
45 "natural-geographical\0"
49 "hospital-health-care-facility\0"
50 "eat-drink|restaurant\0"
51 "eat-drink|coffee-tea\0"
52 "eat-drink|snacks-fast-food\0"
57 0, 10, 20, 35, 45, 59, 68, 84,
58 115, 136, 151, 169, 186, 216, 237, 258,
73 bool parse(
const QString &fileName);
81 void processCategory(
int level,
const QString &id,
82 const QString &parentId = QString());
84 QJsonObject m_exploreObject;
86 QString m_errorString;
88 QHash<QString, QUrl> m_restIdToIconHash;
97 m_exploreObject = QJsonObject();
99 m_errorString.clear();
101 QFile mappingFile(fileName);
103 if (mappingFile.open(QIODevice::ReadOnly)) {
104 QJsonDocument document = QJsonDocument::fromJson(mappingFile.readAll());
105 if (document.isObject()) {
106 QJsonObject docObject = document.object();
107 if (docObject.contains(QStringLiteral(
"offline_explore"))) {
108 m_exploreObject = docObject.value(QStringLiteral(
"offline_explore"))
110 if (m_exploreObject.contains(QStringLiteral(
"ROOT"))) {
111 processCategory(0, QString());
115 m_errorString = fileName
116 + QStringLiteral(
"does not contain the offline_explore property");
120 m_errorString = fileName + QStringLiteral(
"is not an json object");
124 m_errorString = QString::fromLatin1(
"Unable to open ") + fileName;
128void CategoryParser::processCategory(
int level,
const QString &id,
const QString &parentId)
141 const int maxLevel = 2;
143 node.category.setCategoryId(id);
144 node.parentId = parentId;
146 m_tree.insert(node.category.categoryId(), node);
150 QJsonObject categoryJson = m_exploreObject.value(id.isEmpty()
151 ? QStringLiteral(
"ROOT") : id).toObject();
152 QJsonArray children = categoryJson.value(QStringLiteral(
"children")).toArray();
154 if (level + 1 <= maxLevel && !categoryJson.contains(QStringLiteral(
"final"))) {
155 for (
int i = 0; i < children.count(); ++i) {
156 QString childId = children.at(i).toString();
157 if (!m_tree.contains(childId)) {
158 node.childIds.append(childId);
159 processCategory(level + 1, childId, id);
164 m_tree.insert(node.category.categoryId(), node);
169 const QVariantMap ¶meters,
170 QGeoServiceProvider::Error *error,
171 QString *errorString)
172 : QPlaceManagerEngine(parameters)
173 , m_manager(networkManager)
174 , m_uriProvider(
new QGeoUriProvider(
this, parameters, QStringLiteral(
"here.places.host"), PLACES_HOST))
176 Q_ASSERT(networkManager);
177 m_manager->setParent(
this);
179 m_locales.append(QLocale());
181 m_apiKey = parameters.value(QStringLiteral(
"here.apiKey")).toString();
183 m_theme = parameters.value(IconThemeKey, QString()).toString();
185 if (m_theme == QStringLiteral(
"default"))
188 m_localDataPath = parameters.value(LocalDataPathKey, QString()).toString();
189 if (m_localDataPath.isEmpty()) {
190 QStringList dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
192 if (!dataLocations.isEmpty() && !dataLocations.first().isEmpty()) {
193 m_localDataPath = dataLocations.first()
194 + QStringLiteral(
"/here/qtlocation/data");
199 *error = QGeoServiceProvider::NoError;
202 errorString->clear();
209 QUrl requestUrl(QString::fromLatin1(
"https://") + m_uriProvider->getCurrentHost() +
210 QStringLiteral(
"/places/v1/places/") + placeId);
212 QUrlQuery queryItems;
214 queryItems.addQueryItem(QStringLiteral(
"tf"), QStringLiteral(
"html"));
218 requestUrl.setQuery(queryItems);
220 QNetworkReply *networkReply = sendRequest(requestUrl);
223 reply->setPlaceId(placeId);
224 connect(reply, &QPlaceDetailsReplyImpl::finished,
225 this, &QPlaceManagerEngineNokiaV2::replyFinished);
226 connect(reply, &QPlaceDetailsReplyImpl::errorOccurred,
227 this, &QPlaceManagerEngineNokiaV2::replyError);
234 QNetworkReply *networkReply =
nullptr;
236 if (request.contentContext().userType() == qMetaTypeId<QUrl>()) {
237 QUrl u = request.contentContext().value<QUrl>();
239 networkReply = sendRequest(u);
241 QUrl requestUrl(QString::fromLatin1(
"https://") + m_uriProvider->getCurrentHost() +
242 QStringLiteral(
"/places/v1/places/") + request.placeId() +
243 QStringLiteral(
"/media/"));
245 QUrlQuery queryItems;
247 switch (request.contentType()) {
248 case QPlaceContent::ImageType:
249 requestUrl.setPath(requestUrl.path() + QStringLiteral(
"images"));
251 queryItems.addQueryItem(QStringLiteral(
"tf"), QStringLiteral(
"html"));
253 if (request.limit() > 0)
254 queryItems.addQueryItem(QStringLiteral(
"size"), QString::number(request.limit()));
258 requestUrl.setQuery(queryItems);
260 networkReply = sendRequest(requestUrl);
262 case QPlaceContent::ReviewType:
263 requestUrl.setPath(requestUrl.path() + QStringLiteral(
"reviews"));
265 queryItems.addQueryItem(QStringLiteral(
"tf"), QStringLiteral(
"html"));
267 if (request.limit() > 0)
268 queryItems.addQueryItem(QStringLiteral(
"size"), QString::number(request.limit()));
270 requestUrl.setQuery(queryItems);
272 networkReply = sendRequest(requestUrl);
274 case QPlaceContent::EditorialType:
275 requestUrl.setPath(requestUrl.path() + QStringLiteral(
"editorials"));
277 queryItems.addQueryItem(QStringLiteral(
"tf"), QStringLiteral(
"html"));
279 if (request.limit() > 0)
280 queryItems.addQueryItem(QStringLiteral(
"size"), QString::number(request.limit()));
282 requestUrl.setQuery(queryItems);
284 networkReply = sendRequest(requestUrl);
286 case QPlaceContent::NoType:
293 connect(reply, &QPlaceContentReply::finished,
294 this, &QPlaceManagerEngineNokiaV2::replyFinished);
295 connect(reply, &QPlaceContentReply::errorOccurred,
296 this, &QPlaceManagerEngineNokiaV2::replyError);
299 QMetaObject::invokeMethod(reply,
"setError", Qt::QueuedConnection,
300 Q_ARG(QPlaceReply::Error, QPlaceReply::UnsupportedError),
301 Q_ARG(QString, QString(
"Retrieval of given content type not supported.")));
308 QUrlQuery *queryItems)
310 QGeoCoordinate center = area.center();
311 if (!center.isValid())
314 queryItems->addQueryItem(QStringLiteral(
"at"),
315 QString::number(center.latitude()) +
317 QString::number(center.longitude()));
323 bool unsupported =
false;
325 unsupported |= query.visibilityScope() != QLocation::UnspecifiedVisibility &&
326 query.visibilityScope() != QLocation::PublicVisibility;
329 unsupported |= !query.searchTerm().isEmpty() && !query.categories().isEmpty();
332 unsupported |= !query.recommendationId().isEmpty()
333 && (!query.searchTerm().isEmpty() || !query.categories().isEmpty()
334 || query.searchArea().type() != QGeoShape::UnknownType);
338 connect(reply, &QPlaceSearchReplyHere::finished,
339 this, &QPlaceManagerEngineNokiaV2::replyFinished);
340 connect(reply, &QPlaceSearchReplyHere::errorOccurred,
341 this, &QPlaceManagerEngineNokiaV2::replyError);
342 QMetaObject::invokeMethod(reply,
"setError", Qt::QueuedConnection,
343 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
344 Q_ARG(QString,
"Unsupported search request options specified."));
348 QUrlQuery queryItems;
352 if (query.recommendationId().isEmpty() && !query.searchContext().isValid()) {
353 if (!addAtForBoundingArea(query.searchArea(), &queryItems)) {
355 connect(reply, &QPlaceSearchReplyHere::finished,
356 this, &QPlaceManagerEngineNokiaV2::replyFinished);
357 connect(reply, &QPlaceSearchReplyHere::errorOccurred,
358 this, &QPlaceManagerEngineNokiaV2::replyError);
359 QMetaObject::invokeMethod(reply,
"setError", Qt::QueuedConnection,
360 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
361 Q_ARG(QString,
"Invalid search area provided"));
366 QNetworkReply *networkReply =
nullptr;
368 if (query.searchContext().userType() == qMetaTypeId<QUrl>()) {
370 QUrl u = query.searchContext().value<QUrl>();
372 typedef QPair<QString, QString> QueryItem;
373 const QList<QueryItem> queryItemList = queryItems.queryItems(QUrl::FullyEncoded);
374 queryItems = QUrlQuery(u);
375 for (
const QueryItem &item : queryItemList)
376 queryItems.addQueryItem(item.first, item.second);
378 if (query.limit() > 0)
379 queryItems.addQueryItem(QStringLiteral(
"size"), QString::number(query.limit()));
381 u.setQuery(queryItems);
383 networkReply = sendRequest(u);
384 }
else if (!query.searchTerm().isEmpty()) {
386 QUrl requestUrl(QString::fromLatin1(
"https://") + m_uriProvider->getCurrentHost() +
387 QStringLiteral(
"/places/v1/discover/search"));
389 queryItems.addQueryItem(QStringLiteral(
"q"), query.searchTerm());
390 queryItems.addQueryItem(QStringLiteral(
"tf"), QStringLiteral(
"html"));
392 if (query.limit() > 0) {
393 queryItems.addQueryItem(QStringLiteral(
"size"),
394 QString::number(query.limit()));
397 requestUrl.setQuery(queryItems);
399 QNetworkReply *networkReply = sendRequest(requestUrl);
402 connect(reply, &QPlaceSearchReplyHere::finished,
403 this, &QPlaceManagerEngineNokiaV2::replyFinished);
404 connect(reply, &QPlaceSearchReplyHere::errorOccurred,
405 this, &QPlaceManagerEngineNokiaV2::replyError);
408 }
else if (!query.recommendationId().isEmpty()) {
409 QUrl requestUrl(QString::fromLatin1(
"https://") + m_uriProvider->getCurrentHost() +
410 QStringLiteral(
"/places/v1/places/") + query.recommendationId() +
411 QStringLiteral(
"/related/recommended"));
413 queryItems.addQueryItem(QStringLiteral(
"tf"), QStringLiteral(
"html"));
415 requestUrl.setQuery(queryItems);
417 networkReply = sendRequest(requestUrl);
420 QUrl requestUrl(QStringLiteral(
"https://") + m_uriProvider->getCurrentHost() +
421 QStringLiteral(
"/places/v1/discover/explore"));
424 for (
const QPlaceCategory &category : query.categories())
425 ids.append(category.categoryId());
427 QUrlQuery queryItems;
430 queryItems.addQueryItem(QStringLiteral(
"cat"), ids.join(QStringLiteral(
",")));
432 addAtForBoundingArea(query.searchArea(), &queryItems);
434 queryItems.addQueryItem(QStringLiteral(
"tf"), QStringLiteral(
"html"));
436 if (query.limit() > 0) {
437 queryItems.addQueryItem(QStringLiteral(
"size"),
438 QString::number(query.limit()));
441 requestUrl.setQuery(queryItems);
443 networkReply = sendRequest(requestUrl);
447 connect(reply, &QPlaceSearchReplyHere::finished,
448 this, &QPlaceManagerEngineNokiaV2::replyFinished);
449 connect(reply, &QPlaceSearchReplyHere::errorOccurred,
450 this, &QPlaceManagerEngineNokiaV2::replyError);
457 bool unsupported =
false;
459 unsupported |= query.visibilityScope() != QLocation::UnspecifiedVisibility &&
460 query.visibilityScope() != QLocation::PublicVisibility;
462 unsupported |= !query.categories().isEmpty();
463 unsupported |= !query.recommendationId().isEmpty();
466 QPlaceSearchSuggestionReplyImpl *reply =
new QPlaceSearchSuggestionReplyImpl(0,
this);
467 connect(reply, &QPlaceSearchSuggestionReplyImpl::finished,
468 this, &QPlaceManagerEngineNokiaV2::replyFinished);
469 connect(reply, &QPlaceSearchSuggestionReplyImpl::errorOccurred,
470 this, &QPlaceManagerEngineNokiaV2::replyError);
471 QMetaObject::invokeMethod(reply,
"setError", Qt::QueuedConnection,
472 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
473 Q_ARG(QString,
"Unsupported search request options specified."));
477 QUrl requestUrl(QString::fromLatin1(
"https://") + m_uriProvider->getCurrentHost() +
478 QStringLiteral(
"/places/v1/suggest"));
480 QUrlQuery queryItems;
482 queryItems.addQueryItem(QStringLiteral(
"q"), query.searchTerm());
484 if (!addAtForBoundingArea(query.searchArea(), &queryItems)) {
485 QPlaceSearchSuggestionReplyImpl *reply =
new QPlaceSearchSuggestionReplyImpl(0,
this);
486 connect(reply, &QPlaceSearchSuggestionReplyImpl::finished,
487 this, &QPlaceManagerEngineNokiaV2::replyFinished);
488 connect(reply, &QPlaceSearchSuggestionReplyImpl::errorOccurred,
489 this, &QPlaceManagerEngineNokiaV2::replyError);
490 QMetaObject::invokeMethod(reply,
"setError", Qt::QueuedConnection,
491 Q_ARG(QPlaceReply::Error, QPlaceReply::BadArgumentError),
492 Q_ARG(QString,
"Invalid search area provided"));
496 requestUrl.setQuery(queryItems);
498 QNetworkReply *networkReply = sendRequest(requestUrl);
500 QPlaceSearchSuggestionReplyImpl *reply =
new QPlaceSearchSuggestionReplyImpl(networkReply,
this);
501 connect(reply, &QPlaceSearchSuggestionReplyImpl::finished,
502 this, &QPlaceManagerEngineNokiaV2::replyFinished);
503 connect(reply, &QPlaceSearchSuggestionReplyImpl::errorOccurred,
504 this, &QPlaceManagerEngineNokiaV2::replyError);
512 return m_categoryReply.data();
517 if (parser.parse(m_localDataPath + QStringLiteral(
"/offline/offline-mapping.json"))) {
523 const QString id = QString::fromLatin1(FIXED_CATEGORIES_string +
524 FIXED_CATEGORIES_indices[i]);
526 int subCatDivider = id.indexOf(QChar(
'|'));
527 if (subCatDivider >= 0) {
529 const QString subCategoryId = id.mid(subCatDivider+1);
530 const QString parentCategoryId = id.left(subCatDivider);
532 if (m_tempTree.contains(parentCategoryId)) {
534 node.category.setCategoryId(subCategoryId);
535 node.parentId = parentCategoryId;
539 parent.childIds.append(subCategoryId);
540 m_tempTree.insert(subCategoryId, node);
545 node.category.setCategoryId(id);
547 m_tempTree.insert(id, node);
548 rootNode.childIds.append(id);
552 m_tempTree.insert(QString(), rootNode);
557 for (
auto it = m_tempTree.keyBegin(), end = m_tempTree.keyEnd(); it != end; ++it) {
558 if (*it == QString())
560 QUrl requestUrl(QString::fromLatin1(
"https://") + m_uriProvider->getCurrentHost() +
561 QStringLiteral(
"/places/v1/categories/places/") + *it);
562 QNetworkReply *networkReply = sendRequest(requestUrl);
563 connect(networkReply, &QNetworkReply::finished,
564 this, &QPlaceManagerEngineNokiaV2::categoryReplyFinished);
565 connect(networkReply, &QNetworkReply::errorOccurred,
566 this, &QPlaceManagerEngineNokiaV2::categoryReplyError);
568 m_categoryRequests.insert(*it, networkReply);
571 QPlaceCategoriesReplyHere *reply =
new QPlaceCategoriesReplyHere(
this);
572 connect(reply, &QPlaceCategoriesReplyHere::finished,
573 this, &QPlaceManagerEngineNokiaV2::replyFinished);
574 connect(reply, &QPlaceCategoriesReplyHere::errorOccurred,
575 this, &QPlaceManagerEngineNokiaV2::replyError);
577 m_categoryReply = reply;
583 return m_categoryTree.value(categoryId).parentId;
588 return m_categoryTree.value(categoryId).childIds;
593 return m_categoryTree.value(categoryId).category;
598 QList<QPlaceCategory> results;
599 for (
const QString &childId : m_categoryTree.value(parentId).childIds)
600 results.append(m_categoryTree.value(childId).category);
615 const QList<QPlaceCategory> &categories)
const
620 QRegularExpression rx(
"(.*)(/icons/categories/.*)");
621 QRegularExpressionMatch match = rx.match(remotePath);
625 if (match.hasMatch() && !match.capturedView(1).isEmpty() && !match.capturedView(2).isEmpty()) {
626 iconPrefix = match.captured(1);
627 nokiaIcon = match.captured(2);
629 if (QFile::exists(m_localDataPath + nokiaIcon))
630 iconPrefix = QString::fromLatin1(
"file://") + m_localDataPath;
635 for (
const QPlaceCategory &category : categories) {
636 if (category.icon().parameters().value(NokiaIcon) == nokiaIcon) {
637 params.insert(NokiaIconGenerated,
true);
642 QString path = remotePath + (!m_theme.isEmpty()
643 ? QLatin1Char(
'.') + m_theme : QString());
644 params.insert(QPlaceIcon::SingleUrl, QUrl(path));
646 if (!nokiaIcon.isEmpty()) {
653 icon.setParameters(params);
656 icon.setManager(manager());
662 const QSize &size)
const
665 QVariantMap params = icon.parameters();
666 QString nokiaIcon = params.value(
NokiaIcon).toString();
668 if (!nokiaIcon.isEmpty()) {
669 nokiaIcon.append(!m_theme.isEmpty() ?
670 QLatin1Char(
'.') + m_theme : QString());
673 return QUrl(params.value(
IconPrefix).toString() +
676 return QUrl(QString::fromLatin1(
"file://") + m_localDataPath
686 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
688 emit finished(reply);
693 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
695 emit errorOccurred(reply, error_, errorString);
700 QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
706 if (reply->error() == QNetworkReply::NoError) {
707 QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
708 if (!document.isObject()) {
709 if (m_categoryReply) {
710 QMetaObject::invokeMethod(m_categoryReply.data(),
"setError", Qt::QueuedConnection,
711 Q_ARG(QPlaceReply::Error, QPlaceReply::ParseError),
712 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, PARSE_ERROR)));
717 QJsonObject category = document.object();
719 categoryId = category.value(QStringLiteral(
"categoryId")).toString();
720 if (m_tempTree.contains(categoryId)) {
722 node.category.setName(category.value(QStringLiteral(
"name")).toString());
723 node.category.setCategoryId(categoryId);
724 node.category.setIcon(icon(category.value(QStringLiteral(
"icon")).toString()));
726 m_tempTree.insert(categoryId, node);
729 categoryId = m_categoryRequests.key(reply);
731 rootNode.childIds.removeAll(categoryId);
732 m_tempTree.insert(QString(), rootNode);
733 m_tempTree.remove(categoryId);
736 m_categoryRequests.remove(categoryId);
737 reply->deleteLater();
739 if (m_categoryRequests.isEmpty()) {
740 m_categoryTree = m_tempTree;
744 m_categoryReply.data()->emitFinished();
750 if (m_categoryReply) {
751 QMetaObject::invokeMethod(m_categoryReply.data(),
"setError", Qt::QueuedConnection,
752 Q_ARG(QPlaceReply::Error, QPlaceReply::CommunicationError),
753 Q_ARG(QString, QCoreApplication::translate(NOKIA_PLUGIN_CONTEXT_NAME, NETWORK_ERROR)));
759 QUrlQuery queryItems(url);
760 queryItems.addQueryItem(QStringLiteral(
"apiKey"), m_apiKey);
762 QUrl requestUrl = url;
763 requestUrl.setQuery(queryItems);
765 QNetworkRequest request;
766 request.setUrl(requestUrl);
768 request.setRawHeader(
"Accept",
"application/json");
769 request.setRawHeader(
"Accept-Language", createLanguageString());
771 return m_manager->get(request);
778 QList<QLocale> locales = m_locales;
779 if (locales.isEmpty())
780 locales << QLocale();
782 for (
const QLocale &loc : std::as_const(locales)) {
783 language.append(loc.name().replace(2, 1, QLatin1Char(
'-')).toLatin1());
784 language.append(
", ");
bool parse(const QString &fileName)
QHash< QString, QUrl > restIdToIconHash() const
QString errorString() const
QPlaceCategoryTree tree() const
QStringList childCategoryIds(const QString &categoryId) const override
Returns the child category identifiers of the category corresponding to categoryId.
QString parentCategoryId(const QString &categoryId) const override
Returns the parent category identifier of the category corresponding to categoryId.
QPlaceSearchSuggestionReply * searchSuggestions(const QPlaceSearchRequest &query) override
Requests a set of search term suggestions according to the parameters specified in request.
QPlaceReply * initializeCategories() override
Initializes the categories of the manager engine.
QPlaceDetailsReply * getPlaceDetails(const QString &placeId) override
Retrieves details of place corresponding to the given placeId.
QList< QPlaceCategory > childCategories(const QString &parentId) const override
Returns a list of categories that are children of the category corresponding to parentId.
QUrl constructIconUrl(const QPlaceIcon &icon, const QSize &size) const override
QUrl QPlaceManagerEngine::constructIconUrl(const QPlaceIcon &icon, const QSize &size)
QPlaceSearchReply * search(const QPlaceSearchRequest &query) override
Searches for places according to the parameters specified in request.
void setLocales(const QList< QLocale > &locales) override
Set the list of preferred locales.
QList< QLocale > locales() const override
Returns a list of preferred locales.
QPlaceCategory category(const QString &categoryId) const override
Returns the category corresponding to the given categoryId.
QPlaceIcon icon(const QString &remotePath, const QList< QPlaceCategory > &categories=QList< QPlaceCategory >()) const
~QPlaceManagerEngineNokiaV2()
static const char *const LocalDataPathKey
static bool addAtForBoundingArea(const QGeoShape &area, QUrlQuery *queryItems)
static const char *const IconThemeKey
static const char *const IconPrefix
static const char *const NokiaIcon
static QT_BEGIN_NAMESPACE const char FIXED_CATEGORIES_string[]
static const int FIXED_CATEGORIES_indices[]
static const char *const NokiaIconGenerated
QMap< QString, PlaceCategoryNode > QPlaceCategoryTree