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 "GeoDataData.h"
10#include "GeoDataDocument.h"
11#include "GeoDataExtendedData.h"
12#include "GeoDataFolder.h"
13#include "GeoDataParser.h"
14#include "GeoDataPlacemark.h"
15#include "GeoDataTreeModel.h"
16#include "GeoWriter.h"
17#include "MarbleColors.h"
18#include "MarbleDebug.h"
19#include "MarbleDirs.h"
20#include "MarbleModel.h"
21#include "PluginManager.h"
22#include "PositionProviderPlugin.h"
23#include "PositionTracking.h"
24#include "Route.h"
25#include "RouteRequest.h"
26#include "RoutingModel.h"
27#include "RoutingProfilesModel.h"
28#include "RoutingRunnerManager.h"
29#include "RoutingRunnerPlugin.h"
30#include <KmlElementDictionary.h>
31
32#include <QCheckBox>
33#include <QFile>
34#include <QMessageBox>
35#include <QMutexLocker>
36
37namespace Marble
38{
39
40class RoutingManagerPrivate
41{
42public:
43 RoutingManager *const 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 = QStringLiteral("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, QList<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 auto result = new GeoDataFolder;
131
132 result->setName(QStringLiteral("Route Request"));
133
134 for (int i = 0; i < request.size(); ++i) {
135 auto 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 = QStringLiteral("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(QString::fromLatin1(kml::kmlTag_nameSpaceOgc22));
164
165 QMutexLocker locker(&m_fileMutex);
166 QFile file(filename);
167 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
168 mDebug() << "Cannot write to " << file.fileName();
169 return;
170 }
171
172 GeoDataDocument container;
173 container.setName(QStringLiteral("Route"));
174 GeoDataFolder *request = createFolderFromRequest(m_routeRequest);
175 if (request) {
176 container.append(request);
177 }
178
179 const GeoDataDocument *route = m_alternativeRoutesModel.currentRoute();
180 if (route) {
181 container.append(new GeoDataDocument(*route));
182 }
183
184 if (!writer.write(&file, &container)) {
185 mDebug() << "Can not write route state to " << file.fileName();
186 }
187 file.close();
188}
189
190void RoutingManagerPrivate::loadRoute(const QString &filename)
191{
192 QFile file(filename);
193 if (!file.open(QIODevice::ReadOnly)) {
194 mDebug() << "Can not read route from " << file.fileName();
195 return;
196 }
197
198 GeoDataParser parser(GeoData_KML);
199 if (!parser.read(&file)) {
200 mDebug() << "Could not parse file: " << parser.errorString();
201 return;
202 }
203
204 GeoDocument *doc = parser.releaseDocument();
205 file.close();
206 bool loaded = false;
207
208 auto container = dynamic_cast<GeoDataDocument *>(doc);
209 if (container && !container->isEmpty()) {
210 auto viaPoints = dynamic_cast<GeoDataFolder *>(&container->first());
211 if (viaPoints) {
212 loaded = true;
213 QList<GeoDataPlacemark *> placemarks = viaPoints->placemarkList();
214 for (int i = 0; i < placemarks.size(); ++i) {
215 if (i < m_routeRequest.size()) {
216 m_routeRequest[i] = *placemarks[i];
217 } else {
218 m_routeRequest.append(*placemarks[i]);
219 }
220 }
221
222 // clear unneeded via points
223 const int viaPoints_needed = placemarks.size();
224 for (int i = m_routeRequest.size(); i > viaPoints_needed; --i) {
225 m_routeRequest.remove(viaPoints_needed);
226 }
227 } else {
228 mDebug() << "Expected a GeoDataDocument with at least one child, didn't get one though";
229 }
230 }
231
232 if (container && container->size() == 2) {
233 auto route = dynamic_cast<GeoDataDocument *>(&container->last());
234 if (route) {
235 loaded = true;
236 m_alternativeRoutesModel.clear();
237 m_alternativeRoutesModel.addRoute(new GeoDataDocument(*route), AlternativeRoutesModel::Instant);
238 m_alternativeRoutesModel.setCurrentRoute(0);
239 m_state = RoutingManager::Retrieved;
240 Q_EMIT q->stateChanged(m_state);
241 Q_EMIT q->routeRetrieved(route);
242 } else {
243 mDebug() << "Expected a GeoDataDocument child, didn't get one though";
244 }
245 }
246
247 if (loaded) {
248 delete doc; // == container
249 } else {
250 mDebug() << "File " << filename << " is not a valid Marble route .kml file";
251 if (container) {
252 m_treeModel->addDocument(container);
253 }
254 }
255}
256
257RoutingManager::RoutingManager(MarbleModel *marbleModel, QObject *parent)
258 : QObject(parent)
259 , d(new RoutingManagerPrivate(marbleModel, this))
260{
261 connect(&d->m_runnerManager, SIGNAL(routeRetrieved(GeoDataDocument *)), this, SLOT(addRoute(GeoDataDocument *)));
262 connect(&d->m_runnerManager, SIGNAL(routingFinished()), this, SLOT(routingFinished()));
263 connect(&d->m_alternativeRoutesModel, SIGNAL(currentRouteChanged(const GeoDataDocument *)), this, SLOT(setCurrentRoute(const GeoDataDocument *)));
264 connect(&d->m_routingModel, SIGNAL(deviatedFromRoute(bool)), this, SLOT(recalculateRoute(bool)));
265}
266
268{
269 delete d;
270}
271
272RoutingProfilesModel *RoutingManager::profilesModel()
273{
274 return &d->m_profilesModel;
275}
276
278{
279 return &d->m_routingModel;
280}
281
282const RoutingModel *RoutingManager::routingModel() const
283{
284 return &d->m_routingModel;
285}
286
288{
289 return &d->m_routeRequest;
290}
291
292RoutingManager::State RoutingManager::state() const
293{
294 return d->m_state;
295}
296
298{
299 d->m_haveRoute = false;
300
301 int realSize = 0;
302 for (int i = 0; i < d->m_routeRequest.size(); ++i) {
303 // Sort out dummy targets
304 if (d->m_routeRequest.at(i).isValid()) {
305 ++realSize;
306 }
307 }
308
309 d->m_alternativeRoutesModel.newRequest(&d->m_routeRequest);
310 if (realSize > 1) {
311 d->m_state = RoutingManager::Downloading;
312 d->m_runnerManager.retrieveRoute(&d->m_routeRequest);
313 } else {
314 d->m_routingModel.clear();
315 d->m_state = RoutingManager::Retrieved;
316 }
317 Q_EMIT stateChanged(d->m_state);
318}
319
320void RoutingManagerPrivate::addRoute(GeoDataDocument *route)
321{
322 if (route) {
323 m_alternativeRoutesModel.addRoute(route);
324 }
325
326 if (!m_haveRoute) {
327 m_haveRoute = route != nullptr;
328 }
329
330 Q_EMIT q->routeRetrieved(route);
331}
332
333void RoutingManagerPrivate::routingFinished()
334{
335 m_state = RoutingManager::Retrieved;
336 Q_EMIT q->stateChanged(m_state);
337}
338
339void RoutingManagerPrivate::setCurrentRoute(const GeoDataDocument *document)
340{
341 QList<RouteSegment> segments;
342 RouteSegment outline;
343
344 if (document != nullptr) {
345 const auto folders = document->folderList();
346 for (const auto folder : folders) {
347 for (const auto placemark : folder->placemarkList()) {
348 importPlacemark(outline, segments, placemark);
349 }
350 }
351
352 for (const auto placemark : document->placemarkList()) {
353 importPlacemark(outline, segments, placemark);
354 }
355 }
356
357 if (segments.isEmpty()) {
358 segments << outline;
359 }
360
361 // Map via points onto segments
362 if (m_routeRequest.size() > 1 && segments.size() > 1) {
363 int index = 0;
364 for (int j = 0; j < m_routeRequest.size(); ++j) {
365 QPair<int, qreal> minimum(-1, -1.0);
366 int viaIndex = -1;
367 for (int i = index; i < segments.size(); ++i) {
368 const RouteSegment &segment = segments[i];
369 GeoDataCoordinates closest;
370 const qreal distance = segment.distanceTo(m_routeRequest.at(j), closest, closest);
371 if (minimum.first < 0 || distance < minimum.second) {
372 minimum.first = i;
373 minimum.second = distance;
374 viaIndex = j;
375 }
376 }
377
378 if (minimum.first >= 0) {
379 index = minimum.first;
380 Maneuver viaPoint = segments[minimum.first].maneuver();
381 viaPoint.setWaypoint(m_routeRequest.at(viaIndex), viaIndex);
382 segments[minimum.first].setManeuver(viaPoint);
383 }
384 }
385 }
386
387 Route route;
388
389 if (!segments.isEmpty()) {
390 for (const RouteSegment &segment : std::as_const(segments)) {
391 route.addRouteSegment(segment);
392 }
393 }
394
395 m_routingModel.setRoute(route);
396}
397
398void RoutingManagerPrivate::importPlacemark(RouteSegment &outline, QList<RouteSegment> &segments, const GeoDataPlacemark *placemark)
399{
400 const GeoDataGeometry *geometry = placemark->geometry();
401 const auto lineString = dynamic_cast<const GeoDataLineString *>(geometry);
402 QStringList blacklist = QStringList() << QString() << QStringLiteral("Route") << QStringLiteral("Tessellated");
403 RouteSegment segment;
404 bool isOutline = true;
405 if (!blacklist.contains(placemark->name())) {
406 if (lineString) {
407 Maneuver maneuver;
408 maneuver.setInstructionText(placemark->name());
409 maneuver.setPosition(lineString->at(0));
410
411 if (placemark->extendedData().contains(QStringLiteral("turnType"))) {
412 QVariant turnType = placemark->extendedData().value(QStringLiteral("turnType")).value();
413 // The enum value is converted to/from an int in the QVariant
414 // because only a limited set of data types can be serialized with QVariant's
415 // toString() method (which is used to serialize <ExtendedData>/<Data> values)
416 maneuver.setDirection(Maneuver::Direction(turnType.toInt()));
417 }
418
419 if (placemark->extendedData().contains(QStringLiteral("roadName"))) {
420 QVariant roadName = placemark->extendedData().value(QStringLiteral("roadName")).value();
421 maneuver.setRoadName(roadName.toString());
422 }
423
424 segment.setManeuver(maneuver);
425 isOutline = false;
426 }
427 }
428
429 if (lineString) {
430 segment.setPath(*lineString);
431
432 if (isOutline) {
433 outline = segment;
434 } else {
435 segments.push_back(segment);
436 }
437 }
438}
439
441{
442 return &d->m_alternativeRoutesModel;
443}
444
446{
447 d->saveRoute(d->stateFile());
448}
449
450void RoutingManager::saveRoute(const QString &filename) const
451{
452 d->saveRoute(filename);
453}
454
456{
457 d->loadRoute(filename);
458}
459
460RoutingProfile RoutingManager::defaultProfile(RoutingProfile::TransportType transportType) const
461{
462 RoutingProfile profile;
463 RoutingProfilesModel::ProfileTemplate tpl = RoutingProfilesModel::CarFastestTemplate;
464 switch (transportType) {
465 case RoutingProfile::Motorcar:
466 tpl = RoutingProfilesModel::CarFastestTemplate;
467 profile.setName(QStringLiteral("Motorcar"));
468 profile.setTransportType(RoutingProfile::Motorcar);
469 break;
470 case RoutingProfile::Bicycle:
471 tpl = RoutingProfilesModel::BicycleTemplate;
472 profile.setName(QStringLiteral("Bicycle"));
473 profile.setTransportType(RoutingProfile::Bicycle);
474 break;
475 case RoutingProfile::Pedestrian:
476 tpl = RoutingProfilesModel::PedestrianTemplate;
477 profile.setName(QStringLiteral("Pedestrian"));
478 profile.setTransportType(RoutingProfile::Pedestrian);
479 break;
480 }
481
482 for (RoutingRunnerPlugin *plugin : d->m_pluginManager->routingRunnerPlugins()) {
483 if (plugin->supportsTemplate(tpl)) {
484 profile.pluginSettings()[plugin->nameId()] = plugin->templateSettings(tpl);
485 }
486 }
487
488 return profile;
489}
490
492{
493 d->loadRoute(d->stateFile());
494}
495
497{
498 if (d->m_guidanceModeEnabled == enabled) {
499 return;
500 }
501
502 d->m_guidanceModeEnabled = enabled;
503
504 if (enabled) {
505 d->saveRoute(d->stateFile(QStringLiteral("guidance.kml")));
506
507 if (d->m_guidanceModeWarning) {
508 QString text = QLatin1StringView("<p>") + tr("Caution: Driving instructions may be incomplete or wrong.") + QLatin1Char(' ')
509 + tr("Road construction, weather and other unforeseen variables can result in the suggested route not to be the most expedient or safest route "
510 "to your destination.")
511 + QLatin1Char(' ') + tr("Please use common sense while navigating.") + QLatin1StringView("</p>") + QLatin1StringView("<p>")
512 + tr("The Marble development team wishes you a pleasant and safe journey.") + QLatin1StringView("</p>");
513 QPointer<QMessageBox> messageBox = new QMessageBox(QMessageBox::Information, tr("Guidance Mode"), text, QMessageBox::Ok);
514 auto showAgain = new QCheckBox(tr("Show again"));
515 showAgain->setChecked(true);
516 showAgain->blockSignals(true); // otherwise it'd close the dialog
517 messageBox->addButton(showAgain, QMessageBox::ActionRole);
518 const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
519 messageBox->resize(380, smallScreen ? 400 : 240);
520 messageBox->exec();
521 if (!messageBox.isNull()) {
522 d->m_guidanceModeWarning = showAgain->isChecked();
523 }
524 delete messageBox;
525 }
526 } else {
527 d->loadRoute(d->stateFile(QStringLiteral("guidance.kml")));
528 }
529
530 PositionProviderPlugin *positionProvider = d->m_positionTracking->positionProviderPlugin();
531 if (!positionProvider && enabled) {
532 const QList<const PositionProviderPlugin *> plugins = d->m_pluginManager->positionProviderPlugins();
533 if (!plugins.isEmpty()) {
534 positionProvider = plugins.first()->newInstance();
535 }
536 d->m_positionTracking->setPositionProviderPlugin(positionProvider);
537 d->m_shutdownPositionTracking = true;
538 } else if (positionProvider && !enabled && d->m_shutdownPositionTracking) {
539 d->m_shutdownPositionTracking = false;
540 d->m_positionTracking->setPositionProviderPlugin(nullptr);
541 }
542
543 Q_EMIT guidanceModeEnabledChanged(d->m_guidanceModeEnabled);
544}
545
546void RoutingManagerPrivate::recalculateRoute(bool deviated)
547{
548 if (m_guidanceModeEnabled && deviated) {
549 for (int i = m_routeRequest.size() - 3; i >= 0; --i) {
550 if (m_routeRequest.visited(i)) {
551 m_routeRequest.remove(i);
552 }
553 }
554
555 if (m_routeRequest.size() == 2 && m_routeRequest.visited(0) && !m_routeRequest.visited(1)) {
556 m_routeRequest.setPosition(0, m_positionTracking->currentLocation(), QObject::tr("Current Location"));
557 q->retrieveRoute();
558 } else if (m_routeRequest.size() != 0 && !m_routeRequest.visited(m_routeRequest.size() - 1)) {
559 m_routeRequest.insert(0, m_positionTracking->currentLocation(), QObject::tr("Current Location"));
560 q->retrieveRoute();
561 }
562 }
563}
564
566{
567 d->m_routeRequest.reverse();
569}
570
572{
573 d->m_routeRequest.clear();
575}
576
578{
579 d->m_guidanceModeWarning = show;
580}
581
583{
584 return d->m_guidanceModeWarning;
585}
586
588{
589 d->m_lastOpenPath = path;
590}
591
593{
594 return d->m_lastOpenPath;
595}
596
598{
599 d->m_lastSavePath = path;
600}
601
603{
604 return d->m_lastSavePath;
605}
606
608{
609 d->m_routeColorStandard = color;
610}
611
613{
614 return d->m_routeColorStandard;
615}
616
618{
619 d->m_routeColorHighlighted = color;
620}
621
623{
624 return d->m_routeColorHighlighted;
625}
626
628{
629 d->m_routeColorAlternative = color;
630}
631
633{
634 return d->m_routeColorAlternative;
635}
636
637bool RoutingManager::guidanceModeEnabled() const
638{
639 return d->m_guidanceModeEnabled;
640}
641
642} // namespace Marble
643
644#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:84
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)
T & first()
bool isEmpty() const const
void push_back(parameter_type value)
qsizetype size() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString fromLatin1(QByteArrayView str)
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 Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.