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"