diff options
author | Liang Qi <liang.qi@qt.io> | 2016-10-12 12:39:13 +0200 |
---|---|---|
committer | Liang Qi <liang.qi@qt.io> | 2016-10-12 12:39:13 +0200 |
commit | 3abe52c28d9d960b284586e5b6f0277f91362ee9 (patch) | |
tree | 201f539b5ac6dddc9543daaf7ad7b7a18c5f8f14 | |
parent | 3f813d1c8c24e5da67d3f096eb3f5c73e760e641 (diff) | |
parent | 8bc909704986209e98feae8057b0e3ce07a28cd8 (diff) | |
download | qtlocation-3abe52c28d9d960b284586e5b6f0277f91362ee9.tar.gz |
Merge remote-tracking branch 'origin/5.6' into 5.7.1
Conflicts:
src/location/maps/maps.pri
Change-Id: I08158c8ba03d9345097cc4017556971ba3ec0dc6
-rw-r--r-- | src/location/doc/src/plugins/osm.qdoc | 8 | ||||
-rw-r--r-- | src/location/maps/maps.pri | 12 | ||||
-rw-r--r-- | src/location/maps/qgeorouteparser.cpp | 90 | ||||
-rw-r--r-- | src/location/maps/qgeorouteparser_p.h | 79 | ||||
-rw-r--r-- | src/location/maps/qgeorouteparser_p_p.h | 60 | ||||
-rw-r--r-- | src/location/maps/qgeorouteparserosrmv4.cpp | 404 | ||||
-rw-r--r-- | src/location/maps/qgeorouteparserosrmv4_p.h | 72 | ||||
-rw-r--r-- | src/location/maps/qgeorouteparserosrmv5.cpp | 979 | ||||
-rw-r--r-- | src/location/maps/qgeorouteparserosrmv5_p.h | 72 | ||||
-rw-r--r-- | src/plugins/geoservices/osm/qgeoroutereplyosm.cpp | 326 | ||||
-rw-r--r-- | src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp | 31 | ||||
-rw-r--r-- | src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h | 3 | ||||
-rw-r--r-- | src/positioning/qlocationutils_p.h | 93 |
13 files changed, 1903 insertions, 326 deletions
diff --git a/src/location/doc/src/plugins/osm.qdoc b/src/location/doc/src/plugins/osm.qdoc index 1960fae9..a0f2bc2b 100644 --- a/src/location/doc/src/plugins/osm.qdoc +++ b/src/location/doc/src/plugins/osm.qdoc @@ -94,8 +94,14 @@ a prefix. \row \li osm.routing.host \li Url string set when making network requests to the routing server. This parameter should be set to a - valid server url with the correct osrm API. If not specified the default \l {http://router.project-osrm.org/viaroute}{url} will be used. + valid server url with the correct osrm API. If not specified the default \l {http://router.project-osrm.org/route/v1/driving/}{url} will be used. \note The API documentation and sources are available at \l {http://project-osrm.org/}{Project OSRM}. + +\row + \li osm.routing.apiversion + \li String defining the api version of the (custom) OSRM server. Valid values are \b{v4} and \b{v5}. The default is \b{v5}. + This parameter should be set only if \tt{osm.routing.host} is set, and is an OSRM v4 server. + \row \li osm.geocoding.host \li Url string set when making network requests to the geocoding server. This parameter should be set to a diff --git a/src/location/maps/maps.pri b/src/location/maps/maps.pri index c8e28bab..db43962e 100644 --- a/src/location/maps/maps.pri +++ b/src/location/maps/maps.pri @@ -54,6 +54,10 @@ PRIVATE_HEADERS += \ maps/qgeotiledmapreply_p_p.h \ maps/qgeotilespec_p.h \ maps/qgeotilespec_p_p.h \ + maps/qgeorouteparser_p.h \ + maps/qgeorouteparser_p_p.h \ + maps/qgeorouteparserosrmv5_p.h \ + maps/qgeorouteparserosrmv4_p.h \ maps/qcache3q_p.h SOURCES += \ @@ -84,6 +88,12 @@ SOURCES += \ maps/qgeotiledmapreply.cpp \ maps/qgeotilespec.cpp \ maps/qgeotiledmap.cpp \ - maps/qgeotiledmapscene.cpp + maps/qgeotiledmapscene.cpp \ + maps/qgeorouteparser.cpp \ + maps/qgeorouteparserosrmv5.cpp \ + maps/qgeorouteparserosrmv4.cpp + + + diff --git a/src/location/maps/qgeorouteparser.cpp b/src/location/maps/qgeorouteparser.cpp new file mode 100644 index 00000000..646902e0 --- /dev/null +++ b/src/location/maps/qgeorouteparser.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorouteparser_p.h" +#include "qgeorouteparser_p_p.h" +#include "qgeoroutesegment.h" +#include "qgeomaneuver.h" + +#include <QtCore/private/qobject_p.h> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtPositioning/private/qlocationutils_p.h> + +QT_BEGIN_NAMESPACE + +/* + Private class implementations +*/ + +QGeoRouteParserPrivate::QGeoRouteParserPrivate() : QObjectPrivate() +{ +} + +QGeoRouteParserPrivate::~QGeoRouteParserPrivate() +{ +} + +/* + Public class implementations +*/ + +QGeoRouteParser::~QGeoRouteParser() +{ + +} + +QGeoRouteParser::QGeoRouteParser(QGeoRouteParserPrivate &dd, QObject *parent) : QObject(dd, parent) +{ + +} + +QGeoRouteReply::Error QGeoRouteParser::parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const +{ + Q_D(const QGeoRouteParser); + return d->parseReply(routes, errorString, reply); +} + +QUrl QGeoRouteParser::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const +{ + Q_D(const QGeoRouteParser); + return d->requestUrl(request, prefix); +} + +QT_END_NAMESPACE + + diff --git a/src/location/maps/qgeorouteparser_p.h b/src/location/maps/qgeorouteparser_p.h new file mode 100644 index 00000000..da1c09f2 --- /dev/null +++ b/src/location/maps/qgeorouteparser_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOSRMROUTEPARSER_P_H +#define QOSRMROUTEPARSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/qlocationglobal.h> +#include <QtLocation/qgeoroutereply.h> +#include <QtLocation/qgeorouterequest.h> +#include <QtCore/QByteArray> +#include <QtCore/QUrl> + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserPrivate; +class Q_LOCATION_EXPORT QGeoRouteParser : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoRouteParser) + +public: + virtual ~QGeoRouteParser(); + QGeoRouteReply::Error parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const; + QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const; + +protected: + QGeoRouteParser(QGeoRouteParserPrivate &dd, QObject *parent = Q_NULLPTR); + +private: + Q_DISABLE_COPY(QGeoRouteParser) +}; + +QT_END_NAMESPACE + +#endif // QOSRMROUTEPARSER_P_H diff --git a/src/location/maps/qgeorouteparser_p_p.h b/src/location/maps/qgeorouteparser_p_p.h new file mode 100644 index 00000000..7bf41f87 --- /dev/null +++ b/src/location/maps/qgeorouteparser_p_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEPARSER_P_P_H +#define QGEOROUTEPARSER_P_P_H + +#include <QtCore/private/qobject_p.h> +#include <QtCore/QUrl> +#include <QtLocation/qgeoroutereply.h> +#include <QtLocation/qgeorouterequest.h> + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGeoRouteParser) +public: + QGeoRouteParserPrivate(); + virtual ~QGeoRouteParserPrivate(); + + virtual QGeoRouteReply::Error parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const = 0; + virtual QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const = 0; +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTEPARSER_P_P_H diff --git a/src/location/maps/qgeorouteparserosrmv4.cpp b/src/location/maps/qgeorouteparserosrmv4.cpp new file mode 100644 index 00000000..7321fb6f --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv4.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorouteparserosrmv4_p.h" +#include "qgeorouteparser_p_p.h" +#include "qgeoroutesegment.h" +#include "qgeomaneuver.h" + +#include <QtCore/private/qobject_p.h> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtCore/QUrlQuery> + +QT_BEGIN_NAMESPACE + +static QList<QGeoCoordinate> parsePolyline(const QByteArray &data) +{ + QList<QGeoCoordinate> path; + + bool parsingLatitude = true; + + int shift = 0; + int value = 0; + + QGeoCoordinate coord(0, 0); + + for (int i = 0; i < data.length(); ++i) { + unsigned char c = data.at(i) - 63; + + value |= (c & 0x1f) << shift; + shift += 5; + + // another chunk + if (c & 0x20) + continue; + + int diff = (value & 1) ? ~(value >> 1) : (value >> 1); + + if (parsingLatitude) { + coord.setLatitude(coord.latitude() + (double)diff/1e6); + } else { + coord.setLongitude(coord.longitude() + (double)diff/1e6); + path.append(coord); + } + + parsingLatitude = !parsingLatitude; + + value = 0; + shift = 0; + } + + return path; +} + +static QGeoManeuver::InstructionDirection osrmInstructionDirection(const QString &instructionCode) +{ + if (instructionCode == QLatin1String("0")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("1")) + return QGeoManeuver::DirectionForward; + else if (instructionCode == QLatin1String("2")) + return QGeoManeuver::DirectionBearRight; + else if (instructionCode == QLatin1String("3")) + return QGeoManeuver::DirectionRight; + else if (instructionCode == QLatin1String("4")) + return QGeoManeuver::DirectionHardRight; + else if (instructionCode == QLatin1String("5")) + return QGeoManeuver::DirectionUTurnLeft; + else if (instructionCode == QLatin1String("6")) + return QGeoManeuver::DirectionHardLeft; + else if (instructionCode == QLatin1String("7")) + return QGeoManeuver::DirectionLeft; + else if (instructionCode == QLatin1String("8")) + return QGeoManeuver::DirectionBearLeft; + else if (instructionCode == QLatin1String("9")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("10")) + return QGeoManeuver::DirectionForward; + else if (instructionCode == QLatin1String("11")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("12")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("13")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("14")) + return QGeoManeuver::NoDirection; + else if (instructionCode == QLatin1String("15")) + return QGeoManeuver::NoDirection; + else + return QGeoManeuver::NoDirection; +} + +static QString osrmInstructionText(const QString &instructionCode, const QString &wayname) +{ + if (instructionCode == QLatin1String("0")) { + return QString(); + } else if (instructionCode == QLatin1String("1")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Go straight."); + else + return QGeoRouteParserOsrmV4::tr("Go straight onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("2")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn slightly right."); + else + return QGeoRouteParserOsrmV4::tr("Turn slightly right onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("3")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn right."); + else + return QGeoRouteParserOsrmV4::tr("Turn right onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("4")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Make a sharp right."); + else + return QGeoRouteParserOsrmV4::tr("Make a sharp right onto %1.").arg(wayname); + } + else if (instructionCode == QLatin1String("5")) { + return QGeoRouteParserOsrmV4::tr("When it is safe to do so, perform a U-turn."); + } else if (instructionCode == QLatin1String("6")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Make a sharp left."); + else + return QGeoRouteParserOsrmV4::tr("Make a sharp left onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("7")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn left."); + else + return QGeoRouteParserOsrmV4::tr("Turn left onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("8")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Turn slightly left."); + else + return QGeoRouteParserOsrmV4::tr("Turn slightly left onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("9")) { + return QGeoRouteParserOsrmV4::tr("Reached waypoint."); + } else if (instructionCode == QLatin1String("10")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Head on."); + else + return QGeoRouteParserOsrmV4::tr("Head onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11")) { + return QGeoRouteParserOsrmV4::tr("Enter the roundabout."); + } else if (instructionCode == QLatin1String("11-1")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the first exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the first exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-2")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the second exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the second exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-3")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the third exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the third exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-4")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fourth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fourth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-5")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fifth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the fifth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-6")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the sixth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the sixth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-7")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the seventh exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the seventh exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-8")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the eighth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the eighth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("11-9")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("At the roundabout take the ninth exit."); + else + return QGeoRouteParserOsrmV4::tr("At the roundabout take the ninth exit onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("12")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Leave the roundabout."); + else + return QGeoRouteParserOsrmV4::tr("Leave the roundabout onto %1.").arg(wayname); + } else if (instructionCode == QLatin1String("13")) { + return QGeoRouteParserOsrmV4::tr("Stay on the roundabout."); + } else if (instructionCode == QLatin1String("14")) { + if (wayname.isEmpty()) + return QGeoRouteParserOsrmV4::tr("Start at the end of the street."); + else + return QGeoRouteParserOsrmV4::tr("Start at the end of %1.").arg(wayname); + } else if (instructionCode == QLatin1String("15")) { + return QGeoRouteParserOsrmV4::tr("You have reached your destination."); + } else { + return QGeoRouteParserOsrmV4::tr("Don't know what to say for '%1'").arg(instructionCode); + } +} + +static QGeoRoute constructRoute(const QByteArray &geometry, const QJsonArray &instructions, + const QJsonObject &summary) +{ + QGeoRoute route; + + QList<QGeoCoordinate> path = parsePolyline(geometry); + + QGeoRouteSegment firstSegment; + int firstPosition = -1; + + int segmentPathLengthCount = 0; + + for (int i = instructions.count() - 1; i >= 0; --i) { + QJsonArray instruction = instructions.at(i).toArray(); + + if (instruction.count() < 8) { + qWarning("Instruction does not contain enough fields."); + continue; + } + + const QString instructionCode = instruction.at(0).toString(); + const QString wayname = instruction.at(1).toString(); + double segmentLength = instruction.at(2).toDouble(); + int position = instruction.at(3).toDouble(); + int time = instruction.at(4).toDouble(); + //const QString segmentLengthString = instruction.at(5).toString(); + //const QString direction = instruction.at(6).toString(); + //double azimuth = instruction.at(7).toDouble(); + + QGeoRouteSegment segment; + segment.setDistance(segmentLength); + + QGeoManeuver maneuver; + maneuver.setDirection(osrmInstructionDirection(instructionCode)); + maneuver.setDistanceToNextInstruction(segmentLength); + maneuver.setInstructionText(osrmInstructionText(instructionCode, wayname)); + maneuver.setPosition(path.at(position)); + maneuver.setTimeToNextInstruction(time); + + segment.setManeuver(maneuver); + + if (firstPosition == -1) + segment.setPath(path.mid(position)); + else + segment.setPath(path.mid(position, firstPosition - position)); + + segmentPathLengthCount += segment.path().length(); + + segment.setTravelTime(time); + + segment.setNextRouteSegment(firstSegment); + + firstSegment = segment; + firstPosition = position; + } + + route.setDistance(summary.value(QStringLiteral("total_distance")).toDouble()); + route.setTravelTime(summary.value(QStringLiteral("total_time")).toDouble()); + route.setFirstRouteSegment(firstSegment); + route.setPath(path); + + return route; +} + +class QGeoRouteParserOsrmV4Private : public QGeoRouteParserPrivate +{ + Q_DECLARE_PUBLIC(QGeoRouteParserOsrmV4) +public: + QGeoRouteParserOsrmV4Private(); + virtual ~QGeoRouteParserOsrmV4Private(); + + QGeoRouteReply::Error parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const Q_DECL_OVERRIDE; + QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const Q_DECL_OVERRIDE; +}; + +QGeoRouteParserOsrmV4Private::QGeoRouteParserOsrmV4Private() : QGeoRouteParserPrivate() +{ +} + +QGeoRouteParserOsrmV4Private::~QGeoRouteParserOsrmV4Private() +{ +} + +QGeoRouteReply::Error QGeoRouteParserOsrmV4Private::parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const +{ + // OSRM v4 specs: https://github.com/Project-OSRM/osrm-backend/wiki/Server-API---v4,-old + QJsonDocument document = QJsonDocument::fromJson(reply); + + if (document.isObject()) { + QJsonObject object = document.object(); + + //double version = object.value(QStringLiteral("version")).toDouble(); + int status = object.value(QStringLiteral("status")).toDouble(); + QString statusMessage = object.value(QStringLiteral("status_message")).toString(); + + // status code 0 or 200 are case of success + // status code is 207 if no route was found + // an error occurred when trying to find a route + if (0 != status && 200 != status) { + errorString = statusMessage; + return QGeoRouteReply::UnknownError; + } + + QJsonObject routeSummary = object.value(QStringLiteral("route_summary")).toObject(); + + QByteArray routeGeometry = + object.value(QStringLiteral("route_geometry")).toString().toLatin1(); + + QJsonArray routeInstructions = object.value(QStringLiteral("route_instructions")).toArray(); + + QGeoRoute route = constructRoute(routeGeometry, routeInstructions, routeSummary); + + routes.append(route); + + QJsonArray alternativeSummaries = + object.value(QStringLiteral("alternative_summaries")).toArray(); + QJsonArray alternativeGeometries = + object.value(QStringLiteral("alternative_geometries")).toArray(); + QJsonArray alternativeInstructions = + object.value(QStringLiteral("alternative_instructions")).toArray(); + + if (alternativeSummaries.count() == alternativeGeometries.count() && + alternativeSummaries.count() == alternativeInstructions.count()) { + for (int i = 0; i < alternativeSummaries.count(); ++i) { + route = constructRoute(alternativeGeometries.at(i).toString().toLatin1(), + alternativeInstructions.at(i).toArray(), + alternativeSummaries.at(i).toObject()); + //routes.append(route); + } + } + + return QGeoRouteReply::NoError; + } else { + errorString = QStringLiteral("Couldn't parse json."); + return QGeoRouteReply::ParseError; + } +} + +QUrl QGeoRouteParserOsrmV4Private::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const +{ + QUrl url(prefix); + QUrlQuery query; + + query.addQueryItem(QStringLiteral("instructions"), QStringLiteral("true")); + + foreach (const QGeoCoordinate &c, request.waypoints()) { + query.addQueryItem(QStringLiteral("loc"), QString::number(c.latitude()) + QLatin1Char(',') + + QString::number(c.longitude())); + } + + url.setQuery(query); + return url; +} + +QGeoRouteParserOsrmV4::QGeoRouteParserOsrmV4(QObject *parent) : QGeoRouteParser(*new QGeoRouteParserOsrmV4Private(), parent) +{ +} + +QGeoRouteParserOsrmV4::~QGeoRouteParserOsrmV4() +{ +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeorouteparserosrmv4_p.h b/src/location/maps/qgeorouteparserosrmv4_p.h new file mode 100644 index 00000000..3d38cbf7 --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv4_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEPARSEROSRMV4_H +#define QGEOROUTEPARSEROSRMV4_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + +#include <QtLocation/private/qgeorouteparser_p.h> + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserOsrmV4Private; +class Q_LOCATION_EXPORT QGeoRouteParserOsrmV4 : public QGeoRouteParser +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoRouteParserOsrmV4) + +public: + QGeoRouteParserOsrmV4(QObject *parent = Q_NULLPTR); + virtual ~QGeoRouteParserOsrmV4(); + +private: + Q_DISABLE_COPY(QGeoRouteParserOsrmV4) +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTEPARSEROSRMV4_H diff --git a/src/location/maps/qgeorouteparserosrmv5.cpp b/src/location/maps/qgeorouteparserosrmv5.cpp new file mode 100644 index 00000000..cbbf7fc4 --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv5.cpp @@ -0,0 +1,979 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeorouteparserosrmv5_p.h" +#include "qgeorouteparser_p_p.h" +#include "qgeoroutesegment.h" +#include "qgeomaneuver.h" + +#include <QtCore/private/qobject_p.h> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtCore/QUrlQuery> +#include <QtPositioning/private/qlocationutils_p.h> + +QT_BEGIN_NAMESPACE + +static QList<QGeoCoordinate> decodePolyline(const QString &polylineString) +{ + QList<QGeoCoordinate> path; + if (polylineString.isEmpty()) + return path; + + QByteArray data = polylineString.toLatin1(); + + bool parsingLatitude = true; + + int shift = 0; + int value = 0; + + QGeoCoordinate coord(0, 0); + + for (int i = 0; i < data.length(); ++i) { + unsigned char c = data.at(i) - 63; + + value |= (c & 0x1f) << shift; + shift += 5; + + // another chunk + if (c & 0x20) + continue; + + int diff = (value & 1) ? ~(value >> 1) : (value >> 1); + + if (parsingLatitude) { + coord.setLatitude(coord.latitude() + (double)diff/1e5); + } else { + coord.setLongitude(coord.longitude() + (double)diff/1e5); + path.append(coord); + } + + parsingLatitude = !parsingLatitude; + + value = 0; + shift = 0; + } + + return path; +} + +static QString cardinalDirection4(QLocationUtils::CardinalDirection direction) +{ + switch (direction) { + case QLocationUtils::CardinalN: + //: Always used in "Head %1 [onto <street name>]" + return QGeoRouteParserOsrmV5::tr("North"); + case QLocationUtils::CardinalE: + return QGeoRouteParserOsrmV5::tr("East"); + case QLocationUtils::CardinalS: + return QGeoRouteParserOsrmV5::tr("South"); + case QLocationUtils::CardinalW: + return QGeoRouteParserOsrmV5::tr("West"); + default: + return QString(); + } +} + +static QString exitOrdinal(int exit) +{ + static QList<QString> ordinals; + + if (!ordinals.size()) { + ordinals.append(QStringLiteral("")); + //: always used in " and take the %1 exit [onto <street name>]" + ordinals.append(QGeoRouteParserOsrmV5::tr("first", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("second", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("third", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fourth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fifth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("sixth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("seventh", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("eighth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("ninth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("tenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("eleventh", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("twelfth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("thirteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fourteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("fifteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("sixteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("seventeenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("eighteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("nineteenth", "roundabout exit")); + ordinals.append(QGeoRouteParserOsrmV5::tr("twentieth", "roundabout exit")); + }; + + if (exit < 1 || exit > ordinals.size()) + return QString(); + return ordinals[exit]; +} + +static QString exitDirection(int exit, const QString &wayName) +{ + /*: Always appended to one of the following strings: + - "Enter the roundabout" + - "Enter the rotary" + - "Enter the rotary <rotaryname>" + */ + static QString directionExit = QGeoRouteParserOsrmV5::tr(" and take the %1 exit"); + static QString directionExitOnto = QGeoRouteParserOsrmV5::tr(" and take the %1 exit onto %2"); + + if (exit < 1 || exit > 20) + return QString(); + if (wayName.isEmpty()) + return directionExit.arg(exitOrdinal(exit)); + else + return directionExitOnto.arg(exitOrdinal(exit), wayName); +} + +static QString instructionArrive(QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination, straight ahead"); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination, on the left"); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination, on the right"); + default: + return QGeoRouteParserOsrmV5::tr("You have arrived at your destination"); + } +} + +static QString instructionContinue(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue straight"); + else + return QGeoRouteParserOsrmV5::tr("Continue straight on %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue left"); + else + return QGeoRouteParserOsrmV5::tr("Continue left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Continue slightly left on %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue right"); + else + return QGeoRouteParserOsrmV5::tr("Continue right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Continue slightly right on %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue"); + else + return QGeoRouteParserOsrmV5::tr("Continue on %1").arg(wayName); + } +} + +static QString instructionDepart(const QJsonObject &maneuver, const QString &wayName) +{ + double bearing = maneuver.value(QLatin1String("bearing_after")).toDouble(-1.0); + if (bearing >= 0.0) { + if (wayName.isEmpty()) + //: %1 is "North", "South", "East" or "West" + return QGeoRouteParserOsrmV5::tr("Head %1").arg(cardinalDirection4(QLocationUtils::azimuthToCardinalDirection4(bearing))); + else + return QGeoRouteParserOsrmV5::tr("Head %1 onto %2").arg(cardinalDirection4(QLocationUtils::azimuthToCardinalDirection4(bearing)), wayName); + } else { + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Depart"); + else + return QGeoRouteParserOsrmV5::tr("Depart onto %1").arg(wayName); + } +} + +static QString instructionEndOfRoad(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn left"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn right"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, make a U-turn onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue straight"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue straight onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue"); + else + return QGeoRouteParserOsrmV5::tr("At the end of the road, continue onto %1").arg(wayName); + } +} + +static QString instructionFerry(const QString &wayName) +{ + QString instruction = QGeoRouteParserOsrmV5::tr("Take the ferry"); + if (!wayName.isEmpty()) + instruction += QLatin1String(" [") + wayName + QLatin1Char(']'); + + return instruction; +} + +static QString instructionFork(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp left"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, turn left"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, keep left"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, keep left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp right"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, take a sharp right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, turn right"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, keep right"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, keep right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, continue straight ahead"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, continue straight ahead onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the fork, continue"); + else + return QGeoRouteParserOsrmV5::tr("At the fork, continue onto %1").arg(wayName); + } +} + +static QString instructionMerge(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge sharply left"); + else + return QGeoRouteParserOsrmV5::tr("Merge sharply left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge left"); + else + return QGeoRouteParserOsrmV5::tr("Merge left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Merge slightly left on %1").arg(wayName); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge sharply right"); + else + return QGeoRouteParserOsrmV5::tr("Merge sharply right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge right"); + else + return QGeoRouteParserOsrmV5::tr("Merge right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Merge slightly right on %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge straight"); + else + return QGeoRouteParserOsrmV5::tr("Merge straight on %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Merge"); + else + return QGeoRouteParserOsrmV5::tr("Merge onto %1").arg(wayName); + } +} + +static QString instructionNewName(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take a sharp left"); + else + return QGeoRouteParserOsrmV5::tr("Take a sharp left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn left"); + else + return QGeoRouteParserOsrmV5::tr("Turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Continue slightly left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take a sharp right"); + else + return QGeoRouteParserOsrmV5::tr("Take a sharp right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn right"); + else + return QGeoRouteParserOsrmV5::tr("Turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Contine slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Contine slightly right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue straight"); + else + return QGeoRouteParserOsrmV5::tr("Continue straight onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue"); + else + return QGeoRouteParserOsrmV5::tr("Continue onto %1").arg(wayName); + } +} + +static QString instructionNotification(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue on the left"); + else + return QGeoRouteParserOsrmV5::tr("Continue on the left on %1").arg(wayName); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Contine on the right"); + else + return QGeoRouteParserOsrmV5::tr("Contine on the right on %1").arg(wayName); + case QGeoManeuver::DirectionForward: + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue"); + else + return QGeoRouteParserOsrmV5::tr("Continue on %1").arg(wayName); + } +} + +static QString instructionOffRamp(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take the ramp on the left"); + else + return QGeoRouteParserOsrmV5::tr("Take the ramp on the left onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnRight: + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take the ramp on the right"); + else + return QGeoRouteParserOsrmV5::tr("Take the ramp on the right onto %1").arg(wayName); + case QGeoManeuver::DirectionForward: + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Take the ramp"); + else + return QGeoRouteParserOsrmV5::tr("Take the ramp onto %1").arg(wayName); + } +} + +static QString instructionOnRamp(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + return instructionOffRamp(wayName, direction); +} + +static QString instructionPushingBike(const QString &wayName) +{ + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Get off the bike and push"); + else + return QGeoRouteParserOsrmV5::tr("Get off the bike and push onto %1").arg(wayName); +} + +static QString instructionRotary(const QJsonObject &step, const QJsonObject &maneuver, const QString &wayName) +{ + QString instruction; + QString rotaryName = step.value(QLatin1String("rotary_name")).toString(); + //QString modifier = maneuver.value(QLatin1String("modifier")).toString(); // Apparently not used for rotaries + int exit = maneuver.value(QLatin1String("exit")).toInt(0); + + //: This string will be prepended to " and take the <nth> exit [onto <streetname>] + instruction += QGeoRouteParserOsrmV5::tr("Enter the rotary"); + if (!rotaryName.isEmpty()) + instruction += QLatin1Char(' ') + rotaryName; + instruction += exitDirection(exit, wayName); + return instruction; +} + +static QString instructionRoundabout(const QJsonObject &maneuver, const QString &wayName) +{ + QString instruction; + //QString modifier = maneuver.value(QLatin1String("modifier")).toString(); // Apparently not used for rotaries + int exit = maneuver.value(QLatin1String("exit")).toInt(0); + + //: This string will be prepended to " and take the <nth> exit [onto <streetname>] + instruction += QGeoRouteParserOsrmV5::tr("Enter the roundabout"); + instruction += exitDirection(exit, wayName); + return instruction; +} + +static QString instructionRoundaboutTurn(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue straight"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue straight on %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn left"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn right"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn around"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, turn around onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue"); + else + return QGeoRouteParserOsrmV5::tr("At the roundabout, continue onto %1").arg(wayName); + } +} + +static QString instructionTrain(const QString &wayName) +{ + QString instruction = QGeoRouteParserOsrmV5::tr("Take the train"); + if (!wayName.isEmpty()) + instruction += QLatin1String(" [") + wayName + QLatin1Char(']'); + + return instruction; +} + +static QString instructionTurn(const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Go straight"); + else + return QGeoRouteParserOsrmV5::tr("Go straight onto %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn left"); + else + return QGeoRouteParserOsrmV5::tr("Turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn slightly left"); + else + return QGeoRouteParserOsrmV5::tr("Turn slightly left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn right"); + else + return QGeoRouteParserOsrmV5::tr("Turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn slightly right"); + else + return QGeoRouteParserOsrmV5::tr("Turn slightly right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Make a U-turn"); + else + return QGeoRouteParserOsrmV5::tr("Make a U-turn onto %1").arg(wayName); + default: + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Turn"); + else + return QGeoRouteParserOsrmV5::tr("Turn onto %1").arg(wayName); + } +} + +static QString instructionUseLane(const QJsonObject &maneuver, const QString &wayName, QGeoManeuver::InstructionDirection direction) +{ + QString laneTypes = maneuver.value(QLatin1String("laneTypes")).toString(); + QString laneInstruction; + if (laneTypes == QLatin1String("xo") || laneTypes == QLatin1String("xoo") || laneTypes == QLatin1String("xxo")) + //: "and <instruction direction> [onto <street name>] will be appended to this string. E.g., "Keep right and make a sharp left" + laneInstruction = QLatin1String("Keep right"); + else if (laneTypes == QLatin1String("ox") || laneTypes == QLatin1String("oox") || laneTypes == QLatin1String("oxx")) + laneInstruction = QLatin1String("Keep left"); + else if (laneTypes == QLatin1String("xox")) + laneInstruction = QLatin1String("Use the middle lane"); + else if (laneTypes == QLatin1String("oxo")) + laneInstruction = QLatin1String("Use the left or the right lane"); + + if (laneInstruction.isEmpty()) { + if (wayName.isEmpty()) + return QGeoRouteParserOsrmV5::tr("Continue straight"); + else + return QGeoRouteParserOsrmV5::tr("Continue straight onto %1").arg(wayName); + } + + switch (direction) { + case QGeoManeuver::DirectionForward: + if (wayName.isEmpty()) + //: This string will be prepended with lane instructions. E.g., "Use the left or the right lane and continue straight" + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and continue straight"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and continue straight onto %1").arg(wayName); + case QGeoManeuver::DirectionHardLeft: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp left"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp left onto %1").arg(wayName); + case QGeoManeuver::DirectionLeft: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn left"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn left onto %1").arg(wayName); + case QGeoManeuver::DirectionLightLeft: + case QGeoManeuver::DirectionBearLeft: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight left"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight left onto %1").arg(wayName); + case QGeoManeuver::DirectionHardRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp right"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a sharp right onto %1").arg(wayName); + case QGeoManeuver::DirectionRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn right"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and turn right onto %1").arg(wayName); + case QGeoManeuver::DirectionLightRight: + case QGeoManeuver::DirectionBearRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight right"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a slight right onto %1").arg(wayName); + case QGeoManeuver::DirectionUTurnLeft: + case QGeoManeuver::DirectionUTurnRight: + if (wayName.isEmpty()) + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a U-turn"); + else + return laneInstruction + QGeoRouteParserOsrmV5::tr(" and make a U-turn onto %1").arg(wayName); + default: + return laneInstruction; + } +} + +static QString instructionText(const QJsonObject &step, const QJsonObject &maneuver, QGeoManeuver::InstructionDirection direction) { + QString modifier; + if (maneuver.value(QLatin1String("modifier")).isString()) + modifier = maneuver.value(QLatin1String("modifier")).toString(); + QString maneuverType; + if (maneuver.value(QLatin1String("type")).isString()) + maneuverType = maneuver.value(QLatin1String("type")).toString(); + QString wayName = QStringLiteral("unknown street"); + if (step.value(QLatin1String("name")).isString()) + wayName = step.value(QLatin1String("name")).toString(); + + + if (maneuverType == QLatin1String("arrive")) + return instructionArrive(direction); + else if (maneuverType == QLatin1String("continue")) + return instructionContinue(wayName, direction); + else if (maneuverType == QLatin1String("depart")) + return instructionDepart(maneuver, wayName); + else if (maneuverType == QLatin1String("end of road")) + return instructionEndOfRoad(wayName, direction); + else if (maneuverType == QLatin1String("ferry")) + return instructionFerry(wayName); + else if (maneuverType == QLatin1String("fork")) + return instructionFork(wayName, direction); + else if (maneuverType == QLatin1String("merge")) + return instructionMerge(wayName, direction); + else if (maneuverType == QLatin1String("new name")) + return instructionNewName(wayName, direction); + else if (maneuverType == QLatin1String("notification")) + return instructionNotification(wayName, direction); + else if (maneuverType == QLatin1String("off ramp")) + return instructionOffRamp(wayName, direction); + else if (maneuverType == QLatin1String("on ramp")) + return instructionOnRamp(wayName, direction); + else if (maneuverType == QLatin1String("pushing bike")) + return instructionPushingBike(wayName); + else if (maneuverType == QLatin1String("rotary")) + return instructionRotary(step, maneuver, wayName); + else if (maneuverType == QLatin1String("roundabout")) + return instructionRoundabout(maneuver, wayName); + else if (maneuverType == QLatin1String("roundabout turn")) + return instructionRoundaboutTurn(wayName, direction); + else if (maneuverType == QLatin1String("train")) + return instructionTrain(wayName); + else if (maneuverType == QLatin1String("turn")) + return instructionTurn(wayName, direction); + else if (maneuverType == QLatin1String("use lane")) + return instructionUseLane(maneuver, wayName, direction); + else + return maneuverType + QLatin1String(" to/onto ") + wayName; +} + +static QGeoManeuver::InstructionDirection instructionDirection(const QJsonObject &maneuver) +{ + QString modifier; + if (maneuver.value(QLatin1String("modifier")).isString()) + modifier = maneuver.value(QLatin1String("modifier")).toString(); + + if (modifier.isEmpty()) + return QGeoManeuver::NoDirection; + else if (modifier == QLatin1String("straight")) + return QGeoManeuver::DirectionForward; + else if (modifier == QLatin1String("right")) + return QGeoManeuver::DirectionRight; + else if (modifier == QLatin1String("sharp right")) + return QGeoManeuver::DirectionHardRight; + else if (modifier == QLatin1String("slight right")) + return QGeoManeuver::DirectionLightRight; + else if (modifier == QLatin1String("uturn")) + return QGeoManeuver::DirectionUTurnRight; + else if (modifier == QLatin1String("left")) + return QGeoManeuver::DirectionLeft; + else if (modifier == QLatin1String("sharp left")) + return QGeoManeuver::DirectionHardLeft; + else if (modifier == QLatin1String("slight left")) + return QGeoManeuver::DirectionLightLeft; + else + return QGeoManeuver::NoDirection; +} + +static QGeoRouteSegment parseStep(const QJsonObject &step) { + // OSRM Instructions documentation: https://github.com/Project-OSRM/osrm-text-instructions/blob/master/instructions.json + QGeoRouteSegment segment; + if (!step.value(QLatin1String("maneuver")).isObject()) + return segment; + QJsonObject maneuver = step.value(QLatin1String("maneuver")).toObject(); + if (!step.value(QLatin1String("duration")).isDouble()) + return segment; + if (!step.value(QLatin1String("distance")).isDouble()) + return segment; + if (!step.value(QLatin1String("intersections")).isArray()) + return segment; + if (!maneuver.value(QLatin1String("location")).isArray()) + return segment; + + double time = step.value(QLatin1String("duration")).toDouble(); + double distance = step.value(QLatin1String("distance")).toDouble(); + + QJsonArray position = maneuver.value(QLatin1String("location")).toArray(); + if (position.isEmpty()) + return segment; + double latitude = position[1].toDouble(); + double longitude = position[0].toDouble(); + QGeoCoordinate coord(latitude, longitude); + + QString geometry = step.value(QLatin1String("geometry")).toString(); + QList<QGeoCoordinate> path = decodePolyline(geometry); + + QGeoManeuver geoManeuver; + geoManeuver.setDirection(instructionDirection(maneuver)); + geoManeuver.setDistanceToNextInstruction(distance); + geoManeuver.setTimeToNextInstruction(time); + geoManeuver.setInstructionText(instructionText(step, maneuver, geoManeuver.direction())); + geoManeuver.setPosition(coord); + geoManeuver.setWaypoint(coord); + + segment.setDistance(distance); + segment.setPath(path); + segment.setTravelTime(time); + segment.setManeuver(geoManeuver); + return segment; +} + +class QGeoRouteParserOsrmV5Private : public QGeoRouteParserPrivate +{ + Q_DECLARE_PUBLIC(QGeoRouteParserOsrmV5) +public: + QGeoRouteParserOsrmV5Private(); + virtual ~QGeoRouteParserOsrmV5Private(); + + QGeoRouteReply::Error parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const Q_DECL_OVERRIDE; + QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const Q_DECL_OVERRIDE; +}; + +QGeoRouteParserOsrmV5Private::QGeoRouteParserOsrmV5Private() : QGeoRouteParserPrivate() +{ +} + +QGeoRouteParserOsrmV5Private::~QGeoRouteParserOsrmV5Private() +{ +} + +QGeoRouteReply::Error QGeoRouteParserOsrmV5Private::parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const +{ + // OSRM v5 specs: https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md + QJsonDocument document = QJsonDocument::fromJson(reply); + if (document.isObject()) { + QJsonObject object = document.object(); + + QString status = object.value(QStringLiteral("code")).toString(); + if (status != QLatin1String("Ok")) { + errorString = status; + return QGeoRouteReply::UnknownError; + } + if (!object.value(QLatin1String("routes")).isArray()) { + errorString = QLatin1String("No routes found"); + return QGeoRouteReply::ParseError; + } + + QJsonArray osrmRoutes = object.value(QLatin1String("routes")).toArray(); + foreach (const QJsonValue &r, osrmRoutes) { + if (!r.isObject()) + continue; + QJsonObject route = r.toObject(); + if (!route.value(QLatin1String("legs")).isArray()) + continue; + if (!route.value(QLatin1String("duration")).isDouble()) + continue; + if (!route.value(QLatin1String("distance")).isDouble()) + continue; + + double distance = route.value(QLatin1String("distance")).toDouble(); + double travelTime = route.value(QLatin1String("duration")).toDouble(); + bool error = false; + QList<QGeoRouteSegment> segments; + + QJsonArray legs = route.value(QLatin1String("legs")).toArray(); + foreach (const QJsonValue &l, legs) { + if (!l.isObject()) { // invalid leg record + error = true; + break; + } + QJsonObject leg = l.toObject(); + if (!leg.value(QLatin1String("steps")).isArray()) { // Invalid steps field + error = true; + break; + } + QJsonArray steps = leg.value(QLatin1String("steps")).toArray(); + foreach (const QJsonValue &s, steps) { + if (!s.isObject()) { + error = true; + break; + } + QGeoRouteSegment segment = parseStep(s.toObject()); + if (segment.isValid()) { + segments.append(segment); + } else { + error = true; + break; + } + } + if (error) + break; + } + + if (!error) { + QList<QGeoCoordinate> path; + foreach (const QGeoRouteSegment &s, segments) + path.append(s.path()); + + for (int i = segments.size() - 1; i > 0; --i) + segments[i-1].setNextRouteSegment(segments[i]); + + QGeoRoute r; + r.setDistance(distance); + r.setTravelTime(travelTime); + if (!path.isEmpty()) { + r.setPath(path); + r.setFirstRouteSegment(segments.first()); + } + //r.setTravelMode(QGeoRouteRequest::CarTravel); // The only one supported by OSRM demo service, but other OSRM servers might do cycle or pedestrian too + routes.append(r); + } + } + + // setError(QGeoRouteReply::NoError, status); // can't do this, or NoError is emitted and does damages + return QGeoRouteReply::NoError; + } else { + errorString = QStringLiteral("Couldn't parse json."); + return QGeoRouteReply::ParseError; + } +} + +QUrl QGeoRouteParserOsrmV5Private::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const +{ + QString routingUrl = prefix; + int notFirst = 0; + foreach (const QGeoCoordinate &c, request.waypoints()) { + if (notFirst) + routingUrl.append(QLatin1Char(';')); + routingUrl.append(QString::number(c.longitude())).append(QLatin1Char(',')).append(QString::number(c.latitude())); + ++notFirst; + } + + QUrl url(routingUrl); + QUrlQuery query; + query.addQueryItem(QStringLiteral("overview"), QStringLiteral("full")); + query.addQueryItem(QStringLiteral("steps"), QStringLiteral("true")); + query.addQueryItem(QStringLiteral("geometries"), QStringLiteral("polyline")); + query.addQueryItem(QStringLiteral("alternatives"), QStringLiteral("true")); + url.setQuery(query); + return url; +} + +QGeoRouteParserOsrmV5::QGeoRouteParserOsrmV5(QObject *parent) : QGeoRouteParser(*new QGeoRouteParserOsrmV5Private(), parent) +{ +} + +QGeoRouteParserOsrmV5::~QGeoRouteParserOsrmV5() +{ +} + +QT_END_NAMESPACE diff --git a/src/location/maps/qgeorouteparserosrmv5_p.h b/src/location/maps/qgeorouteparserosrmv5_p.h new file mode 100644 index 00000000..47c68919 --- /dev/null +++ b/src/location/maps/qgeorouteparserosrmv5_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOROUTEPARSEROSRMV5_H +#define QGEOROUTEPARSEROSRMV5_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + +#include <QtLocation/private/qgeorouteparser_p.h> + +QT_BEGIN_NAMESPACE + +class QGeoRouteParserOsrmV5Private; +class Q_LOCATION_EXPORT QGeoRouteParserOsrmV5 : public QGeoRouteParser +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoRouteParserOsrmV5) + +public: + QGeoRouteParserOsrmV5(QObject *parent = Q_NULLPTR); + virtual ~QGeoRouteParserOsrmV5(); + +private: + Q_DISABLE_COPY(QGeoRouteParserOsrmV5) +}; + +QT_END_NAMESPACE + +#endif // QGEOROUTEPARSEROSRMV5_H diff --git a/src/plugins/geoservices/osm/qgeoroutereplyosm.cpp b/src/plugins/geoservices/osm/qgeoroutereplyosm.cpp index a104df54..da28317f 100644 --- a/src/plugins/geoservices/osm/qgeoroutereplyosm.cpp +++ b/src/plugins/geoservices/osm/qgeoroutereplyosm.cpp @@ -38,207 +38,10 @@ ****************************************************************************/ #include "qgeoroutereplyosm.h" - -#include <QtCore/QJsonDocument> -#include <QtCore/QJsonObject> -#include <QtCore/QJsonArray> -#include <QtLocation/QGeoRouteSegment> -#include <QtLocation/QGeoManeuver> +#include "qgeoroutingmanagerengineosm.h" QT_BEGIN_NAMESPACE -static QList<QGeoCoordinate> parsePolyline(const QByteArray &data) -{ - QList<QGeoCoordinate> path; - - bool parsingLatitude = true; - - int shift = 0; - int value = 0; - - QGeoCoordinate coord(0, 0); - - for (int i = 0; i < data.length(); ++i) { - unsigned char c = data.at(i) - 63; - - value |= (c & 0x1f) << shift; - shift += 5; - - // another chunk - if (c & 0x20) - continue; - - int diff = (value & 1) ? ~(value >> 1) : (value >> 1); - - if (parsingLatitude) { - coord.setLatitude(coord.latitude() + (double)diff/1e6); - } else { - coord.setLongitude(coord.longitude() + (double)diff/1e6); - path.append(coord); - } - - parsingLatitude = !parsingLatitude; - - value = 0; - shift = 0; - } - - return path; -} - -static QGeoManeuver::InstructionDirection osrmInstructionDirection(const QString &instructionCode) -{ - if (instructionCode == QLatin1String("0")) - return QGeoManeuver::NoDirection; - else if (instructionCode == QLatin1String("1")) - return QGeoManeuver::DirectionForward; - else if (instructionCode == QLatin1String("2")) - return QGeoManeuver::DirectionBearRight; - else if (instructionCode == QLatin1String("3")) - return QGeoManeuver::DirectionRight; - else if (instructionCode == QLatin1String("4")) - return QGeoManeuver::DirectionHardRight; - else if (instructionCode == QLatin1String("5")) - return QGeoManeuver::DirectionUTurnLeft; - else if (instructionCode == QLatin1String("6")) - return QGeoManeuver::DirectionHardLeft; - else if (instructionCode == QLatin1String("7")) - return QGeoManeuver::DirectionLeft; - else if (instructionCode == QLatin1String("8")) - return QGeoManeuver::DirectionBearLeft; - else if (instructionCode == QLatin1String("9")) - return QGeoManeuver::NoDirection; - else if (instructionCode == QLatin1String("10")) - return QGeoManeuver::DirectionForward; - else if (instructionCode == QLatin1String("11")) - return QGeoManeuver::NoDirection; - else if (instructionCode == QLatin1String("12")) - return QGeoManeuver::NoDirection; - else if (instructionCode == QLatin1String("13")) - return QGeoManeuver::NoDirection; - else if (instructionCode == QLatin1String("14")) - return QGeoManeuver::NoDirection; - else if (instructionCode == QLatin1String("15")) - return QGeoManeuver::NoDirection; - else - return QGeoManeuver::NoDirection; -} - -const QString osrmInstructionText(const QString &instructionCode, const QString &wayname) -{ - if (instructionCode == QLatin1String("0")) { - return QString(); - } else if (instructionCode == QLatin1String("1")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Go straight."); - else - return QGeoRouteReplyOsm::tr("Go straight onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("2")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Turn slightly right."); - else - return QGeoRouteReplyOsm::tr("Turn slightly right onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("3")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Turn right."); - else - return QGeoRouteReplyOsm::tr("Turn right onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("4")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Make a sharp right."); - else - return QGeoRouteReplyOsm::tr("Make a sharp right onto %1.").arg(wayname); - } - else if (instructionCode == QLatin1String("5")) { - return QGeoRouteReplyOsm::tr("When it is safe to do so, perform a U-turn."); - } else if (instructionCode == QLatin1String("6")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Make a sharp left."); - else - return QGeoRouteReplyOsm::tr("Make a sharp left onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("7")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Turn left."); - else - return QGeoRouteReplyOsm::tr("Turn left onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("8")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Turn slightly left."); - else - return QGeoRouteReplyOsm::tr("Turn slightly left onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("9")) { - return QGeoRouteReplyOsm::tr("Reached waypoint."); - } else if (instructionCode == QLatin1String("10")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Head on."); - else - return QGeoRouteReplyOsm::tr("Head onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11")) { - return QGeoRouteReplyOsm::tr("Enter the roundabout."); - } else if (instructionCode == QLatin1String("11-1")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the first exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the first exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-2")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the second exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the second exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-3")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the third exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the third exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-4")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the fourth exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the fourth exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-5")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the fifth exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the fifth exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-6")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the sixth exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the sixth exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-7")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the seventh exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the seventh exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-8")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the eighth exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the eighth exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("11-9")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("At the roundabout take the ninth exit."); - else - return QGeoRouteReplyOsm::tr("At the roundabout take the ninth exit onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("12")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Leave the roundabout."); - else - return QGeoRouteReplyOsm::tr("Leave the roundabout onto %1.").arg(wayname); - } else if (instructionCode == QLatin1String("13")) { - return QGeoRouteReplyOsm::tr("Stay on the roundabout."); - } else if (instructionCode == QLatin1String("14")) { - if (wayname.isEmpty()) - return QGeoRouteReplyOsm::tr("Start at the end of the street."); - else - return QGeoRouteReplyOsm::tr("Start at the end of %1.").arg(wayname); - } else if (instructionCode == QLatin1String("15")) { - return QGeoRouteReplyOsm::tr("You have reached your destination."); - } else { - return QGeoRouteReplyOsm::tr("Don't know what to say for '%1'").arg(instructionCode); - } -} - QGeoRouteReplyOsm::QGeoRouteReplyOsm(QNetworkReply *reply, const QGeoRouteRequest &request, QObject *parent) : QGeoRouteReply(request, parent), m_reply(reply) @@ -265,70 +68,6 @@ void QGeoRouteReplyOsm::abort() m_reply = 0; } -static QGeoRoute constructRoute(const QByteArray &geometry, const QJsonArray &instructions, - const QJsonObject &summary) -{ - QGeoRoute route; - - QList<QGeoCoordinate> path = parsePolyline(geometry); - - QGeoRouteSegment firstSegment; - int firstPosition = -1; - - int segmentPathLengthCount = 0; - - for (int i = instructions.count() - 1; i >= 0; --i) { - QJsonArray instruction = instructions.at(i).toArray(); - - if (instruction.count() < 8) { - qWarning("Instruction does not contain enough fields."); - continue; - } - - const QString instructionCode = instruction.at(0).toString(); - const QString wayname = instruction.at(1).toString(); - double segmentLength = instruction.at(2).toDouble(); - int position = instruction.at(3).toDouble(); - int time = instruction.at(4).toDouble(); - //const QString segmentLengthString = instruction.at(5).toString(); - //const QString direction = instruction.at(6).toString(); - //double azimuth = instruction.at(7).toDouble(); - - QGeoRouteSegment segment; - segment.setDistance(segmentLength); - - QGeoManeuver maneuver; - maneuver.setDirection(osrmInstructionDirection(instructionCode)); - maneuver.setDistanceToNextInstruction(segmentLength); - maneuver.setInstructionText(osrmInstructionText(instructionCode, wayname)); - maneuver.setPosition(path.at(position)); - maneuver.setTimeToNextInstruction(time); - - segment.setManeuver(maneuver); - - if (firstPosition == -1) - segment.setPath(path.mid(position)); - else - segment.setPath(path.mid(position, firstPosition - position)); - - segmentPathLengthCount += segment.path().length(); - - segment.setTravelTime(time); - - segment.setNextRouteSegment(firstSegment); - - firstSegment = segment; - firstPosition = position; - } - - route.setDistance(summary.value(QStringLiteral("total_distance")).toDouble()); - route.setTravelTime(summary.value(QStringLiteral("total_time")).toDouble()); - route.setFirstRouteSegment(firstSegment); - route.setPath(path); - - return route; -} - void QGeoRouteReplyOsm::networkReplyFinished() { if (!m_reply) @@ -341,59 +80,26 @@ void QGeoRouteReplyOsm::networkReplyFinished() return; } - QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll()); - - if (document.isObject()) { - QJsonObject object = document.object(); - - //double version = object.value(QStringLiteral("version")).toDouble(); - int status = object.value(QStringLiteral("status")).toDouble(); - QString statusMessage = object.value(QStringLiteral("status_message")).toString(); - - // status code 0 or 200 are case of success - // status code is 207 if no route was found - // an error occurred when trying to find a route - if (0 != status && 200 != status) { - setError(QGeoRouteReply::UnknownError, statusMessage); - m_reply->deleteLater(); - m_reply = 0; - return; - } - - QJsonObject routeSummary = object.value(QStringLiteral("route_summary")).toObject(); - - QByteArray routeGeometry = - object.value(QStringLiteral("route_geometry")).toString().toLatin1(); - - QJsonArray routeInstructions = object.value(QStringLiteral("route_instructions")).toArray(); - - QGeoRoute route = constructRoute(routeGeometry, routeInstructions, routeSummary); - - QList<QGeoRoute> routes; - routes.append(route); - - QJsonArray alternativeSummaries = - object.value(QStringLiteral("alternative_summaries")).toArray(); - QJsonArray alternativeGeometries = - object.value(QStringLiteral("alternative_geometries")).toArray(); - QJsonArray alternativeInstructions = - object.value(QStringLiteral("alternative_instructions")).toArray(); + if (m_reply->error() != QNetworkReply::NoError) { + setError(QGeoRouteReply::CommunicationError, m_reply->errorString()); + m_reply->deleteLater(); + m_reply = 0; + return; + } - if (alternativeSummaries.count() == alternativeGeometries.count() && - alternativeSummaries.count() == alternativeInstructions.count()) { - for (int i = 0; i < alternativeSummaries.count(); ++i) { - route = constructRoute(alternativeGeometries.at(i).toString().toLatin1(), - alternativeInstructions.at(i).toArray(), - alternativeSummaries.at(i).toObject()); - //routes.append(route); - } - } + QGeoRoutingManagerEngineOsm *engine = qobject_cast<QGeoRoutingManagerEngineOsm *>(parent()); + const QGeoRouteParser *parser = engine->routeParser(); - setRoutes(routes); + QList<QGeoRoute> routes; + QString errorString; + QGeoRouteReply::Error error = parser->parseReply(routes, errorString, m_reply->readAll()); + if (error == QGeoRouteReply::NoError) { + setRoutes(routes.mid(0,1)); // TODO QTBUG-56426 + // setError(QGeoRouteReply::NoError, status); // can't do this, or NoError is emitted and does damages setFinished(true); } else { - setError(QGeoRouteReply::ParseError, QStringLiteral("Couldn't parse json.")); + setError(error, errorString); } m_reply->deleteLater(); diff --git a/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp index 0d7277a5..12db22a9 100644 --- a/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp +++ b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.cpp @@ -39,6 +39,8 @@ #include "qgeoroutingmanagerengineosm.h" #include "qgeoroutereplyosm.h" +#include "QtLocation/private/qgeorouteparserosrmv4_p.h" +#include "QtLocation/private/qgeorouteparserosrmv5_p.h" #include <QtCore/QUrlQuery> @@ -57,7 +59,14 @@ QGeoRoutingManagerEngineOsm::QGeoRoutingManagerEngineOsm(const QVariantMap ¶ if (parameters.contains(QStringLiteral("osm.routing.host"))) m_urlPrefix = parameters.value(QStringLiteral("osm.routing.host")).toString().toLatin1(); else - m_urlPrefix = QStringLiteral("http://router.project-osrm.org/viaroute"); + m_urlPrefix = QStringLiteral("http://router.project-osrm.org/route/v1/driving/"); + // for v4 it was "http://router.project-osrm.org/viaroute" + + if (parameters.contains(QStringLiteral("osm.routing.apiversion")) + && (parameters.value(QStringLiteral("osm.routing.apiversion")).toString().toLatin1() == QByteArray("v4"))) + m_routeParser = new QGeoRouteParserOsrmV4(this); + else + m_routeParser = new QGeoRouteParserOsrmV5(this); *error = QGeoServiceProvider::NoError; errorString->clear(); @@ -70,20 +79,9 @@ QGeoRoutingManagerEngineOsm::~QGeoRoutingManagerEngineOsm() QGeoRouteReply* QGeoRoutingManagerEngineOsm::calculateRoute(const QGeoRouteRequest &request) { QNetworkRequest networkRequest; - networkRequest.setRawHeader("User-Agent", m_userAgent); - - QUrl url(m_urlPrefix); - QUrlQuery query; - - query.addQueryItem(QStringLiteral("instructions"), QStringLiteral("true")); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent); - foreach (const QGeoCoordinate &c, request.waypoints()) { - query.addQueryItem(QStringLiteral("loc"), QString::number(c.latitude()) + QLatin1Char(',') + - QString::number(c.longitude())); - } - - url.setQuery(query); - networkRequest.setUrl(url); + networkRequest.setUrl(routeParser()->requestUrl(request, m_urlPrefix)); QNetworkReply *reply = m_networkManager->get(networkRequest); @@ -96,6 +94,11 @@ QGeoRouteReply* QGeoRoutingManagerEngineOsm::calculateRoute(const QGeoRouteReque return routeReply; } +const QGeoRouteParser *QGeoRoutingManagerEngineOsm::routeParser() const +{ + return m_routeParser; +} + void QGeoRoutingManagerEngineOsm::replyFinished() { QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender()); diff --git a/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h index 0dac897a..8e2d7f50 100644 --- a/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h +++ b/src/plugins/geoservices/osm/qgeoroutingmanagerengineosm.h @@ -42,6 +42,7 @@ #include <QtLocation/QGeoServiceProvider> #include <QtLocation/QGeoRoutingManagerEngine> +#include <QtLocation/private/qgeorouteparser_p.h> QT_BEGIN_NAMESPACE @@ -58,6 +59,7 @@ public: ~QGeoRoutingManagerEngineOsm(); QGeoRouteReply *calculateRoute(const QGeoRouteRequest &request); + const QGeoRouteParser *routeParser() const; private Q_SLOTS: void replyFinished(); @@ -65,6 +67,7 @@ private Q_SLOTS: private: QNetworkAccessManager *m_networkManager; + QGeoRouteParser *m_routeParser; QByteArray m_userAgent; QString m_urlPrefix; }; diff --git a/src/positioning/qlocationutils_p.h b/src/positioning/qlocationutils_p.h index b5d90b93..00c4d3e3 100644 --- a/src/positioning/qlocationutils_p.h +++ b/src/positioning/qlocationutils_p.h @@ -51,6 +51,7 @@ // #include <QtCore/QtGlobal> +#include <math.h> QT_BEGIN_NAMESPACE class QTime; @@ -60,6 +61,25 @@ class QGeoPositionInfo; class QLocationUtils { public: + enum CardinalDirection { + CardinalN, + CardinalE, + CardinalS, + CardinalW, + CardinalNE, + CardinalSE, + CardinalSW, + CardinalNW, + CardinalNNE, + CardinalENE, + CardinalESE, + CardinalSSE, + CardinalSSW, + CardinalWSW, + CardinalWNW, + CardinalNNW + }; + inline static bool isValidLat(double lat) { return lat >= -90 && lat <= 90; } @@ -83,6 +103,79 @@ public: return lng; } + inline static CardinalDirection azimuthToCardinalDirection4(double azimuth) + { + azimuth = fmod(azimuth, 360.0); + if (azimuth < 45.0 || azimuth > 315.0 ) + return CardinalN; + else if (azimuth < 135.0) + return CardinalE; + else if (azimuth < 225.0) + return CardinalS; + else + return CardinalW; + } + + inline static CardinalDirection azimuthToCardinalDirection8(double azimuth) + { + azimuth = fmod(azimuth, 360.0); + if (azimuth < 22.5 || azimuth > 337.5 ) + return CardinalN; + else if (azimuth < 67.5) + return CardinalNE; + else if (azimuth < 112.5) + return CardinalE; + else if (azimuth < 157.5) + return CardinalSE; + else if (azimuth < 202.5) + return CardinalS; + + else if (azimuth < 247.5) + return CardinalSW; + else if (azimuth < 292.5) + return CardinalW; + else + return CardinalNW; + } + + inline static CardinalDirection azimuthToCardinalDirection16(double azimuth) + { + azimuth = fmod(azimuth, 360.0); + if (azimuth < 11.5 || azimuth > 348.75 ) + return CardinalN; + else if (azimuth < 33.75) + return CardinalNNE; + else if (azimuth < 56.25) + return CardinalNE; + else if (azimuth < 78.75) + return CardinalENE; + else if (azimuth < 101.25) + return CardinalE; + else if (azimuth < 123.75) + return CardinalESE; + else if (azimuth < 146.25) + return CardinalSE; + else if (azimuth < 168.75) + return CardinalSSE; + else if (azimuth < 191.25) + return CardinalS; + + else if (azimuth < 213.75) + return CardinalSSW; + else if (azimuth < 236.25) + return CardinalSW; + else if (azimuth < 258.75) + return CardinalWSW; + else if (azimuth < 281.25) + return CardinalW; + else if (azimuth < 303.75) + return CardinalWNW; + else if (azimuth < 326.25) + return CardinalNW; + else + return CardinalNNW; + } + /* Creates a QGeoPositionInfo from a GGA, GLL, RMC, VTG or ZDA sentence. |