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