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 <QStringList>
10 #include <QTextStream>
11
12 #include <cmath>
13
14 namespace Marble
15 {
16
RoutingInstruction(const RoutingWaypoint & item)17 RoutingInstruction::RoutingInstruction( const RoutingWaypoint &item ) :
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
append(const RoutingWaypoint & item,int angle)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
roadName() const63 QString RoutingInstruction::roadName() const
64 {
65 return m_roadName;
66 }
67
roadType() const68 QString RoutingInstruction::roadType() const
69 {
70 return m_roadType;
71 }
72
secondsLeft() const73 int RoutingInstruction::secondsLeft() const
74 {
75 return m_secondsLeft;
76 }
77
calculateAngle()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
calculateTurnType()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
points() const167 QVector<RoutingWaypoint> RoutingInstruction::points() const
168 {
169 return m_points;
170 }
171
intersectionPoints() const172 QVector<RoutingPoint> RoutingInstruction::intersectionPoints() const
173 {
174 return m_intersectionPoints;
175 }
176
angleToPredecssor() const177 qreal RoutingInstruction::angleToPredecssor() const
178 {
179 return m_angleToPredecessor;
180 }
181
predecessor()182 RoutingInstruction* RoutingInstruction::predecessor()
183 {
184 return m_predecessor;
185 }
186
predecessor() const187 const RoutingInstruction* RoutingInstruction::predecessor() const
188 {
189 return m_predecessor;
190 }
191
setPredecessor(RoutingInstruction * predecessor)192 void RoutingInstruction::setPredecessor( RoutingInstruction* predecessor )
193 {
194 m_predecessor = predecessor;
195 calculateAngle();
196 calculateTurnType();
197 }
198
successor()199 RoutingInstruction* RoutingInstruction::successor()
200 {
201 return m_successor;
202 }
203
successor() const204 const RoutingInstruction* RoutingInstruction::successor() const
205 {
206 return m_successor;
207 }
208
setSuccessor(RoutingInstruction * successor)209 void RoutingInstruction::setSuccessor( RoutingInstruction* successor )
210 {
211 m_successor = successor;
212 }
213
distance() const214 qreal RoutingInstruction::distance() const
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
distanceFromStart() const224 qreal RoutingInstruction::distanceFromStart() const
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
distanceToEnd() const235 qreal RoutingInstruction::distanceToEnd() const
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
nextRoadInstruction() const246 QString RoutingInstruction::nextRoadInstruction() const
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
nextDistanceInstruction() const287 QString RoutingInstruction::nextDistanceInstruction() const
288 {
289 QLocale::MeasurementSystem const measurement = QLocale::system().measurementSystem();
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
totalDurationRemaining() const326 QString RoutingInstruction::totalDurationRemaining() const
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
instructionText() const346 QString RoutingInstruction::instructionText() const
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
generateRoadInstruction(RoutingInstruction::TurnType turnType,const QString & roadName)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
operator <<(QTextStream & stream,const RoutingInstruction & i)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
roundaboutExitNumber() const514 int RoutingInstruction::roundaboutExitNumber() const
515 {
516 return m_roundaboutExit;
517 }
518
turnType() const519 RoutingInstruction::TurnType RoutingInstruction::turnType() const
520 {
521 return m_turnType;
522 }
523
524 } // namespace Marble
525