File indexing completed on 2025-02-16 03:37:59
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"