File indexing completed on 2024-05-12 15:31:32

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 // SPDX-FileCopyrightText: 2010 Niko Sams <niko.sams@gmail.com>
0005 //
0006 
0007 #include "RoutinoRunner.h"
0008 
0009 #include "MarbleDebug.h"
0010 #include "MarbleDirs.h"
0011 #include "routing/RouteRequest.h"
0012 #include "routing/instructions/WaypointParser.h"
0013 #include "routing/instructions/InstructionTransformation.h"
0014 #include "GeoDataDocument.h"
0015 #include "GeoDataExtendedData.h"
0016 #include "GeoDataData.h"
0017 #include "GeoDataPlacemark.h"
0018 #include "GeoDataLineString.h"
0019 
0020 #include <QProcess>
0021 #include <QTemporaryFile>
0022 #include <MarbleMap.h>
0023 #include <MarbleModel.h>
0024 #include <routing/RoutingManager.h>
0025 #include <routing/RoutingProfilesModel.h>
0026 
0027 namespace Marble
0028 {
0029 
0030 class RoutinoRunnerPrivate
0031 {
0032 public:
0033     QDir m_mapDir;
0034 
0035     WaypointParser m_parser;
0036 
0037     QByteArray retrieveWaypoints( const QStringList &params ) const;
0038 
0039     static GeoDataDocument* createDocument( GeoDataLineString* routeWaypoints, const QVector<GeoDataPlacemark*> instructions );
0040 
0041     static GeoDataLineString* parseRoutinoOutput( const QByteArray &content );
0042 
0043     QVector<GeoDataPlacemark*> parseRoutinoInstructions( const QByteArray &content ) const;
0044 
0045     RoutinoRunnerPrivate();
0046 };
0047 
0048 RoutinoRunnerPrivate::RoutinoRunnerPrivate()
0049 {
0050     m_parser.setLineSeparator("\n");
0051     m_parser.setFieldSeparator(QLatin1Char('\t'));
0052     m_parser.setFieldIndex( WaypointParser::RoadName, 10 );
0053 }
0054 
0055 class TemporaryDir
0056 {
0057 public:
0058     TemporaryDir() {
0059         QTemporaryFile f;
0060         f.setAutoRemove( false );
0061         f.open();
0062         m_dirName = f.fileName();
0063         f.close();
0064         f.remove();
0065         QFileInfo( m_dirName ).dir().mkdir( m_dirName );
0066     }
0067 
0068     ~TemporaryDir() {
0069         QDir dir( m_dirName );
0070         QFileInfoList entries = dir.entryInfoList( QDir::Files );
0071         for ( const QFileInfo &file: entries ) {
0072             QFile( file.absoluteFilePath() ).remove();
0073         }
0074         dir.rmdir( dir.absolutePath() );
0075     }
0076 
0077     QString dirName() const
0078     {
0079         return m_dirName;
0080     }
0081 private:
0082     QString m_dirName;
0083 };
0084 
0085 QByteArray RoutinoRunnerPrivate::retrieveWaypoints( const QStringList &params ) const
0086 {
0087     TemporaryDir dir;
0088     QProcess routinoProcess;
0089     routinoProcess.setWorkingDirectory( dir.dirName() );
0090 
0091     QStringList routinoParams;
0092     routinoParams << params;
0093     routinoParams << QLatin1String("--dir=") + m_mapDir.absolutePath();
0094     routinoParams << "--output-text-all";
0095     mDebug() << routinoParams;
0096     routinoProcess.start( "routino-router", routinoParams );
0097     if ( !routinoProcess.waitForStarted( 5000 ) ) {
0098         mDebug() << "Couldn't start routino-router from the current PATH. Install it to retrieve routing results from routino.";
0099         return nullptr;
0100     }
0101 
0102     if ( routinoProcess.waitForFinished(60 * 1000) ) {
0103         mDebug() << routinoProcess.readAll();
0104         mDebug() << "routino finished";
0105         QFile file(routinoProcess.workingDirectory() + QLatin1String("/shortest-all.txt"));
0106         if ( !file.exists() ) {
0107             file.setFileName(routinoProcess.workingDirectory() + QLatin1String("/quickest-all.txt"));
0108         }
0109         if ( !file.exists() ) {
0110             mDebug() << "Can't get results";
0111         } else {
0112             file.open( QIODevice::ReadOnly );
0113             return file.readAll();
0114         }
0115     }
0116     else {
0117         mDebug() << "Couldn't stop routino";
0118     }
0119     return nullptr;
0120 }
0121 
0122 GeoDataLineString* RoutinoRunnerPrivate::parseRoutinoOutput( const QByteArray &content )
0123 {
0124     GeoDataLineString* routeWaypoints = new GeoDataLineString;
0125 
0126     const QStringList lines = QString::fromUtf8(content).split(QLatin1Char('\n'));
0127     mDebug() << lines.count() << "lines";
0128     for( const QString &line: lines ) {
0129         if (line.startsWith(QLatin1Char('#'))) {
0130             //skip comment
0131             continue;
0132         }
0133         const QStringList fields = line.split(QLatin1Char('\t'));
0134         if ( fields.size() >= 10 ) {
0135             qreal lon = fields.at(1).trimmed().toDouble();
0136             qreal lat = fields.at(0).trimmed().toDouble();
0137             GeoDataCoordinates coordinates( lon, lat, 0.0, GeoDataCoordinates::Degree );
0138             routeWaypoints->append( coordinates );
0139         }
0140     }
0141 
0142     return routeWaypoints;
0143 }
0144 
0145 QVector<GeoDataPlacemark*> RoutinoRunnerPrivate::parseRoutinoInstructions( const QByteArray &content ) const
0146 {
0147     QVector<GeoDataPlacemark*> result;
0148     QTextStream stream( content );
0149     stream.setCodec("UTF8");
0150     stream.setAutoDetectUnicode( true );
0151 
0152     RoutingInstructions directions = InstructionTransformation::process( m_parser.parse( stream ) );
0153     for( int i=0; i<directions.size(); ++i ) {
0154         GeoDataPlacemark* placemark = new GeoDataPlacemark( directions[i].instructionText() );
0155         GeoDataExtendedData extendedData;
0156         GeoDataData turnType;
0157         turnType.setName(QStringLiteral("turnType"));
0158         turnType.setValue( QVariant::fromValue( int( directions[i].turnType() ) ) );
0159         extendedData.addValue( turnType );
0160         GeoDataData roadName;
0161         roadName.setName(QStringLiteral("roadName"));
0162         roadName.setValue( directions[i].roadName() );
0163         extendedData.addValue( roadName );
0164         placemark->setExtendedData( extendedData );
0165         Q_ASSERT( !directions[i].points().isEmpty() );
0166         GeoDataLineString* geometry = new GeoDataLineString;
0167         QVector<RoutingWaypoint> items = directions[i].points();
0168         for (int j=0; j<items.size(); ++j ) {
0169             RoutingPoint point = items[j].point();
0170             GeoDataCoordinates coordinates( point.lon(), point.lat(), 0.0, GeoDataCoordinates::Degree );
0171             geometry->append( coordinates );
0172         }
0173         placemark->setGeometry( geometry );
0174         result.push_back( placemark );
0175     }
0176 
0177     return result;
0178 }
0179 
0180 GeoDataDocument* RoutinoRunnerPrivate::createDocument( GeoDataLineString* routeWaypoints, const QVector<GeoDataPlacemark*> instructions )
0181 {
0182     if ( !routeWaypoints || routeWaypoints->isEmpty() ) {
0183         return nullptr;
0184     }
0185 
0186     GeoDataDocument* result = new GeoDataDocument();
0187     GeoDataPlacemark* routePlacemark = new GeoDataPlacemark;
0188     routePlacemark->setName( "Route" );
0189     routePlacemark->setGeometry( routeWaypoints );
0190     result->append( routePlacemark );
0191 
0192     QString name = "%1 %2 (Routino)";
0193     QString unit = QLatin1String( "m" );
0194     qreal length = routeWaypoints->length( EARTH_RADIUS );
0195     if (length >= 1000) {
0196         length /= 1000.0;
0197         unit = "km";
0198     }
0199     result->setName( name.arg( length, 0, 'f', 1 ).arg( unit ) );
0200 
0201     for( GeoDataPlacemark* placemark: instructions )
0202     {
0203         result->append( placemark );
0204     }
0205 
0206     return result;
0207 }
0208 
0209 RoutinoRunner::RoutinoRunner( QObject *parent ) :
0210         RoutingRunner( parent ),
0211         d( new RoutinoRunnerPrivate )
0212 {
0213     // Check installation
0214     d->m_mapDir = QDir(MarbleDirs::localPath() + QLatin1String("/maps/earth/routino/"));
0215 }
0216 
0217 RoutinoRunner::~RoutinoRunner()
0218 {
0219     delete d;
0220 }
0221 
0222 void RoutinoRunner::retrieveRoute( const RouteRequest *route )
0223 {
0224     mDebug();
0225 
0226     if ( ! QFileInfo( d->m_mapDir, "nodes.mem" ).exists() )
0227     {
0228         emit routeCalculated( nullptr );
0229         return;
0230     }
0231 
0232     QStringList params;
0233     for( int i=0; i<route->size(); ++i )
0234     {
0235         double fLon = route->at(i).longitude( GeoDataCoordinates::Degree );
0236         double fLat = route->at(i).latitude( GeoDataCoordinates::Degree );
0237         params << QString("--lat%1=%2").arg(i+1).arg(fLat, 0, 'f', 8);
0238         params << QString("--lon%1=%2").arg(i+1).arg(fLon, 0, 'f', 8);
0239     }
0240 
0241     QHash<QString, QVariant> settings = route->routingProfile().pluginSettings()[QStringLiteral("routino")];
0242     QString transport = settings[QStringLiteral("transport")].toString();
0243     params << QString( "--transport=%0" ).arg( transport );
0244 
0245     if (settings[QStringLiteral("method")] == QLatin1String("shortest")) {
0246         params << "--shortest";
0247     } else {
0248         params << "--quickest";
0249     }
0250     /*
0251     if ( route->avoidFeatures() & RouteRequest::AvoidHighway ) {
0252         params << "--highway-motorway=0";
0253     }
0254     */
0255 
0256     QByteArray output = d->retrieveWaypoints( params );
0257     GeoDataLineString* wayPoints = d->parseRoutinoOutput( output );
0258     QVector<GeoDataPlacemark*> instructions = d->parseRoutinoInstructions( output );
0259 
0260     GeoDataDocument* result = d->createDocument( wayPoints, instructions );
0261     mDebug() << this << "routeCalculated";
0262     emit routeCalculated( result );
0263 }
0264 
0265 } // namespace Marble
0266 
0267 #include "moc_RoutinoRunner.cpp"