Marble

VoiceNavigationModel.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2012 Dennis Nienhüser <[email protected]>
4 //
5 
6 #include "VoiceNavigationModel.h"
7 
8 #include "Route.h"
9 
10 #include "MarbleDirs.h"
11 #include "MarbleDebug.h"
12 
13 namespace Marble
14 {
15 
16 class VoiceNavigationModelPrivate
17 {
18 public:
19 
20  struct Announcement
21  {
22  bool announcementDone;
23  bool turnInstructionDone;
24 
25  Announcement(){
26  announcementDone = false;
27  turnInstructionDone = false;
28  }
29  };
30 
31  VoiceNavigationModel* m_parent;
32 
33  QString m_speaker;
34 
35  bool m_speakerEnabled;
36 
38 
40 
41  qreal m_lastDistance;
42 
43  qreal m_lastDistanceTraversed;
44 
45  GeoDataLineString m_lastRoutePath;
46 
47  Maneuver::Direction m_lastTurnType;
48 
49  GeoDataCoordinates m_lastTurnPoint;
50 
51  QStringList m_queue;
52 
53  QString m_announcementText;
54 
55  bool m_destinationReached;
56 
57  bool m_deviated;
58 
59  QVector<Announcement> m_announcementList;
60 
61  explicit VoiceNavigationModelPrivate( VoiceNavigationModel* parent );
62 
63  void reset();
64 
65  QString audioFile(const QString &name) const;
66 
67  QString distanceAudioFile( qreal dest ) const;
68 
69  QString turnTypeAudioFile( Maneuver::Direction turnType, qreal distance );
70 
71  QString announcementText( Maneuver::Direction turnType, qreal distance );
72 
73  void updateInstruction(const RouteSegment &segment, qreal distance, Maneuver::Direction turnType );
74 
75  void updateInstruction( const QString &name );
76 
77  void initializeMaps();
78 };
79 
80 VoiceNavigationModelPrivate::VoiceNavigationModelPrivate( VoiceNavigationModel* parent ) :
81  m_parent( parent ),
82  m_speakerEnabled( true ),
83  m_lastDistance( 0.0 ),
84  m_lastDistanceTraversed( 0.0 ),
85  m_lastTurnType( Maneuver::Unknown ),
86  m_destinationReached( false ),
87  m_deviated( false )
88 {
89  initializeMaps();
90 }
91 
92 void VoiceNavigationModelPrivate::reset()
93 {
94  m_lastDistance = 0.0;
95  m_lastDistanceTraversed = 0.0;
96 }
97 
98 QString VoiceNavigationModelPrivate::audioFile( const QString &name ) const
99 {
100 #ifdef Q_OS_ANDROID
101  return name;
102 #else
103  QStringList const formats = QStringList() << "ogg" << "mp3" << "wav";
104  if ( m_speakerEnabled ) {
105  QString const audioTemplate = "%1/%2.%3";
106  for( const QString &format: formats ) {
107  QString const result = audioTemplate.arg( m_speaker, name, format );
108  QFileInfo audioFile( result );
109  if ( audioFile.exists() ) {
110  return result;
111  }
112  }
113  }
114 
115  QString const audioTemplate = "audio/%1.%2";
116  for( const QString &format: formats ) {
117  QString const result = MarbleDirs::path( audioTemplate.arg( name, format ) );
118  if ( !result.isEmpty() ) {
119  return result;
120  }
121  }
122 
123  return QString();
124 #endif
125 }
126 
127 QString VoiceNavigationModelPrivate::distanceAudioFile( qreal dest ) const
128 {
129  if ( dest > 0.0 && dest < 900.0 ) {
130  qreal minDistance = 0.0;
131  int targetDistance = 0;
132  QVector<int> distances;
133  distances << 50 << 80 << 100 << 200 << 300 << 400 << 500 << 600 << 700 << 800;
134  for( int distance: distances ) {
135  QString file = audioFile( QString::number( distance ) );
136  qreal currentDistance = qAbs( distance - dest );
137  if ( !file.isEmpty() && ( minDistance == 0.0 || currentDistance < minDistance ) ) {
138  minDistance = currentDistance;
139  targetDistance = distance;
140  }
141  }
142 
143  if ( targetDistance > 0 ) {
144  return audioFile( QString::number( targetDistance ) );
145  }
146  }
147 
148  return QString();
149 }
150 
151 QString VoiceNavigationModelPrivate::turnTypeAudioFile( Maneuver::Direction turnType, qreal distance )
152 {
153  bool const announce = distance >= 75;
154  QMap<Maneuver::Direction, QString> const & map = announce ? m_announceMap : m_turnTypeMap;
155  if ( m_speakerEnabled && map.contains( turnType ) ) {
156  return audioFile( map[turnType] );
157  }
158 
159  return audioFile( announce ? "ListEnd" : "AppPositive" );
160 }
161 
162 QString VoiceNavigationModelPrivate::announcementText( Maneuver::Direction turnType, qreal distance )
163 {
164  QString announcementText = QString("");
165  if (distance >= 75) {
166  announcementText = QString("In "+distanceAudioFile(distance)+" meters, ");
167  }
168  switch (turnType) {
169  case Maneuver::Continue:
170  case Maneuver::Straight:
171  announcementText += QString("Continue straight");
172  break;
173  case Maneuver::SlightRight:
174  announcementText += QString("Turn slight right");
175  break;
176  case Maneuver::SlightLeft:
177  announcementText += QString("Turn slight left");
178  break;
179  case Maneuver::Right:
180  case Maneuver::SharpRight:
181  announcementText += QString("Turn right");
182  break;
183  case Maneuver::Left:
184  case Maneuver::SharpLeft:
185  announcementText += QString("Turn left");
186  break;
187  case Maneuver::TurnAround:
188  announcementText += QString("Take a U-turn");
189  break;
190  case Maneuver::ExitLeft:
191  announcementText += QString("Exit left");
192  break;
193  case Maneuver::ExitRight:
194  announcementText += QString("Exit right");
195  break;
196  case Maneuver::RoundaboutFirstExit:
197  announcementText += QString("Take the first exit");
198  break;
199  case Maneuver::RoundaboutSecondExit:
200  announcementText += QString("Take the second exit");
201  break;
202  case Maneuver::RoundaboutThirdExit:
203  announcementText += QString("Take the third exit");
204  break;
205  default:
206  announcementText = QString("");
207  break;
208  }
209  return announcementText;
210 }
211 
212 void VoiceNavigationModelPrivate::updateInstruction( const RouteSegment & segment, qreal distance, Maneuver::Direction turnType )
213 {
214  QString turnTypeAudio = turnTypeAudioFile( turnType, distance );
215  if ( turnTypeAudio.isEmpty() ) {
216  mDebug() << "Missing audio file for turn type " << turnType << " and speaker " << m_speaker;
217  return;
218  }
219 
220  m_queue.clear();
221  m_queue << turnTypeAudio;
222  m_announcementText = announcementText(turnType, distance);
223  qreal nextSegmentDistance = segment.nextRouteSegment().distance();
224  Maneuver::Direction nextSegmentDirection = segment.nextRouteSegment().nextRouteSegment().maneuver().direction();
225  if (!m_announcementText.isEmpty() && distance < 75 && nextSegmentDistance != 0 && nextSegmentDistance < 75) {
226  QString nextSegmentAnnouncementText = announcementText(nextSegmentDirection, nextSegmentDistance);
227  if (!nextSegmentAnnouncementText.isEmpty()) {
228  m_announcementText += QLatin1String(", then ") + nextSegmentAnnouncementText;
229  }
230  }
231  emit m_parent->instructionChanged();
232 }
233 
234 void VoiceNavigationModelPrivate::updateInstruction( const QString &name )
235 {
236  m_queue.clear();
237  m_queue << audioFile( name );
238  m_announcementText = name;
239  emit m_parent->instructionChanged();
240 }
241 
242 void VoiceNavigationModelPrivate::initializeMaps()
243 {
244  m_turnTypeMap.clear();
245  m_announceMap.clear();
246 
247  m_announceMap[Maneuver::Continue] = "Straight";
248  // none of our voice navigation commands fits, so leave out
249  // Maneuver::Merge here to have a sound play instead
250  m_announceMap[Maneuver::Straight] = "Straight";
251  m_announceMap[Maneuver::SlightRight] = "AhKeepRight";
252  m_announceMap[Maneuver::Right] = "AhRightTurn";
253  m_announceMap[Maneuver::SharpRight] = "AhRightTurn";
254  m_announceMap[Maneuver::TurnAround] = "AhUTurn";
255  m_announceMap[Maneuver::SharpLeft] = "AhLeftTurn";
256  m_announceMap[Maneuver::Left] = "AhLeftTurn";
257  m_announceMap[Maneuver::SlightLeft] = "AhKeepLeft";
258  m_announceMap[Maneuver::RoundaboutFirstExit] = "RbExit1";
259  m_announceMap[Maneuver::RoundaboutSecondExit] = "RbExit2";
260  m_announceMap[Maneuver::RoundaboutThirdExit] = "RbExit3";
261  m_announceMap[Maneuver::ExitLeft] = "AhExitLeft";
262  m_announceMap[Maneuver::ExitRight] = "AhExitRight";
263 
264  m_turnTypeMap[Maneuver::Continue] = "Straight";
265  // none of our voice navigation commands fits, so leave out
266  // Maneuver::Merge here to have a sound play instead
267  m_turnTypeMap[Maneuver::Straight] = "Straight";
268  m_turnTypeMap[Maneuver::SlightRight] = "BearRight";
269  m_turnTypeMap[Maneuver::Right] = "TurnRight";
270  m_turnTypeMap[Maneuver::SharpRight] = "SharpRight";
271  m_turnTypeMap[Maneuver::TurnAround] = "UTurn";
272  m_turnTypeMap[Maneuver::SharpLeft] = "SharpLeft";
273  m_turnTypeMap[Maneuver::Left] = "TurnLeft";
274  m_turnTypeMap[Maneuver::SlightLeft] = "BearLeft";
275  m_turnTypeMap[Maneuver::RoundaboutFirstExit] = "";
276  m_turnTypeMap[Maneuver::RoundaboutSecondExit] = "";
277  m_turnTypeMap[Maneuver::RoundaboutThirdExit] = "";
278  m_turnTypeMap[Maneuver::ExitLeft] = "TurnLeft";
279  m_turnTypeMap[Maneuver::ExitRight] = "TurnRight";
280 }
281 
282 VoiceNavigationModel::VoiceNavigationModel( QObject *parent ) :
283  QObject( parent ), d( new VoiceNavigationModelPrivate( this ) )
284 {
285  // nothing to do
286 }
287 
288 VoiceNavigationModel::~VoiceNavigationModel()
289 {
290  delete d;
291 }
292 
293 QString VoiceNavigationModel::speaker() const
294 {
295  return d->m_speaker;
296 }
297 
298 void VoiceNavigationModel::setSpeaker(const QString &speaker)
299 {
300  if ( speaker != d->m_speaker ) {
301  QFileInfo speakerDir = QFileInfo( speaker );
302  if ( !speakerDir.exists() ) {
303  d->m_speaker = MarbleDirs::path(QLatin1String("/audio/speakers/") + speaker);
304  } else {
305  d->m_speaker = speaker;
306  }
307 
308  emit speakerChanged();
309  emit previewChanged();
310  }
311 }
312 
313 bool VoiceNavigationModel::isSpeakerEnabled() const
314 {
315  return d->m_speakerEnabled;
316 }
317 
318 void VoiceNavigationModel::setSpeakerEnabled( bool enabled )
319 {
320  if ( enabled != d->m_speakerEnabled ) {
321  d->m_speakerEnabled = enabled;
322  emit isSpeakerEnabledChanged();
323  emit previewChanged();
324  }
325 }
326 
327 void VoiceNavigationModel::reset()
328 {
329  d->reset();
330 }
331 
332 void VoiceNavigationModel::update(const Route &route, qreal distanceManuever, qreal distanceTarget, bool deviated )
333 {
334  if (d->m_lastRoutePath != route.path()){
335  d->m_announcementList.clear();
336  d->m_announcementList.resize(route.size());
337  d->m_lastRoutePath = route.path();
338  }
339 
340  if ( d->m_destinationReached && distanceTarget < 250 ) {
341  return;
342  }
343 
344  if ( !d->m_destinationReached && distanceTarget < 50 ) {
345  d->m_destinationReached = true;
346  d->updateInstruction( d->m_speakerEnabled ? "You have arrived at your destination" : "AppPositive" );
347  return;
348  }
349 
350  if ( distanceTarget > 150 ) {
351  d->m_destinationReached = false;
352  }
353 
354  if ( deviated && !d->m_deviated ) {
355  d->updateInstruction( d->m_speakerEnabled ? "Deviated from the route" : "ListEnd" );
356  }
357  d->m_deviated = deviated;
358  if ( deviated ) {
359  return;
360  }
361 
362  Maneuver::Direction turnType = route.currentSegment().nextRouteSegment().maneuver().direction();
363  if ( !( d->m_lastTurnPoint == route.currentSegment().nextRouteSegment().maneuver().position() ) || turnType != d->m_lastTurnType ) {
364  d->m_lastTurnPoint = route.currentSegment().nextRouteSegment().maneuver().position();
365  d->reset();
366  }
367 
368  int index = route.indexOf(route.currentSegment());
369 
370  qreal const distanceTraversed = route.currentSegment().distance() - distanceManuever;
371  bool const minDistanceTraversed = d->m_lastDistanceTraversed < 40 && distanceTraversed >= 40;
372  bool const announcementAfterTurn = minDistanceTraversed && distanceManuever >= 75;
373  bool const announcement = ( d->m_lastDistance > 850 || announcementAfterTurn ) && distanceManuever <= 850;
374  bool const turn = ( d->m_lastDistance == 0 || d->m_lastDistance > 75 ) && distanceManuever <= 75;
375 
376  bool const announcementDone = d->m_announcementList[index].announcementDone;
377  bool const turnInstructionDone = d->m_announcementList[index].turnInstructionDone;
378 
379  if ( ( announcement && !announcementDone ) || ( turn && !turnInstructionDone ) ) {
380  d->updateInstruction( route.currentSegment(), distanceManuever, turnType );
381  VoiceNavigationModelPrivate::Announcement & curAnnouncement = d->m_announcementList[index];
382  if (announcement){
383  curAnnouncement.announcementDone = true;
384  }
385  if (turn){
386  curAnnouncement.turnInstructionDone = true;
387  }
388  }
389 
390  d->m_lastTurnType = turnType;
391  d->m_lastDistance = distanceManuever;
392  d->m_lastDistanceTraversed = distanceTraversed;
393 }
394 
395 QString VoiceNavigationModel::preview() const
396 {
397  return d->audioFile( d->m_speakerEnabled ? "The Marble team wishes you a pleasant and safe journey!" : "AppPositive" );
398 }
399 
400 QString VoiceNavigationModel::instruction() const
401 {
402  return d->m_announcementText;
403 }
404 
405 }
406 
407 #include "moc_VoiceNavigationModel.cpp"
QString number(int n, int base)
void clear()
bool exists() const const
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
bool isEmpty() const const
Binds a QML item to a specific geodetic location in screen coordinates.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const char * name(StandardAction id)
KGuiItem reset()
QFuture< void > map(Sequence &sequence, MapFunctor function)
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Sep 21 2023 04:12:28 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.