Marble

RoutingInstruction.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 "RoutingInstruction.h"
7
8#include <QCoreApplication>
9#include <QLocale>
10#include <QStringList>
11#include <QTextStream>
12
13#include <cmath>
14
15namespace Marble
16{
17
19 : m_roadName(item.roadName())
20 , m_roadType(item.roadType())
21 , m_secondsLeft(item.secondsRemaining())
22 , m_angleToPredecessor(0.0)
23 , m_roundaboutExit(0)
24 , m_predecessor(nullptr)
25 , m_successor(nullptr)
26{
27 m_points.append(item);
28}
29
30bool RoutingInstruction::append(const RoutingWaypoint &item, int angle)
31{
32 if (m_points.size() && m_points.last().roadType() != QLatin1StringView("roundabout") && item.roadType() == QLatin1StringView("roundabout")) {
33 // Entering a roundabout. Merge with previous segment to avoid 'Enter the roundabout' instructions
34 m_points.push_back(item);
35 return true;
36 }
37
38 if (m_points.size() && m_points.last().roadType() == QLatin1StringView("roundabout") && item.roadType() != QLatin1StringView("roundabout")) {
39 // Exiting a roundabout
40 m_points.push_back(item);
41 return false;
42 }
43
44 m_points.push_back(item);
45
46 if (item.junctionType() == RoutingWaypoint::Roundabout) {
47 // Passing a roundabout exit
48 ++m_roundaboutExit;
49 return true;
50 }
51
52 if (item.roadName().isEmpty()) {
53 if (item.junctionType() == RoutingWaypoint::None) {
54 return true;
55 }
56
57 return angle >= 150 && angle <= 210;
58 } else {
59 return item.roadType() == QLatin1StringView("roundabout") || item.roadName() == roadName();
60 }
61}
62
64{
65 return m_roadName;
66}
67
69{
70 return m_roadType;
71}
72
74{
75 return m_secondsLeft;
76}
77
78void RoutingInstruction::calculateAngle()
79{
80 if (!m_predecessor) {
81 return;
82 }
83
84 int hisSize = m_predecessor->points().size();
85 int mySize = m_points.size();
86 Q_ASSERT(mySize > 0 && hisSize > 0);
87 RoutingPoint one = points().constFirst().point();
88 RoutingPoint two = m_predecessor->points().at(hisSize - 1).point();
89 qreal distance = 0;
90 for (int i = 2; i <= qMin<int>(hisSize, 20) && distance < 50.0; ++i) {
91 two = m_predecessor->points().at(hisSize - i).point();
92 m_intersectionPoints.push_front(two);
93 distance = one.distance(two);
94 }
95 qreal before = two.bearing(one);
96 m_intersectionPoints.push_back(one);
97
98 one = points().constFirst().point();
99 if (mySize == 1 && !m_successor) {
100 return;
101 } else if (mySize == 1) {
102 Q_ASSERT(!m_successor->points().isEmpty());
103 two = m_successor->points().constFirst().point();
104 } else {
105 two = points().at(1).point();
106 }
107
108 distance = 0;
109 m_intersectionPoints.push_back(one);
110 for (int i = 2; i < qMin<int>(mySize, 20) && distance < 50.0; ++i) {
111 two = points().at(i).point();
112 m_intersectionPoints.push_back(two);
113 distance = one.distance(two);
114 }
115
116 qreal after = one.bearing(two);
117 m_angleToPredecessor = after - before;
118}
119
120void RoutingInstruction::calculateTurnType()
121{
122 if (predecessor() && predecessor()->roundaboutExitNumber()) {
123 int exit = predecessor()->roundaboutExitNumber();
124 switch (exit) {
125 case 1:
126 m_turnType = RoundaboutFirstExit;
127 break;
128 case 2:
129 m_turnType = RoundaboutSecondExit;
130 break;
131 case 3:
132 m_turnType = RoundaboutThirdExit;
133 break;
134 default:
135 m_turnType = RoundaboutExit;
136 break;
137 }
138
139 return;
140 }
141
142 int angle = qRound(angleToPredecssor() * 180.0 / M_PI + 540) % 360;
143 Q_ASSERT(angle >= 0 && angle <= 360);
144
145 const int sharp = 30;
146 if (angle >= 360 - sharp || angle < sharp) {
147 m_turnType = TurnAround;
148 } else if (angle >= sharp && angle < 90 - sharp) {
149 m_turnType = SharpLeft;
150 } else if (angle >= 90 - sharp && angle < 90 + sharp) {
151 m_turnType = Left;
152 } else if (angle >= 90 + sharp && angle < 180 - sharp) {
153 m_turnType = SlightLeft;
154 } else if (angle >= 180 - sharp && angle < 180 + sharp) {
155 m_turnType = Straight;
156 } else if (angle >= 180 + sharp && angle < 270 - sharp) {
157 m_turnType = SlightRight;
158 } else if (angle >= 270 - sharp && angle < 270 + sharp) {
159 m_turnType = Right;
160 } else if (angle >= 270 + sharp && angle < 360 - sharp) {
161 m_turnType = SharpRight;
162 } else {
163 Q_ASSERT(false && "Internal error: not all angles are properly handled");
164 }
165}
166
168{
169 return m_points;
170}
171
173{
174 return m_intersectionPoints;
175}
176
178{
179 return m_angleToPredecessor;
180}
181
183{
184 return m_predecessor;
185}
186
188{
189 return m_predecessor;
190}
191
193{
194 m_predecessor = predecessor;
195 calculateAngle();
196 calculateTurnType();
197}
198
200{
201 return m_successor;
202}
203
205{
206 return m_successor;
207}
208
210{
211 m_successor = successor;
212}
213
215{
216 qreal result = 0.0;
217 for (int i = 1; i < m_points.size(); ++i) {
218 result += m_points[i - 1].point().distance(m_points[i].point());
219 }
220
221 return result;
222}
223
225{
226 qreal result = 0.0;
227 const RoutingInstruction *i = predecessor();
228 while (i) {
229 result += i->distance();
230 i = i->predecessor();
231 }
232 return result;
233}
234
236{
237 qreal result = distance();
238 const RoutingInstruction *i = successor();
239 while (i) {
240 result += i->distance();
241 i = i->successor();
242 }
243 return result;
244}
245
247{
248 if (roadType() == QLatin1StringView("roundabout")) {
249 return QObject::tr("Enter the roundabout.");
250 }
251
252 if (roadType() == QLatin1StringView("motorway_link")) {
253 QStringList motorways = QStringList() << QStringLiteral("motorway") << QStringLiteral("motorway_link");
254 bool const leaving = predecessor() && motorways.contains(predecessor()->roadType());
255 if (leaving) {
256 if (roadName().isEmpty()) {
257 return QObject::tr("Take the exit.");
258 } else {
259 return QObject::tr("Take the exit towards %1.").arg(roadName());
260 }
261 }
262 if (roadName().isEmpty()) {
263 return QObject::tr("Take the ramp.");
264 } else {
265 return QObject::tr("Take the ramp towards %1.").arg(roadName());
266 }
267 }
268
269 TurnType turnType = m_turnType;
270 if (predecessor() && predecessor()->roundaboutExitNumber()) {
271 switch (predecessor()->roundaboutExitNumber()) {
272 case 1:
273 turnType = RoundaboutFirstExit;
274 break;
275 case 2:
276 turnType = RoundaboutSecondExit;
277 break;
278 case 3:
279 turnType = RoundaboutThirdExit;
280 break;
281 }
282 }
283
284 return generateRoadInstruction(turnType, roadName());
285}
286
288{
290 int precision = 0;
291 qreal length = distance();
292 QString distanceUnit = QLatin1StringView("m");
293
294 if (measurement != QLocale::MetricSystem) {
295 precision = 1;
296 distanceUnit = QStringLiteral("mi");
297 length /= 1000.0;
298 length /= 1.609344;
299 if (length < 0.1) {
300 length = 10 * qRound(length * 528);
301 precision = 0;
302 distanceUnit = QStringLiteral("ft");
303 }
304 } else {
305 if (length >= 1000) {
306 length /= 1000;
307 distanceUnit = QStringLiteral("km");
308 precision = 1;
309 } else if (length >= 200) {
310 length = 50 * qRound(length / 50);
311 } else if (length >= 100) {
312 length = 25 * qRound(length / 25);
313 } else {
314 length = 10 * qRound(length / 10);
315 }
316 }
317
318 if (length == 0) {
319 return {};
320 } else {
321 QString text = QObject::tr("Follow the road for %1 %2.");
322 return text.arg(length, 0, 'f', precision).arg(distanceUnit);
323 }
324}
325
327{
328 qreal duration = secondsLeft();
329 QString durationUnit = QStringLiteral("sec");
330 int precision = 0;
331 if (duration >= 60.0) {
332 duration /= 60.0;
333 durationUnit = QStringLiteral("min");
334 precision = 0;
335 }
336 if (duration >= 60.0) {
337 duration /= 60.0;
338 durationUnit = QStringLiteral("h");
339 precision = 1;
340 }
341
342 QString text = QStringLiteral("Arrival in %1 %2.");
343 return text.arg(duration, 0, 'f', precision).arg(durationUnit);
344}
345
347{
349 text += QLatin1Char(' ') + nextDistanceInstruction();
350 if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--remaining-duration"))) {
351 text += QLatin1Char(' ') + totalDurationRemaining();
352 }
353 return text;
354}
355
356QString RoutingInstruction::generateRoadInstruction(RoutingInstruction::TurnType turnType, const QString &roadName)
357{
358 int roundaboutExit = 0;
359 switch (turnType) {
360 case RoundaboutFirstExit:
361 roundaboutExit = 1;
362 break;
363 case RoundaboutSecondExit:
364 roundaboutExit = 2;
365 break;
366 case RoundaboutThirdExit:
367 roundaboutExit = 3;
368 break;
369 default:
370 break;
371 }
372
373 if (roundaboutExit > 0) {
374 if (roadName.isEmpty()) {
375 return QObject::tr("Take the %1. exit in the roundabout.").arg(roundaboutExit); // One sentence
376 } else {
377 QString text = QObject::tr("Take the %1. exit in the roundabout into %2."); // One sentence
378 return text.arg(roundaboutExit).arg(roadName);
379 }
380 }
381
382 if (roadName.isEmpty()) {
383 switch (turnType) {
384 case Continue:
385 return QObject::tr("Continue.");
386 case Merge:
387 return QObject::tr("Merge.");
388 case TurnAround:
389 return QObject::tr("Turn around.");
390 case SharpLeft:
391 return QObject::tr("Turn sharp left.");
392 case Left:
393 return QObject::tr("Turn left.");
394 case SlightLeft:
395 return QObject::tr("Keep slightly left.");
396 case Straight:
397 return QObject::tr("Go straight ahead.");
398 case SlightRight:
399 return QObject::tr("Keep slightly right.");
400 case Right:
401 return QObject::tr("Turn right.");
402 case SharpRight:
403 return QObject::tr("Turn sharp right.");
404 case RoundaboutExit:
405 return QObject::tr("Exit the roundabout.");
406 case Unknown:
407 case RoundaboutFirstExit:
408 case RoundaboutSecondExit:
409 case RoundaboutThirdExit:
410 Q_ASSERT(false && "Internal error: Unknown/Roundabout should have been handled earlier.");
411 return {};
412 case ExitLeft:
413 return QObject::tr("Take the exit to the left.");
414 case ExitRight:
415 return QObject::tr("Take the exit to the right.");
416 }
417 } else {
418 switch (turnType) {
419 case Continue:
420 return QObject::tr("Continue onto %1.").arg(roadName);
421 case Merge:
422 return QObject::tr("Merge onto %1.").arg(roadName);
423 case TurnAround:
424 return QObject::tr("Turn around onto %1.").arg(roadName);
425 case SharpLeft:
426 return QObject::tr("Turn sharp left on %1.").arg(roadName);
427 case Left:
428 return QObject::tr("Turn left into %1.").arg(roadName);
429 case SlightLeft:
430 return QObject::tr("Keep slightly left on %1.").arg(roadName);
431 case Straight:
432 return QObject::tr("Continue on %1.").arg(roadName);
433 case SlightRight:
434 return QObject::tr("Keep slightly right on %1.").arg(roadName);
435 case Right:
436 return QObject::tr("Turn right into %1.").arg(roadName);
437 case SharpRight:
438 return QObject::tr("Turn sharp right into %1.").arg(roadName);
439 case RoundaboutExit:
440 return QObject::tr("Exit the roundabout into %2.").arg(roadName);
441 case Unknown:
442 case RoundaboutFirstExit:
443 case RoundaboutSecondExit:
444 case RoundaboutThirdExit:
445 Q_ASSERT(false && "Internal error: Unknown/Roundabout should have been handled earlier.");
446 return {};
447 case ExitLeft:
448 return QObject::tr("Take the exit to the left onto %1.").arg(roadName);
449 case ExitRight:
450 return QObject::tr("Take the exit to the right onto %1.").arg(roadName);
451 }
452 }
453
454 Q_ASSERT(false && "Internal error: Switch did not handle all cases.");
455 return {};
456}
457
458QTextStream &operator<<(QTextStream &stream, const RoutingInstruction &i)
459{
460 stream.setRealNumberPrecision(8);
461 if (i.points().isEmpty()) {
462 return stream;
463 }
464
465 if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--dense"))) {
466 QList<RoutingWaypoint> points = i.points();
467 int maxElement = points.size() - (i.successor() ? 1 : 0);
468 for (int j = 0; j < maxElement; ++j) {
469 stream << points[j].point().lat() << ',';
470 stream << points[j].point().lon() << ',';
471 stream << points[j].junctionTypeRaw() << ',';
472 stream << points[j].roadType() << ',';
473 stream << points[j].secondsRemaining() << ',';
474 if (!j) {
475 stream << i.instructionText();
476 }
477 if (j < maxElement - 1) {
478 stream << '\n';
479 }
480 }
481
482 return stream;
483 }
484
485 if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--csv"))) {
486 stream << i.points().constFirst().point().lat() << ',';
487 stream << i.points().constFirst().point().lon() << ',';
488 } else {
489 QString distanceUnit = QStringLiteral("m ");
490 int precision = 0;
491 qreal length = i.distanceFromStart();
492 if (length >= 1000) {
493 length /= 1000;
494 distanceUnit = QStringLiteral("km");
495 precision = 1;
496 }
497
498 QString totalDistance = QStringLiteral("[%1 %2] ");
499 stream << totalDistance.arg(length, 3, 'f', precision).arg(distanceUnit);
500 }
501
502 stream << i.instructionText();
503
504 if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--csv"))
505 && QCoreApplication::instance()->arguments().contains(QStringLiteral("--intersection-points"))) {
506 const auto points = i.intersectionPoints();
507 for (const RoutingPoint &point : points) {
508 stream << ',' << point.lat() << ',' << point.lon();
509 }
510 }
511
512 return stream;
513}
514
515int RoutingInstruction::roundaboutExitNumber() const
516{
517 return m_roundaboutExit;
518}
519
520RoutingInstruction::TurnType RoutingInstruction::turnType() const
521{
522 return m_turnType;
523}
524
525} // namespace Marble
Stores data related to one instruction: Road name, angle to predecessor, associated waypoints etc.
bool append(const RoutingWaypoint &item, int angle)
Append data of the given item, returns true if item's street name matches instructions street name.
QString roadName() const
Name of the road to turn into.
QString totalDurationRemaining() const
Formats the instruction (duration to destination) for a human reader.
QList< RoutingWaypoint > points() const
Waypoints from the last instruction to this instruction.
RoutingInstruction(const RoutingWaypoint &item=RoutingWaypoint())
Constructor.
void setPredecessor(RoutingInstruction *predecessor)
Change the predecessor.
QString instructionText() const
Formats the instruction for a human reader.
QString nextDistanceInstruction() const
Formats the instruction (distance to next instruction) for a human reader.
qreal angleToPredecssor() const
The angle between the two turn roads, in radians.
QString nextRoadInstruction() const
Formats the instruction (road name) for a human reader.
qreal distanceToEnd() const
The distance to the route end.
QList< RoutingPoint > intersectionPoints() const
Contains the intersection point and points near it on the previous and current road.
QString roadType() const
OSM type of the road to turn into.
RoutingInstruction * predecessor()
Previous turn road.
qreal distanceFromStart() const
The distance from the route start.
int secondsLeft() const
Estimated number of seconds to the route destination.
RoutingInstruction * successor()
Next turn road.
void setSuccessor(RoutingInstruction *successor)
Change the successor.
qreal distance() const
The accumulated distance of all waypoints belonging to this instruction.
Stores one line of gosmore/routino output.
QString roadType() const
OSM type of the road.
QString roadName() const
OSM name of the road.
JunctionType junctionType() const
Parsed junction type.
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
Binds a QML item to a specific geodetic location in screen coordinates.
QCoreApplication * instance()
qsizetype size() const const
MeasurementSystem measurementSystem() const const
QLocale system()
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(Args &&... args) const const
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
void setRealNumberPrecision(int precision)
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.