File indexing completed on 2025-02-09 04:17:39
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "OpenRouteServiceRunner.h" 0007 0008 #include "MarbleDebug.h" 0009 #include "GeoDataDocument.h" 0010 #include "GeoDataPlacemark.h" 0011 #include "GeoDataData.h" 0012 #include "GeoDataExtendedData.h" 0013 #include "GeoDataLineString.h" 0014 #include "routing/RouteRequest.h" 0015 0016 #include <QUrl> 0017 #include <QUrlQuery> 0018 #include <QTime> 0019 #include <QTimer> 0020 #include <QDomDocument> 0021 0022 namespace Marble 0023 { 0024 0025 OpenRouteServiceRunner::OpenRouteServiceRunner( QObject *parent ) : 0026 RoutingRunner( parent ), 0027 m_networkAccessManager() 0028 { 0029 connect( &m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), 0030 this, SLOT(retrieveData(QNetworkReply*))); 0031 } 0032 0033 void OpenRouteServiceRunner::retrieveRoute( const RouteRequest *route ) 0034 { 0035 if ( route->size() < 2 ) { 0036 return; 0037 } 0038 0039 GeoDataCoordinates source = route->source(); 0040 GeoDataCoordinates destination = route->destination(); 0041 QHash<QString, QVariant> settings = route->routingProfile().pluginSettings()["openrouteservice"]; 0042 0043 QUrlQuery queries; 0044 queries.addQueryItem("api_key", "ee0b8233adff52ce9fd6afc2a2859a28"); 0045 0046 QString unit = "KM"; 0047 QString preference = "Fastest"; 0048 if (settings.contains(QStringLiteral("preference"))) { 0049 preference = settings[QStringLiteral("preference")].toString(); 0050 } 0051 if (preference == QLatin1String("Pedestrian")) { 0052 unit = QStringLiteral("M"); 0053 } 0054 0055 queries.addQueryItem("start", formatCoordinates(source)); 0056 QStringList via; 0057 for (int i = 1; i < route->size()-1; ++i) { 0058 via << formatCoordinates(route->at(i)); 0059 } 0060 queries.addQueryItem("via", via.join(' ')); 0061 queries.addQueryItem("end", formatCoordinates(destination)); 0062 0063 queries.addQueryItem("distunit", unit); 0064 if (preference == "Fastest" || preference == "Shortest" || preference == "Recommended") { 0065 queries.addQueryItem("routepref", "Car"); 0066 queries.addQueryItem("weighting", preference); 0067 } else { 0068 queries.addQueryItem("routepref", preference); 0069 queries.addQueryItem("weighting", "Recommended"); 0070 } 0071 0072 QString const motorways = settings.value("noMotorways").toInt() == 0 ? "false" : "true"; 0073 queries.addQueryItem("noMotorways", motorways); 0074 QString const tollways = settings.value("noTollways").toInt() == 0 ? "false" : "true"; 0075 queries.addQueryItem("noTollways", tollways); 0076 queries.addQueryItem("noUnpavedroads", "false"); 0077 queries.addQueryItem("noSteps", "false"); 0078 QString const ferries = settings.value("noFerries").toInt() == 0 ? "false" : "true"; 0079 queries.addQueryItem("noFerries", ferries); 0080 queries.addQueryItem("instructions", "true"); 0081 queries.addQueryItem("lang", "en"); 0082 0083 QUrl url = QUrl( "http://openls.geog.uni-heidelberg.de/route" ); 0084 // QUrlQuery strips empty value pairs, but OpenRouteService does not work without 0085 QString const trailer = route->size() == 2 ? "&via=" : QString(); 0086 url.setQuery(queries.toString() + trailer); 0087 0088 m_request = QNetworkRequest( url ); 0089 0090 QEventLoop eventLoop; 0091 QTimer timer; 0092 timer.setSingleShot( true ); 0093 timer.setInterval( 15000 ); 0094 0095 connect( &timer, SIGNAL(timeout()), 0096 &eventLoop, SLOT(quit())); 0097 connect( this, SIGNAL(routeCalculated(GeoDataDocument*)), 0098 &eventLoop, SLOT(quit())); 0099 0100 // @todo FIXME Must currently be done in the main thread, see bug 257376 0101 QTimer::singleShot( 0, this, SLOT(get())); 0102 timer.start(); 0103 0104 eventLoop.exec(); 0105 } 0106 0107 void OpenRouteServiceRunner::get() 0108 { 0109 QNetworkReply *reply = m_networkAccessManager.get(m_request); 0110 connect( reply, SIGNAL(error(QNetworkReply::NetworkError)), 0111 this, SLOT(handleError(QNetworkReply::NetworkError)), Qt::DirectConnection); 0112 } 0113 0114 QString OpenRouteServiceRunner::formatCoordinates(const GeoDataCoordinates &coordinates) 0115 { 0116 return QStringLiteral("%1,%2") 0117 .arg(coordinates.longitude(GeoDataCoordinates::Degree ), 0, 'f', 8) 0118 .arg(coordinates.latitude(GeoDataCoordinates::Degree ), 0, 'f', 8); 0119 } 0120 0121 void OpenRouteServiceRunner::retrieveData( QNetworkReply *reply ) 0122 { 0123 if ( reply->isFinished() ) { 0124 QByteArray data = reply->readAll(); 0125 reply->deleteLater(); 0126 //mDebug() << "Download completed: " << data; 0127 GeoDataDocument* document = parse( data ); 0128 0129 if ( !document ) { 0130 mDebug() << "Failed to parse the downloaded route data" << data; 0131 } 0132 0133 emit routeCalculated( document ); 0134 } 0135 } 0136 0137 void OpenRouteServiceRunner::handleError( QNetworkReply::NetworkError error ) 0138 { 0139 mDebug() << " Error when retrieving openrouteservice.org route: " << error; 0140 } 0141 0142 GeoDataDocument* OpenRouteServiceRunner::parse( const QByteArray &content ) const 0143 { 0144 QDomDocument xml; 0145 if ( !xml.setContent( content ) ) { 0146 mDebug() << "Cannot parse xml file with routing instructions."; 0147 return nullptr; 0148 } 0149 0150 QDomElement root = xml.documentElement(); 0151 0152 GeoDataDocument* result = new GeoDataDocument(); 0153 result->setName(QStringLiteral("OpenRouteService")); 0154 0155 QDomNodeList errors = root.elementsByTagName(QStringLiteral("xls:Error")); 0156 if ( errors.size() > 0 ) { 0157 return nullptr; 0158 // Returning early because fallback routing providers are used now 0159 // The code below can be used to parse OpenGis errors reported by ORS 0160 // and may be useful in the future 0161 0162 for (int i=0 ; i < errors.length(); ++i ) { 0163 QDomNode node = errors.item( i ); 0164 QString errorMessage = node.attributes().namedItem(QStringLiteral("message")).nodeValue(); 0165 QRegExp regexp = QRegExp( "^(.*) Please Check your Position: (-?[0-9]+.[0-9]+) (-?[0-9]+.[0-9]+) !" ); 0166 if ( regexp.indexIn( errorMessage ) == 0 ) { 0167 if ( regexp.capturedTexts().size() == 4 ) { 0168 GeoDataPlacemark* placemark = new GeoDataPlacemark; 0169 placemark->setName( regexp.capturedTexts().at( 1 ) ); 0170 GeoDataCoordinates position; 0171 position.setLongitude( regexp.capturedTexts().at( 2 ).toDouble(), GeoDataCoordinates::Degree ); 0172 position.setLatitude( regexp.capturedTexts().at( 3 ).toDouble(), GeoDataCoordinates::Degree ); 0173 placemark->setCoordinate( position ); 0174 result->append( placemark ); 0175 } 0176 } else { 0177 mDebug() << "Error message " << errorMessage << " not parsable."; 0178 /** @todo: How to handle this now with plugins? */ 0179 // QString message = tr( "Sorry, a problem occurred when calculating the route. Try adjusting start and destination points." ); 0180 // QPointer<QMessageBox> messageBox = new QMessageBox( QMessageBox::Warning, "Route Error", message ); 0181 // messageBox->setDetailedText( errorMessage ); 0182 // messageBox->exec(); 0183 // delete messageBox; 0184 } 0185 } 0186 } 0187 0188 GeoDataPlacemark* routePlacemark = new GeoDataPlacemark; 0189 routePlacemark->setName(QStringLiteral("Route")); 0190 QTime time; 0191 QDomNodeList summary = root.elementsByTagName(QStringLiteral("xls:RouteSummary")); 0192 if ( summary.size() > 0 ) { 0193 QDomNodeList timeNodeList = summary.item(0).toElement().elementsByTagName(QStringLiteral("xls:TotalTime")); 0194 if ( timeNodeList.size() == 1 ) { 0195 QRegExp regexp = QRegExp( "^P(?:(\\d+)D)?T(?:(\\d+)H)?(?:(\\d+)M)?(\\d+)S" ); 0196 if ( regexp.indexIn( timeNodeList.item( 0 ).toElement().text() ) == 0 ) { 0197 QStringList matches = regexp.capturedTexts(); 0198 unsigned int hours( 0 ), minutes( 0 ), seconds( 0 ); 0199 switch ( matches.size() ) { 0200 case 5: 0201 // days = regexp.cap( matches.size() - 4 ).toInt(); 0202 Q_FALLTHROUGH(); 0203 case 4: 0204 hours = regexp.cap( matches.size() - 3 ).toInt(); 0205 Q_FALLTHROUGH(); 0206 case 3: 0207 minutes = regexp.cap( matches.size() - 2 ).toInt(); 0208 Q_FALLTHROUGH(); 0209 case 2: 0210 seconds = regexp.cap( matches.size() - 1 ).toInt(); 0211 break; 0212 default: 0213 mDebug() << "Unable to parse time string " << timeNodeList.item( 0 ).toElement().text(); 0214 } 0215 0216 time = QTime( hours, minutes, seconds, 0 ); 0217 } 0218 } 0219 } 0220 0221 GeoDataLineString* routeWaypoints = new GeoDataLineString; 0222 QDomNodeList geometry = root.elementsByTagName(QStringLiteral("xls:RouteGeometry")); 0223 if ( geometry.size() > 0 ) { 0224 QDomNodeList waypoints = geometry.item( 0 ).toElement().elementsByTagName( "gml:pos" ); 0225 for (int i=0 ; i < waypoints.length(); ++i ) { 0226 QDomNode node = waypoints.item( i ); 0227 const QStringList content = node.toElement().text().split(QLatin1Char(' ')); 0228 if ( content.length() == 2 ) { 0229 GeoDataCoordinates position; 0230 position.setLongitude( content.at( 0 ).toDouble(), GeoDataCoordinates::Degree ); 0231 position.setLatitude( content.at( 1 ).toDouble(), GeoDataCoordinates::Degree ); 0232 routeWaypoints->append( position ); 0233 } 0234 } 0235 } 0236 routePlacemark->setGeometry( routeWaypoints ); 0237 0238 qreal length = routeWaypoints->length( EARTH_RADIUS ); 0239 const QString name = nameString( "ORS", length, time ); 0240 const GeoDataExtendedData data = routeData( length, time ); 0241 routePlacemark->setExtendedData( data ); 0242 result->setName( name ); 0243 0244 result->append( routePlacemark ); 0245 0246 QDomNodeList instructionList = root.elementsByTagName(QStringLiteral("xls:RouteInstructionsList")); 0247 if ( instructionList.size() > 0 ) { 0248 QDomNodeList instructions = instructionList.item(0).toElement().elementsByTagName(QStringLiteral("xls:RouteInstruction")); 0249 for (int i=0 ; i < instructions.length(); ++i ) { 0250 QDomElement node = instructions.item( i ).toElement(); 0251 0252 QDomNodeList textNodes = node.elementsByTagName(QStringLiteral("xls:Instruction")); 0253 QDomNodeList positions = node.elementsByTagName(QStringLiteral("gml:pos")); 0254 0255 if ( textNodes.size() > 0 && positions.size() > 0 ) { 0256 const QStringList content = positions.at(0).toElement().text().split(QLatin1Char(' ')); 0257 if ( content.length() == 2 ) { 0258 GeoDataLineString *lineString = new GeoDataLineString; 0259 0260 for( int i = 0; i < positions.count(); ++i ) { 0261 const QStringList pointList = positions.at(i).toElement().text().split(QLatin1Char(' ')); 0262 GeoDataCoordinates position; 0263 position.setLongitude( pointList.at( 0 ).toDouble(), GeoDataCoordinates::Degree ); 0264 position.setLatitude( pointList.at( 1 ).toDouble(), GeoDataCoordinates::Degree ); 0265 lineString->append( position ); 0266 } 0267 0268 GeoDataPlacemark* instruction = new GeoDataPlacemark; 0269 0270 QString const text = textNodes.item( 0 ).toElement().text().remove(QRegExp("<[^>]*>")); 0271 GeoDataExtendedData extendedData; 0272 GeoDataData turnTypeData; 0273 turnTypeData.setName(QStringLiteral("turnType")); 0274 QString road; 0275 RoutingInstruction::TurnType turnType = parseTurnType( text, &road ); 0276 turnTypeData.setValue( turnType ); 0277 extendedData.addValue( turnTypeData ); 0278 if ( !road.isEmpty() ) { 0279 GeoDataData roadName; 0280 roadName.setName(QStringLiteral("roadName")); 0281 roadName.setValue( road ); 0282 extendedData.addValue( roadName ); 0283 } 0284 0285 QString const instructionText = turnType == RoutingInstruction::Unknown ? text : RoutingInstruction::generateRoadInstruction( turnType, road ); 0286 instruction->setName( instructionText ); 0287 instruction->setExtendedData( extendedData ); 0288 instruction->setGeometry( lineString ); 0289 result->append( instruction ); 0290 } 0291 } 0292 } 0293 } 0294 0295 return result; 0296 } 0297 0298 RoutingInstruction::TurnType OpenRouteServiceRunner::parseTurnType( const QString &text, QString *road ) 0299 { 0300 QRegExp syntax( "^(Go|Drive|Turn) (half left|left|sharp left|straight forward|half right|right|sharp right)( on )?(.*)?$", Qt::CaseSensitive, QRegExp::RegExp2 ); 0301 QString instruction; 0302 if ( syntax.indexIn( text ) == 0 ) { 0303 if ( syntax.captureCount() > 1 ) { 0304 instruction = syntax.cap( 2 ); 0305 if ( syntax.captureCount() == 4 ) { 0306 *road = syntax.cap( 4 ).remove(QLatin1String( " - Arrived at destination!")); 0307 } 0308 } 0309 } 0310 0311 if (instruction == QLatin1String("Continue")) { 0312 return RoutingInstruction::Straight; 0313 } else if (instruction == QLatin1String("half right")) { 0314 return RoutingInstruction::SlightRight; 0315 } else if (instruction == QLatin1String("right")) { 0316 return RoutingInstruction::Right; 0317 } else if (instruction == QLatin1String("sharp right")) { 0318 return RoutingInstruction::SharpRight; 0319 } else if (instruction == QLatin1String("straight forward")) { 0320 return RoutingInstruction::Straight; 0321 } else if (instruction == QLatin1String("turn")) { 0322 return RoutingInstruction::TurnAround; 0323 } else if (instruction == QLatin1String("sharp left")) { 0324 return RoutingInstruction::SharpLeft; 0325 } else if (instruction == QLatin1String("left")) { 0326 return RoutingInstruction::Left; 0327 } else if (instruction == QLatin1String("half left")) { 0328 return RoutingInstruction::SlightLeft; 0329 } 0330 0331 return RoutingInstruction::Unknown; 0332 } 0333 0334 } // namespace Marble 0335 0336 #include "moc_OpenRouteServiceRunner.cpp"