/**************************************************************************** ** ** 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 #include #include #include #include #include #include QT_BEGIN_NAMESPACE static QList decodePolyline(const QString &polylineString) { QList 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 ]" 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 ordinals; if (!ordinals.size()) { ordinals.append(QLatin1String("")); //: always used in " and take the %1 exit [onto ]" 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 " */ 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 exit [onto ] 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 exit [onto ] 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 [onto ] 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 &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 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 &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 segments; QJsonArray legs = routeObject.value(QLatin1String("legs")).toArray(); QList routeLegs; QGeoRoute route; for (int legIndex = 0; legIndex < legs.size(); ++legIndex) { const QJsonValue &l = legs.at(legIndex); QGeoRouteLeg routeLeg; QList 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 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 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 metadata = request.waypointsMetadata(); const QList 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