mirror of https://github.com/espruino/BangleApps
Waypointer Moto: Add routes, support for Bangle.js 2, better editor UI
"Routes" are where you preconfigure several waypoints in order and it automatically jumps from one to the next as you approach. There is a flaw in that if you accidentally miss one waypoint on your route, there's no way to make it go on to the next one without going all the way back. We probably want some way to manually skip back/forth in the route. Bangle.js 2 support is provided via hardcoded layout coordinates. This should probably use the Layout library instead, but it'll probably do for now. I've also replaced the table-based waypoint editor UI with an entirely map-based replacement which I find much more intuitive.pull/1431/head
parent
2f3a9c8c87
commit
f24ec9404d
|
@ -52,7 +52,8 @@ where "*n*" is the next unused number.
|
||||||
|
|
||||||
Select a waypoint using the menu. Once the waypoint is selected and you're
|
Select a waypoint using the menu. Once the waypoint is selected and you're
|
||||||
back on the main screen, press either the top or bottom button (`BTN1` or
|
back on the main screen, press either the top or bottom button (`BTN1` or
|
||||||
`BTN3`). Confirm that you want to delete the waypoint with the middle
|
`BTN3`), or, on Bangle.js 2, scroll the screen up or down.
|
||||||
|
Confirm that you want to delete the waypoint with the middle
|
||||||
button (`BTN2`).
|
button (`BTN2`).
|
||||||
|
|
||||||
## Waypoint editor
|
## Waypoint editor
|
||||||
|
@ -68,27 +69,43 @@ This will load up the waypoint editor:
|
||||||
|
|
||||||
### Add a waypoint
|
### Add a waypoint
|
||||||
|
|
||||||
Use the map to find your destination. Clicking on the map will
|
Click on the map to add a waypoint. You'll be prompted to give it
|
||||||
populate the latitude/longitude input boxes with the coordinates
|
a name.
|
||||||
of the point you clicked on. Type in a name for the waypoint and
|
|
||||||
click "Add Waypoint". Click "Upload" to send the updated list of
|
|
||||||
waypoints to the watch.
|
|
||||||
|
|
||||||
### Edit a waypoint
|
### Edit a waypoint
|
||||||
|
|
||||||
Click on the pencil icon next to the waypoint you wish to edit.
|
Click on the map marker of the waypoint you wish to edit. You
|
||||||
This will remove the waypoint from the list and populate the
|
can then click on the blue pencil icon to edit the name of the
|
||||||
input boxes.
|
waypoint. If you want to move the waypoint to a new location then
|
||||||
Edit the coordinates by hand, or by clicking on the map. Edit
|
you need to delete it and re-add it.
|
||||||
the name if you want. Click "Add Waypoint" to save the waypoint
|
|
||||||
back to the list. Click "Upload" to send the updated list of
|
|
||||||
waypoints to the watch.
|
|
||||||
|
|
||||||
### Delete a waypoint
|
### Delete a waypoint
|
||||||
|
|
||||||
Click on the pencil icon next to the waypoint you wish to edit.
|
Click on the map marker of the waypoint you wish to delete. You
|
||||||
This will remove the waypoint from the list.
|
can then click on the red bin icon to delete the waypoint.
|
||||||
Click "Upload" to send the updated list of waypoints to the watch.
|
|
||||||
|
### Add a route
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Click on the map to place the first waypoint. The name of the first
|
||||||
|
waypoint will become the name of the route.
|
||||||
|
|
||||||
|
Click on the map marker for the new waypoint and click "Make route".
|
||||||
|
Now every time you click on the map it will add another point
|
||||||
|
on this route. When you're done either right click or click the
|
||||||
|
"Close route" button above the map.
|
||||||
|
|
||||||
|
Points along the route don't have names by default, but if you wish
|
||||||
|
to add one you can click on the waypoint and use the blue pencil icon
|
||||||
|
to give it a name.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Delete a route
|
||||||
|
|
||||||
|
Click on the map marker for any point on the route, and select
|
||||||
|
"Delete entire route".
|
||||||
|
|
||||||
## Mounting the watch on the bike
|
## Mounting the watch on the bike
|
||||||
|
|
||||||
|
@ -123,6 +140,7 @@ Compared to the original Way Pointer app, Waypointer Moto:
|
||||||
* can add new waypoints from inside the app without requiring a blank slot
|
* can add new waypoints from inside the app without requiring a blank slot
|
||||||
* can delete waypoints from inside the app without needing the PC
|
* can delete waypoints from inside the app without needing the PC
|
||||||
* still uses the same `waypoints.json` file
|
* still uses the same `waypoints.json` file
|
||||||
|
* supports "routes" which automatically step from one waypoint to the next
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
|
@ -144,8 +162,6 @@ turns white again.
|
||||||
|
|
||||||
## Possible Future Enhancements
|
## Possible Future Enhancements
|
||||||
|
|
||||||
- "routes" with multiple waypoints; automatically step from one
|
|
||||||
waypoint to the next when you get near to it
|
|
||||||
- some way to manually input coordinates directly on the watch
|
- some way to manually input coordinates directly on the watch
|
||||||
- make the text & arrow more legible in direct sunlight
|
- make the text & arrow more legible in direct sunlight
|
||||||
- integrate a charging connector into the handlebar mount
|
- integrate a charging connector into the handlebar mount
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
var loc = require("locale");
|
var loc = require("locale");
|
||||||
|
|
||||||
var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}];
|
var waypoints = require("Storage").readJSON("waypoints.json") || [];
|
||||||
var wp = waypoints[0];
|
var wp = waypoints[0];
|
||||||
|
if (wp == undefined) wp = {name:"NONE"};
|
||||||
var wp_bearing = 0;
|
var wp_bearing = 0;
|
||||||
|
var routeidx = 0;
|
||||||
var candraw = true;
|
var candraw = true;
|
||||||
|
|
||||||
|
const ROUTE_STEP = 50; // metres
|
||||||
|
const EPSILON = 1; // degrees
|
||||||
|
|
||||||
var direction = 0;
|
var direction = 0;
|
||||||
var dist = 0;
|
var dist = 0;
|
||||||
|
|
||||||
|
@ -15,138 +20,130 @@ var previous = {
|
||||||
wp_name: '',
|
wp_name: '',
|
||||||
course: 180,
|
course: 180,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
routeidx: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*** Drawing ***/
|
/*** Drawing ***/
|
||||||
|
|
||||||
var pal_by = new Uint16Array([0x0000,0xFFC0],0,1); // black, yellow
|
var W = g.getWidth();
|
||||||
var pal_bw = new Uint16Array([0x0000,0xffff],0,1); // black, white
|
var H = g.getHeight();
|
||||||
var pal_bb = new Uint16Array([0x0000,0x07ff],0,1); // black, blue
|
// layout (XXX: this should probably use the Layout library instead)
|
||||||
var pal_br = new Uint16Array([0x0000,0xf800],0,1); // black, red
|
var L = { // banglejs1
|
||||||
var pal_compass = pal_by;
|
arrow: {
|
||||||
|
x: 120,
|
||||||
|
y: 80,
|
||||||
|
r1: 79,
|
||||||
|
r2: 69,
|
||||||
|
bufh: 160,
|
||||||
|
bufy: 40,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
bufh: 40,
|
||||||
|
bufy: 200,
|
||||||
|
largesize: 40,
|
||||||
|
smallsize: 15,
|
||||||
|
waypointy: 20,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (W == 176) {
|
||||||
|
L = { // banglejs2
|
||||||
|
arrow: {
|
||||||
|
x: 88,
|
||||||
|
y: 70,
|
||||||
|
r1: 70,
|
||||||
|
r2: 62,
|
||||||
|
bufh: 160,
|
||||||
|
bufy: 0,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
bufh: 40,
|
||||||
|
bufy: 142,
|
||||||
|
largesize: 40,
|
||||||
|
smallsize: 14,
|
||||||
|
waypointy: 20,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var buf = Graphics.createArrayBuffer(160,160,1, {msb:true});
|
var pal_by = new Uint16Array([0x0000,0xffc0],0,1); // black, yellow
|
||||||
|
var pal_bw = new Uint16Array([0x0000,0xffff],0,1); // black, white
|
||||||
|
var pal_br = new Uint16Array([0x0000,0xf800],0,1); // black, red
|
||||||
|
|
||||||
|
var buf = Graphics.createArrayBuffer(240,160, 1, {msb:true});
|
||||||
var arrow_img = require("heatshrink").decompress(atob("vF4wJC/AEMYBxs8Bxt+Bxv/BpkB/+ABxcD//ABxcH//gBxcP//wBxcf//4Bxc///8Bxd///+OxgABOxgABPBR2BAAJ4KOwIABPBR2BAAJ4KOwIABPBR2BAAJ4KOwIABPBQNCPBR2DPBR2DPBR2DPBR2DPBR2DPBR2DPBR2DPBQNEPBB2FPBB2FPBB2FPBB2FPBB2FPBB2FPBB2FPBANGPAx2HPAx2HPAx2HPAx2HPAx2HPAx2HeJTeJB34O/B34O/B34O/B34O/B34O/B34O/B34O/B34OTAH4AT"));
|
var arrow_img = require("heatshrink").decompress(atob("vF4wJC/AEMYBxs8Bxt+Bxv/BpkB/+ABxcD//ABxcH//gBxcP//wBxcf//4Bxc///8Bxd///+OxgABOxgABPBR2BAAJ4KOwIABPBR2BAAJ4KOwIABPBR2BAAJ4KOwIABPBQNCPBR2DPBR2DPBR2DPBR2DPBR2DPBR2DPBR2DPBQNEPBB2FPBB2FPBB2FPBB2FPBB2FPBB2FPBB2FPBANGPAx2HPAx2HPAx2HPAx2HPAx2HPAx2HeJTeJB34O/B34O/B34O/B34O/B34O/B34O/B34O/B34OTAH4AT"));
|
||||||
|
|
||||||
function flip1(x,y,palette) {
|
function flip(y,h,palette) {
|
||||||
g.drawImage({width:160,height:160,bpp:1,buffer:buf.buffer, palette:palette},x,y);
|
g.drawImage({width:240,height:h,bpp:1,buffer:buf.buffer, palette:palette},0,y);
|
||||||
buf.clear();
|
buf.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function flip2_bw(x,y) {
|
function draw(force) {
|
||||||
g.drawImage({width:160,height:40,bpp:1,buffer:buf.buffer, palette:pal_bw},x,y);
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
function flip2_bb(x,y) {
|
|
||||||
g.drawImage({width:160,height:40,bpp:1,buffer:buf.buffer, palette:pal_bb},x,y);
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawCompass(course) {
|
|
||||||
if (!candraw) return;
|
if (!candraw) return;
|
||||||
|
|
||||||
previous.course = course;
|
var course = direction;
|
||||||
|
|
||||||
buf.setColor(1);
|
|
||||||
buf.fillCircle(80,80, 79);
|
|
||||||
buf.setColor(0);
|
|
||||||
buf.fillCircle(80,80, 69);
|
|
||||||
buf.setColor(1);
|
|
||||||
buf.drawImage(arrow_img, 80, 80, {rotate:radians(course)} );
|
|
||||||
var palette = pal_br;
|
|
||||||
if (savedfix !== undefined && savedfix.fix !== 0) palette = pal_compass;
|
|
||||||
flip1(40, 30, palette);
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawN(force){
|
|
||||||
if (!candraw) return;
|
|
||||||
|
|
||||||
buf.setFont("Vector",24);
|
|
||||||
var dst = loc.distance(dist);
|
var dst = loc.distance(dist);
|
||||||
|
|
||||||
// distance on left
|
if (force || previous.dst !== dst || previous.wp_name !== wp.name || previous.routeidx !== routeidx || Math.abs(course-previous.course)>EPSILON) {
|
||||||
if (force || previous.dst !== dst) {
|
previous.course = course;
|
||||||
|
|
||||||
|
var palette = pal_br;
|
||||||
|
if (savedfix !== undefined && savedfix.fix !== 0)
|
||||||
|
palette = isNaN(savedfix.course) ? pal_by : pal_bw;
|
||||||
|
|
||||||
|
buf.setColor(1);
|
||||||
|
buf.fillCircle(L.arrow.x,L.arrow.y, L.arrow.r1);
|
||||||
|
buf.setColor(0);
|
||||||
|
buf.fillCircle(L.arrow.x,L.arrow.y, L.arrow.r2);
|
||||||
|
buf.setColor(1);
|
||||||
|
buf.drawImage(arrow_img, L.arrow.x, L.arrow.y, {rotate:radians(course)} );
|
||||||
|
flip(L.arrow.bufy,L.arrow.bufh,palette);
|
||||||
|
|
||||||
|
// distance on left
|
||||||
previous.dst = dst;
|
previous.dst = dst;
|
||||||
|
previous.wp_name = wp.name;
|
||||||
|
previous.routeidx = routeidx;
|
||||||
|
|
||||||
buf.setColor(1);
|
buf.setColor(1);
|
||||||
buf.setFontAlign(-1, -1);
|
buf.setFontAlign(-1, -1);
|
||||||
buf.setFont("Vector",40);
|
buf.setFont("Vector",L.text.largesize);
|
||||||
buf.drawString(dst,0,0);
|
buf.drawString(dst,0,0);
|
||||||
flip2_bw(8, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
// waypoint name on right
|
// waypoint name on right
|
||||||
if (force || previous.wp_name !== wp.name) {
|
|
||||||
previous.wp_name = wp.name;
|
|
||||||
buf.setColor(1);
|
buf.setColor(1);
|
||||||
buf.setFontAlign(1, -1);
|
buf.setFontAlign(1, -1);
|
||||||
buf.setFont("Vector", 15);
|
buf.setFont("Vector", L.text.smallsize);
|
||||||
buf.drawString(wp.name, 80, 0);
|
buf.drawString(wp.name, W, L.text.waypointy);
|
||||||
flip2_bw(160, 220);
|
|
||||||
|
// if this is a route, draw the step name above the route name
|
||||||
|
if (wp.route) {
|
||||||
|
buf.drawString((wp.route[routeidx].name||'') + " " + (routeidx+1) + "/" + wp.route.length, W, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
flip(L.text.bufy,L.text.bufh,pal_bw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawAll(force) {
|
|
||||||
if (!candraw) return;
|
|
||||||
|
|
||||||
g.setColor(1,1,1);
|
|
||||||
drawN(force);
|
|
||||||
drawCompass(direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Heading ***/
|
/*** Heading ***/
|
||||||
|
|
||||||
var heading = 0;
|
var heading = 0;
|
||||||
function newHeading(m,h){
|
|
||||||
var s = Math.abs(m - h);
|
|
||||||
var delta = (m>h)?1:-1;
|
|
||||||
if (s>=180){s=360-s; delta = -delta;}
|
|
||||||
if (s<2) return h;
|
|
||||||
var hd = h + delta*(1 + Math.round(s/5));
|
|
||||||
if (hd<0) hd+=360;
|
|
||||||
if (hd>360)hd-= 360;
|
|
||||||
return hd;
|
|
||||||
}
|
|
||||||
|
|
||||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
|
||||||
|
|
||||||
function tiltfixread(O,S){
|
|
||||||
var start = Date.now();
|
|
||||||
var m = Bangle.getCompass();
|
|
||||||
var g = Bangle.getAccel();
|
|
||||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
|
||||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
|
||||||
if (d<0) d+=360;
|
|
||||||
var phi = Math.atan(-g.x/-g.z);
|
|
||||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
|
||||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
|
||||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
|
||||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
|
||||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
|
||||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
|
||||||
if (psi<0) psi+=360;
|
|
||||||
return psi;
|
|
||||||
}
|
|
||||||
|
|
||||||
function read_heading() {
|
function read_heading() {
|
||||||
if (savedfix !== undefined && !isNaN(savedfix.course)) {
|
if (savedfix !== undefined && savedfix.satellites > 0 && !isNaN(savedfix.course)) {
|
||||||
Bangle.setCompassPower(0);
|
Bangle.setCompassPower(0);
|
||||||
heading = savedfix.course;
|
heading = savedfix.course;
|
||||||
pal_compass = pal_bw;
|
|
||||||
} else {
|
} else {
|
||||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
|
||||||
Bangle.setCompassPower(1);
|
Bangle.setCompassPower(1);
|
||||||
heading = newHeading(d,heading);
|
var d = 0;
|
||||||
pal_compass = pal_by;
|
var m = Bangle.getCompass();
|
||||||
|
if (!isNaN(m.heading)) d = -m.heading;
|
||||||
|
heading = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
direction = wp_bearing - heading;
|
direction = wp_bearing - heading;
|
||||||
if (direction < 0) direction += 360;
|
if (direction < 0) direction += 360;
|
||||||
if (direction > 360) direction -= 360;
|
if (direction > 360) direction -= 360;
|
||||||
drawCompass(direction);
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*** Maths ***/
|
/*** Maths ***/
|
||||||
|
|
||||||
function radians(a) {
|
function radians(a) {
|
||||||
|
@ -218,23 +215,39 @@ function onGPS(fix) {
|
||||||
savedfix = fix;
|
savedfix = fix;
|
||||||
|
|
||||||
if (fix !== undefined && fix.fix == 1){
|
if (fix !== undefined && fix.fix == 1){
|
||||||
dist = distance(fix, wp);
|
if (wp.route) {
|
||||||
|
while (true) {
|
||||||
|
dist = distance(fix, wp.route[routeidx]);
|
||||||
|
// step to next point if we're within ROUTE_STEP metres
|
||||||
|
if (!isNaN(dist) && dist < ROUTE_STEP && routeidx < wp.route.length-1)
|
||||||
|
routeidx++;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dist = distance(fix, wp);
|
||||||
|
}
|
||||||
if (isNaN(dist)) dist = 0;
|
if (isNaN(dist)) dist = 0;
|
||||||
wp_bearing = bearing(fix, wp);
|
|
||||||
|
if (wp.route) {
|
||||||
|
wp_bearing = bearing(fix, wp.route[routeidx]);
|
||||||
|
} else {
|
||||||
|
wp_bearing = bearing(fix, wp);
|
||||||
|
}
|
||||||
if (isNaN(wp_bearing)) wp_bearing = 0;
|
if (isNaN(wp_bearing)) wp_bearing = 0;
|
||||||
drawN();
|
draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startTimers() {
|
function startTimers() {
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
Bangle.setLCDPower(1);
|
if (W==240) Bangle.setLCDPower(1); // keep banglejs1 display on
|
||||||
read_heading();
|
read_heading();
|
||||||
}, 500);
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWaypointToMenu(menu, i) {
|
function addWaypointToMenu(menu, i) {
|
||||||
menu[waypoints[i].name] = function() {
|
menu[waypoints[i].name + (waypoints[i].route ? " (R)" : "")] = function() {
|
||||||
wp = waypoints[i];
|
wp = waypoints[i];
|
||||||
mainScreen();
|
mainScreen();
|
||||||
};
|
};
|
||||||
|
@ -243,7 +256,9 @@ function addWaypointToMenu(menu, i) {
|
||||||
function mainScreen() {
|
function mainScreen() {
|
||||||
E.showMenu();
|
E.showMenu();
|
||||||
candraw = true;
|
candraw = true;
|
||||||
drawAll(true);
|
g.setColor(0,0,0);
|
||||||
|
g.fillRect(0,0,W,H);
|
||||||
|
draw(true);
|
||||||
|
|
||||||
Bangle.setUI("updown", function(v) {
|
Bangle.setUI("updown", function(v) {
|
||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
|
@ -262,11 +277,13 @@ function mainScreen() {
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
} else {
|
} else {
|
||||||
candraw = false;
|
candraw = false;
|
||||||
E.showPrompt("Delete waypoint: " + wp.name + "?").then(function(confirmed) {
|
var thing = wp.route ? "route" : "waypoint";
|
||||||
|
E.showPrompt("Delete " + thing + ": " + wp.name + "?").then(function(confirmed) {
|
||||||
var name = wp.name;
|
var name = wp.name;
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
var thing = wp.route ? "Route" : "Waypoint";
|
||||||
deleteWaypoint(wp);
|
deleteWaypoint(wp);
|
||||||
E.showAlert("Waypoint deleted: " + name).then(mainScreen);
|
E.showAlert(thing + " deleted: " + name).then(mainScreen);
|
||||||
} else {
|
} else {
|
||||||
mainScreen();
|
mainScreen();
|
||||||
}
|
}
|
||||||
|
@ -281,7 +298,6 @@ Bangle.on('kill',()=>{
|
||||||
});
|
});
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.setLCDBrightness(1);
|
|
||||||
Bangle.setGPSPower(1);
|
Bangle.setGPSPower(1);
|
||||||
startTimers();
|
startTimers();
|
||||||
Bangle.on('GPS', onGPS);
|
Bangle.on('GPS', onGPS);
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 140 KiB |
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
|
@ -2,11 +2,11 @@
|
||||||
"id": "wpmoto",
|
"id": "wpmoto",
|
||||||
"name": "Waypointer Moto",
|
"name": "Waypointer Moto",
|
||||||
"shortName": "Waypointer Moto",
|
"shortName": "Waypointer Moto",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Waypoint-based motorcycle navigation aid",
|
"description": "Waypoint-based motorcycle navigation aid",
|
||||||
"icon": "wpmoto.png",
|
"icon": "wpmoto.png",
|
||||||
"tags": "tool,outdoors,gps",
|
"tags": "tool,outdoors,gps",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-menu.png"},{"url":"screenshot-delete.png"}],
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-menu.png"},{"url":"screenshot-delete.png"}],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"interface": "wpmoto.html",
|
"interface": "wpmoto.html",
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 125 KiB |
|
@ -1,198 +1,352 @@
|
||||||
<html>
|
<!doctype html>
|
||||||
<head>
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.12.0/css/ol.css" type="text/css">
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="anonymous">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/ol-geocoder@latest/dist/ol-geocoder.min.css" rel="stylesheet">
|
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css">
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h4>List of waypoints</h4>
|
<style type="text/css">
|
||||||
<table class="table">
|
html, body { height: 100% }
|
||||||
<thead>
|
.flex-col { display:flex; flex-direction:column; height:100% }
|
||||||
<tr>
|
#map { width:100%; height:100% }
|
||||||
<th>Name</th>
|
|
||||||
<th>Lat.</th>
|
/* https://stackoverflow.com/a/58686215 */
|
||||||
<th>Long.</th>
|
.arrow-icon {
|
||||||
<th>Actions</th>
|
width: 14px;
|
||||||
</tr>
|
height: 14px;
|
||||||
</thead>
|
}
|
||||||
<tbody id="waypoints">
|
.arrow-icon > div {
|
||||||
|
margin-left: -1px;
|
||||||
</tbody>
|
margin-top: -3px;
|
||||||
</table>
|
transform-origin: center center;
|
||||||
<br>
|
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
<h4>Add a new waypoint</h4>
|
}
|
||||||
<form id="add_waypoint_form">
|
</style>
|
||||||
<div class="columns">
|
</head>
|
||||||
<div class="column col-3 col-xs-8">
|
<body>
|
||||||
<input class="form-input input-sm" type="text" id="add_waypoint_name" placeholder="Name">
|
<div class="flex-col">
|
||||||
|
<div id="statusarea">
|
||||||
|
<button id="download" class="btn btn-error">Reload</button> <button id="upload" class="btn btn-primary">Upload</button>
|
||||||
|
<span id="status"></span>
|
||||||
|
<span id="routestatus"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-3 col-xs-8">
|
<div style="flex: 1">
|
||||||
<input class="form-input input-sm" value="0.0000" type="number" step="any" id="add_latitude" placeholder="Lat">
|
<div id="map"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-3 col-xs-8">
|
</div>
|
||||||
<input class="form-input input-sm" value="0.0000" type="number" step="any" id="add_longitude" placeholder="Long">
|
|
||||||
</div>
|
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin="anonymous"></script>
|
||||||
</div>
|
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
|
||||||
<div class="columns">
|
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
|
||||||
<div class="column col-3 col-xs-8">
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||||
<button id="add_waypoint_button" class="btn btn-primary btn-sm">Add Waypoint</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<br>
|
|
||||||
<button id="Download" class="btn btn-error">Reload</button> <button id="Upload" class="btn btn-primary">Upload</button>
|
|
||||||
<br>
|
|
||||||
<div id="map" class="map" style="width:100%; height:400px"></div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.12.0/build/ol.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/ol-geocoder"></script>
|
|
||||||
<script src="../../core/lib/interface.js"></script>
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var map = new ol.Map({
|
var map;
|
||||||
target: 'map',
|
var waypoints = [];
|
||||||
layers: [
|
var mapmarkers = L.layerGroup();
|
||||||
new ol.layer.Tile({
|
var searchresult = L.layerGroup();
|
||||||
source: new ol.source.OSM()
|
var dynamicarrow = L.layerGroup();
|
||||||
})
|
var editingroute = null;
|
||||||
],
|
var lastroutepoint;
|
||||||
view: new ol.View({
|
|
||||||
center: ol.proj.fromLonLat([37.41, 8.82]),
|
|
||||||
zoom: 4
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
var geocoder = new Geocoder('nominatim', {
|
/*** map ***/
|
||||||
provider: 'osm',
|
|
||||||
lang: 'en-GB',
|
map = L.map('map').setView([51.505, -0.09], 8);
|
||||||
placeholder: 'Search...',
|
|
||||||
targetType: 'text-input',
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
});
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||||
map.addControl(geocoder);
|
subdomains: ['a','b','c'],
|
||||||
geocoder.on('addresschosen', function(e) {
|
}).addTo(map);
|
||||||
map.getView().animate({
|
|
||||||
center: e.coordinate,
|
L.Control.geocoder({defaultMarkGeocode: false}).addTo(map).on('markgeocode', function(e) {
|
||||||
zoom: Math.max(map.getView().getZoom(),16)
|
searchresult.clearLayers();
|
||||||
|
var bbox = e.geocode.bbox;
|
||||||
|
var poly = L.polygon([
|
||||||
|
bbox.getSouthEast(),
|
||||||
|
bbox.getNorthEast(),
|
||||||
|
bbox.getNorthWest(),
|
||||||
|
bbox.getSouthWest()
|
||||||
|
], {fill:false}).addTo(searchresult);
|
||||||
|
map.addLayer(searchresult);
|
||||||
|
map.fitBounds(poly.getBounds());
|
||||||
});
|
});
|
||||||
|
|
||||||
var lonlat = ol.proj.toLonLat(e.coordinate);
|
map.on('click', function(e) {
|
||||||
$longitude.value = lonlat[0];
|
if (editingroute != null) {
|
||||||
$latitude.value = lonlat[1];
|
searchresult.clearLayers();
|
||||||
});
|
addWaypoint(waypoints[editingroute].route, e.latlng.lat, e.latlng.lng, "");
|
||||||
|
} else {
|
||||||
|
swal({
|
||||||
|
icon: 'info',
|
||||||
|
text: "Enter a name for the waypoint:",
|
||||||
|
buttons: true,
|
||||||
|
content: 'input',
|
||||||
|
}).then((name) => {
|
||||||
|
if (name != null && name != "") {
|
||||||
|
searchresult.clearLayers();
|
||||||
|
addWaypoint(waypoints, e.latlng.lat, e.latlng.lng, name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
map.on('mousemove', function(e) {
|
||||||
|
if (editingroute == null) return;
|
||||||
|
let latlngs = [lastroutepoint, [e.latlng.lat, e.latlng.lng]];
|
||||||
|
dynamicarrow.clearLayers();
|
||||||
|
L.polyline(latlngs, { color:'black'}).addTo(dynamicarrow);
|
||||||
|
L.featureGroup(getArrows(latlngs, 'black', 2, map)).addTo(dynamicarrow)
|
||||||
|
map.addLayer(dynamicarrow);
|
||||||
|
});
|
||||||
|
clean();
|
||||||
|
renderAllWaypoints();
|
||||||
|
|
||||||
var waypoints = []
|
/*** status ***/
|
||||||
|
|
||||||
var $name = document.getElementById('add_waypoint_name')
|
function clean() {
|
||||||
var $form = document.getElementById('add_waypoint_form')
|
$('#status').html('<i class="icon icon-check"></i> No pending changes.');
|
||||||
var $button = document.getElementById('add_waypoint_button')
|
routestatus();
|
||||||
var $latitude = document.getElementById('add_latitude')
|
|
||||||
var $longitude = document.getElementById('add_longitude')
|
|
||||||
var $list = document.getElementById('waypoints')
|
|
||||||
|
|
||||||
map.on('click', function(e) {
|
|
||||||
var lonlat = ol.proj.toLonLat(e.coordinate);
|
|
||||||
$longitude.value = lonlat[0];
|
|
||||||
$latitude.value = lonlat[1];
|
|
||||||
});
|
|
||||||
|
|
||||||
function compare(a, b){
|
|
||||||
var x = a.name.toLowerCase();
|
|
||||||
var y = b.name.toLowerCase();
|
|
||||||
if (x=="none") {return -1};
|
|
||||||
if (y=="none") {return 1};
|
|
||||||
if (x < y) {return -1;}
|
|
||||||
if (x > y) {return 1;}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$button.addEventListener('click', event => {
|
function dirty() {
|
||||||
event.preventDefault()
|
$('#status').html('<b><i class="icon icon-edit"></i> Changes have not been sent to the watch.</b>');
|
||||||
var name = $name.value.trim()
|
routestatus();
|
||||||
if(!name) return;
|
}
|
||||||
var lat = parseFloat($latitude.value).toPrecision(8);
|
|
||||||
var lon = parseFloat($longitude.value).toPrecision(8);
|
|
||||||
|
|
||||||
waypoints.push({
|
function routestatus() {
|
||||||
name, lat,lon,
|
if (editingroute == null) {
|
||||||
});
|
$('#routestatus').html('');
|
||||||
|
dynamicarrow.clearLayers();
|
||||||
waypoints.sort(compare);
|
} else {
|
||||||
|
$('#routestatus').html('Editing route: ' + escapeHTML(waypoints[editingroute].name) + ' <button class="btn btn-sm btn-primary" onclick="closeRoute()">close route</button>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderWaypoints()
|
/*** waypoints ***/
|
||||||
$name.value = ''
|
|
||||||
$latitude.value = (0).toPrecision(8);
|
|
||||||
$longitude.value = (0).toPrecision(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
function removeWaypoint(index){
|
function addWaypoint(arr, lat, lon, name) {
|
||||||
$name.value = waypoints[index].name
|
arr.push({lat:lat, lon:lon, name:name});
|
||||||
if (waypoints[index].lat !== undefined && waypoints[index].lon !== undefined
|
renderAllWaypoints();
|
||||||
&& !isNaN(waypoints[index].lat) && !isNaN(waypoints[index].lon)) {
|
dirty();
|
||||||
$latitude.value = waypoints[index].lat
|
}
|
||||||
$longitude.value = waypoints[index].lon
|
|
||||||
map.getView().animate({
|
function deleteWaypoint(arr, i) {
|
||||||
center: ol.proj.fromLonLat([waypoints[index].lon, waypoints[index].lat]),
|
arr.splice(i, 1);
|
||||||
zoom: Math.max(map.getView().getZoom(),16)
|
if (editingroute != null) {
|
||||||
|
// XXX: ugly: fix editingroute index
|
||||||
|
if (editingroute == i) editingroute = null;
|
||||||
|
else if (editingroute > i) editingroute--;
|
||||||
|
}
|
||||||
|
renderAllWaypoints();
|
||||||
|
dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameWaypoint(arr, i) {
|
||||||
|
var name = prompt("Enter new name for the waypoint:", arr[i].name);
|
||||||
|
if (name == null || name == "" || name == arr[i].name)
|
||||||
|
return;
|
||||||
|
arr[i].name = name;
|
||||||
|
renderAllWaypoints();
|
||||||
|
dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWaypoints(wps, isroute, parentidx) {
|
||||||
|
var latlngs = [];
|
||||||
|
for (var i = 0; i < wps.length; i++) {
|
||||||
|
if (wps[i].route) {
|
||||||
|
renderWaypoints(wps[i].route, true, i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (wps[i].lat == null || wps[i].lon == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (isroute) {
|
||||||
|
L.marker([wps[i].lat, wps[i].lon], {title: wps[i].name})
|
||||||
|
.bindPopup(`<h4><b>${wps[i].name}</b> <a class="btn btn-primary" href="javascript:renameWaypoint(waypoints[${parentidx}].route,${i})"><i class="icon icon-edit"></i></a> <a class="btn btn-error" href="javascript:deleteWaypoint(waypoints[${parentidx}].route, ${i})"><i class="icon icon-delete"></i></a></h4><button class="btn btn-sm btn-error" onclick="javascript:deleteEntireRoute(${parentidx})">Delete entire route</button>`)
|
||||||
|
.addTo(mapmarkers);
|
||||||
|
latlngs.push([wps[i].lat, wps[i].lon]);
|
||||||
|
lastroutepoint = [wps[i].lat, wps[i].lon];
|
||||||
|
} else {
|
||||||
|
L.marker([wps[i].lat, wps[i].lon], {title: wps[i].name})
|
||||||
|
.bindPopup(`<h4><b>${wps[i].name}</b> <a class="btn btn-primary" href="javascript:renameWaypoint(waypoints,${i})"><i class="icon icon-edit"></i></a> <a class="btn btn-error" href="javascript:deleteWaypoint(waypoints, ${i})"><i class="icon icon-delete"></i></a></h4><button class="btn btn-sm btn-primary" onclick="javascript:makeRoute(${i})">Make route</button>`)
|
||||||
|
.addTo(mapmarkers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isroute) {
|
||||||
|
L.polyline(latlngs, { color:'black'}).addTo(mapmarkers);
|
||||||
|
L.featureGroup(getArrows(latlngs, 'black', 2, map)).addTo(mapmarkers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAllWaypoints() {
|
||||||
|
mapmarkers.clearLayers();
|
||||||
|
renderWaypoints(waypoints, false, 0);
|
||||||
|
map.addLayer(mapmarkers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** routes ***/
|
||||||
|
|
||||||
|
function openRoute(i) {
|
||||||
|
editingroute = i;
|
||||||
|
console.log("edit route "+ i);
|
||||||
|
map.on('contextmenu', closeRoute);
|
||||||
|
renderAllWaypoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRoute(i) {
|
||||||
|
waypoints[i].route = [{
|
||||||
|
name: waypoints[i].name,
|
||||||
|
lat: waypoints[i].lat,
|
||||||
|
lon: waypoints[i].lon,
|
||||||
|
}];
|
||||||
|
openRoute(i);
|
||||||
|
dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeRoute() {
|
||||||
|
map.off('contextmenu', closeRoute);
|
||||||
|
editingroute = null;
|
||||||
|
renderAllWaypoints();
|
||||||
|
routestatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteEntireRoute(i) {
|
||||||
|
swal({
|
||||||
|
icon: 'warning',
|
||||||
|
text: "Really delete entire route '" + waypoints[i].name + "'?",
|
||||||
|
buttons: true,
|
||||||
|
}).then((v) => {
|
||||||
|
console.log(v)
|
||||||
|
if (v) {
|
||||||
|
deleteWaypoint(waypoints, i);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
waypoints = waypoints.filter((p,i) => i!==index)
|
|
||||||
renderWaypoints()
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderWaypoints(){
|
/*** util ***/
|
||||||
$list.innerHTML = ''
|
|
||||||
waypoints.forEach((waypoint,index) => {
|
|
||||||
var $waypoint = document.createElement('tr')
|
|
||||||
if (index==0){
|
|
||||||
$waypoint.innerHTML = `<td>${waypoint.name}</td>`
|
|
||||||
} else if(waypoint.lat==undefined){
|
|
||||||
$waypoint.innerHTML = `<td>${waypoint.name}</td><td>------</td><td>-----</td><td><button class="btn btn-action btn-primary" onclick="removeWaypoint(${index})"><i class="icon icon-edit"></i></button></td>`
|
|
||||||
} else {
|
|
||||||
$waypoint.innerHTML = `<td>${waypoint.name}</td><td>${waypoint.lat}</td><td>${waypoint.lon}</td><td><button class="btn btn-action btn-primary" onclick="removeWaypoint(${index})"><i class="icon icon-edit"></i></button></td>`
|
|
||||||
}
|
|
||||||
$list.appendChild($waypoint)
|
|
||||||
})
|
|
||||||
$name.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadJSONfile(fileid, callback) {
|
// https://stackoverflow.com/a/22706073
|
||||||
Puck.write(`\x10(function() {
|
function escapeHTML(str){
|
||||||
var pts = require("Storage").readJSON("${fileid}")||[{name:"NONE"}];
|
return new Option(str).innerHTML;
|
||||||
Bluetooth.print(JSON.stringify(pts));
|
}
|
||||||
})()\n`,contents=>{
|
|
||||||
var storedpts = JSON.parse(contents);
|
/*** Bangle.js ***/
|
||||||
callback(storedpts);
|
|
||||||
|
function downloadJSONfile(fileid, callback) {
|
||||||
|
Puck.write(`\x10(function() {
|
||||||
|
var pts = require("Storage").readJSON("${fileid}")||[{name:"NONE"}];
|
||||||
|
Bluetooth.print(JSON.stringify(pts));
|
||||||
|
})()\n`, contents => {
|
||||||
|
var storedpts = JSON.parse(contents);
|
||||||
|
callback(storedpts);
|
||||||
|
clean();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFile(fileid, contents) {
|
||||||
|
Puck.write(`\x10(function() {
|
||||||
|
require("Storage").write("${fileid}",'${contents}');
|
||||||
|
Bluetooth.print("OK");
|
||||||
|
})()\n`, ret => {
|
||||||
|
console.log("uploadFile", ret);
|
||||||
|
if (ret == "OK")
|
||||||
|
clean();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotStored(pts) {
|
||||||
|
waypoints = pts;
|
||||||
|
|
||||||
|
var latlngs = waypoints.map(p => [p.lat, p.lon]);
|
||||||
|
var poly = L.polygon(latlngs);
|
||||||
|
map.fitBounds(poly.getBounds());
|
||||||
|
|
||||||
|
renderAllWaypoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInit() {
|
||||||
|
downloadJSONfile("waypoints.json", gotStored);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#download').on('click', function() {
|
||||||
|
downloadJSONfile("waypoints.json", gotStored);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function uploadFile(fileid, contents) {
|
$('#upload').click(function() {
|
||||||
Puck.write(`\x10(function() {
|
var data = JSON.stringify(waypoints);
|
||||||
require("Storage").write("${fileid}",'${contents}');
|
uploadFile("waypoints.json",data);
|
||||||
Bluetooth.print("OK");
|
|
||||||
})()\n`,ret=>{
|
|
||||||
console.log("uploadFile",ret);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function gotStored(pts){
|
$('#statusarea').click(closeRoute);
|
||||||
waypoints = pts;
|
|
||||||
renderWaypoints();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onInit() {
|
/*** map arrows ***/
|
||||||
downloadJSONfile("waypoints.json", gotStored);
|
// https://stackoverflow.com/a/58686215
|
||||||
}
|
function getArrows(arrLatlngs, color, arrowCount, mapObj) {
|
||||||
|
if (typeof arrLatlngs === undefined || arrLatlngs == null ||
|
||||||
|
(!arrLatlngs.length) || arrLatlngs.length < 2)
|
||||||
|
return [];
|
||||||
|
|
||||||
document.getElementById("Download").addEventListener("click", function() {
|
if (typeof arrowCount === 'undefined' || arrowCount == null)
|
||||||
downloadJSONfile("waypoints.json", gotStored);
|
arrowCount = 1;
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("Upload").addEventListener("click", function() {
|
if (typeof color === 'undefined' || color == null)
|
||||||
var data = JSON.stringify(waypoints);
|
color = '';
|
||||||
uploadFile("waypoints.json",data);
|
else
|
||||||
});
|
color = 'color:' + color;
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
for (var i = 1; i < arrLatlngs.length; i++) {
|
||||||
|
var icon = L.divIcon({ className: 'arrow-icon', bgPos: [5, 5], html: '<div style="' + color + ';transform: rotate(' + getAngle(arrLatlngs[i - 1], arrLatlngs[i], -1).toString() + 'deg)">▶</div>' });
|
||||||
|
for (var c = 1; c <= arrowCount; c++) {
|
||||||
|
result.push(L.marker(myMidPoint(arrLatlngs[i], arrLatlngs[i - 1], (c / (arrowCount + 1)), mapObj), { icon: icon }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAngle(latLng1, latlng2, coef) {
|
||||||
|
var dy = latlng2[0] - latLng1[0];
|
||||||
|
var dx = Math.cos(Math.PI / 180 * latLng1[0]) * (latlng2[1] - latLng1[1]);
|
||||||
|
var ang = ((Math.atan2(dy, dx) / Math.PI) * 180 * coef);
|
||||||
|
return (ang).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function myMidPoint(latlng1, latlng2, per, mapObj) {
|
||||||
|
if (!mapObj)
|
||||||
|
throw new Error('map is not defined');
|
||||||
|
|
||||||
|
var halfDist, segDist, dist, p1, p2, ratio,
|
||||||
|
points = [];
|
||||||
|
|
||||||
|
p1 = mapObj.project(new L.latLng(latlng1));
|
||||||
|
p2 = mapObj.project(new L.latLng(latlng2));
|
||||||
|
|
||||||
|
halfDist = distanceTo(p1, p2) * per;
|
||||||
|
|
||||||
|
if (halfDist === 0)
|
||||||
|
return mapObj.unproject(p1);
|
||||||
|
|
||||||
|
dist = distanceTo(p1, p2);
|
||||||
|
|
||||||
|
if (dist > halfDist) {
|
||||||
|
ratio = (dist - halfDist) / dist;
|
||||||
|
var res = mapObj.unproject(new Point(p2.x - ratio * (p2.x - p1.x), p2.y - ratio * (p2.y - p1.y)));
|
||||||
|
return [res.lat, res.lng];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function distanceTo(p1, p2) {
|
||||||
|
var x = p2.x - p1.x,
|
||||||
|
y = p2.y - p1.y;
|
||||||
|
|
||||||
|
return Math.sqrt(x * x + y * y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Point(x, y, round) {
|
||||||
|
this.x = (round ? Math.round(x) : x);
|
||||||
|
this.y = (round ? Math.round(y) : y);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
|
|
@ -51,8 +51,8 @@
|
||||||
<li class="tab-item" id="tab-myappscontainer">
|
<li class="tab-item" id="tab-myappscontainer">
|
||||||
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="tab-item" id="tab-aboutcontainer">
|
<li class="tab-item" id="tab-morecontainer">
|
||||||
<a href="javascript:showTab('aboutcontainer')">About</a>
|
<a href="javascript:showTab('morecontainer')">More...</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container apploader-tab" id="aboutcontainer" style="display:none">
|
<div class="container apploader-tab" id="morecontainer" style="display:none">
|
||||||
<div class="hero bg-gray">
|
<div class="hero bg-gray">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<a href="https://banglejs.com" target="_blank"><img src="img/banglejs-logo-mid.png" alt="Bangle.js"></a>
|
<a href="https://banglejs.com" target="_blank"><img src="img/banglejs-logo-mid.png" alt="Bangle.js"></a>
|
||||||
|
|
Loading…
Reference in New Issue