This page lists the Code used on the page Creating Flight Plans for Flat Earth. All Code written by me Walter Bislin.
#INCLUDE JsGraphX3D.inc
#INCLUDE EarthMap.inc
#INCLUDE ControlPanel.inc
<style>
table#ControlPanel1 td.Label { width: 7%; }
table#ControlPanel1 td.Value { width: 15.5%; }
table#ControlPanel1 td.Value input { width: 75px; }
table#ControlPanel1 td.Col2 input { width: 100px; }
table#ControlPanel1 td.Col5 input { width: 100px; }
table#ControlPanel1 tr.Row1 { background: #dfd; }
table#ControlPanel1 tr.Row2 { background: #ddf; }
.Wiki table.FlightPlanTab { border-collapse: separate; border-left: 2px solid #aaa; border-right: 2px solid #aaa; }
.Wiki table.FlightPlanTab th { text-align: center; }
.Wiki table.FlightPlanTab td { text-align: center; white-space: nowrap; }
.Wiki table.FlightPlanTab th.bdl { border-left: 2px solid #aaa; }
.Wiki table.FlightPlanTab td.bdl { border-left: 2px solid #aaa; }
</style>
<jscript>
// some usefull functions
var DegPrecision = 3600 * 100;
function RoundDeg( deg, prec ) {
var s = deg < 0 ? -1 : 1;
return s * Math.round( Math.abs(deg) * prec ) / prec;
}
function DegToDMS(x) {
// returns { d, m, s }
var d = Math.abs( x );
var s = x < 0 ? -1 : 1;
var r = { d: 0, m: 0, s: 0 };
r.d = Math.floor( d );
d = (d % 1) * 60;
r.m = Math.floor( d );
r.s = (d % 1) * 60;
if (s < 0) {
if (r.d > 0) {
r.d *= -1;
} else if (r.m > 0) {
r.m *= -1;
} else {
r.s *= -1;
}
}
return r;
}
function RangePM1( x ) {
// limit x to -1..+1
if (x > 1) return 1;
if (x < -1) return -1;
return x;
}
// flight plan classes
function Leg( hdg, lat, lng, dtg, tta, fob ) {
this.Heading = hdg;
this.Lat = lat;
this.LatDeg = lat;
this.LatMin = 0;
this.LatSec = 0;
this.Lng = lng;
this.LngDeg = lng;
this.LngMin = 0;
this.LngSec = 0;
this.DistToGo = dtg;
this.TimeToArr = tta;
this.FuelOnBoard = fob;
this.DegToDMS();
}
Leg.prototype.DegToDMS = function() {
var dms = DegToDMS( RoundDeg( this.Lat, DegPrecision ) );
this.LatDeg = dms.d;
this.LatMin = dms.m;
this.LatSec = dms.s;
dms = DegToDMS( RoundDeg( this.Lng, DegPrecision ) );
this.LngDeg = dms.d;
this.LngMin = dms.m;
this.LngSec = dms.s;
}
function FlightPlan( DomContainerId, origId, destId, textAngle ) {
this.Dom = DomContainerId;
this.OriginId = origId;
this.DestinationId = destId;
this.TextAngle = textAngle;
this.Legs = [];
}
FlightPlan.prototype.AddLeg = function( hdg, lat, lng, dtg, tta, fob ) {
this.Legs.push( new Leg( hdg, lat, lng, dtg, tta, fob ) );
}
FlightPlan.prototype.Unrender = function() {
xInnerHTML( this.Dom, '' );
}
FlightPlan.prototype.Render = function() {
// creates the website table for tge flight plan
if (this.Legs.length == 0) {
xInnerHTML( this.Dom, '' );
return;
}
function renderHeader() {
var s = '<tr>';
s += '<th>WP</th>';
s += '<th>HDG</th>';
s += '<th colspan="3" class="bdl">Latitude</th>';
s += '<th colspan="3" class="bdl">Longitude</th>';
s += '<th class="bdl">DTG</th>';
s += '<th>TTA</th>';
s += '<th>FOB</th>';
s += '</tr>\n';
s += '<tr>';
s += '<th> </th>';
s += '<th>deg</th>';
s += '<th class="bdl">deg</th>';
s += '<th>min</th>';
s += '<th>sec</th>';
s += '<th class="bdl">deg</th>';
s += '<th>min</th>';
s += '<th>sec</th>';
s += '<th class="bdl">nm</th>';
s += '<th>h</th>';
s += '<th>units</th>';
s += '</tr>\n';
return s;
}
function renderLeg( wp, leg ) {
var format0 = { Mode: 'fix0', Precision: 0, UsePrefix: false, Units: '' };
var format1 = { Mode: 'fix0', Precision: 1, UsePrefix: false, Units: '' };
var format2 = { Mode: 'fix0', Precision: 2, UsePrefix: false, Units: '' };
var s = '<tr>\n';
s += '<td>' + wp + '</th>\n';
s += '<td>' + NumFormatter.NumToString( leg.Heading, format1 ) + '</th>\n';
s += '<td class="bdl">' + NumFormatter.NumToString( leg.LatDeg, format0 ) + '</th>\n';
s += '<td>' + NumFormatter.NumToString( leg.LatMin, format0 ) + '</th>\n';
s += '<td>' + NumFormatter.NumToString( leg.LatSec, format2 ) + '</th>\n';
s += '<td class="bdl">' + NumFormatter.NumToString( leg.LngDeg, format0 ) + '</th>\n';
s += '<td>' + NumFormatter.NumToString( leg.LngMin, format0 ) + '</th>\n';
s += '<td>' + NumFormatter.NumToString( leg.LngSec, format2 ) + '</th>\n';
s += '<td class="bdl">' + NumFormatter.NumToString( leg.DistToGo, format1 ) + '</th>\n';
s += '<td>' + NumFormatter.NumToString( leg.TimeToArr, format2 ) + '</th>\n';
s += '<td>' + NumFormatter.NumToString( leg.FuelOnBoard, format1 ) + '</th>\n';
s += '</tr>\n';
return s;
}
function renderTable( s ) {
return '<table class="grid FlightPlanTab" style="width: 100%">\n' + s + '</table>\n';
}
var s = renderHeader();
var nLegs = this.Legs.length;
for (var i = 0; i < nLegs; i++) {
var wp = i.toFixed(0);
if (i == 0) wp = this.OriginId;
if (i == nLegs-1) wp = this.DestinationId;
if (wp == '') wp = i.toFixed(0);
s += renderLeg( wp, this.Legs[i] );
}
s = renderTable( s );
xInnerHTML( this.Dom, s );
}
FlightPlan.prototype.DrawFlatEarthPath = function( g ) {
// g: JsGraph; graphics context for flat earth
var nLegs = this.Legs.length;
if (nLegs < 2) return;
var lat1 = this.Legs[0].Lat;
var lng1 = this.Legs[0].Lng;
var lat2 = this.Legs[nLegs-1].Lat;
var lng2 = this.Legs[nLegs-1].Lng;
g.SetLineAttr( 'blue', 2 );
EarthMap.DrawFlatEarthLine( g, lat1, lng1, lat2, lng2 );
g.SetMarkerAttr( 'Circle', 4, 'blue', 'white', 1 );
g.SetTextAttr( 'Arial', 11, 'black', 'normal', 'normal', 'left', 'middle', 5, 5, this.TextAngle );
for (var i = 0; i < nLegs; i++) {
var leg = this.Legs[i];
EarthMap.DrawFlatEarthMarker( g, leg.Lat, leg.Lng );
var txt = i.toFixed(0);
if (i == 0) txt = '--- ' + this.OriginId;
if (i == nLegs-1) txt = '--- ' + this.DestinationId;
if (txt == '--- ') txt = i.toFixed(0);
g.Text( txt, EarthMap.PointOnFE( leg.Lat, leg.Lng ) );
}
}
FlightPlan.prototype.DrawGlobePath = function( g, showWP, showNav, showGC ) {
// g: JsGraphX3D; graphics context for globe earth
function pointIsVisible( p ) {
var px = g.TransPoint3D( p[0], p[1], p[2] );
return px[0] > 0;
}
var nLegs = this.Legs.length;
if (nLegs < 2) return;
var p = JsgVect3.Null();
var p1 = JsgVect3.Null();
var p2 = JsgVect3.Null();
var pOrig = JsgVect3.Null();
var pDest = JsgVect3.Null();
var pNavDest = null;
// to clipp all graphics behind he earth horizon...
g.AddClipPlane( [0,0,0], [0,1,0], [0,0,1] );
// draw city markers
g.SetMarkerAttr( 'Circle', 8, 'black', 'white', 1 );
var legOrig = this.Legs[0];
var legDest = this.Legs[nLegs-1];
EarthMap.PointOnGlobe( legOrig.Lat, legOrig.Lng, pOrig );
EarthMap.PointOnGlobe( legDest.Lat, legDest.Lng, pDest );
g.Marker3D( pOrig, 3 );
g.Marker3D( pDest, 3 );
// draw great circle between departure and destination
if (showGC) {
g.SetLineAttr( 'green', 2 );
if (!EarthMap.DrawGlobeGreatCircleArc( g, legOrig.Lat, legOrig.Lng, legDest.Lat, legDest.Lng )) {
// special cases: origin is opposite dest and/or both at the pole
// split great circle into 2 parts
if (Math.abs(legOrig.Lat) > 89.99999) {
// pole to pole -> use southpole longitude
var interLat, interLng;
if (legOrig.Lat < 0) {
interLng = legOrig.Lng;
} else {
interLng = legDest.Lng;
}
interLat = 0;
EarthMap.DrawGlobeGreatCircleArc( g, legOrig.Lat, legOrig.Lng, interLat, interLng );
EarthMap.DrawGlobeGreatCircleArc( g, interLat, interLng, legDest.Lat, legDest.Lng );
} else {
// opposite points are not to pole -> use always path over the pole as on flat earth
var interLat = 90;
var interLng = 0;
EarthMap.DrawGlobeGreatCircleArc( g, legOrig.Lat, legOrig.Lng, interLat, interLng );
EarthMap.DrawGlobeGreatCircleArc( g, interLat, interLng, legDest.Lat, legDest.Lng );
}
}
}
// draw flat earth leg lines
if (showWP) {
g.SetLineAttr( 'blue', 2 );
for (var i = 1; i < nLegs; i++) {
var leg1 = this.Legs[i-1];
var leg2 = this.Legs[i];
EarthMap.DrawGlobeGreatCircleArc( g, leg1.Lat, leg1.Lng, leg2.Lat, leg2.Lng );
}
}
// some usefull local functions
function drawLocalNorthVector(p) {
// draw vector from p to direction north
var localEast = JsgVect3.Mult( [0,0,1], p );
if (JsgVect3.Length(localEast) == 0) return;
var localNorth = JsgVect3.Scale( JsgVect3.Norm( JsgVect3.Mult( p, localEast ) ), 500 );
g.SetLineWidth( 1 );
g.SetAlpha( 0.33 );
g.Line3D( p, JsgVect3.Add( p, localNorth ) );
g.SetAlpha( 1 );
}
function compTextAngle( p1, p2 ) {
// compute text rotation angle to be perpendicular to line p1 to p2
// p1, p2: JsgVect3; 3D points on the globe
var p1screen = JsgVect3.Copy( g.VTransPoint3D( p1 ) );
var p2screen = JsgVect3.Copy( g.VTransPoint3D( p2 ) );
var legDir = JsgVect2.Sub( p2screen, p1screen ); // z coordinate (depth) is ignored
var textAngle = - JsgVect2.Angle( [0,1], legDir, true ) * 180 / Math.PI;
if (textAngle > 90) {
textAngle -= 180;
} else if (textAngle < -90) {
textAngle += 180;
}
return textAngle;
}
function makeText( wp, origId, destId, prefix ) {
if (!xStr(prefix)) prefix = '--- ';
var txt = wp.toFixed(0);
if (wp == 0) txt = prefix + origId;
if (wp == nLegs-1) txt = prefix + destId;
if (txt == prefix) txt = wp.toFixed(0);
return txt;
}
function pointDist( p1, p2 ) {
return JsgVect3.Length( JsgVect3.Sub( p1, p2 ) );
}
// draw navigation path resulting flying headings and leg distances from FE flight plan
g.SetTextAttr( 'Arial', 11, 'red', 'normal', 'normal', 'left', 'middle', 5, 5, this.TextAngle );
if (showNav) {
g.SetLineAttr( 'red', 2 );
g.SetMarkerAttr( 'Circle', 4, 'red', 'white', 1 );
var leg1 = this.Legs[0];
EarthMap.PointOnGlobe( leg1.Lat, leg1.Lng, p1 );
// plane used to compute the next navigation point
var plane = new JsgPlane( [0,0,0], [1,0,0], [0,1,0], false );
for (var i = 1; i < nLegs; i++) {
var leg2 = this.Legs[i];
drawLocalNorthVector( p1 );
// compute p2 from heading and legDist
var localEast = JsgVect3.Mult( [0,0,1], p1 );
if (JsgVect3.Length(localEast) == 0) {
EarthMap.PointOnGlobe( leg2.Lat, leg2.Lng, p2 );
var p12 = JsgVect3.Sub( p2, p1 );
localEast = JsgVect3.Mult( p12, [0,0,1] );
}
JsgVect3.NormTo( localEast );
var localNorth = JsgVect3.Norm( JsgVect3.Mult( p1, localEast ) );
var a = leg1.Heading * Math.PI / 180;
var flightDir = JsgVect3.Add(
JsgVect3.Scale( localEast, Math.sin(a) ),
JsgVect3.Scale( localNorth, Math.cos(a) )
);
var localUp = JsgVect3.Norm( p1 );
var planeNormal = JsgVect3.Norm( JsgVect3.Mult( flightDir, localUp ) );
var planeXdir = JsgVect3.Norm( JsgVect3.Mult( localUp, planeNormal ) );
var legDist = (leg1.DistToGo - leg2.DistToGo) * 1.852; // km
var legAngle = legDist / EarthMap.Radius;
// now we have all plane properties and can compute p2
// points p1 and p2 lie on the plane, plane y dir is dir from earth center to p1
plane.Set( [0,0,0], planeXdir, localUp, false );
plane.PointOnPlane( EarthMap.Radius * Math.sin(legAngle), EarthMap.Radius * Math.cos(legAngle), p2 );
// use plane to draw great circle arc from p1 to p2
g.SetPlane( plane );
g.SetLineWidth( 2 );
g.ArcOnPlane( 0, 0, EarthMap.Radius, 90-(legAngle*180/Math.PI), 90, 1 );
g.SetLineWidth( 1 );
g.Marker3D( p1, 3 );
if (pointIsVisible(p1) && (i > 1 || !showWP)) {
g.SetTextRotation( compTextAngle( p1, p2 ) );
g.Text3D( makeText( i-1, this.OriginId, '???' ), p1 );
}
if (i == nLegs-1) {
g.Marker3D( p2, 3 );
if (pointIsVisible(p2) && pointDist( pDest, p2 ) > 100 ) {
pNavDest = JsgVect3.Copy( p2 );
g.SetTextRotation( compTextAngle( p1, p2 ) );
g.Text3D( makeText( i, this.OriginId, '???' ), p2 );
}
} else {
JsgVect3.CopyTo( p2, p1 );
leg1 = leg2;
}
}
}
g.SetTextColor( 'black' );
// draw way point markers
if (showWP) {
g.SetMarkerAttr( 'Circle', 4, 'blue', 'white', 1 );
for (var i = 0; i < nLegs; i++) {
var leg = this.Legs[i];
EarthMap.PointOnGlobe( leg.Lat, leg.Lng, p );
if (pointIsVisible(p)) {
g.Marker3D( p, 3 );
// get 2 leg points to compute text angle
var leg1, leg2;
if (i == nLegs-1) {
leg1 = this.Legs[i-1];
leg2 = this.Legs[i];
} else {
leg1 = this.Legs[i];
leg2 = this.Legs[i+1];
}
EarthMap.PointOnGlobe( leg1.Lat, leg1.Lng, p1 );
EarthMap.PointOnGlobe( leg2.Lat, leg2.Lng, p2 );
g.SetTextRotation( compTextAngle( p1, p2 ) );
g.Text3D( makeText( i, this.OriginId, this.DestinationId ), p );
}
}
}
// label airports if not already labeled
g.SetTextAttr( 'Arial', 11, 'black', 'normal', 'normal', 'center', 'top', 8, 8, 0 );
if (!showWP && !showNav) {
g.Text3D( makeText( 0, this.OriginId, this.DestinationId, '' ), pOrig );
}
if (!showWP) {
if (!pNavDest || pointDist( pNavDest, pDest ) > 100) {
g.Text3D( makeText( nLegs-1, this.OriginId, this.DestinationId, '' ), pDest );
}
}
g.DeleteClipPlanes();
}
// flight plan App
var FlightPlanApp = {
gFlatEarth: null, // JsGraph; flat earth graphics context
GlobeEarth: null, // GlobeEarthModel
OrigLatDeg: 0,
OrigLatMin: 0,
OrigLatSec: 0,
OrigLngDeg: 0,
OrigLngMin: 0,
OrigLngSec: 0,
DestLatDeg: 0,
DestLatMin: 0,
DestLatSec: 0,
DestLngDeg: 0,
DestLngMin: 0,
DestLngSec: 0,
OrigLatRad: 0,
OrigLngRad: 0,
DestLatRad: 0,
DestLngRad: 0,
origValid: false,
destValid: false,
OrigFEPos: [ 0, 0 ],
DestFEPos: [ 0, 0 ],
R: 6371, // km
EQ: 6371 * Math.PI / 2, // flat earth radius of equator in km
legDist: 500 * 1.852, // km
TAS: 470 * 1.852, // km/h
FuelOnBoard: 0, // units
FuelFlow: 0, // units/h
OriginId: '',
DestinationId: '',
dist: 0, // flat earth distances
dist_nm: 0,
dist_mile: 0,
distGC: 0, // globe great circle distances
distGC_nm: 0,
distGC_mile: 0,
vLeg: [ 0, 0 ],
tFlight: 0,
tLeg: 0,
FlightPlan: null,
OrigToDMS: function() {
var s = DegToDMS( this.OrigLatDeg );
this.OrigLatDeg = s.d;
this.OrigLatMin = s.m;
this.OrigLatSec = s.s;
var s = DegToDMS( this.OrigLngDeg );
this.OrigLngDeg = s.d;
this.OrigLngMin = s.m;
this.OrigLngSec = s.s;
},
DestToDMS: function() {
var s = DegToDMS( this.DestLatDeg );
this.DestLatDeg = s.d;
this.DestLatMin = s.m;
this.DestLatSec = s.s;
var s = DegToDMS( this.DestLngDeg );
this.DestLngDeg = s.d;
this.DestLngMin = s.m;
this.DestLngSec = s.s;
},
ToDMS: function() {
this.OrigToDMS();
this.DestToDMS();
},
ToDeg: function() {
var abs = Math.abs;
function DMStoDeg( d, m, s ) {
var sgn = d < 0 ? -1 : (m < 0 ? -1 : (s < 0 ? -1 : 1 ));
return sgn * (abs(d) + (abs(m) + (abs(s) / 60)) / 60);
}
this.OrigLatDeg = DMStoDeg( this.OrigLatDeg, this.OrigLatMin, this.OrigLatSec );
this.OrigLatMin = 0; this.OrigLatSec = 0;
this.OrigLngDeg = DMStoDeg( this.OrigLngDeg, this.OrigLngMin, this.OrigLngSec );
this.OrigLngMin = 0; this.OrigLngSec = 0;
this.DestLatDeg = DMStoDeg( this.DestLatDeg, this.DestLatMin, this.DestLatSec );
this.DestLatMin = 0; this.DestLatSec = 0;
this.DestLngDeg = DMStoDeg( this.DestLngDeg, this.DestLngMin, this.DestLngSec );
this.DestLngMin = 0; this.DestLngSec = 0;
},
EnterAirport: function( id, lat, lng ) {
if (!this.origValid) {
this.OriginId = id;
this.OrigLatDeg = lat;
this.OrigLngDeg = lng;
this.OrigToDMS();
this.origValid = true;
if (this.destValid) {
this.CreateFlightPlan();
}
return;
}
if (this.destValid) {
// shift destiation to origin
this.OriginId = this.DestinationId;
this.OrigLatRad = this.DestLatRad;
this.OrigLngRad = this.DestLngRad;
this.OrigLatDeg = this.DestLatDeg;
this.OrigLatMin = this.DestLatMin;
this.OrigLatSec = this.DestLatSec;
this.OrigLngDeg = this.DestLngDeg;
this.OrigLngMin = this.DestLngMin;
this.OrigLngSec = this.DestLngSec;
this.origValid = this.destValid;
}
this.DestinationId = id;
this.DestLatDeg = lat;
this.DestLngDeg = lng;
this.DestToDMS();
this.destValid = true;
this.CreateFlightPlan();
},
BackTrack: function() {
var t;
t = this.OriginId;
this.OriginId = this.DestinationId;
this.DestinationId = t;
t = this.OrigLatDeg;
this.OrigLatDeg = this.DestLatDeg;
this.DestLatDeg = t;
t = this.OrigLatMin;
this.OrigLatMin = this.DestLatMin;
this.DestLatMin = t;
t = this.OrigLatSec;
this.OrigLatSec = this.DestLatSec;
this.DestLatSec = t;
t = this.OrigLngDeg;
this.OrigLngDeg = this.DestLngDeg;
this.DestLngDeg = t;
t = this.OrigLngMin;
this.OrigLngMin = this.DestLngMin;
this.DestLngMin = t;
t = this.OrigLngSec;
this.OrigLngSec = this.DestLngSec;
this.DestLngSec = t;
},
Update: function() {
var cos = Math.cos, sin = Math.sin, abs = Math.abs, DegToRad = Math.PI / 180;
function DMStoRad( d, m, s ) {
var sign = d < 0 ? -1 : 1;
if (d == 0) sign = m < 0 ? -1 : 1;
if (d == 0 && m == 0) sign = s < 0 ? -1 : 1;
return DegToRad * sign * (abs(d) + abs(m) / 60 + abs(s) / 3600);
}
this.OrigLatRad = DMStoRad( this.OrigLatDeg, this.OrigLatMin, this.OrigLatSec );
this.OrigLngRad = DMStoRad( this.OrigLngDeg, this.OrigLngMin, this.OrigLngSec );
this.DestLatRad = DMStoRad( this.DestLatDeg, this.DestLatMin, this.DestLatSec );
this.DestLngRad = DMStoRad( this.DestLngDeg, this.DestLngMin, this.DestLngSec );
// limit some input values
if (this.legDist < 50 * 1.852) this.legDist = 50 * 1.852;
if (this.TAS < 50 * 1.852) this.TAS = 50 * 1.852;
if (this.FuelOnBoard < 0) this.FuelOnBoard = 0;
if (this.FuelFlow < 0) this.FuelFlow = 0;
this.origValid = this.origValid || this.OrigLatDeg != 0 || this.OrigLatMin != 0 || this.OrigLatSec != 0;
this.destValid = this.destValid || this.DestLatDeg != 0 || this.DestLatMin != 0 || this.DestLatSec != 0;
// compute flat earth distances
// see http://walter.bislins.ch/bloge/index.asp?page=Distances+on+Globe+and+Flat+Earth
var pi2 = Math.PI / 2;
var r1 = (1 - this.OrigLatRad/pi2) * this.EQ;
var r2 = (1 - this.DestLatRad/pi2) * this.EQ;
var x1 = r1 * cos( this.OrigLngRad );
var y1 = r1 * sin( this.OrigLngRad );
var x2 = r2 * cos( this.DestLngRad );
var y2 = r2 * sin( this.DestLngRad );
this.OrigFEPos = [ x1, y1 ];
this.DestFEPos = [ x2, y2 ];
var dx = x2 - x1;
var dy = y2 - y1;
this.dist = Math.sqrt( dx*dx + dy*dy );
this.dist_nm = this.dist / 1.852;
this.dist_mile = this.dist / 1.609344;
// some leg vales
this.vLeg = JsgVect2.Scale( JsgVect2.Norm( JsgVect2.Sub( this.DestFEPos, this.OrigFEPos ) ), this.legDist );
this.tFlight = this.dist / this.TAS;
this.tLeg = this.legDist / this.TAS;
// compute globe great circle distances
// see http://walter.bislins.ch/bloge/index.asp?page=Distances+on+Globe+and+Flat+Earth
var r = cos( this.OrigLatRad );
var v1x = cos( this.OrigLngRad ) * r;
var v1y = sin( this.OrigLngRad ) * r;
var v1z = sin( this.OrigLatRad );
var r = cos( this.DestLatRad );
var v2x = cos( this.DestLngRad ) * r;
var v2y = sin( this.DestLngRad ) * r;
var v2z = sin( this.DestLatRad );
var alpha = Math.acos( RangePM1( v1x * v2x + v1y * v2y + v1z * v2z ) ); // 0..pi
this.distGC = this.R * alpha;
this.distGC_nm = this.distGC / 1.852;
this.distGC_mile = this.distGC / 1.609344;
if (this.FlightPlan || (this.origValid && this.destValid)) {
this.UpdateFlightPlan();
}
this.DrawGraphs();
},
DrawGraphs: function() {
if (this.gFlatEarth) {
DrawFlatEarthModel( this.gFlatEarth );
}
if (this.GlobeEarth) {
this.GlobeEarth.Draw();
}
},
DeleteFlightPlan: function() {
if (this.FlightPlan) {
this.FlightPlan.Unrender();
this.FlightPlan = null;
}
this.DrawGraphs();
this.origValid = false;
this.destValid = false;
},
CreateFlightPlan: function() {
this.origValid = true;
this.destValid = true;
this.FlightPlan = this.FlightPlanFromData();
this.FlightPlan.Render();
this.DrawGraphs();
},
UpdateFlightPlan: function() {
this.FlightPlan = this.FlightPlanFromData();
this.FlightPlan.Render();
},
FlightPlanFromData: function() {
var textAngle = - JsgVect2.Angle( [-1,0], this.vLeg, true ) * 180 / Math.PI;
if (textAngle > 90) {
textAngle -= 180;
} else if (textAngle < -90) {
textAngle += 180;
}
var fp = new FlightPlan( 'FlightPlan', this.OriginId, this.DestinationId, textAngle );
var nLegs = Math.floor( this.dist / this.legDist );
for (var i = 0; i <= nLegs; i++) {
var p = JsgVect2.Add( this.OrigFEPos, JsgVect2.Scale( this.vLeg, i ) );
var tta = this.tFlight - i * this.tLeg;
var ddg = (this.dist - i * this.legDist) / 1.852;
var pLatLng = this.LatLngOfP( p );
var hdg = this.Heading( p, this.vLeg );
var fob = this.FuelOnBoard - i * this.tLeg * this.FuelFlow;
fp.AddLeg( hdg, pLatLng.Lat, pLatLng.Lng, ddg, tta, fob );
}
// and add last leg to destination
var p = this.DestFEPos;
var pLatLng = this.LatLngOfP( p );
var hdg = this.Heading( p, this.vLeg );
var fob = this.FuelOnBoard - this.tFlight * this.FuelFlow;
fp.AddLeg( hdg, pLatLng.Lat, pLatLng.Lng, 0, 0, fob );
return fp;
},
LatLngOfP: function( p ) {
// computes latitude and longitude for FE point p: JsgVect2
var r = JsgVect2.Length( p );
if (r == 0) return { Lat: 90, Lng: 0 }; // north pole
var lat = 90 * ( 1 - r / this.EQ );
var lng = Math.acos( p[0] / r ) * 180 / Math.PI;
if (p[1] < 0) lng = -lng;
return { Lat: lat, Lng: lng };
},
Heading: function( p, v ) {
// returns heading on FE of vector v at point p on flat earth 0..359.999
var t = [ -v[1], v[0] ];
var north = JsgVect2.Norm( JsgVect2.Scale( p, -1 ) );
var vdir = JsgVect2.Norm( v );
if (JsgVect2.Length(p) < 0.000001) {
// on north pole direction to north is opposite to direction of travel v
north = JsgVect2.Scale( vdir, -1 );
}
var hdg = Math.acos( RangePM1( JsgVect2.ScalarProd( vdir, north ) ) ) * 180 / Math.PI;
if (JsgVect2.ScalarProd( north, t ) < 0) {
hdg = 360 - hdg;
}
if (hdg > 359.9) hdg = 0;
return hdg;
},
};
function UpdateAll() {
FlightPlanApp.Update();
ControlPanels.Update();
}
function Reset() {
FlightPlanApp.DeleteFlightPlan();
ControlPanels.Reset();
UpdateAll();
}
function ToDMS() {
FlightPlanApp.ToDMS();
UpdateAll();
}
function ToDeg() {
FlightPlanApp.ToDeg();
UpdateAll();
}
function CreateFlightPlan() {
FlightPlanApp.CreateFlightPlan();
UpdateAll();
}
function BackTrack() {
FlightPlanApp.BackTrack();
UpdateAll();
}
function EnterSydney() {
FlightPlanApp.EnterAirport( 'YSSY', -33.86882, 151.209296 );
UpdateAll();
}
function EnterPapeete() {
FlightPlanApp.EnterAirport( 'NTAA', -17.5537, -149.606995 );
UpdateAll();
}
function EnterSantiago() {
FlightPlanApp.EnterAirport( 'SCEL', -33.3930016, -70.7857971 );
UpdateAll();
}
function EnterPerth() {
FlightPlanApp.EnterAirport( 'YPPH', -31.950527, 115.860457 );
UpdateAll();
}
function EnterJohannesburg() {
FlightPlanApp.EnterAirport( 'FAOR', -26.204103, 28.047305 );
UpdateAll();
}
function EnterAuckland() {
FlightPlanApp.EnterAirport( 'NZAA', -37.0080986, 174.7920074 );
UpdateAll();
}
function EnterChristchurch() {
FlightPlanApp.EnterAirport( 'NZCH', -43.489399, 172.5319977 );
UpdateAll();
}
function EnterPuntaArenas() {
FlightPlanApp.EnterAirport( 'SCCI', -53.0026016, -70.854599 );
UpdateAll();
}
function EnterBuenosAires() {
FlightPlanApp.EnterAirport( 'SAEZ', -34.8222, -58.5358 );
UpdateAll();
}
function EnterLondon() {
FlightPlanApp.EnterAirport( 'EGLL', 51.148102, -0.190278 );
UpdateAll();
}
function EnterMoscow() {
FlightPlanApp.EnterAirport( 'UUDD', 55.408798, 37.9063 );
UpdateAll();
}
function EnterSPole() {
FlightPlanApp.EnterAirport( 'NZSP', -90, -129.16130065 );
UpdateAll();
}
xOnLoad( UpdateAll );
</jscript>
{{scroll}}
<jscript>
ControlPanels.NewPanel( {
ModelRef: 'FlightPlanApp',
OnModelChange: UpdateAll,
NCols: 6,
Digits: 3,
PanelFormat: 'LabelLeft',
}
).AddHeader( {
Text: '',
ColSpan: 1
}
).AddHeader( {
Text: 'Latitude (N > 0, S < 0)',
ColSpan: 5
}
).AddHeader( {
Text: 'Longitude (E > 0, W < 0)' + ControlPanels.ResetButtonR() + ControlPanels.SmallButtonR('→Deg','ToDeg()') + ControlPanels.SmallButtonR('→DMS','ToDMS()'),
ColSpan: 6
}
).AddTextField( {
Name: 'OrigLatDeg',
Label: 'Orig.',
Units: '°',
Digits: 6,
}
).AddTextField( {
Name: 'OrigLatMin',
Label: '-',
Units: 'min',
ColSpan: 2,
}
).AddTextField( {
Name: 'OrigLatSec',
Label: '-',
Units: 'sec',
ColSpan: 2,
}
).AddTextField( {
Name: 'OrigLngDeg',
Label: '',
Units: '°',
ColSpan: 2,
Digits: 6,
}
).AddTextField( {
Name: 'OrigLngMin',
Label: '-',
Units: 'min',
ColSpan: 2,
}
).AddTextField( {
Name: 'OrigLngSec',
Label: '-',
Units: 'sec',
ColSpan: 2,
}
).AddTextField( {
Name: 'DestLatDeg',
Label: 'Dest.',
Units: '°',
Digits: 6,
}
).AddTextField( {
Name: 'DestLatMin',
Label: '-',
Units: 'min',
ColSpan: 2,
}
).AddTextField( {
Name: 'DestLatSec',
Label: '-',
Units: 'sec',
ColSpan: 2,
}
).AddTextField( {
Name: 'DestLngDeg',
Units: '°',
ColSpan: 2,
Digits: 6,
}
).AddTextField( {
Name: 'DestLngMin',
Label: '-',
Units: 'min',
ColSpan: 2,
}
).AddTextField( {
Name: 'DestLngSec',
Label: '-',
Units: 'sec',
ColSpan: 2,
}
).Render();
</jscript>
{{end scroll}}
{{scroll}}
<jscript>
ControlPanels.NewPanel( {
ModelRef: 'FlightPlanApp',
OnModelChange: UpdateAll,
NCols: 3,
ReadOnly: true,
Digits: 6,
}
).AddTextField( {
Name: 'dist_nm',
Label: 'Distance',
Units: 'nm'
}
).AddTextField( {
Name: 'dist_mile',
Label: '-',
Units: 'mile'
}
).AddTextField( {
Name: 'dist',
Label: '-',
Units: 'km'
}
).AddTextField( {
Name: 'distGC_nm',
Label: 'GC-Dist',
Units: 'nm'
}
).AddTextField( {
Name: 'distGC_mile',
Label: '-',
Units: 'mile'
}
).AddTextField( {
Name: 'distGC',
Label: '-',
Units: 'km'
}
).AddTextField( {
Name: 'OriginId',
Label: 'Orig-Id',
Mult: 0,
ReadOnly: false,
}
).AddTextField( {
Name: 'legDist',
Label: 'LegDist',
Mult: 1.852,
Units: 'nm',
ReadOnly: false,
}
).AddTextField( {
Name: 'TAS',
Mult: 1.852,
Units: 'kts',
ReadOnly: false,
}
).AddTextField( {
Name: 'DestinationId',
Label: 'Dest-Id',
Mult: 0,
ReadOnly: false,
}
).AddTextField( {
Name: 'FuelFlow',
Label: 'FF',
Units: 'units/h',
ReadOnly: false,
}
).AddTextField( {
Name: 'FuelOnBoard',
Label: 'FOB',
Units: 'units',
ReadOnly: false,
}
).Render();
</jscript>
{{end scroll}}
[javascript:EnterSydney()|{{ButtonText|Sydney|blue}}]
[javascript:EnterChristchurch()|{{ButtonText|Christchurch|blue}}]
[javascript:EnterAuckland()|{{ButtonText|Auckland|blue}}]
[javascript:EnterPapeete()|{{ButtonText|Papeete|blue}}]
[javascript:EnterSantiago()|{{ButtonText|Santiago|blue}}]
[javascript:EnterPuntaArenas()|{{ButtonText|Punta Arenas|blue}}]
[javascript:EnterBuenosAires()|{{ButtonText|Buenos Aires|blue}}]
[javascript:EnterJohannesburg()|{{ButtonText|Johannesburg|blue}}]
[javascript:EnterPerth()|{{ButtonText|Perth|blue}}]
[javascript:EnterLondon()|{{ButtonText|London|blue}}]
[javascript:EnterMoscow()|{{ButtonText|Moscow|blue}}]
[javascript:EnterSPole()|{{ButtonText|South Pole|blue}}]
[javascript:BackTrack()|{{ButtonText|Back Track|red}}]
[javascript:CreateFlightPlan()|{{ButtonText|Create Flight Plan|green}}]
{{OnOff|Show Flight Plan|Hide Flight Plan|open|noborder}}
{{scroll}}
{{div|id=FlightPlan|$VMargin}}
To create a Flight Plan...
{{end div}}
{{end scroll}}
{{OnOffEnd}}
{{OnOff|Show Flat Earth Map|Hide Flat Earth Map|open|noborder}}
<jscript>
FlightPlanApp.gFlatEarth = NewGraph2D( {
Id: 'JsGraph1',
Width: '100%',
Height: '100%',
DrawFunc: DrawFlatEarthModel,
AutoReset: false,
AutoClear: false,
AutoScalePix: true
} );
function DrawFlatEarthModel( g ) {
g.Reset();
var VpScaleArgs = true;
var VpClip = false;
var VpLeft = 8;
var VpTop = 8;
var VpRight = -8;
var VpBottom = -8;
g.SetViewport( VpLeft, VpTop, VpRight, VpBottom, VpScaleArgs, VpClip );
var WinXmin = -1;
var WinXmax = 1;
var WinYmin = -1;
var WinYmax = 1;
g.SetWindow( WinXmin, WinYmin, WinXmax, WinYmax );
EarthMap.Radius = 1;
EarthMap.SetWaterColor( '#d3e2f5' );
EarthMap.SetLakeColor( '#d3e2f5', '#8cbe5d' );
EarthMap.SetContinentColor( null, '#c6dfaf', '#8cbe5d' );
EarthMap.SetLandColor( 'Antarctica', '#eee', '#ccc' );
EarthMap.DrawFlatEarth( g );
g.SetAlpha( 0.3 );
g.SetLineAttr( 'gray', 1 );
EarthMap.DrawFlatEarthGrid( g, 15, 15 );
g.SetLineAttr( 'black', 1 );
EarthMap.DrawFlatEarthEquator( g );
EarthMap.DrawFlatEarthBorder( g );
EarthMap.DrawFlatEarthMeridian( g );
g.SetAlpha( 1 );
if (FlightPlanApp.FlightPlan) {
FlightPlanApp.FlightPlan.DrawFlatEarthPath( g );
}
}
</jscript>
{{OnOffEnd}}
{{OnOff|Show Globe Earth Map|Hide Globe Earth Map|open|noborder}}
<jscript>
var GlobeEarthModel = {
R: 6371,
Tilt: -25,
Rot: -225,
Zoom: 0.95,
ShowWpPath: true,
ShowNavPath: true,
ShowGCPath: true,
Graph: null, // JsGraphX3D; 3D graphics context for globe earth
DistFact: 100, // times R; distance between camera and earth surface
Create: function() {
var me = this;
this.Graph = NewGraphX3D( {
Id: 'GlobeEarth-Graph',
Width: '100%',
Height: '66%',
DrawFunc: function() { me.Draw(); },
AutoReset: false,
AutoClear: false,
AutoScalePix: true,
BorderWidth: 0,
} );
return this;
},
Draw: function() {
var g = this.Graph;
g.Reset3D();
var vpWidth = g.VpHeight;
g.SetGraphClipping( true, '', 0.01 );
g.SetLineAttr( '#ddd', 1 );
g.Frame();
var dist = (this.DistFact+1) * this.R;
var sceneSize = 2 * dist * Math.tan( Math.asin( this.R / dist ) );
g.SetCameraScale( sceneSize );
g.SetCameraZoom( this.Zoom );
g.SetCameraView( [0,0,0], 0, 0, (this.DistFact+1) * this.R );
g.SetWindowToCameraScreen();
// set earth transformation: earth rotation and tilt
g.TransRotateZ3D( -this.Rot );
g.TransRotateY3D( this.Tilt );
EarthMap.Trans = g.Trans3D;
g.ResetTrans3D();
// draw earth with white Antarctica
EarthMap.Radius = this.R;
EarthMap.SetWaterColor( '#d3e2f5' );
EarthMap.SetLakeColor( '#d3e2f5', '#8cbe5d' );
EarthMap.SetContinentColor( null, '#c6dfaf', '#8cbe5d' );
EarthMap.SetLandColor( 'Antarctica', '#eee', '#ccc' );
EarthMap.DrawGlobe( g );
g.SetAlpha( 0.3 );
g.SetLineAttr( 'gray', 1 );
EarthMap.DrawGlobeGrid( g, 15, 15 );
g.SetLineAttr( 'black', 1 );
EarthMap.DrawGlobeEquator( g );
EarthMap.DrawGlobeMeridian( g );
g.SetAlpha( 1 );
if (FlightPlanApp.FlightPlan) {
g.TransRotateZ3D( -this.Rot );
g.TransRotateY3D( this.Tilt );
FlightPlanApp.FlightPlan.DrawGlobePath( g, this.ShowWpPath, this.ShowNavPath, this.ShowGCPath );
g.ResetTrans3D();
}
},
};
FlightPlanApp.GlobeEarth = GlobeEarthModel.Create();
function UpdateGlobe() {
ControlPanels.Update();
GlobeEarthModel.Draw();
}
ControlPanels.NewSliderPanel( {
Name: 'GlobeEarth-Sliders',
ModelRef: 'GlobeEarthModel',
NCols: 1,
ValuePos: 'left',
OnModelChange: UpdateGlobe,
Format: 'fix0',
Digits: 2,
ReadOnly: false,
PanelFormat: 'InputShortWidth'
} ).AddValueSliderField( {
Name: 'Rot',
Label: 'Rotation',
Units: '°',
Color: 'blue',
Min: -360,
Max: 360,
} ).AddValueSliderField( {
Name: 'Tilt',
Units: '°',
Color: 'green',
Min: -89.999,
Max: 89.999,
} ).AddValueSliderField( {
Name: 'Zoom',
Color: 'black',
Min: 0.9,
Max: 5,
} ).Render();
ControlPanels.NewPanel( {
Name: 'GlobePath-Options',
ModelRef: 'GlobeEarthModel',
NCols: 1,
ValuePos: 'left',
OnModelChange: UpdateAll,
} ).AddCheckboxField( {
Name: 'Options',
Label: 'Show-Path',
ColSpan: 3,
Items: [
{
Name: 'ShowWpPath',
Text: 'Way Points',
},
{
Name: 'ShowNavPath',
Text: 'Navigation',
},
{
Name: 'ShowGCPath',
Text: 'Great Circle',
},
],
} ).Render();
</jscript>
{{OnOffEnd}}