diff options
Diffstat (limited to 'src/plugins/geoservices/osm/qplacemanagerengineosm.cpp')
-rw-r--r-- | src/plugins/geoservices/osm/qplacemanagerengineosm.cpp | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp b/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp new file mode 100644 index 00000000..1c59044f --- /dev/null +++ b/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Aaron McCarthy <mccarthy.aaron@gmail.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtFoo module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplacemanagerengineosm.h" +#include "qplacesearchreplyimpl.h" +#include "qplacecategoriesreplyimpl.h" + +#include <QtCore/QUrlQuery> +#include <QtCore/QXmlStreamReader> +#include <QtCore/QRegularExpression> +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkRequest> +#include <QtNetwork/QNetworkReply> +#include <QtPositioning/QGeoCircle> +#include <QtLocation/private/unsupportedreplies_p.h> + +#include <QtCore/QElapsedTimer> + +namespace +{ +QString SpecialPhrasesBaseUrl = QStringLiteral("http://wiki.openstreetmap.org/wiki/Special:Export/Nominatim/Special_Phrases/"); + +QString nameForTagKey(const QString &tagKey) +{ + if (tagKey == QLatin1String("aeroway")) + return QPlaceManagerEngineOsm::tr("Aeroway"); + else if (tagKey == QLatin1String("amenity")) + return QPlaceManagerEngineOsm::tr("Amenity"); + else if (tagKey == QLatin1String("building")) + return QPlaceManagerEngineOsm::tr("Building"); + else if (tagKey == QLatin1String("highway")) + return QPlaceManagerEngineOsm::tr("Highway"); + else if (tagKey == QLatin1String("historic")) + return QPlaceManagerEngineOsm::tr("Historic"); + else if (tagKey == QLatin1String("landuse")) + return QPlaceManagerEngineOsm::tr("Land use"); + else if (tagKey == QLatin1String("leisure")) + return QPlaceManagerEngineOsm::tr("Leisure"); + else if (tagKey == QLatin1String("man_made")) + return QPlaceManagerEngineOsm::tr("Man made"); + else if (tagKey == QLatin1String("natural")) + return QPlaceManagerEngineOsm::tr("Natural"); + else if (tagKey == QLatin1String("place")) + return QPlaceManagerEngineOsm::tr("Place"); + else if (tagKey == QLatin1String("railway")) + return QPlaceManagerEngineOsm::tr("Railway"); + else if (tagKey == QLatin1String("shop")) + return QPlaceManagerEngineOsm::tr("Shop"); + else if (tagKey == QLatin1String("tourism")) + return QPlaceManagerEngineOsm::tr("Tourism"); + else if (tagKey == QLatin1String("waterway")) + return QPlaceManagerEngineOsm::tr("Waterway"); + else + return tagKey; +} + +} + +QPlaceManagerEngineOsm::QPlaceManagerEngineOsm(const QVariantMap ¶meters, + QGeoServiceProvider::Error *error, + QString *errorString) +: QPlaceManagerEngine(parameters), m_networkManager(new QNetworkAccessManager(this)), + m_categoriesReply(0) +{ + if (parameters.contains(QStringLiteral("useragent"))) + m_userAgent = parameters.value(QStringLiteral("useragent")).toString().toLatin1(); + else + m_userAgent = "Qt Location based application"; + + *error = QGeoServiceProvider::NoError; + errorString->clear(); +} + +QPlaceManagerEngineOsm::~QPlaceManagerEngineOsm() +{ +} + +QPlaceSearchReply *QPlaceManagerEngineOsm::search(const QPlaceSearchRequest &request) +{ + bool unsupported = false; + + // Only public visibility supported + unsupported |= request.visibilityScope() != QLocation::UnspecifiedVisibility && + request.visibilityScope() != QLocation::PublicVisibility; + unsupported |= request.searchTerm().isEmpty() && request.categories().isEmpty(); + + if (unsupported) + return QPlaceManagerEngine::search(request); + + QUrlQuery queryItems; + + queryItems.addQueryItem(QStringLiteral("format"), QStringLiteral("jsonv2")); + + //queryItems.addQueryItem(QStringLiteral("accept-language"), QStringLiteral("en")); + + QGeoRectangle boundingBox; + QGeoShape searchArea = request.searchArea(); + switch (searchArea.type()) { + case QGeoShape::CircleType: { + QGeoCircle c(searchArea); + qreal radius = c.radius(); + if (radius < 0) + radius = 50000; + + boundingBox = QGeoRectangle(c.center().atDistanceAndAzimuth(radius, -45), + c.center().atDistanceAndAzimuth(radius, 135)); + break; + } + case QGeoShape::RectangleType: + boundingBox = searchArea; + break; + default: + ; + } + + if (!boundingBox.isEmpty()) { + queryItems.addQueryItem(QStringLiteral("bounded"), QStringLiteral("1")); + QString coordinates; + coordinates = QString::number(boundingBox.topLeft().longitude()) + QLatin1Char(',') + + QString::number(boundingBox.topLeft().latitude()) + QLatin1Char(',') + + QString::number(boundingBox.bottomRight().longitude()) + QLatin1Char(',') + + QString::number(boundingBox.bottomRight().latitude()); + queryItems.addQueryItem(QStringLiteral("viewbox"), coordinates); + } + + QStringList queryParts; + if (!request.searchTerm().isEmpty()) + queryParts.append(request.searchTerm()); + + foreach (const QPlaceCategory &category, request.categories()) { + QString id = category.categoryId(); + int index = id.indexOf(QLatin1Char('=')); + if (index != -1) + id = id.mid(index+1); + queryParts.append(QLatin1Char('[') + id + QLatin1Char(']')); + } + + queryItems.addQueryItem(QStringLiteral("q"), queryParts.join(QLatin1Char('+'))); + + QVariantMap parameters = request.searchContext().toMap(); + + QStringList placeIds = parameters.value(QStringLiteral("ExcludePlaceIds")).toStringList(); + if (!placeIds.isEmpty()) + queryItems.addQueryItem(QStringLiteral("exclude_place_ids"), placeIds.join(QLatin1Char(','))); + + queryItems.addQueryItem(QStringLiteral("addressdetails"), QStringLiteral("1")); + + QUrl requestUrl(QStringLiteral("http://nominatim.openstreetmap.org/search?")); + requestUrl.setQuery(queryItems); + + QNetworkReply *networkReply = m_networkManager->get(QNetworkRequest(requestUrl)); + + QPlaceSearchReplyImpl *reply = new QPlaceSearchReplyImpl(request, networkReply, this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + return reply; +} + +QPlaceReply *QPlaceManagerEngineOsm::initializeCategories() +{ + // Only fetch categories once + if (m_categories.isEmpty() && !m_categoriesReply) { + m_categoryLocales = m_locales; + m_categoryLocales.append(QLocale(QLocale::English)); + fetchNextCategoryLocale(); + } + + QPlaceCategoriesReplyImpl *reply = new QPlaceCategoriesReplyImpl(this); + connect(reply, SIGNAL(finished()), this, SLOT(replyFinished())); + connect(reply, SIGNAL(error(QPlaceReply::Error,QString)), + this, SLOT(replyError(QPlaceReply::Error,QString))); + + // TODO delayed finished() emission + if (!m_categories.isEmpty()) + reply->emitFinished(); + + m_pendingCategoriesReply.append(reply); + return reply; +} + +QString QPlaceManagerEngineOsm::parentCategoryId(const QString &categoryId) const +{ + Q_UNUSED(categoryId) + + // Only a two category levels + return QString(); +} + +QStringList QPlaceManagerEngineOsm::childCategoryIds(const QString &categoryId) const +{ + return m_subcategories.value(categoryId); +} + +QPlaceCategory QPlaceManagerEngineOsm::category(const QString &categoryId) const +{ + return m_categories.value(categoryId); +} + +QList<QPlaceCategory> QPlaceManagerEngineOsm::childCategories(const QString &parentId) const +{ + QList<QPlaceCategory> categories; + foreach (const QString &id, m_subcategories.value(parentId)) + categories.append(m_categories.value(id)); + return categories; +} + +QList<QLocale> QPlaceManagerEngineOsm::locales() const +{ + return m_locales; +} + +void QPlaceManagerEngineOsm::setLocales(const QList<QLocale> &locales) +{ + m_locales = locales; +} + +void QPlaceManagerEngineOsm::categoryReplyFinished() +{ + QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender()); + if (!reply) + return; + + reply->deleteLater(); + + QXmlStreamReader parser(reply); + while (!parser.atEnd() && parser.readNextStartElement()) { + if (parser.name() == QLatin1String("mediawiki")) + continue; + if (parser.name() == QLatin1String("page")) + continue; + if (parser.name() == QLatin1String("revision")) + continue; + if (parser.name() == QLatin1String("text")) { + // parse + QString page = parser.readElementText(); + QRegularExpression regex(QStringLiteral("\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([^|]+) \\|\\| ([\\-YN])")); + QRegularExpressionMatchIterator i = regex.globalMatch(page); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + QString name = match.capturedRef(1).toString(); + QString tagKey = match.capturedRef(2).toString(); + QString tagValue = match.capturedRef(3).toString(); + QString op = match.capturedRef(4).toString(); + QString plural = match.capturedRef(5).toString(); + + // Only interested in any operator plural forms + if (op != QLatin1String("-") || plural != QLatin1String("Y")) + continue; + + if (!m_categories.contains(tagKey)) { + QPlaceCategory category; + category.setCategoryId(tagKey); + category.setName(nameForTagKey(tagKey)); + m_categories.insert(category.categoryId(), category); + m_subcategories[QString()].append(tagKey); + emit categoryAdded(category, QString()); + } + + QPlaceCategory category; + category.setCategoryId(tagKey + QLatin1Char('=') + tagValue); + category.setName(name); + + if (!m_categories.contains(category.categoryId())) { + m_categories.insert(category.categoryId(), category); + m_subcategories[tagKey].append(category.categoryId()); + emit categoryAdded(category, tagKey); + } + } + } + + parser.skipCurrentElement(); + } + + if (m_categories.isEmpty() && !m_categoryLocales.isEmpty()) { + fetchNextCategoryLocale(); + return; + } else { + m_categoryLocales.clear(); + } + + foreach (QPlaceCategoriesReplyImpl *reply, m_pendingCategoriesReply) + reply->emitFinished(); + m_pendingCategoriesReply.clear(); +} + +void QPlaceManagerEngineOsm::categoryReplyError() +{ + foreach (QPlaceCategoriesReplyImpl *reply, m_pendingCategoriesReply) + reply->setError(QPlaceReply::CommunicationError, tr("Network request error")); +} + +void QPlaceManagerEngineOsm::replyFinished() +{ + QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender()); + if (reply) + emit finished(reply); +} + +void QPlaceManagerEngineOsm::replyError(QPlaceReply::Error errorCode, const QString &errorString) +{ + QPlaceReply *reply = qobject_cast<QPlaceReply *>(sender()); + if (reply) + emit error(reply, errorCode, errorString); +} + +void QPlaceManagerEngineOsm::fetchNextCategoryLocale() +{ + if (m_categoryLocales.isEmpty()) { + qWarning("No locales specified to fetch categories for"); + return; + } + + QLocale locale = m_categoryLocales.takeFirst(); + + // FIXME: Categories should be cached. + QUrl requestUrl = QUrl(SpecialPhrasesBaseUrl + locale.name().left(2).toUpper()); + + m_categoriesReply = m_networkManager->get(QNetworkRequest(requestUrl)); + connect(m_categoriesReply, SIGNAL(finished()), this, SLOT(categoryReplyFinished())); + connect(m_categoriesReply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(categoryReplyError())); +} |