File indexing completed on 2024-04-28 03:50:35

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2012 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "OSRMRunner.h"
0007 
0008 #include "MarbleDebug.h"
0009 #include "GeoDataDocument.h"
0010 #include "GeoDataPlacemark.h"
0011 #include "GeoDataExtendedData.h"
0012 #include "GeoDataData.h"
0013 #include "GeoDataLineString.h"
0014 #include "routing/Maneuver.h"
0015 #include "routing/RouteRequest.h"
0016 #include "HttpDownloadManager.h"
0017 
0018 #include <QVector>
0019 #include <QUrl>
0020 #include <QTime>
0021 #include <QTimer>
0022 #include <QJsonDocument>
0023 #include <QJsonArray>
0024 #include <QJsonObject>
0025 
0026 namespace Marble
0027 {
0028 
0029 OSRMRunner::OSRMRunner( QObject *parent ) :
0030     RoutingRunner( parent ),
0031     m_networkAccessManager()
0032 {
0033     connect( &m_networkAccessManager, SIGNAL(finished(QNetworkReply*)),
0034              this, SLOT(retrieveData(QNetworkReply*)) );
0035 }
0036 
0037 OSRMRunner::~OSRMRunner()
0038 {
0039     // nothing to do
0040 }
0041 
0042 void OSRMRunner::retrieveRoute( const RouteRequest *route )
0043 {
0044     if ( route->size() < 2 ) {
0045         return;
0046     }
0047 
0048     QString url = "http://router.project-osrm.org/route/v1/driving/";
0049     GeoDataCoordinates::Unit const degree = GeoDataCoordinates::Degree;
0050     for ( int i=0; i<route->size(); ++i ) {
0051         GeoDataCoordinates const coordinates = route->at( i );
0052         url += QString::number(coordinates.longitude(degree), 'f', 6);
0053         url += ',';
0054         url += QString::number(coordinates.latitude(degree), 'f', 6);
0055         if (i+1<route->size()) {
0056             url += ';';
0057         }
0058     }
0059 
0060     url += QStringLiteral("?alternatives=false&overview=full&geometries=polyline6");
0061 
0062     m_request = QNetworkRequest( QUrl( url ) );
0063     m_request.setRawHeader( "User-Agent", HttpDownloadManager::userAgent( "Browser", "OSRMRunner" ) );
0064 
0065     QEventLoop eventLoop;
0066 
0067     QTimer timer;
0068     timer.setSingleShot( true );
0069     timer.setInterval( 15000 );
0070 
0071     connect( &timer, SIGNAL(timeout()),
0072              &eventLoop, SLOT(quit()));
0073     connect( this, SIGNAL(routeCalculated(GeoDataDocument*)),
0074              &eventLoop, SLOT(quit()) );
0075 
0076     // @todo FIXME Must currently be done in the main thread, see bug 257376
0077     QTimer::singleShot( 0, this, SLOT(get()) );
0078     timer.start();
0079 
0080     eventLoop.exec();
0081 }
0082 
0083 void OSRMRunner::retrieveData( QNetworkReply *reply )
0084 {
0085     if ( reply->isFinished() ) {
0086         QByteArray data = reply->readAll();
0087         reply->deleteLater();
0088         GeoDataDocument* document = parse( data );
0089 
0090         if ( !document ) {
0091             mDebug() << "Failed to parse the downloaded route data" << data;
0092         }
0093 
0094         emit routeCalculated( document );
0095     }
0096 }
0097 
0098 void OSRMRunner::handleError( QNetworkReply::NetworkError error )
0099 {
0100     mDebug() << " Error when retrieving OSRM route: " << error;
0101 }
0102 
0103 void OSRMRunner::get()
0104 {
0105     QNetworkReply *reply = m_networkAccessManager.get( m_request );
0106     connect( reply, SIGNAL(error(QNetworkReply::NetworkError)),
0107              this, SLOT(handleError(QNetworkReply::NetworkError)), Qt::DirectConnection );
0108 }
0109 
0110 void OSRMRunner::append(QString *input, const QString &key, const QString &value)
0111 {
0112     *input += QLatin1Char('&') + key + QLatin1Char('=') + value;
0113 }
0114 
0115 GeoDataLineString *OSRMRunner::decodePolyline( const QString &geometry )
0116 {
0117     // See https://developers.google.com/maps/documentation/utilities/polylinealgorithm
0118     GeoDataLineString* lineString = new GeoDataLineString;
0119     int coordinates[2] = { 0, 0 };
0120     int const length = geometry.length();
0121     for( int i=0; i<length; /* increment happens below */ ) {
0122         for ( int j=0; j<2; ++j ) { // lat and lon
0123             int block( 0 ), shift( 0 ), result( 0 );
0124             do {
0125                 block = geometry.at( i++ /* increment for outer loop */ ).toLatin1() - 63;
0126                 result |= ( block & 0x1F ) << shift;
0127                 shift += 5;
0128             } while ( block >= 0x20 );
0129             coordinates[j] += ( ( result & 1 ) != 0 ? ~( result >> 1 ) : ( result >> 1 ) );
0130         }
0131         lineString->append( GeoDataCoordinates( double( coordinates[1] ) / 1E6,
0132                                                 double( coordinates[0] ) / 1E6,
0133                                                 0.0, GeoDataCoordinates::Degree ) );
0134     }
0135     return lineString;
0136 }
0137 
0138 RoutingInstruction::TurnType OSRMRunner::parseTurnType( const QString &instruction )
0139 {   
0140     if (instruction == QLatin1String("1")) {
0141         return RoutingInstruction::Straight;
0142     } else if (instruction == QLatin1String("2")) {
0143         return RoutingInstruction::SlightRight;
0144     } else if (instruction == QLatin1String("3")) {
0145         return RoutingInstruction::Right;
0146     } else if (instruction == QLatin1String("4")) {
0147         return RoutingInstruction::SharpRight;
0148     } else if (instruction == QLatin1String("5")) {
0149         return RoutingInstruction::TurnAround;
0150     } else if (instruction == QLatin1String("6")) {
0151         return RoutingInstruction::SharpLeft;
0152     } else if (instruction == QLatin1String("7")) {
0153         return RoutingInstruction::Left;
0154     } else if (instruction == QLatin1String("8")) {
0155         return RoutingInstruction::SlightLeft;
0156     } else if (instruction == QLatin1String("10")) {
0157         return RoutingInstruction::Continue;
0158     } else if (instruction.startsWith(QLatin1String("11-"))) {
0159         int const exit = instruction.mid( 3 ).toInt();
0160         switch ( exit ) {
0161         case 1: return RoutingInstruction::RoundaboutFirstExit;
0162         case 2: return RoutingInstruction::RoundaboutSecondExit;
0163         case 3: return RoutingInstruction::RoundaboutThirdExit;
0164         default: return RoutingInstruction::RoundaboutExit;
0165         }
0166     } else if (instruction == QLatin1String("12")) {
0167         return RoutingInstruction::RoundaboutExit;
0168     }
0169 
0170     // ignoring ReachViaPoint = 9;
0171     // ignoring StayOnRoundAbout = 13;
0172     // ignoring StartAtEndOfStreet = 14;
0173     // ignoring ReachedYourDestination = 15;
0174 
0175     return RoutingInstruction::Unknown;
0176 }
0177 
0178 GeoDataDocument *OSRMRunner::parse( const QByteArray &input ) const
0179 {
0180     QJsonDocument jsonDoc = QJsonDocument::fromJson(input);
0181     QJsonObject data = jsonDoc.object();
0182 
0183     GeoDataDocument* result = nullptr;
0184     GeoDataLineString* routeWaypoints = nullptr;
0185     QJsonValue routeGeometryValue = data.value(QStringLiteral("routes"));
0186     if (routeGeometryValue.isArray()) {
0187         auto routes = routeGeometryValue.toArray();
0188         if (!routes.isEmpty()) {
0189             auto route = routes[0].toObject();
0190             auto routeGeometryValue = route.value(QStringLiteral("geometry"));
0191             if (routeGeometryValue.isString()) {
0192                 result = new GeoDataDocument();
0193                 result->setName(QStringLiteral("Open Source Routing Machine"));
0194                 GeoDataPlacemark* routePlacemark = new GeoDataPlacemark;
0195                 routePlacemark->setName(QStringLiteral("Route"));
0196                 routeWaypoints = decodePolyline(routeGeometryValue.toString());
0197                 routePlacemark->setGeometry( routeWaypoints );
0198 
0199                 auto time = QTime(0, 0, 0);
0200                 time = time.addSecs(qRound(route.value(QStringLiteral("duration")).toDouble()));
0201                 qreal length = routeWaypoints->length( EARTH_RADIUS );
0202                 const QString name = nameString( "OSRM", length, time );
0203                 const GeoDataExtendedData extendedData = routeData( length, time );
0204                 routePlacemark->setExtendedData( extendedData );
0205                 result->setName( name );
0206                 result->append( routePlacemark );
0207             }
0208         }
0209     }
0210 
0211     /*
0212     QJsonValue routeInstructionsValue = data.value(QStringLiteral("route_instructions"));
0213     if (result && routeWaypoints && routeInstructionsValue.isArray()) {
0214         bool first = true;
0215         GeoDataPlacemark* instruction = new GeoDataPlacemark;
0216         int lastWaypointIndex = 0;
0217 
0218         const QJsonArray routeInstructionsArray = routeInstructionsValue.toArray();
0219         for (int index = 0; index < routeInstructionsArray.size(); ++index) {
0220             QVariantList details = routeInstructionsArray[index].toVariant().toList();
0221             if ( details.size() > 7 ) {
0222                 QString const text = details.at( 0 ).toString();
0223                 QString const road = details.at( 1 ).toString();
0224                 int const waypointIndex = details.at( 3 ).toInt();
0225 
0226                 if ( waypointIndex < routeWaypoints->size() ) {
0227                     const bool isLastInstruction = (index+1 >= routeInstructionsArray.size());
0228                     if (!isLastInstruction) {
0229                         GeoDataLineString *lineString = new GeoDataLineString;
0230                         for ( int i=lastWaypointIndex; i<=waypointIndex; ++i ) {
0231                             lineString->append(routeWaypoints->at( i ) );
0232                         }
0233                         instruction->setGeometry( lineString );
0234                         result->append( instruction );
0235                         instruction = new GeoDataPlacemark;
0236                     }
0237                     lastWaypointIndex = waypointIndex;
0238                     GeoDataExtendedData extendedData;
0239                     GeoDataData turnTypeData;
0240                     turnTypeData.setName(QStringLiteral("turnType"));
0241                     RoutingInstruction::TurnType turnType = parseTurnType( text );
0242                     turnTypeData.setValue( turnType );
0243                     extendedData.addValue( turnTypeData );
0244                     if (!road.isEmpty()) {
0245                         GeoDataData roadName;
0246                         roadName.setName(QStringLiteral("roadName"));
0247                         roadName.setValue( road );
0248                         extendedData.addValue( roadName );
0249                     }
0250 
0251                     if ( first ) {
0252                         turnType = RoutingInstruction::Continue;
0253                         first = false;
0254                     }
0255 
0256                     if ( turnType == RoutingInstruction::Unknown ) {
0257                         instruction->setName( text );
0258                     } else {
0259                         instruction->setName( RoutingInstruction::generateRoadInstruction( turnType, road ) );
0260                     }
0261                     instruction->setExtendedData( extendedData );
0262 
0263                     if (isLastInstruction && lastWaypointIndex > 0 ) {
0264                         GeoDataLineString *lineString = new GeoDataLineString;
0265                         for ( int i=lastWaypointIndex; i<waypointIndex; ++i ) {
0266                             lineString->append(routeWaypoints->at( i ) );
0267                         }
0268                         instruction->setGeometry( lineString );
0269                         result->append( instruction );
0270                     }
0271                 }
0272             }
0273         }
0274     }
0275     */
0276 
0277     return result;
0278 }
0279 
0280 } // namespace Marble
0281 
0282 #include "moc_OSRMRunner.cpp"