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 
48 QT_BEGIN_NAMESPACE
49 
parsePolyline(const QByteArray & data)50 static 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(coord);
78         }
79 
80         parsingLatitude = !parsingLatitude;
81 
82         value = 0;
83         shift = 0;
84     }
85 
86     return path;
87 }
88 
osrmInstructionDirection(const QString & instructionCode,QGeoRouteParser::TrafficSide trafficSide)89 static 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 
osrmInstructionText(const QString & instructionCode,const QString & wayname)133 static 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("Go straight.");
140         else
141             return QGeoRouteParserOsrmV4::tr("Go straight onto %1.").arg(wayname);
142     } else if (instructionCode == QLatin1String("2")) {
143         if (wayname.isEmpty())
144             return QGeoRouteParserOsrmV4::tr("Turn slightly right.");
145         else
146             return QGeoRouteParserOsrmV4::tr("Turn slightly right onto %1.").arg(wayname);
147     } else if (instructionCode == QLatin1String("3")) {
148         if (wayname.isEmpty())
149             return QGeoRouteParserOsrmV4::tr("Turn right.");
150         else
151             return QGeoRouteParserOsrmV4::tr("Turn right onto %1.").arg(wayname);
152     } else if (instructionCode == QLatin1String("4")) {
153         if (wayname.isEmpty())
154             return QGeoRouteParserOsrmV4::tr("Make a sharp right.");
155         else
156             return QGeoRouteParserOsrmV4::tr("Make a sharp right onto %1.").arg(wayname);
157     }
158     else if (instructionCode == QLatin1String("5")) {
159         return QGeoRouteParserOsrmV4::tr("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("Make a sharp left.");
163         else
164             return QGeoRouteParserOsrmV4::tr("Make a sharp left onto %1.").arg(wayname);
165     } else if (instructionCode == QLatin1String("7")) {
166         if (wayname.isEmpty())
167             return QGeoRouteParserOsrmV4::tr("Turn left.");
168         else
169             return QGeoRouteParserOsrmV4::tr("Turn left onto %1.").arg(wayname);
170     } else if (instructionCode == QLatin1String("8")) {
171         if (wayname.isEmpty())
172             return QGeoRouteParserOsrmV4::tr("Turn slightly left.");
173         else
174             return QGeoRouteParserOsrmV4::tr("Turn slightly left onto %1.").arg(wayname);
175     } else if (instructionCode == QLatin1String("9")) {
176         return QGeoRouteParserOsrmV4::tr("Reached waypoint.");
177     } else if (instructionCode == QLatin1String("10")) {
178         if (wayname.isEmpty())
179             return QGeoRouteParserOsrmV4::tr("Head on.");
180         else
181             return QGeoRouteParserOsrmV4::tr("Head onto %1.").arg(wayname);
182     } else if (instructionCode == QLatin1String("11")) {
183         return QGeoRouteParserOsrmV4::tr("Enter the roundabout.");
184     } else if (instructionCode == QLatin1String("11-1")) {
185         if (wayname.isEmpty())
186             return QGeoRouteParserOsrmV4::tr("At the roundabout take the first exit.");
187         else
188             return QGeoRouteParserOsrmV4::tr("At the roundabout take the first exit onto %1.").arg(wayname);
189     } else if (instructionCode == QLatin1String("11-2")) {
190         if (wayname.isEmpty())
191             return QGeoRouteParserOsrmV4::tr("At the roundabout take the second exit.");
192         else
193             return QGeoRouteParserOsrmV4::tr("At the roundabout take the second exit onto %1.").arg(wayname);
194     } else if (instructionCode == QLatin1String("11-3")) {
195         if (wayname.isEmpty())
196             return QGeoRouteParserOsrmV4::tr("At the roundabout take the third exit.");
197         else
198             return QGeoRouteParserOsrmV4::tr("At the roundabout take the third exit onto %1.").arg(wayname);
199     } else if (instructionCode == QLatin1String("11-4")) {
200         if (wayname.isEmpty())
201             return QGeoRouteParserOsrmV4::tr("At the roundabout take the fourth exit.");
202         else
203             return QGeoRouteParserOsrmV4::tr("At the roundabout take the fourth exit onto %1.").arg(wayname);
204     } else if (instructionCode == QLatin1String("11-5")) {
205         if (wayname.isEmpty())
206             return QGeoRouteParserOsrmV4::tr("At the roundabout take the fifth exit.");
207         else
208             return QGeoRouteParserOsrmV4::tr("At the roundabout take the fifth exit onto %1.").arg(wayname);
209     } else if (instructionCode == QLatin1String("11-6")) {
210         if (wayname.isEmpty())
211             return QGeoRouteParserOsrmV4::tr("At the roundabout take the sixth exit.");
212         else
213             return QGeoRouteParserOsrmV4::tr("At the roundabout take the sixth exit onto %1.").arg(wayname);
214     } else if (instructionCode == QLatin1String("11-7")) {
215         if (wayname.isEmpty())
216             return QGeoRouteParserOsrmV4::tr("At the roundabout take the seventh exit.");
217         else
218             return QGeoRouteParserOsrmV4::tr("At the roundabout take the seventh exit onto %1.").arg(wayname);
219     } else if (instructionCode == QLatin1String("11-8")) {
220         if (wayname.isEmpty())
221             return QGeoRouteParserOsrmV4::tr("At the roundabout take the eighth exit.");
222         else
223             return QGeoRouteParserOsrmV4::tr("At the roundabout take the eighth exit onto %1.").arg(wayname);
224     } else if (instructionCode == QLatin1String("11-9")) {
225         if (wayname.isEmpty())
226             return QGeoRouteParserOsrmV4::tr("At the roundabout take the ninth exit.");
227         else
228             return QGeoRouteParserOsrmV4::tr("At the roundabout take the ninth exit onto %1.").arg(wayname);
229     } else if (instructionCode == QLatin1String("12")) {
230         if (wayname.isEmpty())
231             return QGeoRouteParserOsrmV4::tr("Leave the roundabout.");
232         else
233             return QGeoRouteParserOsrmV4::tr("Leave the roundabout onto %1.").arg(wayname);
234     } else if (instructionCode == QLatin1String("13")) {
235         return QGeoRouteParserOsrmV4::tr("Stay on the roundabout.");
236     } else if (instructionCode == QLatin1String("14")) {
237         if (wayname.isEmpty())
238             return QGeoRouteParserOsrmV4::tr("Start at the end of the street.");
239         else
240             return QGeoRouteParserOsrmV4::tr("Start at the end of %1.").arg(wayname);
241     } else if (instructionCode == QLatin1String("15")) {
242         return QGeoRouteParserOsrmV4::tr("You have reached your destination.");
243     } else {
244         return QGeoRouteParserOsrmV4::tr("Don't know what to say for '%1'").arg(instructionCode);
245     }
246 }
247 
constructRoute(const QByteArray & geometry,const QJsonArray & instructions,const QJsonObject & summary,QGeoRouteParser::TrafficSide trafficSide)248 static 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(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("Instruction does not contain enough fields.");
265             continue;
266         }
267 
268         const QString instructionCode = instruction.at(0).toString();
269         const QString wayname = instruction.at(1).toString();
270         double segmentLength = instruction.at(2).toDouble();
271         int position = instruction.at(3).toDouble();
272         int time = instruction.at(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(position));
285         maneuver.setTimeToNextInstruction(time);
286 
287         segment.setManeuver(maneuver);
288 
289         if (firstPosition == -1)
290             segment.setPath(path.mid(position));
291         else
292             segment.setPath(path.mid(position, 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 
312 class QGeoRouteParserOsrmV4Private :  public QGeoRouteParserPrivate
313 {
314     Q_DECLARE_PUBLIC(QGeoRouteParserOsrmV4)
315 public:
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 
QGeoRouteParserOsrmV4Private()323 QGeoRouteParserOsrmV4Private::QGeoRouteParserOsrmV4Private() : QGeoRouteParserPrivate()
324 {
325 }
326 
~QGeoRouteParserOsrmV4Private()327 QGeoRouteParserOsrmV4Private::~QGeoRouteParserOsrmV4Private()
328 {
329 }
330 
parseReply(QList<QGeoRoute> & routes,QString & errorString,const QByteArray & reply) const331 QGeoRouteReply::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(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(routeGeometry, routeInstructions, routeSummary, trafficSide);
359 
360         routes.append(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(alternativeGeometries.at(i).toString().toLatin1(),
373                                        alternativeInstructions.at(i).toArray(),
374                                        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 
requestUrl(const QGeoRouteRequest & request,const QString & prefix) const387 QUrl 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"), QString::number(c.latitude()) + QLatin1Char(',') +
396                                                  QString::number(c.longitude()));
397     }
398 
399     url.setQuery(query);
400     return url;
401 }
402 
QGeoRouteParserOsrmV4(QObject * parent)403 QGeoRouteParserOsrmV4::QGeoRouteParserOsrmV4(QObject *parent) : QGeoRouteParser(*new QGeoRouteParserOsrmV4Private(), parent)
404 {
405 }
406 
~QGeoRouteParserOsrmV4()407 QGeoRouteParserOsrmV4::~QGeoRouteParserOsrmV4()
408 {
409 }
410 
411 QT_END_NAMESPACE
412