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