1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
4 //
5 
6 #include "RoutingManager.h"
7 
8 #include "AlternativeRoutesModel.h"
9 #include "MarbleModel.h"
10 #include "RouteRequest.h"
11 #include "RoutingModel.h"
12 #include "RoutingProfilesModel.h"
13 #include "RoutingRunnerPlugin.h"
14 #include "GeoWriter.h"
15 #include "GeoDataDocument.h"
16 #include "GeoDataExtendedData.h"
17 #include "GeoDataData.h"
18 #include "GeoDataFolder.h"
19 #include "GeoDataParser.h"
20 #include "GeoDataPlacemark.h"
21 #include "GeoDataTreeModel.h"
22 #include "MarbleColors.h"
23 #include "MarbleDirs.h"
24 #include "MarbleDebug.h"
25 #include "PositionTracking.h"
26 #include "PluginManager.h"
27 #include "PositionProviderPlugin.h"
28 #include "Route.h"
29 #include "RoutingRunnerManager.h"
30 #include <KmlElementDictionary.h>
31 
32 #include <QFile>
33 #include <QMessageBox>
34 #include <QCheckBox>
35 #include <QMutexLocker>
36 
37 namespace Marble
38 {
39 
40 class RoutingManagerPrivate
41 {
42 public:
43     RoutingManager* q;
44 
45     RouteRequest m_routeRequest;
46 
47     RoutingModel m_routingModel;
48 
49     RoutingProfilesModel m_profilesModel;
50 
51     RoutingManager::State m_state;
52 
53     const PluginManager *const m_pluginManager;
54 
55     GeoDataTreeModel *const m_treeModel;
56 
57     PositionTracking *const m_positionTracking;
58 
59     AlternativeRoutesModel m_alternativeRoutesModel;
60 
61     RoutingRunnerManager m_runnerManager;
62 
63     bool m_haveRoute;
64 
65     bool m_guidanceModeEnabled;
66 
67     QMutex m_fileMutex;
68 
69     bool m_shutdownPositionTracking;
70 
71     bool m_guidanceModeWarning;
72 
73     QString m_lastOpenPath;
74 
75     QString m_lastSavePath;
76 
77     QColor m_routeColorStandard;
78 
79     QColor m_routeColorHighlighted;
80 
81     QColor m_routeColorAlternative;
82 
83     RoutingManagerPrivate(MarbleModel *marbleModel, RoutingManager *manager);
84 
85     static GeoDataFolder *createFolderFromRequest(const RouteRequest &request);
86 
87     static QString stateFile( const QString &name = QString( "route.kml" ) );
88 
89     void saveRoute( const QString &filename );
90 
91     void loadRoute( const QString &filename );
92 
93     void addRoute( GeoDataDocument* route );
94 
95     void routingFinished();
96 
97     void setCurrentRoute(const GeoDataDocument *route);
98 
99     void recalculateRoute( bool deviated );
100 
101     static void importPlacemark( RouteSegment &outline, QVector<RouteSegment> &segments, const GeoDataPlacemark *placemark );
102 };
103 
RoutingManagerPrivate(MarbleModel * model,RoutingManager * manager)104 RoutingManagerPrivate::RoutingManagerPrivate(MarbleModel *model, RoutingManager *manager) :
105         q( manager ),
106         m_routeRequest( manager ),
107         m_routingModel(&m_routeRequest, model->positionTracking(), manager),
108         m_profilesModel( model->pluginManager() ),
109         m_state( RoutingManager::Retrieved ),
110         m_pluginManager( model->pluginManager() ),
111         m_treeModel( model->treeModel() ),
112         m_positionTracking( model->positionTracking() ),
113         m_alternativeRoutesModel(manager),
114         m_runnerManager(model, manager),
115         m_haveRoute( false ),
116         m_guidanceModeEnabled( false ),
117         m_shutdownPositionTracking( false ),
118         m_guidanceModeWarning( true ),
119         m_routeColorStandard( Oxygen::skyBlue4 ),
120         m_routeColorHighlighted( Oxygen::skyBlue1 ),
121         m_routeColorAlternative( Oxygen::aluminumGray4 )
122 {
123     m_routeColorStandard.setAlpha( 200 );
124     m_routeColorHighlighted.setAlpha( 200 );
125     m_routeColorAlternative.setAlpha( 200 );
126 }
127 
createFolderFromRequest(const RouteRequest & request)128 GeoDataFolder *RoutingManagerPrivate::createFolderFromRequest(const RouteRequest &request)
129 {
130     GeoDataFolder* result = new GeoDataFolder;
131 
132     result->setName(QStringLiteral("Route Request"));
133 
134     for (int i = 0; i < request.size(); ++i) {
135         GeoDataPlacemark *placemark = new GeoDataPlacemark(request[i]);
136         result->append( placemark );
137     }
138 
139     return result;
140 }
141 
stateFile(const QString & name)142 QString RoutingManagerPrivate::stateFile( const QString &name)
143 {
144     QString const subdir = "routing";
145     QDir dir( MarbleDirs::localPath() );
146     if ( !dir.exists( subdir ) ) {
147         if ( !dir.mkdir( subdir ) ) {
148             mDebug() << "Unable to create dir " << dir.absoluteFilePath( subdir );
149             return dir.absolutePath();
150         }
151     }
152 
153     if ( !dir.cd( subdir ) ) {
154         mDebug() << "Cannot change into " << dir.absoluteFilePath( subdir );
155     }
156 
157     return dir.absoluteFilePath( name );
158 }
159 
saveRoute(const QString & filename)160 void RoutingManagerPrivate::saveRoute(const QString &filename)
161 {
162     GeoWriter writer;
163     writer.setDocumentType( kml::kmlTag_nameSpaceOgc22 );
164 
165     QMutexLocker locker( &m_fileMutex );
166     QFile file( filename );
167     if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
168     {
169         mDebug() << "Cannot write to " << file.fileName();
170         return;
171     }
172 
173     GeoDataDocument container;
174     container.setName(QStringLiteral("Route"));
175     GeoDataFolder *request = createFolderFromRequest(m_routeRequest);
176     if ( request ) {
177         container.append( request );
178     }
179 
180     const GeoDataDocument *route = m_alternativeRoutesModel.currentRoute();
181     if ( route ) {
182         container.append( new GeoDataDocument( *route ) );
183     }
184 
185     if ( !writer.write( &file, &container ) ) {
186         mDebug() << "Can not write route state to " << file.fileName();
187     }
188     file.close();
189 }
190 
loadRoute(const QString & filename)191 void RoutingManagerPrivate::loadRoute(const QString &filename)
192 {
193     QFile file( filename );
194     if ( !file.open( QIODevice::ReadOnly ) ) {
195         mDebug() << "Can not read route from " << file.fileName();
196         return;
197     }
198 
199     GeoDataParser parser( GeoData_KML );
200     if ( !parser.read( &file ) ) {
201         mDebug() << "Could not parse file: " << parser.errorString();
202         return;
203     }
204 
205     GeoDocument *doc = parser.releaseDocument();
206     file.close();
207     bool loaded = false;
208 
209     GeoDataDocument* container = dynamic_cast<GeoDataDocument*>( doc );
210     if (container && !container->isEmpty()) {
211         GeoDataFolder* viaPoints = dynamic_cast<GeoDataFolder*>( &container->first() );
212         if ( viaPoints ) {
213             loaded = true;
214             QVector<GeoDataPlacemark*> placemarks = viaPoints->placemarkList();
215             for( int i=0; i<placemarks.size(); ++i ) {
216                 if ( i < m_routeRequest.size() ) {
217                     m_routeRequest[i] = *placemarks[i];
218                 } else {
219                     m_routeRequest.append( *placemarks[i] );
220                 }
221             }
222 
223             // clear unneeded via points
224             const int viaPoints_needed = placemarks.size();
225             for ( int i = m_routeRequest.size(); i > viaPoints_needed; --i ) {
226                 m_routeRequest.remove( viaPoints_needed );
227             }
228         } else {
229             mDebug() << "Expected a GeoDataDocument with at least one child, didn't get one though";
230         }
231     }
232 
233     if ( container && container->size() == 2 ) {
234         GeoDataDocument* route = dynamic_cast<GeoDataDocument*>(&container->last());
235         if ( route ) {
236             loaded = true;
237             m_alternativeRoutesModel.clear();
238             m_alternativeRoutesModel.addRoute( new GeoDataDocument(*route), AlternativeRoutesModel::Instant );
239             m_alternativeRoutesModel.setCurrentRoute( 0 );
240             m_state = RoutingManager::Retrieved;
241             emit q->stateChanged( m_state );
242             emit q->routeRetrieved( route );
243         } else {
244             mDebug() << "Expected a GeoDataDocument child, didn't get one though";
245         }
246     }
247 
248     if (loaded) {
249         delete doc; // == container
250     } else {
251         mDebug() << "File " << filename << " is not a valid Marble route .kml file";
252         if ( container ) {
253             m_treeModel->addDocument( container );
254         }
255     }
256 }
257 
RoutingManager(MarbleModel * marbleModel,QObject * parent)258 RoutingManager::RoutingManager(MarbleModel *marbleModel, QObject *parent) :
259     QObject(parent),
260     d(new RoutingManagerPrivate(marbleModel, this))
261 {
262     connect( &d->m_runnerManager, SIGNAL(routeRetrieved(GeoDataDocument*)),
263              this, SLOT(addRoute(GeoDataDocument*)) );
264     connect( &d->m_runnerManager, SIGNAL(routingFinished()),
265              this, SLOT(routingFinished()) );
266     connect(&d->m_alternativeRoutesModel, SIGNAL(currentRouteChanged(const GeoDataDocument*)),
267             this, SLOT(setCurrentRoute(const GeoDataDocument*)));
268     connect( &d->m_routingModel, SIGNAL(deviatedFromRoute(bool)),
269              this, SLOT(recalculateRoute(bool)) );
270 }
271 
~RoutingManager()272 RoutingManager::~RoutingManager()
273 {
274     delete d;
275 }
276 
profilesModel()277 RoutingProfilesModel *RoutingManager::profilesModel()
278 {
279     return &d->m_profilesModel;
280 }
281 
routingModel()282 RoutingModel *RoutingManager::routingModel()
283 {
284     return &d->m_routingModel;
285 }
286 
routingModel() const287 const RoutingModel *RoutingManager::routingModel() const
288 {
289     return &d->m_routingModel;
290 }
291 
routeRequest()292 RouteRequest* RoutingManager::routeRequest()
293 {
294     return &d->m_routeRequest;
295 }
296 
state() const297 RoutingManager::State RoutingManager::state() const
298 {
299     return d->m_state;
300 }
301 
retrieveRoute()302 void RoutingManager::retrieveRoute()
303 {
304     d->m_haveRoute = false;
305 
306     int realSize = 0;
307     for ( int i = 0; i < d->m_routeRequest.size(); ++i ) {
308         // Sort out dummy targets
309         if ( d->m_routeRequest.at( i ).isValid() ) {
310             ++realSize;
311         }
312     }
313 
314     d->m_alternativeRoutesModel.newRequest( &d->m_routeRequest );
315     if ( realSize > 1 ) {
316         d->m_state = RoutingManager::Downloading;
317         d->m_runnerManager.retrieveRoute( &d->m_routeRequest );
318     } else {
319         d->m_routingModel.clear();
320         d->m_state = RoutingManager::Retrieved;
321     }
322     emit stateChanged( d->m_state );
323 }
324 
addRoute(GeoDataDocument * route)325 void RoutingManagerPrivate::addRoute( GeoDataDocument* route )
326 {
327     if ( route ) {
328         m_alternativeRoutesModel.addRoute( route );
329     }
330 
331     if ( !m_haveRoute ) {
332         m_haveRoute = route != nullptr;
333     }
334 
335     emit q->routeRetrieved( route );
336 }
337 
routingFinished()338 void RoutingManagerPrivate::routingFinished()
339 {
340     m_state = RoutingManager::Retrieved;
341     emit q->stateChanged( m_state );
342 }
343 
setCurrentRoute(const GeoDataDocument * document)344 void RoutingManagerPrivate::setCurrentRoute(const GeoDataDocument *document)
345 {
346     QVector<RouteSegment> segments;
347     RouteSegment outline;
348 
349     if (document != nullptr) {
350         const auto folders = document->folderList();
351         for (const auto folder : folders) {
352             for (const auto placemark : folder->placemarkList()) {
353                 importPlacemark(outline, segments, placemark);
354             }
355         }
356 
357         for (const auto placemark : document->placemarkList()) {
358             importPlacemark(outline, segments, placemark);
359         }
360     }
361 
362     if ( segments.isEmpty() ) {
363         segments << outline;
364     }
365 
366     // Map via points onto segments
367     if ( m_routeRequest.size() > 1 && segments.size() > 1 ) {
368         int index = 0;
369         for ( int j = 0; j < m_routeRequest.size(); ++j ) {
370             QPair<int, qreal> minimum( -1, -1.0 );
371             int viaIndex = -1;
372             for ( int i = index; i < segments.size(); ++i ) {
373                 const RouteSegment &segment = segments[i];
374                 GeoDataCoordinates closest;
375                 const qreal distance = segment.distanceTo( m_routeRequest.at( j ), closest, closest );
376                 if ( minimum.first < 0 || distance < minimum.second ) {
377                     minimum.first = i;
378                     minimum.second = distance;
379                     viaIndex = j;
380                 }
381             }
382 
383             if ( minimum.first >= 0 ) {
384                 index = minimum.first;
385                 Maneuver viaPoint = segments[ minimum.first ].maneuver();
386                 viaPoint.setWaypoint( m_routeRequest.at( viaIndex ), viaIndex );
387                 segments[ minimum.first ].setManeuver( viaPoint );
388             }
389         }
390     }
391 
392     Route route;
393 
394     if ( segments.size() > 0 ) {
395         for( const RouteSegment &segment: segments ) {
396             route.addRouteSegment( segment );
397         }
398     }
399 
400     m_routingModel.setRoute( route );
401 }
402 
importPlacemark(RouteSegment & outline,QVector<RouteSegment> & segments,const GeoDataPlacemark * placemark)403 void RoutingManagerPrivate::importPlacemark( RouteSegment &outline, QVector<RouteSegment> &segments, const GeoDataPlacemark *placemark )
404 {
405     const GeoDataGeometry* geometry = placemark->geometry();
406     const GeoDataLineString* lineString = dynamic_cast<const GeoDataLineString*>( geometry );
407     QStringList blacklist = QStringList() << "" << "Route" << "Tessellated";
408     RouteSegment segment;
409     bool isOutline = true;
410     if ( !blacklist.contains( placemark->name() ) ) {
411         if( lineString ) {
412             Maneuver maneuver;
413             maneuver.setInstructionText( placemark->name() );
414             maneuver.setPosition( lineString->at( 0 ) );
415 
416             if (placemark->extendedData().contains(QStringLiteral("turnType"))) {
417                 QVariant turnType = placemark->extendedData().value(QStringLiteral("turnType")).value();
418                 // The enum value is converted to/from an int in the QVariant
419                 // because only a limited set of data types can be serialized with QVariant's
420                 // toString() method (which is used to serialize <ExtendedData>/<Data> values)
421                 maneuver.setDirection( Maneuver::Direction( turnType.toInt() ) );
422             }
423 
424             if (placemark->extendedData().contains(QStringLiteral("roadName"))) {
425                 QVariant roadName = placemark->extendedData().value(QStringLiteral("roadName")).value();
426                 maneuver.setRoadName( roadName.toString() );
427             }
428 
429             segment.setManeuver( maneuver );
430             isOutline = false;
431         }
432     }
433 
434     if ( lineString ) {
435         segment.setPath( *lineString );
436 
437         if ( isOutline ) {
438             outline = segment;
439         } else {
440             segments.push_back( segment );
441         }
442     }
443 }
444 
alternativeRoutesModel()445 AlternativeRoutesModel* RoutingManager::alternativeRoutesModel()
446 {
447     return &d->m_alternativeRoutesModel;
448 }
449 
writeSettings() const450 void RoutingManager::writeSettings() const
451 {
452     d->saveRoute( d->stateFile() );
453 }
454 
saveRoute(const QString & filename) const455 void RoutingManager::saveRoute( const QString &filename ) const
456 {
457     d->saveRoute( filename );
458 }
459 
loadRoute(const QString & filename)460 void RoutingManager::loadRoute( const QString &filename )
461 {
462     d->loadRoute( filename );
463 }
464 
defaultProfile(RoutingProfile::TransportType transportType) const465 RoutingProfile RoutingManager::defaultProfile( RoutingProfile::TransportType transportType ) const
466 {
467     RoutingProfile profile;
468     RoutingProfilesModel::ProfileTemplate tpl = RoutingProfilesModel::CarFastestTemplate;
469     switch ( transportType ) {
470     case RoutingProfile::Motorcar:
471         tpl = RoutingProfilesModel::CarFastestTemplate;
472         profile.setName(QStringLiteral("Motorcar"));
473         profile.setTransportType( RoutingProfile::Motorcar );
474         break;
475     case RoutingProfile::Bicycle:
476         tpl = RoutingProfilesModel::BicycleTemplate;
477         profile.setName(QStringLiteral("Bicycle"));
478         profile.setTransportType( RoutingProfile::Bicycle );
479         break;
480     case RoutingProfile::Pedestrian:
481         tpl = RoutingProfilesModel::PedestrianTemplate;
482         profile.setName(QStringLiteral("Pedestrian"));
483         profile.setTransportType( RoutingProfile::Pedestrian );
484         break;
485     }
486 
487     for( RoutingRunnerPlugin* plugin: d->m_pluginManager->routingRunnerPlugins() ) {
488         if ( plugin->supportsTemplate( tpl ) ) {
489             profile.pluginSettings()[plugin->nameId()] = plugin->templateSettings( tpl );
490         }
491     }
492 
493     return profile;
494 }
495 
readSettings()496 void RoutingManager::readSettings()
497 {
498     d->loadRoute( d->stateFile() );
499 }
500 
setGuidanceModeEnabled(bool enabled)501 void RoutingManager::setGuidanceModeEnabled( bool enabled )
502 {
503     if ( d->m_guidanceModeEnabled == enabled ) {
504         return;
505     }
506 
507     d->m_guidanceModeEnabled = enabled;
508 
509     if ( enabled ) {
510         d->saveRoute( d->stateFile( "guidance.kml" ) );
511 
512         if ( d->m_guidanceModeWarning ) {
513             QString text = QLatin1String("<p>") + tr("Caution: Driving instructions may be incomplete or wrong.") +
514                 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.") +
515                 QLatin1Char(' ') + tr("Please use common sense while navigating.") + QLatin1String("</p>") +
516                 QLatin1String("<p>") + tr("The Marble development team wishes you a pleasant and safe journey.") + QLatin1String("</p>");
517             QPointer<QMessageBox> messageBox = new QMessageBox(QMessageBox::Information, tr("Guidance Mode"), text, QMessageBox::Ok);
518             QCheckBox *showAgain = new QCheckBox( tr( "Show again" ) );
519             showAgain->setChecked( true );
520             showAgain->blockSignals( true ); // otherwise it'd close the dialog
521             messageBox->addButton( showAgain, QMessageBox::ActionRole );
522             const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
523             messageBox->resize( 380, smallScreen ? 400 : 240 );
524             messageBox->exec();
525             if ( !messageBox.isNull() ) {
526                 d->m_guidanceModeWarning = showAgain->isChecked();
527             }
528             delete messageBox;
529         }
530     } else {
531         d->loadRoute( d->stateFile( "guidance.kml" ) );
532     }
533 
534     PositionProviderPlugin* positionProvider = d->m_positionTracking->positionProviderPlugin();
535     if ( !positionProvider && enabled ) {
536         QList<const PositionProviderPlugin*> plugins = d->m_pluginManager->positionProviderPlugins();
537         if ( plugins.size() > 0 ) {
538             positionProvider = plugins.first()->newInstance();
539         }
540         d->m_positionTracking->setPositionProviderPlugin( positionProvider );
541         d->m_shutdownPositionTracking = true;
542     } else if ( positionProvider && !enabled && d->m_shutdownPositionTracking ) {
543         d->m_shutdownPositionTracking = false;
544         d->m_positionTracking->setPositionProviderPlugin( nullptr );
545     }
546 
547     emit guidanceModeEnabledChanged( d->m_guidanceModeEnabled );
548 }
549 
recalculateRoute(bool deviated)550 void RoutingManagerPrivate::recalculateRoute( bool deviated )
551 {
552     if ( m_guidanceModeEnabled && deviated ) {
553         for ( int i=m_routeRequest.size()-3; i>=0; --i ) {
554             if ( m_routeRequest.visited( i ) ) {
555                 m_routeRequest.remove( i );
556             }
557         }
558 
559         if ( m_routeRequest.size() == 2 && m_routeRequest.visited( 0 ) && !m_routeRequest.visited( 1 ) ) {
560             m_routeRequest.setPosition( 0, m_positionTracking->currentLocation(), QObject::tr( "Current Location" ) );
561             q->retrieveRoute();
562         } else if ( m_routeRequest.size() != 0 && !m_routeRequest.visited( m_routeRequest.size()-1 ) ) {
563             m_routeRequest.insert( 0, m_positionTracking->currentLocation(), QObject::tr( "Current Location" ) );
564             q->retrieveRoute();
565         }
566     }
567 }
568 
reverseRoute()569 void RoutingManager::reverseRoute()
570 {
571     d->m_routeRequest.reverse();
572     retrieveRoute();
573 }
574 
clearRoute()575 void RoutingManager::clearRoute()
576 {
577     d->m_routeRequest.clear();
578     retrieveRoute();
579 }
580 
setShowGuidanceModeStartupWarning(bool show)581 void RoutingManager::setShowGuidanceModeStartupWarning( bool show )
582 {
583     d->m_guidanceModeWarning = show;
584 }
585 
showGuidanceModeStartupWarning() const586 bool RoutingManager::showGuidanceModeStartupWarning() const
587 {
588     return d->m_guidanceModeWarning;
589 }
590 
setLastOpenPath(const QString & path)591 void RoutingManager::setLastOpenPath( const QString &path )
592 {
593     d->m_lastOpenPath = path;
594 }
595 
lastOpenPath() const596 QString RoutingManager::lastOpenPath() const
597 {
598     return d->m_lastOpenPath;
599 }
600 
setLastSavePath(const QString & path)601 void RoutingManager::setLastSavePath( const QString &path )
602 {
603     d->m_lastSavePath = path;
604 }
605 
lastSavePath() const606 QString RoutingManager::lastSavePath() const
607 {
608     return d->m_lastSavePath;
609 }
610 
setRouteColorStandard(const QColor & color)611 void RoutingManager::setRouteColorStandard( const QColor& color )
612 {
613     d->m_routeColorStandard = color;
614 }
615 
routeColorStandard() const616 QColor RoutingManager::routeColorStandard() const
617 {
618     return d->m_routeColorStandard;
619 }
620 
setRouteColorHighlighted(const QColor & color)621 void RoutingManager::setRouteColorHighlighted( const QColor& color )
622 {
623     d->m_routeColorHighlighted = color;
624 }
625 
routeColorHighlighted() const626 QColor RoutingManager::routeColorHighlighted() const
627 {
628     return d->m_routeColorHighlighted;
629 }
630 
setRouteColorAlternative(const QColor & color)631 void RoutingManager::setRouteColorAlternative( const QColor& color )
632 {
633     d->m_routeColorAlternative = color;
634 }
635 
routeColorAlternative() const636 QColor RoutingManager::routeColorAlternative() const
637 {
638     return d->m_routeColorAlternative;
639 }
640 
guidanceModeEnabled() const641 bool RoutingManager::guidanceModeEnabled() const
642 {
643     return d->m_guidanceModeEnabled;
644 }
645 
646 } // namespace Marble
647 
648 #include "moc_RoutingManager.cpp"
649