From 4c19365e8b7588dd484401042b3b3852b3f230bf Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 8 Jun 2020 13:35:51 -0400 Subject: [PATCH] Live Map Chart (#3487) --- src/Gui/GcWindowRegistry.cpp | 5 +- src/Gui/GcWindowRegistry.h | 4 +- src/Train/LiveMapWebPageWindow.cpp | 301 +++++++++++++++++++++++++++++ src/Train/LiveMapWebPageWindow.h | 114 +++++++++++ src/src.pro | 6 +- 5 files changed, 426 insertions(+), 4 deletions(-) create mode 100644 src/Train/LiveMapWebPageWindow.cpp create mode 100644 src/Train/LiveMapWebPageWindow.h diff --git a/src/Gui/GcWindowRegistry.cpp b/src/Gui/GcWindowRegistry.cpp index b7e3cc7cb..71f6c78b4 100644 --- a/src/Gui/GcWindowRegistry.cpp +++ b/src/Gui/GcWindowRegistry.cpp @@ -50,6 +50,7 @@ #include "WorkoutPlotWindow.h" #include "WorkoutWindow.h" #include "WebPageWindow.h" +#include "LiveMapWebPageWindow.h" #ifdef GC_WANT_R #include "RChart.h" #endif @@ -76,7 +77,7 @@ GcWindowRegistry* GcWindows; void GcWindowRegistry::initialize() { - static GcWindowRegistry GcWindowsInit[35] = { + static GcWindowRegistry GcWindowsInit[34] = { // name GcWinID { VIEW_HOME|VIEW_DIARY, tr("Overview "),GcWindowTypes::OverviewTrends }, { VIEW_HOME|VIEW_DIARY, tr("User Chart"),GcWindowTypes::UserTrends }, @@ -115,6 +116,7 @@ GcWindowRegistry::initialize() { VIEW_TRAIN, tr("Pedal Stroke"),GcWindowTypes::SpinScanPlot }, { VIEW_TRAIN, tr("Video Player"),GcWindowTypes::VideoPlayer }, { VIEW_TRAIN, tr("Workout Editor"),GcWindowTypes::WorkoutWindow }, + { VIEW_TRAIN, tr("Live Map"),GcWindowTypes::LiveMapWebPageWindow }, { VIEW_ANALYSIS|VIEW_HOME|VIEW_TRAIN, tr("Web page"),GcWindowTypes::WebPageWindow }, { 0, "", GcWindowTypes::None }}; // initialize the global registry @@ -237,6 +239,7 @@ GcWindowRegistry::newGcWindow(GcWinID id, Context *context) case GcWindowTypes::WorkoutWindow: returning = new WorkoutWindow(context); break; case GcWindowTypes::WebPageWindow: returning = new WebPageWindow(context); break; + case GcWindowTypes::LiveMapWebPageWindow: returning = new LiveMapWebPageWindow(context); break; #if 0 // not till v4.0 case GcWindowTypes::RouteSegment: returning = new RouteWindow(context); break; #else diff --git a/src/Gui/GcWindowRegistry.h b/src/Gui/GcWindowRegistry.h index 849a3ac81..dc3c63854 100644 --- a/src/Gui/GcWindowRegistry.h +++ b/src/Gui/GcWindowRegistry.h @@ -73,7 +73,9 @@ enum gcwinid { PythonSeason = 44, UserTrends=45, UserAnalysis=46, - OverviewTrends=47 + OverviewTrends=47, + LiveMapWebPageWindow = 48 + }; }; typedef enum GcWindowTypes::gcwinid GcWinID; diff --git a/src/Train/LiveMapWebPageWindow.cpp b/src/Train/LiveMapWebPageWindow.cpp new file mode 100644 index 000000000..39fe5d825 --- /dev/null +++ b/src/Train/LiveMapWebPageWindow.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2020 Peter Kanatselis (pkanatselis@gmail.com) + * + * 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 "LiveMapWebPageWindow.h" + +#include "MainWindow.h" +#include "RideItem.h" +#include "RideFile.h" +#include "RideImportWizard.h" +#include "IntervalItem.h" +#include "IntervalTreeView.h" +#include "SmallPlot.h" +#include "Context.h" +#include "Athlete.h" +#include "Zones.h" +#include "Settings.h" +#include "Colors.h" +#include "Units.h" +#include "TimeUtils.h" +#include "HelpWhatsThis.h" +#include "Library.h" +#include "ErgFile.h" +#include "LocationInterpolation.h" + +//#include +//#include + +// overlay helper +#include "TabView.h" +#include "GcOverlayWidget.h" +#include "IntervalSummaryWindow.h" + +// declared in main, we only want to use it to get QStyle +extern QApplication *application; + +LiveMapWebPageWindow::LiveMapWebPageWindow(Context *context) : GcChartWindow(context), context(context) +{ + // Connect signal to receive updates on lat/lon for ploting on map. + connect(context, SIGNAL(telemetryUpdate(RealtimeData)), this, SLOT(telemetryUpdate(RealtimeData))); + connect(context, SIGNAL(stop()), this, SLOT(stop())); + connect(context, SIGNAL(ergFileSelected(ErgFile*)), this, SLOT(ergFileSelected(ErgFile*))); + + // reveal controls widget + // layout reveal controls + QHBoxLayout *revealLayout = new QHBoxLayout; + revealLayout->setContentsMargins(0,0,0,0); + + rButton = new QPushButton(application->style()->standardIcon(QStyle::SP_ArrowRight), "", this); + rCustomUrl = new QLineEdit(this); + revealLayout->addStretch(); + revealLayout->addWidget(rButton); + revealLayout->addWidget(rCustomUrl); + revealLayout->addStretch(); + setRevealLayout(revealLayout); + + connect(rButton, SIGNAL(clicked(bool)), this, SLOT(userUrl())); + + // Chart settings + QWidget * settingsWidget = new QWidget(this); + settingsWidget->setContentsMargins(0,0,0,0); + setProperty("color", GColor(CTRAINPLOTBACKGROUND)); + + QFormLayout* commonLayout = new QFormLayout(settingsWidget); + + QString sValue = ""; + customUrlLabel = new QLabel(tr("OSM Base URL")); + customUrl = new QLineEdit(this); + customUrl->setFixedWidth(300); + + if (customUrl->text() == "") { + customUrl->setText("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"); + } + commonLayout->addRow(customUrlLabel, customUrl); + + connect(customUrlLabel, SIGNAL(returnPressed()), this, SLOT(userUrl())); + + applyButton = new QPushButton(application->style()->standardIcon(QStyle::SP_ArrowRight), tr("Apply changes"), this); + commonLayout->addRow(applyButton); + + // Connect signal to update map + connect(applyButton, SIGNAL(clicked(bool)), this, SLOT(userUrl())); + + setControls(settingsWidget); + setContentsMargins(0, 0, 0, 0); + layout = new QVBoxLayout(); + layout->setSpacing(0); + layout->setContentsMargins(2, 2, 2, 2); + setChartLayout(layout); + + // set webview for map + view = new QWebEngineView(this); + webPage = view->page(); + view->setPage(webPage); + + view->setContentsMargins(0,0,0,0); + view->page()->view()->setContentsMargins(0,10,0,0); + view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + view->setAcceptDrops(false); + layout->addWidget(view); + + configChanged(CONFIG_APPEARANCE); + + // Finish initialization using current settings + userUrl(); + ergFileSelected(context->currentErgFile()); +} + +void LiveMapWebPageWindow::userUrl() +{ + // add http:// if scheme is missing + QRegExp hasscheme("^[^:]*://.*"); + QString url = rCustomUrl->text(); + if (!hasscheme.exactMatch(url)) url = "http://" + url; + view->setZoomFactor(dpiXFactor); + view->setUrl(QUrl(url)); +} + +LiveMapWebPageWindow::~LiveMapWebPageWindow() +{ +} + +void LiveMapWebPageWindow::ergFileSelected(ErgFile* f) +{ + // rename window to workout name and draw route if data exists + if (f && f->filename != "" ) + { + setIsBlank(false); + QString startingLat = QString::number(((f->Points)[0]).lat); + QString startingLon = QString::number(((f->Points)[0]).lon); + if (startingLat == "0" && startingLon == "0") + { + markerIsVisible = false; + setIsBlank(true); + } + else + { + QString js = ("
\n"); + routeLatLngs = "["; + QString code = ""; + + for (int pt = 0; pt < f->Points.size() - 1; pt++) { + + geolocation geoloc(f->Points[pt].lat, f->Points[pt].lon, f->Points[pt].y); + if (geoloc.IsReasonableGeoLocation()) { + if (pt == 0) { routeLatLngs += "["; } + else { routeLatLngs += ",["; } + routeLatLngs += QVariant(f->Points[pt].lat).toString(); + routeLatLngs += ","; + routeLatLngs += QVariant(f->Points[pt].lon).toString(); + routeLatLngs += "]"; + } + + } + routeLatLngs += "]"; + // We can either setHTML page or runJavaScript but not both. + // So we create divs with the 2 methods we need to run when the document loads + code = QString("showRoute (" + routeLatLngs + ");"); + js += ("
\n"); + createHtml(customUrl->text(), js); + view->page()->setHtml(currentPage); + } + } + else + { + markerIsVisible = false; + setIsBlank(true); + } +} + +void LiveMapWebPageWindow::drawRoute(ErgFile* f) { + routeLatLngs = "["; + QString code = ""; + + for (int pt = 0; pt < f->Points.size() - 1; pt++) { + + geolocation geoloc(f->Points[pt].lat, f->Points[pt].lon, f->Points[pt].y); + if (geoloc.IsReasonableGeoLocation()) { + if (pt == 0) { routeLatLngs += "["; } + else { routeLatLngs += ",["; } + routeLatLngs += QVariant(f->Points[pt].lat).toString(); + routeLatLngs += ","; + routeLatLngs += QVariant(f->Points[pt].lon).toString(); + routeLatLngs += "]"; + } + + } + routeLatLngs += "]"; + code = QString("showRoute (" + routeLatLngs + ");"); + view->page()->runJavaScript(code); +} + +// Reset map to preferred View when the activity is stopped. +void LiveMapWebPageWindow::stop() +{ + markerIsVisible = false; +} + +void LiveMapWebPageWindow::configChanged(qint32) +{ + // tinted palette for headings etc + QPalette palette; + palette.setBrush(QPalette::Window, QBrush(GColor(CPLOTBACKGROUND))); + palette.setColor(QPalette::WindowText, GColor(CPLOTMARKER)); + palette.setColor(QPalette::Text, GColor(CPLOTMARKER)); + palette.setColor(QPalette::Base, GCColor::alternateColor(GColor(CPLOTBACKGROUND))); + setPalette(palette); + +} + +// Update position on the map when telemetry changes. +void LiveMapWebPageWindow::telemetryUpdate(RealtimeData rtd) +{ + QString code = ""; + geolocation geoloc(rtd.getLatitude(), rtd.getLongitude(), rtd.getAltitude()); + if (geoloc.IsReasonableGeoLocation()) { + QString sLat = QVariant(rtd.getLatitude()).toString(); + QString sLon = QVariant(rtd.getLongitude()).toString(); + code = ""; + if (!markerIsVisible) + { + code = QString("centerMap (" + sLat + ", " + sLon + ", " + "15" + ");"); + code += QString("showMyMarker (" + sLat + ", " + sLon + ");"); + markerIsVisible = true; + } + else + { + code += QString("moveMarker (" + sLat + ", " + sLon + ");"); + } + view->page()->runJavaScript(code); + } +} +// Build HTML code with all the javascript functions to be called later +// to update the postion on the mapp +void LiveMapWebPageWindow::createHtml(QString sBaseUrl, QString autoRunJS) +{ + currentPage = ""; + + currentPage = QString("\n" + " \n" + "\n" + "GoldenCheetah LiveMap - TrainView\n" + "\n" + "\n" + "\n" + "
\n" + "\n" + + autoRunJS + + "\n" + ); +} diff --git a/src/Train/LiveMapWebPageWindow.h b/src/Train/LiveMapWebPageWindow.h new file mode 100644 index 000000000..953d2eb00 --- /dev/null +++ b/src/Train/LiveMapWebPageWindow.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016 Damien Grauser (Damien.Grauser@gmail.com) + * updated (c) 2020 Peter Kanatselis (pkanatselis@gmail.com) + * + * 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 + */ + +#ifndef _GC_LiveMapWebPageWindow_h +#define _GC_LiveMapWebPageWindow_h +#include "GoldenCheetah.h" + +#include +#include + +#include +#include +#include +#include +#include "RideFile.h" +#include "IntervalItem.h" +#include "Context.h" + +#include +#include +#include +#include + + +class QMouseEvent; +class RideItem; +class Context; +class QColor; +class QVBoxLayout; +class QTabWidget; +class LLiveMapWebPageWindow; +class IntervalSummaryWindow; +class SmallPlot; + +class LiveMapsimpleWebPage : public QWebEnginePage +{ +}; + +class LiveMapWebPageWindow : public GcChartWindow +{ + Q_OBJECT + G_OBJECT + + // properties can be saved/restored/set by the layout manager + Q_PROPERTY(QString url READ url WRITE setUrl USER true) + + public: + LiveMapWebPageWindow(Context *); + ~LiveMapWebPageWindow(); + bool markerIsVisible; + double plotLon = 0; + double plotLat = 0; + QString currentPage; + QString routeLatLngs; + + // set/get properties + QString url() const { return customUrl->text(); } + void setUrl(QString x) { customUrl->setText(x); } + + public slots: + void configChanged(qint32); + void ergFileSelected(ErgFile*); + void userUrl(); + + private: + Context *context; + QVBoxLayout *layout; + QComboBox* mapCombo; + + QWebEngineView *view; + QWebEnginePage* webPage; + LiveMapWebPageWindow(); // default ctor + // setting dialog + QLabel* customUrlLabel; + QLabel* customLonLabel; + QLabel* customLatLabel; + QLabel* customZoomLabel; + QLineEdit* customUrl; + // reveal controls + QLineEdit* rCustomUrl; + QLineEdit* customLat; + QLineEdit* customLon; + QLineEdit* customZoom; + QPushButton* rButton; + QPushButton* applyButton; + + void createHtml(QString sBaseUrl, QString autoRunJS); + void drawRoute(ErgFile* f); + + private slots: + void telemetryUpdate(RealtimeData rtd); + void stop(); + + protected: + +}; + +#endif diff --git a/src/src.pro b/src/src.pro index 0efad4a28..3afccd703 100644 --- a/src/src.pro +++ b/src/src.pro @@ -756,7 +756,8 @@ greaterThan(QT_MAJOR_VERSION, 4) { HEADERS += Train/TrainBottom.h Train/TrainDB.h Train/TrainSidebar.h \ Train/VideoLayoutParser.h Train/VideoSyncFile.h Train/WorkoutPlotWindow.h Train/WebPageWindow.h \ - Train/WorkoutWidget.h Train/WorkoutWidgetItems.h Train/WorkoutWindow.h Train/WorkoutWizard.h Train/ZwoParser.h + Train/WorkoutWidget.h Train/WorkoutWidgetItems.h Train/WorkoutWindow.h Train/WorkoutWizard.h Train/ZwoParser.h \ + Train/LiveMapWebPageWindow.h ###============= @@ -859,7 +860,8 @@ greaterThan(QT_MAJOR_VERSION, 4) { SOURCES += Train/TrainBottom.cpp Train/TrainDB.cpp Train/TrainSidebar.cpp \ Train/VideoLayoutParser.cpp Train/VideoSyncFile.cpp Train/WorkoutPlotWindow.cpp Train/WebPageWindow.cpp \ - Train/WorkoutWidget.cpp Train/WorkoutWidgetItems.cpp Train/WorkoutWindow.cpp Train/WorkoutWizard.cpp Train/ZwoParser.cpp + Train/WorkoutWidget.cpp Train/WorkoutWidgetItems.cpp Train/WorkoutWindow.cpp Train/WorkoutWizard.cpp Train/ZwoParser.cpp \ + Train/LiveMapWebPageWindow.cpp ## Crash Handling win32-msvc* {