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"