Files
GoldenCheetah/src/Route.cpp
2015-08-13 22:25:54 +02:00

456 lines
12 KiB
C++

/*
* Copyright (c) 2011,2014 Damien Grauser (Damien.Grauser@pev-geneve.ch)
*
* This program 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 2 of the License, or (at your option)
* any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Route.h"
#include "Athlete.h"
#include "MainWindow.h"
#include "RideCache.h"
#include "RideItem.h"
#include "IntervalItem.h"
#include "RouteParser.h"
#include "RideFile.h"
#include "GProgressDialog.h"
#include <QString>
#include <QFile>
#include <QXmlInputSource>
#include <QXmlSimpleReader>
#define tr(s) QObject::tr(s)
#define pi 3.14159265358979323846
/*
* RouteSegment
*
*/
RouteSegment::RouteSegment() : minLat(180), maxLat(-180), minLon(180), maxLon(-180)
{
_id = QUuid::createUuid(); // in case it isn't set yet
}
RouteSegment::RouteSegment(Routes *routes) : routes(routes)
{
_id = QUuid::createUuid(); // in case it isn't set yet
}
QString RouteSegment::getName() {
return name;
}
void
RouteSegment::setName(QString _name)
{
name = _name;
}
double
RouteSegment::getMinLat() {
return minLat;
}
void
RouteSegment::setMinLat(double _minLat) {
minLat = _minLat;
}
double
RouteSegment::getMaxLat() {
return maxLat;
}
void
RouteSegment::setMaxLat(double _maxLat) {
maxLat = _maxLat;
}
double
RouteSegment::getMinLon() {
return minLon;
}
void
RouteSegment::setMinLon(double _minLon) {
minLon = _minLon;
}
double
RouteSegment::getMaxLon() {
return maxLon;
}
void
RouteSegment::setMaxLon(double _maxLon) {
maxLon = _maxLon;
}
QList<RoutePoint> RouteSegment::getPoints() {
return points;
}
int
RouteSegment::addPoint(RoutePoint _point)
{
points.append(_point);
// Update Min-Max
if (minLat==180 || _point.lat<minLat)
minLat = _point.lat;
if (maxLat==-180 || _point.lat>maxLat)
maxLat = _point.lat;
if (minLon==180 || _point.lon<minLon)
minLon = _point.lon;
if (maxLon==-180 || _point.lon>maxLon)
maxLon = _point.lon;
return points.count();
}
void
RouteSegment::search(RideItem *item, RideFile*ride, QList<IntervalItem*>&here)
{
double minimumprecision = 0.100; //100m
double maximumprecision = 0.010; //10m
double precision = -1;
int found = 0;
int diverge = 0;
int lastpoint = -1; // Last point to match
double start = -1, stop = -1; // Start and stop secs
for (int n=0; n< this->getPoints().count();n++) {
RoutePoint routepoint = this->getPoints().at(n);
bool present = false;
RideFilePoint* point;
for (int i=lastpoint+1; i<ride->dataPoints().count();i++) {
point = ride->dataPoints().at(i);
double minimumdistance = -1;
if (point->lat != 0 && point->lon !=0 &&
ceil(point->lat) != 180 && ceil(point->lon) != 180 &&
ceil(point->lat) != 540 && ceil(point->lon) != 540) {
// Valid GPS value
if (start == -1) {
diverge = 0;
// Calculate distance to route point
double _dist = distance(routepoint.lat, routepoint.lon, point->lat, point->lon) ;
minimumdistance = _dist;
if (precision == -1 || _dist<precision)
precision = _dist;
if (_dist>1) {
// fare away from reference point
i += 50;
} else {
if (_dist<minimumprecision) {
start = 0; //try to start
}
}
}
if (start != -1) {
int end = i+10;
for (int j=i; j<ride->dataPoints().count() && j<end;j++) {
RideFilePoint* nextpoint = ride->dataPoints().at(j);
if (nextpoint->lat != 0 && nextpoint->lon !=0 && ceil(nextpoint->lat) != 180 && ceil(nextpoint->lon) != 180) {
double _nextdist = distance(routepoint.lat, routepoint.lon, nextpoint->lat, nextpoint->lon) ;
if (minimumdistance ==-1 || _nextdist<minimumdistance){
//new minimumdistance
point = nextpoint;
i = j;
minimumdistance = _nextdist;
}
if (_nextdist <= minimumprecision) {
end = j+10;
}
if (_nextdist <= maximumprecision) {
// maximum precision reached
j = end;
}
}
}
if (minimumdistance <= minimumprecision) {
// Close enough from reference point
present = true;
lastpoint = i;
if (start == 0) {
start = point->secs;
precision = 0;
}
if (minimumdistance>precision)
precision = minimumdistance;
break;
}
else {
diverge++;
if (diverge>2) {
start = -1; //try to restart
n = 0;
}
//present = true;
}
}
}
}
stop = point->secs;
if (!present) {
break;
} else {
if (n == this->getPoints().count()-1){
// Add the interval and continue search
IntervalItem *intervalItem = new IntervalItem(item, name,
start, stop,
ride->timeToDistance(start),
ride->timeToDistance(stop),
++found,
QColor(255,127,80),
RideFileInterval::ROUTE);
intervalItem->route = id();
here << intervalItem;
// reset to restart find on next iteration
start = -1;
n=0;
// skip on a few samples to avoid finding the same
// segment again - this happens when a segment is very
// short and ends at lights or top of a hill.
lastpoint += 30;
}
}
}
}
// This function converts decimal degrees to radians
double deg2rad(double deg) {
return (deg * pi / 180);
}
// This function converts radians to decimal degrees
double rad2deg(double rad) {
return (rad * 180 / pi);
}
// lat1, lon1 = point 1, Latitude and Longitude of
// lat2, lon2 = Latitude and Longitude of point 2
double
RouteSegment::distance(double lat1, double lon1, double lat2, double lon2) {
double _theta, _dist;
_theta = lon1 - lon2;
if (_theta == 0 && (lat1 - lat2) == 0)
_dist = 0;
else {
_dist = sin(deg2rad(lat1)) * sin(deg2rad(lat2)) + cos(deg2rad(lat1)) * cos(deg2rad(lat2)) * cos(deg2rad(_theta));
_dist = acos(_dist) * 6371;
}
return (_dist);
}
/*
* Routes (list of RouteSegment)
*
*
*/
Routes::Routes(Context *context, const QDir &home)
{
this->home = home;
this->context = context;
readRoutes();
}
Routes::~Routes()
{
writeRoutes();
}
quint16
Routes::getFingerprint() const
{
// all the QUuids will suffice
QByteArray ba;
foreach(RouteSegment segment, routes) ba += segment.id().toByteArray();
// we spot other things separately
return qChecksum(ba, ba.length());
}
void
Routes::readRoutes()
{
QFile routeFile(home.canonicalPath() + "/routes.xml");
QXmlInputSource source( &routeFile );
QXmlSimpleReader xmlReader;
RouteParser( handler );
xmlReader.setContentHandler(&handler);
xmlReader.setErrorHandler(&handler);
xmlReader.parse( source );
routes = handler.getRoutes();
}
int
Routes::newRoute(QString name)
{
RouteSegment add;
add.setName(name);
routes.insert(0, add);
return 0; // always add at the top
}
void
Routes::deleteRoute(QUuid identifier)
{
// find via Identifier then delete
for(int i=0; i<routes.count(); i++) {
if (routes.at(i).id() == identifier) {
deleteRoute(i);
return;
}
}
}
void
Routes::deleteRoute(int index)
{
// now delete!
routes.removeAt(index);
writeRoutes();
}
void
Routes::renameRoute(QUuid identifier, QString newname)
{
// find via Identifier then delete
for(int i=0; i<routes.count(); i++) {
if (routes.at(i).id() == identifier) {
RouteSegment route = routes.at(i);
route.setName(newname);
routes.replace(i, route);
writeRoutes();
return;
}
}
}
void
Routes::writeRoutes()
{
// update routes.xml
QString file = QString(home.canonicalPath() + "/routes.xml");
RouteParser::serialize(file, routes);
}
void
Routes::search(RideItem *item, RideFile*ride, QList<IntervalItem*>&here)
{
if (ride) {
// search all segments
for (int routecount=0;routecount<routes.count();routecount++) {
RouteSegment *segment = &routes[routecount];
// The third decimal place is worth up to 110 m
if (ride->getMinPoint(RideFile::lat).toDouble()<segment->getMinLat()+0.001 &&
ride->getMaxPoint(RideFile::lat).toDouble()>segment->getMaxLat()-0.001 &&
ride->getMinPoint(RideFile::lon).toDouble()<segment->getMinLon()+0.001 &&
ride->getMaxPoint(RideFile::lon).toDouble()>segment->getMaxLon()-0.001 )
segment->search(item, ride, here);
}
}
}
void
Routes::createRouteFromInterval(IntervalItem *activeInterval)
{
// if refresh is running cancel it now, as we're adding
// a new route to search, so will need to restart
context->athlete->rideCache->cancel();
// create a new route
int index = context->athlete->routes->newRoute("route");
RouteSegment *route = &context->athlete->routes->routes[index];
QRegExp watts("\\([0-9]* *watts\\)");
QString name = activeInterval->name; //activeInterval->text(0).trimmed();
if (name.contains(watts))
name = name.left(name.indexOf(watts)).trimmed();
if (name.length()<4 || name.startsWith("Selection #") )
name = QString(tr("Route #%1")).arg(context->athlete->routes->routes.length());
route->setName(name);
// Construct the route with interval gps data
double dist = 0, lastLat = 0, lastLon = 0;
foreach (RideFilePoint *point, activeInterval->rideItem()->ride()->dataPoints()) {
if (point->secs >= activeInterval->start && point->secs < activeInterval->stop) {
if (lastLat != 0 && lastLon != 0 &&
point->lat != 0 && point->lon != 0 &&
ceil(point->lat) != 180 && ceil(point->lon) != 180) {
// distance ith last point
double _dist = route->distance(lastLat, lastLon, point->lat, point->lon);
if (_dist>=0.001)
dist += _dist;
if (dist>0.02) {
RoutePoint _point = RoutePoint(point->lon, point->lat);
route->addPoint(_point);
dist = 0;
}
}
lastLat = point->lat;
lastLon = point->lon;
}
}
// update on disk
context->athlete->routes->writeRoutes();
// now go and refresh !
context->athlete->rideCache->refresh();
}