BikeTracker
2 years ago in HTML
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>BikeTracker</title>
<style>
body{
background-color: black;
color: white;
display: flex;
justify-content: center;
align-items: center;
box-sizing:border-box;
width: 100vw;
height: 100vh;
margin: 0;
overflow: hidden;
font-family: Trebuchet MS;
}
body p{
margin: 0;
}
#TimePannel{
position: absolute;
top: 2vh;
width: 100%;
display: flex;
justify-content: space-around;
}
#CurrentTime{
display: flex;
align-items: center;
}
.TimeText{
font-size: 8vh;
}
#WindPannel{
position: absolute;
top: 12vh;
width: 100%;
height: 25vh;
display: flex;
justify-content: center;
align-items: center;
}
#Compass{
width: 20vh;
display: flex;
justify-content: center;
align-items: center;
}
.CompassLabel{
font-family: Trebuchet MS;
}
#HeadingText{
font-size: 3.5vh;
margin-bottom: 1vh;
width: 100%;
text-align: center;
}
#WindArrow{
position: absolute;
height: 18vw;
width: 18vw;
transform-origin: 50% 50%;
transition: transform 1s, opacity 1s;
}
#WindText{
font-size: 4vh;
position: absolute;
right: 10vw;
text-align: center;
transition: opacity 1s;
}
#BikePannel{
width: 90%;
padding-bottom: 15vh;
padding-top: 1vh;
overflow: hidden;
position: absolute;
bottom: 23vh;
}
#BikeDiv{
position: relative;
border-bottom: 0.3vh solid white;
transform-origin: 50% 100%;
transition: transform 1s;
display: flex;
justify-content: center;
}
#Bike{
position: relative;
width: 40vw;
}
#BikeFrame{
/*opacity: 0.5;*/
}
.BikeWheel{
position: absolute;
bottom: 0;
width: 35%;
z-index: -10;
}
#SlopeText{
position: absolute;
font-size: 6vh;
bottom: -7vh;
right: 0;
}
#SpeedIndicator{
position: absolute;
bottom: 0;
width: 75vw;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
#SpeedBack{
width: 100%;
}
#SpeedPointer{
position: absolute;
width: 100%;
height: 100%;
transform-origin: 50% 74%;
}
#SpeedTextDiv{
position: absolute;
bottom: 10%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#SpeedText{
font-size: 25vw;
font-weight: bold;
}
#SpeedTextUnit{
font-size: 5vw;
}
</style>
<body>
<div id="TimePannel">
<p id="CurrentTimeText" class="TimeText">00:00</p>
<p id="RideTimeText" class="TimeText">00:00</p>
</div>
<div id="WindPannel">
<div id="HeadingDiv">
<p id="HeadingText">000°</p>
<div id="Compass">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" stroke="white" stroke-width="2" stroke-linecap="round">
<circle cx="50" cy="50" r="46"/>
<line x1="50" y1="0" x2="50" y2="6"/>
<text x="50" y="8" class="CompassLabel" stroke="none" fill="white" dominant-baseline="hanging" text-anchor="middle" font-size="12">N</text>
<line class="South" x1="50" y1="100" x2="50" y2="94"/>
<text x="50" y="91" class="CompassLabel" stroke="none" fill="white" dominant-baseline="auto" text-anchor="middle" font-size="12">S</text>
<line class="East" x1="100" y1="50" x2="94" y2="50"/>
<text x="92" y="51" class="CompassLabel" stroke="none" fill="white" dominant-baseline="middle" text-anchor="end" font-size="12">E</text>
<line class="West" x1="0" y1="50" x2="7" y2="50"/>
<text x="9" y="51" class="CompassLabel" stroke="none" fill="white" dominant-baseline="middle" text-anchor="start" font-size="12">W</text>
</svg>
<div id="WindArrow">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" stroke="rgb(0,255,255)" stroke-width="4" stroke-linecap="round">
<line x1="50" y1="0" x2="50" y2="100"/>
<line x1="50" y1="100" x2="42" y2="85"/>
<line x1="50" y1="100" x2="58" y2="85"/>
<circle cx="50" cy="50" r="5"/>
</svg>
</div>
</div>
</div>
<p id="WindText">000°/00kts</p>
</div>
<div id="BikePannel">
<div id="BikeDiv">
<div id="Bike">
<div class="BikeWheel" style="left: 0;">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" stroke="white" stroke-width="2">
<circle cx="50" cy="50" r="48"/>
<path d=" M 85 50 A 35 35 0 1 1 50 15"/>
<path d=" M 68 20 A 35 35 0 0 1 80 33"/>
</svg>
</div>
<div class="BikeWheel" style="right: 0;">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" stroke="white" stroke-width="2">
<circle cx="50" cy="50" r="48"/>
<path d=" M 85 50 A 35 35 0 1 1 50 15"/>
<path d=" M 68 20 A 35 35 0 0 1 80 33"/>
</svg>
</div>
<div id="BikeFrame">
<svg viewBox="0 0 100 65" xmlns="http://www.w3.org/2000/svg" stroke-width="1.5" stroke-linecap="round" stroke="white">
<line class="Column" x1="82.5" y1="47.5" x2="66" y2="10" />
<line class="Handle" x1="66" y1="10" x2="78" y2="5"/>
<circle class="Bearing" cx="82.5" cy="47.5" r="3" fill="black"/>
<line class="FrameUp" x1="35" y1="25" x2="72" y2="25"/>
<line class="FrameDiagRight" x1="72" y1="25" x2="50" y2="47.5"/>
<line class="FrameSaddleColumn" x1="50" y1="47.5" x2="30" y2="15"/>
<line class="FrameSaddle" x1="25" y1="14.5" x2="37" y2="14.5"/>
<line class="FrameDiagLeft" x1="17.5" y1="47.5" x2="35" y2="25"/>
<line class="FrameBottomLeft" x1="17.5" y1="47.5" x2="50" y2="47.5"/>
<circle class="Bearing" cx="17.5" cy="47.5" r="3" fill="black"/>
</svg>
</div>
</div>
<p id="SlopeText">+00°</p>
</div>
</div>
<div id="SpeedIndicator">
<div id="SpeedBack">
<svg viewBox="0 0 100 65" xmlns="http://www.w3.org/2000/svg">
<path class="OuterRing" d=" M 6 58 A 45 45 0 1 1 94 58" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<div id="SpeedPointer">
<svg viewBox="0 0 100 65" xmlns="http://www.w3.org/2000/svg" stroke-linecap="round">
<path class="Tangencial" d=" M 6 58 A 45 45 0 0 1 5 50" stroke="red" stroke-width="3"/>
<line class="Radial" x1="2" y1="55" x2="11" y2="53.5" stroke="red" stroke-width="1.5"/>
</svg>
</div>
<div id="SpeedTextDiv">
<p id="SpeedText">0</p>
<p id="SpeedTextUnit">km/h</p>
</div>
</div>
<!-- HMI -->
<script type="text/javascript">
var HEADING_LOOP = 3000; // ms
var WHEEL_LOOP = 3000; // ms
var SPEED_LOOP = WHEEL_LOOP // ms
var speedTextValue = 0;
var speedAnimDelay = 0;
var PARAM_COURSE_TIME = 0;
var PARAM_COURSE_STATE = 0;
var PARAM_HEADING = 0;
var PARAM_WIND_DIR = 0;
var PARAM_WIND = 0;
var PARAM_SLOPE = 0;
var PARAM_SPEED = 0; // km/h
var currentTimeText = document.getElementById("CurrentTimeText");
var rideTimeText = document.getElementById("RideTimeText");
var compass = document.getElementById("Compass");
var headingText = document.getElementById("HeadingText");
var windArrow = document.getElementById("WindArrow");
var windText = document.getElementById("WindText");
var bikeDiv = document.getElementById("BikeDiv");
var wheels = document.getElementsByClassName("BikeWheel");
var slopeText = document.getElementById("SlopeText");
var speedPointer = document.getElementById("SpeedPointer");
var speedText = document.getElementById("SpeedText");
compass.style.transition = "transform "+(HEADING_LOOP/1000)+"s linear";
windArrow.style.transform = "rotate(0deg)";
windArrow.style.opacity = "0";
windText.style.opacity = "0";
speedPointer.style.transition = "transform "+(SPEED_LOOP/1000)+"s linear";
for (var i in wheels){
if (wheels[i].style){
wheels[i].style.transform = "rotate(360deg)";
}
}
function UpdateTimer(){
if (PARAM_COURSE_STATE == 1){
PARAM_COURSE_TIME += 1
}
if (PARAM_COURSE_STATE == 0){
if (PARAM_SPEED > 5){
PARAM_COURSE_STATE = 1
}
}
var minutes = Math.floor(PARAM_COURSE_TIME/60);
var seconds = (PARAM_COURSE_TIME-minutes*60)
rideTimeText.innerHTML = ("0"+minutes).slice(-2)+":"+("0"+seconds).slice(-2);
}
function UpdateHour(){
let date = new Date();
let hh = ("0"+date.getHours()).slice(-2);
let mm = ("0"+date.getMinutes()).slice(-2);
let ss = date.getSeconds();
currentTimeText.innerHTML = hh+":"+mm
setTimeout(UpdateHour,(1000*60)-ss*1000)
}
function UpdateHeading(){
compass.style.transform = "rotate("+(-PARAM_HEADING)+"deg)";
headingText.innerHTML = ("00"+PARAM_HEADING).slice(-3)+"°";
}
function UpdateWind(){
if (PARAM_WIND_DIR >= 0){
windArrow.style.transform = "rotate("+PARAM_WIND_DIR+"deg)";
windArrow.style.opacity = "1";
windText.innerHTML = ("00"+PARAM_WIND_DIR).slice(-3)+"°"+"<br>"+("0"+PARAM_WIND).slice(-2)+" kts"
windText.style.opacity = "1";
}
else{
windText.innerHTML = "VRB"+"<br>"+("0"+PARAM_WIND).slice(-2)+" kts"
windText.style.opacity = "1";
}
}
function UpdateSlopeAngle(){
var slopeAngle = -PARAM_SLOPE*2
if (slopeAngle > 20){slopeAngle=20;}
if (slopeAngle < -20){slopeAngle=-20;}
bikeDiv.style.transform = "rotate("+slopeAngle+"deg)";
if (PARAM_SLOPE>=0){
slopeText.innerHTML = ("0"+Math.round(PARAM_SLOPE)).slice(-2)+"°"
}
else{
slopeText.innerHTML = ("-0"+Math.abs(Math.round(PARAM_SLOPE))).slice(-2)+"°"
}
}
function KeepRotationRate(){
for (var i in wheels){
if (wheels[i].style){
wheels[i].style.transition = "";
wheels[i].classList.add('notransition');
wheels[i].style.transform = "rotate(0deg)";
wheels[i].offsetHeight; // Trigger reflow
}
}
for (var i in wheels){
if (wheels[i].style){
wheels[i].style.transition = "transform "+(WHEEL_LOOP/1000)+"s linear";
var wheelFactor = Math.ceil(PARAM_SPEED/8*WHEEL_LOOP/1000)
wheels[i].style.transform = "rotate("+(wheelFactor*360)+"deg)";
}
}
setTimeout(KeepRotationRate,WHEEL_LOOP);
}
function UpdateSpeed(){
var speedAngle = 195*PARAM_SPEED/40
if (speedAngle > 195){speedAngle = 195;}
speedPointer.style.transform = "rotate("+speedAngle+"deg)";
speedAnimDelay = WHEEL_LOOP/Math.abs(PARAM_SPEED-speedTextValue+1)
AnimateSpeedText();
}
function AnimateSpeedText(){
if (speedTextValue < Math.round(PARAM_SPEED)){
speedTextValue += 1;
}
if (speedTextValue > Math.round(PARAM_SPEED)){
speedTextValue -= 1;
}
if (speedTextValue == Math.round(PARAM_SPEED)){
speedText.innerHTML = ("0"+speedTextValue).slice(-2);
return true;
}
speedText.innerHTML = ("0"+speedTextValue).slice(-2);
setTimeout(AnimateSpeedText,speedAnimDelay);
}
function LogMessage(msg){
headingText.innerHTML = msg;
}
// PARAM_HEADING = 270;
// PARAM_WIND_DIR = 270;
// PARAM_WIND = 24;
// PARAM_SLOPE = 15;
// PARAM_SPEED = 120;
// setInterval(UpdateTimer,1000);
// UpdateHour();
// UpdateHeading()
// KeepRotationRate();
// UpdateSlopeAngle();
// UpdateSpeed();
// setInterval(
// function(){
// PARAM_HEADING = Math.round(Math.random()*360);
// UpdateHeading()
// PARAM_SLOPE = -10+Math.random()*20;
// UpdateSlopeAngle()
// PARAM_SPEED = Math.random()*40;
// UpdateSpeed()
// },SPEED_LOOP
// )
document.body.onclick = document.body.requestFullscreen;
</script>
<!-- DATA ACQUISITION -->
<script type="text/javascript">
function getMetar(metarID)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
receiveMetar(xmlHttp.responseText);
}
xmlHttp.open("GET", window.location.href.replace("BikeTracker.html","GetMetar")+"/"+metarID, true); // true for asynchronous
xmlHttp.setRequestHeader("Content-Type", "text/plain");
xmlHttp.send(null);
}
function receiveMetar(metar){
var windStr = metar.split("KT")[0].split(" ").pop()+"KT"
if (windStr.includes("VRB")){
PARAM_WIND_DIR = -1
PARAM_WIND = parseInt(windStr.substring(3,5))
}
else{
PARAM_WIND_DIR = parseInt(windStr.substring(0,3))
PARAM_WIND = parseInt(windStr.substring(3,5))
}
UpdateWind()
}
// getMetar("LFBO")
// navigator.permissions.query({name: "accelerometer"});
// navigator.permissions.query({name: "magnetometer"});
// navigator.permissions.query({name: "gyroscope"});
function handleOrientation(event) {
alert(event)
}
window.addEventListener('deviceorientation', handleOrientation);
LogMessage("Hello world")
</script>
</body>
</html>