blob: 24c920b0ab59caf67532681792ed0db9055033ce [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 "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