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}}