File indexing completed on 2024-04-28 03:49:31

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2012 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "VoiceNavigationModel.h"
0007 
0008 #include "Route.h"
0009 
0010 #include "MarbleDirs.h"
0011 #include "MarbleDebug.h"
0012 
0013 namespace Marble
0014 {
0015 
0016 class VoiceNavigationModelPrivate
0017 {
0018 public:
0019 
0020     struct Announcement
0021     {
0022         bool announcementDone;
0023         bool turnInstructionDone;
0024 
0025         Announcement(){
0026             announcementDone = false;
0027             turnInstructionDone = false;
0028         }
0029     };
0030 
0031     VoiceNavigationModel* m_parent;
0032 
0033     QString m_speaker;
0034 
0035     bool m_speakerEnabled;
0036 
0037     QMap<Maneuver::Direction, QString> m_turnTypeMap;
0038 
0039     QMap<Maneuver::Direction, QString> m_announceMap;
0040 
0041     qreal m_lastDistance;
0042 
0043     qreal m_lastDistanceTraversed;
0044 
0045     GeoDataLineString m_lastRoutePath;
0046 
0047     Maneuver::Direction m_lastTurnType;
0048 
0049     GeoDataCoordinates m_lastTurnPoint;
0050 
0051     QStringList m_queue;
0052 
0053     QString m_announcementText;
0054 
0055     bool m_destinationReached;
0056 
0057     bool m_deviated;
0058 
0059     QVector<Announcement> m_announcementList;
0060 
0061     explicit VoiceNavigationModelPrivate( VoiceNavigationModel* parent );
0062 
0063     void reset();
0064 
0065     QString audioFile(const QString &name) const;
0066 
0067     QString distanceAudioFile( qreal dest ) const;
0068 
0069     QString turnTypeAudioFile( Maneuver::Direction turnType, qreal distance );
0070 
0071     QString announcementText( Maneuver::Direction turnType, qreal distance );
0072 
0073     void updateInstruction(const RouteSegment &segment, qreal distance, Maneuver::Direction turnType );
0074 
0075     void updateInstruction( const QString &name );
0076 
0077     void initializeMaps();
0078 };
0079 
0080 VoiceNavigationModelPrivate::VoiceNavigationModelPrivate( VoiceNavigationModel* parent ) :
0081     m_parent( parent ),
0082     m_speakerEnabled( true ),
0083     m_lastDistance( 0.0 ),
0084     m_lastDistanceTraversed( 0.0 ),
0085     m_lastTurnType( Maneuver::Unknown ),
0086     m_destinationReached( false ),
0087     m_deviated( false )
0088 {
0089     initializeMaps();
0090 }
0091 
0092 void VoiceNavigationModelPrivate::reset()
0093 {
0094     m_lastDistance = 0.0;
0095     m_lastDistanceTraversed = 0.0;
0096 }
0097 
0098 QString VoiceNavigationModelPrivate::audioFile( const QString &name ) const
0099 {
0100 #ifdef Q_OS_ANDROID
0101     return name;
0102 #else
0103     QStringList const formats = QStringList() << "ogg" << "mp3" << "wav";
0104     if ( m_speakerEnabled ) {
0105         QString const audioTemplate = "%1/%2.%3";
0106         for( const QString &format: formats ) {
0107             QString const result = audioTemplate.arg( m_speaker, name, format );
0108             QFileInfo audioFile( result );
0109             if ( audioFile.exists() ) {
0110                 return result;
0111             }
0112         }
0113     }
0114 
0115     QString const audioTemplate = "audio/%1.%2";
0116     for( const QString &format: formats ) {
0117         QString const result = MarbleDirs::path( audioTemplate.arg( name, format ) );
0118         if ( !result.isEmpty() ) {
0119             return result;
0120         }
0121     }
0122 
0123     return QString();
0124 #endif
0125 }
0126 
0127 QString VoiceNavigationModelPrivate::distanceAudioFile( qreal dest ) const
0128 {
0129     if ( dest > 0.0 && dest < 900.0 ) {
0130         qreal minDistance = 0.0;
0131         int targetDistance = 0;
0132         QVector<int> distances;
0133         distances << 50 << 80 << 100 << 200 << 300 << 400 << 500 << 600 << 700 << 800;
0134         for( int distance: distances ) {
0135             QString file = audioFile( QString::number( distance ) );
0136             qreal currentDistance = qAbs( distance - dest );
0137             if ( !file.isEmpty() && ( minDistance == 0.0 || currentDistance < minDistance ) ) {
0138                 minDistance = currentDistance;
0139                 targetDistance = distance;
0140             }
0141         }
0142 
0143         if ( targetDistance > 0 ) {
0144             return audioFile( QString::number( targetDistance ) );
0145         }
0146     }
0147 
0148     return QString();
0149 }
0150 
0151 QString VoiceNavigationModelPrivate::turnTypeAudioFile( Maneuver::Direction turnType, qreal distance )
0152 {
0153     bool const announce = distance >= 75;
0154     QMap<Maneuver::Direction, QString> const & map = announce ? m_announceMap : m_turnTypeMap;
0155     if ( m_speakerEnabled && map.contains( turnType ) ) {
0156         return audioFile( map[turnType] );
0157     }
0158 
0159     return audioFile( announce ? "ListEnd" : "AppPositive" );
0160 }
0161 
0162 QString VoiceNavigationModelPrivate::announcementText( Maneuver::Direction turnType, qreal distance )
0163 {
0164     QString announcementText = QString("");
0165     if (distance >= 75) {
0166         announcementText = QString("In "+distanceAudioFile(distance)+" meters, ");
0167     }
0168     switch (turnType) {
0169     case Maneuver::Continue:
0170     case Maneuver::Straight:
0171         announcementText += QString("Continue straight");
0172         break;
0173     case Maneuver::SlightRight:
0174         announcementText += QString("Turn slight right");
0175         break;
0176     case Maneuver::SlightLeft:
0177         announcementText += QString("Turn slight left");
0178         break;
0179     case Maneuver::Right:
0180     case Maneuver::SharpRight:
0181         announcementText += QString("Turn right");
0182         break;
0183     case Maneuver::Left:
0184     case Maneuver::SharpLeft:
0185         announcementText += QString("Turn left");
0186         break;
0187     case Maneuver::TurnAround:
0188         announcementText += QString("Take a U-turn");
0189         break;
0190     case Maneuver::ExitLeft:
0191         announcementText += QString("Exit left");
0192         break;
0193     case Maneuver::ExitRight:
0194         announcementText += QString("Exit right");
0195         break;
0196     case Maneuver::RoundaboutFirstExit:
0197         announcementText += QString("Take the first exit");
0198         break;
0199     case Maneuver::RoundaboutSecondExit:
0200         announcementText += QString("Take the second exit");
0201         break;
0202     case Maneuver::RoundaboutThirdExit:
0203         announcementText += QString("Take the third exit");
0204         break;
0205     default:
0206         announcementText = QString("");
0207         break;
0208     }
0209     return announcementText;
0210 }
0211 
0212 void VoiceNavigationModelPrivate::updateInstruction( const RouteSegment & segment, qreal distance, Maneuver::Direction turnType )
0213 {
0214     QString turnTypeAudio = turnTypeAudioFile( turnType, distance );
0215     if ( turnTypeAudio.isEmpty() ) {
0216         mDebug() << "Missing audio file for turn type " << turnType << " and speaker " << m_speaker;
0217         return;
0218     }
0219 
0220     m_queue.clear();
0221     m_queue << turnTypeAudio;
0222     m_announcementText = announcementText(turnType, distance);
0223     qreal nextSegmentDistance = segment.nextRouteSegment().distance();
0224     Maneuver::Direction nextSegmentDirection = segment.nextRouteSegment().nextRouteSegment().maneuver().direction();
0225     if (!m_announcementText.isEmpty() && distance < 75 && nextSegmentDistance != 0 && nextSegmentDistance < 75) {
0226         QString nextSegmentAnnouncementText = announcementText(nextSegmentDirection, nextSegmentDistance);
0227         if (!nextSegmentAnnouncementText.isEmpty()) {
0228             m_announcementText += QLatin1String(", then ") + nextSegmentAnnouncementText;
0229         }
0230     }
0231     emit m_parent->instructionChanged();
0232 }
0233 
0234 void VoiceNavigationModelPrivate::updateInstruction( const QString &name )
0235 {
0236     m_queue.clear();
0237     m_queue << audioFile( name );
0238     m_announcementText = name;
0239     emit m_parent->instructionChanged();
0240 }
0241 
0242 void VoiceNavigationModelPrivate::initializeMaps()
0243 {
0244     m_turnTypeMap.clear();
0245     m_announceMap.clear();
0246 
0247     m_announceMap[Maneuver::Continue] = "Straight";
0248     // none of our voice navigation commands fits, so leave out
0249     // Maneuver::Merge here to have a sound play instead
0250     m_announceMap[Maneuver::Straight] = "Straight";
0251     m_announceMap[Maneuver::SlightRight] = "AhKeepRight";
0252     m_announceMap[Maneuver::Right] = "AhRightTurn";
0253     m_announceMap[Maneuver::SharpRight] = "AhRightTurn";
0254     m_announceMap[Maneuver::TurnAround] = "AhUTurn";
0255     m_announceMap[Maneuver::SharpLeft] = "AhLeftTurn";
0256     m_announceMap[Maneuver::Left] = "AhLeftTurn";
0257     m_announceMap[Maneuver::SlightLeft] = "AhKeepLeft";
0258     m_announceMap[Maneuver::RoundaboutFirstExit] = "RbExit1";
0259     m_announceMap[Maneuver::RoundaboutSecondExit] = "RbExit2";
0260     m_announceMap[Maneuver::RoundaboutThirdExit] = "RbExit3";
0261     m_announceMap[Maneuver::ExitLeft] = "AhExitLeft";
0262     m_announceMap[Maneuver::ExitRight] = "AhExitRight";
0263 
0264     m_turnTypeMap[Maneuver::Continue] = "Straight";
0265     // none of our voice navigation commands fits, so leave out
0266     // Maneuver::Merge here to have a sound play instead
0267     m_turnTypeMap[Maneuver::Straight] = "Straight";
0268     m_turnTypeMap[Maneuver::SlightRight] = "BearRight";
0269     m_turnTypeMap[Maneuver::Right] = "TurnRight";
0270     m_turnTypeMap[Maneuver::SharpRight] = "SharpRight";
0271     m_turnTypeMap[Maneuver::TurnAround] = "UTurn";
0272     m_turnTypeMap[Maneuver::SharpLeft] = "SharpLeft";
0273     m_turnTypeMap[Maneuver::Left] = "TurnLeft";
0274     m_turnTypeMap[Maneuver::SlightLeft] = "BearLeft";
0275     m_turnTypeMap[Maneuver::RoundaboutFirstExit] = "";
0276     m_turnTypeMap[Maneuver::RoundaboutSecondExit] = "";
0277     m_turnTypeMap[Maneuver::RoundaboutThirdExit] = "";
0278     m_turnTypeMap[Maneuver::ExitLeft] = "TurnLeft";
0279     m_turnTypeMap[Maneuver::ExitRight] = "TurnRight";
0280 }
0281 
0282 VoiceNavigationModel::VoiceNavigationModel( QObject *parent ) :
0283     QObject( parent ), d( new VoiceNavigationModelPrivate( this ) )
0284 {
0285     // nothing to do
0286 }
0287 
0288 VoiceNavigationModel::~VoiceNavigationModel()
0289 {
0290     delete d;
0291 }
0292 
0293 QString VoiceNavigationModel::speaker() const
0294 {
0295     return d->m_speaker;
0296 }
0297 
0298 void VoiceNavigationModel::setSpeaker(const QString &speaker)
0299 {
0300     if ( speaker != d->m_speaker ) {
0301         QFileInfo speakerDir = QFileInfo( speaker );
0302         if ( !speakerDir.exists() ) {
0303             d->m_speaker = MarbleDirs::path(QLatin1String("/audio/speakers/") + speaker);
0304         } else {
0305             d->m_speaker = speaker;
0306         }
0307 
0308         emit speakerChanged();
0309         emit previewChanged();
0310     }
0311 }
0312 
0313 bool VoiceNavigationModel::isSpeakerEnabled() const
0314 {
0315     return d->m_speakerEnabled;
0316 }
0317 
0318 void VoiceNavigationModel::setSpeakerEnabled( bool enabled )
0319 {
0320     if ( enabled != d->m_speakerEnabled ) {
0321         d->m_speakerEnabled = enabled;
0322         emit isSpeakerEnabledChanged();
0323         emit previewChanged();
0324     }
0325 }
0326 
0327 void VoiceNavigationModel::reset()
0328 {
0329     d->reset();
0330 }
0331 
0332 void VoiceNavigationModel::update(const Route &route, qreal distanceManuever, qreal distanceTarget, bool deviated )
0333 {
0334     if (d->m_lastRoutePath != route.path()){
0335         d->m_announcementList.clear();
0336         d->m_announcementList.resize(route.size());
0337         d->m_lastRoutePath = route.path();
0338     }
0339 
0340     if ( d->m_destinationReached && distanceTarget < 250 ) {
0341         return;
0342     }
0343 
0344     if ( !d->m_destinationReached && distanceTarget < 50 ) {
0345         d->m_destinationReached = true;
0346         d->updateInstruction( d->m_speakerEnabled ? "You have arrived at your destination" : "AppPositive" );
0347         return;
0348     }
0349 
0350     if ( distanceTarget > 150 ) {
0351         d->m_destinationReached = false;
0352     }
0353 
0354     if ( deviated && !d->m_deviated ) {
0355         d->updateInstruction( d->m_speakerEnabled ? "Deviated from the route" : "ListEnd" );
0356     }
0357     d->m_deviated = deviated;
0358     if ( deviated ) {
0359         return;
0360     }
0361 
0362     Maneuver::Direction turnType = route.currentSegment().nextRouteSegment().maneuver().direction();
0363     if ( !( d->m_lastTurnPoint == route.currentSegment().nextRouteSegment().maneuver().position() ) || turnType != d->m_lastTurnType ) {
0364         d->m_lastTurnPoint = route.currentSegment().nextRouteSegment().maneuver().position();
0365         d->reset();
0366     }
0367 
0368     int index = route.indexOf(route.currentSegment());
0369 
0370     qreal const distanceTraversed = route.currentSegment().distance() - distanceManuever;
0371     bool const minDistanceTraversed = d->m_lastDistanceTraversed < 40 && distanceTraversed >= 40;
0372     bool const announcementAfterTurn = minDistanceTraversed && distanceManuever >= 75;
0373     bool const announcement = ( d->m_lastDistance > 850 || announcementAfterTurn ) && distanceManuever <= 850;
0374     bool const turn = ( d->m_lastDistance == 0 || d->m_lastDistance > 75 ) && distanceManuever <= 75;
0375 
0376     bool const announcementDone = d->m_announcementList[index].announcementDone;
0377     bool const turnInstructionDone = d->m_announcementList[index].turnInstructionDone;
0378 
0379     if ( ( announcement && !announcementDone ) || ( turn && !turnInstructionDone ) ) {
0380         d->updateInstruction( route.currentSegment(), distanceManuever, turnType );
0381         VoiceNavigationModelPrivate::Announcement & curAnnouncement = d->m_announcementList[index];
0382         if (announcement){
0383             curAnnouncement.announcementDone = true;
0384         }
0385         if (turn){
0386             curAnnouncement.turnInstructionDone = true;
0387         }
0388     }
0389 
0390     d->m_lastTurnType = turnType;
0391     d->m_lastDistance = distanceManuever;
0392     d->m_lastDistanceTraversed = distanceTraversed;
0393 }
0394 
0395 QString VoiceNavigationModel::preview() const
0396 {
0397     return d->audioFile( d->m_speakerEnabled ? "The Marble team wishes you a pleasant and safe journey!" : "AppPositive" );
0398 }
0399 
0400 QString VoiceNavigationModel::instruction() const
0401 {
0402     return d->m_announcementText;
0403 }
0404 
0405 }
0406 
0407 #include "moc_VoiceNavigationModel.cpp"