mirror of https://github.com/espruino/BangleApps
328 lines
9.6 KiB
JavaScript
328 lines
9.6 KiB
JavaScript
(function(back) {
|
|
const APP_NAME = 'flightdash';
|
|
const FILE = APP_NAME+'.json';
|
|
|
|
// if the avwx module is available, include an extra menu item to query nearest airports via AVWX
|
|
var avwx;
|
|
try {
|
|
avwx = require('avwx');
|
|
} catch (error) {
|
|
// avwx module not installed
|
|
}
|
|
|
|
// Load settings
|
|
var settings = Object.assign({
|
|
useBaro: false,
|
|
speedUnits: 0, // KTS
|
|
altimeterUnits: 0, // FT
|
|
destID: 'KOSH',
|
|
destLat: 43.9844,
|
|
destLon: -88.5570,
|
|
}, require('Storage').readJSON(FILE, true) || {});
|
|
|
|
function writeSettings() {
|
|
require('Storage').writeJSON(FILE, settings);
|
|
}
|
|
|
|
// update the nav destination
|
|
function updateNavDest(destID, destLat, destLon) {
|
|
settings.destID = destID.replace(/[\W]+/g, '').slice(0, 7);
|
|
settings.destLat = parseFloat(destLat);
|
|
settings.destLon = parseFloat(destLon);
|
|
writeSettings();
|
|
createDestMainMenu();
|
|
}
|
|
|
|
var airports; // cache list of airports
|
|
function readAirportsList(empty_cb) {
|
|
if (airports) { // airport list has already been read in
|
|
return true;
|
|
}
|
|
airports = require('Storage').readJSON(APP_NAME+'.airports.json', true);
|
|
if (! airports) {
|
|
E.showPrompt('No airports stored - download from the Bangle Apps Loader!',
|
|
{title: 'Flight-Dash', buttons: {OK: true} }).then((v) => {
|
|
empty_cb();
|
|
});
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// use GPS fix
|
|
var afterGPSfixMenu = 'destNearest';
|
|
function getLatLon(fix) {
|
|
if (!('fix' in fix) || fix.fix == 0 || fix.satellites < 4) return;
|
|
Bangle.setGPSPower(false, APP_NAME+'-settings');
|
|
Bangle.removeListener('GPS', getLatLon);
|
|
switch (afterGPSfixMenu) {
|
|
case 'destNearest':
|
|
loadNearest(fix.lat, fix.lon);
|
|
break;
|
|
case 'createUserWaypoint':
|
|
{
|
|
if (!('userWaypoints' in settings))
|
|
settings.userWaypoints = [];
|
|
let newIdx = settings.userWaypoints.length;
|
|
settings.userWaypoints[newIdx] = {
|
|
'ID': 'USER'+(newIdx + 1),
|
|
'lat': fix.lat,
|
|
'lon': fix.lon,
|
|
};
|
|
writeSettings();
|
|
showUserWaypoints();
|
|
break;
|
|
}
|
|
case 'destAVWX':
|
|
// the free ("hobby") account of AVWX is limited to 10 nearest stations
|
|
avwx.request('station/near/'+fix.lat+','+fix.lon, 'n=10&airport=true&reporting=false', data => {
|
|
loadAVWX(data);
|
|
}, error => {
|
|
console.log(error);
|
|
E.showPrompt('AVWX query failed: '+error, {title: 'Flight-Dash', buttons: {OK: true} }).then((v) => {
|
|
createDestMainMenu();
|
|
});
|
|
});
|
|
break;
|
|
default:
|
|
back();
|
|
}
|
|
}
|
|
|
|
// find nearest airports
|
|
function loadNearest(lat, lon) {
|
|
if (! readAirportsList(createDestMainMenu))
|
|
return;
|
|
|
|
const latRad1 = lat * Math.PI/180;
|
|
const lonRad1 = lon * Math.PI/180;
|
|
for (let i = 0; i < airports.length; i++) {
|
|
const latRad2 = airports[i].la * Math.PI/180;
|
|
const lonRad2 = airports[i].lo * Math.PI/180;
|
|
let x = (lonRad2 - lonRad1) * Math.cos((latRad1 + latRad2) / 2);
|
|
let y = (latRad2 - latRad1);
|
|
airports[i].distance = Math.sqrt(x*x + y*y) * 6371;
|
|
}
|
|
let nearest = airports.sort((a, b) => a.distance - b.distance).slice(0, 14);
|
|
|
|
let destNearest = {
|
|
'' : { 'title' : 'Nearest' },
|
|
'< Back' : () => createDestMainMenu(),
|
|
};
|
|
for (let i in nearest) {
|
|
let airport = nearest[i];
|
|
destNearest[airport.i+' - '+airport.n] =
|
|
() => setTimeout(updateNavDest, 10, airport.i, airport.la, airport.lo);
|
|
}
|
|
|
|
E.showMenu(destNearest);
|
|
}
|
|
|
|
// process the data returned by AVWX
|
|
function loadAVWX(data) {
|
|
let AVWXairports = JSON.parse(data.resp);
|
|
|
|
let destAVWX = {
|
|
'' : { 'title' : 'Nearest (AVWX)' },
|
|
'< Back' : () => createDestMainMenu(),
|
|
};
|
|
for (let i in AVWXairports) {
|
|
let airport = AVWXairports[i].station;
|
|
let airport_id = ( airport.icao ? airport.icao : airport.gps );
|
|
destAVWX[airport_id+' - '+airport.name] =
|
|
() => setTimeout(updateNavDest, 10, airport_id, airport.latitude, airport.longitude);
|
|
}
|
|
|
|
E.showMenu(destAVWX);
|
|
}
|
|
|
|
// individual user waypoint menu
|
|
function showUserWaypoint(idx) {
|
|
let wayptID = settings.userWaypoints[idx].ID;
|
|
let wayptLat = settings.userWaypoints[idx].lat;
|
|
let wayptLon = settings.userWaypoints[idx].lon;
|
|
let destUser = {
|
|
'' : { 'title' : wayptID },
|
|
'< Back' : () => showUserWaypoints(),
|
|
};
|
|
destUser['Set as Dest.'] =
|
|
() => setTimeout(updateNavDest, 10, wayptID, wayptLat, wayptLon);
|
|
destUser['Edit ID'] = function() {
|
|
require('textinput').input({text: wayptID}).then(result => {
|
|
if (result) {
|
|
if (result.length > 7) {
|
|
console.log('test');
|
|
E.showPrompt('ID is too long!\n(max. 7 chars)',
|
|
{title: 'Flight-Dash', buttons: {OK: true} }).then((v) => {
|
|
showUserWaypoint(idx);
|
|
});
|
|
} else {
|
|
settings.userWaypoints[idx].ID = result;
|
|
writeSettings();
|
|
showUserWaypoint(idx);
|
|
}
|
|
} else {
|
|
showUserWaypoint(idx);
|
|
}
|
|
});
|
|
};
|
|
destUser['Delete'] = function() {
|
|
E.showPrompt('Delete user waypoint '+wayptID+'?',
|
|
{'title': 'Flight-Dash'}).then((v) => {
|
|
if (v) {
|
|
settings.userWaypoints.splice(idx, 1);
|
|
writeSettings();
|
|
showUserWaypoints();
|
|
} else {
|
|
showUserWaypoint(idx);
|
|
}
|
|
});
|
|
};
|
|
|
|
E.showMenu(destUser);
|
|
}
|
|
|
|
// user waypoints menu
|
|
function showUserWaypoints() {
|
|
let destUser = {
|
|
'' : { 'title' : 'User Waypoints' },
|
|
'< Back' : () => createDestMainMenu(),
|
|
};
|
|
for (let i in settings.userWaypoints) {
|
|
let waypt = settings.userWaypoints[i];
|
|
let idx = i;
|
|
destUser[waypt.ID] =
|
|
() => setTimeout(showUserWaypoint, 10, idx);
|
|
}
|
|
destUser['Create New'] = function() {
|
|
E.showMessage('Waiting for GPS fix', {title: 'Flight-Dash'});
|
|
afterGPSfixMenu = 'createUserWaypoint';
|
|
Bangle.setGPSPower(true, APP_NAME+'-settings');
|
|
Bangle.on('GPS', getLatLon);
|
|
};
|
|
|
|
E.showMenu(destUser);
|
|
}
|
|
|
|
// destination main menu
|
|
function createDestMainMenu() {
|
|
let destMainMenu = {
|
|
'' : { 'title' : 'Nav Dest.' },
|
|
'< Back' : () => E.showMenu(mainMenu),
|
|
};
|
|
destMainMenu['Is: '+settings.destID] = {};
|
|
destMainMenu['Nearest'] = function() {
|
|
E.showMessage('Waiting for GPS fix', {title: 'Flight-Dash'});
|
|
afterGPSfixMenu = 'destNearest';
|
|
Bangle.setGPSPower(true, APP_NAME+'-settings');
|
|
Bangle.on('GPS', getLatLon);
|
|
};
|
|
destMainMenu['Search'] = function() {
|
|
require('textinput').input({text: ''}).then(result => {
|
|
if (result) {
|
|
if (! readAirportsList(createDestMainMenu))
|
|
return;
|
|
|
|
result = result.toUpperCase();
|
|
let matches = [];
|
|
let tooManyFound = false;
|
|
for (let i in airports) {
|
|
if (airports[i].i.toUpperCase().includes(result) ||
|
|
airports[i].n.toUpperCase().includes(result)) {
|
|
matches.push(airports[i]);
|
|
if (matches.length >= 15) {
|
|
tooManyFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (! matches.length) {
|
|
E.showPrompt('No airports found!', {title: 'Flight-Dash', buttons: {OK: true} }).then((v) => {
|
|
createDestMainMenu();
|
|
});
|
|
return;
|
|
}
|
|
|
|
let destSearch = {
|
|
'' : { 'title' : 'Search Results' },
|
|
'< Back' : () => createDestMainMenu(),
|
|
};
|
|
for (let i in matches) {
|
|
let airport = matches[i];
|
|
destSearch[airport.i+' - '+airport.n] =
|
|
() => setTimeout(updateNavDest, 10, airport.i, airport.la, airport.lo);
|
|
}
|
|
if (tooManyFound) {
|
|
destSearch['More than 15 airports found!'] = {};
|
|
}
|
|
|
|
E.showMenu(destSearch);
|
|
} else {
|
|
createDestMainMenu();
|
|
}
|
|
});
|
|
};
|
|
destMainMenu['User waypts'] = function() { showUserWaypoints(); };
|
|
if (avwx) {
|
|
destMainMenu['Nearest (AVWX)'] = function() {
|
|
E.showMessage('Waiting for GPS fix', {title: 'Flight-Dash'});
|
|
afterGPSfixMenu = 'destAVWX';
|
|
Bangle.setGPSPower(true, APP_NAME+'-settings');
|
|
Bangle.on('GPS', getLatLon);
|
|
};
|
|
}
|
|
E.showMenu(destMainMenu);
|
|
}
|
|
|
|
// main menu
|
|
mainMenu = {
|
|
'' : { 'title' : 'Flight-Dash' },
|
|
'< Back' : () => {
|
|
Bangle.setGPSPower(false, APP_NAME+'-settings');
|
|
Bangle.removeListener('GPS', getLatLon);
|
|
back();
|
|
},
|
|
'Nav Dest.': () => createDestMainMenu(),
|
|
'Speed': {
|
|
value: parseInt(settings.speedUnits) || 0,
|
|
min: 0,
|
|
max: 2,
|
|
format: v => {
|
|
switch (v) {
|
|
case 0: return 'Knots';
|
|
case 1: return 'km/h';
|
|
case 2: return 'MPH';
|
|
}
|
|
},
|
|
onchange: v => {
|
|
settings.speedUnits = v;
|
|
writeSettings();
|
|
}
|
|
},
|
|
'Altitude': {
|
|
value: parseInt(settings.altimeterUnits) || 0,
|
|
min: 0,
|
|
max: 1,
|
|
format: v => {
|
|
switch (v) {
|
|
case 0: return 'Feet';
|
|
case 1: return 'Meters';
|
|
}
|
|
},
|
|
onchange: v => {
|
|
settings.altimeterUnits = v;
|
|
writeSettings();
|
|
}
|
|
},
|
|
'Use Baro': {
|
|
value: !!settings.useBaro, // !! converts undefined to false
|
|
onchange: v => {
|
|
settings.useBaro = v;
|
|
writeSettings();
|
|
}
|
|
},
|
|
};
|
|
|
|
E.showMenu(mainMenu);
|
|
})
|