9#include <QtCore/QElapsedTimer>
10#include <QtCore/QLocale>
11#include <QtCore/QRegularExpression>
12#include <QtCore/QUrlQuery>
13#include <QtCore/QXmlStreamReader>
15#include <QtNetwork/QNetworkAccessManager>
16#include <QtNetwork/QNetworkRequest>
17#include <QtNetwork/QNetworkReply>
19#include <QtPositioning/QGeoCircle>
21#include <QtLocation/QPlaceCategory>
22#include <QtLocation/QPlaceSearchRequest>
23#include <QtLocation/private/unsupportedreplies_p.h>
27QString SpecialPhrasesBaseUrl = QStringLiteral(
"http://wiki.openstreetmap.org/wiki/Special:Export/Nominatim/Special_Phrases/");
29QString nameForTagKey(
const QString &tagKey)
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");
66 QGeoServiceProvider::Error *error,
68: QPlaceManagerEngine(parameters), m_networkManager(
new QNetworkAccessManager(
this)),
71 if (parameters.contains(QStringLiteral(
"osm.useragent")))
72 m_userAgent = parameters.value(QStringLiteral(
"osm.useragent")).toString().toLatin1();
74 m_userAgent =
"Qt Location based application";
76 if (parameters.contains(QStringLiteral(
"osm.places.host")))
77 m_urlPrefix = parameters.value(QStringLiteral(
"osm.places.host")).toString();
79 m_urlPrefix = QStringLiteral(
"http://nominatim.openstreetmap.org/search");
82 if (parameters.contains(QStringLiteral(
"osm.places.debug_query")))
83 m_debugQuery = parameters.value(QStringLiteral(
"osm.places.debug_query")).toBool();
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();
89 *error = QGeoServiceProvider::NoError;
99 bool unsupported =
false;
102 unsupported |= request.visibilityScope() != QLocation::UnspecifiedVisibility &&
103 request.visibilityScope() != QLocation::PublicVisibility;
104 unsupported |= request.searchTerm().isEmpty() && request.categories().isEmpty();
107 return QPlaceManagerEngine::search(request);
109 QUrlQuery queryItems;
111 queryItems.addQueryItem(QStringLiteral(
"format"), QStringLiteral(
"jsonv2"));
115 QGeoRectangle boundingBox = request.searchArea().boundingGeoRectangle();
117 if (!boundingBox.isEmpty()) {
118 queryItems.addQueryItem(QStringLiteral(
"bounded"), QStringLiteral(
"1"));
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);
127 QStringList queryParts;
128 if (!request.searchTerm().isEmpty())
129 queryParts.append(request.searchTerm());
131 for (
const QPlaceCategory &category : request.categories()) {
132 QString id = category.categoryId();
133 queryParts.append(QLatin1Char(
'[') + id + QLatin1Char(
']'));
136 queryItems.addQueryItem(QStringLiteral(
"q"), queryParts.join(QLatin1Char(
'+')));
138 QVariantMap parameters = request.searchContext().toMap();
140 QStringList placeIds = parameters.value(QStringLiteral(
"ExcludePlaceIds")).toStringList();
141 if (!placeIds.isEmpty())
142 queryItems.addQueryItem(QStringLiteral(
"exclude_place_ids"), placeIds.join(QLatin1Char(
',')));
144 queryItems.addQueryItem(QStringLiteral(
"addressdetails"), QStringLiteral(
"1"));
145 queryItems.addQueryItem(QStringLiteral(
"limit"), (request.limit() > 0) ? QString::number(request.limit())
146 : QString::number(m_pageSize));
148 QUrl requestUrl(m_urlPrefix);
149 requestUrl.setQuery(queryItems);
151 QNetworkRequest rq(requestUrl);
152 rq.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
153 QNetworkReply *networkReply = m_networkManager->get(rq);
156 connect(reply, &QPlaceSearchReplyOsm::finished,
158 connect(reply, &QPlaceSearchReplyOsm::errorOccurred,
159 this, &QPlaceManagerEngineOsm::replyError);
162 reply->requestUrl = requestUrl.url(QUrl::None);
170 if (m_categories.isEmpty() && !m_categoriesReply) {
171 m_categoryLocales = m_locales;
172 m_categoryLocales.append(QLocale(QLocale::English));
173 fetchNextCategoryLocale();
176 QPlaceCategoriesReplyOsm *reply =
new QPlaceCategoriesReplyOsm(
this);
177 connect(reply, &QPlaceCategoriesReplyOsm::finished,
179 connect(reply, &QPlaceCategoriesReplyOsm::errorOccurred,
180 this, &QPlaceManagerEngineOsm::replyError);
183 if (!m_categories.isEmpty())
184 reply->emitFinished();
186 m_pendingCategoriesReply.append(reply);
192 Q_UNUSED(categoryId);
200 return m_subcategories.value(categoryId);
205 return m_categories.value(categoryId);
210 QList<QPlaceCategory> categories;
211 for (
const QString &id : m_subcategories.value(parentId))
212 categories.append(m_categories.value(id));
228 QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
229 reply->deleteLater();
231 QXmlStreamReader parser(reply);
232 while (!parser.atEnd() && parser.readNextStartElement()) {
233 if (parser.name() == QLatin1String(
"mediawiki"))
235 if (parser.name() == QLatin1String(
"page"))
237 if (parser.name() == QLatin1String(
"revision"))
239 if (parser.name() == QLatin1String(
"text")) {
241 QString page = parser.readElementText();
242 QRegularExpression regex(QStringLiteral(
"\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([\\-YN])"));
243 QRegularExpressionMatchIterator i = regex.globalMatch(page);
244 while (i.hasNext()) {
245 QRegularExpressionMatch match = i.next();
246 QString name = match.capturedView(1).toString();
247 QString tagKey = match.capturedView(2).toString();
248 QString tagValue = match.capturedView(3).toString();
249 QString op = match.capturedView(4).toString();
250 QString plural = match.capturedView(5).toString();
253 if (op != QLatin1String(
"-") || plural != QLatin1String(
"Y"))
256 if (!m_categories.contains(tagKey)) {
257 QPlaceCategory category;
258 category.setCategoryId(tagKey);
259 category.setName(nameForTagKey(tagKey));
260 m_categories.insert(category.categoryId(), category);
261 m_subcategories[QString()].append(tagKey);
262 emit categoryAdded(category, QString());
265 QPlaceCategory category;
266 category.setCategoryId(tagKey + QLatin1Char(
'=') + tagValue);
267 category.setName(name);
269 if (!m_categories.contains(category.categoryId())) {
270 m_categories.insert(category.categoryId(), category);
271 m_subcategories[tagKey].append(category.categoryId());
272 emit categoryAdded(category, tagKey);
277 parser.skipCurrentElement();
280 if (m_categories.isEmpty() && !m_categoryLocales.isEmpty()) {
281 fetchNextCategoryLocale();
284 m_categoryLocales.clear();
287 for (QPlaceCategoriesReplyOsm *reply : m_pendingCategoriesReply)
288 reply->emitFinished();
289 m_pendingCategoriesReply.clear();
294 for (QPlaceCategoriesReplyOsm *reply : m_pendingCategoriesReply)
295 reply->setError(QPlaceReply::CommunicationError, tr(
"Network request error"));
300 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
302 emit finished(reply);
307 QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender());
309 emit errorOccurred(reply, errorCode, errorString);
314 if (m_categoryLocales.isEmpty()) {
315 qWarning(
"No locales specified to fetch categories for");
319 QLocale locale = m_categoryLocales.takeFirst();
322 QUrl requestUrl = QUrl(SpecialPhrasesBaseUrl + locale.name().left(2).toUpper());
324 m_categoriesReply = m_networkManager->get(QNetworkRequest(requestUrl));
325 connect(m_categoriesReply, &QNetworkReply::finished,
326 this, &QPlaceManagerEngineOsm::categoryReplyFinished);
327 connect(m_categoriesReply, &QNetworkReply::errorOccurred,
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.
~QPlaceManagerEngineOsm()