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