File indexing completed on 2024-05-12 03:50:40

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "RoutingInstruction.h"
0007 
0008 #include <QCoreApplication>
0009 #include <QStringList>
0010 #include <QTextStream>
0011 
0012 #include <cmath>
0013 
0014 namespace Marble
0015 {
0016 
0017 RoutingInstruction::RoutingInstruction( const RoutingWaypoint &item ) :
0018         m_roadName( item.roadName() ), m_roadType( item.roadType() ),
0019         m_secondsLeft( item.secondsRemaining() ),
0020         m_angleToPredecessor( 0.0 ), m_roundaboutExit( 0 ),
0021         m_predecessor( nullptr ), m_successor( nullptr )
0022 {
0023     m_points.append( item );
0024 }
0025 
0026 bool RoutingInstruction::append( const RoutingWaypoint &item, int angle )
0027 {
0028     if (m_points.size() &&
0029         m_points.last().roadType() != QLatin1String("roundabout") &&
0030         item.roadType() == QLatin1String("roundabout")) {
0031         // Entering a roundabout. Merge with previous segment to avoid 'Enter the roundabout' instructions
0032         m_points.push_back( item );
0033         return true;
0034     }
0035 
0036     if (m_points.size() &&
0037         m_points.last().roadType() == QLatin1String("roundabout") &&
0038         item.roadType() != QLatin1String("roundabout")) {
0039         // Exiting a roundabout
0040         m_points.push_back( item );
0041         return false;
0042     }
0043 
0044     m_points.push_back( item );
0045 
0046     if ( item.junctionType() == RoutingWaypoint::Roundabout ) {
0047         // Passing a roundabout exit
0048         ++m_roundaboutExit;
0049         return true;
0050     }
0051 
0052     if ( item.roadName().isEmpty() ) {
0053         if ( item.junctionType() == RoutingWaypoint::None ) {
0054             return true;
0055         }
0056 
0057         return angle >= 150 && angle <= 210;
0058     } else {
0059         return item.roadType() == QLatin1String("roundabout") || item.roadName() == roadName();
0060     }
0061 }
0062 
0063 QString RoutingInstruction::roadName() const
0064 {
0065     return m_roadName;
0066 }
0067 
0068 QString RoutingInstruction::roadType() const
0069 {
0070     return m_roadType;
0071 }
0072 
0073 int RoutingInstruction::secondsLeft() const
0074 {
0075     return m_secondsLeft;
0076 }
0077 
0078 void RoutingInstruction::calculateAngle()
0079 {
0080     if ( !m_predecessor ) {
0081         return;
0082     }
0083 
0084     int hisSize = m_predecessor->points().size();
0085     int mySize = m_points.size();
0086     Q_ASSERT( mySize > 0 && hisSize > 0 );
0087     RoutingPoint one = points().first().point();
0088     RoutingPoint two = m_predecessor->points().at( hisSize - 1 ).point();
0089     qreal distance = 0;
0090     for ( int i = 2; i <= qMin<int>( hisSize, 20 ) && distance < 50.0; ++i ) {
0091         two = m_predecessor->points().at( hisSize - i ).point();
0092         m_intersectionPoints.push_front( two );
0093         distance = one.distance( two );
0094     }
0095     qreal before = two.bearing( one );
0096     m_intersectionPoints.push_back( one );
0097 
0098     one = points().first().point();
0099     if ( mySize == 1 && !m_successor ) {
0100         return;
0101     } else if ( mySize == 1 ) {
0102         Q_ASSERT( !m_successor->points().isEmpty() );
0103         two = m_successor->points().first().point();
0104     } else {
0105         two = points().at( 1 ).point();
0106     }
0107 
0108     distance = 0;
0109     m_intersectionPoints.push_back( one );
0110     for ( int i = 2; i < qMin<int>( mySize, 20 ) && distance < 50.0; ++i ) {
0111         two = points().at( i ).point();
0112         m_intersectionPoints.push_back( two );
0113         distance = one.distance( two );
0114     }
0115 
0116     qreal after = one.bearing( two );
0117     m_angleToPredecessor = after - before;
0118 }
0119 
0120 void RoutingInstruction::calculateTurnType()
0121 {
0122     if ( predecessor() && predecessor()->roundaboutExitNumber() ) {
0123         int exit = predecessor()->roundaboutExitNumber();
0124         switch( exit ) {
0125         case 1:
0126             m_turnType = RoundaboutFirstExit;
0127             break;
0128         case 2:
0129             m_turnType = RoundaboutSecondExit;
0130             break;
0131         case 3:
0132             m_turnType = RoundaboutThirdExit;
0133             break;
0134         default:
0135             m_turnType = RoundaboutExit;
0136             break;
0137         }
0138 
0139         return;
0140     }
0141 
0142     int angle = qRound( angleToPredecssor() * 180.0 / M_PI + 540 ) % 360;
0143     Q_ASSERT( angle >= 0 && angle <= 360 );
0144 
0145     const int sharp = 30;
0146     if ( angle >= 360 - sharp || angle < sharp ) {
0147         m_turnType = TurnAround;
0148     } else if ( angle >= sharp && angle < 90 - sharp ) {
0149         m_turnType = SharpLeft;
0150     } else if ( angle >= 90 - sharp && angle < 90 + sharp ) {
0151         m_turnType = Left;
0152     } else if ( angle >= 90 + sharp && angle < 180 - sharp ) {
0153         m_turnType = SlightLeft;
0154     } else if ( angle >= 180 - sharp && angle < 180 + sharp ) {
0155         m_turnType = Straight;
0156     } else if ( angle >= 180 + sharp && angle < 270 - sharp ) {
0157         m_turnType = SlightRight;
0158     } else if ( angle >= 270 - sharp && angle < 270 + sharp ) {
0159         m_turnType = Right;
0160     } else if ( angle >= 270 + sharp && angle < 360 - sharp ) {
0161         m_turnType = SharpRight;
0162     } else {
0163         Q_ASSERT( false && "Internal error: not all angles are properly handled" );
0164     }
0165 }
0166 
0167 QVector<RoutingWaypoint> RoutingInstruction::points() const
0168 {
0169     return m_points;
0170 }
0171 
0172 QVector<RoutingPoint> RoutingInstruction::intersectionPoints() const
0173 {
0174     return m_intersectionPoints;
0175 }
0176 
0177 qreal RoutingInstruction::angleToPredecssor() const
0178 {
0179     return m_angleToPredecessor;
0180 }
0181 
0182 RoutingInstruction* RoutingInstruction::predecessor()
0183 {
0184     return m_predecessor;
0185 }
0186 
0187 const RoutingInstruction* RoutingInstruction::predecessor() const
0188 {
0189     return m_predecessor;
0190 }
0191 
0192 void RoutingInstruction::setPredecessor( RoutingInstruction* predecessor )
0193 {
0194     m_predecessor = predecessor;
0195     calculateAngle();
0196     calculateTurnType();
0197 }
0198 
0199 RoutingInstruction* RoutingInstruction::successor()
0200 {
0201     return m_successor;
0202 }
0203 
0204 const RoutingInstruction* RoutingInstruction::successor() const
0205 {
0206     return m_successor;
0207 }
0208 
0209 void RoutingInstruction::setSuccessor( RoutingInstruction* successor )
0210 {
0211     m_successor = successor;
0212 }
0213 
0214 qreal RoutingInstruction::distance() const
0215 {
0216     qreal result = 0.0;
0217     for ( int i = 1; i < m_points.size(); ++i ) {
0218         result += m_points[i-1].point().distance( m_points[i].point() );
0219     }
0220 
0221     return result;
0222 }
0223 
0224 qreal RoutingInstruction::distanceFromStart() const
0225 {
0226     qreal result = 0.0;
0227     const RoutingInstruction* i = predecessor();
0228     while ( i ) {
0229         result += i->distance();
0230         i = i->predecessor();
0231     }
0232     return result;
0233 }
0234 
0235 qreal RoutingInstruction::distanceToEnd() const
0236 {
0237     qreal result = distance();
0238     const RoutingInstruction* i = successor();
0239     while ( i ) {
0240         result += i->distance();
0241         i = i->successor();
0242     }
0243     return result;
0244 }
0245 
0246 QString RoutingInstruction::nextRoadInstruction() const
0247 {
0248     if (roadType() == QLatin1String("roundabout")) {
0249         return QObject::tr( "Enter the roundabout." );
0250     }
0251 
0252     if (roadType() == QLatin1String("motorway_link")) {
0253         QStringList motorways = QStringList() << "motorway" << "motorway_link";
0254         bool const leaving = predecessor() && motorways.contains( predecessor()->roadType() );
0255         if ( leaving ) {
0256             if ( roadName().isEmpty() ) {
0257                 return QObject::tr( "Take the exit." );
0258             } else {
0259                 return QObject::tr( "Take the exit towards %1." ).arg( roadName() );
0260             }
0261         }
0262         if ( roadName().isEmpty() ) {
0263             return QObject::tr( "Take the ramp." );
0264         } else {
0265             return QObject::tr( "Take the ramp towards %1." ).arg( roadName() );
0266         }
0267     }
0268 
0269     TurnType turnType = m_turnType;
0270     if ( predecessor() && predecessor()->roundaboutExitNumber() ) {
0271         switch ( predecessor()->roundaboutExitNumber() ) {
0272         case 1:
0273             turnType = RoundaboutFirstExit;
0274             break;
0275         case 2:
0276             turnType = RoundaboutSecondExit;
0277             break;
0278         case 3:
0279             turnType = RoundaboutThirdExit;
0280             break;
0281         }
0282     }
0283 
0284     return generateRoadInstruction( turnType, roadName() );
0285 }
0286 
0287 QString RoutingInstruction::nextDistanceInstruction() const
0288 {
0289     QLocale::MeasurementSystem const measurement = QLocale::system().measurementSystem();
0290     int precision = 0;
0291     qreal length = distance();
0292     QString distanceUnit = QLatin1String( "m" );
0293 
0294     if ( measurement != QLocale::MetricSystem ) {
0295         precision = 1;
0296         distanceUnit = "mi";
0297         length /= 1000.0;
0298         length /= 1.609344;
0299         if ( length < 0.1 ) {
0300             length = 10 * qRound( length * 528 );
0301             precision = 0;
0302             distanceUnit = "ft";
0303         }
0304     } else {
0305         if ( length >= 1000 ) {
0306             length /= 1000;
0307             distanceUnit = "km";
0308             precision = 1;
0309         } else if ( length >= 200 ) {
0310             length = 50 * qRound( length / 50 );
0311         } else if ( length >= 100 ) {
0312             length = 25 * qRound( length / 25 );
0313         } else {
0314             length = 10 * qRound( length / 10 );
0315         }
0316     }
0317 
0318     if ( length == 0 ) {
0319         return QString();
0320     } else {
0321         QString text = QObject::tr( "Follow the road for %1 %2." );
0322         return text.arg( length, 0, 'f', precision ).arg( distanceUnit );
0323     }
0324 }
0325 
0326 QString RoutingInstruction::totalDurationRemaining() const
0327 {
0328     qreal duration = secondsLeft();
0329     QString durationUnit = "sec";
0330     int precision = 0;
0331     if ( duration >= 60.0 ) {
0332         duration /= 60.0;
0333         durationUnit = "min";
0334         precision = 0;
0335     }
0336     if ( duration >= 60.0 ) {
0337         duration /= 60.0;
0338         durationUnit = QStringLiteral("h");
0339         precision = 1;
0340     }
0341 
0342     QString text = "Arrival in %1 %2.";
0343     return text.arg( duration, 0, 'f', precision ).arg( durationUnit );
0344 }
0345 
0346 QString RoutingInstruction::instructionText() const
0347 {
0348     QString text = nextRoadInstruction();
0349     text += QLatin1Char(' ') + nextDistanceInstruction();
0350     if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--remaining-duration"))) {
0351         text += QLatin1Char(' ') + totalDurationRemaining();
0352     }
0353     return text;
0354 }
0355 
0356 QString RoutingInstruction::generateRoadInstruction( RoutingInstruction::TurnType turnType, const QString &roadName )
0357 {   
0358     int roundaboutExit = 0;
0359     switch ( turnType ) {
0360     case RoundaboutFirstExit:
0361         roundaboutExit = 1;
0362         break;
0363     case RoundaboutSecondExit:
0364         roundaboutExit = 2;
0365         break;
0366     case RoundaboutThirdExit:
0367         roundaboutExit = 3;
0368         break;
0369     default:
0370         break;
0371     }
0372 
0373     if ( roundaboutExit > 0 ) {
0374         if ( roadName.isEmpty() ) {
0375             return QObject::tr( "Take the %1. exit in the roundabout." ).arg( roundaboutExit ); // One sentence
0376         } else {
0377             QString text = QObject::tr( "Take the %1. exit in the roundabout into %2." );  // One sentence
0378             return text.arg( roundaboutExit ).arg( roadName );
0379         }
0380     }
0381 
0382     if ( roadName.isEmpty() ) {
0383         switch( turnType ) {
0384         case Continue:
0385             return QObject::tr( "Continue." );
0386         case Merge:
0387             return QObject::tr( "Merge." );
0388         case TurnAround:
0389             return QObject::tr( "Turn around." );
0390         case SharpLeft:
0391             return QObject::tr( "Turn sharp left." );
0392         case Left:
0393             return QObject::tr( "Turn left." );
0394         case SlightLeft:
0395             return QObject::tr( "Keep slightly left." );
0396         case Straight:
0397             return QObject::tr( "Go straight ahead." );
0398         case SlightRight:
0399             return QObject::tr( "Keep slightly right." );
0400         case Right:
0401             return QObject::tr( "Turn right." );
0402         case SharpRight:
0403             return QObject::tr( "Turn sharp right." );
0404         case RoundaboutExit:
0405             return QObject::tr( "Exit the roundabout." );
0406         case Unknown:
0407         case RoundaboutFirstExit:
0408         case RoundaboutSecondExit:
0409         case RoundaboutThirdExit:
0410             Q_ASSERT( false && "Internal error: Unknown/Roundabout should have been handled earlier." );
0411             return QString();
0412         case ExitLeft:
0413             return QObject::tr( "Take the exit to the left." );
0414         case ExitRight:
0415             return QObject::tr( "Take the exit to the right." );
0416         }
0417     } else {
0418         switch( turnType ) {
0419         case Continue:
0420             return QObject::tr( "Continue onto %1." ).arg( roadName );
0421         case Merge:
0422             return QObject::tr( "Merge onto %1." ).arg( roadName );
0423         case TurnAround:
0424             return QObject::tr( "Turn around onto %1." ).arg( roadName );
0425         case SharpLeft:
0426             return QObject::tr( "Turn sharp left on %1." ).arg( roadName );
0427         case Left:
0428             return QObject::tr( "Turn left into %1." ).arg( roadName );
0429         case SlightLeft:
0430             return QObject::tr( "Keep slightly left on %1." ).arg( roadName );
0431         case Straight:
0432             return QObject::tr( "Continue on %1." ).arg( roadName );
0433         case SlightRight:
0434             return QObject::tr( "Keep slightly right on %1." ).arg( roadName );
0435         case Right:
0436             return QObject::tr( "Turn right into %1." ).arg( roadName );
0437         case SharpRight:
0438             return QObject::tr( "Turn sharp right into %1." ).arg( roadName );
0439         case RoundaboutExit:
0440             return QObject::tr( "Exit the roundabout into %2." ).arg( roadName );
0441         case Unknown:
0442         case RoundaboutFirstExit:
0443         case RoundaboutSecondExit:
0444         case RoundaboutThirdExit:
0445             Q_ASSERT( false && "Internal error: Unknown/Roundabout should have been handled earlier." );
0446             return QString();
0447         case ExitLeft:
0448             return QObject::tr( "Take the exit to the left onto %1." ).arg( roadName );
0449         case ExitRight:
0450             return QObject::tr( "Take the exit to the right onto %1." ).arg( roadName );
0451         }
0452     }
0453 
0454     Q_ASSERT( false && "Internal error: Switch did not handle all cases.");
0455     return QString();
0456 }
0457 
0458 QTextStream& operator<<( QTextStream& stream, const RoutingInstruction &i )
0459 {
0460     stream.setRealNumberPrecision( 8 );
0461     if ( i.points().isEmpty() ) {
0462         return stream;
0463     }
0464 
0465     if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--dense"))) {
0466         QVector<RoutingWaypoint> points = i.points();
0467         int maxElement = points.size() - ( i.successor() ? 1 : 0 );
0468         for ( int j = 0; j < maxElement; ++j ) {
0469             stream << points[j].point().lat() << ',';
0470             stream << points[j].point().lon() << ',';
0471             stream << points[j].junctionTypeRaw() << ',';
0472             stream << points[j].roadType() << ',';
0473             stream << points[j].secondsRemaining() << ',';
0474             if ( !j ) {
0475                 stream << i.instructionText();
0476             }
0477             if ( j < maxElement - 1 ) {
0478                 stream << '\n';
0479             }
0480         }
0481 
0482         return stream;
0483     }
0484 
0485     if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--csv"))) {
0486         stream << i.points().first().point().lat() << ',';
0487         stream << i.points().first().point().lon() << ',';
0488     } else {
0489         QString distanceUnit = "m ";
0490         int precision = 0;
0491         qreal length = i.distanceFromStart();
0492         if ( length >= 1000 ) {
0493             length /= 1000;
0494             distanceUnit = "km";
0495             precision = 1;
0496         }
0497 
0498         QString totalDistance = "[%1 %2] ";
0499         stream << totalDistance.arg( length, 3, 'f', precision ).arg( distanceUnit );
0500     }
0501 
0502     stream << i.instructionText();
0503 
0504     if (QCoreApplication::instance()->arguments().contains(QStringLiteral("--csv")) &&
0505         QCoreApplication::instance()->arguments().contains(QStringLiteral("--intersection-points"))) {
0506         for ( const RoutingPoint &point: i.intersectionPoints() ) {
0507             stream << ',' << point.lat() << ',' << point.lon();
0508         }
0509     }
0510 
0511     return stream;
0512 }
0513 
0514 int RoutingInstruction::roundaboutExitNumber() const
0515 {
0516     return m_roundaboutExit;
0517 }
0518 
0519 RoutingInstruction::TurnType RoutingInstruction::turnType() const
0520 {
0521     return m_turnType;
0522 }
0523 
0524 } // namespace Marble