WaBis

walter.bislins.ch

Source Code: Search and Rescue Mission Contest

This is part of the Source Code of the App Search and Rescue Mission Contest. The Code for the Panels and for drawing the Globe is not included, because it is not relevant to the Mission. The calculations are executed in the function Update() of the Object Mission.

// some usefull functions

function Limit1( x ) { return x < -1 ? -1 : x > 1 ? 1 : x; }

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;
  // rounding
  if (r.s > 59.9995) {
    r.s = 0;
    r.m += 1;
    if (r.m >= 60) {
      r.m = 0;
      r.d += 1;
    }
  }
  if (s < 0) {
    if (r.d > 0) {
      r.d *= -1;
    } else if (r.m > 0) {
      r.m *= -1;
    } else {
      r.s *= -1;
    }
  }
  return r;
}

/////////////////////////////////
// Mission Model

function Leg( wp, wpType, hdg, alt, lat, lon, dtg, tta, fob ) {
  this.WP = wp;
  this.WPType = wpType; // 1 -> outbound, 2 -> search, 3 -> back
  this.HDG = hdg;
  this.Alt = alt;
  this.Lat = lat;
  this.Lon = lon;
  this.DTG = dtg;
  this.TTA = tta;
  this.FOB = 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.Lon, DegPrecision ) );
  this.LonDeg = dms.d;
  this.LonMin = dms.m;
  this.LonSec = dms.s;
}

function LegWP( id, lat, lon, dist ) {
  this.ID = id;
  this.Lat = lat;
  this.Lon = lon;
  this.Dist = dist;
}

