+/*
+ * This file is part of jSpeed.
+ *
+ * jSpeed is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * jSpeed is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with jSpeed. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <QtCore/QString>
+#include <QtCore/QDir>
+#include <QtCore/QTimer>
+#include <QtCore/QFile>
+#include <QtCore/QDebug>
+#include <math.h>
+#include "poialerts.h"
+#include "odometer.h"
+#include "settings.h"
+#include "location.h"
+#include "mediaplayer.h"
+
+namespace
+{
+ static double const EARTH_MEAN_RADIUS = 6371.0072;
+ static double const PI = 3.14159265;
+}
+
+inline static double degToRad(double deg)
+{
+ return deg * PI / 180;
+}
+
+inline static double radToDeg(double rad)
+{
+ return rad * 180 / PI;
+}
+
+PoiAlerts::PoiAlerts(): QObject(0), enabled_(false), currentPoi_(0), loaded_(false)
+{
+}
+
+PoiAlerts::~PoiAlerts()
+{
+ end();
+}
+
+PoiAlerts& PoiAlerts::instance()
+{
+ static PoiAlerts instance;
+ return instance;
+}
+
+bool PoiAlerts::start()
+{
+ if(!loaded_)
+ {
+ if(!loadConfig())
+ {
+ return false;
+ }
+ }
+
+ if(enabled_)
+ {
+ connect(&(Odometer::instance()), SIGNAL(dataUpdated()), this, SLOT(onDataUpdated()));
+ }
+
+ return true;
+}
+
+void PoiAlerts::end()
+{
+ if(enabled_)
+ {
+ disconnect(&(Odometer::instance()), SIGNAL(dataUpdated()), this, SLOT(onDataUpdated()));
+ }
+
+ pois_.clear();
+ playedSounds_.clear();
+ travelled_ = 0;
+ enabled_ = false;
+ currentPoi_ = 0;
+}
+
+bool PoiAlerts::loadConfig()
+{
+ loaded_ = true;
+
+ bool enabled = Settings::instance().value("alert_enabled", false).toBool();
+
+ if(!enabled)
+ {
+ end();
+ enabled_ = false;
+ return true;
+ }
+
+ distance_ = Settings::instance().value("alert_distance", 300).toBool();
+ onlyOnRoute_ = Settings::instance().value("alert_only_on_route", true).toBool();
+
+ if(distance_ < 0)
+ {
+ distance_ = 0;
+ }
+
+ QString filename = Settings::instance().value("alert_sound", "").toString();
+
+ if(filename.isEmpty())
+ {
+ filename = "alert.wav";
+ }
+
+ QString soundDir = MediaPlayer::getSoundDir();
+ QString localDir = MediaPlayer::getLocalSoundDir();
+
+ if(!filename.isEmpty() && QFile::exists(soundDir + filename))
+ {
+ file_ = soundDir + filename;
+ }
+ else if(!filename.isEmpty() && QFile::exists(localDir + filename))
+ {
+ file_ = localDir + filename;
+ }
+ else
+ {
+ enabled_ = false;
+ return false;
+ }
+
+ if(!loadPois())
+ {
+ enabled_ = false;
+ return false;
+ }
+
+ if(!enabled_)
+ {
+ enabled_ = true;
+ start();
+ }
+
+ return true;
+}
+
+bool PoiAlerts::loadPois()
+{
+ QString filename = Settings::instance().value("alert_poi_file", "").toString();
+
+ QString poiFile = getPoiDir() + filename;
+
+ if(filename.isEmpty() || !QFile::exists(poiFile))
+ {
+ error_ = "Poi file doesn't exist";
+ return false;
+ }
+
+ PoiReader* reader = PoiReader::getReader(poiFile);
+
+ if(!reader)
+ {
+ error_ = "Unknown file format: " + poiFile;
+ return false;
+ }
+
+ if(!reader->read(pois_))
+ {
+ error_ = reader->error();
+ return false;
+ }
+
+ return true;
+}
+
+double PoiAlerts::getCurrentDistance() const
+{
+ if(!currentPoi_)
+ {
+ return 0.0;
+ }
+
+ return currentDistance_;
+}
+
+QString PoiAlerts::getCurrentPoi() const
+{
+ if(currentPoi_)
+ {
+ return currentPoi_->name;
+ }
+
+ return "";
+}
+
+bool PoiAlerts::poiInView() const
+{
+ return currentPoi_ != 0;
+}
+
+QString const& PoiAlerts::error() const
+{
+ return error_;
+}
+
+void PoiAlerts::onDataUpdated()
+{
+ qDebug() << "Data update";
+
+ static int i = 0;
+ i++;
+
+ double travelled = Odometer::instance().getTotal();
+ const Location::Fix* fix = &(Odometer::instance().getLatestFix());
+
+ if(fix->latitude < 0.01 || fix->longitude < 0.01 || fix->kmSpeed < 0.01)
+ {
+ return;
+ }
+ else
+ {
+ /*if(i < 5)
+ {
+ pois_[0].latitude = fix->latitude;
+ pois_[0].longitude = fix->longitude;
+ }*/
+ }
+
+ double distance;
+ double inRouteMargin = IN_ROUTE_MARGIN + (fix->eph / 1000.0);
+
+ qDebug() << "Eph: " << fix->eph;
+ qDebug() << "In route margin: " << inRouteMargin;
+
+ if(1 == 1 || abs(travelled - travelled_) > 0.03)
+ {
+ travelled_ = travelled;
+
+ for(int i = 0; i < pois_.size(); i++)
+ {
+ if((distance = calculateDistance(pois_.at(i).latitude, pois_.at(i).longitude,
+ fix->latitude, fix->longitude)) <= distance_)
+ {
+ qDebug() << "Distance: " << distance;
+
+ if(onlyOnRoute_)
+ {
+ double track = abs(calculateTrack(fix->latitude, fix->longitude,
+ pois_.at(i).latitude, pois_.at(i).longitude) - fix->track);
+
+ if(track > 180)
+ {
+ track = 360.0 - track;
+ }
+
+ qDebug() << "Real track: " << track;
+ qDebug() << "Epd: " << fix->epd;
+
+ double trackLimit;
+
+ if(distance < (inRouteMargin * 2.0))
+ {
+ trackLimit = 60.0;
+ }
+ else
+ {
+ trackLimit = 90.0 - radToDeg(acos((inRouteMargin + (distance * 0.15)) / distance));
+ }
+
+ qDebug() << "Tracklimit: " << trackLimit;
+
+ if(track < trackLimit)
+ {
+ currentPoi_ = &pois_[i];
+ currentDistance_ = distance;
+ playSound(i);
+ }
+ else
+ {
+ currentPoi_ = 0;
+ }
+
+ }
+ else
+ {
+ currentPoi_ = &pois_[i];
+ currentDistance_ = distance;
+ playSound(i);
+ }
+
+ break;
+ }
+ else
+ {
+ currentPoi_ = 0;
+ qDebug() << "Distance: " << distance;
+ }
+ }
+ }
+
+ qDebug() << '\n';
+}
+
+void PoiAlerts::startTest()
+{
+ Odometer::instance().start();
+ enabled_ = true;
+ distance_ = 100;
+ PoiReader::Poi poi;
+ poi.latitude = 0.0;
+ poi.longitude = 0.0;
+ poi.name = "Mokki";
+ pois_.push_back(poi);
+ onlyOnRoute_ = true;
+ start();
+}
+
+double PoiAlerts::calculateDistance(double latitude1, double longitude1,
+ double latitude2, double longitude2)
+{
+ double dlat = degToRad(latitude1 - latitude2);
+ double dlon = degToRad(longitude1 - longitude2);
+ double y = sin(dlat / 2.0) * sin(dlat / 2.0)
+ + cos(degToRad(latitude2))
+ * cos(degToRad(latitude1))
+ * sin(dlon / 2.0) * sin(dlon / 2.0);
+ double x = 2 * atan2(sqrt(y), sqrt(1 - y));
+ return x * EARTH_MEAN_RADIUS * 1000;
+}
+
+double PoiAlerts::calculateTrack(double latitude1, double longitude1,
+ double latitude2, double longitude2)
+{
+ double dlon = degToRad(longitude2 - longitude1);
+ double lat1Rad = degToRad(latitude1);
+ double lat2Rad = degToRad(latitude2);
+ double y = sin(dlon) * cos(lat2Rad);
+ double x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dlon);
+ double whole;
+ double fraction = modf(radToDeg(atan2(y, x)), &whole);
+ return (int(whole + 360) % 360) + fraction;
+}
+
+void PoiAlerts::playSound(int poiIndex)
+{
+ qDebug() << "Almost play sound";
+
+ if(playedSounds_.indexOf(poiIndex) == -1)
+ {
+ playedSounds_.enqueue(poiIndex);
+
+ qDebug() << "Play sound";
+ MediaPlayer::play(file_);
+
+ QTimer::singleShot(POI_ALERT_INTERVAL * 1000, this, SLOT(removePlayed()));
+ }
+
+}
+
+void PoiAlerts::removePlayed()
+{
+ if(!playedSounds_.isEmpty())
+ {
+ int removed = playedSounds_.dequeue();
+ qDebug() << "Removed: " << removed;
+ }
+}
+
+QString PoiAlerts::getPoiDir()
+{
+ return Settings::getDir() + "pois" + QDir::separator();
+}
+