File indexing completed on 2024-07-21 09:27:05

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"