| /**************************************************************************** |
| ** |
| ** 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, QGeoRouteParser::TrafficSide trafficSide) |
| { |
| 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")) { |
| switch (trafficSide) { |
| case QGeoRouteParser::RightHandTraffic: |
| return QGeoManeuver::DirectionUTurnLeft; |
| case QGeoRouteParser::LeftHandTraffic: |
| return QGeoManeuver::DirectionUTurnRight; |
| } |
| 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, QGeoRouteParser::TrafficSide trafficSide) |
| { |
| 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, trafficSide)); |
| 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 override; |
| QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const 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, trafficSide); |
| |
| 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(), |
| trafficSide); |
| //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 |