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
placemanagerengine_esri.cpp
Go to the documentation of this file.
1// Copyright (C) 2013-2018 Esri <contracts@esri.com>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
7
8#include <QJsonDocument>
9#include <QJsonObject>
10#include <QJsonArray>
11#include <QtCore/QUrlQuery>
12
13#include <QtPositioning/QGeoRectangle>
14#include <QtPositioning/QGeoShape>
15
16#include <QtLocation/QPlaceCategory>
17#include <QtLocation/QPlaceSearchRequest>
18
20
21// https://developers.arcgis.com/rest/geocode/api-reference/geocoding-find-address-candidates.htm
22// https://developers.arcgis.com/rest/geocode/api-reference/geocoding-category-filtering.htm
23// https://developers.arcgis.com/rest/geocode/api-reference/geocoding-service-output.htm
24
25static const QString kCategoriesKey(QStringLiteral("categories"));
26static const QString kSingleLineKey(QStringLiteral("singleLine"));
27static const QString kLocationKey(QStringLiteral("location"));
28static const QString kNameKey(QStringLiteral("name"));
29static const QString kOutFieldsKey(QStringLiteral("outFields"));
30static const QString kCandidateFieldsKey(QStringLiteral("candidateFields"));
31static const QString kCountriesKey(QStringLiteral("detailedCountries"));
32static const QString kLocalizedNamesKey(QStringLiteral("localizedNames"));
33static const QString kMaxLocationsKey(QStringLiteral("maxLocations"));
34
36 "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer?f=pjson");
37static const QUrl kUrlFindAddressCandidates("https://geocode.arcgis.com/arcgis/rest/services/World/"
38 "GeocodeServer/findAddressCandidates");
39
40PlaceManagerEngineEsri::PlaceManagerEngineEsri(const QVariantMap &parameters, QGeoServiceProvider::Error *error,
41 QString *errorString) :
42 QPlaceManagerEngine(parameters),
43 m_networkManager(new QNetworkAccessManager(this))
44{
45 *error = QGeoServiceProvider::NoError;
46 errorString->clear();
47}
48
52
54{
55 return m_locales;
56}
57
58void PlaceManagerEngineEsri::setLocales(const QList<QLocale> &locales)
59{
60 m_locales = locales;
61}
62
63/***** Search *****/
64
65QPlaceSearchReply *PlaceManagerEngineEsri::search(const QPlaceSearchRequest &request)
66{
67 bool unsupported = false;
68
69 // Only public visibility supported
70 unsupported |= request.visibilityScope() != QLocation::UnspecifiedVisibility &&
71 request.visibilityScope() != QLocation::PublicVisibility;
72 unsupported |= request.searchTerm().isEmpty() && request.categories().isEmpty();
73
74 if (unsupported)
75 return QPlaceManagerEngine::search(request);
76
77 QUrlQuery queryItems;
78 queryItems.addQueryItem(QStringLiteral("f"), QStringLiteral("json"));
79
80 const QGeoCoordinate center = request.searchArea().center();
81 if (center.isValid())
82 {
83 const QString location = QString("%1,%2").arg(center.longitude()).arg(center.latitude());
84 queryItems.addQueryItem(kLocationKey, location);
85 }
86
87 const QGeoRectangle boundingBox = request.searchArea().boundingGeoRectangle();
88 if (!boundingBox.isEmpty())
89 {
90 const QString searchExtent = QString("%1,%2,%3,%4")
91 .arg(boundingBox.topLeft().longitude())
92 .arg(boundingBox.topLeft().latitude())
93 .arg(boundingBox.bottomRight().longitude())
94 .arg(boundingBox.bottomRight().latitude());
95 queryItems.addQueryItem(QStringLiteral("searchExtent"), searchExtent);
96 }
97
98 if (!request.searchTerm().isEmpty())
99 queryItems.addQueryItem(kSingleLineKey, request.searchTerm());
100
101 QStringList categories;
102 if (!request.categories().isEmpty())
103 {
104 for (const QPlaceCategory &placeCategory : request.categories())
105 categories.append(placeCategory.categoryId());
106 queryItems.addQueryItem("category", categories.join(","));
107 }
108
109 if (request.limit() > 0)
110 queryItems.addQueryItem(kMaxLocationsKey, QString::number(request.limit()));
111
112 queryItems.addQueryItem(kOutFieldsKey, QStringLiteral("*"));
113
114 QUrl requestUrl(kUrlFindAddressCandidates);
115 requestUrl.setQuery(queryItems);
116
117 QNetworkRequest networkRequest(requestUrl);
118 networkRequest.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
119 QNetworkReply *networkReply = m_networkManager->get(networkRequest);
120
121 PlaceSearchReplyEsri *reply = new PlaceSearchReplyEsri(request, networkReply, m_candidateFieldsLocale, m_countriesLocale, this);
122 connect(reply, &PlaceSearchReplyEsri::finished,
123 this, &PlaceManagerEngineEsri::replyFinished);
124 connect(reply, &PlaceSearchReplyEsri::errorOccurred,
125 this, &PlaceManagerEngineEsri::replyError);
126
127 return reply;
128}
129
130void PlaceManagerEngineEsri::replyFinished()
131{
132 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
133 if (reply)
134 emit finished(reply);
135}
136
137void PlaceManagerEngineEsri::replyError(QPlaceReply::Error errorCode, const QString &errorString)
138{
139 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
140 if (reply)
141 emit errorOccurred(reply, errorCode, errorString);
142}
143
144/***** Categories *****/
145
147{
148 initializeGeocodeServer();
149
150 PlaceCategoriesReplyEsri *reply = new PlaceCategoriesReplyEsri(this);
151 connect(reply, &PlaceCategoriesReplyEsri::finished,
152 this, &PlaceManagerEngineEsri::replyFinished);
153 connect(reply, &PlaceCategoriesReplyEsri::errorOccurred,
154 this, &PlaceManagerEngineEsri::replyError);
155
156 // TODO delayed finished() emission
157 if (!m_categories.isEmpty())
158 reply->emitFinished();
159
160 m_pendingCategoriesReply.append(reply);
161 return reply;
162}
163
164void PlaceManagerEngineEsri::parseCategories(const QJsonArray &jsonArray, const QString &parentCategoryId)
165{
166 for (const QJsonValueConstRef jsonValue : jsonArray)
167 {
168 if (!jsonValue.isObject())
169 continue;
170
171 const QJsonObject jsonCategory = jsonValue.toObject();
172 const QString key = jsonCategory.value(kNameKey).toString();
173 const QString localeName = localizedName(jsonCategory);
174
175 if (key.isEmpty())
176 continue;
177
178 QPlaceCategory category;
179 category.setCategoryId(key);
180 category.setName(localeName.isEmpty() ? key : localeName); // localizedNames
181 m_categories.insert(key, category);
182 m_subcategories[parentCategoryId].append(key);
183 m_parentCategory.insert(key, parentCategoryId);
184 emit categoryAdded(category, parentCategoryId);
185
186 if (jsonCategory.contains(kCategoriesKey))
187 {
188 const QJsonArray jsonArray = jsonCategory.value(kCategoriesKey).toArray();
189 parseCategories(jsonArray, key);
190 }
191 }
192}
193
194QString PlaceManagerEngineEsri::parentCategoryId(const QString &categoryId) const
195{
196 return m_parentCategory.value(categoryId);
197}
198
199QStringList PlaceManagerEngineEsri::childCategoryIds(const QString &categoryId) const
200{
201 return m_subcategories.value(categoryId);
202}
203
204QPlaceCategory PlaceManagerEngineEsri::category(const QString &categoryId) const
205{
206 return m_categories.value(categoryId);
207}
208
210{
211 QList<QPlaceCategory> categories;
212 for (const QString &id : m_subcategories.value(parentId))
213 categories.append(m_categories.value(id));
214 return categories;
215}
216
217void PlaceManagerEngineEsri::finishCategories()
218{
219 for (PlaceCategoriesReplyEsri *reply : m_pendingCategoriesReply)
220 reply->emitFinished();
221 m_pendingCategoriesReply.clear();
222}
223
224void PlaceManagerEngineEsri::errorCaterogies(const QString &error)
225{
226 for (PlaceCategoriesReplyEsri *reply : m_pendingCategoriesReply)
227 reply->setError(QPlaceReply::CommunicationError, error);
228}
229
230/***** GeocodeServer *****/
231
232void PlaceManagerEngineEsri::initializeGeocodeServer()
233{
234 // Only fetch categories once
235 if (m_categories.isEmpty() && !m_geocodeServerReply)
236 {
237 m_geocodeServerReply = m_networkManager->get(QNetworkRequest(kUrlGeocodeServer));
238 connect(m_geocodeServerReply, &QNetworkReply::finished,
239 this, &PlaceManagerEngineEsri::geocodeServerReplyFinished);
240 connect(m_geocodeServerReply, &QNetworkReply::errorOccurred,
241 this, &PlaceManagerEngineEsri::geocodeServerReplyError);
242 }
243}
244
245QString PlaceManagerEngineEsri::localizedName(const QJsonObject &jsonObject)
246{
247 const QJsonObject localizedNames = jsonObject.value(kLocalizedNamesKey).toObject();
248
249 for (const QLocale &locale : std::as_const(m_locales)) {
250 const QString localeStr = locale.name();
251 if (localizedNames.contains(localeStr))
252 {
253 return localizedNames.value(localeStr).toString();
254 }
255
256 const QString shortLocale = localeStr.left(2);
257 if (localizedNames.contains(shortLocale))
258 {
259 return localizedNames.value(shortLocale).toString();
260 }
261 }
262 return QString();
263}
264
265void PlaceManagerEngineEsri::parseCandidateFields(const QJsonArray &jsonArray)
266{
267 for (const QJsonValueConstRef jsonValue : jsonArray)
268 {
269 if (!jsonValue.isObject())
270 continue;
271
272 const QJsonObject jsonCandidateField = jsonValue.toObject();
273 if (!jsonCandidateField.contains(kLocalizedNamesKey))
274 continue;
275
276 const QString key = jsonCandidateField.value(kNameKey).toString();
277 m_candidateFieldsLocale.insert(key, localizedName(jsonCandidateField));
278 }
279}
280
281void PlaceManagerEngineEsri::parseCountries(const QJsonArray &jsonArray)
282{
283 for (const QJsonValueConstRef jsonValue : jsonArray)
284 {
285 if (!jsonValue.isObject())
286 continue;
287
288 const QJsonObject jsonCountry = jsonValue.toObject();
289 if (!jsonCountry.contains(kLocalizedNamesKey))
290 continue;
291
292 const QString key = jsonCountry.value(kNameKey).toString();
293 m_countriesLocale.insert(key, localizedName(jsonCountry));
294 }
295}
296
297void PlaceManagerEngineEsri::geocodeServerReplyFinished()
298{
299 if (!m_geocodeServerReply)
300 return;
301
302 QJsonDocument document = QJsonDocument::fromJson(m_geocodeServerReply->readAll());
303 if (!document.isObject())
304 {
305 errorCaterogies(m_geocodeServerReply->errorString());
306 return;
307 }
308
309 QJsonObject jsonObject = document.object();
310
311 // parse categories
312 if (jsonObject.contains(kCategoriesKey))
313 {
314 const QJsonArray jsonArray = jsonObject.value(kCategoriesKey).toArray();
315 parseCategories(jsonArray, QString());
316 }
317
318 // parse candidateFields
319 if (jsonObject.contains(kCandidateFieldsKey))
320 {
321 const QJsonArray jsonArray = jsonObject.value(kCandidateFieldsKey).toArray();
322 parseCandidateFields(jsonArray);
323 }
324
325 // parse countries
326 if (jsonObject.contains(kCountriesKey))
327 {
328 const QJsonArray jsonArray = jsonObject.value(kCountriesKey).toArray();
329 parseCountries(jsonArray);
330 }
331
332 finishCategories();
333
334 m_geocodeServerReply->deleteLater();
335}
336
337void PlaceManagerEngineEsri::geocodeServerReplyError()
338{
339 if (m_categories.isEmpty() && !m_geocodeServerReply)
340 return;
341
342 errorCaterogies(m_geocodeServerReply->errorString());
343}
344
345QT_END_NAMESPACE
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.
QPlaceSearchReply * search(const QPlaceSearchRequest &request) 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.
QList< QLocale > locales() const override
Returns a list of preferred locales.
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.
QString parentCategoryId(const QString &categoryId) const override
Returns the parent category identifier of the category corresponding to categoryId.
static const QString kSingleLineKey(QStringLiteral("singleLine"))
static const QString kCountriesKey(QStringLiteral("detailedCountries"))
static const QString kLocalizedNamesKey(QStringLiteral("localizedNames"))
static const QString kOutFieldsKey(QStringLiteral("outFields"))
static const QString kCandidateFieldsKey(QStringLiteral("candidateFields"))
static const QString kMaxLocationsKey(QStringLiteral("maxLocations"))
static QT_BEGIN_NAMESPACE const QString kCategoriesKey(QStringLiteral("categories"))
static const QString kLocationKey(QStringLiteral("location"))
static const QUrl kUrlFindAddressCandidates("https://geocode.arcgis.com/arcgis/rest/services/World/" "GeocodeServer/findAddressCandidates")
static const QString kNameKey(QStringLiteral("name"))
static const QUrl kUrlGeocodeServer("https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer?f=pjson")