Marble

RoutingManager.cpp
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
37namespace Marble
38{
39
40class RoutingManagerPrivate
41{
42public:
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
104RoutingManagerPrivate::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
128GeoDataFolder *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
142QString 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
160void 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
191void 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
258RoutingManager::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
273{
274 delete d;
275}
276
277RoutingProfilesModel *RoutingManager::profilesModel()
278{
279 return &d->m_profilesModel;
280}
281
283{
284 return &d->m_routingModel;
285}
286
287const RoutingModel *RoutingManager::routingModel() const
288{
289 return &d->m_routingModel;
290}
291
293{
294 return &d->m_routeRequest;
295}
296
297RoutingManager::State RoutingManager::state() const
298{
299 return d->m_state;
300}
301
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
325void 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
338void RoutingManagerPrivate::routingFinished()
339{
340 m_state = RoutingManager::Retrieved;
341 emit q->stateChanged( m_state );
342}
343
344void 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
403void 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
446{
447 return &d->m_alternativeRoutesModel;
448}
449
451{
452 d->saveRoute( d->stateFile() );
453}
454
455void RoutingManager::saveRoute( const QString &filename ) const
456{
457 d->saveRoute( filename );
458}
459
460void RoutingManager::loadRoute( const QString &filename )
461{
462 d->loadRoute( filename );
463}
464
465RoutingProfile 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
497{
498 d->loadRoute( d->stateFile() );
499}
500
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 ) {
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
550void 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
570{
571 d->m_routeRequest.reverse();
573}
574
576{
577 d->m_routeRequest.clear();
579}
580
582{
583 d->m_guidanceModeWarning = show;
584}
585
587{
588 return d->m_guidanceModeWarning;
589}
590
592{
593 d->m_lastOpenPath = path;
594}
595
597{
598 return d->m_lastOpenPath;
599}
600
602{
603 d->m_lastSavePath = path;
604}
605
607{
608 return d->m_lastSavePath;
609}
610
612{
613 d->m_routeColorStandard = color;
614}
615
617{
618 return d->m_routeColorStandard;
619}
620
622{
623 d->m_routeColorHighlighted = color;
624}
625
627{
628 return d->m_routeColorHighlighted;
629}
630
632{
633 d->m_routeColorAlternative = color;
634}
635
637{
638 return d->m_routeColorAlternative;
639}
640
641bool RoutingManager::guidanceModeEnabled() const
642{
643 return d->m_guidanceModeEnabled;
644}
645
646} // namespace Marble
647
648#include "moc_RoutingManager.cpp"
This file contains the headers for MarbleModel.
A container for Features, Styles and in the future Schemas.
The data model (not based on QAbstractModel) for a MarbleWidget.
Definition MarbleModel.h:87
QList< const PositionProviderPlugin * > positionProviderPlugins() const
Returns all available PositionProviderPlugins.
QList< RoutingRunnerPlugin * > routingRunnerPlugins() const
Returns all routing runner plugins.
The abstract class that provides position information.
Points to be included in a route.
int size() const
Number of points in the route.
void clear()
Remove all elements.
GeoDataCoordinates at(int index) const
Accessor for the n-th position.
QString lastOpenPath() const
Return last directory the user opened a route from.
void setGuidanceModeEnabled(bool enabled)
Toggle turn by turn navigation mode.
void clearRoute()
Clear all via points.
~RoutingManager() override
Destructor.
RoutingProfile defaultProfile(RoutingProfile::TransportType transportType) const
Generates a routing profile with default settings for the given transport type.
void loadRoute(const QString &filename)
Opens the given filename (kml format) and loads the route contained in it.
QColor routeColorStandard() const
Get color for standard route rendering.
void setRouteColorAlternative(const QColor &color)
Set color for alternative route rendering.
RoutingModel * routingModel()
Provides access to the routing model which contains a list of routing instructions describing steps t...
void setShowGuidanceModeStartupWarning(bool show)
Set whether a warning message should be shown to the user before starting guidance mode.
RouteRequest * routeRequest()
Returns the current route request.
RoutingProfilesModel * profilesModel()
Provides access to the model which contains all possible routing profiles.
AlternativeRoutesModel * alternativeRoutesModel()
Provides access to the model which contains a list of alternative routes.
void setRouteColorStandard(const QColor &color)
Set color for standard route rendering.
void setRouteColorHighlighted(const QColor &color)
Set color for highlighted route rendering.
void stateChanged(RoutingManager::State newState)
Directions and waypoints for the given route are being downloaded or have been retrieved – newState t...
bool showGuidanceModeStartupWarning() const
Returns true (default) if a warning is shown to the user when starting guidance mode.
void setLastSavePath(const QString &path)
Set last directory the user saved a route to.
void retrieveRoute()
Retrieve a route suiting the routeRequest.
void setLastOpenPath(const QString &path)
Set last directory the user opened a route from.
void saveRoute(const QString &filename) const
Saves the current route to the file with the given filename.
QColor routeColorHighlighted() const
Get color for highlighted route rendering.
QColor routeColorAlternative() const
Get color for alternative route rendering.
void reverseRoute()
Reverse the previously requested route, i.e.
void writeSettings() const
Saves the current route request and the current route to disk.
QString lastSavePath() const
Return last directory the user saved a route to.
void readSettings()
Restores a previously saved route request and route from disk, if any.
A plugin for Marble to execute a routing task.
KIOCORE_EXPORT QString dir(const QString &fileClass)
Binds a QML item to a specific geodetic location in screen coordinates.
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
void setAlpha(int alpha)
void append(QList< T > &&value)
T & first()
bool isEmpty() const const
void push_back(parameter_type value)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
int toInt(bool *ok) const const
QString toString() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.