From 0c1b533a3b775fdcba65ebb3fd2c07c21795c4f5 Mon Sep 17 00:00:00 2001 From: Tobias Weber Date: Fri, 3 Jan 2025 13:36:42 +0100 Subject: [PATCH] plot exporting --- src/gui/dialogs/about.cpp | 2 +- src/gui/dialogs/reports.cpp | 85 ++++++++++++++++++++++++++++- src/gui/dialogs/reports.h | 7 +++ src/gui/dialogs/statistics.cpp | 86 ++++++++++++++++++++++++++++- src/gui/dialogs/statistics.h | 7 +++ src/gui/docks/track_infos.cpp | 98 ++++++++++++++++++++++++++++++---- src/gui/docks/track_infos.h | 7 ++- src/lib/track.h | 52 ++++++++++++++++-- 8 files changed, 325 insertions(+), 19 deletions(-) diff --git a/src/gui/dialogs/about.cpp b/src/gui/dialogs/about.cpp index 6b85513..ee169ff 100644 --- a/src/gui/dialogs/about.cpp +++ b/src/gui/dialogs/about.cpp @@ -65,7 +65,7 @@ About::About(QWidget *parent, const QIcon *progIcon) { namespace alg = boost::algorithm; - setWindowTitle("About"); + setWindowTitle("About " TRACKS_TITLE); setSizeGripEnabled(true); diff --git a/src/gui/dialogs/reports.cpp b/src/gui/dialogs/reports.cpp index 8189162..829dc29 100644 --- a/src/gui/dialogs/reports.cpp +++ b/src/gui/dialogs/reports.cpp @@ -19,6 +19,14 @@ #include #include +#if __has_include() + #include + namespace fs = std::filesystem; +#else + #include + namespace fs = boost::filesystem; +#endif + // table columns #define TAB_DATE 0 @@ -53,6 +61,14 @@ Reports::Reports(QWidget* parent) m_plot->xAxis->setTicker(QSharedPointer{ticker}); connect(m_plot/*.get()*/, &QCustomPlot::mouseMove, this, &Reports::PlotMouseMove); + connect(m_plot/*.get()*/, &QCustomPlot::mousePress, this, &Reports::PlotMouseClick); + + // context menu + m_context = std::make_shared(this); + QIcon iconSavePdf = QIcon::fromTheme("image-x-generic"); + QAction *actionSavePdf = new QAction(iconSavePdf, "Save Image...", m_context.get()); + m_context->addAction(actionSavePdf); + connect(actionSavePdf, &QAction::triggered, this, &Reports::SavePlotPdf); // "all tracks" checkbox m_all_tracks = std::make_shared(plot_panel); @@ -201,6 +217,9 @@ Reports::Reports(QWidget* parent) m_table->setColumnWidth(TAB_TIME_SUM, settings.value("dlg_reports/timesum_col").toInt()); else m_table->setColumnWidth(TAB_TIME_SUM, 150); + + if(settings.contains("dlg_reports/recent_pdfs")) + m_pdfdir = settings.value("dlg_reports/recent_pdfs").toString().toStdString(); } @@ -442,7 +461,7 @@ void Reports::PlotDistances() graph->setData(epochs, dists, true); graph->setLineStyle(QCPGraph::lsLine); - graph->setScatterStyle(QCPScatterStyle{QCPScatterStyle::ssCircle, pen, brush, 6.}); + graph->setScatterStyle(QCPScatterStyle{QCPScatterStyle::ssDisc, pen, brush, 6.}); graph->setPen(pen); //graph->setBrush(brush); }; @@ -476,6 +495,9 @@ void Reports::PlotDistances() } +/** + * the mouse has been moved in the plot widget + */ void Reports::PlotMouseMove(QMouseEvent *evt) { if(!m_plot) @@ -504,6 +526,66 @@ void Reports::PlotMouseMove(QMouseEvent *evt) } +/** + * the mouse has been clicked in the plot widget + */ +void Reports::PlotMouseClick(QMouseEvent *evt) +{ + if(!m_plot || !m_context) + return; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QPoint pos = evt->position(); +#else + QPoint pos = evt->pos(); +#endif + + if(evt->buttons() & Qt::RightButton) + { + QPoint posGlobal = m_plot->mapToGlobal(pos); + posGlobal.rx() += 4; + posGlobal.ry() += 4; + + m_context->popup(posGlobal); + } +} + + +/** + * save the plot as a pdf image + */ +void Reports::SavePlotPdf() +{ + if(!m_plot) + return; + + auto filedlg = std::make_shared( + this, "Save Plot as Image", m_pdfdir.c_str(), + "PDF Files (*.pdf)"); + filedlg->setAcceptMode(QFileDialog::AcceptSave); + filedlg->setDefaultSuffix("pdf"); + filedlg->selectFile("distance"); + filedlg->setFileMode(QFileDialog::AnyFile); + + if(!filedlg->exec()) + return; + + QStringList files = filedlg->selectedFiles(); + if(files.size() == 0 || files[0] == "") + return; + + if(!m_plot->savePdf(files[0])) + { + QMessageBox::critical(this, "Error", + QString("File \"%1\" could not be saved.").arg(files[0])); + return; + } + + fs::path file{files[0].toStdString()}; + m_pdfdir = file.parent_path().string(); +} + + void Reports::accept() { // save settings @@ -514,6 +596,7 @@ void Reports::accept() settings.setValue("dlg_reports/all_tracks", m_all_tracks->isChecked()); settings.setValue("dlg_reports/sum_distances", m_cumulative->isChecked()); + settings.setValue("dlg_reports/recent_pdfs", m_pdfdir.c_str()); QByteArray split{m_split->saveState()}; settings.setValue("dlg_reports/split", split); diff --git a/src/gui/dialogs/reports.h b/src/gui/dialogs/reports.h index d0b356e..4e76463 100644 --- a/src/gui/dialogs/reports.h +++ b/src/gui/dialogs/reports.h @@ -14,6 +14,7 @@ #include #include #include +#include // https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html #pragma GCC diagnostic push @@ -53,7 +54,9 @@ class Reports : public QDialog virtual void reject() override; void PlotMouseMove(QMouseEvent *evt); + void PlotMouseClick(QMouseEvent *evt); void ResetDistPlotRange(); + void SavePlotPdf(); private: @@ -64,12 +67,16 @@ class Reports : public QDialog std::shared_ptr m_all_tracks{}, m_cumulative{}; std::shared_ptr m_status{}; std::shared_ptr m_buttonbox{}; + std::shared_ptr m_context{}; const t_tracks *m_trackdb{}; typename t_tracks::t_timept_map m_monthly{}, m_yearly{}; t_real m_min_epoch{}, m_max_epoch{}; t_real m_min_dist{}, m_max_dist{}; + + // directory with recently saved pdf files + std::string m_pdfdir{}; }; diff --git a/src/gui/dialogs/statistics.cpp b/src/gui/dialogs/statistics.cpp index 324edfc..c91fb68 100644 --- a/src/gui/dialogs/statistics.cpp +++ b/src/gui/dialogs/statistics.cpp @@ -18,6 +18,14 @@ #include #include +#if __has_include() + #include + namespace fs = std::filesystem; +#else + #include + namespace fs = boost::filesystem; +#endif + Statistics::Statistics(QWidget* parent) : QDialog(parent) @@ -36,8 +44,15 @@ Statistics::Statistics(QWidget* parent) ticker->setDateTimeSpec(Qt::LocalTime); ticker->setDateTimeFormat("yyyy-MM-dd"); m_plot->xAxis->setTicker(QSharedPointer{ticker}); - connect(m_plot.get(), &QCustomPlot::mouseMove, this, &Statistics::PlotMouseMove); + connect(m_plot.get(), &QCustomPlot::mousePress, this, &Statistics::PlotMouseClick); + + // context menu + m_context = std::make_shared(this); + QIcon iconSavePdf = QIcon::fromTheme("image-x-generic"); + QAction *actionSavePdf = new QAction(iconSavePdf, "Save Image...", m_context.get()); + m_context->addAction(actionSavePdf); + connect(actionSavePdf, &QAction::triggered, this, &Statistics::SavePlotPdf); // speed/pace checkbox m_speed_check = std::make_shared(this); @@ -151,6 +166,9 @@ Statistics::Statistics(QWidget* parent) if(settings.contains("dlg_statistics/speed_check")) m_speed_check->setChecked(settings.value("dlg_statistics/speed_check").toBool()); + + if(settings.contains("dlg_statistics/recent_pdfs")) + m_pdfdir = settings.value("dlg_statistics/recent_pdfs").toString().toStdString(); } @@ -276,7 +294,7 @@ void Statistics::PlotSpeeds() graph->setData(epochs, paces, true); graph->setLineStyle(QCPGraph::lsLine); - graph->setScatterStyle(QCPScatterStyle{QCPScatterStyle::ssCircle, pen, brush, 6.}); + graph->setScatterStyle(QCPScatterStyle{QCPScatterStyle::ssDisc, pen, brush, 6.}); graph->setPen(pen); //graph->setBrush(brush); }; @@ -305,6 +323,9 @@ void Statistics::PlotSpeeds() } +/** + * the mouse has been moved in the plot widget + */ void Statistics::PlotMouseMove(QMouseEvent *evt) { if(!m_plot) @@ -338,6 +359,66 @@ void Statistics::PlotMouseMove(QMouseEvent *evt) } +/** + * the mouse has been clicked in the plot widget + */ +void Statistics::PlotMouseClick(QMouseEvent *evt) +{ + if(!m_plot || !m_context) + return; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QPoint pos = evt->position(); +#else + QPoint pos = evt->pos(); +#endif + + if(evt->buttons() & Qt::RightButton) + { + QPoint posGlobal = m_plot->mapToGlobal(pos); + posGlobal.rx() += 4; + posGlobal.ry() += 4; + + m_context->popup(posGlobal); + } +} + + +/** + * save the plot as a pdf image + */ +void Statistics::SavePlotPdf() +{ + if(!m_plot) + return; + + auto filedlg = std::make_shared( + this, "Save Plot as Image", m_pdfdir.c_str(), + "PDF Files (*.pdf)"); + filedlg->setAcceptMode(QFileDialog::AcceptSave); + filedlg->setDefaultSuffix("pdf"); + filedlg->selectFile("pace"); + filedlg->setFileMode(QFileDialog::AnyFile); + + if(!filedlg->exec()) + return; + + QStringList files = filedlg->selectedFiles(); + if(files.size() == 0 || files[0] == "") + return; + + if(!m_plot->savePdf(files[0])) + { + QMessageBox::critical(this, "Error", + QString("File \"%1\" could not be saved.").arg(files[0])); + return; + } + + fs::path file{files[0].toStdString()}; + m_pdfdir = file.parent_path().string(); +} + + void Statistics::accept() { // save settings @@ -353,6 +434,7 @@ void Statistics::accept() } settings.setValue("dlg_statistics/speed_check", m_speed_check->isChecked()); + settings.setValue("dlg_statistics/recent_pdfs", m_pdfdir.c_str()); QDialog::accept(); } diff --git a/src/gui/dialogs/statistics.h b/src/gui/dialogs/statistics.h index f7ea21b..44b785e 100644 --- a/src/gui/dialogs/statistics.h +++ b/src/gui/dialogs/statistics.h @@ -12,6 +12,7 @@ #include #include #include +#include // https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html #pragma GCC diagnostic push @@ -48,7 +49,9 @@ class Statistics : public QDialog virtual void reject() override; void PlotMouseMove(QMouseEvent *evt); + void PlotMouseClick(QMouseEvent *evt); void ResetSpeedPlotRange(); + void SavePlotPdf(); private: @@ -56,6 +59,7 @@ class Statistics : public QDialog std::shared_ptr m_speed_check{}; std::shared_ptr m_status{}; std::shared_ptr m_buttonbox{}; + std::shared_ptr m_context{}; static constexpr const t_real s_lengths[] = { 25., 20., 15., 10., 5., 0. }; static constexpr const t_size s_num_lengths = sizeof(s_lengths)/sizeof(s_lengths[0]); @@ -65,6 +69,9 @@ class Statistics : public QDialog t_real m_min_pace{}, m_max_pace{}; const t_tracks *m_trackdb{}; + + // directory with recently saved pdf files + std::string m_pdfdir{}; }; diff --git a/src/gui/docks/track_infos.cpp b/src/gui/docks/track_infos.cpp index 0b6f53f..cbc811e 100644 --- a/src/gui/docks/track_infos.cpp +++ b/src/gui/docks/track_infos.cpp @@ -51,7 +51,7 @@ TrackInfos::TrackInfos(QWidget* parent) : QWidget{parent} m_tab->addTab(pace_panel, "Pace"); m_tab->addTab(map_panel, "Map"); #ifndef _TRACKS_USE_OSMIUM_ - m_tab->setTabEnabled(1, false); + m_tab->setTabEnabled(TAB_MAP, false); map_panel->setEnabled(false); #endif @@ -63,6 +63,19 @@ TrackInfos::TrackInfos(QWidget* parent) : QWidget{parent} m_track_plot->yAxis->setLabel("Latitude (Degrees)"); m_track_plot->legend->setVisible(false); connect(m_track_plot.get(), &QCustomPlot::mouseMove, this, &TrackInfos::TrackPlotMouseMove); + connect(m_track_plot.get(), &QCustomPlot::mousePress, [this](QMouseEvent *evt) + { + PlotMouseClick(evt, m_track_context.get(), m_track_plot.get()); + }); + + m_track_context = std::make_shared(plot_panel); + QIcon iconSaveTrackPdf = QIcon::fromTheme("image-x-generic"); + QAction *actionSaveTrackPdf = new QAction(iconSaveTrackPdf, "Save Image...", m_track_context.get()); + m_track_context->addAction(actionSaveTrackPdf); + connect(actionSaveTrackPdf, &QAction::triggered, [this]() + { + SavePlotPdf(m_track_plot.get(), "track"); + }); m_same_range = std::make_shared(plot_panel); m_same_range->setText("Keep Aspect"); @@ -90,6 +103,19 @@ TrackInfos::TrackInfos(QWidget* parent) : QWidget{parent} m_alt_plot->yAxis->setLabel("Altitude (m)"); m_alt_plot->legend->setVisible(false); connect(m_alt_plot.get(), &QCustomPlot::mouseMove, this, &TrackInfos::AltPlotMouseMove); + connect(m_alt_plot.get(), &QCustomPlot::mousePress, [this](QMouseEvent *evt) + { + PlotMouseClick(evt, m_alt_context.get(), m_alt_plot.get()); + }); + + m_alt_context = std::make_shared(alt_panel); + QIcon iconSaveAltPdf = QIcon::fromTheme("image-x-generic"); + QAction *actionSaveAltPdf = new QAction(iconSaveAltPdf, "Save Image...", m_alt_context.get()); + m_alt_context->addAction(actionSaveAltPdf); + connect(actionSaveAltPdf, &QAction::triggered, [this]() + { + SavePlotPdf(m_alt_plot.get(), "altitude"); + }); m_time_check = std::make_shared(alt_panel); m_time_check->setText("Plot Times"); @@ -117,6 +143,19 @@ TrackInfos::TrackInfos(QWidget* parent) : QWidget{parent} m_pace_plot->xAxis->setLabel("Distance (km)"); m_pace_plot->legend->setVisible(true); connect(m_pace_plot.get(), &QCustomPlot::mouseMove, this, &TrackInfos::PacePlotMouseMove); + connect(m_pace_plot.get(), &QCustomPlot::mousePress, [this](QMouseEvent *evt) + { + PlotMouseClick(evt, m_pace_context.get(), m_pace_plot.get()); + }); + + m_pace_context = std::make_shared(pace_panel); + QIcon iconSavePacePdf = QIcon::fromTheme("image-x-generic"); + QAction *actionSavePacePdf = new QAction(iconSavePacePdf, "Save Image...", m_pace_context.get()); + m_pace_context->addAction(actionSavePacePdf); + connect(actionSavePacePdf, &QAction::triggered, [this]() + { + SavePlotPdf(m_pace_plot.get(), "pace"); + }); m_speed_check = std::make_shared(pace_panel); m_speed_check->setText("Plot Speeds"); @@ -155,14 +194,15 @@ TrackInfos::TrackInfos(QWidget* parent) : QWidget{parent} //m_map->renderer()->setAnimationEnabled(false); m_map->renderer()->setAspectRatioMode(Qt::KeepAspectRatio); connect(m_map.get(), &MapDrawer::MouseMoved, this, &TrackInfos::MapMouseMove); - connect(m_map.get(), &MapDrawer::MousePressed, this, &TrackInfos::MapMouseClick); + connect(m_map.get(), &MapDrawer::MousePressed, [this](QMouseEvent *evt) + { + PlotMouseClick(evt, m_map_context.get(), m_map.get()); + }); m_map_context = std::make_shared(map_panel); - QIcon iconSaveSvg = QIcon::fromTheme("image-x-generic"); QAction *actionSaveSvg = new QAction(iconSaveSvg, "Save Image...", m_map_context.get()); connect(actionSaveSvg, &QAction::triggered, this, &TrackInfos::SaveMapSvg); - m_map_context->addAction(actionSaveSvg); m_mapfile = std::make_shared(map_panel); @@ -405,7 +445,7 @@ void TrackInfos::PlotAlt() if(plot_time) { m_min_dist_alt = *dist.begin(); - m_max_dist_alt = *dist.rbegin(); + m_max_dist_alt = *dist.rbegin() * 1.01; } else { @@ -682,6 +722,44 @@ void TrackInfos::PlotMap(bool load_cached) } +/** + * save a plot as a pdf image + */ +void TrackInfos::SavePlotPdf(QCustomPlot *plot, const char *name) +{ + if(!m_track) + { + QMessageBox::warning(this, "Warning", "No track is loaded."); + return; + } + + auto filedlg = std::make_shared( + this, "Save Plot as Image", m_svgdir.c_str(), + "PDF Files (*.pdf)"); + filedlg->setAcceptMode(QFileDialog::AcceptSave); + filedlg->setDefaultSuffix("pdf"); + filedlg->selectFile(name); + filedlg->setFileMode(QFileDialog::AnyFile); + + if(!filedlg->exec()) + return; + + QStringList files = filedlg->selectedFiles(); + if(files.size() == 0 || files[0] == "") + return; + + if(!plot->savePdf(files[0])) + { + QMessageBox::critical(this, "Error", + QString("File \"%1\" could not be saved.").arg(files[0])); + return; + } + + fs::path file{files[0].toStdString()}; + m_svgdir = file.parent_path().string(); +} + + /** * save the map as an svg image */ @@ -875,11 +953,11 @@ void TrackInfos::MapMouseMove(QMouseEvent *evt) /** - * the mouse has been clicked in the map widget + * the mouse has been clicked in one of the plot widgets */ -void TrackInfos::MapMouseClick(QMouseEvent *evt) +void TrackInfos::PlotMouseClick(QMouseEvent *evt, QMenu *context, QWidget *plot) { - if(!m_map) + if(!plot || !context) return; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) @@ -890,11 +968,11 @@ void TrackInfos::MapMouseClick(QMouseEvent *evt) if(evt->buttons() & Qt::RightButton) { - QPoint posGlobal = m_map->mapToGlobal(pos); + QPoint posGlobal = plot->mapToGlobal(pos); posGlobal.rx() += 4; posGlobal.ry() += 4; - m_map_context->popup(posGlobal); + context->popup(posGlobal); } } diff --git a/src/gui/docks/track_infos.h b/src/gui/docks/track_infos.h index 61dc242..6bf1d4c 100644 --- a/src/gui/docks/track_infos.h +++ b/src/gui/docks/track_infos.h @@ -55,13 +55,15 @@ class TrackInfos : public QWidget protected: virtual QSize sizeHint() const override; + void PlotMouseClick(QMouseEvent *evt, QMenu *context, QWidget *plot); + void SavePlotPdf(QCustomPlot *plot, const char *name); + void TrackPlotMouseMove(QMouseEvent *evt); void CalcTrackPlotRange(); void ResetTrackPlotRange(); void PlotTrack(); void MapMouseMove(QMouseEvent *evt); - void MapMouseClick(QMouseEvent *evt); void SelectMap(); void PlotMap(bool load_cached = false); void SaveMapSvg(); @@ -83,6 +85,7 @@ class TrackInfos : public QWidget // track tab std::shared_ptr m_track_plot{}; std::shared_ptr m_same_range{}; + std::shared_ptr m_track_context{}; // map tab std::shared_ptr m_map{}; @@ -92,11 +95,13 @@ class TrackInfos : public QWidget // altitude tab std::shared_ptr m_alt_plot{}; std::shared_ptr m_time_check{}; + std::shared_ptr m_alt_context{}; // pace tab std::shared_ptr m_pace_plot{}; std::shared_ptr m_dist_binlen{}; std::shared_ptr m_speed_check{}; + std::shared_ptr m_pace_context{}; // svg image of the map QByteArray m_map_image{}; diff --git a/src/lib/track.h b/src/lib/track.h index e2b8152..bb73e96 100644 --- a/src/lib/track.h +++ b/src/lib/track.h @@ -82,10 +82,15 @@ class SingleTrack */ void Calculate() { + // minimum height difference [m] before counted as climb + const t_real asc_eps = 1.; + // clear old values m_total_dist = 0.; m_total_dist_planar = 0.; m_total_time = 0.; + m_ascent = 0.; + m_descent = 0.; // reset ranges m_min_elev = std::numeric_limits::max(); @@ -127,6 +132,16 @@ class SingleTrack *elevation_last, trackpt.elevation); } + // ascent and descent (TODO) + if(elevation_last) + { + t_real elev_diff = trackpt.elevation - *elevation_last; + if(elev_diff > asc_eps) + m_ascent += elev_diff; + else if(elev_diff < asc_eps) + m_descent += -elev_diff; + } + // cumulative values m_total_time += trackpt.elapsed; m_total_dist += trackpt.distance; @@ -168,23 +183,32 @@ class SingleTrack t_real time = 0., dist = 0.; t_size bin_idx = 0; + for(const t_TrackPoint& pt : m_points) { time += pt.elapsed; dist += planar ? pt.distance_planar : pt.distance; - if(dist >= dist_bin) + while(dist >= dist_bin) { - time *= dist_bin / dist; // normalise to bin size + t_real time_part = time * dist_bin / dist; - times.push_back(time); + times.push_back(time_part); dists.push_back(dist_bin * static_cast(bin_idx + 1)); - time = dist = 0.; + dist -= dist_bin; + time -= time_part; ++bin_idx; } } + // add remaining time + if(time > 0. && dist > 0.) + { + times.push_back(time * dist_bin / dist); + dists.push_back(dist_bin * static_cast(bin_idx + 1)); + } + return std::make_pair(times, dists); } @@ -370,6 +394,13 @@ class SingleTrack + std::pair GetAscentDescent() const + { + return std::make_pair(m_ascent, m_descent); + } + + + t_size GetHash() const { return m_hash; @@ -429,6 +460,9 @@ class SingleTrack ofstr.write(reinterpret_cast(&m_min_elev), sizeof(m_min_elev)); ofstr.write(reinterpret_cast(&m_max_elev), sizeof(m_max_elev)); + ofstr.write(reinterpret_cast(&m_ascent), sizeof(m_ascent)); + ofstr.write(reinterpret_cast(&m_descent), sizeof(m_descent)); + // write track name const t_size name_len = m_filename.size(); ofstr.write(reinterpret_cast(&name_len), sizeof(name_len)); @@ -489,6 +523,9 @@ class SingleTrack ifstr.read(reinterpret_cast(&m_min_elev), sizeof(m_min_elev)); ifstr.read(reinterpret_cast(&m_max_elev), sizeof(m_max_elev)); + ifstr.read(reinterpret_cast(&m_ascent), sizeof(m_ascent)); + ifstr.read(reinterpret_cast(&m_descent), sizeof(m_descent)); + // read track name t_size name_len{}; ifstr.read(reinterpret_cast(&name_len), sizeof(name_len)); @@ -516,6 +553,7 @@ class SingleTrack t_real s = GetTotalDistance(false); t_real s_planar = GetTotalDistance(true); auto [ min_elev, max_elev ] = GetElevationRange(); + //auto [ asc, desc ] = GetAscentDescent(); std::optional start_time = GetStartTime(); std::optional end_time = GetEndTime(); @@ -532,6 +570,8 @@ class SingleTrack } ostr << "
  • Altitude range: [ " << min_elev << ", " << max_elev << " ] m" << " (height difference: " << max_elev - min_elev << " m).
  • "; + // TODO + //ostr << "
  • Climb: " << asc << " m, fall: " << desc << " m.
  • "; ostr << "
  • Distance: " << s / 1000. << " km" << " (planar: " << s_planar / 1000. << " km).
  • "; ostr << "
  • Pace: " << get_pace_str((t / 60.) / (s / 1000.)) @@ -589,11 +629,13 @@ class SingleTrack t_real s = track.GetTotalDistance(false); t_real s_planar = track.GetTotalDistance(true); auto [ min_elev, max_elev ] = track.GetElevationRange(); + auto [ asc, desc ] = track.GetAscentDescent(); ostr << "\n"; ostr << "Number of track points: " << track.GetPoints().size() << "\n"; ostr << "Altitude range: [ " << min_elev << ", " << max_elev << " ] m\n"; ostr << "Height difference: " << max_elev - min_elev << " m\n"; + ostr << "Climb: " << asc << " m, descent: " << desc << " m\n"; ostr << "Total distance: " << s << " m = " << s / 1000. << " km\n"; ostr << "Total planar distance: " << s_planar / 1000. << " km\n"; ostr << "Total time: " << get_time_str(t) << "\n"; @@ -642,6 +684,8 @@ class SingleTrack t_real m_min_long{}, m_max_long{}; t_real m_min_elev{}, m_max_elev{}; + t_real m_ascent{}, m_descent{}; + int m_distance_function{0}; t_size m_hash{};