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 | |
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(t: coord); |
78 | } |
79 | |
80 | parsingLatitude = !parsingLatitude; |
81 | |
82 | value = 0; |
83 | shift = 0; |
84 | } |
85 | |
86 | return path; |
87 | } |
88 | |
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 | |
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(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 | |
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(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 | |
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 | |
323 | QGeoRouteParserOsrmV4Private::QGeoRouteParserOsrmV4Private() : QGeoRouteParserPrivate() |
324 | { |
325 | } |
326 | |
327 | QGeoRouteParserOsrmV4Private::~QGeoRouteParserOsrmV4Private() |
328 | { |
329 | } |
330 | |
331 | 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(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 | |
387 | 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" ), value: QString::number(c.latitude()) + QLatin1Char(',') + |
396 | QString::number(c.longitude())); |
397 | } |
398 | |
399 | url.setQuery(query); |
400 | return url; |
401 | } |
402 | |
403 | QGeoRouteParserOsrmV4::QGeoRouteParserOsrmV4(QObject *parent) : QGeoRouteParser(*new QGeoRouteParserOsrmV4Private(), parent) |
404 | { |
405 | } |
406 | |
407 | QGeoRouteParserOsrmV4::~QGeoRouteParserOsrmV4() |
408 | { |
409 | } |
410 | |
411 | QT_END_NAMESPACE |
412 | |