File indexing completed on 2024-06-16 03:46:32

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "MonavPlugin.h"
0007 
0008 #include "signals.h"
0009 #include "MonavRunner.h"
0010 #include "MonavConfigWidget.h"
0011 #include "MonavMap.h"
0012 #include "MonavMapsModel.h"
0013 
0014 #include "MarbleDirs.h"
0015 #include "MarbleDebug.h"
0016 #include "GeoDataLatLonBox.h"
0017 #include "GeoDataData.h"
0018 #include "GeoDataExtendedData.h"
0019 #include "routing/RouteRequest.h"
0020 
0021 #include <QProcess>
0022 #include <QDirIterator>
0023 #include <QLocalSocket>
0024 #include <QThread>
0025 #include <QTextStream>
0026 
0027 namespace Marble
0028 {
0029 
0030 /** A helper class to have a portable sleep call */
0031 class MonavWaiter : private QThread
0032 {
0033 public:
0034     static void msleep( unsigned long milliSeconds ) {
0035         QThread::msleep( milliSeconds );
0036     }
0037 
0038 private:
0039     MonavWaiter() = delete;
0040     Q_DISABLE_COPY( MonavWaiter )
0041 };
0042 
0043 class MonavPluginPrivate
0044 {
0045 public:
0046     QDir m_mapDir;
0047 
0048     QVector<MonavMap> m_maps;
0049 
0050     bool m_ownsServer;
0051 
0052     QString m_monavDaemonProcess;
0053 
0054     MonavPlugin::MonavRoutingDaemonVersion m_monavVersion;
0055 
0056     MonavPluginPrivate();
0057 
0058     ~MonavPluginPrivate();
0059 
0060     bool startDaemon();
0061 
0062     void stopDaemon();
0063 
0064     static bool isDaemonRunning();
0065 
0066     static bool isDaemonInstalled();
0067 
0068     void loadMaps();
0069 
0070     void initialize();
0071 
0072     static bool areaLessThan( const MonavMap &first, const MonavMap &second );
0073 
0074 private:
0075     void loadMap( const QString &path );
0076 
0077     bool m_initialized;
0078 };
0079 
0080 MonavPluginPrivate::MonavPluginPrivate() : m_ownsServer( false ),
0081     m_monavDaemonProcess("monav-daemon"), m_monavVersion( MonavPlugin::Monav_0_3 ),
0082     m_initialized( false )
0083 {
0084     // nothing to do
0085 }
0086 
0087 MonavPluginPrivate::~MonavPluginPrivate()
0088 {
0089     stopDaemon();
0090 }
0091 
0092 bool MonavPluginPrivate::isDaemonRunning()
0093 {
0094     QLocalSocket socket;
0095     socket.connectToServer( "MoNavD" );
0096     return socket.waitForConnected();
0097 }
0098 
0099 bool MonavPluginPrivate::isDaemonInstalled()
0100 {
0101     QString path = QProcessEnvironment::systemEnvironment().value(QStringLiteral("PATH"), QStringLiteral("/usr/local/bin:/usr/bin:/bin"));
0102     auto const applications = QStringList() << "monav-daemon" << "MoNavD";
0103     for( const QString &application: applications ) {
0104         for( const QString &dir: path.split( QLatin1Char( ':' ) ) ) {
0105             QFileInfo executable( QDir( dir ), application );
0106             if ( executable.exists() ) {
0107                 return true;
0108             }
0109         }
0110     }
0111 
0112     return false;
0113 }
0114 
0115 bool MonavPluginPrivate::startDaemon()
0116 {
0117     if ( !isDaemonRunning() ) {
0118         if ( QProcess::startDetached( m_monavDaemonProcess, QStringList() ) ) {
0119             m_ownsServer = true;
0120         } else {
0121             if ( QProcess::startDetached( "MoNavD", QStringList() ) ) {
0122                 m_ownsServer = true;
0123                 m_monavDaemonProcess = "MoNavD";
0124                 m_monavVersion = MonavPlugin::Monav_0_2;
0125             } else {
0126                 return false;
0127             }
0128         }
0129 
0130         // Give monav-daemon up to one second to set up its server
0131         // Without that, the first route request would fail
0132         for ( int i = 0; i < 10; ++i ) {
0133             if ( isDaemonRunning() ) {
0134                 break;
0135             }
0136             MonavWaiter::msleep( 100 );
0137         }
0138 
0139         return true;
0140     }
0141 
0142     return true;
0143 }
0144 
0145 void MonavPluginPrivate::stopDaemon()
0146 {
0147     if ( m_ownsServer ) {
0148         m_ownsServer = false;
0149         QProcess::startDetached( m_monavDaemonProcess, QStringList() << "-t" );
0150     }
0151 }
0152 
0153 void MonavPluginPrivate::loadMaps()
0154 {
0155     if ( m_maps.isEmpty() ) {
0156         QStringList const baseDirs = QStringList() << MarbleDirs::systemPath() << MarbleDirs::localPath();
0157         for ( const QString &baseDir: baseDirs ) {
0158             const QString base = baseDir + QLatin1String("/maps/earth/monav/");
0159             loadMap( base );
0160             QDir::Filters filters = QDir::AllDirs | QDir::Readable | QDir::NoDotAndDotDot;
0161             QDirIterator::IteratorFlags flags = QDirIterator::Subdirectories | QDirIterator::FollowSymlinks;
0162             QDirIterator iter( base, filters, flags );
0163             while ( iter.hasNext() ) {
0164                 iter.next();
0165                 loadMap( iter.filePath() );
0166             }
0167         }
0168         // Prefer maps where bounding boxes are known
0169         std::sort( m_maps.begin(), m_maps.end(), MonavMap::areaLessThan );
0170     }
0171 }
0172 
0173 void MonavPluginPrivate::loadMap( const QString &path )
0174 {
0175     QDir mapDir( path );
0176     QFileInfo pluginsFile( mapDir, "plugins.ini" );
0177     QFileInfo moduleFile( mapDir, "Module.ini" );
0178     if ( pluginsFile.exists() && !moduleFile.exists() ) {
0179         qDebug() << "Migrating" << mapDir.dirName() << "from monav-0.2";
0180         QFile file( moduleFile.absoluteFilePath() );
0181         file.open( QIODevice::WriteOnly );
0182         QTextStream stream( &file );
0183         stream << "[General]\nconfigVersion=2\n";
0184         stream << "router=Contraction Hierarchies\ngpsLookup=GPS Grid\n";
0185         stream << "routerFileFormatVersion=1\ngpsLookupFileFormatVersion=1\n";
0186         stream.flush();
0187         file.close();
0188         moduleFile.refresh();
0189     }
0190 
0191     if ( moduleFile.exists() ) {
0192         MonavMap map;
0193         map.setDirectory( mapDir );
0194         m_maps.append( map );
0195     }
0196 }
0197 
0198 void MonavPluginPrivate::initialize()
0199 {
0200     if ( !m_initialized ) {
0201         m_initialized = true;
0202         loadMaps();
0203     }
0204 }
0205 
0206 MonavPlugin::MonavPlugin( QObject *parent ) :
0207     RoutingRunnerPlugin( parent ),
0208     d( new MonavPluginPrivate )
0209 {
0210     setSupportedCelestialBodies(QStringList(QStringLiteral("earth")));
0211     setCanWorkOffline( true );
0212 
0213     if ( d->isDaemonInstalled() ) {
0214         d->initialize();
0215         if ( d->m_maps.isEmpty() ) {
0216             setStatusMessage( tr ( "No offline maps installed yet." ) );
0217         }
0218     } else {
0219         setStatusMessage( tr ( "The monav routing daemon does not seem to be installed on your system." ) );
0220     }
0221 
0222     connect( qApp, SIGNAL(aboutToQuit()), this, SLOT(stopDaemon()) );
0223 }
0224 
0225 MonavPlugin::~MonavPlugin()
0226 {
0227     delete d;
0228 }
0229 
0230 QString MonavPlugin::name() const
0231 {
0232     return tr( "Monav Routing" );
0233 }
0234 
0235 QString MonavPlugin::guiString() const
0236 {
0237     return tr( "Monav" );
0238 }
0239 
0240 QString MonavPlugin::nameId() const
0241 {
0242     return QStringLiteral("monav");
0243 }
0244 
0245 QString MonavPlugin::version() const
0246 {
0247     return QStringLiteral("1.0");
0248 }
0249 
0250 QString MonavPlugin::description() const
0251 {
0252     return tr( "Offline routing using the monav daemon" );
0253 }
0254 
0255 QString MonavPlugin::copyrightYears() const
0256 {
0257     return QStringLiteral("2010");
0258 }
0259 
0260 QVector<PluginAuthor> MonavPlugin::pluginAuthors() const
0261 {
0262     return QVector<PluginAuthor>()
0263             << PluginAuthor(QStringLiteral("Dennis Nienhüser"), QStringLiteral("nienhueser@kde.org"));
0264 }
0265 
0266 RoutingRunner *MonavPlugin::newRunner() const
0267 {
0268     d->initialize();
0269     if ( !d->startDaemon() ) {
0270         mDebug() << "Failed to start the monav routing daemon";
0271     }
0272 
0273     return new MonavRunner( this );
0274 }
0275 
0276 QString MonavPlugin::mapDirectoryForRequest( const RouteRequest* request ) const
0277 {
0278     d->initialize();
0279 
0280     QHash<QString, QVariant> settings = request->routingProfile().pluginSettings()[nameId()];
0281     const QString transport = settings[QStringLiteral("transport")].toString();
0282 
0283     for ( int j=0; j<d->m_maps.size(); ++j ) {
0284         bool valid = true;
0285         if ( transport.isEmpty() || transport == d->m_maps[j].transport() ) {
0286             for ( int i = 0; i < request->size(); ++i ) {
0287                 GeoDataCoordinates via = request->at( i );
0288                 if ( !d->m_maps[j].containsPoint( via ) ) {
0289                     valid = false;
0290                     break;
0291                 }
0292             }
0293         } else {
0294             valid = false;
0295         }
0296 
0297         if ( valid ) {
0298             if ( j ) {
0299                 // Subsequent route requests will likely be in the same country
0300                 qSwap( d->m_maps[0], d->m_maps[j] );
0301             }
0302             // mDebug() << "Using " << d->m_maps.first().m_directory.dirName() << " as monav map";
0303             return d->m_maps.first().directory().absolutePath();
0304         }
0305     }
0306 
0307     return QString();
0308 }
0309 
0310 QStringList MonavPlugin::mapDirectoriesForRequest( const RouteRequest* request ) const
0311 {
0312     QStringList result;
0313     d->initialize();
0314     QHash<QString, QVariant> settings = request->routingProfile().pluginSettings()[nameId()];
0315     const QString transport = settings[QStringLiteral("transport")].toString();
0316 
0317     for ( int j=0; j<d->m_maps.size(); ++j ) {
0318         bool valid = true;
0319         if ( transport.isEmpty() || transport == d->m_maps[j].transport() ) {
0320             for ( int i = 0; i < request->size(); ++i ) {
0321                 GeoDataCoordinates via = request->at( i );
0322                 if ( !d->m_maps[j].containsPoint( via ) ) {
0323                     valid = false;
0324                     break;
0325                 }
0326             }
0327         } else {
0328             valid = false;
0329         }
0330 
0331         if ( valid ) {
0332             result << d->m_maps[j].directory().absolutePath();
0333         }
0334     }
0335 
0336     return result;
0337 }
0338 
0339 RoutingRunnerPlugin::ConfigWidget *MonavPlugin::configWidget()
0340 {
0341     return new MonavConfigWidget( this );
0342 }
0343 
0344 MonavMapsModel* MonavPlugin::installedMapsModel()
0345 {
0346     d->initialize();
0347     return new MonavMapsModel( d->m_maps );
0348 }
0349 
0350 void MonavPlugin::reloadMaps()
0351 {
0352     d->m_maps.clear();
0353     d->loadMaps();
0354 }
0355 
0356 bool MonavPlugin::canWork() const
0357 {
0358     d->initialize();
0359     return !d->m_maps.isEmpty();
0360 }
0361 
0362 bool MonavPlugin::supportsTemplate( RoutingProfilesModel::ProfileTemplate profileTemplate ) const
0363 {
0364     // Since we support multiple maps, pretty much anything can be installed, but ecological is
0365     // not supported by monav
0366     return profileTemplate != RoutingProfilesModel::CarEcologicalTemplate;
0367 }
0368 
0369 QHash< QString, QVariant > MonavPlugin::templateSettings( RoutingProfilesModel::ProfileTemplate profileTemplate ) const
0370 {
0371     QHash<QString, QVariant> result;
0372     switch ( profileTemplate ) {
0373         case RoutingProfilesModel::CarFastestTemplate:
0374             result["transport"] = "Motorcar";
0375             break;
0376         case RoutingProfilesModel::CarShortestTemplate:
0377             result["transport"] = "Motorcar";
0378             break;
0379         case RoutingProfilesModel::CarEcologicalTemplate:
0380             break;
0381         case RoutingProfilesModel::BicycleTemplate:
0382             result["transport"] = "Bicycle";
0383             break;
0384         case RoutingProfilesModel::PedestrianTemplate:
0385             result["transport"] = "Pedestrian";
0386             break;
0387         case RoutingProfilesModel::LastTemplate:
0388             Q_ASSERT( false );
0389             break;
0390     }
0391     return result;
0392 }
0393 
0394 MonavPlugin::MonavRoutingDaemonVersion MonavPlugin::monavVersion() const
0395 {
0396     return d->m_monavVersion;
0397 }
0398 
0399 }
0400 
0401 #include "moc_MonavPlugin.cpp"