BangleApps/apps/banglerun/interface.html

218 lines
8.5 KiB
HTML

<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div id="tracks"></div>
<script src="../../core/lib/interface.js"></script>
<script>
/* TODO: Calculate cadence from step count */
var domTracks = document.getElementById("tracks");
function saveKML(track,title) {
var kml = `<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
<Document>
<Schema id="schema">
<gx:SimpleArrayField name="heartrate" type="int">
<displayName>Heart Rate</displayName>
</gx:SimpleArrayField>
<gx:SimpleArrayField name="steps" type="int">
<displayName>Step Count</displayName>
</gx:SimpleArrayField>
<gx:SimpleArrayField name="distance" type="float">
<displayName>Distance</displayName>
</gx:SimpleArrayField>
<gx:SimpleArrayField name="cadence" type="int">
<displayName>Cadence</displayName>
</gx:SimpleArrayField>
</Schema>
<Folder>
<name>Tracks</name>
<Placemark>
<name>${title}</name>
<gx:Track>
${track.map(pt=>` <when>${pt.date.toISOString()}</when>\n`).join("")}
${track.map(pt=>` <gx:coord>${pt.lon} ${pt.lat} ${pt.alt}</gx:coord>\n`).join("")}
<ExtendedData>
<SchemaData schemaUrl="#schema">
<gx:SimpleArrayData name="heartrate">
${track.map(pt=>` <gx:value>${pt.heartrate}</gx:value>\n`).join("")}
</gx:SimpleArrayData>
<gx:SimpleArrayData name="steps">
${track.map(pt=>` <gx:value>${pt.steps}</gx:value>\n`).join("")}
</gx:SimpleArrayData>
<gx:SimpleArrayData name="distance">
${track.map(pt=>` <gx:value>${pt.distance}</gx:value>\n`).join("")}
</gx:SimpleArrayData>
</SchemaData>
</ExtendedData>
</gx:Track>
</Placemark>
</Folder>
</Document>
</kml>`;
var a = document.createElement("a"),
file = new Blob([kml], {type: "application/vnd.google-earth.kml+xml"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = title+".kml";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
function saveGPX(track, title) {
var gpx = `<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
<metadata>
<time>${track[0].date.toISOString()}</time>
</metadata>
<trk>
<name>${title}</name>
<trkseg>`;
track.forEach(pt=>{
gpx += `
<trkpt lat="${pt.lat}" lon="${pt.lon}">
<ele>${pt.alt}</ele>
<time>${pt.date.toISOString()}</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:hr>${pt.heartrate}</gpxtpx:hr>
<gpxtpx:distance>${pt.distance}</gpxtpx:distance>
${/* <gpxtpx:cad>65</gpxtpx:cad> */""}
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>`;
});
gpx += `
</trkseg>
</trk>
</gpx>`;
var a = document.createElement("a"),
file = new Blob([gpx], {type: "application/gpx+xml"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = title+".gpx";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
function trackLineToObject(l, hasFileName) {
// "timestamp,latitude,longitude,altitude,duration,distance,heartrate,steps\n"
var t = l.trim().split(",");
var n = hasFileName ? 1 : 0;
var o = {
invalid : t.length < 8,
date : new Date(parseInt(t[n+0])),
lat : parseFloat(t[n+1]),
lon : parseFloat(t[n+2]),
alt : parseFloat(t[n+3]),
duration : parseFloat(t[n+4]),
distance : parseFloat(t[n+5]),
heartrate : parseInt(t[n+6]),
steps : parseInt(t[n+7]),
};
if (hasFileName)
o.filename = t[0];
return o;
}
function downloadTrack(trackid, callback) {
Util.showModal("Downloading Track...");
Util.readStorageFile(trackid, data=>{
Util.hideModal();
var trackLines = data.trim().split("\n");
trackLines.shift(); // remove first line, which is column header
// should be:
// "timestamp,latitude,longitude,altitude,duration,distance,heartrate,steps\n"
var track = trackLines.map(l=>trackLineToObject(l,false));
callback(track);
});
}
function getTrackList() {
Util.showModal("Loading Tracks...");
domTracks.innerHTML = "";
Puck.eval(`require("Storage").list(/banglerun_.*\\x01/).map(fn=>{fn=fn.slice(0,-1);var f=require("Storage").open(fn,"r");f.readLine();return fn+","+f.readLine()})`,trackLines=>{
var html = `<div class="container">
<div class="columns">\n`;
trackLines.forEach(l => {
var track = trackLineToObject(l, true /*has filename*/);
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">Track ${track.filename}</div>
<div class="card-subtitle text-gray">${track.invalid ? "No Data":track.date.toString().substr(0,24)}</div>
</div>
${track.invalid?``:`<div class="card-image">
<iframe
width="100%"
height="250"
frameborder="0" style="border:0"
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyBxTcwrrVOh2piz7EmIs1Xn4FsRxJWeVH4&q=${track.lat},${track.lon}&zoom=10" allowfullscreen>
</iframe>
</div>
<div class="card-body"></div>`}
<div class="card-footer">${track.invalid?``:`
<button class="btn btn-primary" trackid="${track.filename}" task="downloadkml">Download KML</button>
<button class="btn btn-primary" trackid="${track.filename}" task="downloadgpx">Download GPX</button>`}
<button class="btn btn-default" trackid="${track.filename}" task="delete">Delete</button>
</div>
</div>
`;
});
if (trackLines.length==0) {
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">No tracks</div>
<div class="card-subtitle text-gray">No GPS tracks found</div>
</div>
</div>
`;
}
html += `
</div>
</div>`;
domTracks.innerHTML = html;
Util.hideModal();
var buttons = domTracks.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var trackid = button.getAttribute("trackid");
var task = button.getAttribute("task");
if (task=="delete") {
Util.showModal("Deleting Track...");
Util.eraseStorageFile(trackid,()=>{
Util.hideModal();
getTrackList();
});
}
if (task=="downloadkml") {
downloadTrack(trackid, track => saveKML(track, `Bangle.js Track ${trackid}`));
}
if (task=="downloadgpx") {
downloadTrack(trackid, track => saveGPX(track, `Bangle.js Track ${trackid}`));
}
});
}
})
}
function onInit() {
getTrackList();
}
</script>
</body>
</html>