var Mission = {

  // parameters
  // ----------

  // airplane: Fuel

  FuelCapacity:      20000, // lbs
  FuelTaxi:            300, // lbs
  FuelReserveTime: 45 / 60, // h on cruise of 40000 ft
  FuelReserve:           0, // lbs = FuelFlowCruise * FuelReserveTime

  // airplane: FuelFlow

  FuelFlowClimb:      3000, // lbs/h
  FuelFlowCruise:     2000, // lbs/h
  FuelFlowDescent:    1500, // lbs/h
  FuelFlowSearch:     1500, // lbs/h

  // airplane: Speeds

  SpeedClimb:          300, // kt
  SpeedCruise:         400, // kt
  SpeedDescent:        300, // kt
  SpeedSearch:         200, // kt

  // airplane: Rates

  RateClimbTrans:     2000, // ft/min
  RateClimbCruise:    1500, // ft/min
  RateDescent:        2000, // ft/min

  AltTrans:          30000, // ft
  AltCruise:         40000, // ft
  AltSearch:           500, // ft
  AltBase:               0, // ft

  // constants

  RadiusEarth:        6371, // km
  DegToRad:  Math.PI / 180,
  RadToDeg:  180 / Math.PI,
  KmToNmi:       1 / 1.852, // nmi/km
  NmiToKm:           1.852, // km/nmi

  // locations

  BaseLat:   -34.945,
  BaseLon:   138.530556,

  // search grid

  GridWidth:  60, // nmi
  GridHeight: 60, // nmi
  GridSpacing: 5, // nmi

  GridCenterLat: -44.948333,
  GridCenterLon: 124.97083,
  GridEntryLat:    0,
  GridEntryLon:    0,
  GridExitLat:     0,
  GridExitLon:     0,

  // calculations
  // ------------

  FlightPlanLegs:    [], // array of Leg
  SearchWayPoints:   [], // array of LegWP
  GlobeEarth:      null,

  // headings and distances

  DistToGridEntry:    0,
  DistToBase:         0,

  // outbound / back to base

  TimeOutbClimbTrans:  0,
  TimeOutbClimbCruise: 0,
  TimeOutbDescent:     0,
  TimeOutbCruise:      0,
  TimeOutbTotal:       0,

  TimeBackClimbTrans:  0,
  TimeBackClimbCruise: 0,
  TimeBackDescent:     0,
  TimeBackCruise:      0,
  TimeBackTotal:       0,

  TimeTotal:           0,
  DistTotal:           0,
  FuelTotal:           0,
  FuelBingo:           0,
  FuelRemain:          0,

  DistOutbClimbTrans:  0,
  DistOutbClimbCruise: 0,
  DistOutbDescent:     0,
  DistOutbCruise:      0,
  DistOutbTotal:       0,

  DistBackClimbTrans:  0,
  DistBackClimbCruise: 0,
  DistBackDescent:     0,
  DistBackCruise:      0,
  DistBackTotal:       0,

  FuelOutbClimbTrans:  0,
  FuelOutbClimbCruise: 0,
  FuelOutbDescent:     0,
  FuelOutbCruise:      0,
  FuelOutbTotal:       0,

  FuelBackClimbTrans:  0,
  FuelBackClimbCruise: 0,
  FuelBackDescent:     0,
  FuelBackCruise:      0,
  FuelBackTotal:       0,  // Bingo Fuel

  HeadingOutb:         0,
  HeadingBack:         0,

  // search phase

  FuelSearch: 0,
  TimeSearch: 0,
  DistSearch: 0,

  FuelSearchLeg: 0,
  TimeSearchLeg: 0,
  DistSearchLeg: 0,

  NSearchLegsAvailable: 0,
  NSearchLegs: 0,

  // functions

  TimeFrom_Alt_Rate: function( alt_ft, rate_ft_min ) {
    // returns time in hours
    return alt_ft / rate_ft_min / 60;
  },

  DistFrom_Speed_Time: function( speed_kt, time_h ) {
    // return distance in nmi
    return speed_kt * time_h;
  },

  FuelFrom_FuelFlow_Time: function( fuelFlow_lbs_h, time_h ) {
    // returns fuel used in lbs
    return fuelFlow_lbs_h * time_h;
  },

  ToRad: function( deg ) {
    return this.DegToRad * deg;
  },

  GreatCircleDist: function( lat1, lon1, lat2, lon2 ) {
    // compute globe great circle distances from lat/lon in deg
    // returns distance in km
    // see http://walter.bislins.ch/bloge/index.asp?page=Distances+on+Globe+and+Flat+Earth
    var cos = Math.cos;
    var sin = Math.sin;
    var r = cos( this.ToRad(lat1) );
    var v1x = cos( this.ToRad(lon1) ) * r;
    var v1y = sin( this.ToRad(lon1) ) * r;
    var v1z = sin( this.ToRad(lat1) );
    var r = cos( this.ToRad(lat2) );
    var v2x = cos( this.ToRad(lon2) ) * r;
    var v2y = sin( this.ToRad(lon2) ) * r;
    var v2z = sin( this.ToRad(lat2) );
    var alpha = Math.acos( Limit1( v1x * v2x + v1y * v2y + v1z * v2z ) ); // 0..pi
    return this.RadiusEarth * alpha;
  },

  HeadingOfVect: function( p, v ) {
    // returns heading 0..2pi of vector v at point p on a unit sphere
    // returns 0 if p is one of the poles, because then the heading is undefined

    // compute local coordinate system at point p, N = north, E = east
    var E = JsgVect3.Mult( [0,0,1], p );
    // if p is at one of the poles, direction is undefined
    if (JsgVect3.Length2(E) == 0) return 0;
    E = JsgVect3.Norm( E );
    var N = JsgVect3.Norm( JsgVect3.Mult( p, E ) );
    var s = JsgVect3.ScalarProd( N, v );
    var a = Math.acos( Limit1( s ) );
    var s = JsgVect3.ScalarProd( E, v );
    if (s < 0) a = 2 * Math.PI - a;
    return a;
  },

  HeadingOfLatLon: function( lat1, lon1, lat2, lon2 ) {
    var p1 = JsgVect3.FromAngle( lon1, lat1, 1 );
    var p2 = JsgVect3.FromAngle( lon2, lat2, 1 );
    // n is norm vector on plane spaned by p1 and p2
    var n = JsgVect3.Mult( p1, p2 );
    if (JsgVect3.Length2 == 0) return 0;
    n = JsgVect3.Norm( n );
    // x is tangent direction at p1 to p2 along great circle from p1 to p2
    var x = JsgVect3.Norm( JsgVect3.Mult( n, p1 ) );
    return this.HeadingOfVect( p1, x ) * this.RadToDeg;
  },

  LatLonToCoord: function( latDeg, longDeg, length ) {
    return JsgVect3.FromAngle( longDeg, latDeg, length );
  },

  CoordToLatLon: function( coord ) {
    // returns { Lat, Lon } object, angles in degrees
    var ret  = {};
    var vectXY = [ coord[0], coord[1], 0 ];
    if (JsgVect3.Length(vectXY) == 0) {
      // coord is up or down, so long is undefined -> set long = 0
      ret.Lon = 0;
      ret.Lat = (coord[2] >= 0) ? 90 : -90;
      return ret;
    }
    // assert JsgVect3.Length(vectXY) > 0, so Norm returns no null vector
    var vectXYNorm = JsgVect3.Norm( vectXY );
    var coordNorm = JsgVect3.Norm( coord );
    ret.Lat  = 90 - Math.acos( Limit1( JsgVect3.ScalarProd( [0,0,1], coordNorm ) ) ) * this.RadToDeg;
    ret.Lon = Math.acos( Limit1( JsgVect3.ScalarProd( [1,0,0], vectXYNorm ) ) ) * this.RadToDeg;
    if (vectXYNorm[1] < 0) ret.Lon *= -1;
    return ret;
  },

  VectAtDistance: function( originLat, originLon, destLat, destLon, distNmi ) {
    // returns a unit vector to a point on the great circle between origin and dest
    // at a distance distNmi from origin in direction dest

    var p1 = JsgVect3.FromAngle( originLon, originLat, 1 );
    var p2 = JsgVect3.FromAngle( destLon, destLat, 1 );
    // n is norm vector on plane spaned by p1 and p2
    var n = JsgVect3.Mult( p1, p2 );
    if (JsgVect3.Length2 == 0) return { Lat: originLat, Lon: originLon };
    n = JsgVect3.Norm( n );
    var ex = p1;
    var ey = JsgVect3.Norm( JsgVect3.Mult( n, ex ) );
    var a = distNmi * this.NmiToKm / this.RadiusEarth;
    return JsgVect3.Add( JsgVect3.Scale( ex, Math.cos(a) ), JsgVect3.Scale( ey, Math.sin(a) ) );
  },

  LatLonAtDistance: function( originLat, originLon, destLat, destLon, distNmi ) {
    // returns a { Lat, Lon } of a point on the great circle between origin and dest
    // at a distance distNmi from origin in direction dest
    var px = this.VectAtDistance( originLat, originLon, destLat, destLon, distNmi );
    return this.CoordToLatLon( px );
  },

  LatOffsetFromNmi: function( lat, nmi ) {
    // requires lat + nmi lies between -90 and 90
    var a = nmi * this.NmiToKm / this.RadiusEarth;
    return lat + a * this.RadToDeg;
  },

  LonOffsetFromNmi: function( lat, lon, nmi ) {
    // requires lat < 90 and lat > -90
    var r = this.RadiusEarth * Math.cos( lat * this.DegToRad );
    if (r <= 0) return lon;
    var a = nmi * this.NmiToKm / r;
    return lon + a * this.RadToDeg;
  },

  Reset: function() {
    this.FlightPlanLegs =       [];
    this.SearchWayPoints =      [];
    this.FuelCapacity =      20000; // lbs
    this.FuelTaxi =            300; // lbs
    this.FuelReserveTime = 45 / 60; // h on cruise of 40000 ft
    this.FuelReserve =           0; // lbs = FuelFlowCruise * FuelReserveTime
    this.FuelFlowClimb =      3000; // lbs/h
    this.FuelFlowCruise =     2000; // lbs/h
    this.FuelFlowDescent =    1500; // lbs/h
    this.FuelFlowSearch =     1500; // lbs/h
    this.SpeedClimb =          300; // kt
    this.SpeedCruise =         400; // kt
    this.SpeedDescent =        300; // kt
    this.SpeedSearch =         200; // kt
    this.RateClimbTrans =     2000; // ft/min
    this.RateClimbCruise =    1500; // ft/min
    this.RateDescent =        2000; // ft/min
    this.AltTrans =          30000; // ft
    this.AltCruise =         40000; // ft
    this.AltSearch =           500; // ft
    this.AltBase =               0; // ft
    this.BaseLat =         -34.945;
    this.BaseLon =      138.530556;
    this.GridWidth =            60; // nmi
    this.GridHeight =           60; // nmi
    this.GridSpacing =           5; // nmi
    this.GridCenterLat = -44.948333;
    this.GridCenterLon = 124.97083;
  },

  Update: function() {
    this.FuelReserve = this.FuelFlowCruise * this.FuelReserveTime;
    this.NSearchLegs = Math.round( this.GridHeight / this.GridSpacing ) + 1;
    this.FlightPlanLegs = [];
    this.SearchWayPoints = [];

    // compute grid entry point
    var lonOffsetNmi = this.GridWidth / 2;
    if (this.BaseLon < this.GridCenterLon) lonOffsetNmi *= -1;
    var latOffsetNmi = this.GridHeight / 2;
    if (this.GridCenterLat < this.BaseLat) latOffsetNmi *= -1;
    this.GridEntryLat = this.LatOffsetFromNmi( this.GridCenterLat, latOffsetNmi );
    this.GridEntryLon = this.LonOffsetFromNmi( this.GridEntryLat, this.GridCenterLon, lonOffsetNmi );
    this.DistToGridEntry = this.GreatCircleDist( this.BaseLat, this.BaseLon, this.GridEntryLat, this.GridEntryLon ) * this.KmToNmi;

    // most distant grid edge used as exit point estimate
    var lonOffsetNmi = this.GridWidth / 2;
    if (this.BaseLon >= this.GridCenterLon) lonOffsetNmi *= -1;
    var latOffsetNmi = this.GridHeight / 2;
    if (this.GridCenterLat < this.BaseLat) latOffsetNmi *= -1;
    this.GridExitLat = this.LatOffsetFromNmi( this.GridCenterLat, latOffsetNmi );
    this.GridExitLon = this.LonOffsetFromNmi( this.GridEntryLat, this.GridCenterLon, lonOffsetNmi );
    this.DistToBase = this.GreatCircleDist( this.GridExitLat, this.GridExitLon, this.BaseLat, this.BaseLon ) * this.KmToNmi;

    // compute climb/descent phases
    this.TimeOutbClimbTrans  = this.TimeFrom_Alt_Rate( this.AltTrans-this.AltBase, this.RateClimbTrans );
    this.TimeOutbClimbCruise = this.TimeFrom_Alt_Rate( this.AltCruise-this.AltTrans, this.RateClimbCruise );
    this.TimeOutbDescent     = this.TimeFrom_Alt_Rate( this.AltCruise-this.AltSearch, this.RateDescent );

    this.TimeBackClimbTrans  = this.TimeFrom_Alt_Rate( this.AltTrans-this.AltSearch, this.RateClimbTrans );
    this.TimeBackClimbCruise = this.TimeFrom_Alt_Rate( this.AltCruise-this.AltTrans, this.RateClimbCruise );
    this.TimeBackDescent     = this.TimeFrom_Alt_Rate( this.AltCruise-this.AltBase,  this.RateDescent );

    this.DistOutbClimbTrans  = this.DistFrom_Speed_Time( this.SpeedClimb,   this.TimeOutbClimbTrans );
    this.DistOutbClimbCruise = this.DistFrom_Speed_Time( this.SpeedClimb,   this.TimeOutbClimbCruise );
    this.DistOutbDescent     = this.DistFrom_Speed_Time( this.SpeedDescent, this.TimeOutbDescent );

    this.DistBackClimbTrans  = this.DistFrom_Speed_Time( this.SpeedClimb,   this.TimeBackClimbTrans );
    this.DistBackClimbCruise = this.DistFrom_Speed_Time( this.SpeedClimb,   this.TimeBackClimbCruise );
    this.DistBackDescent     = this.DistFrom_Speed_Time( this.SpeedDescent, this.TimeBackDescent );

    this.FuelOutbClimbTrans  = this.FuelFrom_FuelFlow_Time( this.FuelFlowClimb,   this.TimeOutbClimbTrans );
    this.FuelOutbClimbCruise = this.FuelFrom_FuelFlow_Time( this.FuelFlowClimb,   this.TimeOutbClimbCruise );
    this.FuelOutbDescent     = this.FuelFrom_FuelFlow_Time( this.FuelFlowDescent, this.TimeOutbDescent );

    this.FuelBackClimbTrans  = this.FuelFrom_FuelFlow_Time( this.FuelFlowClimb,   this.TimeBackClimbTrans );
    this.FuelBackClimbCruise = this.FuelFrom_FuelFlow_Time( this.FuelFlowClimb,   this.TimeBackClimbCruise );
    this.FuelBackDescent     = this.FuelFrom_FuelFlow_Time( this.FuelFlowDescent, this.TimeBackDescent );

    // compute cruise phases
    this.DistOutbCruise = this.DistToGridEntry - this.DistOutbClimbTrans - this.DistOutbClimbCruise - this.DistOutbDescent;
    // require: this.DistOutbCruise >= 0
    if (this.DistOutbCruise < 0) alert( 'Error: Can not reach cruise altitude on outbound leg' );
    this.TimeOutbCruise = this.DistOutbCruise / this.SpeedCruise;
    this.TimeOutbTotal  = this.TimeOutbClimbTrans + this.TimeOutbClimbCruise + this.TimeOutbCruise + this.TimeOutbDescent;
    this.FuelOutbCruise = this.FuelFlowCruise * this.TimeOutbCruise;

    this.DistBackCruise = this.DistToBase - this.DistBackClimbTrans - this.DistBackClimbCruise - this.DistBackDescent;
    // require: this.DistBackCruise > 0
    if (this.DistBackCruise < 0) alert( 'Error: Can not reach cruise altitude on back leg' );
    this.TimeBackCruise = this.DistBackCruise / this.SpeedCruise;
    this.TimeBackTotal  = this.TimeBackClimbTrans + this.TimeBackClimbCruise + this.TimeBackCruise + this.TimeBackDescent;
    this.FuelBackCruise = this.FuelFlowCruise * this.TimeBackCruise;

    // compute outbound and back fuel usage
    this.FuelOutbTotal = this.FuelOutbClimbTrans + this.FuelOutbClimbCruise + this.FuelOutbCruise + this.FuelOutbDescent;
    this.FuelBackTotal = this.FuelBackClimbTrans + this.FuelBackClimbCruise + this.FuelBackCruise + this.FuelBackDescent;

    // compute search phase
    this.DistSearchLeg = this.GridWidth + this.GridSpacing;
    this.TimeSearchLeg = this.DistSearchLeg / this.SpeedSearch;
    this.FuelSearchLeg = this.FuelFlowSearch * this.TimeSearchLeg;

    // compute search waypoint until Bingo Fuel is reached
    this.NSearchLegsAvailable = this.ComputeLegWayPoints();

    if (this.NSearchLegsAvailable <= 0) {
      alert( 'Error: Not enough search fuel' );
    }

    this.DistSearch = this.NSearchLegsAvailable * this.DistSearchLeg - this.GridSpacing;
    this.TimeSearch = this.DistSearch / this.SpeedSearch;
    this.FuelSearch = this.FuelFlowSearch * this.TimeSearch;

    // correct grid exit point, use last leg waypoint
    var lastWPix = this.SearchWayPoints.length - 1;
    if (lastWPix >= 0) {
      var wp = this.SearchWayPoints[lastWPix];
      this.GridExitLat = wp.Lat;
      this.GridExitLon = wp.Lon;
    } else {
      this.GridExitLat = this.GridEntryLat;
      this.GridExitLon = this.GridEntryLon;
    }
    this.DistToBase = this.GreatCircleDist( this.GridExitLat, this.GridExitLon, this.BaseLat, this.BaseLon ) * this.KmToNmi;
    this.DistBackCruise = this.DistToBase - this.DistBackClimbTrans - this.DistBackClimbCruise - this.DistBackDescent;
    // require: this.DistBackCruise > 0
    if (this.DistBackCruise < 0) alert( 'Error: Can not reach cruise altitude on back leg' );
    this.TimeBackCruise = this.DistBackCruise / this.SpeedCruise;
    this.TimeBackTotal  = this.TimeBackClimbTrans + this.TimeBackClimbCruise + this.TimeBackCruise + this.TimeBackDescent;
    this.FuelBackCruise = this.FuelFlowCruise * this.TimeBackCruise;
    this.FuelBackTotal = this.FuelBackClimbTrans + this.FuelBackClimbCruise + this.FuelBackCruise + this.FuelBackDescent;

    // compute headings
    this.HeadingOutb = this.HeadingOfLatLon( this.BaseLat, this.BaseLon, this.GridEntryLat, this.GridEntryLon );
    this.HeadingBack = this.HeadingOfLatLon( this.GridExitLat, this.GridExitLon, this.BaseLat, this.BaseLon );

    // summary
    this.TimeTotal = this.TimeOutbTotal + this.TimeBackTotal + this.TimeSearch;
    this.DistOutbTotal = this.DistOutbClimbTrans + this.DistOutbClimbCruise + this.DistOutbCruise + this.DistOutbDescent;
    this.DistBackTotal = this.DistBackClimbTrans + this.DistBackClimbCruise + this.DistBackCruise + this.DistBackDescent;
    this.DistTotal = this.DistOutbTotal + this.DistBackTotal + this.DistSearch;
    this.FuelTotal = this.FuelTaxi + this.FuelOutbTotal + this.FuelBackTotal + this.FuelSearch;
    this.FuelBingo = this.FuelReserve + this.FuelBackTotal;
    this.FuelRemain = this.FuelCapacity - this.FuelTotal;

    this.CreateFlightPlan();

  },

  ComputeLegWayPoints: function() {
    // compute search waypoint until Bingo Fuel is reached.
    // Note: Bingo Fuel changes with each search leg
    // returns number of legs available

    var lonOffsetNmi = this.GridWidth / 2;
    if (this.BaseLon < this.GridCenterLon) lonOffsetNmi *= -1;
    var latOffset = (this.GridSpacing * this.NmiToKm / this.RadiusEarth) * this.RadToDeg;
    if (this.GridCenterLat >= this.BaseLat) latOffset *= -1;
    var currLeg = 0;
    var legLat = this.GridEntryLat;
    var idIx = 0;
    var did = 1;
    var legNum = 0;
    while (legNum < 1000) {
      var legLon1 = this.LonOffsetFromNmi( legLat, this.GridCenterLon, lonOffsetNmi );
      var legLon2 = this.LonOffsetFromNmi( legLat, this.GridCenterLon, -lonOffsetNmi );

      if (this.IsUsingBingoFuel( legLat, legLon2, legNum+1 )) return legNum;

      var id1 = 'S' + idIx + (lonOffsetNmi > 0 ? ',E' : ',W');
      var id2 = 'S' + idIx + (lonOffsetNmi > 0 ? ',W' : ',E');
      this.SearchWayPoints.push( new LegWP( id1, legLat, legLon1, this.GridWidth ) );
      this.SearchWayPoints.push( new LegWP( id2, legLat, legLon2, this.GridSpacing ) );
      lonOffsetNmi *= -1;
      currLeg++;
      if (currLeg >= this.NSearchLegs) {
        currLeg = 1;
        latOffset *= -1;
        did *= -1;
      }
      legLat += latOffset;
      idIx += did;
      legNum++;
    }
    return 0;
  },

  IsUsingBingoFuel: function( lat, lon, legNum ) {
    var distToBase = this.GreatCircleDist( lat, lon, this.BaseLat, this.BaseLon ) * this.KmToNmi;
    var distBackCruise = distToBase - this.DistBackClimbTrans - this.DistBackClimbCruise - this.DistBackDescent;
    var timeBackCruise = distBackCruise / this.SpeedCruise;
    var timeBackTotal  = this.TimeBackClimbTrans + this.TimeBackClimbCruise + timeBackCruise + this.TimeBackDescent;
    var fuelBackCruise = this.FuelFlowCruise * timeBackCruise;
    var fuelBackTotal = this.FuelBackClimbTrans + this.FuelBackClimbCruise + fuelBackCruise + this.FuelBackDescent;

    var distSearch = legNum * this.DistSearchLeg - this.GridSpacing;
    var timeSearch = distSearch / this.SpeedSearch;
    var fuelSearch = this.FuelFlowSearch * timeSearch;

    var fuelRemaining = this.FuelCapacity - this.FuelTaxi - this.FuelOutbTotal - fuelSearch;

    return fuelRemaining < fuelBackTotal + this.FuelReserve;
  },

  CreateFlightPlan: function() {
    // init
    var dtg = this.DistTotal;
    var tta = this.TimeTotal;
    var fob = this.FuelCapacity - this.FuelTaxi;

    // waypoint base to transition
    var pTrans = this.LatLonAtDistance( this.BaseLat, this.BaseLon, this.GridEntryLat, this.GridEntryLon, this.DistOutbClimbTrans );
    var hdgTrans = this.HeadingOfLatLon( this.BaseLat, this.BaseLon, pTrans.Lat, pTrans.Lon );
    this.FlightPlanLegs.push( new Leg( 'Base', 1, hdgTrans, this.AltBase, this.BaseLat, this.BaseLon, dtg, tta, fob ) );

    // waypoint transition to cruise altitude
    var pCruise = this.LatLonAtDistance( pTrans.Lat, pTrans.Lon, this.GridEntryLat, this.GridEntryLon, this.DistOutbClimbCruise );
    var hdgCruise = this.HeadingOfLatLon( pTrans.Lat, pTrans.Lon, pCruise.Lat, pCruise.Lon );
    dtg -= this.DistOutbClimbTrans;
    tta -= this.TimeOutbClimbTrans;
    fob -= this.FuelOutbClimbTrans;
    this.FlightPlanLegs.push( new Leg( 'Trans', 1, hdgCruise, this.AltTrans, pTrans.Lat, pTrans.Lon, dtg, tta, fob ) );

    // waypoint cruise to descent
    var pDescent = this.LatLonAtDistance( pCruise.Lat, pCruise.Lon, this.GridEntryLat, this.GridEntryLon, this.DistOutbCruise );
    var hdgDescent = this.HeadingOfLatLon( pCruise.Lat, pCruise.Lon, pDescent.Lat, pDescent.Lon );
    dtg -= this.DistOutbClimbCruise;
    tta -= this.TimeOutbClimbCruise;
    fob -= this.FuelOutbClimbCruise;
    this.FlightPlanLegs.push( new Leg( 'TC', 1, hdgDescent, this.AltCruise, pCruise.Lat, pCruise.Lon, dtg, tta, fob ) );

    // waypoint descent to grid endtry
    var hdgGrid = this.HeadingOfLatLon( pDescent.Lat, pDescent.Lon, this.GridEntryLat, this.GridEntryLon );
    dtg -= this.DistOutbCruise;
    tta -= this.TimeOutbCruise;
    fob -= this.FuelOutbCruise;
    this.FlightPlanLegs.push( new Leg( 'TD', 1, hdgGrid, this.AltCruise, pDescent.Lat, pDescent.Lon, dtg, tta, fob ) );

    // search grid waypoints
    dtg -= this.DistOutbDescent;
    tta -= this.TimeOutbDescent;
    fob -= this.FuelOutbDescent;
    var lastLeg = this.SearchWayPoints.length - 1;
    // require lastLeg >= 1
    for (var i = 0; i < lastLeg; i++) {
      var l1 = this.SearchWayPoints[i];
      var l2 = this.SearchWayPoints[i+1];
      var hdg = this.HeadingOfLatLon( l1.Lat, l1.Lon, l2.Lat, l2.Lon );
      this.FlightPlanLegs.push( new Leg( l1.ID, 2, hdg, this.AltSearch, l1.Lat, l1.Lon, dtg, tta, fob ) );
      dtg -= l1.Dist;
      tta -= l1.Dist / this.SpeedSearch;
      fob -= (l1.Dist / this.SpeedSearch) * this.FuelFlowSearch;
    }

    // waypoint grid exit to transition
    var pTrans = this.LatLonAtDistance( this.GridExitLat, this.GridExitLon, this.BaseLat, this.BaseLon, this.DistBackClimbTrans );
    var hdgTrans = this.HeadingOfLatLon( this.GridExitLat, this.GridExitLon, pTrans.Lat, pTrans.Lon );
    this.FlightPlanLegs.push( new Leg( 'S,fin', 3, hdgTrans, this.AltSearch, this.GridExitLat, this.GridExitLon, dtg, tta, fob ) );

    // waypoint transition to cruise altitude
    var pCruise = this.LatLonAtDistance( pTrans.Lat, pTrans.Lon, this.BaseLat, this.BaseLon, this.DistBackClimbCruise );
    var hdgCruise = this.HeadingOfLatLon( pTrans.Lat, pTrans.Lon, pCruise.Lat, pCruise.Lon );
    dtg -= this.DistBackClimbTrans;
    tta -= this.TimeBackClimbTrans;
    fob -= this.FuelBackClimbTrans;
    this.FlightPlanLegs.push( new Leg( 'Trans', 3, hdgCruise, this.AltTrans, pTrans.Lat, pTrans.Lon, dtg, tta, fob ) );

    // waypoint cruise to descent
    var pDescent = this.LatLonAtDistance( pCruise.Lat, pCruise.Lon, this.BaseLat, this.BaseLon, this.DistBackCruise );
    var hdgDescent = this.HeadingOfLatLon( pCruise.Lat, pCruise.Lon, pDescent.Lat, pDescent.Lon );
    dtg -= this.DistBackClimbCruise;
    tta -= this.TimeBackClimbCruise;
    fob -= this.FuelBackClimbCruise;
    this.FlightPlanLegs.push( new Leg( 'TC', 3, hdgDescent, this.AltCruise, pCruise.Lat, pCruise.Lon, dtg, tta, fob ) );

    // waypoint descent to base
    var hdgBase = this.HeadingOfLatLon( pDescent.Lat, pDescent.Lon, this.BaseLat, this.BaseLon );
    dtg -= this.DistBackCruise;
    tta -= this.TimeBackCruise;
    fob -= this.FuelBackCruise;
    this.FlightPlanLegs.push( new Leg( 'TD', 3, hdgBase, this.AltCruise, pDescent.Lat, pDescent.Lon, dtg, tta, fob ) );

    // waypoint base
    dtg -= this.DistBackDescent;
    tta -= this.TimeBackDescent;
    fob -= this.FuelBackDescent;
    this.FlightPlanLegs.push( new Leg( 'Base', 3, hdgBase, this.AltBase, this.BaseLat, this.BaseLon, dtg, tta, fob ) );

  },

  RenderFlightPlan: function() {

    function renderHeader() {
      var s = '<tr>';
      s += '<th>WP</th>';
      s += '<th>HDG</th>';
      s += '<th>ALT</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>&nbsp;</th>';
      s += '<th>deg</th>';
      s += '<th>ft</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">nmi</th>';
      s += '<th>h</th>';
      s += '<th>lbs</th>';
      s += '</tr>\n';
      return s;
    }

    function renderLeg( 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>' + leg.WP + '</th>\n';
      s += '<td>' + NumFormatter.NumToString( leg.HDG, format1 ) + '</th>\n';
      s += '<td>' + NumFormatter.NumToString( leg.Alt, format0 ) + '</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.LonDeg, format0 ) + '</th>\n';
      s += '<td>' + NumFormatter.NumToString( leg.LonMin, format0 ) + '</th>\n';
      s += '<td>' + NumFormatter.NumToString( leg.LonSec, format2 ) + '</th>\n';
      s += '<td class="bdl">' + NumFormatter.NumToString( leg.DTG, format1 ) + '</th>\n';
      s += '<td>' + NumFormatter.NumToString( leg.TTA, format2 ) + '</th>\n';
      s += '<td>' + NumFormatter.NumToString( leg.FOB, format1 ) + '</th>\n';
      s += '</tr>\n';
      return s;
    }

    function renderTable( s ) {
      return '<table class="grid FlightPlanTab" style="width: 100%">\n' + s + '</table>\n';
    }

    var nLegs = this.FlightPlanLegs.length;
    if (nLegs == 0) {
      xInnerHTML( 'FlightPlanTab', '&nbsp;' );
      return;
    }

    var s = renderHeader();
    for (var i = 0; i < nLegs; i++) {
      s += renderLeg( this.FlightPlanLegs[i] );
    }
    s = renderTable( s );
    xInnerHTML( 'FlightPlanTab', s );

  },

  DrawFlightPlan: function( g ) {
    // 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.FlightPlanLegs.length;
    if (nLegs < 2) return;

    var p = JsgVect3.Null();
    var p1 = JsgVect3.Null();
    var p2 = JsgVect3.Null();
    var pNavDest = null;

    // to clipp all graphics behind the earth horizon...
    g.AddClipPlane( [0,0,0], [0,1,0], [0,0,1] );

    // draw Base and MPP markers
    var pBase = JsgVect3.Null();
    var pMPP = JsgVect3.Null();
    g.SetMarkerAttr( 'Circle', 8, 'black', 'white', 1 );
    EarthMap.PointOnGlobe( this.BaseLat, this.BaseLon, pBase );
    EarthMap.PointOnGlobe( this.GridCenterLat, this.GridCenterLon, pMPP );
    g.Marker3D( pBase, 3 );
    g.Marker3D( pMPP, 3 );

    // draw entrypoint and exitpoint markers
    var pEntry = JsgVect3.Null();
    var pExit = JsgVect3.Null();
    g.SetMarkerAttr( 'Circle', 5, 'black', 'white', 1 );
    EarthMap.PointOnGlobe( this.GridEntryLat, this.GridEntryLon, pEntry );
    EarthMap.PointOnGlobe( this.GridExitLat, this.GridExitLon, pExit );
    g.Marker3D( pEntry, 3 );
    g.Marker3D( pExit, 3 );

    // draw flight plan
    g.SetLineAttr( 'red', 1 );
    g.SetMarkerAttr( 'Arrow1', 4, 'red', 'red', 1 );
    var fp = this.FlightPlanLegs;
    var lastLeg = fp.length - 1;
    for (var i = 0; i < lastLeg; i++) {
      var l1 = fp[i];
      var l2 = fp[i+1];
      if (l1.WPType == 1) {
        g.SetAreaAttr( 'darkgreen', 'darkgreen', 1 );
      } else if (l1.WPType == 2) {
        g.SetAreaAttr( 'blue', 'blue', 1 );
      } else {
        g.SetAreaAttr( 'red', 'red', 1 );
      }
      EarthMap.DrawGlobeGreatCircleArc( g, l1.Lat, l1.Lon, l2.Lat, l2.Lon );
      drawArrow( this, l1, l2 );
    }

    function drawArrow( app, leg1, leg2 ) {
      if (app.GlobeEarth.Zoom < 5) return;
      if (leg1.WPType == 2 && app.GlobeEarth.Zoom < 20) return;
      var pArrStart = JsgVect3.Scale( app.VectAtDistance( leg2.Lat, leg2.Lon, leg1.Lat, leg1.Lon, 1 ), app.RadiusEarth );
      var pArrEnd = app.LatLonToCoord( leg2.Lat, leg2.Lon, app.RadiusEarth );
      g.Arrow3D( pArrStart, pArrEnd );
    }

    // label legs

    g.SetTextAttr( 'Arial', 11, 'black', 'normal', 'normal', 'left', 'middle', 5, 5 );
    for (var i = 0; i < lastLeg; i++) {
      var l1 = fp[i];
      var l2 = fp[i+1];
      if (l1.WPType != 2 && this.GlobeEarth.Zoom >= 5) {
        drawLabel( this, l1, l2 );
      }
    }
    g.SetTextRotation( 0 );
    if (this.GlobeEarth.Zoom >= 5) {
      g.SetTextAttr( 'Arial', 11, 'black', 'normal', 'normal', 'center', 'top', 5, 5 );
      var pText = this.LatLonToCoord( this.GridEntryLat, this.GridEntryLon, this.RadiusEarth );
      g.Text3D( 'S0', pText );
      if (this.GlobeEarth.Zoom >= 20) {
        var pText = this.LatLonToCoord( this.GridCenterLat, this.GridCenterLon, this.RadiusEarth );
        g.Text3D( 'MPP', pText );
      }
    }

    function drawLabel( app, leg1, leg2 ) {
      var pText = app.LatLonToCoord( leg1.Lat, leg1.Lon, app.RadiusEarth );
      var pTo = app.LatLonToCoord( leg2.Lat, leg2.Lon, app.RadiusEarth );
      var aText = compTextAngle( pText, pTo );
      if (leg1.WPType == 1) {
        g.SetTextHAlign( 'left' );
      } else {
        g.SetTextHAlign( 'right' );
      }
      g.SetTextRotation( aText );
      g.Text3D( leg1.WP, pText );
    }

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

    g.DeleteClipPlanes();
  },

};

More Page Infos / Sitemap
Created Monday, May 21, 2018
Scroll to Top of Page
Changed Tuesday, May 22, 2018