2019-11-06 21:51:47 +00:00
|
|
|
<html>
|
|
|
|
<head>
|
2019-11-20 15:54:57 +00:00
|
|
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
2019-11-06 21:51:47 +00:00
|
|
|
<style>li {line-height: 1;}</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<ul>
|
|
|
|
<li>Go to <a href="http://google.com/mymaps">http://google.com/mymaps</a></li>
|
|
|
|
<li>Create a new map</li>
|
|
|
|
<li>Add a line or shape (under search box)</li>
|
|
|
|
<li>Click around, double click to end</li>
|
|
|
|
<li>Click '...' to the right of 'Untitled Map'</li>
|
|
|
|
<li>Export to KML - just 'Untitled Layer' and export as KML</li>
|
|
|
|
</ul>
|
|
|
|
<p><b>Or...</b></p>
|
|
|
|
<ul>
|
|
|
|
<li>Go to <a href="https://umap.openstreetmap.fr/en/">https://umap.openstreetmap.fr/en/</a></li>
|
|
|
|
<li>Create a map</li>
|
|
|
|
<li>Draw a polyline (right hand side)</li>
|
2022-02-11 12:59:18 +00:00
|
|
|
<li>Embed and share (left hand side)</li>
|
2019-11-06 21:51:47 +00:00
|
|
|
<li>Download data as KML</li>
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
<p><b>Upload KML file here:
|
|
|
|
<input class="form-input" type="file" id="fileLoader"/></p>
|
|
|
|
<p><button id="upload" style="display:none" class="btn btn-primary">Upload</button></p>
|
|
|
|
<pre id="log"></pre>
|
2020-08-24 10:59:52 +00:00
|
|
|
<script src="../../core/lib/customize.js"></script>
|
2019-11-06 21:51:47 +00:00
|
|
|
<script>
|
|
|
|
var xmlText = "";
|
|
|
|
var xmlDoc;
|
|
|
|
var js = "";
|
|
|
|
function log(t) {
|
|
|
|
document.getElementById('fileLoader').innerText += t+"\n";
|
|
|
|
console.log(t);
|
|
|
|
}
|
|
|
|
function project(latlong) {
|
|
|
|
var d = Math.PI / 180,
|
|
|
|
max = 85.0511287798,
|
|
|
|
R = 6378137, // earth radius in m
|
|
|
|
lat = Math.max(Math.min(max, latlong.lat), -max),
|
|
|
|
sin = Math.sin(lat * d);
|
|
|
|
return {x:R * latlong.lon * d,
|
|
|
|
y:R * Math.log((1 + sin) / (1 - sin)) / 2};
|
|
|
|
}
|
|
|
|
|
|
|
|
function fileLoaded() {
|
|
|
|
// kml -> Document -> Placemark -> LineString -> Coordinates
|
|
|
|
var coordinateNode = xmlDoc.getElementsByTagName("coordinates");
|
|
|
|
if (!coordinateNode) {
|
|
|
|
log("No 'coordinates' node found");
|
|
|
|
return;
|
|
|
|
}
|
2019-11-08 13:00:11 +00:00
|
|
|
var coordinateLine = coordinateNode[0].textContent.trim();
|
2019-11-06 21:51:47 +00:00
|
|
|
var coordinateList = coordinateLine.split(/\s+/);
|
|
|
|
var coords = [];
|
|
|
|
var pmin, pmax;
|
2019-11-09 18:03:13 +00:00
|
|
|
var latitude;
|
2019-11-06 21:51:47 +00:00
|
|
|
coordinateList.forEach(function(c) {
|
|
|
|
c = c.split(",");
|
2019-11-09 18:03:13 +00:00
|
|
|
var latlon = {
|
2019-11-06 21:51:47 +00:00
|
|
|
lat : parseFloat(c[1]),
|
|
|
|
lon : parseFloat(c[0])
|
2019-11-09 18:03:13 +00:00
|
|
|
};
|
|
|
|
if (latitude===undefined)
|
|
|
|
latitude = latlon.lat;
|
|
|
|
var p = project(latlon);
|
2019-11-06 21:51:47 +00:00
|
|
|
p.x = Math.round(p.x);
|
|
|
|
p.y = Math.round(p.y);
|
|
|
|
if (!pmin) pmin = {x:p.x, y:p.y};
|
|
|
|
if (!pmax) pmax = {x:p.x, y:p.y};
|
|
|
|
if (p.x<pmin.x) pmin.x=p.x;
|
|
|
|
if (p.x>pmax.x) pmax.x=p.x;
|
|
|
|
if (p.y<pmin.y) pmin.y=p.y;
|
|
|
|
if (p.y>pmax.y) pmax.y=p.y;
|
|
|
|
coords.push(p.x,p.y);
|
|
|
|
});
|
2019-11-09 18:03:13 +00:00
|
|
|
/* coordScale is a scale factor because the map projection
|
|
|
|
stretches anything not on the equator */
|
2019-11-06 21:51:47 +00:00
|
|
|
js = `
|
2019-11-09 18:03:13 +00:00
|
|
|
var coordScale = ${Math.cos(latitude*Math.PI/180)};
|
2019-11-06 21:51:47 +00:00
|
|
|
var coords = new Int32Array([${coords.join(",")}]);
|
|
|
|
var min = ${JSON.stringify(pmin)};
|
|
|
|
var max = ${JSON.stringify(pmax)};
|
|
|
|
`.trim();
|
|
|
|
console.log(js);
|
|
|
|
document.getElementById("upload").style = "";
|
|
|
|
}
|
|
|
|
function handleFileSelect(event) {
|
|
|
|
if (event.target.files.length!=1) {
|
|
|
|
log("More than one file selected!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var reader = new FileReader();
|
|
|
|
reader.onload = function(event) {
|
|
|
|
xmlText = event.target.result;
|
|
|
|
var parser = new DOMParser();
|
|
|
|
xmlDoc = parser.parseFromString(xmlText, "application/xml");
|
|
|
|
fileLoaded();
|
|
|
|
};
|
|
|
|
reader.readAsText(event.target.files[0]);
|
|
|
|
};
|
|
|
|
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
|
|
|
|
|
|
|
document.getElementById("upload").addEventListener("click", function() {
|
|
|
|
var app = `${js}
|
2022-02-11 11:58:34 +00:00
|
|
|
var gcoords = new Uint8Array(coords.length);
|
|
|
|
var coordDistance = new Uint16Array(coords.length/2);
|
|
|
|
var colHL = g.theme.dark ? "#FF0" : "#00F";
|
|
|
|
var W = g.getWidth(), H = g.getHeight();
|
2019-11-06 21:51:47 +00:00
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
var PT_DISTANCE = 30; // distance to a point before we consider it complete
|
2019-11-06 21:51:47 +00:00
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
function toScr(p) {
|
|
|
|
return {
|
|
|
|
x : 10 + (p.x-min.x)*100/(max.x-min.x),
|
|
|
|
y : H - 10 - (p.y-min.y)*100/(max.y-min.y)
|
|
|
|
};
|
2019-11-06 21:51:47 +00:00
|
|
|
}
|
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
var last;
|
|
|
|
var totalDistance = 0;
|
|
|
|
for (var i=0;i<coords.length;i+=2) {
|
|
|
|
var c = {x:coords[i],y:coords[i+1]};
|
|
|
|
var s = toScr(c);
|
|
|
|
gcoords[i ] = s.x;
|
|
|
|
gcoords[i+1] = s.y;
|
|
|
|
if (last) {
|
|
|
|
var dx = c.x-last.x;
|
|
|
|
var dy = c.y-last.y;
|
|
|
|
totalDistance += Math.sqrt(dx*dx+dy*dy)*coordScale;
|
|
|
|
coordDistance[i/2] = totalDistance;
|
|
|
|
}
|
|
|
|
last = c;
|
2019-11-06 21:51:47 +00:00
|
|
|
}
|
2022-02-11 11:58:34 +00:00
|
|
|
var fix, lastFix;
|
|
|
|
var nextPtIdx = 0; // 2x the number of points
|
|
|
|
var nextPt = {x:coords[nextPtIdx], y:coords[nextPtIdx+1]};
|
|
|
|
var nextAngle = 0;
|
|
|
|
var nextDist = 0;
|
|
|
|
var currentDist = 0;
|
2019-11-06 21:51:47 +00:00
|
|
|
|
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
|
|
|
|
function drawMap() {
|
|
|
|
var R = W*180/240;
|
|
|
|
var L = W*50/240;
|
|
|
|
|
|
|
|
g.clearRect(0,0,W,H/2);
|
|
|
|
g.setFontAlign(0,0);
|
|
|
|
g.setColor(colHL);
|
|
|
|
g.setFontVector(40);
|
|
|
|
g.drawString((currentDist===undefined)?"?":(Math.round(currentDist)+"m"), R, 30);
|
|
|
|
g.setColor(g.theme.fg);
|
|
|
|
g.setFont("6x8",2);
|
|
|
|
g.drawString(Math.round(totalDistance)+"m", R, 70);
|
|
|
|
g.drawString((nextPtIdx/2)+"/"+coordDistance.length, L, 20);
|
|
|
|
if (!fix.fix) {
|
|
|
|
g.setColor(colHL);
|
|
|
|
g.drawString("No GPS", L, 50);
|
|
|
|
g.setFont("6x8",1);
|
|
|
|
g.drawString(fix.satellites+" Sats", L, 70);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastFix && lastFix.fix) {
|
|
|
|
g.setColor(g.theme.bg);
|
|
|
|
g.drawCircle(lastFix.s.x,lastFix.s.y,10);
|
|
|
|
}
|
|
|
|
var c1 = g.toColor(colHL);
|
|
|
|
var c2 = g.toColor("#888");
|
|
|
|
for (var i=0;i<gcoords.length;i+=2)
|
|
|
|
g.setColor((i<=nextPtIdx) ? c1 : c2).fillRect(gcoords[i]-2,gcoords[i+1]-2,gcoords[i]+2,gcoords[i+1]+2);
|
|
|
|
g.setColor(colHL); // first part of path
|
|
|
|
g.drawPoly(new Uint8Array(gcoords.buffer, 0, nextPtIdx+2));
|
|
|
|
g.setColor(g.theme.fg); // remaining part of path
|
|
|
|
g.drawPoly(new Uint8Array(gcoords.buffer, nextPtIdx));
|
|
|
|
|
|
|
|
if (fix && fix.fix) {
|
|
|
|
g.setColor(colHL);
|
|
|
|
g.drawCircle(fix.s.x,fix.s.y,10);
|
|
|
|
}
|
|
|
|
lastFix = fix;
|
2019-11-09 18:03:13 +00:00
|
|
|
}
|
2022-02-11 11:58:34 +00:00
|
|
|
|
|
|
|
function getNextPtInfo() {
|
|
|
|
var dx = nextPt.x - fix.p.x;
|
|
|
|
var dy = nextPt.y - fix.p.y;
|
|
|
|
nextAngle = Math.atan2(dx,dy)*180/Math.PI;
|
|
|
|
nextDist = Math.sqrt(dx*dx+dy*dy)*coordScale;
|
2019-11-06 21:51:47 +00:00
|
|
|
}
|
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
function onGPS(f) {
|
|
|
|
fix = f;
|
|
|
|
fix.p = Bangle.project(fix);
|
|
|
|
fix.s = toScr(fix.p);
|
|
|
|
getNextPtInfo();
|
|
|
|
if ((nextDist < PT_DISTANCE) &&
|
|
|
|
(nextPtIdx < coords.length)) {
|
|
|
|
nextPtIdx+=2;
|
|
|
|
nextPt = {x:coords[nextPtIdx], y:coords[nextPtIdx+1]};
|
|
|
|
getNextPtInfo();
|
|
|
|
}
|
|
|
|
// work out how far we are (based on distance to next point)
|
|
|
|
if (!fix.fix) {
|
|
|
|
currentDist = undefined
|
|
|
|
} else if (nextPtIdx+2 < coordDistance.length) {
|
|
|
|
currentDist = coordDistance[1+(nextPtIdx/2)] - nextDist;
|
|
|
|
} else if (nextPtIdx+2 == coordDistance.length) {
|
|
|
|
currentDist = totalDistance - nextDist;
|
|
|
|
} else {
|
|
|
|
currentDist = totalDistance;
|
|
|
|
}
|
2019-11-09 18:03:13 +00:00
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
if (!Bangle.isLCDOn()) return;
|
|
|
|
drawMap();
|
|
|
|
}
|
2019-11-09 18:03:13 +00:00
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
function arrow(r,c) {
|
|
|
|
r=r*Math.PI/180;
|
|
|
|
var p = Math.PI*3/4;
|
|
|
|
g.setColor(c);
|
|
|
|
g.fillPoly([
|
|
|
|
180+40*Math.sin(r), 180-40*Math.cos(r),
|
|
|
|
180+20*Math.sin(r+p), 180-20*Math.cos(r+p),
|
|
|
|
180-10*Math.sin(r), 180+10*Math.cos(r),
|
|
|
|
180+20*Math.sin(r+-p), 180-20*Math.cos(r-p),
|
|
|
|
]);
|
|
|
|
}
|
2019-11-09 18:03:13 +00:00
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
function onCompass(m) {
|
|
|
|
if (!Bangle.isLCDOn()) return;
|
|
|
|
|
|
|
|
arrow(oldHeading,g.theme.bg);
|
2022-11-01 10:16:28 +00:00
|
|
|
var heading = (360-m.heading) + nextAngle;
|
2022-02-11 11:58:34 +00:00
|
|
|
arrow(heading,"#f00");
|
|
|
|
oldHeading = heading;
|
|
|
|
}
|
2019-11-09 18:03:13 +00:00
|
|
|
|
2019-11-06 21:51:47 +00:00
|
|
|
|
2022-02-11 11:58:34 +00:00
|
|
|
// draw the heading
|
|
|
|
var oldHeading = 0;
|
|
|
|
Bangle.on('GPS', onGPS);
|
|
|
|
Bangle.on('mag', onCompass);
|
|
|
|
Bangle.setGPSPower(1);
|
|
|
|
Bangle.setCompassPower(1);
|
|
|
|
g.clear();
|
2019-11-06 21:51:47 +00:00
|
|
|
`;
|
|
|
|
|
2020-02-10 13:49:36 +00:00
|
|
|
sendCustomizedApp({
|
2019-11-06 21:51:47 +00:00
|
|
|
storage:[
|
2020-09-01 13:43:54 +00:00
|
|
|
{name:"route.app.js", url:"app.js", content:app}
|
2019-11-06 21:51:47 +00:00
|
|
|
]
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
</script>
|
|
|
|
</body>
|