Marble

VoiceNavigationModel.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2012 Dennis Nienhüser <nienhueser@kde.org>
4//
5
6#include "VoiceNavigationModel.h"
7
8#include "Route.h"
9
10#include "MarbleDirs.h"
11#include "MarbleDebug.h"
12
13namespace Marble
14{
15
16class VoiceNavigationModelPrivate
17{
18public:
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
80VoiceNavigationModelPrivate::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
92void VoiceNavigationModelPrivate::reset()
93{
94 m_lastDistance = 0.0;
95 m_lastDistanceTraversed = 0.0;
96}
97
98QString 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
127QString 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
151QString 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
162QString 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
212void 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
234void 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
242void 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
282VoiceNavigationModel::VoiceNavigationModel( QObject *parent ) :
283 QObject( parent ), d( new VoiceNavigationModelPrivate( this ) )
284{
285 // nothing to do
286}
287
288VoiceNavigationModel::~VoiceNavigationModel()
289{
290 delete d;
291}
292
293QString VoiceNavigationModel::speaker() const
294{
295 return d->m_speaker;
296}
297
298void 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
313bool VoiceNavigationModel::isSpeakerEnabled() const
314{
315 return d->m_speakerEnabled;
316}
317
318void 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
327void VoiceNavigationModel::reset()
328{
329 d->reset();
330}
331
332void 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
395QString VoiceNavigationModel::preview() const
396{
397 return d->audioFile( d->m_speakerEnabled ? "The Marble team wishes you a pleasant and safe journey!" : "AppPositive" );
398}
399
400QString VoiceNavigationModel::instruction() const
401{
402 return d->m_announcementText;
403}
404
405}
406
407#include "moc_VoiceNavigationModel.cpp"
QString name(StandardShortcut id)
Binds a QML item to a specific geodetic location in screen coordinates.
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
bool exists(const QString &path)
QString arg(Args &&... args) const const
void clear()
bool isEmpty() const const
QString number(double n, char format, int precision)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.