File indexing completed on 2024-04-28 03:49:29
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "RoutingManager.h" 0007 0008 #include "AlternativeRoutesModel.h" 0009 #include "MarbleModel.h" 0010 #include "RouteRequest.h" 0011 #include "RoutingModel.h" 0012 #include "RoutingProfilesModel.h" 0013 #include "RoutingRunnerPlugin.h" 0014 #include "GeoWriter.h" 0015 #include "GeoDataDocument.h" 0016 #include "GeoDataExtendedData.h" 0017 #include "GeoDataData.h" 0018 #include "GeoDataFolder.h" 0019 #include "GeoDataParser.h" 0020 #include "GeoDataPlacemark.h" 0021 #include "GeoDataTreeModel.h" 0022 #include "MarbleColors.h" 0023 #include "MarbleDirs.h" 0024 #include "MarbleDebug.h" 0025 #include "PositionTracking.h" 0026 #include "PluginManager.h" 0027 #include "PositionProviderPlugin.h" 0028 #include "Route.h" 0029 #include "RoutingRunnerManager.h" 0030 #include <KmlElementDictionary.h> 0031 0032 #include <QFile> 0033 #include <QMessageBox> 0034 #include <QCheckBox> 0035 #include <QMutexLocker> 0036 0037 namespace Marble 0038 { 0039 0040 class RoutingManagerPrivate 0041 { 0042 public: 0043 RoutingManager* q; 0044 0045 RouteRequest m_routeRequest; 0046 0047 RoutingModel m_routingModel; 0048 0049 RoutingProfilesModel m_profilesModel; 0050 0051 RoutingManager::State m_state; 0052 0053 const PluginManager *const m_pluginManager; 0054 0055 GeoDataTreeModel *const m_treeModel; 0056 0057 PositionTracking *const m_positionTracking; 0058 0059 AlternativeRoutesModel m_alternativeRoutesModel; 0060 0061 RoutingRunnerManager m_runnerManager; 0062 0063 bool m_haveRoute; 0064 0065 bool m_guidanceModeEnabled; 0066 0067 QMutex m_fileMutex; 0068 0069 bool m_shutdownPositionTracking; 0070 0071 bool m_guidanceModeWarning; 0072 0073 QString m_lastOpenPath; 0074 0075 QString m_lastSavePath; 0076 0077 QColor m_routeColorStandard; 0078 0079 QColor m_routeColorHighlighted; 0080 0081 QColor m_routeColorAlternative; 0082 0083 RoutingManagerPrivate(MarbleModel *marbleModel, RoutingManager *manager); 0084 0085 static GeoDataFolder *createFolderFromRequest(const RouteRequest &request); 0086 0087 static QString stateFile( const QString &name = QString( "route.kml" ) ); 0088 0089 void saveRoute( const QString &filename ); 0090 0091 void loadRoute( const QString &filename ); 0092 0093 void addRoute( GeoDataDocument* route ); 0094 0095 void routingFinished(); 0096 0097 void setCurrentRoute(const GeoDataDocument *route); 0098 0099 void recalculateRoute( bool deviated ); 0100 0101 static void importPlacemark( RouteSegment &outline, QVector<RouteSegment> &segments, const GeoDataPlacemark *placemark ); 0102 }; 0103 0104 RoutingManagerPrivate::RoutingManagerPrivate(MarbleModel *model, RoutingManager *manager) : 0105 q( manager ), 0106 m_routeRequest( manager ), 0107 m_routingModel(&m_routeRequest, model->positionTracking(), manager), 0108 m_profilesModel( model->pluginManager() ), 0109 m_state( RoutingManager::Retrieved ), 0110 m_pluginManager( model->pluginManager() ), 0111 m_treeModel( model->treeModel() ), 0112 m_positionTracking( model->positionTracking() ), 0113 m_alternativeRoutesModel(manager), 0114 m_runnerManager(model, manager), 0115 m_haveRoute( false ), 0116 m_guidanceModeEnabled( false ), 0117 m_shutdownPositionTracking( false ), 0118 m_guidanceModeWarning( true ), 0119 m_routeColorStandard( Oxygen::skyBlue4 ), 0120 m_routeColorHighlighted( Oxygen::skyBlue1 ), 0121 m_routeColorAlternative( Oxygen::aluminumGray4 ) 0122 { 0123 m_routeColorStandard.setAlpha( 200 ); 0124 m_routeColorHighlighted.setAlpha( 200 ); 0125 m_routeColorAlternative.setAlpha( 200 ); 0126 } 0127 0128 GeoDataFolder *RoutingManagerPrivate::createFolderFromRequest(const RouteRequest &request) 0129 { 0130 GeoDataFolder* result = new GeoDataFolder; 0131 0132 result->setName(QStringLiteral("Route Request")); 0133 0134 for (int i = 0; i < request.size(); ++i) { 0135 GeoDataPlacemark *placemark = new GeoDataPlacemark(request[i]); 0136 result->append( placemark ); 0137 } 0138 0139 return result; 0140 } 0141 0142 QString RoutingManagerPrivate::stateFile( const QString &name) 0143 { 0144 QString const subdir = "routing"; 0145 QDir dir( MarbleDirs::localPath() ); 0146 if ( !dir.exists( subdir ) ) { 0147 if ( !dir.mkdir( subdir ) ) { 0148 mDebug() << "Unable to create dir " << dir.absoluteFilePath( subdir ); 0149 return dir.absolutePath(); 0150 } 0151 } 0152 0153 if ( !dir.cd( subdir ) ) { 0154 mDebug() << "Cannot change into " << dir.absoluteFilePath( subdir ); 0155 } 0156 0157 return dir.absoluteFilePath( name ); 0158 } 0159 0160 void RoutingManagerPrivate::saveRoute(const QString &filename) 0161 { 0162 GeoWriter writer; 0163 writer.setDocumentType( kml::kmlTag_nameSpaceOgc22 ); 0164 0165 QMutexLocker locker( &m_fileMutex ); 0166 QFile file( filename ); 0167 if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) 0168 { 0169 mDebug() << "Cannot write to " << file.fileName(); 0170 return; 0171 } 0172 0173 GeoDataDocument container; 0174 container.setName(QStringLiteral("Route")); 0175 GeoDataFolder *request = createFolderFromRequest(m_routeRequest); 0176 if ( request ) { 0177 container.append( request ); 0178 } 0179 0180 const GeoDataDocument *route = m_alternativeRoutesModel.currentRoute(); 0181 if ( route ) { 0182 container.append( new GeoDataDocument( *route ) ); 0183 } 0184 0185 if ( !writer.write( &file, &container ) ) { 0186 mDebug() << "Can not write route state to " << file.fileName(); 0187 } 0188 file.close(); 0189 } 0190 0191 void RoutingManagerPrivate::loadRoute(const QString &filename) 0192 { 0193 QFile file( filename ); 0194 if ( !file.open( QIODevice::ReadOnly ) ) { 0195 mDebug() << "Can not read route from " << file.fileName(); 0196 return; 0197 } 0198 0199 GeoDataParser parser( GeoData_KML ); 0200 if ( !parser.read( &file ) ) { 0201 mDebug() << "Could not parse file: " << parser.errorString(); 0202 return; 0203 } 0204 0205 GeoDocument *doc = parser.releaseDocument(); 0206 file.close(); 0207 bool loaded = false; 0208 0209 GeoDataDocument* container = dynamic_cast<GeoDataDocument*>( doc ); 0210 if (container && !container->isEmpty()) { 0211 GeoDataFolder* viaPoints = dynamic_cast<GeoDataFolder*>( &container->first() ); 0212 if ( viaPoints ) { 0213 loaded = true; 0214 QVector<GeoDataPlacemark*> placemarks = viaPoints->placemarkList(); 0215 for( int i=0; i<placemarks.size(); ++i ) { 0216 if ( i < m_routeRequest.size() ) { 0217 m_routeRequest[i] = *placemarks[i]; 0218 } else { 0219 m_routeRequest.append( *placemarks[i] ); 0220 } 0221 } 0222 0223 // clear unneeded via points 0224 const int viaPoints_needed = placemarks.size(); 0225 for ( int i = m_routeRequest.size(); i > viaPoints_needed; --i ) { 0226 m_routeRequest.remove( viaPoints_needed ); 0227 } 0228 } else { 0229 mDebug() << "Expected a GeoDataDocument with at least one child, didn't get one though"; 0230 } 0231 } 0232 0233 if ( container && container->size() == 2 ) { 0234 GeoDataDocument* route = dynamic_cast<GeoDataDocument*>(&container->last()); 0235 if ( route ) { 0236 loaded = true; 0237 m_alternativeRoutesModel.clear(); 0238 m_alternativeRoutesModel.addRoute( new GeoDataDocument(*route), AlternativeRoutesModel::Instant ); 0239 m_alternativeRoutesModel.setCurrentRoute( 0 ); 0240 m_state = RoutingManager::Retrieved; 0241 emit q->stateChanged( m_state ); 0242 emit q->routeRetrieved( route ); 0243 } else { 0244 mDebug() << "Expected a GeoDataDocument child, didn't get one though"; 0245 } 0246 } 0247 0248 if (loaded) { 0249 delete doc; // == container 0250 } else { 0251 mDebug() << "File " << filename << " is not a valid Marble route .kml file"; 0252 if ( container ) { 0253 m_treeModel->addDocument( container ); 0254 } 0255 } 0256 } 0257 0258 RoutingManager::RoutingManager(MarbleModel *marbleModel, QObject *parent) : 0259 QObject(parent), 0260 d(new RoutingManagerPrivate(marbleModel, this)) 0261 { 0262 connect( &d->m_runnerManager, SIGNAL(routeRetrieved(GeoDataDocument*)), 0263 this, SLOT(addRoute(GeoDataDocument*)) ); 0264 connect( &d->m_runnerManager, SIGNAL(routingFinished()), 0265 this, SLOT(routingFinished()) ); 0266 connect(&d->m_alternativeRoutesModel, SIGNAL(currentRouteChanged(const GeoDataDocument*)), 0267 this, SLOT(setCurrentRoute(const GeoDataDocument*))); 0268 connect( &d->m_routingModel, SIGNAL(deviatedFromRoute(bool)), 0269 this, SLOT(recalculateRoute(bool)) ); 0270 } 0271 0272 RoutingManager::~RoutingManager() 0273 { 0274 delete d; 0275 } 0276 0277 RoutingProfilesModel *RoutingManager::profilesModel() 0278 { 0279 return &d->m_profilesModel; 0280 } 0281 0282 RoutingModel *RoutingManager::routingModel() 0283 { 0284 return &d->m_routingModel; 0285 } 0286 0287 const RoutingModel *RoutingManager::routingModel() const 0288 { 0289 return &d->m_routingModel; 0290 } 0291 0292 RouteRequest* RoutingManager::routeRequest() 0293 { 0294 return &d->m_routeRequest; 0295 } 0296 0297 RoutingManager::State RoutingManager::state() const 0298 { 0299 return d->m_state; 0300 } 0301 0302 void RoutingManager::retrieveRoute() 0303 { 0304 d->m_haveRoute = false; 0305 0306 int realSize = 0; 0307 for ( int i = 0; i < d->m_routeRequest.size(); ++i ) { 0308 // Sort out dummy targets 0309 if ( d->m_routeRequest.at( i ).isValid() ) { 0310 ++realSize; 0311 } 0312 } 0313 0314 d->m_alternativeRoutesModel.newRequest( &d->m_routeRequest ); 0315 if ( realSize > 1 ) { 0316 d->m_state = RoutingManager::Downloading; 0317 d->m_runnerManager.retrieveRoute( &d->m_routeRequest ); 0318 } else { 0319 d->m_routingModel.clear(); 0320 d->m_state = RoutingManager::Retrieved; 0321 } 0322 emit stateChanged( d->m_state ); 0323 } 0324 0325 void RoutingManagerPrivate::addRoute( GeoDataDocument* route ) 0326 { 0327 if ( route ) { 0328 m_alternativeRoutesModel.addRoute( route ); 0329 } 0330 0331 if ( !m_haveRoute ) { 0332 m_haveRoute = route != nullptr; 0333 } 0334 0335 emit q->routeRetrieved( route ); 0336 } 0337 0338 void RoutingManagerPrivate::routingFinished() 0339 { 0340 m_state = RoutingManager::Retrieved; 0341 emit q->stateChanged( m_state ); 0342 } 0343 0344 void RoutingManagerPrivate::setCurrentRoute(const GeoDataDocument *document) 0345 { 0346 QVector<RouteSegment> segments; 0347 RouteSegment outline; 0348 0349 if (document != nullptr) { 0350 const auto folders = document->folderList(); 0351 for (const auto folder : folders) { 0352 for (const auto placemark : folder->placemarkList()) { 0353 importPlacemark(outline, segments, placemark); 0354 } 0355 } 0356 0357 for (const auto placemark : document->placemarkList()) { 0358 importPlacemark(outline, segments, placemark); 0359 } 0360 } 0361 0362 if ( segments.isEmpty() ) { 0363 segments << outline; 0364 } 0365 0366 // Map via points onto segments 0367 if ( m_routeRequest.size() > 1 && segments.size() > 1 ) { 0368 int index = 0; 0369 for ( int j = 0; j < m_routeRequest.size(); ++j ) { 0370 QPair<int, qreal> minimum( -1, -1.0 ); 0371 int viaIndex = -1; 0372 for ( int i = index; i < segments.size(); ++i ) { 0373 const RouteSegment &segment = segments[i]; 0374 GeoDataCoordinates closest; 0375 const qreal distance = segment.distanceTo( m_routeRequest.at( j ), closest, closest ); 0376 if ( minimum.first < 0 || distance < minimum.second ) { 0377 minimum.first = i; 0378 minimum.second = distance; 0379 viaIndex = j; 0380 } 0381 } 0382 0383 if ( minimum.first >= 0 ) { 0384 index = minimum.first; 0385 Maneuver viaPoint = segments[ minimum.first ].maneuver(); 0386 viaPoint.setWaypoint( m_routeRequest.at( viaIndex ), viaIndex ); 0387 segments[ minimum.first ].setManeuver( viaPoint ); 0388 } 0389 } 0390 } 0391 0392 Route route; 0393 0394 if ( segments.size() > 0 ) { 0395 for( const RouteSegment &segment: segments ) { 0396 route.addRouteSegment( segment ); 0397 } 0398 } 0399 0400 m_routingModel.setRoute( route ); 0401 } 0402 0403 void RoutingManagerPrivate::importPlacemark( RouteSegment &outline, QVector<RouteSegment> &segments, const GeoDataPlacemark *placemark ) 0404 { 0405 const GeoDataGeometry* geometry = placemark->geometry(); 0406 const GeoDataLineString* lineString = dynamic_cast<const GeoDataLineString*>( geometry ); 0407 QStringList blacklist = QStringList() << "" << "Route" << "Tessellated"; 0408 RouteSegment segment; 0409 bool isOutline = true; 0410 if ( !blacklist.contains( placemark->name() ) ) { 0411 if( lineString ) { 0412 Maneuver maneuver; 0413 maneuver.setInstructionText( placemark->name() ); 0414 maneuver.setPosition( lineString->at( 0 ) ); 0415 0416 if (placemark->extendedData().contains(QStringLiteral("turnType"))) { 0417 QVariant turnType = placemark->extendedData().value(QStringLiteral("turnType")).value(); 0418 // The enum value is converted to/from an int in the QVariant 0419 // because only a limited set of data types can be serialized with QVariant's 0420 // toString() method (which is used to serialize <ExtendedData>/<Data> values) 0421 maneuver.setDirection( Maneuver::Direction( turnType.toInt() ) ); 0422 } 0423 0424 if (placemark->extendedData().contains(QStringLiteral("roadName"))) { 0425 QVariant roadName = placemark->extendedData().value(QStringLiteral("roadName")).value(); 0426 maneuver.setRoadName( roadName.toString() ); 0427 } 0428 0429 segment.setManeuver( maneuver ); 0430 isOutline = false; 0431 } 0432 } 0433 0434 if ( lineString ) { 0435 segment.setPath( *lineString ); 0436 0437 if ( isOutline ) { 0438 outline = segment; 0439 } else { 0440 segments.push_back( segment ); 0441 } 0442 } 0443 } 0444 0445 AlternativeRoutesModel* RoutingManager::alternativeRoutesModel() 0446 { 0447 return &d->m_alternativeRoutesModel; 0448 } 0449 0450 void RoutingManager::writeSettings() const 0451 { 0452 d->saveRoute( d->stateFile() ); 0453 } 0454 0455 void RoutingManager::saveRoute( const QString &filename ) const 0456 { 0457 d->saveRoute( filename ); 0458 } 0459 0460 void RoutingManager::loadRoute( const QString &filename ) 0461 { 0462 d->loadRoute( filename ); 0463 } 0464 0465 RoutingProfile RoutingManager::defaultProfile( RoutingProfile::TransportType transportType ) const 0466 { 0467 RoutingProfile profile; 0468 RoutingProfilesModel::ProfileTemplate tpl = RoutingProfilesModel::CarFastestTemplate; 0469 switch ( transportType ) { 0470 case RoutingProfile::Motorcar: 0471 tpl = RoutingProfilesModel::CarFastestTemplate; 0472 profile.setName(QStringLiteral("Motorcar")); 0473 profile.setTransportType( RoutingProfile::Motorcar ); 0474 break; 0475 case RoutingProfile::Bicycle: 0476 tpl = RoutingProfilesModel::BicycleTemplate; 0477 profile.setName(QStringLiteral("Bicycle")); 0478 profile.setTransportType( RoutingProfile::Bicycle ); 0479 break; 0480 case RoutingProfile::Pedestrian: 0481 tpl = RoutingProfilesModel::PedestrianTemplate; 0482 profile.setName(QStringLiteral("Pedestrian")); 0483 profile.setTransportType( RoutingProfile::Pedestrian ); 0484 break; 0485 } 0486 0487 for( RoutingRunnerPlugin* plugin: d->m_pluginManager->routingRunnerPlugins() ) { 0488 if ( plugin->supportsTemplate( tpl ) ) { 0489 profile.pluginSettings()[plugin->nameId()] = plugin->templateSettings( tpl ); 0490 } 0491 } 0492 0493 return profile; 0494 } 0495 0496 void RoutingManager::readSettings() 0497 { 0498 d->loadRoute( d->stateFile() ); 0499 } 0500 0501 void RoutingManager::setGuidanceModeEnabled( bool enabled ) 0502 { 0503 if ( d->m_guidanceModeEnabled == enabled ) { 0504 return; 0505 } 0506 0507 d->m_guidanceModeEnabled = enabled; 0508 0509 if ( enabled ) { 0510 d->saveRoute( d->stateFile( "guidance.kml" ) ); 0511 0512 if ( d->m_guidanceModeWarning ) { 0513 QString text = QLatin1String("<p>") + tr("Caution: Driving instructions may be incomplete or wrong.") + 0514 QLatin1Char(' ') + tr("Road construction, weather and other unforeseen variables can result in the suggested route not to be the most expedient or safest route to your destination.") + 0515 QLatin1Char(' ') + tr("Please use common sense while navigating.") + QLatin1String("</p>") + 0516 QLatin1String("<p>") + tr("The Marble development team wishes you a pleasant and safe journey.") + QLatin1String("</p>"); 0517 QPointer<QMessageBox> messageBox = new QMessageBox(QMessageBox::Information, tr("Guidance Mode"), text, QMessageBox::Ok); 0518 QCheckBox *showAgain = new QCheckBox( tr( "Show again" ) ); 0519 showAgain->setChecked( true ); 0520 showAgain->blockSignals( true ); // otherwise it'd close the dialog 0521 messageBox->addButton( showAgain, QMessageBox::ActionRole ); 0522 const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; 0523 messageBox->resize( 380, smallScreen ? 400 : 240 ); 0524 messageBox->exec(); 0525 if ( !messageBox.isNull() ) { 0526 d->m_guidanceModeWarning = showAgain->isChecked(); 0527 } 0528 delete messageBox; 0529 } 0530 } else { 0531 d->loadRoute( d->stateFile( "guidance.kml" ) ); 0532 } 0533 0534 PositionProviderPlugin* positionProvider = d->m_positionTracking->positionProviderPlugin(); 0535 if ( !positionProvider && enabled ) { 0536 QList<const PositionProviderPlugin*> plugins = d->m_pluginManager->positionProviderPlugins(); 0537 if ( plugins.size() > 0 ) { 0538 positionProvider = plugins.first()->newInstance(); 0539 } 0540 d->m_positionTracking->setPositionProviderPlugin( positionProvider ); 0541 d->m_shutdownPositionTracking = true; 0542 } else if ( positionProvider && !enabled && d->m_shutdownPositionTracking ) { 0543 d->m_shutdownPositionTracking = false; 0544 d->m_positionTracking->setPositionProviderPlugin( nullptr ); 0545 } 0546 0547 emit guidanceModeEnabledChanged( d->m_guidanceModeEnabled ); 0548 } 0549 0550 void RoutingManagerPrivate::recalculateRoute( bool deviated ) 0551 { 0552 if ( m_guidanceModeEnabled && deviated ) { 0553 for ( int i=m_routeRequest.size()-3; i>=0; --i ) { 0554 if ( m_routeRequest.visited( i ) ) { 0555 m_routeRequest.remove( i ); 0556 } 0557 } 0558 0559 if ( m_routeRequest.size() == 2 && m_routeRequest.visited( 0 ) && !m_routeRequest.visited( 1 ) ) { 0560 m_routeRequest.setPosition( 0, m_positionTracking->currentLocation(), QObject::tr( "Current Location" ) ); 0561 q->retrieveRoute(); 0562 } else if ( m_routeRequest.size() != 0 && !m_routeRequest.visited( m_routeRequest.size()-1 ) ) { 0563 m_routeRequest.insert( 0, m_positionTracking->currentLocation(), QObject::tr( "Current Location" ) ); 0564 q->retrieveRoute(); 0565 } 0566 } 0567 } 0568 0569 void RoutingManager::reverseRoute() 0570 { 0571 d->m_routeRequest.reverse(); 0572 retrieveRoute(); 0573 } 0574 0575 void RoutingManager::clearRoute() 0576 { 0577 d->m_routeRequest.clear(); 0578 retrieveRoute(); 0579 } 0580 0581 void RoutingManager::setShowGuidanceModeStartupWarning( bool show ) 0582 { 0583 d->m_guidanceModeWarning = show; 0584 } 0585 0586 bool RoutingManager::showGuidanceModeStartupWarning() const 0587 { 0588 return d->m_guidanceModeWarning; 0589 } 0590 0591 void RoutingManager::setLastOpenPath( const QString &path ) 0592 { 0593 d->m_lastOpenPath = path; 0594 } 0595 0596 QString RoutingManager::lastOpenPath() const 0597 { 0598 return d->m_lastOpenPath; 0599 } 0600 0601 void RoutingManager::setLastSavePath( const QString &path ) 0602 { 0603 d->m_lastSavePath = path; 0604 } 0605 0606 QString RoutingManager::lastSavePath() const 0607 { 0608 return d->m_lastSavePath; 0609 } 0610 0611 void RoutingManager::setRouteColorStandard( const QColor& color ) 0612 { 0613 d->m_routeColorStandard = color; 0614 } 0615 0616 QColor RoutingManager::routeColorStandard() const 0617 { 0618 return d->m_routeColorStandard; 0619 } 0620 0621 void RoutingManager::setRouteColorHighlighted( const QColor& color ) 0622 { 0623 d->m_routeColorHighlighted = color; 0624 } 0625 0626 QColor RoutingManager::routeColorHighlighted() const 0627 { 0628 return d->m_routeColorHighlighted; 0629 } 0630 0631 void RoutingManager::setRouteColorAlternative( const QColor& color ) 0632 { 0633 d->m_routeColorAlternative = color; 0634 } 0635 0636 QColor RoutingManager::routeColorAlternative() const 0637 { 0638 return d->m_routeColorAlternative; 0639 } 0640 0641 bool RoutingManager::guidanceModeEnabled() const 0642 { 0643 return d->m_guidanceModeEnabled; 0644 } 0645 0646 } // namespace Marble 0647 0648 #include "moc_RoutingManager.cpp"