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
qplacemanagerengineosm.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Aaron McCarthy <mccarthy.aaron@gmail.com>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:network-protocol
4
8
9#include <QtCore/QElapsedTimer>
10#include <QtCore/QLocale>
11#include <QtCore/QRegularExpression>
12#include <QtCore/QUrlQuery>
13#include <QtCore/QXmlStreamReader>
14
15#include <QtNetwork/QNetworkAccessManager>
16#include <QtNetwork/QNetworkRequest>
17#include <QtNetwork/QNetworkReply>
18
19#include <QtPositioning/QGeoCircle>
20
21#include <QtLocation/QPlaceCategory>
22#include <QtLocation/QPlaceSearchRequest>
23#include <QtLocation/private/unsupportedreplies_p.h>
24
25namespace
26{
27QString SpecialPhrasesBaseUrl = QStringLiteral("http://wiki.openstreetmap.org/wiki/Special:Export/Nominatim/Special_Phrases/");
28
29QString nameForTagKey(const QString &tagKey)
30{
31 if (tagKey == QLatin1String("aeroway"))
32 return QPlaceManagerEngineOsm::tr("Aeroway");
33 else if (tagKey == QLatin1String("amenity"))
34 return QPlaceManagerEngineOsm::tr("Amenity");
35 else if (tagKey == QLatin1String("building"))
36 return QPlaceManagerEngineOsm::tr("Building");
37 else if (tagKey == QLatin1String("highway"))
38 return QPlaceManagerEngineOsm::tr("Highway");
39 else if (tagKey == QLatin1String("historic"))
40 return QPlaceManagerEngineOsm::tr("Historic");
41 else if (tagKey == QLatin1String("landuse"))
42 return QPlaceManagerEngineOsm::tr("Land use");
43 else if (tagKey == QLatin1String("leisure"))
44 return QPlaceManagerEngineOsm::tr("Leisure");
45 else if (tagKey == QLatin1String("man_made"))
46 return QPlaceManagerEngineOsm::tr("Man made");
47 else if (tagKey == QLatin1String("natural"))
48 return QPlaceManagerEngineOsm::tr("Natural");
49 else if (tagKey == QLatin1String("place"))
50 return QPlaceManagerEngineOsm::tr("Place");
51 else if (tagKey == QLatin1String("railway"))
52 return QPlaceManagerEngineOsm::tr("Railway");
53 else if (tagKey == QLatin1String("shop"))
54 return QPlaceManagerEngineOsm::tr("Shop");
55 else if (tagKey == QLatin1String("tourism"))
56 return QPlaceManagerEngineOsm::tr("Tourism");
57 else if (tagKey == QLatin1String("waterway"))
58 return QPlaceManagerEngineOsm::tr("Waterway");
59 else
60 return tagKey;
61}
62
63}
64
65QPlaceManagerEngineOsm::QPlaceManagerEngineOsm(const QVariantMap &parameters,
66 QGeoServiceProvider::Error *error,
67 QString *errorString)
68: QPlaceManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this)),
69 m_categoriesReply(0)
70{
71 if (parameters.contains(QStringLiteral("osm.useragent")))
72 m_userAgent = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1();
73 else
74 m_userAgent = "Qt Location based application";
75
76 if (parameters.contains(QStringLiteral("osm.places.host")))
77 m_urlPrefix = parameters.value(QStringLiteral("osm.places.host")).toString();
78 else
79 m_urlPrefix = QStringLiteral("http://nominatim.openstreetmap.org/search");
80
81
82 if (parameters.contains(QStringLiteral("osm.places.debug_query")))
83 m_debugQuery = parameters.value(QStringLiteral("osm.places.debug_query")).toBool();
84
85 if (parameters.contains(QStringLiteral("osm.places.page_size"))
86 && parameters.value(QStringLiteral("osm.places.page_size")).canConvert<int>())
87 m_pageSize = parameters.value(QStringLiteral("osm.places.page_size")).toInt();
88
89 *error = QGeoServiceProvider::NoError;
90 errorString->clear();
91}
92
96
97QPlaceSearchReply *QPlaceManagerEngineOsm::search(const QPlaceSearchRequest &request)
98{
99 bool unsupported = false;
100
101 // Only public visibility supported
102 unsupported |= request.visibilityScope() != QLocation::UnspecifiedVisibility &&
103 request.visibilityScope() != QLocation::PublicVisibility;
104 unsupported |= request.searchTerm().isEmpty() && request.categories().isEmpty();
105
106 if (unsupported)
107 return QPlaceManagerEngine::search(request);
108
109 QUrlQuery queryItems;
110
111 queryItems.addQueryItem(QStringLiteral("format"), QStringLiteral("jsonv2"));
112
113 //queryItems.addQueryItem(QStringLiteral("accept-language"), QStringLiteral("en"));
114
115 QGeoRectangle boundingBox = request.searchArea().boundingGeoRectangle();
116
117 if (!boundingBox.isEmpty()) {
118 queryItems.addQueryItem(QStringLiteral("bounded"), QStringLiteral("1"));
119 QString coordinates;
120 coordinates = QString::number(boundingBox.topLeft().longitude()) + QLatin1Char(',') +
121 QString::number(boundingBox.topLeft().latitude()) + QLatin1Char(',') +
122 QString::number(boundingBox.bottomRight().longitude()) + QLatin1Char(',') +
123 QString::number(boundingBox.bottomRight().latitude());
124 queryItems.addQueryItem(QStringLiteral("viewbox"), coordinates);
125 }
126
127 QStringList queryParts;
128 if (!request.searchTerm().isEmpty())
129 queryParts.append(request.searchTerm());
130
131 const auto categoriesList = request.categories();
132 for (const QPlaceCategory &category : categoriesList) {
133 QString id = category.categoryId();
134 queryParts.append(QLatin1Char('[') + id + QLatin1Char(']'));
135 }
136
137 queryItems.addQueryItem(QStringLiteral("q"), queryParts.join(QLatin1Char('+')));
138
139 QVariantMap parameters = request.searchContext().toMap();
140
141 QStringList placeIds = parameters.value(QStringLiteral("ExcludePlaceIds")).toStringList();
142 if (!placeIds.isEmpty())
143 queryItems.addQueryItem(QStringLiteral("exclude_place_ids"), placeIds.join(QLatin1Char(',')));
144
145 queryItems.addQueryItem(QStringLiteral("addressdetails"), QStringLiteral("1"));
146 queryItems.addQueryItem(QStringLiteral("limit"), (request.limit() > 0) ? QString::number(request.limit())
147 : QString::number(m_pageSize));
148
149 QUrl requestUrl(m_urlPrefix);
150 requestUrl.setQuery(queryItems);
151
152 QNetworkRequest rq(requestUrl);
153 rq.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
154 QNetworkReply *networkReply = m_networkManager->get(rq);
155
156 QPlaceSearchReplyOsm *reply = new QPlaceSearchReplyOsm(request, networkReply, this);
157 connect(reply, &QPlaceSearchReplyOsm::finished,
158 this, &QPlaceManagerEngineOsm::replyFinished);
159 connect(reply, &QPlaceSearchReplyOsm::errorOccurred,
160 this, &QPlaceManagerEngineOsm::replyError);
161
162 if (m_debugQuery)
163 reply->requestUrl = requestUrl.url(QUrl::None);
164
165 return reply;
166}
167
169{
170 // Only fetch categories once
171 if (m_categories.isEmpty() && !m_categoriesReply) {
172 m_categoryLocales = m_locales;
173 m_categoryLocales.append(QLocale(QLocale::English));
174 fetchNextCategoryLocale();
175 }
176
177 QPlaceCategoriesReplyOsm *reply = new QPlaceCategoriesReplyOsm(this);
178 connect(reply, &QPlaceCategoriesReplyOsm::finished,
179 this, &QPlaceManagerEngineOsm::replyFinished);
180 connect(reply, &QPlaceCategoriesReplyOsm::errorOccurred,
181 this, &QPlaceManagerEngineOsm::replyError);
182
183 // TODO delayed finished() emission
184 if (!m_categories.isEmpty())
185 reply->emitFinished();
186
187 m_pendingCategoriesReply.append(reply);
188 return reply;
189}
190
191QString QPlaceManagerEngineOsm::parentCategoryId(const QString &categoryId) const
192{
193 Q_UNUSED(categoryId);
194
195 // Only a two category levels
196 return QString();
197}
198
199QStringList QPlaceManagerEngineOsm::childCategoryIds(const QString &categoryId) const
200{
201 return m_subcategories.value(categoryId);
202}
203
204QPlaceCategory QPlaceManagerEngineOsm::category(const QString &categoryId) const
205{
206 return m_categories.value(categoryId);
207}
208
210{
211 QList<QPlaceCategory> categories;
212 const QStringList subcategoriesList = m_subcategories.value(parentId);
213 for (const QString &id : subcategoriesList)
214 categories.append(m_categories.value(id));
215 return categories;
216}
217
219{
220 return m_locales;
221}
222
223void QPlaceManagerEngineOsm::setLocales(const QList<QLocale> &locales)
224{
225 m_locales = locales;
226}
227
228void QPlaceManagerEngineOsm::categoryReplyFinished()
229{
230 QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
231 reply->deleteLater();
232
233 QXmlStreamReader parser(reply);
234 while (!parser.atEnd() && parser.readNextStartElement()) {
235 if (parser.name() == QLatin1String("mediawiki"))
236 continue;
237 if (parser.name() == QLatin1String("page"))
238 continue;
239 if (parser.name() == QLatin1String("revision"))
240 continue;
241 if (parser.name() == QLatin1String("text")) {
242 // parse
243 QString page = parser.readElementText();
244 QRegularExpression regex(QStringLiteral("\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([\\-YN])"));
245 QRegularExpressionMatchIterator i = regex.globalMatch(page);
246 while (i.hasNext()) {
247 QRegularExpressionMatch match = i.next();
248 QString name = match.capturedView(1).toString();
249 QString tagKey = match.capturedView(2).toString();
250 QString tagValue = match.capturedView(3).toString();
251 QString op = match.capturedView(4).toString();
252 QString plural = match.capturedView(5).toString();
253
254 // Only interested in any operator plural forms
255 if (op != QLatin1String("-") || plural != QLatin1String("Y"))
256 continue;
257
258 if (!m_categories.contains(tagKey)) {
259 QPlaceCategory category;
260 category.setCategoryId(tagKey);
261 category.setName(nameForTagKey(tagKey));
262 m_categories.insert(category.categoryId(), category);
263 m_subcategories[QString()].append(tagKey);
264 emit categoryAdded(category, QString());
265 }
266
267 QPlaceCategory category;
268 category.setCategoryId(tagKey + QLatin1Char('=') + tagValue);
269 category.setName(name);
270
271 if (!m_categories.contains(category.categoryId())) {
272 m_categories.insert(category.categoryId(), category);
273 m_subcategories[tagKey].append(category.categoryId());
274 emit categoryAdded(category, tagKey);
275 }
276 }
277 }
278
279 parser.skipCurrentElement();
280 }
281
282 if (m_categories.isEmpty() && !m_categoryLocales.isEmpty()) {
283 fetchNextCategoryLocale();
284 return;
285 } else {
286 m_categoryLocales.clear();
287 }
288
289 for (QPlaceCategoriesReplyOsm *reply : std::as_const(m_pendingCategoriesReply))
290 reply->emitFinished();
291 m_pendingCategoriesReply.clear();
292}
293
294void QPlaceManagerEngineOsm::categoryReplyError()
295{
296 for (QPlaceCategoriesReplyOsm *reply : std::as_const(m_pendingCategoriesReply))
297 reply->setError(QPlaceReply::CommunicationError, tr("Network request error"));
298}
299
300void QPlaceManagerEngineOsm::replyFinished()
301{
302 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
303 if (reply)
304 emit finished(reply);
305}
306
307void QPlaceManagerEngineOsm::replyError(QPlaceReply::Error errorCode, const QString &errorString)
308{
309 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
310 if (reply)
311 emit errorOccurred(reply, errorCode, errorString);
312}
313
314void QPlaceManagerEngineOsm::fetchNextCategoryLocale()
315{
316 if (m_categoryLocales.isEmpty()) {
317 qWarning("No locales specified to fetch categories for");
318 return;
319 }
320
321 QLocale locale = m_categoryLocales.takeFirst();
322
323 // FIXME: Categories should be cached.
324 QUrl requestUrl = QUrl(SpecialPhrasesBaseUrl + locale.name().left(2).toUpper());
325
326 m_categoriesReply = m_networkManager->get(QNetworkRequest(requestUrl));
327 connect(m_categoriesReply, &QNetworkReply::finished,
328 this, &QPlaceManagerEngineOsm::categoryReplyFinished);
329 connect(m_categoriesReply, &QNetworkReply::errorOccurred,
330 this, &QPlaceManagerEngineOsm::categoryReplyError);
331}
QStringList childCategoryIds(const QString &categoryId) const override
Returns the child category identifiers of the category corresponding to categoryId.
QPlaceReply * initializeCategories() override
Initializes the categories of the manager engine.
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.
QPlaceCategory category(const QString &categoryId) const override
Returns the category corresponding to the given categoryId.
QPlaceSearchReply * search(const QPlaceSearchRequest &request) override
Searches for places according to the parameters specified in request.
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.