blob: cc39158fa67ed442e6ddacb2f68831daf8ac64bb [file] [log] [blame]
/****************************************************************************
**
** 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 "qgeoroute.h"
#include "qgeoroute_p.h"
#include "qgeorouteparser_p_p.h"
#include "qgeoroutesegment.h"
#include "qgeoroutesegment_p.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>
#include <QtPositioning/qgeopath.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/1e6);
} else {
coord.setLongitude(coord.longitude() + (double)diff/1e6);
path.append(coord);
}
parsingLatitude = !parsingLatitude;
value = 0;
shift = 0;
}
return path;
}
static QString cardinalDirection4(QLocationUtils::CardinalDirection direction)
{
switch (direction) {
case QLocationUtils::CardinalN:
//: Translations exist at https://github.com/Project-OSRM/osrm-text-instructions.
//: 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(QLatin1String(""));
//: 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("Continue slightly right");
else
return QGeoRouteParserOsrmV5::tr("Continue 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("Continue on the right");
else
return QGeoRouteParserOsrmV5::tr("Continue 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)
{
return wayName.isEmpty()
? QGeoRouteParserOsrmV5::tr("Take the train")
: QGeoRouteParserOsrmV5::tr("Take the train [%1]").arg(wayName);
}
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 = QLatin1String("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, QGeoRouteParser::TrafficSide trafficSide)
{
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")) {
switch (trafficSide) {
case QGeoRouteParser::RightHandTraffic:
return QGeoManeuver::DirectionUTurnLeft;
case QGeoRouteParser::LeftHandTraffic:
return QGeoManeuver::DirectionUTurnRight;
}
return QGeoManeuver::DirectionUTurnLeft;
} 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;
}
class QGeoRouteParserOsrmV5Private : public QGeoRouteParserPrivate
{
Q_DECLARE_PUBLIC(QGeoRouteParserOsrmV5)
public:
QGeoRouteParserOsrmV5Private();
virtual ~QGeoRouteParserOsrmV5Private();
QGeoRouteSegment parseStep(const QJsonObject &step, int legIndex, int stepIndex) const;
// QGeoRouteParserPrivate
QGeoRouteReply::Error parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const override;
QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const override;
QVariantMap m_vendorParams;
const QGeoRouteParserOsrmV5Extension *m_extension = nullptr;
};
QGeoRouteParserOsrmV5Private::QGeoRouteParserOsrmV5Private()
: QGeoRouteParserPrivate()
{
}
QGeoRouteParserOsrmV5Private::~QGeoRouteParserOsrmV5Private()
{
delete m_extension;
}
QGeoRouteSegment QGeoRouteParserOsrmV5Private::parseStep(const QJsonObject &step, int legIndex, int stepIndex) const {
// OSRM Instructions documentation: https://github.com/Project-OSRM/osrm-text-instructions
// This goes on top of OSRM: https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md
// Mapbox however, includes this in the reply, under "instruction".
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::InstructionDirection maneuverInstructionDirection = instructionDirection(maneuver, trafficSide);
QString maneuverInstructionText = instructionText(step, maneuver, maneuverInstructionDirection);
QGeoManeuver geoManeuver;
geoManeuver.setDirection(maneuverInstructionDirection);
geoManeuver.setDistanceToNextInstruction(distance);
geoManeuver.setTimeToNextInstruction(time);
geoManeuver.setInstructionText(maneuverInstructionText);
geoManeuver.setPosition(coord);
geoManeuver.setWaypoint(coord);
QVariantMap extraAttributes;
static const QStringList extras {
QLatin1String("bearing_before"),
QLatin1String("bearing_after"),
QLatin1String("instruction"),
QLatin1String("type"),
QLatin1String("modifier") };
for (const QString &e: extras) {
if (maneuver.find(e) != maneuver.end())
extraAttributes.insert(e, maneuver.value(e).toVariant());
}
// These should be removed as soon as route leg support is introduced.
// Ref: http://project-osrm.org/docs/v5.15.2/api/#routeleg-object
extraAttributes.insert(QLatin1String("leg_index"), legIndex);
extraAttributes.insert(QLatin1String("step_index"), stepIndex);
geoManeuver.setExtendedAttributes(extraAttributes);
segment.setDistance(distance);
segment.setPath(path);
segment.setTravelTime(time);
segment.setManeuver(geoManeuver);
if (m_extension)
m_extension->updateSegment(segment, step, maneuver);
return segment;
}
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
// Mapbox Directions API spec: https://www.mapbox.com/api-documentation/#directions
QJsonDocument document = QJsonDocument::fromJson(reply);
if (document.isObject()) {
QJsonObject object = document.object();
QString status = object.value(QLatin1String("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 routeObject = r.toObject();
if (!routeObject.value(QLatin1String("legs")).isArray())
continue;
if (!routeObject.value(QLatin1String("duration")).isDouble())
continue;
if (!routeObject.value(QLatin1String("distance")).isDouble())
continue;
double distance = routeObject.value(QLatin1String("distance")).toDouble();
double travelTime = routeObject.value(QLatin1String("duration")).toDouble();
bool error = false;
QList<QGeoRouteSegment> segments;
QJsonArray legs = routeObject.value(QLatin1String("legs")).toArray();
QList<QGeoRouteLeg> routeLegs;
QGeoRoute route;
for (int legIndex = 0; legIndex < legs.size(); ++legIndex) {
const QJsonValue &l = legs.at(legIndex);
QGeoRouteLeg routeLeg;
QList<QGeoRouteSegment> legSegments;
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;
}
const double legDistance = leg.value(QLatin1String("distance")).toDouble();
const double legTravelTime = leg.value(QLatin1String("duration")).toDouble();
QJsonArray steps = leg.value(QLatin1String("steps")).toArray();
QGeoRouteSegment segment;
for (int stepIndex = 0; stepIndex < steps.size(); ++stepIndex) {
const QJsonValue &s = steps.at(stepIndex);
if (!s.isObject()) {
error = true;
break;
}
segment = parseStep(s.toObject(), legIndex, stepIndex);
if (segment.isValid()) {
// setNextRouteSegment done below for all segments in the route.
legSegments.append(segment);
} else {
error = true;
break;
}
}
if (error)
break;
QGeoRouteSegmentPrivate *segmentPrivate = QGeoRouteSegmentPrivate::get(segment);
segmentPrivate->setLegLastSegment(true);
QList<QGeoCoordinate> path;
for (const QGeoRouteSegment &s: qAsConst(legSegments))
path.append(s.path());
routeLeg.setLegIndex(legIndex);
routeLeg.setOverallRoute(route); // QGeoRoute::d_ptr is explicitlySharedDataPointer. Modifiers below won't detach it.
routeLeg.setDistance(legDistance);
routeLeg.setTravelTime(legTravelTime);
if (!path.isEmpty()) {
routeLeg.setPath(path);
routeLeg.setFirstRouteSegment(legSegments.first());
}
routeLegs << routeLeg;
segments.append(legSegments);
}
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]);
route.setDistance(distance);
route.setTravelTime(travelTime);
if (!path.isEmpty()) {
route.setPath(path);
route.setBounds(QGeoPath(path).boundingGeoRectangle());
route.setFirstRouteSegment(segments.first());
}
route.setRouteLegs(routeLegs);
//r.setTravelMode(QGeoRouteRequest::CarTravel); // The only one supported by OSRM demo service, but other OSRM servers might do cycle or pedestrian too
routes.append(route);
}
}
// setError(QGeoRouteReply::NoError, status); // can't do this, or NoError is emitted and does damages
return QGeoRouteReply::NoError;
} else {
errorString = QLatin1String("Couldn't parse json.");
return QGeoRouteReply::ParseError;
}
}
QUrl QGeoRouteParserOsrmV5Private::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const
{
QString routingUrl = prefix;
int notFirst = 0;
QString bearings;
const QList<QVariantMap> metadata = request.waypointsMetadata();
const QList<QGeoCoordinate> waypoints = request.waypoints();
for (int i = 0; i < waypoints.size(); i++) {
const QGeoCoordinate &c = waypoints.at(i);
if (notFirst) {
routingUrl.append(QLatin1Char(';'));
bearings.append(QLatin1Char(';'));
}
routingUrl.append(QString::number(c.longitude(), 'f', 7)).append(QLatin1Char(',')).append(QString::number(c.latitude(), 'f', 7));
if (metadata.size() > i) {
const QVariantMap &meta = metadata.at(i);
if (meta.contains(QLatin1String("bearing"))) {
qreal bearing = meta.value(QLatin1String("bearing")).toDouble();
bearings.append(QString::number(int(bearing))).append(QLatin1Char(',')).append(QLatin1String("90")); // 90 is the angle of maneuver allowed.
} else {
bearings.append(QLatin1String("0,180")); // 180 here means anywhere
}
}
++notFirst;
}
QUrl url(routingUrl);
QUrlQuery query;
query.addQueryItem(QLatin1String("overview"), QLatin1String("full"));
query.addQueryItem(QLatin1String("steps"), QLatin1String("true"));
query.addQueryItem(QLatin1String("geometries"), QLatin1String("polyline6"));
query.addQueryItem(QLatin1String("alternatives"), QLatin1String("true"));
query.addQueryItem(QLatin1String("bearings"), bearings);
if (m_extension)
m_extension->updateQuery(query);
url.setQuery(query);
return url;
}
QGeoRouteParserOsrmV5::QGeoRouteParserOsrmV5(QObject *parent)
: QGeoRouteParser(*new QGeoRouteParserOsrmV5Private(), parent)
{
}
QGeoRouteParserOsrmV5::~QGeoRouteParserOsrmV5()
{
}
void QGeoRouteParserOsrmV5::setExtension(const QGeoRouteParserOsrmV5Extension *extension)
{
Q_D(QGeoRouteParserOsrmV5);
if (extension)
d->m_extension = extension;
}
QT_END_NAMESPACE