File indexing completed on 2025-02-16 03:37:58
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2012 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "MapQuestRunner.h" 0007 0008 #include "MarbleDebug.h" 0009 #include "MarbleLocale.h" 0010 #include "GeoDataDocument.h" 0011 #include "GeoDataPlacemark.h" 0012 #include "GeoDataExtendedData.h" 0013 #include "GeoDataData.h" 0014 #include "GeoDataLineString.h" 0015 #include "HttpDownloadManager.h" 0016 #include "routing/Maneuver.h" 0017 #include "routing/RouteRequest.h" 0018 0019 #include <QString> 0020 #include <QUrl> 0021 #include <QTime> 0022 #include <QTimer> 0023 #include <QDomDocument> 0024 0025 #include <QUrlQuery> 0026 0027 namespace Marble 0028 { 0029 0030 MapQuestRunner::MapQuestRunner( QObject *parent ) : 0031 RoutingRunner( parent ), 0032 m_networkAccessManager(), 0033 m_request() 0034 { 0035 connect( &m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), 0036 this, SLOT(retrieveData(QNetworkReply*)) ); 0037 } 0038 0039 MapQuestRunner::~MapQuestRunner() 0040 { 0041 // nothing to do 0042 } 0043 0044 void MapQuestRunner::retrieveRoute( const RouteRequest *route ) 0045 { 0046 if ( route->size() < 2 ) { 0047 return; 0048 } 0049 0050 QHash<QString, QVariant> settings = route->routingProfile().pluginSettings()["mapquest"]; 0051 0052 if (settings.value(QStringLiteral("appKey")).toString().isEmpty()) { 0053 return; 0054 } 0055 0056 QString url = "http://open.mapquestapi.com/directions/v1/route?callback=renderAdvancedNarrative&outFormat=xml&narrativeType=text&shapeFormat=raw&generalize=0"; 0057 GeoDataCoordinates::Unit const degree = GeoDataCoordinates::Degree; 0058 append(&url, "from", QString::number(route->source().latitude(degree), 'f', 6) + QLatin1Char(',') + QString::number(route->source().longitude(degree), 'f', 6)); 0059 for ( int i=1; i<route->size(); ++i ) { 0060 append(&url, "to", QString::number(route->at(i).latitude(degree), 'f', 6) + QLatin1Char(',') + QString::number(route->at(i).longitude(degree), 'f', 6)); 0061 } 0062 0063 QString const unit = MarbleGlobal::getInstance()->locale()->measurementSystem() == MarbleLocale::MetricSystem ? "k" : "m"; 0064 append( &url, "units", unit ); 0065 0066 if (settings[QStringLiteral("noMotorways")].toInt()) { 0067 append( &url, "avoids", "Limited Access" ); 0068 } 0069 if (settings[QStringLiteral("noTollroads")].toInt()) { 0070 append( &url, "avoids", "Toll road" ); 0071 } 0072 if (settings[QStringLiteral("noFerries")].toInt()) { 0073 append( &url, "avoids", "Ferry" ); 0074 } 0075 0076 if (!settings[QStringLiteral("preference")].toString().isEmpty()) { 0077 append(&url, QStringLiteral("routeType"), settings[QStringLiteral("preference")].toString()); 0078 } 0079 0080 const QString ascendingSetting = settings[QStringLiteral("ascending")].toString(); 0081 const QString descendingSetting = settings[QStringLiteral("descending")].toString(); 0082 if (!ascendingSetting.isEmpty() && !descendingSetting.isEmpty()) { 0083 if (ascendingSetting == QLatin1String("AVOID_UP_HILL") && 0084 descendingSetting == QLatin1String("AVOID_DOWN_HILL")) { 0085 append(&url, QStringLiteral("roadGradeStrategy"), QStringLiteral("AVOID_ALL_HILLS")); 0086 } 0087 else if (ascendingSetting == QLatin1String("FAVOR_UP_HILL") && 0088 descendingSetting == QLatin1String("FAVOR_DOWN_HILL")) { 0089 append(&url, QStringLiteral("roadGradeStrategy"), QStringLiteral("FAVOR_ALL_HILLS")); 0090 } 0091 else if (ascendingSetting == QLatin1String("DEFAULT_STRATEGY") && 0092 descendingSetting == QLatin1String("DEFAULT_STRATEGY")) { 0093 append(&url, QStringLiteral("roadGradeStrategy"), QStringLiteral("DEFAULT_STRATEGY")); 0094 } 0095 else if (ascendingSetting == QLatin1String("DEFAULT_STRATEGY")) { 0096 append(&url, QStringLiteral("roadGradeStrategy"), descendingSetting); 0097 } 0098 else if (descendingSetting == QLatin1String("DEFAULT_STRATEGY")) { 0099 append(&url, QStringLiteral("roadGradeStrategy"), ascendingSetting); 0100 } 0101 else if (descendingSetting == QLatin1String("AVOID_DOWN_HILL")) { 0102 append(&url, QStringLiteral("roadGradeStrategy"), descendingSetting); 0103 } 0104 else if (ascendingSetting == QLatin1String("AVOID_UP_HILL")) { 0105 append(&url, QStringLiteral("roadGradeStrategy"), ascendingSetting); 0106 } 0107 } 0108 0109 QUrl qurl(url); 0110 // FIXME: verify that this works with special characters. 0111 QUrlQuery urlQuery; 0112 urlQuery.addQueryItem(QStringLiteral("key"), settings.value(QStringLiteral("appKey")).toByteArray()); 0113 qurl.setQuery(urlQuery); 0114 m_request.setUrl( qurl ); 0115 m_request.setRawHeader( "User-Agent", HttpDownloadManager::userAgent( "Browser", "MapQuestRunner" ) ); 0116 0117 QEventLoop eventLoop; 0118 0119 QTimer timer; 0120 timer.setSingleShot( true ); 0121 timer.setInterval( 15000 ); 0122 0123 connect( &timer, SIGNAL(timeout()), 0124 &eventLoop, SLOT(quit())); 0125 connect( this, SIGNAL(routeCalculated(GeoDataDocument*)), 0126 &eventLoop, SLOT(quit()) ); 0127 0128 // @todo FIXME Must currently be done in the main thread, see bug 257376 0129 QTimer::singleShot( 0, this, SLOT(get()) ); 0130 timer.start(); 0131 0132 eventLoop.exec(); 0133 } 0134 0135 void MapQuestRunner::get() 0136 { 0137 QNetworkReply *reply = m_networkAccessManager.get( m_request ); 0138 connect( reply, SIGNAL(error(QNetworkReply::NetworkError)), 0139 this, SLOT(handleError(QNetworkReply::NetworkError)), Qt::DirectConnection ); 0140 } 0141 0142 void MapQuestRunner::retrieveData( QNetworkReply *reply ) 0143 { 0144 if ( reply->isFinished() ) { 0145 QByteArray data = reply->readAll(); 0146 reply->deleteLater(); 0147 //mDebug() << "Download completed: " << data; 0148 GeoDataDocument* document = parse( data ); 0149 0150 if ( !document ) { 0151 mDebug() << "Failed to parse the downloaded route data" << data; 0152 } 0153 0154 emit routeCalculated( document ); 0155 } 0156 } 0157 0158 void MapQuestRunner::handleError( QNetworkReply::NetworkError error ) 0159 { 0160 mDebug() << " Error when retrieving mapquest.org route: " << error; 0161 } 0162 0163 void MapQuestRunner::append(QString *input, const QString &key, const QString &value) 0164 { 0165 *input += QLatin1Char('&') + key + QLatin1Char('=') + value; 0166 } 0167 0168 int MapQuestRunner::maneuverType( int mapQuestId ) 0169 { 0170 /** @todo FIXME: review 10, 11 */ 0171 switch( mapQuestId ) { 0172 case 0: return Maneuver::Straight ; // straight 0173 case 1: return Maneuver::SlightRight ; // slight right 0174 case 2: return Maneuver::Right ; // right 0175 case 3: return Maneuver::SharpRight ; // sharp right 0176 case 4: return Maneuver::TurnAround ; // reverse 0177 case 5: return Maneuver::SharpLeft ; // sharp left 0178 case 6: return Maneuver::Left ; // left 0179 case 7: return Maneuver::SlightLeft ; // slight left 0180 case 8: return Maneuver::TurnAround ; // right u-turn 0181 case 9: return Maneuver::TurnAround ; // left u-turn 0182 case 10: return Maneuver::Merge ; // right merge 0183 case 11: return Maneuver::Merge ; // left merge 0184 case 12: return Maneuver::Merge ; // right on ramp 0185 case 13: return Maneuver::Merge ; // left on ramp 0186 case 14: return Maneuver::ExitRight ; // right off ramp 0187 case 15: return Maneuver::ExitLeft ; // left off ramp 0188 case 16: return Maneuver::Right ; // right fork 0189 case 17: return Maneuver::Left ; // left fork 0190 case 18: return Maneuver::Continue ; // straight fork 0191 } 0192 0193 return Maneuver::Unknown; 0194 } 0195 0196 GeoDataDocument* MapQuestRunner::parse( const QByteArray &content ) const 0197 { 0198 QDomDocument xml; 0199 if ( !xml.setContent( content ) ) { 0200 mDebug() << "Cannot parse xml file with routing instructions."; 0201 return nullptr; 0202 } 0203 0204 // mDebug() << xml.toString(2); 0205 QDomElement root = xml.documentElement(); 0206 0207 GeoDataDocument* result = new GeoDataDocument(); 0208 result->setName(QStringLiteral("MapQuest")); 0209 GeoDataPlacemark* routePlacemark = new GeoDataPlacemark; 0210 routePlacemark->setName(QStringLiteral("Route")); 0211 0212 GeoDataLineString* routeWaypoints = new GeoDataLineString; 0213 QDomNodeList shapePoints = root.elementsByTagName(QStringLiteral("shapePoints")); 0214 if ( shapePoints.size() == 1 ) { 0215 QDomNodeList geometry = shapePoints.at(0).toElement().elementsByTagName(QStringLiteral("latLng")); 0216 for ( int i=0; i<geometry.size(); ++i ) { 0217 double const lat = geometry.item(i).namedItem(QStringLiteral("lat")).toElement().text().toDouble(); 0218 double const lon = geometry.item(i).namedItem(QStringLiteral("lng")).toElement().text().toDouble(); 0219 GeoDataCoordinates const position( lon, lat, 0.0, GeoDataCoordinates::Degree ); 0220 routeWaypoints->append( position ); 0221 } 0222 } 0223 routePlacemark->setGeometry( routeWaypoints ); 0224 0225 QTime time; 0226 time = time.addSecs(root.elementsByTagName(QStringLiteral("time")).at(0).toElement().text().toInt()); 0227 qreal length = routeWaypoints->length( EARTH_RADIUS ); 0228 const QString name = nameString( "MQ", length, time ); 0229 const GeoDataExtendedData data = routeData( length, time ); 0230 routePlacemark->setExtendedData( data ); 0231 result->setName( name ); 0232 result->append( routePlacemark ); 0233 0234 QMap<int,int> mapping; 0235 QDomNodeList maneuvers = root.elementsByTagName(QStringLiteral("maneuverIndexes")); 0236 if ( maneuvers.size() == 1 ) { 0237 maneuvers = maneuvers.at( 0 ).childNodes(); 0238 for ( int i=0; i<maneuvers.size(); ++i ) { 0239 mapping[i] = maneuvers.at( i ).toElement().text().toInt(); 0240 if ( mapping[i] == routeWaypoints->size() ) { 0241 --mapping[i]; 0242 } 0243 } 0244 } 0245 0246 QDomNodeList instructions = root.elementsByTagName(QStringLiteral("maneuver")); 0247 unsigned int const lastInstruction = qMax<int>( 0, instructions.length()-1 ); // ignore the last 'Welcome to xy' instruction 0248 for ( unsigned int i = 0; i < lastInstruction; ++i ) { 0249 QDomElement node = instructions.item( i ).toElement(); 0250 0251 QDomNodeList maneuver = node.elementsByTagName(QStringLiteral("turnType")); 0252 QDomNodeList textNodes = node.elementsByTagName(QStringLiteral("narrative")); 0253 QDomNodeList points = node.elementsByTagName(QStringLiteral("startPoint")); 0254 QDomNodeList streets = node.elementsByTagName(QStringLiteral("streets")); 0255 0256 Q_ASSERT( mapping.contains( i ) ); 0257 if ( textNodes.size() == 1 && maneuver.size() == 1 && points.size() == 1 && mapping.contains( i ) ) { 0258 GeoDataPlacemark* instruction = new GeoDataPlacemark; 0259 instruction->setName( textNodes.at( 0 ).toElement().text() ); 0260 0261 GeoDataExtendedData extendedData; 0262 GeoDataData turnType; 0263 turnType.setName(QStringLiteral("turnType")); 0264 turnType.setValue( maneuverType( maneuver.at( 0 ).toElement().text().toInt() ) ); 0265 extendedData.addValue( turnType ); 0266 if ( streets.size() == 1 ) { 0267 GeoDataData roadName; 0268 roadName.setName(QStringLiteral("roadName")); 0269 roadName.setValue( streets.at( 0 ).toElement().text() ); 0270 extendedData.addValue( roadName ); 0271 } 0272 instruction->setExtendedData( extendedData ); 0273 0274 int const start = mapping[i]; 0275 int const end = mapping.contains(i+1) ? mapping[i+1] : routeWaypoints->size()-1; 0276 if ( start >= 0 && start < routeWaypoints->size() && end < routeWaypoints->size() ) { 0277 instruction->setName( textNodes.item( 0 ).toElement().text() ); 0278 GeoDataLineString *lineString = new GeoDataLineString; 0279 for ( int j=start; j<=end; ++j ) { 0280 *lineString << GeoDataCoordinates( routeWaypoints->at( j ).longitude(), routeWaypoints->at( j ).latitude() ); 0281 } 0282 0283 if ( !lineString->isEmpty() ) { 0284 instruction->setGeometry( lineString ); 0285 result->append( instruction ); 0286 } 0287 } 0288 } 0289 } 0290 0291 if ( routeWaypoints->size() < 1 ) { 0292 delete result; 0293 result = nullptr; 0294 } 0295 0296 return result; 0297 } 0298 0299 0300 } // namespace Marble 0301 0302 #include "moc_MapQuestRunner.cpp"