2022-07-16 14:23:01 +00:00
2022-07-18 15:11:41 +00:00
let simulated = false ;
2022-07-18 15:01:04 +00:00
class Status {
constructor ( path ) {
this . path = path ;
2022-07-19 08:05:34 +00:00
this . on _path = false ; // are we on the path or lost ?
2022-07-18 15:01:04 +00:00
this . position = null ; // where we are
this . cos _direction = null ; // cos of where we look at
this . sin _direction = null ; // sin of where we look at
this . current _segment = null ; // which segment is closest
2022-07-19 08:05:34 +00:00
this . highest _completed _segment = - 1 ; // remember what we already acomplished to disambiguate nearest path when some segments are takend in both directions
this . reaching = null ; // which waypoint are we reaching ?
this . distance _to _next _point = null ; // how far are we from next point ?
2022-07-18 15:01:04 +00:00
let r = [ 0 ] ;
// let's do a reversed prefix computations on all distances:
// loop on all segments in reversed order
let previous _point = null ;
for ( let i = this . path . len - 1 ; i >= 0 ; i -- ) {
let point = this . path . point ( i ) ;
if ( previous _point !== null ) {
r . unshift ( r [ 0 ] + point . distance ( previous _point ) ) ;
}
previous _point = point ;
}
this . remaining _distances = r ; // how much distance remains at start of each segment
}
update _position ( new _position , direction ) {
2022-07-19 08:05:34 +00:00
2022-07-18 15:01:04 +00:00
if ( Bangle . isLocked ( ) && this . position !== null && new _position . lon == this . position . lon && new _position . lat == this . position . lat ) {
return ;
}
2022-07-19 08:05:34 +00:00
2022-07-18 15:01:04 +00:00
this . cos _direction = Math . cos ( - direction - Math . PI / 2.0 ) ;
this . sin _direction = Math . sin ( - direction - Math . PI / 2.0 ) ;
this . position = new _position ;
2022-07-19 08:05:34 +00:00
// detect segment we are on now
let next _segment = this . path . nearest _segment ( this . position , Math . max ( 0 , this . current _segment - 1 ) , Math . min ( this . current _segment + 2 , path . len - 1 ) ) ;
2022-07-20 08:52:00 +00:00
2022-07-19 08:05:34 +00:00
if ( this . is _lost ( next _segment ) ) {
// it did not work, try anywhere
next _segment = this . path . nearest _segment ( this . position , 0 , path . len - 1 ) ;
}
// now check if we strayed away from path or back to it
let lost = this . is _lost ( next _segment ) ;
if ( this . on _path == lost ) {
if ( lost ) {
Bangle . buzz ( ) ; // we lost path
setTimeout ( ( ) => Bangle . buzz ( ) , 300 ) ;
} else {
Bangle . buzz ( ) ; // we found path back
}
this . on _path = ! lost ;
}
if ( this . current _segment != next _segment ) {
if ( this . current _segment == next _segment - 1 ) {
this . highest _completed _segment = this . current _segment ;
}
}
this . current _segment = next _segment ;
// check if we are nearing the next point on our path and alert the user
let next _point = this . current _segment + 1 ;
this . distance _to _next _point = Math . ceil ( this . position . distance ( this . path . point ( next _point ) ) ) ;
if ( this . reaching != next _point && this . distance _to _next _point <= 20 ) {
this . reaching = next _point ;
2022-07-20 08:52:00 +00:00
if ( Bangle . isLocked ( ) ) {
if ( this . path . is _waypoint ( next _point ) ) {
Bangle . buzz ( ) ;
Bangle . setLocked ( false ) ;
}
}
2022-07-19 08:05:34 +00:00
}
// re-display unless locked
if ( ! Bangle . isLocked ( ) ) {
this . display ( ) ;
2022-07-18 15:01:04 +00:00
}
}
remaining _distance ( ) {
return this . remaining _distances [ this . current _segment + 1 ] + this . position . distance ( this . path . point ( this . current _segment + 1 ) ) ;
}
2022-07-19 08:05:34 +00:00
is _lost ( segment ) {
let distance _to _nearest = this . position . fake _distance _to _segment ( this . path . point ( segment ) , this . path . point ( segment + 1 ) ) ;
let meters = 6371e3 * distance _to _nearest ;
return ( meters > 20 ) ;
2022-07-18 15:01:04 +00:00
}
display ( ) {
g . clear ( ) ;
this . display _map ( ) ;
this . display _stats ( ) ;
Bangle . drawWidgets ( ) ;
}
display _stats ( ) {
let rounded _distance = Math . round ( this . remaining _distance ( ) / 100 ) / 10 ;
let total = Math . round ( this . remaining _distances [ 0 ] / 100 ) / 10 ;
let now = new Date ( ) ;
let minutes = now . getMinutes ( ) . toString ( ) ;
if ( minutes . length < 2 ) {
minutes = '0' + minutes ;
}
let hours = now . getHours ( ) . toString ( ) ;
g . setFont ( "6x8:2" ) . drawString ( hours + ":" + minutes , 0 , g . getHeight ( ) - 49 ) ;
g . drawString ( "d. " + rounded _distance + "/" + total , 0 , g . getHeight ( ) - 32 ) ;
2022-07-19 08:05:34 +00:00
g . drawString ( "seg." + ( this . current _segment + 1 ) + "/" + path . len + " " + this . distance _to _next _point + "m" , 0 , g . getHeight ( ) - 15 ) ;
2022-07-18 15:01:04 +00:00
}
display _map ( ) {
// don't display all segments, only those neighbouring current segment
// this is most likely to be the correct display
// while lowering the cost a lot
let start = Math . max ( this . current _segment - 5 , 0 ) ;
let end = Math . min ( this . current _segment + 6 , this . path . len - 1 ) ;
let pos = this . position ;
let cos = this . cos _direction ;
let sin = this . sin _direction ;
// segments
this . path . on _segments ( function ( p1 , p2 , i ) {
if ( i == this . current _segment + 1 ) {
g . setColor ( 0.0 , 1.0 , 0.0 ) ;
} else {
g . setColor ( 1.0 , 0.0 , 0.0 ) ;
}
let c1 = p1 . coordinates ( pos , cos , sin ) ;
let c2 = p2 . coordinates ( pos , cos , sin ) ;
g . drawLine ( c1 [ 0 ] , c1 [ 1 ] , c2 [ 0 ] , c2 [ 1 ] ) ;
} , start , end ) ;
// waypoints
for ( let i = start ; i < end + 1 ; i ++ ) {
let p = this . path . point ( i ) ;
let c = p . coordinates ( pos , cos , sin ) ;
2022-07-20 08:52:00 +00:00
if ( this . path . is _waypoint ( i ) ) {
g . setColor ( g . theme . fg ) ;
g . fillCircle ( c [ 0 ] , c [ 1 ] , 6 ) ;
g . setColor ( g . theme . bg ) ;
g . fillCircle ( c [ 0 ] , c [ 1 ] , 5 ) ;
}
2022-07-18 15:01:04 +00:00
g . setColor ( g . theme . fg ) ;
g . fillCircle ( c [ 0 ] , c [ 1 ] , 4 ) ;
g . setColor ( g . theme . bg ) ;
g . fillCircle ( c [ 0 ] , c [ 1 ] , 3 ) ;
}
// now display ourselves
g . setColor ( g . theme . fgH ) ;
g . fillCircle ( g . getWidth ( ) / 2 , g . getHeight ( ) / 2 , 5 ) ;
}
}
2022-07-11 14:51:03 +00:00
class Path {
2022-07-16 14:23:01 +00:00
constructor ( filename ) {
let buffer = require ( "Storage" ) . readArrayBuffer ( filename ) ;
this . points = Float64Array ( buffer ) ;
}
2022-07-20 08:52:00 +00:00
// if start, end or steep direction change
// we are buzzing and displayed specially
is _waypoint ( point _index ) {
if ( ( point _index == 0 ) || ( point _index == this . len - 1 ) ) {
return true ;
} else {
let p1 = this . point ( point _index - 1 ) ;
let p2 = this . point ( point _index ) ;
let p3 = this . point ( point _index + 1 ) ;
let d1 = p2 . minus ( p1 ) ;
let d2 = p3 . minus ( p2 ) ;
let a1 = Math . atan2 ( d1 . lat , d1 . lon ) ;
let a2 = Math . atan2 ( d2 . lat , d2 . lon ) ;
let direction _change = Math . abs ( a2 - a1 ) ;
return ( direction _change > Math . PI / 3.0 ) ;
}
}
2022-07-18 15:01:04 +00:00
// execute op on all segments.
2022-07-16 14:23:01 +00:00
// start is index of first wanted segment
// end is 1 after index of last wanted segment
on _segments ( op , start , end ) {
let previous _point = null ;
for ( let i = start ; i < end + 1 ; i ++ ) {
let point = new Point ( this . points [ 2 * i ] , this . points [ 2 * i + 1 ] ) ;
if ( previous _point !== null ) {
op ( previous _point , point , i ) ;
}
previous _point = point ;
2022-07-12 14:45:37 +00:00
}
2022-07-16 14:23:01 +00:00
}
2022-07-18 15:01:04 +00:00
// return point at given index
2022-07-16 14:23:01 +00:00
point ( index ) {
let lon = this . points [ 2 * index ] ;
let lat = this . points [ 2 * index + 1 ] ;
return new Point ( lon , lat ) ;
}
// return index of segment which is nearest from point
nearest _segment ( point , start , end ) {
let min _index = 0 ;
let min _distance = Number . MAX _VALUE ;
this . on _segments ( function ( p1 , p2 , i ) {
let distance = point . fake _distance _to _segment ( p1 , p2 ) ;
if ( distance <= min _distance ) {
min _distance = distance ;
min _index = i - 1 ;
}
} , start , end ) ;
return min _index ;
}
get len ( ) {
return this . points . length / 2 ;
}
2022-07-11 14:51:03 +00:00
}
class Point {
2022-07-16 14:23:01 +00:00
constructor ( lon , lat ) {
this . lon = lon ;
this . lat = lat ;
}
2022-07-18 15:01:04 +00:00
coordinates ( current _position , cos _direction , sin _direction ) {
let translated = this . minus ( current _position ) . times ( 20000.0 ) ;
let rotated _x = translated . lon * cos _direction - translated . lat * sin _direction ;
let rotated _y = translated . lon * sin _direction + translated . lat * cos _direction ;
return [
g . getWidth ( ) / 2 - Math . round ( rotated _x ) , // x is inverted
2022-07-16 14:23:01 +00:00
g . getHeight ( ) / 2 + Math . round ( rotated _y )
] ;
}
minus ( other _point ) {
let xdiff = this . lon - other _point . lon ;
let ydiff = this . lat - other _point . lat ;
return new Point ( xdiff , ydiff ) ;
}
plus ( other _point ) {
return new Point ( this . lon + other _point . lon , this . lat + other _point . lat ) ;
}
length _squared ( other _point ) {
let d = this . minus ( other _point ) ;
return ( d . lon * d . lon + d . lat * d . lat ) ;
}
times ( scalar ) {
return new Point ( this . lon * scalar , this . lat * scalar ) ;
}
dot ( other _point ) {
return this . lon * other _point . lon + this . lat * other _point . lat ;
}
distance ( other _point ) {
//see https://www.movable-type.co.uk/scripts/latlong.html
const R = 6371e3 ; // metres
const phi1 = this . lat * Math . PI / 180 ;
const phi2 = other _point . lat * Math . PI / 180 ;
const deltaphi = ( other _point . lat - this . lat ) * Math . PI / 180 ;
const deltalambda = ( other _point . lon - this . lon ) * Math . PI / 180 ;
const a = Math . sin ( deltaphi / 2 ) * Math . sin ( deltaphi / 2 ) +
2022-07-13 09:53:53 +00:00
Math . cos ( phi1 ) * Math . cos ( phi2 ) *
2022-07-16 14:23:01 +00:00
Math . sin ( deltalambda / 2 ) * Math . sin ( deltalambda / 2 ) ;
const c = 2 * Math . atan2 ( Math . sqrt ( a ) , Math . sqrt ( 1 - a ) ) ;
return R * c ; // in metres
}
fake _distance ( other _point ) {
return Math . sqrt ( this . length _squared ( other _point ) ) ;
}
fake _distance _to _segment ( v , w ) {
// from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
// Return minimum distance between line segment vw and point p
let l2 = v . length _squared ( w ) ; // i.e. |w-v|^2 - avoid a sqrt
if ( l2 == 0.0 ) {
return this . distance ( v ) ; // v == w case
}
// Consider the line extending the segment, parameterized as v + t (w - v).
// We find projection of point p onto the line.
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
// We clamp t from [0,1] to handle points outside the segment vw.
2022-07-19 08:05:34 +00:00
let t = Math . max ( 0 , Math . min ( 1 , ( this . minus ( v ) ) . dot ( w . minus ( v ) ) / l2 ) ) ;
2022-07-16 14:23:01 +00:00
let projection = v . plus ( ( w . minus ( v ) ) . times ( t ) ) ; // Projection falls on the segment
return this . fake _distance ( projection ) ;
}
2022-07-11 14:51:03 +00:00
}
2022-07-12 18:07:25 +00:00
Bangle . loadWidgets ( ) ;
2022-07-11 14:51:03 +00:00
let path = new Path ( "test.gpc" ) ;
2022-07-18 15:01:04 +00:00
let status = new Status ( path ) ;
2022-07-11 14:51:03 +00:00
2022-07-16 14:23:01 +00:00
function set _coordinates ( data ) {
2022-07-18 15:01:04 +00:00
let valid _coordinates = ! isNaN ( data . lat ) && ! isNaN ( data . lon ) ;
if ( valid _coordinates ) {
2022-07-16 14:23:01 +00:00
let direction = data . course * Math . PI / 180.0 ;
2022-07-18 15:01:04 +00:00
let position = new Point ( data . lon , data . lat ) ;
status . update _position ( position , direction ) ;
2022-07-16 14:23:01 +00:00
}
}
2022-07-16 13:20:41 +00:00
let fake _gps _point = 0.0 ;
2022-07-18 15:01:04 +00:00
function simulate _gps ( status ) {
2022-07-16 14:23:01 +00:00
let point _index = Math . floor ( fake _gps _point ) ;
if ( point _index >= path . len ) {
return ;
}
let p1 = path . point ( point _index ) ;
let p2 = path . point ( point _index + 1 ) ;
2022-07-18 15:01:04 +00:00
2022-07-16 14:23:01 +00:00
let alpha = fake _gps _point - point _index ;
2022-07-18 15:01:04 +00:00
let pos = p1 . times ( 1 - alpha ) . plus ( p2 . times ( alpha ) ) ;
let old _pos = status . position ;
2022-07-16 14:23:01 +00:00
2022-07-18 15:01:04 +00:00
fake _gps _point += 0.05 ; // advance simulation
let direction = Math . atan2 ( pos . lat - old _pos . lat , pos . lon - old _pos . lon ) ;
status . update _position ( pos , direction ) ;
2022-07-13 11:10:42 +00:00
}
2022-07-16 13:20:41 +00:00
2022-07-16 14:23:01 +00:00
if ( simulated ) {
2022-07-18 15:01:04 +00:00
status . position = new Point ( status . path . point ( 0 ) ) ;
setInterval ( simulate _gps , 500 , status ) ;
2022-07-16 14:23:01 +00:00
} else {
2022-07-18 15:01:04 +00:00
Bangle . setGPSPower ( true , "gipy" ) ;
Bangle . on ( 'GPS' , set _coordinates ) ;
2022-07-16 14:23:01 +00:00
}
2022-07-20 08:52:00 +00:00