mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
40 Commits
ant-remote
...
Cesium
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b6ab0e1cf | ||
|
|
408b21d0c3 | ||
|
|
1f4ed6f2d2 | ||
|
|
e5e0b84615 | ||
|
|
08f548035d | ||
|
|
35d6a7b151 | ||
|
|
de6070464d | ||
|
|
e4eb6d0fbe | ||
|
|
d5a3c6c66e | ||
|
|
3f8d9e8f5b | ||
|
|
81b68b3f89 | ||
|
|
80b7c6b556 | ||
|
|
254e045cc2 | ||
|
|
d47098fd1a | ||
|
|
215e2be0d6 | ||
|
|
90de6902a3 | ||
|
|
107cdd00bd | ||
|
|
110fadb1a2 | ||
|
|
b915cf0d59 | ||
|
|
4921c37b0a | ||
|
|
aa863edd11 | ||
|
|
7fbf64185c | ||
|
|
34c748e130 | ||
|
|
3892799cf0 | ||
|
|
c1caaee60b | ||
|
|
aa5eab57c5 | ||
|
|
f91ddd85da | ||
|
|
91d37a53dd | ||
|
|
d5f0c03334 | ||
|
|
39fcda7367 | ||
|
|
c86ee541f1 | ||
|
|
949dd12526 | ||
|
|
fed03b83a4 | ||
|
|
687cc9c900 | ||
|
|
3fe54e10f5 | ||
|
|
67d6fb0bc3 | ||
|
|
658ef4c628 | ||
|
|
81842afc22 | ||
|
|
673a6d9868 | ||
|
|
865815fca2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -44,3 +44,4 @@ template-examples/train-program-saver/debug.js
|
||||
*.pro.user
|
||||
*build-*
|
||||
!build-qdomyos-zwift-Qt_*_for_iOS-Debug # Needed for Apple Watch
|
||||
src/inner_templates/googlemaps/cesium-key.js
|
||||
|
||||
@@ -54,8 +54,10 @@
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/qdomyoszwift">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "876E4E102594747F00BD5714"
|
||||
@@ -63,7 +65,7 @@
|
||||
BlueprintName = "watchkit"
|
||||
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</RemoteRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -71,8 +73,10 @@
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/qdomyoszwift">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "876E4E102594747F00BD5714"
|
||||
@@ -80,7 +84,16 @@
|
||||
BlueprintName = "watchkit"
|
||||
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "876E4E102594747F00BD5714"
|
||||
BuildableName = "watchkit.app"
|
||||
BlueprintName = "watchkit"
|
||||
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
|
||||
@@ -12,11 +12,12 @@ ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Settings {
|
||||
id: settings
|
||||
property string maps_type: "3D"
|
||||
}
|
||||
WebView {
|
||||
id: webView
|
||||
anchors.fill: parent
|
||||
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/googlemaps/maps.htm"
|
||||
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/" + (settings.value("maps_type") === "3D" ? "googlemaps" : "maps2d") + "/maps.htm"
|
||||
visible: true
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.errorString)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "bluetoothdevice.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
#include <QTime>
|
||||
|
||||
@@ -122,9 +123,9 @@ double bluetoothdevice::difficult() { return m_difficult; }
|
||||
void bluetoothdevice::cadenceSensor(uint8_t cadence) { Q_UNUSED(cadence) }
|
||||
void bluetoothdevice::powerSensor(uint16_t power) { Q_UNUSED(power) }
|
||||
void bluetoothdevice::speedSensor(double speed) { Q_UNUSED(speed) }
|
||||
void bluetoothdevice::instantaneousStrideLengthSensor(double length) {Q_UNUSED(length);}
|
||||
void bluetoothdevice::groundContactSensor(double groundContact) {Q_UNUSED(groundContact);}
|
||||
void bluetoothdevice::verticalOscillationSensor(double verticalOscillation) {Q_UNUSED(verticalOscillation);}
|
||||
void bluetoothdevice::instantaneousStrideLengthSensor(double length) { Q_UNUSED(length); }
|
||||
void bluetoothdevice::groundContactSensor(double groundContact) { Q_UNUSED(groundContact); }
|
||||
void bluetoothdevice::verticalOscillationSensor(double verticalOscillation) { Q_UNUSED(verticalOscillation); }
|
||||
|
||||
double bluetoothdevice::calculateMETS() { return ((0.048 * m_watt.value()) + 1.19); }
|
||||
|
||||
@@ -345,16 +346,19 @@ uint8_t bluetoothdevice::metrics_override_heartrate() {
|
||||
return currentHeart().value();
|
||||
}
|
||||
|
||||
void bluetoothdevice::changeGeoPosition(QGeoCoordinate p, double azimuth) {
|
||||
void bluetoothdevice::changeGeoPosition(QGeoCoordinate p, double azimuth, double avgAzimuthNext300Meters) {
|
||||
coordinateTS = QDateTime::currentMSecsSinceEpoch();
|
||||
coordinateOdometer = odometer();
|
||||
coordinate = p;
|
||||
this->setAverageAzimuthNext300m(avgAzimuthNext300Meters);
|
||||
this->azimuth = azimuth;
|
||||
}
|
||||
QGeoCoordinate bluetoothdevice::currentCordinate() {
|
||||
if (coordinateTS) {
|
||||
double distance = currentSpeed().value() * ((QDateTime::currentMSecsSinceEpoch() - coordinateTS) / 3600.0);
|
||||
double distance = odometer() - coordinateOdometer;
|
||||
QGeoCoordinate c = coordinate.atDistanceAndAzimuth(distance, this->azimuth);
|
||||
qDebug() << "currentCordinate" << c << distance << currentSpeed().value();
|
||||
c.setAltitude(coordinate.altitude());
|
||||
// qDebug() << "currentCordinate" << c << distance << currentSpeed().value();
|
||||
return c;
|
||||
}
|
||||
return coordinate;
|
||||
@@ -362,3 +366,12 @@ QGeoCoordinate bluetoothdevice::currentCordinate() {
|
||||
|
||||
void bluetoothdevice::workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state) { lastState = state; }
|
||||
void bluetoothdevice::setInclination(double inclination) { Inclination = inclination; }
|
||||
void bluetoothdevice::setGPXFile(QString filename) {
|
||||
gpxFileName = filename;
|
||||
QFile input(filename);
|
||||
if (input.open(QIODevice::ReadOnly)) {
|
||||
QByteArray asSaved = input.readAll();
|
||||
gpxBase64 = "data:@file/xml;base64," + asSaved.toBase64();
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
#define SAME_BLUETOOTH_DEVICE(d1, d2) (d1.address() == d2.address())
|
||||
#endif
|
||||
|
||||
class MetersByInclination {
|
||||
public:
|
||||
double meters;
|
||||
double inclination;
|
||||
};
|
||||
|
||||
class bluetoothdevice : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
@@ -50,7 +56,10 @@ class bluetoothdevice : public QObject {
|
||||
virtual metric currentCadence();
|
||||
virtual double currentCrankRevolutions();
|
||||
virtual QGeoCoordinate currentCordinate();
|
||||
virtual double currentAzimuth() {qDebug() << azimuth; return azimuth;}
|
||||
virtual double currentAzimuth() { return azimuth; }
|
||||
virtual QList<MetersByInclination> nextInclination300Meters() { return NextInclination300Meters; }
|
||||
virtual double averageAzimuthNext300m() { return azimuthAvgNext300m; }
|
||||
virtual void setAverageAzimuthNext300m(double azimuth) { azimuthAvgNext300m = azimuth; }
|
||||
virtual uint16_t lastCrankEventTime();
|
||||
virtual void *VirtualDevice();
|
||||
uint16_t watts(double weight);
|
||||
@@ -72,6 +81,8 @@ class bluetoothdevice : public QObject {
|
||||
metric currentMETS() { return METS; }
|
||||
metric currentHeartZone() { return HeartZone; }
|
||||
metric currentPowerZone() { return PowerZone; }
|
||||
void setGPXFile(QString filename);
|
||||
QString currentGPXBase64() { return gpxBase64; }
|
||||
|
||||
// in the future these 2 should be calculated inside the update_metrics()
|
||||
void setHeartZone(double hz) { HeartZone = hz; }
|
||||
@@ -95,11 +106,12 @@ class bluetoothdevice : public QObject {
|
||||
virtual void changeResistance(int8_t res);
|
||||
virtual void changePower(int32_t power);
|
||||
virtual void changeInclination(double grade, double percentage);
|
||||
virtual void changeGeoPosition(QGeoCoordinate p, double azimuth);
|
||||
virtual void changeGeoPosition(QGeoCoordinate p, double azimuth, double avgAzimuthNext300Meters);
|
||||
virtual void workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state);
|
||||
virtual void instantaneousStrideLengthSensor(double length);
|
||||
virtual void groundContactSensor(double groundContact);
|
||||
virtual void verticalOscillationSensor(double verticalOscillation);
|
||||
virtual void changeNextInclination300Meters(QList<MetersByInclination> i) { NextInclination300Meters = i; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectedAndDiscovered();
|
||||
@@ -139,7 +151,12 @@ class bluetoothdevice : public QObject {
|
||||
|
||||
QGeoCoordinate coordinate;
|
||||
double azimuth;
|
||||
double azimuthAvgNext300m;
|
||||
quint64 coordinateTS = 0;
|
||||
double coordinateOdometer = 0;
|
||||
QString gpxBase64 = "";
|
||||
QString gpxFileName = "";
|
||||
QList<MetersByInclination> NextInclination300Meters;
|
||||
|
||||
metric Inclination;
|
||||
metric HeartZone;
|
||||
|
||||
5847
src/gpx/Italy - Passo dello Stelvio.gpx
Normal file
5847
src/gpx/Italy - Passo dello Stelvio.gpx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -356,11 +356,21 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
|
||||
connect(pelotonHandler, &peloton::pzpLoginState, this, &homeform::pzpLoginState);
|
||||
|
||||
// copying bundles zwo files in the right path if necessesary
|
||||
QDirIterator it(":/zwo/");
|
||||
while (it.hasNext()) {
|
||||
qDebug() << it.next() << it.fileName();
|
||||
if (!QFile(getWritableAppDir() + it.fileName()).exists()) {
|
||||
QFile::copy(":/zwo/" + it.fileName(), getWritableAppDir() + it.fileName());
|
||||
QDirIterator itZwo(":/zwo/");
|
||||
QDir().mkdir(getWritableAppDir() + "training/");
|
||||
while (itZwo.hasNext()) {
|
||||
qDebug() << itZwo.next() << itZwo.fileName();
|
||||
if (!QFile(getWritableAppDir() + "training/" + itZwo.fileName()).exists()) {
|
||||
QFile::copy(":/zwo/" + itZwo.fileName(), getWritableAppDir() + "training/" + itZwo.fileName());
|
||||
}
|
||||
}
|
||||
|
||||
QDirIterator itGpx(":/gpx/");
|
||||
QDir().mkdir(getWritableAppDir() + "gpx/");
|
||||
while (itGpx.hasNext()) {
|
||||
qDebug() << itGpx.next() << itGpx.fileName();
|
||||
if (!QFile(getWritableAppDir() + "gpx/" + itGpx.fileName()).exists()) {
|
||||
QFile::copy(":/gpx/" + itGpx.fileName(), getWritableAppDir() + "gpx/" + itGpx.fileName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,6 +581,8 @@ void homeform::trainProgramSignals() {
|
||||
&treadmill::changeSpeed);
|
||||
disconnect(trainProgram, &trainprogram::changeInclination, ((treadmill *)bluetoothManager->device()),
|
||||
&treadmill::changeInclination);
|
||||
disconnect(trainProgram, &trainprogram::changeNextInclination300Meters, bluetoothManager->device(),
|
||||
&bluetoothdevice::changeNextInclination300Meters);
|
||||
disconnect(trainProgram, &trainprogram::changeInclination, ((bike *)bluetoothManager->device()),
|
||||
&bike::changeInclination);
|
||||
disconnect(trainProgram, &trainprogram::changeFanSpeed, ((treadmill *)bluetoothManager->device()),
|
||||
@@ -640,6 +652,8 @@ void homeform::trainProgramSignals() {
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING)
|
||||
connect(trainProgram, &trainprogram::changePower, ((rower *)bluetoothManager->device()),
|
||||
&rower::changePower);
|
||||
connect(trainProgram, &trainprogram::changeNextInclination300Meters, bluetoothManager->device(),
|
||||
&bluetoothdevice::changeNextInclination300Meters);
|
||||
connect(((treadmill *)bluetoothManager->device()), &treadmill::tapeStarted, trainProgram,
|
||||
&trainprogram::onTapeStarted);
|
||||
connect(((bike *)bluetoothManager->device()), &bike::bikeStarted, trainProgram, &trainprogram::onTapeStarted);
|
||||
@@ -3423,6 +3437,8 @@ void homeform::gpx_open_clicked(const QUrl &fileName) {
|
||||
gpx g;
|
||||
QList<trainrow> list;
|
||||
auto g_list = g.open(file.fileName());
|
||||
if (bluetoothManager->device())
|
||||
bluetoothManager->device()->setGPXFile(file.fileName());
|
||||
gpx_altitude_point_for_treadmill last;
|
||||
quint32 i = 0;
|
||||
list.reserve(g_list.size() + 1);
|
||||
|
||||
1
src/inner_templates/googlemaps/bike.js
Normal file
1
src/inner_templates/googlemaps/bike.js
Normal file
File diff suppressed because one or more lines are too long
1
src/inner_templates/googlemaps/cesium-key.js
Normal file
1
src/inner_templates/googlemaps/cesium-key.js
Normal file
@@ -0,0 +1 @@
|
||||
Cesium.Ion.defaultAccessToken = '';
|
||||
94
src/inner_templates/googlemaps/chart.js
Normal file
94
src/inner_templates/googlemaps/chart.js
Normal file
@@ -0,0 +1,94 @@
|
||||
var inclinationArray = []
|
||||
|
||||
const range = ({from = 0, to, step = 1, length = Math.ceil((to - from) / step)}) =>
|
||||
Array.from({length}, (_, i) => from + i * step)
|
||||
|
||||
let canvasChart = {}
|
||||
|
||||
$(window).on('load', function () {
|
||||
const data = {
|
||||
labels: range({to: 300}),
|
||||
datasets: [{
|
||||
label: '',
|
||||
data: inclinationArray,
|
||||
fill: true,
|
||||
tension: 0.2,
|
||||
pointRadius: 0,
|
||||
}]
|
||||
};
|
||||
|
||||
let config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
plugins: {
|
||||
legend : {
|
||||
display: false,
|
||||
}
|
||||
},
|
||||
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
min: 0,
|
||||
max: 3000,
|
||||
ticks: {
|
||||
callback: () => ('')
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawOnChartArea:false,
|
||||
drawBorder: false, // <-- this removes y-axis line
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
callback: () => ('')
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawOnChartArea:false,
|
||||
drawBorder: false, // <-- this removes y-axis line
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ctx = document.getElementById('canvasChart').getContext('2d');
|
||||
canvasChart = new Chart(ctx, config);
|
||||
canvasChart.canvas.parentNode.style.height = '75px';
|
||||
canvasChart.canvas.parentNode.style.width = '150px';
|
||||
setTimeout(chartRefresh, 500);
|
||||
});
|
||||
|
||||
function chartRefresh() {
|
||||
onWorkout = false;
|
||||
let el = new MainWSQueueElement({
|
||||
msg: 'getnextinclination'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getnextinclination') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_nextinclination).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function process_nextinclination(msg) {
|
||||
let arr = msg.split(",");
|
||||
let lastValue = 0
|
||||
inclinationArray = [];
|
||||
for(var i=0; i<arr.length/2.0;i++) {
|
||||
for(var ii=0; ii<parseFloat(arr[i*2]);ii++) {
|
||||
lastValue += parseFloat(arr[(i*2)+1])
|
||||
inclinationArray.push(lastValue);
|
||||
}
|
||||
}
|
||||
canvasChart.data.datasets[0].data = inclinationArray;
|
||||
canvasChart.update();
|
||||
setTimeout(chartRefresh, 500);
|
||||
}
|
||||
8
src/inner_templates/googlemaps/chartjs-adapter-moment.js
Normal file
8
src/inner_templates/googlemaps/chartjs-adapter-moment.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* chartjs-adapter-moment v1.0.0
|
||||
* https://www.chartjs.org
|
||||
* (c) 2021 chartjs-adapter-moment Contributors
|
||||
* Released under the MIT license
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("moment"),require("chart.js")):"function"==typeof define&&define.amd?define(["moment","chart.js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).moment,e.Chart)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=n(e);const a={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};t._adapters._date.override("function"==typeof f.default?{_id:"moment",formats:function(){return a},parse:function(e,t){return"string"==typeof e&&"string"==typeof t?e=f.default(e,t):e instanceof f.default||(e=f.default(e)),e.isValid()?e.valueOf():null},format:function(e,t){return f.default(e).format(t)},add:function(e,t,n){return f.default(e).add(t,n).valueOf()},diff:function(e,t,n){return f.default(e).diff(f.default(t),n)},startOf:function(e,t,n){return e=f.default(e),"isoWeek"===t?(n=Math.trunc(Math.min(Math.max(0,n),6)),e.isoWeekday(n).startOf("day").valueOf()):e.startOf(t).valueOf()},endOf:function(e,t){return f.default(e).endOf(t).valueOf()}}:{})}));
|
||||
//# sourceMappingURL=chartjs-adapter-moment.min.js.map
|
||||
7
src/inner_templates/googlemaps/chartjs-plugin-annotation.min.js
vendored
Normal file
7
src/inner_templates/googlemaps/chartjs-plugin-annotation.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
src/inner_templates/googlemaps/chartjs.3.4.1.min.js
vendored
Normal file
13
src/inner_templates/googlemaps/chartjs.3.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
src/inner_templates/googlemaps/jquery-3.6.0.min.js
vendored
Normal file
2
src/inner_templates/googlemaps/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,26 +1,87 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/css/ol.css" type="text/css">
|
||||
<style>
|
||||
.map {
|
||||
height: 1000px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="globals.js"></script>
|
||||
<script src="main_ws_manager.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/build/ol.js"></script>
|
||||
<title>OpenLayers example</title>
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Include the CesiumJS JavaScript and CSS files -->
|
||||
<script src="https://cesium.com/downloads/cesiumjs/releases/1.94/Build/Cesium/Cesium.js"></script>
|
||||
<link href="https://cesium.com/downloads/cesiumjs/releases/1.94/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
|
||||
<script src="jquery-3.6.0.min.js"></script>
|
||||
<script src="chartjs.3.4.1.min.js"></script>
|
||||
<script src="chart.js"></script>
|
||||
<script src="globals.js"></script>
|
||||
<script src="bike.js"></script>
|
||||
<script src="main_ws_manager.js"></script>
|
||||
<script src="cesium-key.js"></script>
|
||||
<style>
|
||||
canvas{
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cesiumContainer" style="display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;"></div>
|
||||
<script>
|
||||
// Initialize the Cesium Viewer in the HTML element with the `cesiumContainer` ID.
|
||||
const viewer = new Cesium.Viewer('cesiumContainer', {
|
||||
terrainProvider: Cesium.createWorldTerrain()
|
||||
});
|
||||
|
||||
$('.cesium-viewer-animationContainer').css("visibility", "hidden");
|
||||
$('.cesium-viewer-bottom').css("visibility", "hidden");
|
||||
$('.cesium-viewer-timelineContainer').css("visibility", "hidden");
|
||||
$('.cesium-viewer-fullscreenContainer').css("visibility", "hidden");
|
||||
|
||||
|
||||
viewer.scene.globe.enableLighting = true;
|
||||
viewer.scene.fog.enabled = true;
|
||||
viewer.scene.globe.depthTestAgainstTerrain = true;
|
||||
// Add Cesium OSM Buildings, a global 3D buildings layer.
|
||||
//const buildingTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());
|
||||
var center = Cesium.Cartesian3.fromDegrees(10.855092, 44.508273, 180);
|
||||
var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
|
||||
let map = viewer;
|
||||
viewer.scene.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(0), -Math.PI/8, 100));
|
||||
|
||||
var bike = viewer.scene.primitives.add(
|
||||
Cesium.ModelExperimental.fromGltf({
|
||||
url: bikeUri,
|
||||
minimumPixelSize: 48,
|
||||
scale: 1,
|
||||
scene : viewer.scene,
|
||||
color: Cesium.Color.ORANGE,
|
||||
silhouetteColor: Cesium.Color.ORANGE,
|
||||
silhouetteSize: 2,
|
||||
})
|
||||
);
|
||||
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
<body>
|
||||
<div id="map" class="map"></div>
|
||||
<div id="cesiumContainer" class="cesiumContainer"></div>
|
||||
<div><p class="metrics" style="color: #FFFFFF; position: absolute; top: 80px; right: 0;">Speed: --<br>Cadence: --</p></div>
|
||||
<div style="background-color:white; border: 0px solid #aaa; border-radius: 10px; overflow: hidden; position:absolute; bottom: 0px; right: 0; width=150px; height=75px"><canvas id="canvasChart" style="width=150px; height=75px"></canvas></div>
|
||||
<script type="text/javascript">
|
||||
let cameraComplete = true
|
||||
let lastAzimuth = 0
|
||||
let lastAlt = 0
|
||||
let lastLat = 0
|
||||
let lastLon = 0
|
||||
let refreshRate = 25
|
||||
let currentSpeed = 0
|
||||
let lastCoordinateTS = 0
|
||||
let altOffset = 0
|
||||
|
||||
function a() {
|
||||
let lat = 0
|
||||
let lon = 0
|
||||
onWorkout = false;
|
||||
let el = new MainWSQueueElement({
|
||||
msg: 'getlatlon'
|
||||
}, function(msg) {
|
||||
@@ -32,46 +93,243 @@
|
||||
el.enqueue().then(process_latlon).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
keys_arr = ['speed', 'cadence', 'heart', 'calories', 'distance', 'watts', 'elapsed_h', 'elapsed_m', 'elapsed_s', 'inclination', 'resistance', 'elevation', 'altitude']
|
||||
let ell = new MainWSQueueElement({
|
||||
msg: null,
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'workout') {
|
||||
var speed = 0;
|
||||
var cadence = 0;
|
||||
var hr = 0;
|
||||
var calories = 0;
|
||||
var odometer = 0;
|
||||
var watt = 0;
|
||||
var elapsed_h = 0;
|
||||
var elapsed_m = 0;
|
||||
var elapsed_s = 0;
|
||||
var inclination = 0;
|
||||
var resistance = 0;
|
||||
var elevation = 0;
|
||||
var altitude = 0;
|
||||
for (let key of keys_arr) {
|
||||
if (msg.content[key] === undefined)
|
||||
continue;
|
||||
if (key === 'speed') {
|
||||
currentSpeed = speed = msg.content[key];
|
||||
} else if (key === 'cadence') {
|
||||
cadence = msg.content[key];
|
||||
} else if (key === 'heart') {
|
||||
hr = msg.content[key];
|
||||
} else if (key === 'calories') {
|
||||
calories = msg.content[key];
|
||||
} else if (key === 'distance') {
|
||||
odometer = msg.content[key];
|
||||
} else if (key === 'watts') {
|
||||
watt = msg.content[key];
|
||||
} else if (key === 'elapsed_h') {
|
||||
elapsed_h = msg.content[key];
|
||||
} else if (key === 'elapsed_m') {
|
||||
elapsed_m = msg.content[key];
|
||||
} else if (key === 'elapsed_s') {
|
||||
elapsed_s = msg.content[key];
|
||||
} else if (key === 'inclination') {
|
||||
inclination = msg.content[key];
|
||||
} else if (key === 'resistance') {
|
||||
resistance = msg.content[key];
|
||||
} else if (key === 'elevation') {
|
||||
elevation = msg.content[key];
|
||||
} else if (key === 'altitude') {
|
||||
altitude = msg.content[key];
|
||||
}
|
||||
}
|
||||
|
||||
$('.metrics').html("🏃Speed: " + speed.toFixed(2) + "<br>🚴Cadence:" + cadence.toFixed(0) + "<br>💓Heart:"+ hr.toFixed(0) + "<br>🔥Calories:"+ calories.toFixed(1) + "<br>📏Odometer:"+ odometer.toFixed(2) + "<br>⚡Watt:"+ watt.toFixed(0) + "<br>⏲️Elapsed:"+ elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":"+ elapsed_s.toString().padStart(2, "0") + "<br>📐Inclination:"+ inclination.toFixed(1) + "<br>🧲Resistance:"+ resistance.toFixed(0) + "<br>✈️Altitude:"+ altitude.toFixed(1) + "<br>⛰️Elevation:"+ elevation.toFixed(2));
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
ell.enqueue().then(onWorkout).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function camera_complete() {
|
||||
cameraComplete = true;
|
||||
}
|
||||
|
||||
function easing_function(time) {
|
||||
return time;
|
||||
}
|
||||
|
||||
function process_latlon(msg) {
|
||||
/*if(!cameraComplete) return;*/
|
||||
cameraComplete = false;
|
||||
let lat = parseFloat(msg.split(",")[0]);
|
||||
let lon = parseFloat(msg.split(",")[1]);
|
||||
map.getView().animate({center: ol.proj.fromLonLat([lon, lat])});
|
||||
markers.getSource().getFeatures()[0].getGeometry().setCoordinates(ol.proj.fromLonLat([lon, lat]));
|
||||
setTimeout(a,2000);
|
||||
let alt = parseFloat(msg.split(",")[2]) + altOffset;
|
||||
let realAzimuth = parseFloat(msg.split(",")[3]);
|
||||
let azimuth = parseFloat(msg.split(",")[4]);
|
||||
let realLat = lat
|
||||
let realLon = lon
|
||||
if(lastAzimuth != azimuth) {
|
||||
if((lastAzimuth > azimuth && Math.abs(lastAzimuth - azimuth) < 180) ||
|
||||
(lastAzimuth < azimuth && Math.abs(lastAzimuth - azimuth) > 180)) {
|
||||
lastAzimuth = azimuth = lastAzimuth - 0.01;
|
||||
if(azimuth < 0)
|
||||
lastAzimuth = azimuth = 359.99;
|
||||
}
|
||||
else {
|
||||
lastAzimuth = azimuth = lastAzimuth + 0.01;
|
||||
if(azimuth >= 360)
|
||||
lastAzimuth = azimuth = 0;
|
||||
}
|
||||
}
|
||||
if(Math.abs(lastAzimuth - azimuth) < 0.01)
|
||||
lastAzimuth = azimuth;
|
||||
if(lastAlt != alt && Math.abs(lastAlt-alt) < 30) {
|
||||
if(lastAlt > alt)
|
||||
lastAlt = alt = lastAlt - 0.01;
|
||||
else
|
||||
lastAlt = alt = lastAlt + 0.01;
|
||||
} else {
|
||||
lastAlt = alt;
|
||||
}
|
||||
if(Math.abs(lastAlt - alt) < 0.01)
|
||||
lastAlt = alt;
|
||||
|
||||
var p1 = Cesium.Cartesian3.fromDegrees(lon, lat, alt);
|
||||
var p2 = Cesium.Cartesian3.fromDegrees(lastLon, lastLat, lastAlt);
|
||||
var distance = Cesium.Cartesian3.distance(p1, p2);
|
||||
var tick = (Date.now() - lastCoordinateTS)
|
||||
let deltaT = (currentSpeed / 3.6) / (1000 / (tick - 1));
|
||||
if(distance < 30)
|
||||
deltaT = deltaT / 2;
|
||||
lastCoordinateTS = Date.now()
|
||||
deltaT = distance / deltaT;
|
||||
console.log(Date.now() + " distance delta " + distance + " " + tick);
|
||||
if(Math.abs(lat - lastLat) > 0.1 || Math.abs(lon - lastLon) > 0.1) {
|
||||
lastLat = lat;
|
||||
lastLon = lon;
|
||||
lastCoordinateTS = Date.now()
|
||||
} else if(lat != lastLat || lon != lastLon) {
|
||||
if(lat > lastLat) {
|
||||
lastLat = lat = lastLat + ((lat-lastLat) / deltaT);
|
||||
} else {
|
||||
lastLat = lat = lastLat - ((lastLat - lat) / deltaT);
|
||||
}
|
||||
if(lon > lastLon) {
|
||||
lastLon = lon = lastLon + ((lon-lastLon) / deltaT);
|
||||
} else {
|
||||
lastLon = lon = lastLon - ((lastLon-lon) / deltaT);
|
||||
}
|
||||
}
|
||||
|
||||
let map = new ol.Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new ol.layer.Tile({
|
||||
source: new ol.source.OSM()
|
||||
})
|
||||
],
|
||||
view: new ol.View({
|
||||
center: ol.proj.fromLonLat([37.41, 8.82]),
|
||||
zoom: 16
|
||||
})
|
||||
});
|
||||
|
||||
markers = new ol.layer.Vector({
|
||||
source: new ol.source.Vector(),
|
||||
style: new ol.style.Style({
|
||||
image: new ol.style.Icon({
|
||||
anchor: [0.5, 0.5],
|
||||
anchorXUnits: 'fraction',
|
||||
anchorYUnits: 'fraction',
|
||||
scale: 0.2,
|
||||
src: 'marker.png'
|
||||
})
|
||||
})
|
||||
});
|
||||
map.addLayer(markers);
|
||||
console.log("azimuth " + azimuth + " " + lat + " " + lon);
|
||||
var center = Cesium.Cartesian3.fromDegrees(lon, lat, alt);
|
||||
var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
|
||||
viewer.scene.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(azimuth), -Math.PI/8, 300));
|
||||
|
||||
let marker = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat([37.41, 8.82])));
|
||||
markers.getSource().addFeature(marker);
|
||||
|
||||
const length = 2;
|
||||
|
||||
const startLon = Cesium.Math.toRadians(realLon);
|
||||
const endLon = Cesium.Math.toRadians(realLon + 0.000001);
|
||||
|
||||
const startLat = Cesium.Math.toRadians(realLat);
|
||||
|
||||
const terrainSamplePositions = [];
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const lonL = Cesium.Math.lerp(
|
||||
endLon,
|
||||
startLon,
|
||||
i / (length - 1)
|
||||
);
|
||||
const position = new Cesium.Cartographic(lonL, startLat);
|
||||
terrainSamplePositions.push(position);
|
||||
}
|
||||
|
||||
Promise.resolve(
|
||||
Cesium.sampleTerrainMostDetailed(
|
||||
viewer.terrainProvider,
|
||||
terrainSamplePositions
|
||||
)
|
||||
).then(function (samples) {
|
||||
const hpr = new Cesium.HeadingPitchRoll(
|
||||
Cesium.Math.toRadians(realAzimuth),
|
||||
0,
|
||||
0
|
||||
);
|
||||
bike.modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(Cesium.Cartesian3.fromDegrees(realLon, realLat, samples[0].height), hpr);
|
||||
});
|
||||
|
||||
const startLonCamera = Cesium.Math.toRadians(Cesium.Math.toDegrees(viewer.scene.camera.positionCartographic.longitude));
|
||||
const endLonCamera = Cesium.Math.toRadians(Cesium.Math.toDegrees(viewer.scene.camera.positionCartographic.longitude) + 0.000001);
|
||||
const startLatCamera = Cesium.Math.toRadians(Cesium.Math.toDegrees(viewer.scene.camera.positionCartographic.latitude));
|
||||
|
||||
const terrainSamplePositionsCamera = [];
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const lonLCamera = Cesium.Math.lerp(
|
||||
endLonCamera,
|
||||
startLonCamera,
|
||||
i / (length - 1)
|
||||
);
|
||||
const positionCamera = new Cesium.Cartographic(lonLCamera, startLatCamera);
|
||||
terrainSamplePositionsCamera.push(positionCamera);
|
||||
}
|
||||
|
||||
Promise.resolve(
|
||||
Cesium.sampleTerrainMostDetailed(
|
||||
viewer.terrainProvider,
|
||||
terrainSamplePositionsCamera
|
||||
)
|
||||
).then(function (samples) {
|
||||
let terrain_height = samples[0].height;
|
||||
let camera_height = viewer.scene.camera.positionCartographic.height;
|
||||
|
||||
//Determine terrain relative height (edist)
|
||||
let edist = camera_height - terrain_height;
|
||||
//console.log(camera_height + " " + terrain_height)
|
||||
//Unburrow code
|
||||
if (edist - altOffset < 50) {
|
||||
altOffset = 70 - edist
|
||||
} else {
|
||||
altOffset = 0;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(a,refreshRate);
|
||||
/*
|
||||
viewer.camera.flyTo({
|
||||
destination : Cesium.Cartesian3.fromDegrees(lon, lat, alt + 60),
|
||||
duration: 2,
|
||||
orientation : {
|
||||
heading : Cesium.Math.toRadians(azimuth),
|
||||
pitch : Cesium.Math.toRadians(-5.0),
|
||||
},
|
||||
easingFunction: easing_function,
|
||||
complete: camera_complete
|
||||
});*/
|
||||
}
|
||||
function process_gpxbase64(msg) {
|
||||
viewer.dataSources
|
||||
.add(
|
||||
Cesium.GpxDataSource.load(
|
||||
msg,
|
||||
{
|
||||
clampToGround: true,
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
let el = new MainWSQueueElement({
|
||||
msg: 'getgpxbase64'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getgpxbase64') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_gpxbase64).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
setTimeout(a,0);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
9
src/inner_templates/maps2d/globals.js
Normal file
9
src/inner_templates/maps2d/globals.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const host_url = (!location.host || location.host.length == 0)?'192.168.25.24:7666':location.host;
|
||||
|
||||
function get_template_name() {
|
||||
let splits = location.pathname.split('/');
|
||||
if (splits.length>=2)
|
||||
return splits[splits.length - 2];
|
||||
else
|
||||
return '';
|
||||
}
|
||||
132
src/inner_templates/maps2d/main_ws_manager.js
Normal file
132
src/inner_templates/maps2d/main_ws_manager.js
Normal file
@@ -0,0 +1,132 @@
|
||||
let main_ws = null;
|
||||
let main_ws_queue = [];
|
||||
|
||||
class MainWSQueueElement {
|
||||
constructor(msg_to_send, _inner_process, timeout, retry_num) {
|
||||
this.msg_to_send = msg_to_send;
|
||||
this.needs_to_send = msg_to_send != null;
|
||||
this.timeout = timeout || 5000;
|
||||
this.retry_num = retry_num || 1;
|
||||
this.timer = null;
|
||||
this.resolve = null;
|
||||
this.reject = null;
|
||||
this._inner_process = _inner_process;
|
||||
}
|
||||
|
||||
inner_process_msg(msg) {
|
||||
if (this._inner_process)
|
||||
return this._inner_process(msg);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
process_arrived_msg(msg) {
|
||||
let out = this.inner_process_msg(msg);
|
||||
if (out) {
|
||||
if (this.timer !==null) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
if (this.resolve)
|
||||
setTimeout(function() { this.resolve(out); }.bind(this), 0);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
enqueue() {
|
||||
main_ws_enqueue(this);
|
||||
return new Promise(function(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
pop_msg_to_send() {
|
||||
if (this.needs_to_send) {
|
||||
this.needs_to_send = false;
|
||||
if (this.retry_num < 0 || this.retry_num > 0)
|
||||
this.timer = setTimeout(function() {
|
||||
this.timer = null;
|
||||
if (this.retry_num == 0) {
|
||||
main_ws_dequeue(this);
|
||||
if (this.reject) {
|
||||
this.reject(new Error('Timeout error detected'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.needs_to_send = true;
|
||||
main_ws_enqueue();
|
||||
}
|
||||
}.bind(this), this.timeout);
|
||||
if (this.retry_num > 0)
|
||||
this.retry_num--;
|
||||
return this.msg_to_send;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function main_ws_dequeue(el) {
|
||||
let idx = main_ws_queue.indexOf(el);
|
||||
if (idx >= 0) {
|
||||
main_ws_queue.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function main_ws_enqueue(el) {
|
||||
if (el)
|
||||
main_ws_queue.push(el);
|
||||
if (main_ws)
|
||||
main_ws_queue_process();
|
||||
}
|
||||
|
||||
function main_ws_queue_process(msg) {
|
||||
if (!main_ws)
|
||||
return;
|
||||
let jsonobj;
|
||||
for (let i = 0; i < main_ws_queue.length; i++) {
|
||||
let el = main_ws_queue[i];
|
||||
if ((jsonobj = el.pop_msg_to_send())) {
|
||||
let logString = JSON.stringify(jsonobj);
|
||||
main_ws.send(logString);
|
||||
console.log('WS >> ' + logString);
|
||||
}
|
||||
else if (msg) {
|
||||
if (el.process_arrived_msg(msg)) {
|
||||
main_ws_queue.splice(i, 1);
|
||||
i--;
|
||||
msg = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function main_ws_connect() {
|
||||
let socket = new WebSocket((location.protocol == 'https:'?'wss://' : 'ws://') + host_url + '/' + get_template_name() + '-ws');
|
||||
socket.onopen = function (event) {
|
||||
console.log('Upgrade HTTP connection OK');
|
||||
main_ws = socket;
|
||||
main_ws_queue_process();
|
||||
};
|
||||
socket.onclose = function(e) {
|
||||
main_ws = null;
|
||||
console.log('Socket is closed. Reconnect will be attempted in 30 second.', e.reason);
|
||||
setTimeout(function() {
|
||||
main_ws_connect();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
socket.onerror = function(err) {
|
||||
main_ws = null;
|
||||
console.error('Socket encountered error: ', err.message, 'Closing socket');
|
||||
socket.close();
|
||||
};
|
||||
socket.onmessage = function (event) {
|
||||
console.log(event.data);
|
||||
let msg = JSON.parse(event.data);
|
||||
main_ws_queue_process(msg);
|
||||
};
|
||||
}
|
||||
main_ws_connect();
|
||||
78
src/inner_templates/maps2d/maps.htm
Normal file
78
src/inner_templates/maps2d/maps.htm
Normal file
@@ -0,0 +1,78 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/css/ol.css" type="text/css">
|
||||
<style>
|
||||
.map {
|
||||
height: 1000px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="globals.js"></script>
|
||||
<script src="main_ws_manager.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/build/ol.js"></script>
|
||||
<title>OpenLayers example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" class="map"></div>
|
||||
<script type="text/javascript">
|
||||
function a() {
|
||||
let lat = 0
|
||||
let lon = 0
|
||||
let el = new MainWSQueueElement({
|
||||
msg: 'getlatlon'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getlatlon') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_latlon).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function process_latlon(msg) {
|
||||
let lat = parseFloat(msg.split(",")[0]);
|
||||
let lon = parseFloat(msg.split(",")[1]);
|
||||
map.getView().animate({center: ol.proj.fromLonLat([lon, lat])});
|
||||
markers.getSource().getFeatures()[0].getGeometry().setCoordinates(ol.proj.fromLonLat([lon, lat]));
|
||||
setTimeout(a,2000);
|
||||
}
|
||||
|
||||
let map = new ol.Map({
|
||||
target: 'map',
|
||||
layers: [
|
||||
new ol.layer.Tile({
|
||||
source: new ol.source.OSM()
|
||||
})
|
||||
],
|
||||
view: new ol.View({
|
||||
center: ol.proj.fromLonLat([37.41, 8.82]),
|
||||
zoom: 16
|
||||
})
|
||||
});
|
||||
|
||||
markers = new ol.layer.Vector({
|
||||
source: new ol.source.Vector(),
|
||||
style: new ol.style.Style({
|
||||
image: new ol.style.Icon({
|
||||
anchor: [0.5, 0.5],
|
||||
anchorXUnits: 'fraction',
|
||||
anchorYUnits: 'fraction',
|
||||
scale: 0.2,
|
||||
src: 'marker.png'
|
||||
})
|
||||
})
|
||||
});
|
||||
map.addLayer(markers);
|
||||
|
||||
let marker = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat([37.41, 8.82])));
|
||||
markers.getSource().addFeature(marker);
|
||||
|
||||
setTimeout(a,0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src/inner_templates/maps2d/marker.png
Normal file
BIN
src/inner_templates/maps2d/marker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -512,7 +512,7 @@ ApplicationWindow {
|
||||
}
|
||||
ItemDelegate {
|
||||
id: gpx_open
|
||||
text: qsTr("Open GPX")
|
||||
text: qsTr("🗺️ Open GPX")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
fileDialogGPX.visible = true
|
||||
@@ -521,7 +521,7 @@ ApplicationWindow {
|
||||
}
|
||||
ItemDelegate {
|
||||
id: trainprogram_open
|
||||
text: qsTr("Open Train Program")
|
||||
text: qsTr("📈 Open Train Program")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
stackView.push("TrainingProgramsList.qml")
|
||||
@@ -606,7 +606,7 @@ ApplicationWindow {
|
||||
FileDialog {
|
||||
id: fileDialogGPX
|
||||
title: "Please choose a file"
|
||||
folder: shortcuts.home
|
||||
folder: "file://" + rootItem.getWritableAppDir() + 'gpx'
|
||||
onAccepted: {
|
||||
console.log("You chose: " + fileDialogGPX.fileUrl)
|
||||
gpx_open_clicked(fileDialogGPX.fileUrl)
|
||||
|
||||
12
src/qml.qrc
12
src/qml.qrc
@@ -53,6 +53,8 @@
|
||||
<file>inner_templates/googlemaps/marker.png</file>
|
||||
<file>settings-tts.qml</file>
|
||||
<file>NewPageElement.qml</file>
|
||||
<file>inner_templates/googlemaps/jquery-3.6.0.min.js</file>
|
||||
<file>inner_templates/googlemaps/bike.js</file>
|
||||
<file>zwo/Easy intervals.zwo</file>
|
||||
<file>zwo/HIIT Aerobic.zwo</file>
|
||||
<file>zwo/HIIT Anaerobic Sprint.zwo</file>
|
||||
@@ -64,5 +66,15 @@
|
||||
<file>zwo/Tempo and endurance.zwo</file>
|
||||
<file>zwo/VO2max with recovery.zwo</file>
|
||||
<file>zwo/VO2max with sweet spot.zwo</file>
|
||||
<file>inner_templates/googlemaps/chart.js</file>
|
||||
<file>inner_templates/googlemaps/chartjs.3.4.1.min.js</file>
|
||||
<file>inner_templates/googlemaps/chartjs-adapter-moment.js</file>
|
||||
<file>inner_templates/googlemaps/chartjs-plugin-annotation.min.js</file>
|
||||
<file>inner_templates/googlemaps/cesium-key.js</file>
|
||||
<file>inner_templates/maps2d/globals.js</file>
|
||||
<file>inner_templates/maps2d/main_ws_manager.js</file>
|
||||
<file>inner_templates/maps2d/maps.htm</file>
|
||||
<file>inner_templates/maps2d/marker.png</file>
|
||||
<file>gpx/Italy - Passo dello Stelvio.gpx</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -442,6 +442,9 @@ import Qt.labs.settings 1.0
|
||||
property bool tile_vertical_oscillation_enabled: false
|
||||
property int tile_vertical_oscillation_order: 34
|
||||
property string sex: "Male"
|
||||
|
||||
// from the version 2.10.111
|
||||
property string maps_type: "3D"
|
||||
}
|
||||
|
||||
function paddingZeros(text, limit) {
|
||||
@@ -6067,6 +6070,46 @@ import Qt.labs.settings 1.0
|
||||
accordionContent: "settings-tts.qml"
|
||||
}
|
||||
|
||||
AccordionElement {
|
||||
id: mapsAccordion
|
||||
title: qsTr("Maps 🗺️")
|
||||
indicatRectColor: Material.color(Material.Grey)
|
||||
textColor: Material.color(Material.Grey)
|
||||
color: Material.backgroundColor
|
||||
//width: 640
|
||||
//anchors.top: acc1.bottom
|
||||
//anchors.topMargin: 10
|
||||
accordionContent: ColumnLayout {
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
id: labelMapsType
|
||||
text: qsTr("Maps Type:")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ComboBox {
|
||||
id: mapsTypeTextField
|
||||
model: [ "2D", "3D" ]
|
||||
displayText: settings.maps_type
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onActivated: {
|
||||
console.log("combomodel activated" + mapsTypeTextField.currentIndex)
|
||||
displayText = mapsTypeTextField.currentValue
|
||||
}
|
||||
|
||||
}
|
||||
Button {
|
||||
id: okMapsType
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: settings.maps_type = mapsTypeTextField.displayText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AccordionElement {
|
||||
id: experimentalFeatureAccordion
|
||||
title: qsTr("Experimental Features")
|
||||
|
||||
@@ -476,19 +476,45 @@ void TemplateInfoSenderBuilder::onGetSessionArray(TemplateInfoSender *tempSender
|
||||
tempSender->send(out.toJson());
|
||||
}
|
||||
|
||||
void TemplateInfoSenderBuilder::onGetGPXBase64(TemplateInfoSender *tempSender) {
|
||||
if (!device)
|
||||
return;
|
||||
QJsonObject main;
|
||||
main[QStringLiteral("content")] = device->currentGPXBase64();
|
||||
main[QStringLiteral("msg")] = QStringLiteral("R_getgpxbase64");
|
||||
QJsonDocument out(main);
|
||||
tempSender->send(out.toJson());
|
||||
}
|
||||
|
||||
void TemplateInfoSenderBuilder::onGetLatLon(TemplateInfoSender *tempSender) {
|
||||
if (!device)
|
||||
return;
|
||||
QJsonObject main;
|
||||
main[QStringLiteral("content")] = QString::number(device->currentCordinate().latitude()) + "," +
|
||||
QString::number(device->currentCordinate().longitude()) + "," +
|
||||
QString::number(device->currentCordinate().altitude()) + "," +
|
||||
QString::number(device->currentAzimuth());
|
||||
main[QStringLiteral("content")] = QString::number(device->currentCordinate().latitude(), 'g', 9) + "," +
|
||||
QString::number(device->currentCordinate().longitude(), 'g', 9) + "," +
|
||||
QString::number(device->currentCordinate().altitude(), 'g', 9) + "," +
|
||||
QString::number(device->currentAzimuth(), 'g', 9) + "," +
|
||||
QString::number(device->averageAzimuthNext300m());
|
||||
main[QStringLiteral("msg")] = QStringLiteral("R_getlatlon");
|
||||
QJsonDocument out(main);
|
||||
tempSender->send(out.toJson());
|
||||
}
|
||||
|
||||
void TemplateInfoSenderBuilder::onNextInclination300Meters(TemplateInfoSender *tempSender) {
|
||||
if (!device)
|
||||
return;
|
||||
QJsonObject main;
|
||||
QList<MetersByInclination> ii = device->nextInclination300Meters();
|
||||
QString values = "";
|
||||
for (int i = 0; i < ii.length(); i++) {
|
||||
values += QString::number(ii.at(i).meters, 'g', 0) + "," + QString::number(ii.at(i).inclination, 'g', 1) + ",";
|
||||
}
|
||||
main[QStringLiteral("content")] = values;
|
||||
main[QStringLiteral("msg")] = QStringLiteral("R_getnextinclination");
|
||||
QJsonDocument out(main);
|
||||
tempSender->send(out.toJson());
|
||||
}
|
||||
|
||||
void TemplateInfoSenderBuilder::onStart(TemplateInfoSender *tempSender) {
|
||||
if (!device->isPaused()) {
|
||||
device->clearStats();
|
||||
@@ -647,6 +673,12 @@ void TemplateInfoSenderBuilder::onDataReceived(const QByteArray &data) {
|
||||
} else if (msg == QStringLiteral("getlatlon")) {
|
||||
onGetLatLon(sender);
|
||||
return;
|
||||
} else if (msg == QStringLiteral("getnextinclination")) {
|
||||
onNextInclination300Meters(sender);
|
||||
return;
|
||||
} else if (msg == QStringLiteral("getgpxbase64")) {
|
||||
onGetGPXBase64(sender);
|
||||
return;
|
||||
} else if (msg == QStringLiteral("setresistance")) {
|
||||
onSetResistance(jsonObject[QStringLiteral("content")], sender);
|
||||
return;
|
||||
@@ -699,7 +731,7 @@ void TemplateInfoSenderBuilder::onDataReceived(const QByteArray &data) {
|
||||
}
|
||||
}
|
||||
}
|
||||
qDebug() << QStringLiteral("Unrecognized message") << data;
|
||||
// qDebug() << QStringLiteral("Unrecognized message") << data;
|
||||
}
|
||||
|
||||
void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
|
||||
@@ -799,6 +831,7 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
|
||||
obj.setProperty(QStringLiteral("instructorName"), instructorName);
|
||||
obj.setProperty(QStringLiteral("latitude"), device->currentCordinate().latitude());
|
||||
obj.setProperty(QStringLiteral("longitude"), device->currentCordinate().longitude());
|
||||
obj.setProperty(QStringLiteral("altitude"), device->currentCordinate().altitude());
|
||||
obj.setProperty(
|
||||
QStringLiteral("nickName"),
|
||||
(nickName = settings.value(QStringLiteral("user_nickname"), QStringLiteral("")).toString()).isEmpty()
|
||||
|
||||
@@ -59,6 +59,8 @@ class TemplateInfoSenderBuilder : public QObject {
|
||||
void onAppendActivityDescription(const QJsonValue &msgContent, TemplateInfoSender *tempSender);
|
||||
void onGetSessionArray(TemplateInfoSender *tempSender);
|
||||
void onGetLatLon(TemplateInfoSender *tempSender);
|
||||
void onNextInclination300Meters(TemplateInfoSender *tempSender);
|
||||
void onGetGPXBase64(TemplateInfoSender *tempSender);
|
||||
void onStart(TemplateInfoSender *tempSender);
|
||||
void onPause(TemplateInfoSender *tempSender);
|
||||
void onStop(TemplateInfoSender *tempSender);
|
||||
|
||||
@@ -79,6 +79,74 @@ double trainprogram::calculateDistanceForRow(int32_t row) {
|
||||
return rows.at(row).distance;
|
||||
}
|
||||
|
||||
// meters, inclination
|
||||
QList<MetersByInclination> trainprogram::inclinationNext300Meters() {
|
||||
int c = currentStep;
|
||||
double km = 0;
|
||||
QList<MetersByInclination> next300;
|
||||
|
||||
while (1) {
|
||||
if (c < rows.length()) {
|
||||
if (km > 0.3) {
|
||||
return next300;
|
||||
}
|
||||
MetersByInclination p;
|
||||
p.meters = rows.at(c).distance * 1000.0;
|
||||
p.inclination = rows.at(c).inclination;
|
||||
next300.append(p);
|
||||
km += rows.at(c).distance;
|
||||
|
||||
} else {
|
||||
return next300;
|
||||
}
|
||||
c++;
|
||||
}
|
||||
return next300;
|
||||
}
|
||||
|
||||
double trainprogram::avgAzimuthNext300Meters() {
|
||||
int c = currentStep;
|
||||
double km = 0;
|
||||
double sinTotal = 0;
|
||||
double cosTotal = 0;
|
||||
|
||||
if (rows.at(c).latitude != 0 || rows.at(c).longitude != 0) {
|
||||
while (1) {
|
||||
if (c < rows.length()) {
|
||||
if (km > 0.3) {
|
||||
double averageDirection = atan(sinTotal / cosTotal) * (180 / M_PI);
|
||||
|
||||
if (cosTotal < 0) {
|
||||
averageDirection += 180;
|
||||
} else if (sinTotal < 0) {
|
||||
averageDirection += 360;
|
||||
}
|
||||
return averageDirection;
|
||||
}
|
||||
|
||||
for (double i = 0; i < rows.at(c).distance; i += 0.001) {
|
||||
sinTotal += sin(rows.at(c).azimuth * (M_PI / 180));
|
||||
cosTotal += cos(rows.at(c).azimuth * (M_PI / 180));
|
||||
}
|
||||
|
||||
km += rows.at(c).distance;
|
||||
|
||||
} else {
|
||||
double averageDirection = atan(sinTotal / cosTotal) * (180 / M_PI);
|
||||
|
||||
if (cosTotal < 0) {
|
||||
averageDirection += 180;
|
||||
} else if (sinTotal < 0) {
|
||||
averageDirection += 360;
|
||||
}
|
||||
return averageDirection;
|
||||
}
|
||||
c++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void trainprogram::scheduler() {
|
||||
|
||||
QSettings settings;
|
||||
@@ -104,6 +172,7 @@ void trainprogram::scheduler() {
|
||||
if (rows.at(0).inclination != -200) {
|
||||
qDebug() << QStringLiteral("trainprogram change inclination") + QString::number(rows.at(0).inclination);
|
||||
emit changeInclination(rows.at(0).inclination, rows.at(0).inclination);
|
||||
emit changeNextInclination300Meters(inclinationNext300Meters());
|
||||
}
|
||||
} else {
|
||||
if (rows.at(0).resistance != -1) {
|
||||
@@ -139,6 +208,7 @@ void trainprogram::scheduler() {
|
||||
if (!((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
|
||||
bluetoothManager->device()->setInclination(rows.at(0).inclination);
|
||||
emit changeInclination(rows.at(0).inclination, rows.at(0).inclination);
|
||||
emit changeNextInclination300Meters(inclinationNext300Meters());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +225,7 @@ void trainprogram::scheduler() {
|
||||
p.setAltitude(rows.at(0).altitude);
|
||||
p.setLatitude(rows.at(0).latitude);
|
||||
p.setLongitude(rows.at(0).longitude);
|
||||
emit changeGeoPosition(p, rows.at(0).azimuth);
|
||||
emit changeGeoPosition(p, rows.at(0).azimuth, avgAzimuthNext300Meters());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +280,7 @@ void trainprogram::scheduler() {
|
||||
qDebug() << QStringLiteral("trainprogram change inclination ") +
|
||||
QString::number(rows.at(currentStep).inclination);
|
||||
emit changeInclination(rows.at(currentStep).inclination, rows.at(currentStep).inclination);
|
||||
emit changeNextInclination300Meters(inclinationNext300Meters());
|
||||
}
|
||||
} else {
|
||||
if (rows.at(currentStep).resistance != -1) {
|
||||
@@ -251,6 +322,7 @@ void trainprogram::scheduler() {
|
||||
if (!((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
|
||||
bluetoothManager->device()->setInclination(rows.at(currentStep).inclination);
|
||||
emit changeInclination(rows.at(currentStep).inclination, rows.at(currentStep).inclination);
|
||||
emit changeNextInclination300Meters(inclinationNext300Meters());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,12 +338,20 @@ void trainprogram::scheduler() {
|
||||
QString::number(rows.at(currentStep).latitude) + " " +
|
||||
QString::number(rows.at(currentStep).longitude) + " " +
|
||||
QString::number(rows.at(currentStep).altitude) + " " +
|
||||
QString::number(rows.at(currentStep).distance) + " " +
|
||||
QString::number(rows.at(currentStep).azimuth);
|
||||
|
||||
QGeoCoordinate p;
|
||||
p.setAltitude(rows.at(currentStep).altitude);
|
||||
p.setLatitude(rows.at(currentStep).latitude);
|
||||
p.setLongitude(rows.at(currentStep).longitude);
|
||||
emit changeGeoPosition(p, rows.at(currentStep).azimuth);
|
||||
p.setAltitude(rows.at(currentStep).altitude);
|
||||
// qDebug() << c << rows.at(currentStep+1).latitude << rows.at(currentStep + 1).longitude <<
|
||||
// c.distanceTo(p) << rows.at(currentStep).distance;
|
||||
|
||||
if (bluetoothManager->device()->odometer() - lastOdometer > 0)
|
||||
p = p.atDistanceAndAzimuth((bluetoothManager->device()->odometer() - lastOdometer),
|
||||
rows.at(currentStep).azimuth);
|
||||
emit changeGeoPosition(p, rows.at(currentStep).azimuth, avgAzimuthNext300Meters());
|
||||
}
|
||||
} else {
|
||||
qDebug() << QStringLiteral("trainprogram ends!");
|
||||
|
||||
@@ -89,14 +89,17 @@ class trainprogram : public QObject {
|
||||
void changeSpeed(double speed);
|
||||
bool changeFanSpeed(uint8_t speed);
|
||||
void changeInclination(double grade, double inclination);
|
||||
void changeNextInclination300Meters(QList<MetersByInclination>);
|
||||
void changeResistance(int8_t resistance);
|
||||
void changeRequestedPelotonResistance(int8_t resistance);
|
||||
void changeCadence(int16_t cadence);
|
||||
void changePower(int32_t power);
|
||||
void changeSpeedAndInclination(double speed, double inclination);
|
||||
void changeGeoPosition(QGeoCoordinate p, double azimuth);
|
||||
void changeGeoPosition(QGeoCoordinate p, double azimuth, double avgAzimuthNext300Meters);
|
||||
|
||||
private:
|
||||
double avgAzimuthNext300Meters();
|
||||
QList<MetersByInclination> inclinationNext300Meters();
|
||||
uint32_t calculateTimeForRow(int32_t row);
|
||||
uint32_t calculateTimeForRowMergingRamps(int32_t row);
|
||||
double calculateDistanceForRow(int32_t row);
|
||||
|
||||
@@ -171,7 +171,7 @@ void WebServerInfoSender::processTextMessage(QString message) {
|
||||
if (pClient) {
|
||||
pClient->sendTextMessage(message);
|
||||
}*/
|
||||
qDebug() << QStringLiteral("Message received:") << message;
|
||||
//qDebug() << QStringLiteral("Message received:") << message;
|
||||
emit onDataReceived(message.toUtf8());
|
||||
}
|
||||
|
||||
@@ -260,6 +260,6 @@ void WebServerInfoSender::processBinaryMessage(QByteArray message) {
|
||||
if (pClient) {
|
||||
pClient->sendBinaryMessage(message);
|
||||
}*/
|
||||
qDebug() << QStringLiteral("Binary Message received:") << message.toHex();
|
||||
//qDebug() << QStringLiteral("Binary Message received:") << message.toHex();
|
||||
emit onDataReceived(message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user