summaryrefslogtreecommitdiff
path: root/src/plugins/geoservices/osm/qplacemanagerengineosm.cpp
diff options
context:
space:
mode:
authorAaron McCarthy <mccarthy.aaron@gmail.com>2015-01-23 15:37:19 +1000
committerAlex Blasche <alexander.blasche@theqtcompany.com>2015-02-02 07:33:42 +0000
commitdbfa9eeaae4b0508a0d4de41d0578003e98d357b (patch)
treeda81c7ec0773dd8e9021227ad47d1aa7fbb7be5c /src/plugins/geoservices/osm/qplacemanagerengineosm.cpp
parentd2ff57fec56b575059737e91cf6dbdcd3d610e6f (diff)
downloadqtlocation-dbfa9eeaae4b0508a0d4de41d0578003e98d357b.tar.gz
Add support for places to Open Street Map plugin.
Implement basic places support based on the Open Street Map Nominatim service. Support for read only categories and place searching is supported. The plugin does not support getting place details, getting place content, search suggestions, saving/removing places or saving/removing categories. Change-Id: I5a185cdf25b50d5b377be4d2c3c53c8f1e807288 Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
Diffstat (limited to 'src/plugins/geoservices/osm/qplacemanagerengineosm.cpp')
-rw-r--r--src/plugins/geoservices/osm/qplacemanagerengineosm.cpp354
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 &parameters,
+ 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()));
+}