1/****************************************************************************
2**
3** Copyright (C) 2016 Aaron McCarthy <mccarthy.aaron@gmail.com>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtFoo module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qplacemanagerengineosm.h"
41#include "qplacesearchreplyosm.h"
42#include "qplacecategoriesreplyosm.h"
43
44#include <QtCore/QUrlQuery>
45#include <QtCore/QXmlStreamReader>
46#include <QtCore/QRegularExpression>
47#include <QtNetwork/QNetworkAccessManager>
48#include <QtNetwork/QNetworkRequest>
49#include <QtNetwork/QNetworkReply>
50#include <QtPositioning/QGeoCircle>
51#include <QtLocation/private/unsupportedreplies_p.h>
52
53#include <QtCore/QElapsedTimer>
54
55namespace
56{
57QString SpecialPhrasesBaseUrl = QStringLiteral("http://wiki.openstreetmap.org/wiki/Special:Export/Nominatim/Special_Phrases/");
58
59QString nameForTagKey(const QString &tagKey)
60{
61 if (tagKey == QLatin1String("aeroway"))
62 return QPlaceManagerEngineOsm::tr(s: "Aeroway");
63 else if (tagKey == QLatin1String("amenity"))
64 return QPlaceManagerEngineOsm::tr(s: "Amenity");
65 else if (tagKey == QLatin1String("building"))
66 return QPlaceManagerEngineOsm::tr(s: "Building");
67 else if (tagKey == QLatin1String("highway"))
68 return QPlaceManagerEngineOsm::tr(s: "Highway");
69 else if (tagKey == QLatin1String("historic"))
70 return QPlaceManagerEngineOsm::tr(s: "Historic");
71 else if (tagKey == QLatin1String("landuse"))
72 return QPlaceManagerEngineOsm::tr(s: "Land use");
73 else if (tagKey == QLatin1String("leisure"))
74 return QPlaceManagerEngineOsm::tr(s: "Leisure");
75 else if (tagKey == QLatin1String("man_made"))
76 return QPlaceManagerEngineOsm::tr(s: "Man made");
77 else if (tagKey == QLatin1String("natural"))
78 return QPlaceManagerEngineOsm::tr(s: "Natural");
79 else if (tagKey == QLatin1String("place"))
80 return QPlaceManagerEngineOsm::tr(s: "Place");
81 else if (tagKey == QLatin1String("railway"))
82 return QPlaceManagerEngineOsm::tr(s: "Railway");
83 else if (tagKey == QLatin1String("shop"))
84 return QPlaceManagerEngineOsm::tr(s: "Shop");
85 else if (tagKey == QLatin1String("tourism"))
86 return QPlaceManagerEngineOsm::tr(s: "Tourism");
87 else if (tagKey == QLatin1String("waterway"))
88 return QPlaceManagerEngineOsm::tr(s: "Waterway");
89 else
90 return tagKey;
91}
92
93}
94
95QPlaceManagerEngineOsm::QPlaceManagerEngineOsm(const QVariantMap &parameters,
96 QGeoServiceProvider::Error *error,
97 QString *errorString)
98: QPlaceManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this)),
99 m_categoriesReply(0)
100{
101 if (parameters.contains(QStringLiteral("osm.useragent")))
102 m_userAgent = parameters.value(QStringLiteral("osm.useragent")).toString().toLatin1();
103 else
104 m_userAgent = "Qt Location based application";
105
106 if (parameters.contains(QStringLiteral("osm.places.host")))
107 m_urlPrefix = parameters.value(QStringLiteral("osm.places.host")).toString();
108 else
109 m_urlPrefix = QStringLiteral("http://nominatim.openstreetmap.org/search");
110
111
112 if (parameters.contains(QStringLiteral("osm.places.debug_query")))
113 m_debugQuery = parameters.value(QStringLiteral("osm.places.debug_query")).toBool();
114
115 if (parameters.contains(QStringLiteral("osm.places.page_size"))
116 && parameters.value(QStringLiteral("osm.places.page_size")).canConvert<int>())
117 m_pageSize = parameters.value(QStringLiteral("osm.places.page_size")).toInt();
118
119 *error = QGeoServiceProvider::NoError;
120 errorString->clear();
121}
122
123QPlaceManagerEngineOsm::~QPlaceManagerEngineOsm()
124{
125}
126
127QPlaceSearchReply *QPlaceManagerEngineOsm::search(const QPlaceSearchRequest &request)
128{
129 bool unsupported = false;
130
131 // Only public visibility supported
132 unsupported |= request.visibilityScope() != QLocation::UnspecifiedVisibility &&
133 request.visibilityScope() != QLocation::PublicVisibility;
134 unsupported |= request.searchTerm().isEmpty() && request.categories().isEmpty();
135
136 if (unsupported)
137 return QPlaceManagerEngine::search(request);
138
139 QUrlQuery queryItems;
140
141 queryItems.addQueryItem(QStringLiteral("format"), QStringLiteral("jsonv2"));
142
143 //queryItems.addQueryItem(QStringLiteral("accept-language"), QStringLiteral("en"));
144
145 QGeoRectangle boundingBox = request.searchArea().boundingGeoRectangle();
146
147 if (!boundingBox.isEmpty()) {
148 queryItems.addQueryItem(QStringLiteral("bounded"), QStringLiteral("1"));
149 QString coordinates;
150 coordinates = QString::number(boundingBox.topLeft().longitude()) + QLatin1Char(',') +
151 QString::number(boundingBox.topLeft().latitude()) + QLatin1Char(',') +
152 QString::number(boundingBox.bottomRight().longitude()) + QLatin1Char(',') +
153 QString::number(boundingBox.bottomRight().latitude());
154 queryItems.addQueryItem(QStringLiteral("viewbox"), value: coordinates);
155 }
156
157 QStringList queryParts;
158 if (!request.searchTerm().isEmpty())
159 queryParts.append(t: request.searchTerm());
160
161 foreach (const QPlaceCategory &category, request.categories()) {
162 QString id = category.categoryId();
163 int index = id.indexOf(c: QLatin1Char('='));
164 if (index != -1)
165 id = id.mid(position: index+1);
166 queryParts.append(t: QLatin1Char('[') + id + QLatin1Char(']'));
167 }
168
169 queryItems.addQueryItem(QStringLiteral("q"), value: queryParts.join(sep: QLatin1Char('+')));
170
171 QVariantMap parameters = request.searchContext().toMap();
172
173 QStringList placeIds = parameters.value(QStringLiteral("ExcludePlaceIds")).toStringList();
174 if (!placeIds.isEmpty())
175 queryItems.addQueryItem(QStringLiteral("exclude_place_ids"), value: placeIds.join(sep: QLatin1Char(',')));
176
177 queryItems.addQueryItem(QStringLiteral("addressdetails"), QStringLiteral("1"));
178 queryItems.addQueryItem(QStringLiteral("limit"), value: (request.limit() > 0) ? QString::number(request.limit())
179 : QString::number(m_pageSize));
180
181 QUrl requestUrl(m_urlPrefix);
182 requestUrl.setQuery(queryItems);
183
184 QNetworkRequest rq(requestUrl);
185 rq.setAttribute(code: QNetworkRequest::RedirectPolicyAttribute, value: QNetworkRequest::NoLessSafeRedirectPolicy);
186 QNetworkReply *networkReply = m_networkManager->get(request: rq);
187
188 QPlaceSearchReplyOsm *reply = new QPlaceSearchReplyOsm(request, networkReply, this);
189 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
190 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
191 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
192
193 if (m_debugQuery)
194 reply->requestUrl = requestUrl.url(options: QUrl::None);
195
196 return reply;
197}
198
199QPlaceReply *QPlaceManagerEngineOsm::initializeCategories()
200{
201 // Only fetch categories once
202 if (m_categories.isEmpty() && !m_categoriesReply) {
203 m_categoryLocales = m_locales;
204 m_categoryLocales.append(t: QLocale(QLocale::English));
205 fetchNextCategoryLocale();
206 }
207
208 QPlaceCategoriesReplyOsm *reply = new QPlaceCategoriesReplyOsm(this);
209 connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
210 connect(sender: reply, SIGNAL(error(QPlaceReply::Error,QString)),
211 receiver: this, SLOT(replyError(QPlaceReply::Error,QString)));
212
213 // TODO delayed finished() emission
214 if (!m_categories.isEmpty())
215 reply->emitFinished();
216
217 m_pendingCategoriesReply.append(t: reply);
218 return reply;
219}
220
221QString QPlaceManagerEngineOsm::parentCategoryId(const QString &categoryId) const
222{
223 Q_UNUSED(categoryId);
224
225 // Only a two category levels
226 return QString();
227}
228
229QStringList QPlaceManagerEngineOsm::childCategoryIds(const QString &categoryId) const
230{
231 return m_subcategories.value(akey: categoryId);
232}
233
234QPlaceCategory QPlaceManagerEngineOsm::category(const QString &categoryId) const
235{
236 return m_categories.value(akey: categoryId);
237}
238
239QList<QPlaceCategory> QPlaceManagerEngineOsm::childCategories(const QString &parentId) const
240{
241 QList<QPlaceCategory> categories;
242 foreach (const QString &id, m_subcategories.value(parentId))
243 categories.append(t: m_categories.value(akey: id));
244 return categories;
245}
246
247QList<QLocale> QPlaceManagerEngineOsm::locales() const
248{
249 return m_locales;
250}
251
252void QPlaceManagerEngineOsm::setLocales(const QList<QLocale> &locales)
253{
254 m_locales = locales;
255}
256
257void QPlaceManagerEngineOsm::categoryReplyFinished()
258{
259 QNetworkReply *reply = qobject_cast<QNetworkReply *>(object: sender());
260 reply->deleteLater();
261
262 QXmlStreamReader parser(reply);
263 while (!parser.atEnd() && parser.readNextStartElement()) {
264 if (parser.name() == QLatin1String("mediawiki"))
265 continue;
266 if (parser.name() == QLatin1String("page"))
267 continue;
268 if (parser.name() == QLatin1String("revision"))
269 continue;
270 if (parser.name() == QLatin1String("text")) {
271 // parse
272 QString page = parser.readElementText();
273 QRegularExpression regex(QStringLiteral("\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([\\-YN])"));
274 QRegularExpressionMatchIterator i = regex.globalMatch(subject: page);
275 while (i.hasNext()) {
276 QRegularExpressionMatch match = i.next();
277 QString name = match.capturedRef(nth: 1).toString();
278 QString tagKey = match.capturedRef(nth: 2).toString();
279 QString tagValue = match.capturedRef(nth: 3).toString();
280 QString op = match.capturedRef(nth: 4).toString();
281 QString plural = match.capturedRef(nth: 5).toString();
282
283 // Only interested in any operator plural forms
284 if (op != QLatin1String("-") || plural != QLatin1String("Y"))
285 continue;
286
287 if (!m_categories.contains(akey: tagKey)) {
288 QPlaceCategory category;
289 category.setCategoryId(tagKey);
290 category.setName(nameForTagKey(tagKey));
291 m_categories.insert(akey: category.categoryId(), avalue: category);
292 m_subcategories[QString()].append(t: tagKey);
293 emit categoryAdded(category, parentCategoryId: QString());
294 }
295
296 QPlaceCategory category;
297 category.setCategoryId(tagKey + QLatin1Char('=') + tagValue);
298 category.setName(name);
299
300 if (!m_categories.contains(akey: category.categoryId())) {
301 m_categories.insert(akey: category.categoryId(), avalue: category);
302 m_subcategories[tagKey].append(t: category.categoryId());
303 emit categoryAdded(category, parentCategoryId: tagKey);
304 }
305 }
306 }
307
308 parser.skipCurrentElement();
309 }
310
311 if (m_categories.isEmpty() && !m_categoryLocales.isEmpty()) {
312 fetchNextCategoryLocale();
313 return;
314 } else {
315 m_categoryLocales.clear();
316 }
317
318 foreach (QPlaceCategoriesReplyOsm *reply, m_pendingCategoriesReply)
319 reply->emitFinished();
320 m_pendingCategoriesReply.clear();
321}
322
323void QPlaceManagerEngineOsm::categoryReplyError()
324{
325 foreach (QPlaceCategoriesReplyOsm *reply, m_pendingCategoriesReply)
326 reply->setError(errorCode: QPlaceReply::CommunicationError, errorString: tr(s: "Network request error"));
327}
328
329void QPlaceManagerEngineOsm::replyFinished()
330{
331 QPlaceReply *reply = qobject_cast<QPlaceReply *>(object: sender());
332 if (reply)
333 emit finished(reply);
334}
335
336void QPlaceManagerEngineOsm::replyError(QPlaceReply::Error errorCode, const QString &errorString)
337{
338 QPlaceReply *reply = qobject_cast<QPlaceReply *>(object: sender());
339 if (reply)
340 emit error(reply, error: errorCode, errorString);
341}
342
343void QPlaceManagerEngineOsm::fetchNextCategoryLocale()
344{
345 if (m_categoryLocales.isEmpty()) {
346 qWarning(msg: "No locales specified to fetch categories for");
347 return;
348 }
349
350 QLocale locale = m_categoryLocales.takeFirst();
351
352 // FIXME: Categories should be cached.
353 QUrl requestUrl = QUrl(SpecialPhrasesBaseUrl + locale.name().left(n: 2).toUpper());
354
355 m_categoriesReply = m_networkManager->get(request: QNetworkRequest(requestUrl));
356 connect(sender: m_categoriesReply, SIGNAL(finished()), receiver: this, SLOT(categoryReplyFinished()));
357 connect(sender: m_categoriesReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)),
358 receiver: this, SLOT(categoryReplyError()));
359}
360

source code of qtlocation/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp