1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtLocation module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qgeorouteparserosrmv4_p.h"
38#include "qgeorouteparser_p_p.h"
39#include "qgeoroutesegment.h"
40#include "qgeomaneuver.h"
41
42#include <QtCore/private/qobject_p.h>
43#include <QtCore/QJsonDocument>
44#include <QtCore/QJsonObject>
45#include <QtCore/QJsonArray>
46#include <QtCore/QUrlQuery>
47
48QT_BEGIN_NAMESPACE
49
50static QList<QGeoCoordinate> parsePolyline(const QByteArray &data)
51{
52 QList<QGeoCoordinate> path;
53
54 bool parsingLatitude = true;
55
56 int shift = 0;
57 int value = 0;
58
59 QGeoCoordinate coord(0, 0);
60
61 for (int i = 0; i < data.length(); ++i) {
62 unsigned char c = data.at(i) - 63;
63
64 value |= (c & 0x1f) << shift;
65 shift += 5;
66
67 // another chunk
68 if (c & 0x20)
69 continue;
70
71 int diff = (value & 1) ? ~(value >> 1) : (value >> 1);
72
73 if (parsingLatitude) {
74 coord.setLatitude(coord.latitude() + (double)diff/1e6);
75 } else {
76 coord.setLongitude(coord.longitude() + (double)diff/1e6);
77 path.append(t: coord);
78 }
79
80 parsingLatitude = !parsingLatitude;
81
82 value = 0;
83 shift = 0;
84 }
85
86 return path;
87}
88
89static QGeoManeuver::InstructionDirection osrmInstructionDirection(const QString &instructionCode, QGeoRouteParser::TrafficSide trafficSide)
90{
91 if (instructionCode == QLatin1String("0"))
92 return QGeoManeuver::NoDirection;
93 else if (instructionCode == QLatin1String("1"))
94 return QGeoManeuver::DirectionForward;
95 else if (instructionCode == QLatin1String("2"))
96 return QGeoManeuver::DirectionBearRight;
97 else if (instructionCode == QLatin1String("3"))
98 return QGeoManeuver::DirectionRight;
99 else if (instructionCode == QLatin1String("4"))
100 return QGeoManeuver::DirectionHardRight;
101 else if (instructionCode == QLatin1String("5")) {
102 switch (trafficSide) {
103 case QGeoRouteParser::RightHandTraffic:
104 return QGeoManeuver::DirectionUTurnLeft;
105 case QGeoRouteParser::LeftHandTraffic:
106 return QGeoManeuver::DirectionUTurnRight;
107 }
108 return QGeoManeuver::DirectionUTurnLeft;
109 } else if (instructionCode == QLatin1String("6"))
110 return QGeoManeuver::DirectionHardLeft;
111 else if (instructionCode == QLatin1String("7"))
112 return QGeoManeuver::DirectionLeft;
113 else if (instructionCode == QLatin1String("8"))
114 return QGeoManeuver::DirectionBearLeft;
115 else if (instructionCode == QLatin1String("9"))
116 return QGeoManeuver::NoDirection;
117 else if (instructionCode == QLatin1String("10"))
118 return QGeoManeuver::DirectionForward;
119 else if (instructionCode == QLatin1String("11"))
120 return QGeoManeuver::NoDirection;
121 else if (instructionCode == QLatin1String("12"))
122 return QGeoManeuver::NoDirection;
123 else if (instructionCode == QLatin1String("13"))
124 return QGeoManeuver::NoDirection;
125 else if (instructionCode == QLatin1String("14"))
126 return QGeoManeuver::NoDirection;
127 else if (instructionCode == QLatin1String("15"))
128 return QGeoManeuver::NoDirection;
129 else
130 return QGeoManeuver::NoDirection;
131}
132
133static QString osrmInstructionText(const QString &instructionCode, const QString &wayname)
134{
135 if (instructionCode == QLatin1String("0")) {
136 return QString();
137 } else if (instructionCode == QLatin1String("1")) {
138 if (wayname.isEmpty())
139 return QGeoRouteParserOsrmV4::tr(s: "Go straight.");
140 else
141 return QGeoRouteParserOsrmV4::tr(s: "Go straight onto %1.").arg(a: wayname);
142 } else if (instructionCode == QLatin1String("2")) {
143 if (wayname.isEmpty())
144 return QGeoRouteParserOsrmV4::tr(s: "Turn slightly right.");
145 else
146 return QGeoRouteParserOsrmV4::tr(s: "Turn slightly right onto %1.").arg(a: wayname);
147 } else if (instructionCode == QLatin1String("3")) {
148 if (wayname.isEmpty())
149 return QGeoRouteParserOsrmV4::tr(s: "Turn right.");
150 else
151 return QGeoRouteParserOsrmV4::tr(s: "Turn right onto %1.").arg(a: wayname);
152 } else if (instructionCode == QLatin1String("4")) {
153 if (wayname.isEmpty())
154 return QGeoRouteParserOsrmV4::tr(s: "Make a sharp right.");
155 else
156 return QGeoRouteParserOsrmV4::tr(s: "Make a sharp right onto %1.").arg(a: wayname);
157 }
158 else if (instructionCode == QLatin1String("5")) {
159 return QGeoRouteParserOsrmV4::tr(s: "When it is safe to do so, perform a U-turn.");
160 } else if (instructionCode == QLatin1String("6")) {
161 if (wayname.isEmpty())
162 return QGeoRouteParserOsrmV4::tr(s: "Make a sharp left.");
163 else
164 return QGeoRouteParserOsrmV4::tr(s: "Make a sharp left onto %1.").arg(a: wayname);
165 } else if (instructionCode == QLatin1String("7")) {
166 if (wayname.isEmpty())
167 return QGeoRouteParserOsrmV4::tr(s: "Turn left.");
168 else
169 return QGeoRouteParserOsrmV4::tr(s: "Turn left onto %1.").arg(a: wayname);
170 } else if (instructionCode == QLatin1String("8")) {
171 if (wayname.isEmpty())
172 return QGeoRouteParserOsrmV4::tr(s: "Turn slightly left.");
173 else
174 return QGeoRouteParserOsrmV4::tr(s: "Turn slightly left onto %1.").arg(a: wayname);
175 } else if (instructionCode == QLatin1String("9")) {
176 return QGeoRouteParserOsrmV4::tr(s: "Reached waypoint.");
177 } else if (instructionCode == QLatin1String("10")) {
178 if (wayname.isEmpty())
179 return QGeoRouteParserOsrmV4::tr(s: "Head on.");
180 else
181 return QGeoRouteParserOsrmV4::tr(s: "Head onto %1.").arg(a: wayname);
182 } else if (instructionCode == QLatin1String("11")) {
183 return QGeoRouteParserOsrmV4::tr(s: "Enter the roundabout.");
184 } else if (instructionCode == QLatin1String("11-1")) {
185 if (wayname.isEmpty())
186 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the first exit.");
187 else
188 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the first exit onto %1.").arg(a: wayname);
189 } else if (instructionCode == QLatin1String("11-2")) {
190 if (wayname.isEmpty())
191 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the second exit.");
192 else
193 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the second exit onto %1.").arg(a: wayname);
194 } else if (instructionCode == QLatin1String("11-3")) {
195 if (wayname.isEmpty())
196 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the third exit.");
197 else
198 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the third exit onto %1.").arg(a: wayname);
199 } else if (instructionCode == QLatin1String("11-4")) {
200 if (wayname.isEmpty())
201 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the fourth exit.");
202 else
203 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the fourth exit onto %1.").arg(a: wayname);
204 } else if (instructionCode == QLatin1String("11-5")) {
205 if (wayname.isEmpty())
206 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the fifth exit.");
207 else
208 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the fifth exit onto %1.").arg(a: wayname);
209 } else if (instructionCode == QLatin1String("11-6")) {
210 if (wayname.isEmpty())
211 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the sixth exit.");
212 else
213 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the sixth exit onto %1.").arg(a: wayname);
214 } else if (instructionCode == QLatin1String("11-7")) {
215 if (wayname.isEmpty())
216 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the seventh exit.");
217 else
218 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the seventh exit onto %1.").arg(a: wayname);
219 } else if (instructionCode == QLatin1String("11-8")) {
220 if (wayname.isEmpty())
221 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the eighth exit.");
222 else
223 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the eighth exit onto %1.").arg(a: wayname);
224 } else if (instructionCode == QLatin1String("11-9")) {
225 if (wayname.isEmpty())
226 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the ninth exit.");
227 else
228 return QGeoRouteParserOsrmV4::tr(s: "At the roundabout take the ninth exit onto %1.").arg(a: wayname);
229 } else if (instructionCode == QLatin1String("12")) {
230 if (wayname.isEmpty())
231 return QGeoRouteParserOsrmV4::tr(s: "Leave the roundabout.");
232 else
233 return QGeoRouteParserOsrmV4::tr(s: "Leave the roundabout onto %1.").arg(a: wayname);
234 } else if (instructionCode == QLatin1String("13")) {
235 return QGeoRouteParserOsrmV4::tr(s: "Stay on the roundabout.");
236 } else if (instructionCode == QLatin1String("14")) {
237 if (wayname.isEmpty())
238 return QGeoRouteParserOsrmV4::tr(s: "Start at the end of the street.");
239 else
240 return QGeoRouteParserOsrmV4::tr(s: "Start at the end of %1.").arg(a: wayname);
241 } else if (instructionCode == QLatin1String("15")) {
242 return QGeoRouteParserOsrmV4::tr(s: "You have reached your destination.");
243 } else {
244 return QGeoRouteParserOsrmV4::tr(s: "Don't know what to say for '%1'").arg(a: instructionCode);
245 }
246}
247
248static QGeoRoute constructRoute(const QByteArray &geometry, const QJsonArray &instructions,
249 const QJsonObject &summary, QGeoRouteParser::TrafficSide trafficSide)
250{
251 QGeoRoute route;
252
253 QList<QGeoCoordinate> path = parsePolyline(data: geometry);
254
255 QGeoRouteSegment firstSegment;
256 int firstPosition = -1;
257
258 int segmentPathLengthCount = 0;
259
260 for (int i = instructions.count() - 1; i >= 0; --i) {
261 QJsonArray instruction = instructions.at(i).toArray();
262
263 if (instruction.count() < 8) {
264 qWarning(msg: "Instruction does not contain enough fields.");
265 continue;
266 }
267
268 const QString instructionCode = instruction.at(i: 0).toString();
269 const QString wayname = instruction.at(i: 1).toString();
270 double segmentLength = instruction.at(i: 2).toDouble();
271 int position = instruction.at(i: 3).toDouble();
272 int time = instruction.at(i: 4).toDouble();
273 //const QString segmentLengthString = instruction.at(5).toString();
274 //const QString direction = instruction.at(6).toString();
275 //double azimuth = instruction.at(7).toDouble();
276
277 QGeoRouteSegment segment;
278 segment.setDistance(segmentLength);
279
280 QGeoManeuver maneuver;
281 maneuver.setDirection(osrmInstructionDirection(instructionCode, trafficSide));
282 maneuver.setDistanceToNextInstruction(segmentLength);
283 maneuver.setInstructionText(osrmInstructionText(instructionCode, wayname));
284 maneuver.setPosition(path.at(i: position));
285 maneuver.setTimeToNextInstruction(time);
286
287 segment.setManeuver(maneuver);
288
289 if (firstPosition == -1)
290 segment.setPath(path.mid(pos: position));
291 else
292 segment.setPath(path.mid(pos: position, alength: firstPosition - position));
293
294 segmentPathLengthCount += segment.path().length();
295
296 segment.setTravelTime(time);
297
298 segment.setNextRouteSegment(firstSegment);
299
300 firstSegment = segment;
301 firstPosition = position;
302 }
303
304 route.setDistance(summary.value(QStringLiteral("total_distance")).toDouble());
305 route.setTravelTime(summary.value(QStringLiteral("total_time")).toDouble());
306 route.setFirstRouteSegment(firstSegment);
307 route.setPath(path);
308
309 return route;
310}
311
312class QGeoRouteParserOsrmV4Private : public QGeoRouteParserPrivate
313{
314 Q_DECLARE_PUBLIC(QGeoRouteParserOsrmV4)
315public:
316 QGeoRouteParserOsrmV4Private();
317 virtual ~QGeoRouteParserOsrmV4Private();
318
319 QGeoRouteReply::Error parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const override;
320 QUrl requestUrl(const QGeoRouteRequest &request, const QString &prefix) const override;
321};
322
323QGeoRouteParserOsrmV4Private::QGeoRouteParserOsrmV4Private() : QGeoRouteParserPrivate()
324{
325}
326
327QGeoRouteParserOsrmV4Private::~QGeoRouteParserOsrmV4Private()
328{
329}
330
331QGeoRouteReply::Error QGeoRouteParserOsrmV4Private::parseReply(QList<QGeoRoute> &routes, QString &errorString, const QByteArray &reply) const
332{
333 // OSRM v4 specs: https://github.com/Project-OSRM/osrm-backend/wiki/Server-API---v4,-old
334 QJsonDocument document = QJsonDocument::fromJson(json: reply);
335
336 if (document.isObject()) {
337 QJsonObject object = document.object();
338
339 //double version = object.value(QStringLiteral("version")).toDouble();
340 int status = object.value(QStringLiteral("status")).toDouble();
341 QString statusMessage = object.value(QStringLiteral("status_message")).toString();
342
343 // status code 0 or 200 are case of success
344 // status code is 207 if no route was found
345 // an error occurred when trying to find a route
346 if (0 != status && 200 != status) {
347 errorString = statusMessage;
348 return QGeoRouteReply::UnknownError;
349 }
350
351 QJsonObject routeSummary = object.value(QStringLiteral("route_summary")).toObject();
352
353 QByteArray routeGeometry =
354 object.value(QStringLiteral("route_geometry")).toString().toLatin1();
355
356 QJsonArray routeInstructions = object.value(QStringLiteral("route_instructions")).toArray();
357
358 QGeoRoute route = constructRoute(geometry: routeGeometry, instructions: routeInstructions, summary: routeSummary, trafficSide);
359
360 routes.append(t: route);
361
362 QJsonArray alternativeSummaries =
363 object.value(QStringLiteral("alternative_summaries")).toArray();
364 QJsonArray alternativeGeometries =
365 object.value(QStringLiteral("alternative_geometries")).toArray();
366 QJsonArray alternativeInstructions =
367 object.value(QStringLiteral("alternative_instructions")).toArray();
368
369 if (alternativeSummaries.count() == alternativeGeometries.count() &&
370 alternativeSummaries.count() == alternativeInstructions.count()) {
371 for (int i = 0; i < alternativeSummaries.count(); ++i) {
372 route = constructRoute(geometry: alternativeGeometries.at(i).toString().toLatin1(),
373 instructions: alternativeInstructions.at(i).toArray(),
374 summary: alternativeSummaries.at(i).toObject(),
375 trafficSide);
376 //routes.append(route);
377 }
378 }
379
380 return QGeoRouteReply::NoError;
381 } else {
382 errorString = QStringLiteral("Couldn't parse json.");
383 return QGeoRouteReply::ParseError;
384 }
385}
386
387QUrl QGeoRouteParserOsrmV4Private::requestUrl(const QGeoRouteRequest &request, const QString &prefix) const
388{
389 QUrl url(prefix);
390 QUrlQuery query;
391
392 query.addQueryItem(QStringLiteral("instructions"), QStringLiteral("true"));
393
394 foreach (const QGeoCoordinate &c, request.waypoints()) {
395 query.addQueryItem(QStringLiteral("loc"), value: QString::number(c.latitude()) + QLatin1Char(',') +
396 QString::number(c.longitude()));
397 }
398
399 url.setQuery(query);
400 return url;
401}
402
403QGeoRouteParserOsrmV4::QGeoRouteParserOsrmV4(QObject *parent) : QGeoRouteParser(*new QGeoRouteParserOsrmV4Private(), parent)
404{
405}
406
407QGeoRouteParserOsrmV4::~QGeoRouteParserOsrmV4()
408{
409}
410
411QT_END_NAMESPACE
412

source code of qtlocation/src/location/maps/qgeorouteparserosrmv4.cpp