Added theme scheduler, poi support and speed alarm features.
[jspeed] / src / poialerts.cpp
1 /*
2  * This file is part of jSpeed.
3  *
4  * jSpeed is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * jSpeed is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with jSpeed.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  */
18
19 #include <QtCore/QString>
20 #include <QtCore/QDir>
21 #include <QtCore/QTimer>
22 #include <QtCore/QFile>
23 #include <QtCore/QDebug>
24 #include <math.h>
25 #include "poialerts.h"
26 #include "odometer.h"
27 #include "settings.h"
28 #include "location.h"
29 #include "mediaplayer.h"
30
31 namespace
32 {
33     static double const EARTH_MEAN_RADIUS = 6371.0072;
34     static double const PI = 3.14159265;
35 }
36
37 inline static double degToRad(double deg)
38 {
39     return deg * PI / 180;
40 }
41
42 inline static double radToDeg(double rad)
43 {
44     return rad * 180 / PI;
45 }
46
47 PoiAlerts::PoiAlerts(): QObject(0), enabled_(false), currentPoi_(0), loaded_(false)
48 {
49 }
50
51 PoiAlerts::~PoiAlerts()
52 {
53     end();
54 }
55
56 PoiAlerts& PoiAlerts::instance()
57 {
58     static PoiAlerts instance;
59     return instance;
60 }
61
62 bool PoiAlerts::start()
63 {
64     if(!loaded_)
65     {
66         if(!loadConfig())
67         {
68             return false;
69         }
70     }
71
72     if(enabled_)
73     {
74         connect(&(Odometer::instance()), SIGNAL(dataUpdated()), this, SLOT(onDataUpdated()));
75     }
76
77     return true;
78 }
79
80 void PoiAlerts::end()
81 {
82     if(enabled_)
83     {
84         disconnect(&(Odometer::instance()), SIGNAL(dataUpdated()), this, SLOT(onDataUpdated()));
85     }
86
87     pois_.clear();
88     playedSounds_.clear();
89     travelled_ = 0;
90     enabled_ = false;
91     currentPoi_ = 0;
92 }
93
94 bool PoiAlerts::loadConfig()
95 {
96     loaded_ = true;
97
98     bool enabled = Settings::instance().value("alert_enabled", false).toBool();
99
100     if(!enabled)
101     {
102         end();
103         enabled_ = false;
104         return true;
105     }
106
107     distance_ = Settings::instance().value("alert_distance", 300).toBool();
108     onlyOnRoute_ = Settings::instance().value("alert_only_on_route", true).toBool();
109
110     if(distance_ < 0)
111     {
112         distance_ = 0;
113     }
114
115     QString filename = Settings::instance().value("alert_sound", "").toString();
116
117     if(filename.isEmpty())
118     {
119         filename = "alert.wav";
120     }
121
122     QString soundDir = MediaPlayer::getSoundDir();
123     QString localDir = MediaPlayer::getLocalSoundDir();
124
125     if(!filename.isEmpty() && QFile::exists(soundDir + filename))
126     {
127         file_ = soundDir + filename;
128     }
129     else if(!filename.isEmpty() && QFile::exists(localDir + filename))
130     {
131         file_ = localDir + filename;
132     }
133     else
134     {
135         enabled_ = false;
136         return false;
137     }
138
139     if(!loadPois())
140     {
141         enabled_ = false;
142         return false;
143     }
144
145     if(!enabled_)
146     {
147         enabled_ = true;
148         start();
149     }
150
151     return true;
152 }
153
154 bool PoiAlerts::loadPois()
155 {
156     QString filename = Settings::instance().value("alert_poi_file", "").toString();
157
158     QString poiFile = getPoiDir() + filename;
159
160     if(filename.isEmpty() || !QFile::exists(poiFile))
161     {
162         error_ = "Poi file doesn't exist";
163         return false;
164     }
165
166     PoiReader* reader = PoiReader::getReader(poiFile);
167
168     if(!reader)
169     {
170         error_ = "Unknown file format: " + poiFile;
171         return false;
172     }
173
174     if(!reader->read(pois_))
175     {
176         error_ = reader->error();
177         return false;
178     }
179
180     return true;
181 }
182
183 double PoiAlerts::getCurrentDistance() const
184 {
185     if(!currentPoi_)
186     {
187         return 0.0;
188     }
189
190     return currentDistance_;
191 }
192
193 QString PoiAlerts::getCurrentPoi() const
194 {
195     if(currentPoi_)
196     {
197         return currentPoi_->name;
198     }
199
200     return "";
201 }
202
203 bool PoiAlerts::poiInView() const
204 {
205     return currentPoi_ != 0;
206 }
207
208 QString const& PoiAlerts::error() const
209 {
210     return error_;
211 }
212
213 void PoiAlerts::onDataUpdated()
214 {
215     qDebug() << "Data update";
216
217     static int i = 0;
218     i++;
219
220     double travelled = Odometer::instance().getTotal();
221     const Location::Fix* fix = &(Odometer::instance().getLatestFix());
222
223     if(fix->latitude < 0.01 || fix->longitude < 0.01 || fix->kmSpeed < 0.01)
224     {
225         return;
226     }
227     else
228     {
229         /*if(i < 5)
230         {
231             pois_[0].latitude = fix->latitude;
232             pois_[0].longitude = fix->longitude;
233         }*/
234     }
235
236     double distance;
237     double inRouteMargin = IN_ROUTE_MARGIN + (fix->eph / 1000.0);
238
239     qDebug() << "Eph: " << fix->eph;
240     qDebug() << "In route margin: " << inRouteMargin;
241
242     if(1 == 1 || abs(travelled - travelled_) > 0.03)
243     {
244         travelled_ = travelled;
245
246         for(int i = 0; i < pois_.size(); i++)
247         {
248             if((distance = calculateDistance(pois_.at(i).latitude, pois_.at(i).longitude,
249                                              fix->latitude, fix->longitude)) <= distance_)
250             {
251                 qDebug() << "Distance: " << distance;
252
253                 if(onlyOnRoute_)
254                 {
255                     double track = abs(calculateTrack(fix->latitude, fix->longitude,
256                                                       pois_.at(i).latitude, pois_.at(i).longitude) - fix->track);
257
258                     if(track > 180)
259                     {
260                         track = 360.0 - track;
261                     }
262
263                     qDebug() << "Real track: " << track;
264                     qDebug() << "Epd: " << fix->epd;
265
266                     double trackLimit;
267
268                     if(distance < (inRouteMargin * 2.0))
269                     {
270                         trackLimit = 60.0;
271                     }
272                     else
273                     {
274                         trackLimit = 90.0 - radToDeg(acos((inRouteMargin + (distance * 0.15)) / distance));
275                     }
276
277                     qDebug() << "Tracklimit: " << trackLimit;
278
279                     if(track < trackLimit)
280                     {
281                         currentPoi_ = &pois_[i];
282                         currentDistance_ = distance;
283                         playSound(i);
284                     }
285                     else
286                     {
287                         currentPoi_ = 0;
288                     }
289
290                 }
291                 else
292                 {
293                     currentPoi_ = &pois_[i];
294                     currentDistance_ = distance;
295                     playSound(i);
296                 }
297
298                 break;
299             }
300             else
301             {
302                 currentPoi_ = 0;
303                 qDebug() << "Distance: " << distance;
304             }
305         }
306     }
307
308     qDebug() << '\n';
309 }
310
311 void PoiAlerts::startTest()
312 {
313     Odometer::instance().start();
314     enabled_ = true;
315     distance_ = 100;
316     PoiReader::Poi poi;
317     poi.latitude = 0.0;
318     poi.longitude = 0.0;
319     poi.name = "Mokki";
320     pois_.push_back(poi);
321     onlyOnRoute_ = true;
322     start();
323 }
324
325 double PoiAlerts::calculateDistance(double latitude1, double longitude1,
326                          double latitude2, double longitude2)
327 {
328     double dlat = degToRad(latitude1 - latitude2);
329     double dlon = degToRad(longitude1 - longitude2);
330     double y = sin(dlat / 2.0) * sin(dlat / 2.0)
331         + cos(degToRad(latitude2))
332         * cos(degToRad(latitude1))
333         * sin(dlon / 2.0) * sin(dlon / 2.0);
334     double x = 2 * atan2(sqrt(y), sqrt(1 - y));
335     return x * EARTH_MEAN_RADIUS * 1000;
336 }
337
338 double PoiAlerts::calculateTrack(double latitude1, double longitude1,
339                                  double latitude2, double longitude2)
340 {
341     double dlon = degToRad(longitude2 - longitude1);
342     double lat1Rad = degToRad(latitude1);
343     double lat2Rad = degToRad(latitude2);
344     double y = sin(dlon) * cos(lat2Rad);
345     double x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dlon);
346     double whole;
347     double fraction = modf(radToDeg(atan2(y, x)), &whole);
348     return (int(whole + 360) % 360) + fraction;
349 }
350
351 void PoiAlerts::playSound(int poiIndex)
352 {
353     qDebug() << "Almost play sound";
354
355     if(playedSounds_.indexOf(poiIndex) == -1)
356     {
357         playedSounds_.enqueue(poiIndex);
358
359         qDebug() << "Play sound";
360         MediaPlayer::play(file_);
361
362         QTimer::singleShot(POI_ALERT_INTERVAL * 1000, this, SLOT(removePlayed()));
363     }
364
365 }
366
367 void PoiAlerts::removePlayed()
368 {
369     if(!playedSounds_.isEmpty())
370     {
371         int removed = playedSounds_.dequeue();
372         qDebug() << "Removed: " << removed;
373     }
374 }
375
376 QString PoiAlerts::getPoiDir()
377 {
378     return Settings::getDir() + "pois" + QDir::separator();
379 }
380