Marble

RoutingInstruction.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <[email protected]>
4 //
5 
6 #include "RoutingInstruction.h"
7 
8 #include <QCoreApplication>
9 #include <QStringList>
10 #include <QTextStream>
11 
12 #include <cmath>
13 
14 namespace Marble
15 {
16 
18  m_roadName( item.roadName() ), m_roadType( item.roadType() ),
19  m_secondsLeft( item.secondsRemaining() ),
20  m_angleToPredecessor( 0.0 ), m_roundaboutExit( 0 ),
21  m_predecessor( nullptr ), m_successor( nullptr )
22 {
23  m_points.append( item );
24 }
25 
26 bool RoutingInstruction::append( const RoutingWaypoint &item, int angle )
27 {
28  if (m_points.size() &&
29  m_points.last().roadType() != QLatin1String("roundabout") &&
30  item.roadType() == QLatin1String("roundabout")) {
31  // Entering a roundabout. Merge with previous segment to avoid 'Enter the roundabout' instructions
32  m_points.push_back( item );
33  return true;
34  }
35 
36  if (m_points.size() &&
37  m_points.last().roadType() == QLatin1String("roundabout") &&
38  item.roadType() != QLatin1String("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() == QLatin1String("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 
78 void 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().first().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().first().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().first().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 
120 void 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() == QLatin1String("roundabout")) {
249  return QObject::tr( "Enter the roundabout." );
250  }
251 
252  if (roadType() == QLatin1String("motorway_link")) {
253  QStringList motorways = QStringList() << "motorway" << "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 = QLatin1String( "m" );
293 
294  if ( measurement != QLocale::MetricSystem ) {
295  precision = 1;
296  distanceUnit = "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 = "ft";
303  }
304  } else {
305  if ( length >= 1000 ) {
306  length /= 1000;
307  distanceUnit = "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 QString();
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 = "sec";
330  int precision = 0;
331  if ( duration >= 60.0 ) {
332  duration /= 60.0;
333  durationUnit = "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 = "Arrival in %1 %2.";
343  return text.arg( duration, 0, 'f', precision ).arg( durationUnit );
344 }
345 
347 {
348  QString text = nextRoadInstruction();
349  text += QLatin1Char(' ') + nextDistanceInstruction();
350  if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--remaining-duration"))) {
351  text += QLatin1Char(' ') + totalDurationRemaining();
352  }
353  return text;
354 }
355 
356 QString 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 QString();
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 QString();
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 QString();
456 }
457 
458 QTextStream& 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  QVector<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().first().point().lat() << ',';
487  stream << i.points().first().point().lon() << ',';
488  } else {
489  QString distanceUnit = "m ";
490  int precision = 0;
491  qreal length = i.distanceFromStart();
492  if ( length >= 1000 ) {
493  length /= 1000;
494  distanceUnit = "km";
495  precision = 1;
496  }
497 
498  QString totalDistance = "[%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  for ( const RoutingPoint &point: i.intersectionPoints() ) {
507  stream << ',' << point.lat() << ',' << point.lon();
508  }
509  }
510 
511  return stream;
512 }
513 
514 int RoutingInstruction::roundaboutExitNumber() const
515 {
516  return m_roundaboutExit;
517 }
518 
519 RoutingInstruction::TurnType RoutingInstruction::turnType() const
520 {
521  return m_turnType;
522 }
523 
524 } // namespace Marble
qreal distanceToEnd() const
The distance to the route end.
QString roadType() const
OSM type of the road to turn into.
void setSuccessor(RoutingInstruction *successor)
Change the successor.
QString nextRoadInstruction() const
Formats the instruction (road name) for a human reader.
QVector< RoutingWaypoint > points() const
Waypoints from the last instruction to this instruction.
QString nextDistanceInstruction() const
Formats the instruction (distance to next instruction) for a human reader.
QString roadType() const
OSM type of the road.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
qreal distance() const
The accumulated distance of all waypoints belonging to this instruction.
QLocale::MeasurementSystem measurementSystem() const const
QDataStream & operator<<(QDataStream &out, const KDateTime &dateTime)
QString roadName() const
Name of the road to turn into.
JunctionType junctionType() const
Parsed junction type.
Stores one line of gosmore/routino output.
RoutingInstruction(const RoutingWaypoint &item=RoutingWaypoint())
Constructor.
qreal distanceFromStart() const
The distance from the route start.
QLocale system()
qreal angleToPredecssor() const
The angle between the two turn roads, in radians.
void setPredecessor(RoutingInstruction *predecessor)
Change the predecessor.
bool isEmpty() const const
QCoreApplication * instance()
RoutingInstruction * successor()
Next turn road.
Binds a QML item to a specific geodetic location in screen coordinates.
RoutingInstruction * predecessor()
Previous turn road.
QVector< RoutingPoint > intersectionPoints() const
Contains the intersection point and points near it on the previous and current road.
QString instructionText() const
Formats the instruction for a human reader.
void setRealNumberPrecision(int precision)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString roadName() const
OSM name of the road.
int size() const const
QString totalDurationRemaining() const
Formats the instruction (duration to destination) for a human reader.
int secondsLeft() const
Estimated number of seconds to the route destination.
QString tr(const char *sourceText, const char *disambiguation, int n)
QStringList arguments()
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.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Oct 2 2023 03:52:09 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.