| 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 | |