File indexing completed on 2025-04-20 06:39:16
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2013 Mihail Ivchenko <ematirov@gmail.com> 0004 // SPDX-FileCopyrightText: 2017 Sergey Popov <sergobot@protonmail.com> 0005 // 0006 0007 #include "CycleStreetsRunner.h" 0008 0009 #include "MarbleDebug.h" 0010 #include "GeoDataDocument.h" 0011 #include "GeoDataExtendedData.h" 0012 #include "GeoDataData.h" 0013 #include "GeoDataPlacemark.h" 0014 #include "GeoDataLineString.h" 0015 #include "HttpDownloadManager.h" 0016 #include "routing/Maneuver.h" 0017 #include "routing/RouteRequest.h" 0018 0019 #include <QUrl> 0020 #include <QTimer> 0021 #include <QJsonDocument> 0022 #include <QJsonObject> 0023 #include <QJsonArray> 0024 0025 #include <QUrlQuery> 0026 0027 namespace Marble 0028 { 0029 0030 CycleStreetsRunner::CycleStreetsRunner( 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 turns.insert( "", Maneuver::Continue ); 0039 turns.insert( "straight on", Maneuver::Straight ); 0040 turns.insert( "bear right", Maneuver::SlightRight ); 0041 turns.insert( "bear left", Maneuver::SlightLeft ); 0042 turns.insert( "sharp right", Maneuver::SharpRight ); 0043 turns.insert( "sharp left", Maneuver::SharpLeft ); 0044 turns.insert( "turn right", Maneuver::Right ); 0045 turns.insert( "turn left", Maneuver::Left ); 0046 turns.insert( "double-back", Maneuver::TurnAround); 0047 turns.insert( "first exit", Maneuver::RoundaboutFirstExit ); 0048 turns.insert( "second exit", Maneuver::RoundaboutSecondExit ); 0049 turns.insert( "third exit", Maneuver::RoundaboutThirdExit ); 0050 turns.insert( "fourth exit", Maneuver::RoundaboutExit ); 0051 turns.insert( "fifth exit", Maneuver::RoundaboutExit ); 0052 turns.insert( "sixth exit", Maneuver::RoundaboutExit ); 0053 turns.insert( "seventh or more exit", Maneuver::RoundaboutExit ); 0054 } 0055 0056 CycleStreetsRunner::~CycleStreetsRunner() 0057 { 0058 // nothing to do 0059 } 0060 0061 void CycleStreetsRunner::retrieveRoute( const RouteRequest *route ) 0062 { 0063 if ( route->size() < 2 || route->size() > 12 ) { 0064 return; 0065 } 0066 0067 QHash<QString, QVariant> settings = route->routingProfile().pluginSettings()[QStringLiteral("cyclestreets")]; 0068 0069 QUrl url("https://www.cyclestreets.net/api/journey.json"); 0070 QMap<QString, QString> queryStrings; 0071 queryStrings["key"] = "cdccf13997d59e70"; 0072 queryStrings["reporterrors"] = QLatin1Char('1'); 0073 queryStrings["plan"] = settings[QStringLiteral("plan")].toString(); 0074 if (queryStrings["plan"].isEmpty()) { 0075 mDebug() << "Missing a value for 'plan' in the settings, falling back to 'balanced'"; 0076 queryStrings["plan"] = QStringLiteral("balanced"); 0077 } 0078 queryStrings["speed"] = settings[QStringLiteral("speed")].toString(); 0079 if (queryStrings["speed"].isEmpty()) { 0080 mDebug() << "Missing a value for 'speed' in the settings, falling back to '20'"; 0081 queryStrings["speed"] = QStringLiteral("20"); 0082 } 0083 GeoDataCoordinates::Unit const degree = GeoDataCoordinates::Degree; 0084 QString itinerarypoints; 0085 itinerarypoints.append(QString::number(route->source().longitude(degree), 'f', 6) + QLatin1Char(',') + QString::number(route->source().latitude(degree), 'f', 6)); 0086 for ( int i=1; i<route->size(); ++i ) { 0087 itinerarypoints.append(QLatin1Char('|') + QString::number(route->at(i).longitude(degree), 'f', 6) + QLatin1Char(',') + QString::number(route->at(i).latitude(degree), 'f', 6)); 0088 } 0089 queryStrings["itinerarypoints"] = itinerarypoints; 0090 0091 QUrlQuery urlQuery; 0092 for( const QString& key: queryStrings.keys()){ 0093 urlQuery.addQueryItem(key, queryStrings.value(key)); 0094 } 0095 url.setQuery( urlQuery); 0096 0097 m_request.setUrl( url ); 0098 m_request.setRawHeader( "User-Agent", HttpDownloadManager::userAgent( "Browser", "CycleStreetsRunner" ) ); 0099 0100 QEventLoop eventLoop; 0101 0102 QTimer timer; 0103 timer.setSingleShot( true ); 0104 timer.setInterval( 15000 ); 0105 0106 connect( &timer, SIGNAL(timeout()), 0107 &eventLoop, SLOT(quit())); 0108 connect( this, SIGNAL(routeCalculated(GeoDataDocument*)), 0109 &eventLoop, SLOT(quit()) ); 0110 0111 // @todo FIXME Must currently be done in the main thread, see bug 257376 0112 QTimer::singleShot( 0, this, SLOT(get()) ); 0113 timer.start(); 0114 0115 eventLoop.exec(); 0116 } 0117 0118 void CycleStreetsRunner::get() 0119 { 0120 QNetworkReply *reply = m_networkAccessManager.get( m_request ); 0121 connect( reply, SIGNAL(error(QNetworkReply::NetworkError)), 0122 this, SLOT(handleError(QNetworkReply::NetworkError)), Qt::DirectConnection ); 0123 } 0124 0125 void CycleStreetsRunner::retrieveData( QNetworkReply *reply ) 0126 { 0127 if ( reply->isFinished() ) { 0128 QByteArray data = reply->readAll(); 0129 reply->deleteLater(); 0130 //mDebug() << "Download completed: " << data; 0131 GeoDataDocument *document = parse( data ); 0132 0133 if ( !document ) { 0134 mDebug() << "Failed to parse the downloaded route data" << data; 0135 } 0136 0137 emit routeCalculated( document ); 0138 } 0139 } 0140 0141 int CycleStreetsRunner::maneuverType(QString& cycleStreetsName) const 0142 { 0143 if ( turns.contains( cycleStreetsName ) ) { 0144 return turns[cycleStreetsName]; 0145 } 0146 return Maneuver::Unknown; 0147 } 0148 0149 GeoDataDocument *CycleStreetsRunner::parse( const QByteArray &content ) const 0150 { 0151 QJsonParseError error; 0152 QJsonDocument json = QJsonDocument::fromJson(content, &error); 0153 0154 if ( json.isEmpty() ) { 0155 mDebug() << "Cannot parse json file with routing instructions: " << error.errorString(); 0156 return Q_NULLPTR; 0157 } 0158 0159 // Check if CycleStreets has found any error 0160 if ( !json.object()["error"].isNull() ) { 0161 mDebug() << "CycleStreets reported an error: " << json.object()["error"].toString(); 0162 return Q_NULLPTR; 0163 } 0164 0165 GeoDataDocument *result = new GeoDataDocument(); 0166 result->setName(QStringLiteral("CycleStreets")); 0167 GeoDataPlacemark *routePlacemark = new GeoDataPlacemark; 0168 routePlacemark->setName(QStringLiteral("Route")); 0169 0170 GeoDataLineString *routeWaypoints = new GeoDataLineString; 0171 QJsonArray features = json.object()["marker"].toArray(); 0172 0173 if ( features.isEmpty() ) { 0174 return Q_NULLPTR ; 0175 } 0176 QJsonObject route = features.first().toObject()["@attributes"].toObject(); 0177 QJsonValue coordinates = route["coordinates"]; 0178 QStringList coordinatesList = coordinates.toString().split(QLatin1Char(' ')); 0179 0180 QStringList::iterator iter = coordinatesList.begin(); 0181 QStringList::iterator end = coordinatesList.end(); 0182 0183 for( ; iter != end; ++iter) { 0184 const QStringList coordinate = iter->split(QLatin1Char(',')); 0185 if ( coordinate.size() == 2 ) { 0186 double const lon = coordinate.at( 0 ).toDouble(); 0187 double const lat = coordinate.at( 1 ).toDouble(); 0188 GeoDataCoordinates const position( lon, lat, 0.0, GeoDataCoordinates::Degree ); 0189 routeWaypoints->append( position ); 0190 } 0191 } 0192 routePlacemark->setGeometry( routeWaypoints ); 0193 0194 QTime duration; 0195 duration = duration.addSecs( route["time"].toInt() ); 0196 qreal length = routeWaypoints->length( EARTH_RADIUS ); 0197 0198 const QString name = nameString( "CS", length, duration ); 0199 const GeoDataExtendedData data = routeData( length, duration ); 0200 routePlacemark->setExtendedData( data ); 0201 result->setName( name ); 0202 result->append( routePlacemark ); 0203 0204 for (int i = 1; i < features.count(); ++i) { 0205 QJsonObject segment = features.at( i ).toObject()["@attributes"].toObject(); 0206 0207 QString name = segment["name"].toString(); 0208 QString maneuver = segment["turn"].toString(); 0209 QStringList points = segment["points"].toString().split(QLatin1Char(' ')); 0210 QStringList const elevation = segment["elevations"].toString().split(QLatin1Char(',')); 0211 0212 GeoDataPlacemark *instructions = new GeoDataPlacemark; 0213 QString instructionName; 0214 if ( !maneuver.isEmpty() ) { 0215 instructionName = maneuver.left( 1 ).toUpper() + maneuver.mid( 1 ); 0216 } else { 0217 instructionName = "Straight"; 0218 } 0219 if (name != QLatin1String("Short un-named link") && name != QLatin1String("Un-named link")) { 0220 instructionName.append(QLatin1String(" into ") + name); 0221 } 0222 instructions->setName( instructionName ); 0223 0224 GeoDataExtendedData extendedData; 0225 GeoDataData turnType; 0226 turnType.setName(QStringLiteral("turnType")); 0227 turnType.setValue( maneuverType( maneuver ) ); 0228 extendedData.addValue( turnType ); 0229 0230 instructions->setExtendedData( extendedData ); 0231 GeoDataLineString *lineString = new GeoDataLineString; 0232 QStringList::iterator iter = points.begin(); 0233 QStringList::iterator end = points.end(); 0234 for ( int j=0; iter != end; ++iter, ++j ) { 0235 const QStringList coordinate = iter->split(QLatin1Char(',')); 0236 if ( coordinate.size() == 2 ) { 0237 double const lon = coordinate.at( 0 ).toDouble(); 0238 double const lat = coordinate.at( 1 ).toDouble(); 0239 double const alt = j < elevation.size() ? elevation[j].toDouble() : 0.0; 0240 lineString->append( GeoDataCoordinates( lon, lat, alt, GeoDataCoordinates::Degree ) ); 0241 } 0242 } 0243 instructions->setGeometry( lineString ); 0244 result->append( instructions ); 0245 } 0246 return result; 0247 } 0248 0249 void CycleStreetsRunner::handleError( QNetworkReply::NetworkError error ) 0250 { 0251 mDebug() << " Error when retrieving cyclestreets.net route: " << error; 0252 } 0253 0254 } // namespace Marble 0255 0256 #include "moc_CycleStreetsRunner.cpp"