From 0cdf4748dd40ec2e0df20eda4a63e8e070d2b790 Mon Sep 17 00:00:00 2001 From: Guillaume Belz Date: Tue, 9 Oct 2018 14:45:45 +0100 Subject: Esri: enable place search Change-Id: I136ef0a3125b42b484e36413d26aa49e59a43b57 Reviewed-by: Paolo Angelelli --- .../geoservices/esri/placemanagerengine_esri.cpp | 369 +++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 src/plugins/geoservices/esri/placemanagerengine_esri.cpp (limited to 'src/plugins/geoservices/esri/placemanagerengine_esri.cpp') diff --git a/src/plugins/geoservices/esri/placemanagerengine_esri.cpp b/src/plugins/geoservices/esri/placemanagerengine_esri.cpp new file mode 100644 index 00000000..8f973ff3 --- /dev/null +++ b/src/plugins/geoservices/esri/placemanagerengine_esri.cpp @@ -0,0 +1,369 @@ +/**************************************************************************** +** +** Copyright (C) 2013-2018 Esri +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation 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 "placemanagerengine_esri.h" +#include "placesearchreply_esri.h" +#include "placecategoriesreply_esri.h" + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +// https://developers.arcgis.com/rest/geocode/api-reference/geocoding-find-address-candidates.htm +// https://developers.arcgis.com/rest/geocode/api-reference/geocoding-category-filtering.htm +// https://developers.arcgis.com/rest/geocode/api-reference/geocoding-service-output.htm + +static const QString kCategoriesKey(QStringLiteral("categories")); +static const QString kSingleLineKey(QStringLiteral("singleLine")); +static const QString kLocationKey(QStringLiteral("location")); +static const QString kNameKey(QStringLiteral("name")); +static const QString kOutFieldsKey(QStringLiteral("outFields")); +static const QString kCandidateFieldsKey(QStringLiteral("candidateFields")); +static const QString kCountriesKey(QStringLiteral("detailedCountries")); +static const QString kLocalizedNamesKey(QStringLiteral("localizedNames")); +static const QString kMaxLocationsKey(QStringLiteral("maxLocations")); + +static const QUrl kUrlGeocodeServer("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer?f=pjson"); +static const QUrl kUrlFindAddressCandidates("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates"); + +PlaceManagerEngineEsri::PlaceManagerEngineEsri(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, + QString *errorString) : + QPlaceManagerEngine(parameters), + m_networkManager(new QNetworkAccessManager(this)) +{ + *error = QGeoServiceProvider::NoError; + errorString->clear(); +} + +PlaceManagerEngineEsri::~PlaceManagerEngineEsri() +{ +} + +QList PlaceManagerEngineEsri::locales() const +{ + return m_locales; +} + +void PlaceManagerEngineEsri::setLocales(const QList &locales) +{ + m_locales = locales; +} + +/***** Search *****/ + +QPlaceSearchReply *PlaceManagerEngineEsri::search(const QPlaceSearchRequest &request) +{ + bool unsupported = false; + + // Only public visibility supported + unsupported |= request.visibilityScope() != QLocation::UnspecifiedVisibility && + request.visibilityScope() != QLocation::PublicVisibility; + unsupported |= request.searchTerm().isEmpty() && request.categories().isEmpty(); + + if (unsupported) + return QPlaceManagerEngine::search(request); + + QUrlQuery queryItems; + queryItems.addQueryItem(QStringLiteral("f"), QStringLiteral("json")); + + const QGeoCoordinate center = request.searchArea().center(); + if (center.isValid()) + { + const QString location = QString("%1,%2").arg(center.longitude()).arg(center.latitude()); + queryItems.addQueryItem(kLocationKey, location); + } + + const QGeoRectangle boundingBox = request.searchArea().boundingGeoRectangle(); + if (!boundingBox.isEmpty()) + { + const QString searchExtent = QString("%1,%2,%3,%4") + .arg(boundingBox.topLeft().longitude()) + .arg(boundingBox.topLeft().latitude()) + .arg(boundingBox.bottomRight().longitude()) + .arg(boundingBox.bottomRight().latitude()); + queryItems.addQueryItem(QStringLiteral("searchExtent"), searchExtent); + } + + if (!request.searchTerm().isEmpty()) + queryItems.addQueryItem(kSingleLineKey, request.searchTerm()); + + QStringList categories; + if (!request.categories().isEmpty()) + { + foreach (const QPlaceCategory &placeCategory, request.categories()) + categories.append(placeCategory.categoryId()); + queryItems.addQueryItem("category", categories.join(",")); + } + + if (request.limit() > 0) + queryItems.addQueryItem(kMaxLocationsKey, QString::number(request.limit())); + + queryItems.addQueryItem(kOutFieldsKey, QStringLiteral("*")); + + QUrl requestUrl(kUrlFindAddressCandidates); + requestUrl.setQuery(queryItems); + + QNetworkRequest networkRequest(requestUrl); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + QNetworkReply *networkReply = m_networkManager->get(networkRequest); + + PlaceSearchReplyEsri *reply = new PlaceSearchReplyEsri(request, networkReply, m_candidateFieldsLocale, m_countriesLocale, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), this, SLOT(replyError(QPlaceReply::Error,QString))); + + return reply; +} + +void PlaceManagerEngineEsri::replyFinished() +{ + QPlaceReply *reply = qobject_cast(sender()); + if (reply) + emit finished(reply); +} + +void PlaceManagerEngineEsri::replyError(QPlaceReply::Error errorCode, const QString &errorString) +{ + QPlaceReply *reply = qobject_cast(sender()); + if (reply) + emit error(reply, errorCode, errorString); +} + +/***** Categories *****/ + +QPlaceReply *PlaceManagerEngineEsri::initializeCategories() +{ + initializeGeocodeServer(); + + PlaceCategoriesReplyEsri *reply = new PlaceCategoriesReplyEsri(this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), this, SLOT(replyError(QPlaceReply::Error,QString))); + + // TODO delayed finished() emission + if (!m_categories.isEmpty()) + reply->emitFinished(); + + m_pendingCategoriesReply.append(reply); + return reply; +} + +void PlaceManagerEngineEsri::parseCategories(const QJsonArray &jsonArray, const QString &parentCategoryId) +{ + foreach (const QJsonValue &jsonValue, jsonArray) + { + if (!jsonValue.isObject()) + continue; + + const QJsonObject jsonCategory = jsonValue.toObject(); + const QString key = jsonCategory.value(kNameKey).toString(); + const QString localeName = localizedName(jsonCategory); + + if (key.isEmpty()) + continue; + + QPlaceCategory category; + category.setCategoryId(key); + category.setName(localeName.isEmpty() ? key : localeName); // localizedNames + m_categories.insert(key, category); + m_subcategories[parentCategoryId].append(key); + m_parentCategory.insert(key, parentCategoryId); + emit categoryAdded(category, parentCategoryId); + + if (jsonCategory.contains(kCategoriesKey)) + { + const QJsonArray jsonArray = jsonCategory.value(kCategoriesKey).toArray(); + parseCategories(jsonArray, key); + } + } +} + +QString PlaceManagerEngineEsri::parentCategoryId(const QString &categoryId) const +{ + return m_parentCategory.value(categoryId); +} + +QStringList PlaceManagerEngineEsri::childCategoryIds(const QString &categoryId) const +{ + return m_subcategories.value(categoryId); +} + +QPlaceCategory PlaceManagerEngineEsri::category(const QString &categoryId) const +{ + return m_categories.value(categoryId); +} + +QList PlaceManagerEngineEsri::childCategories(const QString &parentId) const +{ + QList categories; + foreach (const QString &id, m_subcategories.value(parentId)) + categories.append(m_categories.value(id)); + return categories; +} + +void PlaceManagerEngineEsri::finishCategories() +{ + foreach (PlaceCategoriesReplyEsri *reply, m_pendingCategoriesReply) + reply->emitFinished(); + m_pendingCategoriesReply.clear(); +} + +void PlaceManagerEngineEsri::errorCaterogies(const QString &error) +{ + foreach (PlaceCategoriesReplyEsri *reply, m_pendingCategoriesReply) + reply->setError(QPlaceReply::CommunicationError, error); +} + +/***** GeocodeServer *****/ + +void PlaceManagerEngineEsri::initializeGeocodeServer() +{ + // Only fetch categories once + if (m_categories.isEmpty() && !m_geocodeServerReply) + { + m_geocodeServerReply = m_networkManager->get(QNetworkRequest(kUrlGeocodeServer)); + connect(m_geocodeServerReply, SIGNAL(finished()), this, SLOT(geocodeServerReplyFinished())); + connect(m_geocodeServerReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(geocodeServerReplyError())); + } +} + +QString PlaceManagerEngineEsri::localizedName(const QJsonObject &jsonObject) +{ + const QJsonObject localizedNames = jsonObject.value(kLocalizedNamesKey).toObject(); + + foreach (const QLocale &locale, m_locales) + { + const QString localeStr = locale.name(); + if (localizedNames.contains(localeStr)) + { + return localizedNames.value(localeStr).toString(); + } + + const QString shortLocale = localeStr.left(2); + if (localizedNames.contains(shortLocale)) + { + return localizedNames.value(shortLocale).toString(); + } + } + return QString(); +} + +void PlaceManagerEngineEsri::parseCandidateFields(const QJsonArray &jsonArray) +{ + foreach (const QJsonValue &jsonValue, jsonArray) + { + if (!jsonValue.isObject()) + continue; + + const QJsonObject jsonCandidateField = jsonValue.toObject(); + if (!jsonCandidateField.contains(kLocalizedNamesKey)) + continue; + + const QString key = jsonCandidateField.value(kNameKey).toString(); + m_candidateFieldsLocale.insert(key, localizedName(jsonCandidateField)); + } +} + +void PlaceManagerEngineEsri::parseCountries(const QJsonArray &jsonArray) +{ + foreach (const QJsonValue &jsonValue, jsonArray) + { + if (!jsonValue.isObject()) + continue; + + const QJsonObject jsonCountry = jsonValue.toObject(); + if (!jsonCountry.contains(kLocalizedNamesKey)) + continue; + + const QString key = jsonCountry.value(kNameKey).toString(); + m_countriesLocale.insert(key, localizedName(jsonCountry)); + } +} + +void PlaceManagerEngineEsri::geocodeServerReplyFinished() +{ + if (!m_geocodeServerReply) + return; + + QJsonDocument document = QJsonDocument::fromJson(m_geocodeServerReply->readAll()); + if (!document.isObject()) + { + errorCaterogies(m_geocodeServerReply->errorString()); + return; + } + + QJsonObject jsonObject = document.object(); + + // parse categories + if (jsonObject.contains(kCategoriesKey)) + { + const QJsonArray jsonArray = jsonObject.value(kCategoriesKey).toArray(); + parseCategories(jsonArray, QString()); + } + + // parse candidateFields + if (jsonObject.contains(kCandidateFieldsKey)) + { + const QJsonArray jsonArray = jsonObject.value(kCandidateFieldsKey).toArray(); + parseCandidateFields(jsonArray); + } + + // parse countries + if (jsonObject.contains(kCountriesKey)) + { + const QJsonArray jsonArray = jsonObject.value(kCountriesKey).toArray(); + parseCountries(jsonArray); + } + + finishCategories(); + + m_geocodeServerReply->deleteLater(); +} + +void PlaceManagerEngineEsri::geocodeServerReplyError() +{ + if (m_categories.isEmpty() && !m_geocodeServerReply) + return; + + errorCaterogies(m_geocodeServerReply->errorString()); +} + +QT_END_NAMESPACE -- cgit v1.2.1