diff --git a/gpxsee.pro b/gpxsee.pro index 08a5da4b..fd75bf1e 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -50,7 +50,9 @@ HEADERS += src/config.h \ src/tooltip.h \ src/route.h \ src/routeitem.h \ - src/graphitem.h + src/graphitem.h \ + src/graph.h \ + src/pathitem.h SOURCES += src/main.cpp \ src/gui.cpp \ src/gpx.cpp \ diff --git a/gpxsee.qrc b/gpxsee.qrc index 928d15e9..ac4aedfb 100644 --- a/gpxsee.qrc +++ b/gpxsee.qrc @@ -14,6 +14,7 @@ icons/arrow-left-double.png icons/arrow-right-double.png icons/view-fullscreen.png + icons/office-chart-line-stacked.png lang/gpxsee_cs.qm diff --git a/lang/gpxsee_cs.ts b/lang/gpxsee_cs.ts index ccc3555d..b14809e2 100644 --- a/lang/gpxsee_cs.ts +++ b/lang/gpxsee_cs.ts @@ -5,54 +5,37 @@ ElevationGraph - Distance - Vzdálenost - - - - + Elevation Výška - - km - km - - - - + m m - + Ascent Stoupání - + Descent Klesání - + Minimum Minimum - - mi - mi - - - - + ft ft - + Maximum Maximum @@ -163,370 +146,377 @@ GUI - + GPXSee is distributed under the terms of the GNU General Public License version 3. For more info about GPXSee visit the project homepage at Program GPXSee je distribuován pod podmínkami licence GNU General Public License verze 3. Pro více informací navštivte stránky programu na adrese - + Open file Otevřít soubor - + Open POI file Otevřít POI soubor - + Open Otevřít - + Quit Ukončit - - - + + + Keyboard controls Ovládací klávesy - + Close Zavřít - + Reload Znovu načíst - + Show Zobrazit - - + + File Soubor - - - + + + Data sources Zdroje dat - + Load POI file Nahrát POI soubor - + Close POI files Zavřit POI soubory - + Overlap POIs Překrývat POI - + Show POI labels Zobrazovat názvy POI - + Show POIs Zobrazit POI - + Show map Zobrazit mapu - + Clear tile cache Vymazat mezipaměť dlaždic - - - + + + Next map Následující mapa - + Show tracks Zobrazit cesty - + Show routes Zobrazit trasy - + Show waypoints Zobrazit navigační body - + Waypoint labels Názvy navigačních bodů - + Show graphs Zobrazovat grafy - + Show toolbars Zobrazovat nástrojové lišty - + Metric Metrické - + Imperial Imperiální - + Fullscreen mode Celoobrazovkový režim - + Next Následující - + Previous Předchozí - + Last Poslední - + First První - + Map Mapa - + + Graph + Graf + + + POI POI - + POI files POI soubory - + Data Data - + Display Zobrazit - + Settings Nastavení - + Units Jednotky - + Help Nápověda - + Previous map Předchozí mapa - - + + Date Datum - + Routes Trasy - + No GPX files loaded Nejsou načteny žádné GPX soubory - + %1 files %1 souborů - + Next file Následující soubor - + Version Verze - + Print... Tisknout... - + Export to PDF... Exportovat do PDF... - + Waypoints Navigační body - + Previous file Předchozí soubor - + Route waypoints Body tras - + First file První soubor - + Last file Poslední soubor - + Append modifier Modifikátor nahradit/přidat - + Map (tiles) source URLs are read on program startup from the following file: URL mapových zdrojů (dlaždic) jsou načteny při startu programu z následujícího souboru: - + The file format is one map entry per line, consisting of the map name and tiles URL delimited by a TAB character. The tile X and Y coordinates are replaced with $x and $y in the URL and the zoom level is replaced with $z. An example map file could look like: Formát souboru je jeden mapový záznam na řádku, kde mapový záznam sestává ze jména mapy a URL dlaždic navzájem oddělených tabulátorem. Souřadnice dlaždice jsou v URL nahrazeny řetězci $x a $y, úroven přiblížení (zoom) pak řetězcem $z. Příklad: - + To make GPXSee load a POI file automatically on startup, add the file to the following directory: POI soubory, které se mají automaticky nahrát při startu programu jsou načítány z následujícího adresáře: - + GPX files (*.gpx);;All files (*) Soubory GPX (*.gpx);;Všechny soubory (*) - - + + Line: %1 Řádka: %1 - + GPX files (*.gpx);;CSV files (*.csv);;All files (*) Soubory GPX (*.gpx);;Soubory CSV (*.csv);;Všechny soubory (*) - + Tracks Cesty - - + + About GPXSee O aplikaci GPXSee - + Navigation Navigace - + Map sources Mapové zdroje - + POIs POI body - + + Distance Vzdálenost - + + Time Čas - - + + Error Chyba - + Error loading GPX file: %1 Soubor GPX nelze otevřít: %1 - + Error loading POI file: %1 Soubor POI nelze otevřít: @@ -534,53 +524,76 @@ - HeartRateGraph + GraphView - + + m + m + + + + km + km + + + + ft + ft + + + + mi + mi + + + + s + s + + + + min + min + + + + h + h + + + Distance Vzdálenost - + + Time + Čas + + + + HeartRateGraph + + Heart rate Tep - - - km - km - 1/min 1/min - + Average Průměr - + Maximum Maximum - - - m - m - - - - ft - ft - - - - mi - mi - Misc @@ -610,7 +623,7 @@ RouteItem - + Distance Vzdálenost @@ -618,22 +631,22 @@ ScaleItem - + mi mi - + ft ft - + km km - + m m @@ -642,52 +655,27 @@ SpeedGraph - Distance - Vzdálenost - - - Speed Rychlost - - m - m - - - - km - - - - - ft - ft - - - + km/h km/h - + Average Průměr - + Maximum Maximum - - mi - mi - - - + mi/h mi/h @@ -696,57 +684,32 @@ TemperatureGraph - Distance - Vzdálenost - - - Temperature Teplota - + Average Průměr - + Minimum Minimum - + Maximum Maximum - - m - m - - - - km - km - - - - ft - ft - - - - mi - mi - - - + C C - + F F @@ -754,17 +717,17 @@ TrackItem - + Distance Vzdálenost - + Time Čas - + Date Datum @@ -772,27 +735,27 @@ WaypointItem - + Name Název - + Coordinates Souřadnice - + Elevation Výška - + Date Datum - + Description Popis diff --git a/src/elevationgraph.cpp b/src/elevationgraph.cpp index 3facfa47..06df870b 100644 --- a/src/elevationgraph.cpp +++ b/src/elevationgraph.cpp @@ -45,7 +45,6 @@ ElevationGraph::ElevationGraph(QWidget *parent) : GraphTab(parent) _units = Metric; setYUnits(); - setXLabel(tr("Distance")); setYLabel(tr("Elevation")); setMinYRange(50.0); @@ -67,20 +66,20 @@ void ElevationGraph::setInfo() } } -void ElevationGraph::loadPath(const QVector &data, Type type) +void ElevationGraph::loadGraph(const Graph &graph, Type type, PathItem *path) { qreal ascent = 0, descent = 0; qreal min, max; - if (data.count() < 2) { + if (graph.y.count() < 2) { skipColor(); return; } - max = min = data.at(0).y(); - for (int j = 1; j < data.size(); j++) { - qreal cur = data.at(j).y(); - qreal prev = data.at(j-1).y(); + max = min = graph.y.at(0); + for (int j = 1; j < graph.y.size(); j++) { + qreal cur = graph.y.at(j); + qreal prev = graph.y.at(j-1); if (cur > prev) ascent += cur - prev; @@ -105,17 +104,18 @@ void ElevationGraph::loadPath(const QVector &data, Type type) _routeMin = nMin(_routeMin, min); } - loadData(data, type); + GraphView::loadGraph(graph, path, type); } -void ElevationGraph::loadGPX(const GPX &gpx) +void ElevationGraph::loadGPX(const GPX &gpx, const QList &paths) { - for (int i = 0; i < gpx.tracks().count(); i++) - loadPath(gpx.tracks().at(i)->elevation(), Track); - for (int i = 0; i < gpx.routes().count(); i++) - loadPath(gpx.routes().at(i)->elevation(), Route); + int p = 0; + + for (int i = 0; i < gpx.tracks().count(); i++) + loadGraph(gpx.tracks().at(i)->elevation(), Track, paths.at(p++)); + for (int i = 0; i < gpx.routes().count(); i++) + loadGraph(gpx.routes().at(i)->elevation(), Route, paths.at(p++)); - setXUnits(); setInfo(); redraw(); @@ -135,27 +135,6 @@ void ElevationGraph::clear() GraphView::clear(); } -void ElevationGraph::setXUnits() -{ - if (_units == Metric) { - if (bounds().width() < KMINM) { - GraphView::setXUnits(tr("m")); - setXScale(1); - } else { - GraphView::setXUnits(tr("km")); - setXScale(M2KM); - } - } else { - if (bounds().width() < MIINM) { - GraphView::setXUnits(tr("ft")); - setXScale(M2FT); - } else { - GraphView::setXUnits(tr("mi")); - setXScale(M2MI); - } - } -} - void ElevationGraph::setYUnits() { if (_units == Metric) { @@ -171,9 +150,9 @@ void ElevationGraph::setUnits(enum Units units) { _units = units; - setXUnits(); setYUnits(); setInfo(); + GraphView::setUnits(units); redraw(); } @@ -184,7 +163,6 @@ void ElevationGraph::showTracks(bool show) setInfo(); showGraph(show, Track); - setXUnits(); redraw(); } @@ -194,7 +172,6 @@ void ElevationGraph::showRoutes(bool show) _showRoutes = show; showGraph(show, Route); - setXUnits(); setInfo(); redraw(); diff --git a/src/elevationgraph.h b/src/elevationgraph.h index b49b73b2..b3a75585 100644 --- a/src/elevationgraph.h +++ b/src/elevationgraph.h @@ -4,6 +4,8 @@ #include "graphtab.h" class GPX; +class Graph; +class PathItem; class ElevationGraph : public GraphTab { @@ -13,7 +15,7 @@ public: ElevationGraph(QWidget *parent = 0); QString label() const {return tr("Elevation");} - void loadGPX(const GPX &gpx); + void loadGPX(const GPX &gpx, const QList &paths); void clear(); void setUnits(enum Units units); void showTracks(bool show); @@ -27,11 +29,10 @@ private: qreal ascent() const; qreal descent() const; - void setXUnits(); void setYUnits(); void setInfo(); - void loadPath(const QVector &data, Type type); + void loadGraph(const Graph &graph, Type type, PathItem *path); qreal _trackAscent, _trackDescent; qreal _routeAscent, _routeDescent; diff --git a/src/graphitem.cpp b/src/graphitem.cpp index e9c8d625..b6ddc6f5 100644 --- a/src/graphitem.cpp +++ b/src/graphitem.cpp @@ -1,10 +1,118 @@ -#include -#include +#include #include "graphitem.h" + +static qreal yAtX(const QPainterPath &path, qreal x) +{ + int low = 0; + int high = path.elementCount() - 1; + int mid = 0; + + Q_ASSERT(high > low); + Q_ASSERT(x >= path.elementAt(low).x && x <= path.elementAt(high).x); + + while (low <= high) { + mid = low + ((high - low) / 2); + const QPainterPath::Element &e = path.elementAt(mid); + if (e.x > x) + high = mid - 1; + else if (e.x < x) + low = mid + 1; + else + return e.y; + } + + QLineF l; + if (path.elementAt(mid).x < x) + l = QLineF(path.elementAt(mid).x, path.elementAt(mid).y, + path.elementAt(mid+1).x, path.elementAt(mid+1).y); + else + l = QLineF(path.elementAt(mid-1).x, path.elementAt(mid-1).y, + path.elementAt(mid).x, path.elementAt(mid).y); + + return l.pointAt((x - l.p1().x()) / (l.p2().x() - l.p1().x())).y(); +} + +GraphItem::GraphItem(const Graph &graph, QGraphicsItem *parent) + : QGraphicsObject(parent) +{ + _id = 0; + _pen = QPen(QBrush(Qt::SolidPattern), 0); + _type = Graph::Distance; + + _distancePath.moveTo(graph.distance.first(), -graph.y.first()); + for (int i = 1; i < graph.y.size(); i++) + _distancePath.lineTo(graph.distance.at(i), -graph.y.at(i)); + + if (!graph.time.isEmpty()) { + _timePath.moveTo(graph.time.first(), -graph.y.first()); + for (int i = 1; i < graph.y.size(); i++) + _timePath.lineTo(graph.time.at(i), -graph.y.at(i)); + } +} + +void GraphItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + painter->setPen(_pen); + painter->drawPath((_type == Graph::Distance) ? _distancePath : _timePath); +} + void GraphItem::setColor(const QColor &color) { QBrush brush(color, Qt::SolidPattern); - QPen pen(brush, 0); - setPen(pen); + _pen.setBrush(brush); +} + +qreal GraphItem::yAtX(qreal x) +{ + return ::yAtX((_type == Graph::Distance) ? _distancePath : _timePath, x); +} + +qreal GraphItem::distanceAtTime(qreal time) +{ + int low = 0; + int high = _timePath.elementCount() - 1; + int mid = 0; + + Q_ASSERT(high > low); + Q_ASSERT(time >= _timePath.elementAt(low).x + && time <= _timePath.elementAt(high).x); + + while (low <= high) { + mid = low + ((high - low) / 2); + const QPainterPath::Element &e = _timePath.elementAt(mid); + if (e.x > time) + high = mid - 1; + else if (e.x < time) + low = mid + 1; + else + return _distancePath.elementAt(mid).x; + } + + if (_timePath.elementAt(mid).x < time) + return ((_distancePath.elementAt(mid+1).x + + _distancePath.elementAt(mid).x) / 2.0); + else + return ((_distancePath.elementAt(mid).x + + _distancePath.elementAt(mid-1).x) / 2.0); +} + +void GraphItem::emitSliderPositionChanged(qreal pos) +{ + if (_type == Graph::Time) { + if (!_timePath.isEmpty()) { + if (pos <= _timePath.elementAt(_timePath.elementCount() - 1).x) + emit sliderPositionChanged(distanceAtTime(pos)); + else + emit sliderPositionChanged(_distancePath.elementAt( + _distancePath.elementCount() - 1).x + 1); + } else + emit sliderPositionChanged(_distancePath.elementAt( + _distancePath.elementCount() - 1).x + 1); + } else + emit sliderPositionChanged(pos); } diff --git a/src/graphitem.h b/src/graphitem.h index 39447a81..fc8d1ee2 100644 --- a/src/graphitem.h +++ b/src/graphitem.h @@ -1,20 +1,42 @@ #ifndef GRAPHITEM_H #define GRAPHITEM_H -#include +#include +#include +#include "graph.h" -class GraphItem : public QGraphicsPathItem +class GraphItem : public QGraphicsObject { -public: - GraphItem(const QPainterPath &path, QGraphicsItem * parent = 0) - : QGraphicsPathItem(path, parent) {_id = 0;} + Q_OBJECT +public: + GraphItem(const Graph &graph, QGraphicsItem *parent = 0); + + QRectF boundingRect() const + {return (_type == Graph::Distance) ? _distancePath.boundingRect() + : _timePath.boundingRect();} + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget); + + void setGraphType(Graph::Type type) {_type = type;} int id() const {return _id;} void setId(int id) {_id = id;} void setColor(const QColor &color); + qreal yAtX(qreal x); + qreal distanceAtTime(qreal time); + +signals: + void sliderPositionChanged(qreal); + +public slots: + void emitSliderPositionChanged(qreal); + private: int _id; + QPen _pen; + QPainterPath _distancePath, _timePath; + Graph::Type _type; }; #endif // GRAPHITEM_H diff --git a/src/graphtab.h b/src/graphtab.h index 77a8f82d..a68c386f 100644 --- a/src/graphtab.h +++ b/src/graphtab.h @@ -1,10 +1,12 @@ #ifndef GRAPHTAB_H #define GRAPHTAB_H +#include #include "graphview.h" #include "units.h" class GPX; +class PathItem; class GraphTab : public GraphView { @@ -15,7 +17,7 @@ public: {setFrameShape(QFrame::NoFrame);} virtual QString label() const = 0; - virtual void loadGPX(const GPX &gpx) = 0; + virtual void loadGPX(const GPX &gpx, const QList &paths) = 0; virtual void clear() = 0; virtual void setUnits(enum Units units) = 0; virtual void showTracks(bool show) = 0; diff --git a/src/graphview.cpp b/src/graphview.cpp index e2e61e5a..bb3dfed0 100644 --- a/src/graphview.cpp +++ b/src/graphview.cpp @@ -7,7 +7,9 @@ #include "slideritem.h" #include "sliderinfoitem.h" #include "infoitem.h" +#include "graph.h" #include "graphitem.h" +#include "pathitem.h" #include "graphview.h" @@ -55,6 +57,12 @@ GraphView::GraphView(QWidget *parent) _minYRange = 0.01; _sliderPos = 0; + + _units = Metric; + _graphType = Graph::Distance; + + setGraphType(_graphType); + setUnits(_units); } GraphView::~GraphView() @@ -83,55 +91,101 @@ void GraphView::createYLabel() _yAxis->setLabel(QString("%1 [%2]").arg(_yLabel).arg(_yUnits)); } -void GraphView::setXLabel(const QString &label) -{ - _xLabel = label; - createXLabel(); -} - void GraphView::setYLabel(const QString &label) { _yLabel = label; createYLabel(); } -void GraphView::setXUnits(const QString &units) -{ - _xUnits = units; - createXLabel(); -} - void GraphView::setYUnits(const QString &units) { _yUnits = units; createYLabel(); } -void GraphView::loadData(const QVector &data, int id) +void GraphView::setXUnits() { - QPainterPath path; - GraphItem *pi; - - - if (data.size() < 2) - return; - - path.moveTo(data.at(0).x(), -data.at(0).y()); - for (int i = 1; i < data.size(); i++) { - const QPointF &p = data.at(i); - path.lineTo(p.x(), -p.y()); + if (_graphType == Graph::Distance) { + if (_units == Metric) { + if (bounds().width() < KMINM) { + _xUnits = tr("m"); + _xScale = 1; + } else { + _xUnits = tr("km"); + _xScale = M2KM; + } + } else { + if (bounds().width() < MIINM) { + _xUnits = tr("ft"); + _xScale = M2FT; + } else { + _xUnits = tr("mi"); + _xScale = M2MI; + } + } + } else { + if (bounds().width() < MININS) { + _xUnits = tr("s"); + _xScale = 1; + } else if (bounds().width() < HINS) { + _xUnits = tr("min"); + _xScale = MIN2S; + } else { + _xUnits = tr("h"); + _xScale = H2S; + } } - pi = new GraphItem(path); - pi->setId(id); - pi->setColor(_palette.color()); + createXLabel(); +} - _graphs.append(pi); +void GraphView::setUnits(Units units) +{ + _units = units; + setXUnits(); +} + +void GraphView::setGraphType(Graph::Type type) +{ + _graphType = type; + _bounds = QRectF(); + + for (int i = 0; i < _graphs.count(); i++) { + _graphs.at(i)->setGraphType(type); + updateBounds(_graphs.at(i)->boundingRect()); + } + + if (type == Graph::Distance) + _xLabel = tr("Distance"); + else + _xLabel = tr("Time"); + setXUnits(); + + redraw(); +} + +void GraphView::loadGraph(const Graph &graph, PathItem *path, int id) +{ + if (graph.y.size() < 2) + return; + + GraphItem *gi = new GraphItem(graph); + gi->setGraphType(_graphType); + gi->setId(id); + gi->setColor(_palette.color()); + + connect(this, SIGNAL(sliderPositionChanged(qreal)), gi, + SLOT(emitSliderPositionChanged(qreal))); + connect(gi, SIGNAL(sliderPositionChanged(qreal)), path, + SLOT(moveMarker(qreal))); + + _graphs.append(gi); if (!_hide.contains(id)) { - _visible.append(pi); - _scene->addItem(pi); - updateBounds(path); + _visible.append(gi); + _scene->addItem(gi); + updateBounds(gi->boundingRect()); + setXUnits(); } } @@ -163,7 +217,7 @@ void GraphView::showGraph(bool show, int id) else { addItem(gi); _visible.append(gi); - updateBounds(gi->path()); + updateBounds(gi->boundingRect()); } } } @@ -173,9 +227,9 @@ void GraphView::redraw() redraw(viewport()->size() - QSizeF(MARGIN, MARGIN)); } -void GraphView::updateBounds(const QPainterPath &path) +void GraphView::updateBounds(const QRectF &boundingRect) { - QRectF br = path.boundingRect(); + QRectF br(boundingRect); br.moveTopLeft(QPointF(br.left(), -br.top() - br.height())); _bounds |= br; } @@ -213,8 +267,8 @@ void GraphView::redraw(const QSizeF &size) addItem(_slider); addItem(_info); - rx = RangeF(_bounds.left() * _xScale, _bounds.right() * _xScale); - ry = RangeF(_bounds.top() * _yScale + _yOffset, _bounds.bottom() * _yScale + rx = RangeF(bounds().left() * _xScale, bounds().right() * _xScale); + ry = RangeF(bounds().top() * _yScale + _yOffset, bounds().bottom() * _yScale + _yOffset); if (ry.size() < _minYRange) ry.resize(_minYRange); @@ -299,45 +353,14 @@ void GraphView::clear() _scene->setSceneRect(0, 0, 0, 0); } -static qreal yAtX(const QPainterPath &path, qreal x) -{ - int low = 0; - int high = path.elementCount() - 1; - int mid = 0; - - Q_ASSERT(high > low); - Q_ASSERT(x >= path.elementAt(low).x && x <= path.elementAt(high).x); - - while (low <= high) { - mid = low + ((high - low) / 2); - const QPainterPath::Element &e = path.elementAt(mid); - if (e.x > x) - high = mid - 1; - else if (e.x < x) - low = mid + 1; - else - return e.y; - } - - QLineF l; - if (path.elementAt(mid).x < x) - l = QLineF(path.elementAt(mid).x, path.elementAt(mid).y, - path.elementAt(mid+1).x, path.elementAt(mid+1).y); - else - l = QLineF(path.elementAt(mid-1).x, path.elementAt(mid-1).y, - path.elementAt(mid).x, path.elementAt(mid).y); - - return l.pointAt((x - l.p1().x()) / (l.p2().x() - l.p1().x())).y(); -} - void GraphView::updateSliderPosition() { - if (_bounds.width() <= 0) + if (bounds().width() <= 0) return; - if (_sliderPos <= _bounds.right() && _sliderPos >= _bounds.left()) { - _slider->setPos((_sliderPos / _bounds.width()) * _slider->area().width(), - _slider->area().bottom()); + if (_sliderPos <= bounds().right() && _sliderPos >= bounds().left()) { + _slider->setPos((_sliderPos / bounds().width()) + * _slider->area().width(), _slider->area().bottom()); _slider->setVisible(!_visible.isEmpty()); } else { _slider->setPos(_slider->area().left(), _slider->area().bottom()); @@ -353,16 +376,15 @@ void GraphView::updateSliderInfo() if (!_sliderInfo->isVisible()) return; - const QPainterPath &path = _visible.first()->path(); - QRectF br = path.boundingRect(); + QRectF br(_visible.first()->boundingRect()); if (br.height() < _minYRange) br.adjust(0, -(_minYRange/2 - br.height()/2), 0, _minYRange/2 - br.height()/2); - qreal y = yAtX(path, _sliderPos); + qreal y = _visible.first()->yAtX(_sliderPos); qreal r = (y - br.bottom()) / br.height(); - qreal pos = (_sliderPos / _bounds.width()) * _slider->area().width(); + qreal pos = (_sliderPos / bounds().width()) * _slider->area().width(); SliderInfoItem::Side s = (pos + _sliderInfo->boundingRect().width() > _slider->area().right()) ? SliderInfoItem::Left : SliderInfoItem::Right; @@ -377,7 +399,7 @@ void GraphView::emitSliderPositionChanged(const QPointF &pos) if (_slider->area().width() <= 0) return; - _sliderPos = (pos.x() / _slider->area().width()) * _bounds.width(); + _sliderPos = (pos.x() / _slider->area().width()) * bounds().width(); updateSliderPosition(); emit sliderPositionChanged(_sliderPos); diff --git a/src/graphview.h b/src/graphview.h index af22d467..f1ef7192 100644 --- a/src/graphview.h +++ b/src/graphview.h @@ -8,12 +8,16 @@ #include #include #include "palette.h" +#include "units.h" +#include "graph.h" + class AxisItem; class SliderItem; class SliderInfoItem; class InfoItem; class GraphItem; +class PathItem; class Scene : public QGraphicsScene { @@ -35,26 +39,21 @@ public: GraphView(QWidget *parent = 0); ~GraphView(); - void loadData(const QVector &data, int id = 0); + void loadGraph(const Graph &graph, PathItem *path, int id = 0); int count() const {return _graphs.count();} void redraw(); void clear(); void showGraph(bool show, int id = 0); + void setGraphType(Graph::Type type); + void setUnits(Units units); - const QString &xLabel() const {return _xLabel;} const QString &yLabel() const {return _yLabel;} - const QString &xUnits() const {return _xUnits;} const QString &yUnits() const {return _yUnits;} - qreal xScale() const {return _xScale;} qreal yScale() const {return _yScale;} qreal yOffset() const {return _yOffset;} - - void setXLabel(const QString &label); void setYLabel(const QString &label); - void setXUnits(const QString &units); void setYUnits(const QString &units); - void setXScale(qreal scale) {_xScale = scale;} void setYScale(qreal scale) {_yScale = scale;} void setYOffset(qreal offset) {_yOffset = offset;} @@ -82,11 +81,12 @@ private slots: void newSliderPosition(const QPointF &pos); private: + void setXUnits(); void createXLabel(); void createYLabel(); void updateSliderPosition(); void updateSliderInfo(); - void updateBounds(const QPainterPath &path); + void updateBounds(const QRectF &boundingRect); QRectF graphsBoundingRect() const; void removeItem(QGraphicsItem *item); void addItem(QGraphicsItem *item); @@ -111,6 +111,9 @@ private: QSet _hide; QRectF _bounds; Palette _palette; + + Units _units; + Graph::Type _graphType; }; #endif // GRAPHVIEW_H diff --git a/src/gui.cpp b/src/gui.cpp index a4fa9749..2d123dad 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -173,6 +173,9 @@ QAction *GUI::createPOIFileAction(int index) void GUI::createActions() { + QActionGroup *ag; + + // Action Groups _fileActionGroup = new QActionGroup(this); _fileActionGroup->setExclusive(false); @@ -297,18 +300,35 @@ void GUI::createActions() connect(_showRouteWaypointsAction, SIGNAL(triggered(bool)), _track, SLOT(showRouteWaypoints(bool))); - // Settings actions - _showGraphsAction = new QAction(tr("Show graphs"), this); + // Graph actions + _showGraphsAction = new QAction(QIcon(QPixmap(SHOW_GRAPHS_ICON)), + tr("Show graphs"), this); _showGraphsAction->setCheckable(true); _showGraphsAction->setShortcut(SHOW_GRAPHS_SHORTCUT); connect(_showGraphsAction, SIGNAL(triggered(bool)), this, SLOT(showGraphs(bool))); addAction(_showGraphsAction); + ag = new QActionGroup(this); + ag->setExclusive(true); + _distanceGraphAction = new QAction(tr("Distance"), this); + _distanceGraphAction->setCheckable(true); + _distanceGraphAction->setActionGroup(ag); + _distanceGraphAction->setShortcut(DISTANCE_GRAPH_SHORTCUT); + connect(_distanceGraphAction, SIGNAL(triggered()), this, + SLOT(setDistanceGraph())); + _timeGraphAction = new QAction(tr("Time"), this); + _timeGraphAction->setCheckable(true); + _timeGraphAction->setActionGroup(ag); + _timeGraphAction->setShortcut(TIME_GRAPH_SHORTCUT); + connect(_timeGraphAction, SIGNAL(triggered()), this, + SLOT(setTimeGraph())); + + // Settings actions _showToolbarsAction = new QAction(tr("Show toolbars"), this); _showToolbarsAction->setCheckable(true); connect(_showToolbarsAction, SIGNAL(triggered(bool)), this, SLOT(showToolbars(bool))); - QActionGroup *ag = new QActionGroup(this); + ag = new QActionGroup(this); ag->setExclusive(true); _metricUnitsAction = new QAction(tr("Metric"), this); _metricUnitsAction->setCheckable(true); @@ -368,6 +388,12 @@ void GUI::createMenus() mapMenu->addSeparator(); mapMenu->addAction(_showMapAction); + QMenu *graphMenu = menuBar()->addMenu(tr("Graph")); + graphMenu->addAction(_distanceGraphAction); + graphMenu->addAction(_timeGraphAction); + graphMenu->addSeparator(); + graphMenu->addAction(_showGraphsAction); + QMenu *poiMenu = menuBar()->addMenu(tr("POI")); _poiFilesMenu = poiMenu->addMenu(tr("POI files")); _poiFilesMenu->addActions(_poiFilesActions); @@ -395,7 +421,6 @@ void GUI::createMenus() unitsMenu->addAction(_imperialUnitsAction); settingsMenu->addSeparator(); settingsMenu->addAction(_showToolbarsAction); - settingsMenu->addAction(_showGraphsAction); settingsMenu->addSeparator(); settingsMenu->addAction(_fullscreenAction); @@ -421,6 +446,7 @@ void GUI::createToolBars() _showToolBar = addToolBar(tr("Show")); _showToolBar->addAction(_showPOIAction); _showToolBar->addAction(_showMapAction); + _showToolBar->addAction(_showGraphsAction); _navigationToolBar = addToolBar(tr("Navigation")); _navigationToolBar->addAction(_firstAction); @@ -586,13 +612,14 @@ bool GUI::openFile(const QString &fileName) bool GUI::loadFile(const QString &fileName) { GPX gpx; + QList paths; if (gpx.loadFile(fileName)) { + paths = _track->loadGPX(gpx); for (int i = 0; i < _tabs.count(); i++) - _tabs.at(i)->loadGPX(gpx); + _tabs.at(i)->loadGPX(gpx, paths); updateGraphTabs(); _track->setHidden(false); - _track->loadGPX(gpx); if (_showPOIAction->isChecked()) _track->loadPOI(_poi); @@ -986,7 +1013,7 @@ void GUI::poiFileChecked(int index) void GUI::sliderPositionChanged(qreal pos) { _sliderPos = pos; - _track->movePositionMarker(_sliderPos); + //_track->movePositionMarker(_sliderPos); } void GUI::graphChanged(int index) @@ -1066,6 +1093,18 @@ void GUI::setImperialUnits() updateStatusBarInfo(); } +void GUI::setDistanceGraph() +{ + for (int i = 0; i <_tabs.count(); i++) + _tabs.at(i)->setGraphType(Graph::Distance); +} + +void GUI::setTimeGraph() +{ + for (int i = 0; i <_tabs.count(); i++) + _tabs.at(i)->setGraphType(Graph::Time); +} + void GUI::next() { QString file = _browser->next(); @@ -1158,7 +1197,6 @@ void GUI::writeSettings() settings.setValue(UNITS_SETTING, _imperialUnitsAction->isChecked() ? Imperial : Metric); settings.setValue(SHOW_TOOLBARS_SETTING, _showToolbarsAction->isChecked()); - settings.setValue(SHOW_GRAPHS_SETTING, _showGraphsAction->isChecked()); settings.endGroup(); settings.beginGroup(MAP_SETTINGS_GROUP); @@ -1167,6 +1205,12 @@ void GUI::writeSettings() settings.setValue(SHOW_MAP_SETTING, _showMapAction->isChecked()); settings.endGroup(); + settings.beginGroup(GRAPH_SETTINGS_GROUP); + settings.setValue(SHOW_GRAPHS_SETTING, _showGraphsAction->isChecked()); + settings.setValue(GRAPH_TYPE_SETTING, _timeGraphAction->isChecked() + ? Graph::Time : Graph::Distance); + settings.endGroup(); + settings.beginGroup(POI_SETTINGS_GROUP); settings.setValue(SHOW_POI_SETTING, _showPOIAction->isChecked()); settings.setValue(OVERLAP_POI_SETTING, _overlapPOIAction->isChecked()); @@ -1216,10 +1260,6 @@ void GUI::readSettings() showToolbars(false); else _showToolbarsAction->setChecked(true); - if (settings.value(SHOW_GRAPHS_SETTING, true).toBool() == false) - showGraphs(false); - else - _showGraphsAction->setChecked(true); settings.endGroup(); settings.beginGroup(MAP_SETTINGS_GROUP); @@ -1235,6 +1275,19 @@ void GUI::readSettings() _currentMap = 0; settings.endGroup(); + settings.beginGroup(GRAPH_SETTINGS_GROUP); + if (settings.value(SHOW_GRAPHS_SETTING, true).toBool() == false) + showGraphs(false); + else + _showGraphsAction->setChecked(true); + if (settings.value(GRAPH_TYPE_SETTING, Graph::Distance).toInt() + == Graph::Time) { + setTimeGraph(); + _timeGraphAction->setChecked(true); + } else + _distanceGraphAction->setChecked(true); + settings.endGroup(); + settings.beginGroup(POI_SETTINGS_GROUP); if (settings.value(OVERLAP_POI_SETTING, true).toBool() == false) _track->setPOIOverlap(false); diff --git a/src/gui.h b/src/gui.h index cd5c8f1c..4c7d20eb 100644 --- a/src/gui.h +++ b/src/gui.h @@ -58,8 +58,6 @@ private slots: void graphChanged(int); void poiFileChecked(int); - - void next(); void prev(); void last(); @@ -67,6 +65,8 @@ private slots: void setMetricUnits(); void setImperialUnits(); + void setDistanceGraph(); + void setTimeGraph(); void sliderPositionChanged(qreal pos); @@ -132,6 +132,8 @@ private: QAction *_fullscreenAction; QAction *_clearMapCacheAction; QAction *_showGraphsAction; + QAction *_distanceGraphAction; + QAction *_timeGraphAction; QAction *_showToolbarsAction; QAction *_nextAction; QAction *_prevAction; diff --git a/src/heartrategraph.cpp b/src/heartrategraph.cpp index 2e83cf39..da92ce69 100644 --- a/src/heartrategraph.cpp +++ b/src/heartrategraph.cpp @@ -8,7 +8,6 @@ HeartRateGraph::HeartRateGraph(QWidget *parent) : GraphTab(parent) _showTracks = true; GraphView::setYUnits(tr("1/min")); - setXLabel(tr("Distance")); setYLabel(tr("Heart rate")); setSliderPrecision(0); @@ -25,30 +24,30 @@ void HeartRateGraph::setInfo() clearInfo(); } -void HeartRateGraph::loadGPX(const GPX &gpx) +void HeartRateGraph::loadGPX(const GPX &gpx, const QList &paths) { for (int i = 0; i < gpx.tracks().count(); i++) { - QVector data = gpx.tracks().at(i)->heartRate(); + const Graph &graph = gpx.tracks().at(i)->heartRate(); qreal sum = 0, w = 0; - if (data.count() < 2) { + if (graph.y.count() < 2) { skipColor(); continue; } - for (int j = 1; j < data.size(); j++) { - sum += data.at(j).y() * (data.at(j).x() - data.at(j-1).x()); - w += data.at(j).x() - data.at(j-1).x(); + for (int j = 1; j < graph.y.size(); j++) { + qreal ds = graph.distance.at(j) - graph.distance.at(j-1); + sum += graph.y.at(j) * ds; + w += ds; } _avg.append(QPointF(gpx.tracks().at(i)->distance(), sum/w)); - loadData(data); + GraphView::loadGraph(graph, paths.at(i)); } for (int i = 0; i < gpx.routes().count(); i++) skipColor(); - setXUnits(); setInfo(); redraw(); @@ -74,43 +73,11 @@ void HeartRateGraph::clear() GraphView::clear(); } -void HeartRateGraph::setXUnits() -{ - if (_units == Metric) { - if (bounds().width() < KMINM) { - GraphView::setXUnits(tr("m")); - setXScale(1); - } else { - GraphView::setXUnits(tr("km")); - setXScale(M2KM); - } - } else { - if (bounds().width() < MIINM) { - GraphView::setXUnits(tr("ft")); - setXScale(M2FT); - } else { - GraphView::setXUnits(tr("mi")); - setXScale(M2MI); - } - } -} - -void HeartRateGraph::setUnits(enum Units units) -{ - _units = units; - - setXUnits(); - setInfo(); - - redraw(); -} - void HeartRateGraph::showTracks(bool show) { _showTracks = show; showGraph(show); - setXUnits(); setInfo(); redraw(); diff --git a/src/heartrategraph.h b/src/heartrategraph.h index b193677e..c70fb2a9 100644 --- a/src/heartrategraph.h +++ b/src/heartrategraph.h @@ -13,16 +13,15 @@ public: HeartRateGraph(QWidget *parent = 0); QString label() const {return tr("Heart rate");} - void loadGPX(const GPX &gpx); + void loadGPX(const GPX &gpx, const QList &paths); void clear(); - void setUnits(enum Units units); + void setUnits(enum Units) {} void showTracks(bool show); void showRoutes(bool show) {Q_UNUSED(show);} private: qreal avg() const; qreal max() const {return bounds().bottom();} - void setXUnits(); void setInfo(); QList _avg; diff --git a/src/icons.h b/src/icons.h index 0cf2ae06..705b4770 100644 --- a/src/icons.h +++ b/src/icons.h @@ -8,6 +8,7 @@ #define CLOSE_FILE_ICON ":/icons/dialog-close.png" #define SHOW_POI_ICON ":/icons/flag.png" #define SHOW_MAP_ICON ":/icons/applications-internet.png" +#define SHOW_GRAPHS_ICON ":/icons/office-chart-line-stacked.png" #define QUIT_ICON ":/icons/application-exit.png" #define RELOAD_FILE_ICON ":/icons/view-refresh.png" #define NEXT_FILE_ICON ":/icons/arrow-right.png" diff --git a/src/keys.h b/src/keys.h index 42b81c60..99b4503e 100644 --- a/src/keys.h +++ b/src/keys.h @@ -4,26 +4,28 @@ #include #include -#define NEXT_KEY Qt::Key_Space -#define PREV_KEY Qt::Key_Backspace -#define FIRST_KEY Qt::Key_Home -#define LAST_KEY Qt::Key_End -#define MODIFIER Qt::ShiftModifier +#define NEXT_KEY Qt::Key_Space +#define PREV_KEY Qt::Key_Backspace +#define FIRST_KEY Qt::Key_Home +#define LAST_KEY Qt::Key_End +#define MODIFIER Qt::ShiftModifier -#define QUIT_SHORTCUT QKeySequence::Quit -#define OPEN_SHORTCUT QKeySequence::Open -#define CLOSE_SHORTCUT QKeySequence::Close -#define RELOAD_SHORTCUT QKeySequence::Refresh -#define EXPORT_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_E) -#define SHOW_POI_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_P) -#define SHOW_MAP_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_M) -#define NEXT_MAP_SHORTCUT QKeySequence::Forward -#define PREV_MAP_SHORTCUT QKeySequence::Back -#define SHOW_GRAPHS_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_G) +#define QUIT_SHORTCUT QKeySequence::Quit +#define OPEN_SHORTCUT QKeySequence::Open +#define CLOSE_SHORTCUT QKeySequence::Close +#define RELOAD_SHORTCUT QKeySequence::Refresh +#define EXPORT_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_E) +#define SHOW_POI_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_P) +#define SHOW_MAP_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_M) +#define NEXT_MAP_SHORTCUT QKeySequence::Forward +#define PREV_MAP_SHORTCUT QKeySequence::Back +#define SHOW_GRAPHS_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_G) +#define DISTANCE_GRAPH_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_D) +#define TIME_GRAPH_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_T) #ifdef Q_OS_MAC -#define FULLSCREEN_SHORTCUT QKeySequence(Qt::META + Qt::CTRL + Qt::Key_F) +#define FULLSCREEN_SHORTCUT QKeySequence(Qt::META + Qt::CTRL + Qt::Key_F) #else // Q_OS_MAC -#define FULLSCREEN_SHORTCUT QKeySequence(Qt::Key_F11) +#define FULLSCREEN_SHORTCUT QKeySequence(Qt::Key_F11) #endif // Q_OS_MAC #endif // KEYS_H diff --git a/src/pathitem.h b/src/pathitem.h index adb9b867..de883bfd 100644 --- a/src/pathitem.h +++ b/src/pathitem.h @@ -1,4 +1,19 @@ #ifndef PATHITEM_H #define PATHITEM_H +#include +#include "units.h" + +class PathItem : public QGraphicsObject +{ + Q_OBJECT + +public: + PathItem(QGraphicsItem *parent = 0) : QGraphicsObject(parent) {} + virtual void showMarker(bool show) = 0; + +public slots: + virtual void moveMarker(qreal distance) = 0; +}; + #endif // PATHITEM_H diff --git a/src/route.cpp b/src/route.cpp index 9f0bfcf0..80d9a6f3 100644 --- a/src/route.cpp +++ b/src/route.cpp @@ -12,14 +12,15 @@ Route::Route(const QVector &data) : _data(data) } } -QVector Route::elevation() const +Graph Route::elevation() const { - QVector graph; + Graph graph; for (int i = 0; i < _data.size(); i++) if (_data.at(i).hasElevation()) - graph.append(QPointF(_dd.at(i), _data.at(i).elevation() - - _data.at(i).geoidHeight())); + graph.y.append(_data.at(i).elevation() - _data.at(i).geoidHeight()); + + graph.distance = _dd; return graph; } diff --git a/src/route.h b/src/route.h index 60a654d7..2371a421 100644 --- a/src/route.h +++ b/src/route.h @@ -3,6 +3,7 @@ #include #include "waypoint.h" +#include "graph.h" class Route { @@ -10,7 +11,7 @@ public: Route(const QVector &data); const QVector &route() const {return _data;} - QVector elevation() const; + Graph elevation() const; qreal distance() const; diff --git a/src/routeitem.cpp b/src/routeitem.cpp index 931628ed..87df61a8 100644 --- a/src/routeitem.cpp +++ b/src/routeitem.cpp @@ -34,7 +34,7 @@ void RouteItem::updateShape() } RouteItem::RouteItem(const Route &route, QGraphicsItem *parent) - : QGraphicsItem(parent) + : PathItem(parent) { WaypointItem *wi; diff --git a/src/routeitem.h b/src/routeitem.h index e2be4d62..e987b8de 100644 --- a/src/routeitem.h +++ b/src/routeitem.h @@ -1,14 +1,17 @@ #ifndef ROUTEITEM_H #define ROUTEITEM_H -#include +#include +#include "pathitem.h" #include "markeritem.h" #include "route.h" #include "units.h" -class RouteItem : public QGraphicsItem +class RouteItem : public PathItem { + Q_OBJECT + public: RouteItem(const Route &route, QGraphicsItem *parent = 0); diff --git a/src/settings.h b/src/settings.h index 3b34238e..2b89035f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -8,7 +8,10 @@ #define SETTINGS_SETTINGS_GROUP "Settings" #define UNITS_SETTING "units" #define SHOW_TOOLBARS_SETTING "toolbar" -#define SHOW_GRAPHS_SETTING "graphs" + +#define GRAPH_SETTINGS_GROUP "Graph" +#define SHOW_GRAPHS_SETTING "show" +#define GRAPH_TYPE_SETTING "type" #define MAP_SETTINGS_GROUP "Map" #define CURRENT_MAP_SETTING "map" diff --git a/src/slideritem.cpp b/src/slideritem.cpp index bfb18b8e..6d4fc9f4 100644 --- a/src/slideritem.cpp +++ b/src/slideritem.cpp @@ -4,7 +4,7 @@ #define SIZE 10 -SliderItem::SliderItem(QGraphicsObject *parent) : QGraphicsObject(parent) +SliderItem::SliderItem(QGraphicsItem *parent) : QGraphicsObject(parent) { setFlag(ItemIsMovable); setFlag(ItemSendsGeometryChanges); diff --git a/src/slideritem.h b/src/slideritem.h index 39a45d49..ab490a99 100644 --- a/src/slideritem.h +++ b/src/slideritem.h @@ -1,14 +1,14 @@ #ifndef SLIDERITEM_H #define SLIDERITEM_H -#include +#include class SliderItem : public QGraphicsObject { Q_OBJECT public: - SliderItem(QGraphicsObject *parent = 0); + SliderItem(QGraphicsItem *parent = 0); QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, diff --git a/src/speedgraph.cpp b/src/speedgraph.cpp index f792ce75..ea8e95e1 100644 --- a/src/speedgraph.cpp +++ b/src/speedgraph.cpp @@ -9,7 +9,6 @@ SpeedGraph::SpeedGraph(QWidget *parent) : GraphTab(parent) _showTracks = true; setYUnits(); - setXLabel(tr("Distance")); setYLabel(tr("Speed")); setSliderPrecision(1); @@ -26,11 +25,11 @@ void SpeedGraph::setInfo() clearInfo(); } -void SpeedGraph::loadGPX(const GPX &gpx) +void SpeedGraph::loadGPX(const GPX &gpx, const QList &paths) { for (int i = 0; i < gpx.tracks().count(); i++) { - QVector data = gpx.tracks().at(i)->speed(); - if (data.count() < 2) { + const Graph &graph = gpx.tracks().at(i)->speed(); + if (graph.y.count() < 2) { skipColor(); continue; } @@ -38,13 +37,12 @@ void SpeedGraph::loadGPX(const GPX &gpx) _avg.append(QPointF(gpx.tracks().at(i)->distance(), gpx.tracks().at(i)->distance() / gpx.tracks().at(i)->time())); - loadData(data); + GraphView::loadGraph(graph, paths.at(i)); } for (int i = 0; i < gpx.routes().count(); i++) skipColor(); - setXUnits(); setInfo(); redraw(); @@ -70,27 +68,6 @@ void SpeedGraph::clear() GraphView::clear(); } -void SpeedGraph::setXUnits() -{ - if (_units == Metric) { - if (bounds().width() < KMINM) { - GraphView::setXUnits(tr("m")); - setXScale(1); - } else { - GraphView::setXUnits(tr("km")); - setXScale(M2KM); - } - } else { - if (bounds().width() < MIINM) { - GraphView::setXUnits(tr("ft")); - setXScale(M2FT); - } else { - GraphView::setXUnits(tr("mi")); - setXScale(M2MI); - } - } -} - void SpeedGraph::setYUnits() { if (_units == Metric) { @@ -106,9 +83,9 @@ void SpeedGraph::setUnits(enum Units units) { _units = units; - setXUnits(); setYUnits(); setInfo(); + GraphView::setUnits(units); redraw(); } @@ -118,7 +95,6 @@ void SpeedGraph::showTracks(bool show) _showTracks = show; showGraph(show); - setXUnits(); setInfo(); redraw(); diff --git a/src/speedgraph.h b/src/speedgraph.h index b483e7dc..e5c50831 100644 --- a/src/speedgraph.h +++ b/src/speedgraph.h @@ -14,7 +14,7 @@ public: SpeedGraph(QWidget *parent = 0); QString label() const {return tr("Speed");} - void loadGPX(const GPX &gpx); + void loadGPX(const GPX &gpx, const QList &paths); void clear(); void setUnits(enum Units units); void showTracks(bool show); @@ -23,7 +23,6 @@ public: private: qreal avg() const; qreal max() const {return bounds().bottom();} - void setXUnits(); void setYUnits(); void setInfo(); diff --git a/src/temperaturegraph.cpp b/src/temperaturegraph.cpp index 174e0d88..e5435229 100644 --- a/src/temperaturegraph.cpp +++ b/src/temperaturegraph.cpp @@ -8,7 +8,6 @@ TemperatureGraph::TemperatureGraph(QWidget *parent) : GraphTab(parent) _showTracks = true; setYUnits(); - setXLabel(tr("Distance")); setYLabel(tr("Temperature")); setSliderPrecision(1); @@ -27,30 +26,30 @@ void TemperatureGraph::setInfo() clearInfo(); } -void TemperatureGraph::loadGPX(const GPX &gpx) +void TemperatureGraph::loadGPX(const GPX &gpx, const QList &paths) { for (int i = 0; i < gpx.tracks().count(); i++) { - QVector data = gpx.tracks().at(i)->temperature(); + const Graph &graph = gpx.tracks().at(i)->temperature(); qreal sum = 0, w = 0; - if (data.count() < 2) { + if (graph.y.count() < 2) { skipColor(); continue; } - for (int j = 1; j < data.size(); j++) { - sum += data.at(j).y() * (data.at(j).x() - data.at(j-1).x()); - w += data.at(j).x() - data.at(j-1).x(); + for (int j = 1; j < graph.y.size(); j++) { + qreal ds = graph.distance.at(j) - graph.distance.at(j-1); + sum += graph.y.at(j) * ds; + w += ds; } _avg.append(QPointF(gpx.tracks().at(i)->distance(), sum/w)); - loadData(data); + GraphView::loadGraph(graph, paths.at(i)); } for (int i = 0; i < gpx.routes().count(); i++) skipColor(); - setXUnits(); setInfo(); redraw(); @@ -76,27 +75,6 @@ void TemperatureGraph::clear() GraphView::clear(); } -void TemperatureGraph::setXUnits() -{ - if (_units == Metric) { - if (bounds().width() < KMINM) { - GraphView::setXUnits(tr("m")); - setXScale(1); - } else { - GraphView::setXUnits(tr("km")); - setXScale(M2KM); - } - } else { - if (bounds().width() < MIINM) { - GraphView::setXUnits(tr("ft")); - setXScale(M2FT); - } else { - GraphView::setXUnits(tr("mi")); - setXScale(M2MI); - } - } -} - void TemperatureGraph::setYUnits() { if (_units == Metric) { @@ -114,9 +92,9 @@ void TemperatureGraph::setUnits(enum Units units) { _units = units; - setXUnits(); setYUnits(); setInfo(); + GraphView::setUnits(units); redraw(); } @@ -126,7 +104,6 @@ void TemperatureGraph::showTracks(bool show) _showTracks = show; showGraph(show); - setXUnits(); setInfo(); redraw(); diff --git a/src/temperaturegraph.h b/src/temperaturegraph.h index 27a61891..4960f724 100644 --- a/src/temperaturegraph.h +++ b/src/temperaturegraph.h @@ -13,7 +13,7 @@ public: TemperatureGraph(QWidget *parent = 0); QString label() const {return tr("Temperature");} - void loadGPX(const GPX &gpx); + void loadGPX(const GPX &gpx, const QList &paths); void clear(); void setUnits(enum Units units); void showTracks(bool show); @@ -23,7 +23,6 @@ private: qreal avg() const; qreal min() const {return bounds().top();} qreal max() const {return bounds().bottom();} - void setXUnits(); void setYUnits(); void setInfo(); diff --git a/src/track.cpp b/src/track.cpp index 04834246..e4da1b37 100644 --- a/src/track.cpp +++ b/src/track.cpp @@ -8,39 +8,39 @@ #define WINDOW_HE 11 #define WINDOW_HF 3 -static bool lt(const QPointF &p1, const QPointF &p2) +static bool lt(qreal v1, qreal v2) { - return p1.y() < p2.y(); + return v1 < v2; } -static qreal median(QVector v) +static qreal median(QVector v) { qSort(v.begin(), v.end(), lt); - return v.at(v.size() / 2).y(); + return v.at(v.size() / 2); } -static qreal MAD(QVector v, qreal m) +static qreal MAD(QVector v, qreal m) { for (int i = 0; i < v.size(); i++) - v[i].setY(qAbs(v.at(i).y() - m)); + v[i] = (qAbs(v.at(i) - m)); qSort(v.begin(), v.end(), lt); - return v.at(v.size() / 2).y(); + return v.at(v.size() / 2); } -static QVector eliminate(const QVector &v, int window) +static QVector eliminate(const QVector &v, int window) { QList rm; - QVector ret; + QVector ret; qreal m, M; if (v.size() < window) - return QVector(v); + return QVector(v); for (int i = window/2; i < v.size() - window/2; i++) { m = median(v.mid(i - window/2, window)); M = MAD(v.mid(i - window/2, window), m); - if (qAbs((0.6745 * (v.at(i).y() - m)) / M) > 3.5) + if (qAbs((0.6745 * (v.at(i) - m)) / M) > 3.5) rm.append(i); } @@ -55,26 +55,26 @@ static QVector eliminate(const QVector &v, int window) return ret; } -static QVector filter(const QVector &v, int window) +static QVector filter(const QVector &v, int window) { qreal acc = 0; - QVector ret; + QVector ret; if (v.size() < window) - return QVector(v); + return QVector(v); for (int i = 0; i < window; i++) - acc += v.at(i).y(); + acc += v.at(i); for (int i = 0; i <= window/2; i++) - ret.append(QPointF(v.at(i).x(), acc/window)); + ret.append(acc/window); for (int i = window/2 + 1; i < v.size() - window/2; i++) { - acc += v.at(i + window/2).y() - v.at(i - (window/2 + 1)).y(); - ret.append(QPointF(v.at(i).x(), acc/window)); + acc += v.at(i + window/2) - v.at(i - (window/2 + 1)); + ret.append(acc/window); } for (int i = v.size() - window/2; i < v.size(); i++) - ret.append(QPointF(v.at(i).x(), acc/window)); + ret.append(acc/window); return ret; } @@ -82,80 +82,107 @@ static QVector filter(const QVector &v, int window) Track::Track(const QVector &data) : _data(data) { qreal dist = 0; + qint64 time; - _dd.append(dist); + _dd.append(0); + _td.append(0); for (int i = 1; i < data.count(); i++) { dist += llDistance(data.at(i).coordinates(), data.at(i-1).coordinates()); _dd.append(dist); + if (data.first().hasTimestamp() && data.at(i).hasTimestamp()) { + time = _data.first().timestamp().msecsTo(_data.at(i).timestamp()); + _td.append((qreal)time / 1000.0); + } } + + if (_dd.size() != _td.size()) + _td.clear(); } -QVector Track::elevation() const +Graph Track::elevation() const { - QVector raw; + Graph ret; + QVector raw; + if (!_data.size()) - return raw; + return ret; for (int i = 0; i < _data.size(); i++) if (_data.at(i).hasElevation()) - raw.append(QPointF(_dd.at(i), _data.at(i).elevation() - - _data.at(i).geoidHeight())); + raw.append(_data.at(i).elevation() - _data.at(i).geoidHeight()); - return filter(raw, WINDOW_EF); + ret.y = filter(raw, WINDOW_EF); + ret.distance = _dd; + ret.time = _td; + + return ret; } -QVector Track::speed() const +Graph Track::speed() const { - qreal v, ds; - qint64 dt; - QVector raw; + Graph ret; + qreal v, ds, dt; + QVector raw; + if (!_data.size()) - return raw; + return ret; - raw.append(QPointF(0, 0)); + raw.append(0); for (int i = 1; i < _data.size(); i++) { if (_data.at(i).hasSpeed()) v = _data.at(i).speed(); else if (_data.at(i).hasTimestamp()) { - dt = _data.at(i-1).timestamp().msecsTo(_data.at(i).timestamp()); + dt = _td.at(i) - _td.at(i-1); if (!dt) continue; ds = _dd.at(i) - _dd.at(i-1); - v = ds / ((qreal)dt / 1000.0); + v = ds / dt; } else continue; - raw.append(QPointF(_dd.at(i), v)); + raw.append(v); } - return filter(eliminate(raw, WINDOW_SE), WINDOW_SF); + ret.y = filter(eliminate(raw, WINDOW_SE), WINDOW_SF); + ret.distance = _dd; + ret.time = _td; + + return ret; } -QVector Track::heartRate() const +Graph Track::heartRate() const { - QVector raw; + Graph ret; + QVector raw; if (!_data.size()) - return raw; + return ret; for (int i = 0; i < _data.count(); i++) if (_data.at(i).hasHeartRate()) - raw.append(QPointF(_dd.at(i), _data.at(i).heartRate())); + raw.append(_data.at(i).heartRate()); - return filter(eliminate(raw, WINDOW_HE), WINDOW_HF); + ret.y = filter(eliminate(raw, WINDOW_HE), WINDOW_HF); + ret.distance = _dd; + ret.time = _td; + + return ret; } -QVector Track::temperature() const +Graph Track::temperature() const { - QVector graph; + Graph ret; for (int i = 0; i < _data.size(); i++) if (_data.at(i).hasTemperature()) - graph.append(QPointF(_dd.at(i), _data.at(i).temperature())); + ret.y.append(_data.at(i).temperature()); - return graph; + ret.distance = _dd; + ret.time = _td; + + return ret; } qreal Track::distance() const diff --git a/src/track.h b/src/track.h index a2c03683..d6a515ce 100644 --- a/src/track.h +++ b/src/track.h @@ -4,6 +4,7 @@ #include #include #include "trackpoint.h" +#include "graph.h" class Track { @@ -11,10 +12,10 @@ public: Track(const QVector &data); const QVector &track() const {return _data;} - QVector elevation() const; - QVector speed() const; - QVector heartRate() const; - QVector temperature() const; + Graph elevation() const; + Graph speed() const; + Graph heartRate() const; + Graph temperature() const; qreal distance() const; qreal time() const; @@ -25,6 +26,7 @@ public: private: const QVector &_data; QVector _dd; + QVector _td; }; #endif // TRACK_H diff --git a/src/trackitem.cpp b/src/trackitem.cpp index 69166bc2..13f53997 100644 --- a/src/trackitem.cpp +++ b/src/trackitem.cpp @@ -38,7 +38,7 @@ void TrackItem::updateShape() } TrackItem::TrackItem(const Track &track, QGraphicsItem *parent) - : QGraphicsItem(parent) + : PathItem(parent) { const QVector &t = track.track(); Q_ASSERT(t.count() >= 2); diff --git a/src/trackitem.h b/src/trackitem.h index 8483070b..11e694fd 100644 --- a/src/trackitem.h +++ b/src/trackitem.h @@ -1,15 +1,18 @@ #ifndef TRACKITEM_H #define TRACKITEM_H -#include #include +#include +#include "pathitem.h" #include "units.h" #include "track.h" #include "markeritem.h" -class TrackItem : public QGraphicsItem +class TrackItem : public PathItem { + Q_OBJECT + public: TrackItem(const Track &track, QGraphicsItem *parent = 0); diff --git a/src/trackview.cpp b/src/trackview.cpp index c6d06c8b..845a6d09 100644 --- a/src/trackview.cpp +++ b/src/trackview.cpp @@ -45,7 +45,7 @@ TrackView::TrackView(QWidget *parent) _showRouteWaypoints = true; _plot = false; - _markerPos = 0; + //_markerPos = 0; } TrackView::~TrackView() @@ -54,11 +54,11 @@ TrackView::~TrackView() delete _mapScale; } -void TrackView::addTrack(const Track &track) +PathItem *TrackView::addTrack(const Track &track) { if (track.isNull()) { _palette.color(); - return; + return 0; } TrackItem *ti = new TrackItem(track); @@ -68,15 +68,17 @@ void TrackView::addTrack(const Track &track) ti->setScale(1.0/_scale); ti->setColor(_palette.color()); ti->setVisible(_showTracks); - ti->moveMarker(_markerPos); + //ti->moveMarker(_markerPos); _scene->addItem(ti); + + return ti; } -void TrackView::addRoute(const Route &route) +PathItem *TrackView::addRoute(const Route &route) { if (route.isNull()) { _palette.color(); - return; + return 0; } RouteItem *ri = new RouteItem(route); @@ -88,8 +90,10 @@ void TrackView::addRoute(const Route &route) ri->setVisible(_showRoutes); ri->showWaypoints(_showRouteWaypoints); ri->showWaypointLabels(_showWaypointLabels); - ri->moveMarker(_markerPos); + //ri->moveMarker(_markerPos); _scene->addItem(ri); + + return ri; } void TrackView::addWaypoints(const QList &waypoints) @@ -111,18 +115,25 @@ void TrackView::addWaypoints(const QList &waypoints) _scale = mapScale(_zoom); } -void TrackView::loadGPX(const GPX &gpx) +QList TrackView::loadGPX(const GPX &gpx) { + QList paths; + PathItem *pi; + int zoom = _zoom; - for (int i = 0; i < gpx.tracks().count(); i++) - addTrack(*(gpx.tracks().at(i))); - for (int i = 0; i < gpx.routes().count(); i++) - addRoute(*(gpx.routes().at(i))); + for (int i = 0; i < gpx.tracks().count(); i++) { + if ((pi = addTrack(*(gpx.tracks().at(i))))) + paths.append(pi); + } + for (int i = 0; i < gpx.routes().count(); i++) { + if ((pi = addRoute(*(gpx.routes().at(i))))) + paths.append(pi); + } addWaypoints(gpx.waypoints()); if (_tracks.empty() && _routes.empty() && _waypoints.empty()) - return; + return paths; if ((_tracks.size() > 1 && _zoom < zoom) || (_routes.size() > 1 && _zoom < zoom) @@ -138,6 +149,8 @@ void TrackView::loadGPX(const GPX &gpx) _mapScale->setZoom(_zoom, -(br.center().ry() * _scale)); if (_mapScale->scene() != _scene) _scene->addItem(_mapScale); + + return paths; } QRectF TrackView::trackBoundingRect() const @@ -478,9 +491,10 @@ void TrackView::clear() _scene->setSceneRect(QRectF()); - _markerPos = 0; + //_markerPos = 0; } +/* void TrackView::movePositionMarker(qreal val) { _markerPos = val; @@ -491,6 +505,7 @@ void TrackView::movePositionMarker(qreal val) for (int i = 0; i < _routes.size(); i++) _routes.at(i)->moveMarker(val); } +*/ void TrackView::showTracks(bool show) { diff --git a/src/trackview.h b/src/trackview.h index 13794e7a..27b579e2 100644 --- a/src/trackview.h +++ b/src/trackview.h @@ -18,6 +18,7 @@ class TrackItem; class RouteItem; class WaypointItem; class ScaleItem; +class PathItem; class TrackView : public QGraphicsView { @@ -27,7 +28,7 @@ public: TrackView(QWidget *parent = 0); ~TrackView(); - void loadGPX(const GPX &gpx); + QList loadGPX(const GPX &gpx); void loadPOI(const POI &poi); void clearPOI(); @@ -43,7 +44,7 @@ public: int waypointCount() const {return _waypoints.count();} public slots: - void movePositionMarker(qreal val); + //void movePositionMarker(qreal val); void redraw(); void setPOIOverlap(bool overlap); @@ -55,8 +56,8 @@ public slots: void showRouteWaypoints(bool show); private: - void addTrack(const Track &track); - void addRoute(const Route &route); + PathItem *addTrack(const Track &track); + PathItem *addRoute(const Route &route); void addWaypoints(const QList &waypoints); void addPOI(const QVector &waypoints); @@ -103,7 +104,7 @@ private: bool _showRouteWaypoints; bool _plot; - qreal _markerPos; + //qreal _markerPos; }; #endif // TRACKVIEW_H diff --git a/src/units.h b/src/units.h index cf53388b..734c661e 100644 --- a/src/units.h +++ b/src/units.h @@ -13,10 +13,14 @@ enum Units { #define MS2MIH 2.236936290000 // m/s -> mi/h #define FT2MI 0.000189393939 // ft -> mi #define MM2IN 0.039370100000 // mm -> in +#define H2S 0.000277777778 // h -> s +#define MIN2S 0.016666666667 // min -> s #define KMINM 1000 // 1 km in m #define MIINFT 5280 // 1 mi in ft -#define MIINM 1609.344 // 1mi in m +#define MIINM 1609.344 // 1 mi in m +#define MININS 60 // 1 min in s +#define HINS 3600 // 1 hins #define C2FS 1.8 // Celsius to Farenheit - scale #define C2FO 32 // Celsius to Farenheit - offset