From d54a1363f4238933d39a483cf279e6ad288e8e9c Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Mon, 31 Oct 2016 07:21:47 -0500 Subject: [PATCH 01/25] feature/ATC-53 - Adds TODO and FIXME comments to fms file in prep for feature work --- .../AircraftFlightManagementSystem.js | 36 ++++++++++++++++++- .../scripts/aircraft/AircraftInstanceModel.js | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 85a6a299..d6474ae5 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -72,12 +72,15 @@ export default class AircraftFlightManagementSystem { anything: false // T/F flag for if anything is being "followed" }; + // TODO: enumerate the magic numbers // set initial this.fp.altitude = clamp(1000, options.model.ceiling, 60000); if (options.aircraft.category === 'arrival') { + // TODO: why KDBG? this.prependLeg({ route: 'KDBG' }); } else if (options.aircraft.category === 'departure') { + // TODO: if we already have a reference to the aircraft with, this.my_aircraft, whay are we getting it again here? this.prependLeg({ route: window.airportController.airport_get().icao }); } @@ -95,6 +98,7 @@ export default class AircraftFlightManagementSystem { this.legs.unshift(new Leg(data, this)); this.update_fp_route(); + // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null const curr = this.currentWaypoint(); if (prev && !curr.altitude) { @@ -112,9 +116,11 @@ export default class AircraftFlightManagementSystem { insertWaypointHere(data) { const prev = this.currentWaypoint(); + // TODO: split this up into smaller chunks this.currentLeg().waypoints.splice(this.current[WAYPOINT_WITHIN_LEG], 0, new Waypoint(data, this)); this.update_fp_route(); + // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null const curr = this.currentWaypoint(); if (prev && !curr.altitude) { @@ -131,11 +137,13 @@ export default class AircraftFlightManagementSystem { * Note: if no position passed in, defaults to add to the end */ insertLeg(data) { + // TODO: reassigining data here is dangerous. if (data.firstIndex == null) { data.firstIndex = this.legs.length; } const prev = this.currentWaypoint(); + // TODO: split up into smaller chunks this.legs.splice(data.firstIndex, 0, new Leg(data, this)); this.update_fp_route(); @@ -145,6 +153,7 @@ export default class AircraftFlightManagementSystem { this.current[WAYPOINT_WITHIN_LEG] = 0; } + // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null const curr = this.currentWaypoint(); if (prev && !curr.altitude) { @@ -172,6 +181,7 @@ export default class AircraftFlightManagementSystem { * Insert a Leg at the end of the flightplan */ appendLeg(data) { + // TODO: split this up into smaller chunks this.legs.push(new Leg(data, this)); this.update_fp_route(); } @@ -180,6 +190,7 @@ export default class AircraftFlightManagementSystem { * Insert a waypoint after the *current* waypoint */ appendWaypoint(data) { + // TODO: split this up into smaller chunks this.currentLeg().waypoints.splice(this.current[WAYPOINT_WITHIN_LEG] + 1, 0, new Waypoint(data, this)); this.update_fp_route(); } @@ -201,6 +212,7 @@ export default class AircraftFlightManagementSystem { this.current[WAYPOINT_WITHIN_LEG] = 0; // look to the first waypoint of that leg } + // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Replace null values with current values const curr = this.currentWaypoint(); if (prev && !curr.altitude) { @@ -224,6 +236,7 @@ export default class AircraftFlightManagementSystem { this.current[LEG]++; this.current[WAYPOINT_WITHIN_LEG] = 0; + // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Replace null values with current values const curr = this.currentWaypoint(); if (prev && !curr.altitude) { @@ -246,11 +259,13 @@ export default class AircraftFlightManagementSystem { skipToFix(name) { const prev = this.currentWaypoint(); + // TODO: these nested for loops should be simplified for (let l = 0; l < this.legs.length; l++) { for (let w = 0; w < this.legs[l].waypoints.length; w++) { if (this.legs[l].waypoints[w].fix === name) { this.current = [l, w]; + // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null const curr = this.currentWaypoint(); if (prev && !curr.altitude) { @@ -273,6 +288,7 @@ export default class AircraftFlightManagementSystem { * Modify all waypoints */ setAll(data) { + // TODO: refactor this, what is actually happening here? for (let i = 0; i < this.legs.length; i++) { for (let j = 0; j < this.legs[i].waypoints.length; j++) { for (const k in data) { @@ -286,6 +302,7 @@ export default class AircraftFlightManagementSystem { * Modify the current waypoint */ setCurrent(data) { + // TODO: refactor this, what is actually happening here? for (const i in data) { this.currentWaypoint()[i] = data[i]; } @@ -304,6 +321,7 @@ export default class AircraftFlightManagementSystem { continue; } + // FIXME: replace the string splitting with the `RouteModel` class methods switch (this.legs[l].type) { case FP_LEG_TYPE.SID: // TODO: this split logic and string building should live in a helper function or or class method @@ -357,6 +375,7 @@ export default class AircraftFlightManagementSystem { const leg = this.currentLeg(); this.following.anything = true; + // tODO replace the string splitting with the `RouteModel` switch (leg.type) { case FP_LEG_TYPE.SID: this.following.sid = leg.route.split('.')[1]; @@ -385,6 +404,7 @@ export default class AircraftFlightManagementSystem { return this.following; } + // TODO: rename to something more accurate like `resetFollowingType` /** * Clears any current follows by updating the 'fms.following' flags */ @@ -410,6 +430,7 @@ export default class AircraftFlightManagementSystem { // Note: 'variant' is set up to pass to this function, but is not used here yet. if (type === 'ils') { this.my_aircraft.cancelFix(); + // TODO: this looks like a model object this.setCurrent({ navmode: 'rwy', runway: rwy.toUpperCase(), @@ -463,8 +484,10 @@ export default class AircraftFlightManagementSystem { this.appendLeg({ type: FP_LEG_TYPE.STAR, route: route }); } + // TODO: move this logic to the `RouteModel` /** * Takes a single-string route and converts it to a segmented route the fms can understand + * * Note: Input Data Format : "KSFO.OFFSH9.SXC.V458.IPL.J2.JCT..LLO..ACT..KACT" * Return Data Format: ["KSFO.OFFSH9.SXC", "SXC.V458.IPL", "IPL.J2.JCT", "LLO", "ACT", "KACT"] */ @@ -530,6 +553,7 @@ export default class AircraftFlightManagementSystem { return route; } + // TODO: refactor this to use `RouteModel` and possibly a `LegsCollection` class /** * Take an array of leg routes and build the legs that will go into the fms * @param {array} route - an array of properly formatted route strings @@ -666,6 +690,7 @@ export default class AircraftFlightManagementSystem { let alt; let maxAlt; + // TODO: use `StandardWaypointModel` methods for this logic // Altitude Control if (altitude) { if (altitude.indexOf('+') !== -1) { @@ -755,6 +780,7 @@ export default class AircraftFlightManagementSystem { let alt; let maxAlt; + // TODO: use `StandardWaypointModel` methods for this logic // Altitude Control if (a) { if (a.indexOf('+') !== -1) { @@ -826,6 +852,7 @@ export default class AircraftFlightManagementSystem { /** * Returns object's position in flightplan as object with 2 formats + * * @param {string} fix - name of the fix to look for in the flightplan * @returns {wp: "position-of-fix-in-waypoint-list", * lw: "position-of-fix-in-leg-wp-matrix"} @@ -836,6 +863,7 @@ export default class AircraftFlightManagementSystem { for (let l = 0; l < this.legs.length; l++) { for (let w = 0; w < this.legs[l].waypoints.length; w++) { if (this.legs[l].waypoints[w].fix === fix) { + // TODO: what do wp and lw stand for? return { wp: wp, lw: [l, w] @@ -863,6 +891,7 @@ export default class AircraftFlightManagementSystem { wp += this.current[WAYPOINT_WITHIN_LEG]; + // TODO: what do wp and lw stand for? return { wp: wp, lw: this.current @@ -872,7 +901,7 @@ export default class AircraftFlightManagementSystem { /** ************************* FMS GET FUNCTIONS ***************************/ - // TODO: this set upd methods could be used as getters instead + // TODO: this set of methods could be used as getters instead // ex: `get currentLeg()` and then used like `this.fms.currentLeg` /** * Return the current leg @@ -905,6 +934,7 @@ export default class AircraftFlightManagementSystem { * Return this fms's parent aircraft */ my_aircraft() { + // TODO: if we already have a ref to the current aircraft, `this.my_aircraft`, why are we getting it again here? return window.aircraftController.aircraft_get(this.my_aircrafts_eid); } @@ -946,6 +976,7 @@ export default class AircraftFlightManagementSystem { * Returns all waypoints in fms, in order */ waypoints() { + // TODO: move to _map() or refactor // TODO: there is a better way to do this with lodash const waypointList = $.map(this.legs, (v) => v.waypoints); @@ -953,6 +984,7 @@ export default class AircraftFlightManagementSystem { } atLastWaypoint() { + // TODO: simplify return this.indexOfCurrentWaypoint().wp === this.waypoints().length - 1; } @@ -975,6 +1007,7 @@ export default class AircraftFlightManagementSystem { return null; } + // TODO: use the `RouteModel` for this return `${this.following.sid}.${this.currentLeg().route.split('.')[2]}`; } @@ -988,6 +1021,7 @@ export default class AircraftFlightManagementSystem { return null; } + // TODO: if we already have a reference to the aircraft in, `this.my_aircraft`, why are we getting it again? return `${this.following.star}.${window.airportController.airport_get().icao}`; } diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index f66544cf..76c47ae6 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -1633,6 +1633,7 @@ export default class Aircraft { window.aircraftController.aircraft_remove(this); } + // TODO: move to `fms.cancelFix()` /** * @for AircraftInstanceModel * @method cancelFix From 767c973ca9e0378f139cc3b05fffccdbe71f645b Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Mon, 31 Oct 2016 18:12:51 -0500 Subject: [PATCH 02/25] feature/ATC-53 - Creates getters for currentLeg and currentWaypoint * Updates refs for new getters * Begins abstracting waypoint creation to individual methods * Adds _get for setting properties from passed in object in Leg and Waypoint models --- .../scripts/aircraft/AircraftController.js | 2 +- .../AircraftFlightManagementSystem.js | 121 +++--- .../scripts/aircraft/AircraftInstanceModel.js | 90 ++--- src/assets/scripts/aircraft/Leg.js | 364 ++++++++++-------- src/assets/scripts/aircraft/Waypoint.js | 12 +- src/assets/scripts/canvas/CanvasController.js | 6 +- src/assets/scripts/tutorial/TutorialView.js | 8 +- 7 files changed, 345 insertions(+), 258 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftController.js b/src/assets/scripts/aircraft/AircraftController.js index 7bc15163..3f0f94a2 100644 --- a/src/assets/scripts/aircraft/AircraftController.js +++ b/src/assets/scripts/aircraft/AircraftController.js @@ -227,7 +227,7 @@ export default class AircraftController { // Clean up the screen from aircraft that are too far if ( (!this.aircraft_visible(aircraft, 2) && !aircraft.inside_ctr) && - aircraft.fms.currentWaypoint().navmode === 'heading' + aircraft.fms.currentWaypoint.navmode === 'heading' ) { if (aircraft.category === 'arrival' || aircraft.category === 'departure') { remove = true; diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index d6474ae5..331f1761 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -56,13 +56,16 @@ const WAYPOINT_WITHIN_LEG = 1; export default class AircraftFlightManagementSystem { constructor(options) { this.my_aircrafts_eid = options.aircraft.eid; + // TODO: we should remove this reference and instead supply methods that the aircraft can call via the fms this.my_aircraft = options.aircraft; this.legs = []; this.current = [0, 0]; // [current_Leg, current_Waypoint_within_that_Leg] + // TODO: possible model object here this.fp = { altitude: null, route: [] }; + // TODO: possible model object here this.following = { sid: null, // Standard Instrument Departure Procedure star: null, // Standard Terminal Arrival Route Procedure @@ -93,14 +96,14 @@ export default class AircraftFlightManagementSystem { * Insert a Leg at the front of the flightplan */ prependLeg(data) { - const prev = this.currentWaypoint(); + const prev = this.currentWaypoint; this.legs.unshift(new Leg(data, this)); this.update_fp_route(); // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null - const curr = this.currentWaypoint(); + const curr = this.currentWaypoint; if (prev && !curr.altitude) { curr.altitude = prev.altitude; } @@ -114,15 +117,15 @@ export default class AircraftFlightManagementSystem { * Insert a waypoint at current position and immediately activate it */ insertWaypointHere(data) { - const prev = this.currentWaypoint(); + const prev = this.currentWaypoint; // TODO: split this up into smaller chunks - this.currentLeg().waypoints.splice(this.current[WAYPOINT_WITHIN_LEG], 0, new Waypoint(data, this)); + this.currentLeg.waypoints.splice(this.current[WAYPOINT_WITHIN_LEG], 0, new Waypoint(data, this)); this.update_fp_route(); // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null - const curr = this.currentWaypoint(); + const curr = this.currentWaypoint; if (prev && !curr.altitude) { curr.altitude = prev.altitude; } @@ -142,7 +145,7 @@ export default class AircraftFlightManagementSystem { data.firstIndex = this.legs.length; } - const prev = this.currentWaypoint(); + const prev = this.currentWaypoint; // TODO: split up into smaller chunks this.legs.splice(data.firstIndex, 0, new Leg(data, this)); @@ -155,7 +158,7 @@ export default class AircraftFlightManagementSystem { // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null - const curr = this.currentWaypoint(); + const curr = this.currentWaypoint; if (prev && !curr.altitude) { curr.altitude = prev.altitude; } @@ -191,7 +194,7 @@ export default class AircraftFlightManagementSystem { */ appendWaypoint(data) { // TODO: split this up into smaller chunks - this.currentLeg().waypoints.splice(this.current[WAYPOINT_WITHIN_LEG] + 1, 0, new Waypoint(data, this)); + this.currentLeg.waypoints.splice(this.current[WAYPOINT_WITHIN_LEG] + 1, 0, new Waypoint(data, this)); this.update_fp_route(); } @@ -199,7 +202,7 @@ export default class AircraftFlightManagementSystem { * Switch to the next waypoint */ nextWaypoint() { - const prev = this.currentWaypoint(); + const prev = this.currentWaypoint; const leg = this.current[LEG]; const wp = this.current[WAYPOINT_WITHIN_LEG] + 1; @@ -214,7 +217,7 @@ export default class AircraftFlightManagementSystem { // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Replace null values with current values - const curr = this.currentWaypoint(); + const curr = this.currentWaypoint; if (prev && !curr.altitude) { curr.altitude = prev.altitude; } @@ -232,13 +235,13 @@ export default class AircraftFlightManagementSystem { * Switch to the next Leg */ nextLeg() { - const prev = this.currentWaypoint(); + const prev = this.currentWaypoint; this.current[LEG]++; this.current[WAYPOINT_WITHIN_LEG] = 0; // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Replace null values with current values - const curr = this.currentWaypoint(); + const curr = this.currentWaypoint; if (prev && !curr.altitude) { curr.altitude = prev.altitude; } @@ -257,7 +260,7 @@ export default class AircraftFlightManagementSystem { * @param {string} name - the name of the fix to skip to */ skipToFix(name) { - const prev = this.currentWaypoint(); + const prev = this.currentWaypoint; // TODO: these nested for loops should be simplified for (let l = 0; l < this.legs.length; l++) { @@ -267,7 +270,7 @@ export default class AircraftFlightManagementSystem { // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? // Verify altitude & speed not null - const curr = this.currentWaypoint(); + const curr = this.currentWaypoint; if (prev && !curr.altitude) { curr.altitude = prev.altitude; } @@ -304,7 +307,7 @@ export default class AircraftFlightManagementSystem { setCurrent(data) { // TODO: refactor this, what is actually happening here? for (const i in data) { - this.currentWaypoint()[i] = data[i]; + this.currentWaypoint[i] = data[i]; } } @@ -312,45 +315,49 @@ export default class AircraftFlightManagementSystem { * Updates fms.fp.route to correspond with the fms Legs */ update_fp_route() { - const r = []; + const flightPlanRoute = []; // TODO: simplify this // FIXME: is this.legs an array? - for (const l in this.legs) { - if (!this.legs[l].type) { + for (let i = 0; i < this.legs.length; i++) { + const leg = this.legs[i]; + + if (!leg.type) { continue; } // FIXME: replace the string splitting with the `RouteModel` class methods - switch (this.legs[l].type) { + switch (leg.type) { case FP_LEG_TYPE.SID: // TODO: this split logic and string building should live in a helper function or or class method // departure airport - r.push(this.legs[l].route.split('.')[0]); + flightPlanRoute.push(leg.route.split('.')[0]); // 'sidname.exitPoint' - r.push(this.legs[l].route.split('.')[1] + '.' + this.legs[l].route.split('.')[2]); + flightPlanRoute.push(leg.route.split('.')[1] + '.' + leg.route.split('.')[2]); break; case FP_LEG_TYPE.STAR: // 'entryPoint.starname.exitPoint' - r.push(this.legs[l].route.split('.')[0] + '.' + this.legs[l].route.split('.')[1]); + flightPlanRoute.push(leg.route.split('.')[0] + '.' + leg.route.split('.')[1]); // arrival airport - r.push(this.legs[l].route.split('.')[2]); + flightPlanRoute.push(leg.route.split('.')[2]); break; case FP_LEG_TYPE.IAP: // no need to include these in flightplan (because wouldn't happen in real life) break; case FP_LEG_TYPE.AWY: - if (r[r.length - 1] !== this.legs[l].route.split('.')[0]) { - r.push(this.legs[l].route.split('.')[0]); // airway entry fix - r.push(this.legs[l].route.split('.')[1]); // airway identifier - r.push(this.legs[l].route.split('.')[2]); // airway exit fix + const previousFlightPlanRoute = flightPlanRoute[flightPlanRoute.length - 1]; + + if (previousFlightPlanRoute !== leg.route.split('.')[0]) { + flightPlanRoute.push(leg.route.split('.')[0]); // airway entry fix + flightPlanRoute.push(leg.route.split('.')[1]); // airway identifier + flightPlanRoute.push(leg.route.split('.')[2]); // airway exit fix } break; case FP_LEG_TYPE.FIX: - r.push(this.legs[l].route); + flightPlanRoute.push(leg.route); break; case FP_LEG_TYPE.MANUAL: @@ -360,19 +367,20 @@ export default class AircraftFlightManagementSystem { break; } - if (r.length === 0) { - r.push(this.legs[0].route); + // TODO: this should be first and return early + if (flightPlanRoute.length === 0) { + flightPlanRoute.push(this.legs[0].route); } } - this.fp.route = r; + this.fp.route = flightPlanRoute; } /** * Calls various task-based functions and sets 'fms.following' flags */ followCheck() { - const leg = this.currentLeg(); + const leg = this.currentLeg; this.following.anything = true; // tODO replace the string splitting with the `RouteModel` @@ -564,7 +572,7 @@ export default class AircraftFlightManagementSystem { */ customRoute(route, fullRouteClearance) { const legs = []; - const curr = this.currentWaypoint(); // save the current waypoint + const curr = this.currentWaypoint; // save the current waypoint for (let i = 0; i < route.length; i++) { let pieces; @@ -637,11 +645,11 @@ export default class AircraftFlightManagementSystem { this.update_fp_route(); // Maintain old speed and altitude - if (this.currentWaypoint().altitude == null) { + if (this.currentWaypoint.altitude == null) { this.setCurrent({ altitude: curr.altitude }); } - if (this.currentWaypoint().speed == null) { + if (this.currentWaypoint.speed == null) { this.setCurrent({ speed: curr.speed }); } @@ -675,11 +683,11 @@ export default class AircraftFlightManagementSystem { * - (spd) waypoint's speed restriction */ climbViaSID() { - if (!this.currentLeg().type === FP_LEG_TYPE.SID) { + if (!this.currentLeg.type === FP_LEG_TYPE.SID) { return; } - let wp = this.currentLeg().waypoints; + let wp = this.currentLeg.waypoints; let cruise_alt = this.fp.altitude; let cruise_spd = this.my_aircraft.model.speed.cruise; @@ -765,8 +773,8 @@ export default class AircraftFlightManagementSystem { return; } - let start_alt = this.currentWaypoint().altitude || this.my_aircraft.altitude; - let start_spd = this.currentWaypoint().speed || this.my_aircraft.model.speed.cruise; + let start_alt = this.currentWaypoint.altitude || this.my_aircraft.altitude; + let start_spd = this.currentWaypoint.speed || this.my_aircraft.model.speed.cruise; for (let i = 0; i < wp.length; i++) { if (i >= 1) { @@ -903,26 +911,41 @@ export default class AircraftFlightManagementSystem { // TODO: this set of methods could be used as getters instead // ex: `get currentLeg()` and then used like `this.fms.currentLeg` - /** - * Return the current leg - */ - currentLeg() { + get currentLeg() { return this.legs[this.current[LEG]]; } /** - * Return the current waypoint + * Return the current leg + * @deprecated */ - currentWaypoint() { + // currentLeg() { + // return this.legs[this.current[LEG]]; + // } + + get currentWaypoint() { if (this.legs.length < 1) { return null; } - const currentLeg = this.currentLeg(); + const currentLeg = this.currentLeg; return currentLeg.waypoints[this.current[WAYPOINT_WITHIN_LEG]]; } + /** + * Return the current waypoint + */ + // currentWaypoint() { + // if (this.legs.length < 1) { + // return null; + // } + // + // const currentLeg = this.currentLeg(); + // + // return currentLeg.waypoints[this.current[WAYPOINT_WITHIN_LEG]]; + // } + /** * Returns an array of all fixes along the flightplan route */ @@ -1008,7 +1031,7 @@ export default class AircraftFlightManagementSystem { } // TODO: use the `RouteModel` for this - return `${this.following.sid}.${this.currentLeg().route.split('.')[2]}`; + return `${this.following.sid}.${this.currentLeg.route.split('.')[2]}`; } /** @@ -1035,7 +1058,7 @@ export default class AircraftFlightManagementSystem { * @return {string} */ getDesinationIcaoWithRunway() { - return `${_last(this.fp.route)} ${this.currentWaypoint().runway}`; + return `${_last(this.fp.route)} ${this.currentWaypoint.runway}`; } /** @@ -1044,6 +1067,6 @@ export default class AircraftFlightManagementSystem { * @return {number|null} */ altitudeForCurrentWaypoint() { - return this.currentWaypoint().altitude; + return this.currentWaypoint.altitude; } } diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index 76c47ae6..57bf903f 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -357,7 +357,7 @@ export default class Aircraft { } // TODO: this could be another class method for FMS - if (this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.HEADING) { + if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.HEADING) { // aim aircraft at airport this.fms.setCurrent({ heading: vradial(this.position) + Math.PI @@ -380,7 +380,7 @@ export default class Aircraft { this.rwy_dep = rwy; // Update the assigned SID to use the portion for the new runway - const leg = this.fms.currentLeg(); + const leg = this.fms.currentLeg; if (leg.type === 'sid') { const a = _map(leg.waypoints, (v) => v.altitude); @@ -777,8 +777,8 @@ export default class Aircraft { // TODO: this probably shouldn't be the AircraftInstanceModel's job. this logic should belong somewhere else. // Update the FMS - let wp = this.fms.currentWaypoint(); - const leg = this.fms.currentLeg(); + let wp = this.fms.currentWaypoint; + const leg = this.fms.currentLeg; const f = this.fms.following; if (wp.navmode === WAYPOINT_NAV_MODE.RWY) { @@ -875,7 +875,7 @@ export default class Aircraft { } } - wp = this.fms.currentWaypoint(); // update 'wp' + wp = this.fms.currentWaypoint; // update 'wp' // Construct the readback if (direction) { @@ -979,12 +979,12 @@ export default class Aircraft { runClimbViaSID() { let fail = false; - if (!(this.fms.currentLeg().type === 'sid')) { + if (!(this.fms.currentLeg.type === 'sid')) { fail = true; } else if (this.fms.climbViaSID()) { const readback = { - log: `climb via the ${this.fms.currentLeg().route.split('.')[1]} departure`, - say: `climb via the ${window.airportController.airport_get().sids[this.fms.currentLeg().route.split('.')[1]].name} departure` + log: `climb via the ${this.fms.currentLeg.route.split('.')[1]} departure`, + say: `climb via the ${window.airportController.airport_get().sids[this.fms.currentLeg.route.split('.')[1]].name} departure` }; return ['ok', readback]; @@ -1036,8 +1036,8 @@ export default class Aircraft { }); const readback = { - log: `${radio_trend('speed', this.speed, this.fms.currentWaypoint().speed)} ${this.fms.currentWaypoint().speed}`, - say: `${radio_trend('speed', this.speed, this.fms.currentWaypoint().speed)} ${radio_spellOut(this.fms.currentWaypoint().speed)}` + log: `${radio_trend('speed', this.speed, this.fms.currentWaypoint.speed)} ${this.fms.currentWaypoint.speed}`, + say: `${radio_trend('speed', this.speed, this.fms.currentWaypoint.speed)} ${radio_spellOut(this.fms.currentWaypoint.speed)}` }; return ['ok', readback]; @@ -1082,7 +1082,7 @@ export default class Aircraft { if (holdFix) { // holding over a specific fix (currently only able to do so on inbound course) inboundHdg = vradial(vsub(this.position, holdFixLocation)); - if (holdFix !== this.fms.currentWaypoint().fix) { + if (holdFix !== this.fms.currentWaypoint.fix) { // not yet headed to the hold fix this.fms.insertLegHere({ type: 'fix', @@ -1093,7 +1093,7 @@ export default class Aircraft { { fix: holdFix, altitude: this.fms.altitudeForCurrentWaypoint(), - speed: this.fms.currentWaypoint().speed + speed: this.fms.currentWaypoint.speed }, this.fms ), @@ -1101,7 +1101,7 @@ export default class Aircraft { new Waypoint( { navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint().speed, + speed: this.fms.currentWaypoint.speed, altitude: this.fms.altitudeForCurrentWaypoint(), fix: null, hold: { @@ -1122,7 +1122,7 @@ export default class Aircraft { // Force the initial turn to outbound heading when entering the hold this.fms.appendWaypoint({ navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint().speed, + speed: this.fms.currentWaypoint.speed, altitude: this.fms.altitudeForCurrentWaypoint(), fix: null, hold: { @@ -1148,11 +1148,11 @@ export default class Aircraft { fix: '[custom]', location: holdFixLocation, altitude: this.fms.altitudeForCurrentWaypoint(), - speed: this.fms.currentWaypoint().speed + speed: this.fms.currentWaypoint.speed }, { // Force the initial turn to outbound heading when entering the hold navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint().speed, + speed: this.fms.currentWaypoint.speed, altitude: this.fms.altitudeForCurrentWaypoint(), fix: null, hold: { @@ -1525,7 +1525,7 @@ export default class Aircraft { prop.game.score.windy_takeoff += this.scoreWind('taking off'); this.takeoffTime = window.gameController.game_time(); - if (this.fms.currentWaypoint().speed == null) { + if (this.fms.currentWaypoint.speed == null) { this.fms.setCurrent({ speed: this.model.speed.cruise }); } @@ -1593,7 +1593,7 @@ export default class Aircraft { }; return ['ok', readback]; - } else if (this.mode === FLIGHT_MODES.CRUISE && this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.RWY) { + } else if (this.mode === FLIGHT_MODES.CRUISE && this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.RWY) { this.cancelLanding(); const readback = { @@ -1602,7 +1602,7 @@ export default class Aircraft { }; return ['ok', readback]; - } else if (this.mode === FLIGHT_MODES.CRUISE && this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.FIX) { + } else if (this.mode === FLIGHT_MODES.CRUISE && this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX) { this.cancelFix(); if (this.category === FLIGHT_CATEGORY.ARRIVAL) { @@ -1640,8 +1640,8 @@ export default class Aircraft { */ cancelFix() { // TODO: this logic could be simplified. do an early return instead of wrapping the entire function in an if. - if (this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.FIX) { - const curr = this.fms.currentWaypoint(); + if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX) { + const curr = this.fms.currentWaypoint; this.fms.appendLeg({ altitude: curr.altitude, @@ -1665,7 +1665,7 @@ export default class Aircraft { */ cancelLanding() { // TODO: this logic could be simplified. do an early return instead of wrapping the entire function in an if. - if (this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.RWY) { + if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.RWY) { const runway = window.airportController.airport_get().getRunway(this.rwy_arr); if (this.mode === FLIGHT_MODES.LANDING) { @@ -1970,21 +1970,21 @@ export default class Aircraft { }); } - if (this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.RWY) { + if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.RWY) { runway = airport.getRunway(this.rwy_arr); offset = getOffset(this, runway.position, runway.angle); offset_angle = vradial(offset); angle = radians_normalize(runway.angle); glideslope_altitude = clamp(runway.elevation, runway.getGlideslopeAltitude(offset[1]), this.altitude); - const assignedHdg = this.fms.currentWaypoint().heading; + const assignedHdg = this.fms.currentWaypoint.heading; const localizerRange = runway.ils.enabled ? runway.ils.loc_maxDist : 40; this.offset_angle = offset_angle; this.approachOffset = abs(offset[0]); this.approachDistance = offset[1]; this.target.heading = assignedHdg; - this.target.turn = this.fms.currentWaypoint().turn; - this.target.altitude = this.fms.currentWaypoint().altitude; - this.target.speed = this.fms.currentWaypoint().speed; + this.target.turn = this.fms.currentWaypoint.turn; + this.target.altitude = this.fms.currentWaypoint.altitude; + this.target.speed = this.fms.currentWaypoint.speed; // Established on ILS if (this.mode === FLIGHT_MODES.LANDING) { @@ -1996,11 +1996,11 @@ export default class Aircraft { this.target.heading = clamp(tgtHdg, minHdg, maxHdg); // Final Approach Altitude Control - this.target.altitude = Math.min(this.fms.currentWaypoint().altitude, glideslope_altitude); + this.target.altitude = Math.min(this.fms.currentWaypoint.altitude, glideslope_altitude); // Final Approach Speed Control - if (this.fms.currentWaypoint().speed > 0) { - this.fms.setCurrent({ start_speed: this.fms.currentWaypoint().speed }); + if (this.fms.currentWaypoint.speed > 0) { + this.fms.setCurrent({ start_speed: this.fms.currentWaypoint.speed }); } if (this.wow()) { @@ -2013,7 +2013,7 @@ export default class Aircraft { dist_final_app_spd, offset[1], dist_assigned_spd, this.model.speed.landing, - this.fms.currentWaypoint().start_speed + this.fms.currentWaypoint.start_speed ); } @@ -2087,12 +2087,12 @@ export default class Aircraft { this.target.heading = clamp(tgtHdg, minHdg, maxHdg); } } - } else if (this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.FIX) { - const fix = this.fms.currentWaypoint().location; + } else if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX) { + const fix = this.fms.currentWaypoint.location; if (!fix) { console.error(`${this.getCallsign()} using "fix" navmode, but no fix location!`); console.log(this.fms); - console.log(this.fms.currentWaypoint()); + console.log(this.fms.currentWaypoint); } const vector_to_fix = vsub(this.position, fix); @@ -2114,8 +2114,8 @@ export default class Aircraft { this.target.heading = vradial(vector_to_fix) - Math.PI; this.target.turn = null; } - } else if (this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.HOLD) { - const hold = this.fms.currentWaypoint().hold; + } else if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.HOLD) { + const hold = this.fms.currentWaypoint.hold; const angle_off_of_leg_hdg = abs(angle_offset(this.heading, this.target.heading)); // within ~2° of upwd/dnwd @@ -2146,15 +2146,15 @@ export default class Aircraft { } } } else { - this.target.heading = this.fms.currentWaypoint().heading; - this.target.turn = this.fms.currentWaypoint().turn; + this.target.heading = this.fms.currentWaypoint.heading; + this.target.turn = this.fms.currentWaypoint.turn; } if (this.mode !== FLIGHT_MODES.LANDING) { this.target.altitude = this.fms.altitudeForCurrentWaypoint(); - this.target.expedite = this.fms.currentWaypoint().expedite; + this.target.expedite = this.fms.currentWaypoint.expedite; this.target.altitude = Math.max(1000, this.target.altitude); - this.target.speed = this.fms.currentWaypoint().speed; + this.target.speed = this.fms.currentWaypoint.speed; this.target.speed = clamp(this.model.speed.min, this.target.speed, this.model.speed.max); } @@ -2211,7 +2211,7 @@ export default class Aircraft { if ((this.altitude - runway.elevation) < 400) { this.target.heading = rwyHdg; } else { - if (!this.fms.followCheck().sid && this.fms.currentWaypoint().heading === null) { + if (!this.fms.followCheck().sid && this.fms.currentWaypoint.heading === null) { // if no directional instructions available after takeoff // fly runway heading this.fms.setCurrent({ heading: rwyHdg }); @@ -2233,7 +2233,7 @@ export default class Aircraft { this.target.speed = Math.min(this.target.speed, 250); } else { // btwn scheduled speed and 250 - this.target.speed = Math.min(this.fms.currentWaypoint().speed, 250); + this.target.speed = Math.min(this.fms.currentWaypoint.speed, 250); } } } @@ -2377,7 +2377,7 @@ export default class Aircraft { let crab_angle = 0; // Compensate for crosswind while tracking a fix or on ILS - if (this.fms.currentWaypoint().navmode === WAYPOINT_NAV_MODE.FIX || this.mode === FLIGHT_MODES.LANDING) { + if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX || this.mode === FLIGHT_MODES.LANDING) { // TODO: this should be abstracted to a helper function const offset = angle_offset(this.heading, wind.angle + Math.PI); crab_angle = Math.asin((wind.speed * sin(offset)) / this.speed); @@ -2565,7 +2565,7 @@ export default class Aircraft { // Update fms.following this.fms.followCheck(); - const wp = this.fms.currentWaypoint(); + const wp = this.fms.currentWaypoint; // Populate strip fields with default values const defaultHeadingText = heading_to_string(wp.heading); const defaultAltitudeText = _get(wp, 'altitude', '-'); @@ -2578,7 +2578,7 @@ export default class Aircraft { const hasAltitude = _has(wp, 'altitude'); const isFollowingSID = _isString(destinationText); const isFollowingSTAR = _isString(this.fms.following.star); - const { fixRestrictions } = this.fms.currentWaypoint(); + const { fixRestrictions } = this.fms.currentWaypoint; this.aircraftStripView.update(defaultHeadingText, defaultAltitudeText, defaultDestinationText, currentSpeedText); diff --git a/src/assets/scripts/aircraft/Leg.js b/src/assets/scripts/aircraft/Leg.js index 2740f59a..f665d7bf 100644 --- a/src/assets/scripts/aircraft/Leg.js +++ b/src/assets/scripts/aircraft/Leg.js @@ -1,3 +1,4 @@ +import _get from 'lodash/get'; import _has from 'lodash/has'; import _map from 'lodash/map'; import Waypoint from './Waypoint'; @@ -33,22 +34,23 @@ export default class Leg { * @constructor */ constructor(data = {}, fms) { - this.route = '[radar vectors]'; // eg 'KSFO.OFFSH9.SXC' or 'FAITH' - this.type = FP_LEG_TYPE.MANUAL; + this.route = ''; // eg 'KSFO.OFFSH9.SXC' or 'FAITH' + this.type = ''; + // TODO: possibly implement as waypointCollection this.waypoints = []; // an array of zlsa.atc.Waypoint objects to follow - // Fill data with default Leg properties if they aren't specified (prevents wp constructor from getting confused) - if (!data.route) { - data.route = this.route; - } - - if (!data.type) { - data.type = this.type; - } - - if (!data.waypoints) { - data.waypoints = this.waypoints; - } + // // Fill data with default Leg properties if they aren't specified (prevents wp constructor from getting confused) + // if (!data.route) { + // data.route = this.route; + // } + // + // if (!data.type) { + // data.type = this.type; + // } + // + // if (!data.waypoints) { + // data.waypoints = this.waypoints; + // } this.parse(data, fms); } @@ -57,10 +59,17 @@ export default class Leg { * Parse input data and apply to this leg */ parse(data, fms) { - // FIXME: make these multi-line, single lines are hard to reason and prone to errors. - for (const i in data) if (this.hasOwnProperty(i)) this[i] = data[i]; // Populate Leg with data - if (this.waypoints.length === 0) this.generateWaypoints(data, fms); - if (this.waypoints.length === 0) this.waypoints = [new Waypoint({ route: '' }, fms)]; + this.route = _get(data, 'route', '[radar vectors]'); + this.type = _get(data, 'type', FP_LEG_TYPE.MANUAL); + this.waypoints = _get(data, 'waypoints', []); + + if (this.waypoints.length === 0) { + this.generateWaypoints(data, fms); + + if (this.waypoints.length === 0) { + this.waypoints = [new Waypoint({ route: '' }, fms)]; + } + } } /** @@ -71,163 +80,212 @@ export default class Leg { return; } - if (this.type === FP_LEG_TYPE.SID) { - if (!fms) { - log('Attempted to generate waypoints for SID, but cannot because fms ref not passed!', LOG.WARNING); + switch (this.type) { + case FP_LEG_TYPE.SID: + this._generateWaypointsForSid(data, fms); - return; - } + break; + case FP_LEG_TYPE.STAR: + this._generateWaypointsForStar(data, fms); - // const { apt, sid, exit } = data.route.split('.'); - const apt = data.route.split('.')[0]; - const sid = data.route.split('.')[1]; - const exit = data.route.split('.')[2]; - const rwy = fms.my_aircraft.rwy_dep; - this.waypoints = []; + break; + case FP_LEG_TYPE.IAP: + // FUTURE FUNCTIONALITY + this._generateWaypointsForIap(data, fms); - // Generate the waypoints - if (!rwy) { - const isWarning = true; - window.uiController.ui_log(`${fms.my_aircraft.getCallsign()} unable to fly SID, we haven't been assigned a departure runway!`, isWarning); + break; + case FP_LEG_TYPE.AWY: + this._generateWaypointsForAirway(data, fms); - return; - } + break; + case FP_LEG_TYPE.FIX: + this._generateWaypointForFix(fms); - const pairs = window.airportController.airport_get(apt).getSID(sid, exit, rwy); + break; + default: + this._generateManualWaypoint(fms); - // Remove the placeholder leg (if present) - if (fms.my_aircraft.wow() && fms.legs.length > 0 - && fms.legs[0].route === window.airportController.airport_get().icao && pairs.length > 0 - ) { - // remove the placeholder leg, to be replaced below with SID Leg - fms.legs.splice(0, 1); - } + break; - // for each fix/restr pair - for (let i = 0; i < pairs.length; i++) { - const f = pairs[i][0]; - let a = null; - let s = null; - - if (pairs[i][1]) { - const a_n_s = pairs[i][1].toUpperCase().split('|'); - - for (const j in a_n_s) { - if (a_n_s[j][0] === 'A') { - a = a_n_s[j].substr(1); - } else if (a_n_s[j][0] === 'S') { - s = a_n_s[j].substr(1); - } - } - } + } + } - this.waypoints.push(new Waypoint( - { - fix: f, - fixRestrictions: { - alt: a, - spd: s - } - }, - fms - )); - } + _generateWaypointsForSid(data, fms) { + if (!fms) { + log('Attempted to generate waypoints for SID, but cannot because fms ref not passed!', LOG.WARNING); - if (!this.waypoints[0].speed) { - this.waypoints[0].speed = fms.my_aircraft.model.speed.cruise; - } - } else if (this.type === FP_LEG_TYPE.STAR) { - if (!fms) { - log('Attempted to generate waypoints for STAR, but cannot because fms ref not passed!', LOG.WARNING); + return; + } - return; - } + const apt = this.route.split('.')[0]; + const sid = this.route.split('.')[1]; + const exit = this.route.split('.')[2]; + const rwy = fms.my_aircraft.rwy_dep; + this.waypoints = []; + + // Generate the waypoints + if (!rwy) { + const isWarning = true; + window.uiController.ui_log( + `${fms.my_aircraft.getCallsign()} unable to fly SID, we haven't been assigned a departure runway!`, + isWarning + ); + + return; + } + + const pairs = window.airportController.airport_get(apt).getSID(sid, exit, rwy); + + // Remove the placeholder leg (if present) + if (fms.my_aircraft.wow() && fms.legs.length > 0 + && fms.legs[0].route === window.airportController.airport_get().icao && pairs.length > 0 + ) { + // remove the placeholder leg, to be replaced below with SID Leg + fms.legs.splice(0, 1); + } + + // for each fix/restr pair + for (let i = 0; i < pairs.length; i++) { + const f = pairs[i][0]; + let a = null; + let s = null; - const entry = data.route.split('.')[0]; - const star = data.route.split('.')[1]; - const apt = data.route.split('.')[2]; - const rwy = fms.my_aircraft.rwy_arr; - this.waypoints = []; - - // Generate the waypoints - const pairs = window.airportController.airport_get(apt).getSTAR(star, entry, rwy); - - // for each fix/restr pair - for (let i = 0; i < pairs.length; i++) { - const f = pairs[i][0]; - let a = null; - let s = null; - - if (pairs[i][1]) { - const a_n_s = pairs[i][1].toUpperCase().split('|'); - - for (const j in a_n_s) { - if (a_n_s[j][0] === 'A') { - a = a_n_s[j].substr(1); - } else if (a_n_s[j][0] === 'S') { - s = a_n_s[j].substr(1); - } + if (pairs[i][1]) { + const a_n_s = pairs[i][1].toUpperCase().split('|'); + + for (const j in a_n_s) { + if (a_n_s[j][0] === 'A') { + a = a_n_s[j].substr(1); + } else if (a_n_s[j][0] === 'S') { + s = a_n_s[j].substr(1); } } - - this.waypoints.push(new Waypoint( - { - fix: f, - fixRestrictions: { - alt: a, - spd: s - } - }, - fms - )); } - if (!this.waypoints[0].speed) { - this.waypoints[0].speed = fms.my_aircraft.model.speed.cruise; - } - } else if (this.type === FP_LEG_TYPE.IAP) { - // FUTURE FUNCTIONALITY - } else if (this.type === FP_LEG_TYPE.AWY) { - const start = data.route.split('.')[0]; - const airway = data.route.split('.')[1]; - const end = data.route.split('.')[2]; - // Verify airway is valid - const apt = window.airportController.airport_get(); - - if (!_has(apt, 'airways') || !_has(apt.airways, 'airway')) { - log(`Airway ${airway} not defined at ${apt.icao}`, LOG.WARNING); - return; - } + this.waypoints.push(new Waypoint( + { + fix: f, + fixRestrictions: { + alt: a, + spd: s + } + }, + fms + )); + } - // Verify start/end points are along airway - const awy = apt.airways[airway]; - if (!(awy.indexOf(start) !== -1 && awy.indexOf(end) !== -1)) { - log(`Unable to follow ${airway} from ${start} to ${end}`, LOG.WARNING); - return; - } + if (!this.waypoints[0].speed) { + this.waypoints[0].speed = fms.my_aircraft.model.speed.cruise; + } + } - // Build list of fixes, depending on direction traveling along airway - const fixes = []; - const readFwd = (awy.indexOf(end) > awy.indexOf(start)); - if (readFwd) { - for (let f = awy.indexOf(start); f <= awy.indexOf(end); f++) { - fixes.push(awy[f]); - } - } else { - for (let f = awy.indexOf(start); f >= awy.indexOf(end); f--) { - fixes.push(awy[f]); + _generateWaypointsForStar(data, fms) { + if (!fms) { + log('Attempted to generate waypoints for STAR, but cannot because fms ref not passed!', LOG.WARNING); + + return; + } + + const entry = this.route.split('.')[0]; + const star = this.route.split('.')[1]; + const apt = this.route.split('.')[2]; + const rwy = fms.my_aircraft.rwy_arr; + this.waypoints = []; + + // Generate the waypoints + const pairs = window.airportController.airport_get(apt).getSTAR(star, entry, rwy); + + // Create a new WaypointModel for each fix found in the Star. + + // for each fix/restr pair + for (let i = 0; i < pairs.length; i++) { + const f = pairs[i][0]; + let a = null; + let s = null; + + if (pairs[i][1]) { + const a_n_s = pairs[i][1].toUpperCase().split('|'); + + for (const j in a_n_s) { + if (a_n_s[j][0] === 'A') { + a = a_n_s[j].substr(1); + } else if (a_n_s[j][0] === 'S') { + s = a_n_s[j].substr(1); + } } } - // Add list of fixes to this.waypoints - this.waypoints = []; - this.waypoints = _map(fixes, (f) => new Waypoint({ fix: f }, fms)); - } else if (this.type === FP_LEG_TYPE.FIX) { - this.waypoints = []; - this.waypoints.push(new Waypoint({ fix: data.route }, fms)); + this.waypoints.push(new Waypoint( + { + fix: f, + fixRestrictions: { + alt: a, + spd: s + } + }, + fms + )); + } + + if (!this.waypoints[0].speed) { + this.waypoints[0].speed = fms.my_aircraft.model.speed.cruise; + } + } + + + _generateWaypointsForIap(data, fms) { + // NOT IN USE + return; + } + + + _generateWaypointsForAirway(data, fms) { + const start = this.route.split('.')[0]; + const airway = this.route.split('.')[1]; + const end = this.route.split('.')[2]; + // Verify airway is valid + const apt = window.airportController.airport_get(); + + if (!_has(apt, 'airways') || !_has(apt.airways, 'airway')) { + log(`Airway ${airway} not defined at ${apt.icao}`, LOG.WARNING); + return; + } + + // Verify start/end points are along airway + const awy = apt.airways[airway]; + if (!(awy.indexOf(start) !== -1 && awy.indexOf(end) !== -1)) { + log(`Unable to follow ${airway} from ${start} to ${end}`, LOG.WARNING); + return; + } + + // Build list of fixes, depending on direction traveling along airway + const fixes = []; + const readFwd = (awy.indexOf(end) > awy.indexOf(start)); + + if (readFwd) { + for (let f = awy.indexOf(start); f <= awy.indexOf(end); f++) { + fixes.push(awy[f]); + } } else { - this.waypoints.push(new Waypoint(data, fms)); + for (let f = awy.indexOf(start); f >= awy.indexOf(end); f--) { + fixes.push(awy[f]); + } } + + // Add list of fixes to this.waypoints + this.waypoints = []; + this.waypoints = _map(fixes, (f) => new Waypoint({ fix: f }, fms)); + } + + + _generateWaypointForFix(fms) { + this.waypoints = []; + this.waypoints.push(new Waypoint({ fix: this.route }, fms)); + } + + + _generateManualWaypoint(fms) { + this.waypoints.push(new Waypoint(this.route, fms)); } } diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index e90d25be..d2bf3c1d 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -1,5 +1,6 @@ /* eslint-disable no-multi-spaces, no-undef */ import _forEach from 'lodash/forEach'; +import _get from 'lodash/get'; import _has from 'lodash/has'; /** @@ -37,6 +38,8 @@ export default class Waypoint { spd: null }; + this.route = ''; + this.parse(data, fms); } @@ -51,9 +54,12 @@ export default class Waypoint { this.location = window.airportController.airport_get().getFixPosition(data.fix); } + this.route = _get(data, 'route', this.route); + _forEach(data, (value, key) => { if (_has(this, key)) { - this[key] = data[key]; + console.log(key); + // this[key] = data[key]; } }); @@ -62,10 +68,10 @@ export default class Waypoint { this.navmode = 'heading'; const apt = window.airportController.airport_get(); - if (data.route.split('.')[0] === apt.icao && this.heading === null) { + if (this.route.split('.')[0] === apt.icao && this.heading === null) { // aim departure along runway heading this.heading = apt.getRunway(apt.runway).angle; - } else if (data.route.split('.')[0] === 'KDBG' && this.heading === null) { + } else if (this.route.split('.')[0] === 'KDBG' && this.heading === null) { // aim arrival @ middle of airspace this.heading = this.radial + Math.PI; } diff --git a/src/assets/scripts/canvas/CanvasController.js b/src/assets/scripts/canvas/CanvasController.js index c1df6f76..6e9a0fa7 100644 --- a/src/assets/scripts/canvas/CanvasController.js +++ b/src/assets/scripts/canvas/CanvasController.js @@ -721,7 +721,7 @@ export default class ConvasController { */ canvas_draw_separation_indicator(cc, aircraft) { // Draw a trailing indicator 2.5 NM (4.6km) behind landing aircraft to help with traffic spacing - const rwy = window.airportController.airport_get().getRunway(aircraft.fms.currentWaypoint().runway); + const rwy = window.airportController.airport_get().getRunway(aircraft.fms.currentWaypoint.runway); if (!rwy) { return; @@ -1011,7 +1011,7 @@ export default class ConvasController { for (let i = 0; i < 60; i++) { twin.update(); - ils_locked = twin.fms.currentWaypoint().runway && + ils_locked = twin.fms.currentWaypoint.runway && twin.category === FLIGHT_MODES.ARRIVAL && twin.mode === FLIGHT_MODES.LANDING; @@ -1118,7 +1118,7 @@ export default class ConvasController { // width of colored bar const bar_width = width / 18; const bar_width2 = bar_width / 2; - const ILS_enabled = aircraft.fms.currentWaypoint().runway && aircraft.category === FLIGHT_CATEGORY.ARRIVAL; + const ILS_enabled = aircraft.fms.currentWaypoint.runway && aircraft.category === FLIGHT_CATEGORY.ARRIVAL; const lock_size = height / 3; const lock_offset = lock_size / 8; const pi = Math.PI; diff --git a/src/assets/scripts/tutorial/TutorialView.js b/src/assets/scripts/tutorial/TutorialView.js index 5c557441..807719cb 100644 --- a/src/assets/scripts/tutorial/TutorialView.js +++ b/src/assets/scripts/tutorial/TutorialView.js @@ -220,7 +220,7 @@ export default class TutorialView { return t; } - return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint().runway); + return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint.runway); }, side: 'left', position: tutorial_position @@ -237,7 +237,7 @@ export default class TutorialView { return t; } - return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint().runway); + return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint.runway); }, side: 'left', position: tutorial_position @@ -255,7 +255,7 @@ export default class TutorialView { return t; } - return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint().runway); + return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint.runway); }, side: 'left', position: tutorial_position @@ -272,7 +272,7 @@ export default class TutorialView { return t; } - return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint().runway); + return t.replace('{RUNWAY}', prop.aircraft.list[0].fms.currentWaypoint.runway); }, side: 'left', position: tutorial_position From c2c17b5bc3d0cec53beed385fc91e14ed2fabb27 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Fri, 4 Nov 2016 16:49:19 -0500 Subject: [PATCH 03/25] feature/ATC-53 - adds .getFixPositionCoordinates() to FixCollection. --- src/assets/scripts/airport/Fix/FixCollection.js | 17 +++++++++++++++++ test/airport/fix/FixCollection.spec.js | 17 +++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/assets/scripts/airport/Fix/FixCollection.js b/src/assets/scripts/airport/Fix/FixCollection.js index 50a018af..5c35c989 100644 --- a/src/assets/scripts/airport/Fix/FixCollection.js +++ b/src/assets/scripts/airport/Fix/FixCollection.js @@ -124,6 +124,23 @@ class FixCollection { return fixModel || null; } + /** + * @for FixCollection + * @method getFixPositionCoordinates + * @param fixName {string} + * @return {array} + */ + getFixPositionCoordinates(fixName) { + const fixModel = this.findFixByName(fixName); + + if (!fixModel) { + // error + return null; + } + + return fixModel.position + } + /** * Find a list of all `FixModel`s within the collection that have a name that does not start with an underscore. * diff --git a/test/airport/fix/FixCollection.spec.js b/test/airport/fix/FixCollection.spec.js index 40556974..5212efc9 100644 --- a/test/airport/fix/FixCollection.spec.js +++ b/test/airport/fix/FixCollection.spec.js @@ -1,11 +1,11 @@ import ava from 'ava'; +import _isEqual from 'lodash/isEqual'; import FixCollection from '../../../src/assets/scripts/airport/Fix/FixCollection'; import FixModel from '../../../src/assets/scripts/airport/Fix/FixModel'; import { airportPositionFixture } from '../../fixtures/airportFixtures'; import { FIX_LIST_MOCK } from './_mocks/fixMocks'; - ava.serial('FixCollection throws when an attempt to instantiate is made', t => { t.throws(() => new FixCollection()); @@ -39,7 +39,20 @@ ava.serial('.findFixByName() returns null if a FixModel does not exist within th t.true(result === null); }); -ava.serial('.findRealFixes() returns a list of fixes that dont have `_` prepedning thier name', t => { +ava.serial('.getFixPositionCoordinates() returns the position of a FixModel', t => { + const result = FixCollection.getFixPositionCoordinates('BAKRR'); + const expectedResult = [ 675.477318026648, -12.012221291734532 ]; + + t.true(_isEqual(result, expectedResult)); +}); + +ava.serial('.getFixPositionCoordinates() returns null if a FixModel does not exist within the collection', t => { + const result = FixCollection.getFixPositionCoordinates(''); + + t.true(result === null); +}); + +ava.serial('.findRealFixes() returns a list of fixes that dont have `_` prepending thier name', t => { const result = FixCollection.findRealFixes(); t.true(result.length === 104); From b93a08752effbaa764c5b868c3d816de77ac53f3 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Fri, 4 Nov 2016 18:39:47 -0500 Subject: [PATCH 04/25] feature/ATC-53 - Adds aircraftConstants and updates refs to use new file. --- .../scripts/aircraft/AircraftInstanceModel.js | 107 ++++++++++-------- .../scripts/aircraft/AircraftStripView.js | 2 +- .../scripts/airport/Arrival/ArrivalBase.js | 2 +- .../airport/Departure/DepartureBase.js | 2 +- src/assets/scripts/canvas/CanvasController.js | 2 +- .../scripts/constants/aircraftConstants.js | 35 ++++++ 6 files changed, 101 insertions(+), 49 deletions(-) create mode 100644 src/assets/scripts/constants/aircraftConstants.js diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index 57bf903f..a435c02f 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -37,44 +37,49 @@ import { getCardinalDirection } from '../utilities/radioUtilities'; import { km, radiansToDegrees, degreesToRadians, heading_to_string } from '../utilities/unitConverters'; +import { + FLIGHT_MODES, + FLIGHT_CATEGORY, + WAYPOINT_NAV_MODE +} from '../constants/aircraftConstants'; import { SELECTORS } from '../constants/selectors'; // TODO: these constants don't belong in this class. they should probably live on their own in a different file. -/** - * @property FLIGHT_MODES - * @type {Object} - * @final - */ -export const FLIGHT_MODES = { - APRON: 'apron', - TAXI: 'taxi', - WAITING: 'waiting', - TAKEOFF: 'takeoff', - CRUISE: 'cruise', - LANDING: 'landing' -}; - -/** - * @property FLIGHT_CATEGORY - * @type {Object} - * @final - */ -export const FLIGHT_CATEGORY = { - ARRIVAL: 'arrival', - DEPARTURE: 'departure' -}; - -/** - * @property WAYPOINT_NAV_MODE - * @type {Object} - * @final - */ -export const WAYPOINT_NAV_MODE = { - FIX: 'fix', - HEADING: 'heading', - HOLD: 'hold', - RWY: 'rwy' -}; +// /** +// * @property FLIGHT_MODES +// * @type {Object} +// * @final +// */ +// export const FLIGHT_MODES = { +// APRON: 'apron', +// TAXI: 'taxi', +// WAITING: 'waiting', +// TAKEOFF: 'takeoff', +// CRUISE: 'cruise', +// LANDING: 'landing' +// }; +// +// /** +// * @property FLIGHT_CATEGORY +// * @type {Object} +// * @final +// */ +// export const FLIGHT_CATEGORY = { +// ARRIVAL: 'arrival', +// DEPARTURE: 'departure' +// }; +// +// /** +// * @property WAYPOINT_NAV_MODE +// * @type {Object} +// * @final +// */ +// export const WAYPOINT_NAV_MODE = { +// FIX: 'fix', +// HEADING: 'heading', +// HOLD: 'hold', +// RWY: 'rwy' +// }; /** * Enum of commands and thier corresponding function. @@ -239,6 +244,7 @@ export default class Aircraft { this.buildRestrictedAreaLinks(); this.assignInitialRunway(options); this.parse(options); + this.updateFmsAfterInitialLoad(options); this.createStrip(); this.updateStrip(); } @@ -300,14 +306,24 @@ export default class Aircraft { } parse(data) { - const keys = ['position', 'model', 'airline', 'callsign', 'category', 'heading', 'altitude', 'speed']; - - _forEach(keys, (key) => { - if (_has(data, key)) { - this[key] = data[key]; - } - }); - + // const keys = ['position', 'model', 'airline', 'callsign', 'category', 'heading', 'altitude', 'speed']; + // _forEach(keys, (key) => { + // if (_has(data, key)) { + // this[key] = data[key]; + // } + // }); + + this.position = _get(data, 'position', this.position); + this.model = _get(data, 'model', this.model); + this.airline = _get(data, 'airline', this.airline); + this.callsign = _get(data, 'callsign', this.callsign); + this.category = _get(data, 'category', this.category); + this.heading = _get(data, 'heading', this.heading); + this.altitude = _get(data, 'altitude', this.altitude); + this.speed = _get(data, 'speed', this.speed); + } + + updateFmsAfterInitialLoad(data) { if (this.category === FLIGHT_CATEGORY.ARRIVAL) { if (data.waypoints.length > 0) { this.setArrivalWaypoints(data.waypoints); @@ -336,8 +352,9 @@ export default class Aircraft { this.fms.setCurrent({ speed: speed }); if (data.route) { - // TODO: what is the true for? enumerate that. - this.fms.customRoute(this.fms.formatRoute(data.route), true); + const route = this.fms.formatRoute(data.route); + + this.fms.customRoute(route, true); this.fms.descendViaSTAR(); } diff --git a/src/assets/scripts/aircraft/AircraftStripView.js b/src/assets/scripts/aircraft/AircraftStripView.js index f39ad58d..474e286c 100644 --- a/src/assets/scripts/aircraft/AircraftStripView.js +++ b/src/assets/scripts/aircraft/AircraftStripView.js @@ -4,7 +4,7 @@ import { FLIGHT_CATEGORY, FLIGHT_MODES, WAYPOINT_NAV_MODE -} from './AircraftInstanceModel'; +} from '../constants/aircraftConstants'; import { SELECTORS } from '../constants/selectors'; /** diff --git a/src/assets/scripts/airport/Arrival/ArrivalBase.js b/src/assets/scripts/airport/Arrival/ArrivalBase.js index 919a5cd9..e281f01f 100644 --- a/src/assets/scripts/airport/Arrival/ArrivalBase.js +++ b/src/assets/scripts/airport/Arrival/ArrivalBase.js @@ -17,7 +17,7 @@ import { calculateDistanceToBoundary, calculateHeadingFromTwoPositions } from '../../math/flightMath'; -import { FLIGHT_CATEGORY } from '../../aircraft/AircraftInstanceModel'; +import { FLIGHT_CATEGORY } from '../../constants/aircraftConstants'; import { AIRPORT_CONSTANTS } from '../../constants/airportConstants'; import { TIME } from '../../constants/globalConstants'; import { LOG } from '../../constants/logLevel'; diff --git a/src/assets/scripts/airport/Departure/DepartureBase.js b/src/assets/scripts/airport/Departure/DepartureBase.js index 33d0bc7f..2890a405 100644 --- a/src/assets/scripts/airport/Departure/DepartureBase.js +++ b/src/assets/scripts/airport/Departure/DepartureBase.js @@ -6,7 +6,7 @@ import { randomAirlineSelectionHelper } from '../../airline/randomAirlineSelectionHelper'; import { choose } from '../../utilities/generalUtilities'; -import { FLIGHT_CATEGORY } from '../../aircraft/AircraftInstanceModel'; +import { FLIGHT_CATEGORY } from '../../constants/aircraftConstants'; import { TIME } from '../../constants/globalConstants'; /** diff --git a/src/assets/scripts/canvas/CanvasController.js b/src/assets/scripts/canvas/CanvasController.js index 6e9a0fa7..8acf18e5 100644 --- a/src/assets/scripts/canvas/CanvasController.js +++ b/src/assets/scripts/canvas/CanvasController.js @@ -10,7 +10,7 @@ import { distance2d } from '../math/distance'; import { vscale, vturn, positive_intersection_with_rect } from '../math/vector'; import { SELECTORS } from '../constants/selectors'; import { LOG } from '../constants/logLevel'; -import { FLIGHT_MODES, FLIGHT_CATEGORY } from '../aircraft/AircraftInstanceModel'; +import { FLIGHT_MODES, FLIGHT_CATEGORY } from '../constants/aircraftConstants'; // Temporary const declaration here to attach to the window AND use as internal property const canvas = {}; diff --git a/src/assets/scripts/constants/aircraftConstants.js b/src/assets/scripts/constants/aircraftConstants.js new file mode 100644 index 00000000..5c6d3c2c --- /dev/null +++ b/src/assets/scripts/constants/aircraftConstants.js @@ -0,0 +1,35 @@ +/** + * @property FLIGHT_MODES + * @type {Object} + * @final + */ +export const FLIGHT_MODES = { + APRON: 'apron', + TAXI: 'taxi', + WAITING: 'waiting', + TAKEOFF: 'takeoff', + CRUISE: 'cruise', + LANDING: 'landing' +}; + +/** + * @property FLIGHT_CATEGORY + * @type {Object} + * @final + */ +export const FLIGHT_CATEGORY = { + ARRIVAL: 'arrival', + DEPARTURE: 'departure' +}; + +/** + * @property WAYPOINT_NAV_MODE + * @type {Object} + * @final + */ +export const WAYPOINT_NAV_MODE = { + FIX: 'fix', + HEADING: 'heading', + HOLD: 'hold', + RWY: 'rwy' +}; From 2c21396d2f53c1bbefc0ffe5abdc8360be45966b Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Fri, 4 Nov 2016 20:07:42 -0500 Subject: [PATCH 05/25] feature/ATC-53 - Cleans and streamlines Waypoint, adds example comment to RouteModel. --- index.html | 4 +- .../AircraftFlightManagementSystem.js | 45 ++++++++++------ .../scripts/aircraft/AircraftInstanceModel.js | 37 ------------- src/assets/scripts/aircraft/Waypoint.js | 54 +++++++++++-------- .../scripts/airport/Route/RouteModel.js | 10 ++++ 5 files changed, 73 insertions(+), 77 deletions(-) diff --git a/index.html b/index.html index 6eb9d7c8..7bc287c0 100644 --- a/index.html +++ b/index.html @@ -74,7 +74,7 @@ - --> diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 331f1761..ca33da6b 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -3,6 +3,7 @@ import _last from 'lodash/last'; import _map from 'lodash/map'; import Waypoint from './Waypoint'; import Leg, { FP_LEG_TYPE } from './Leg'; +import RouteModel from '../airport/Route/RouteModel'; import { clamp } from '../math/core'; import { LOG } from '../constants/logLevel'; @@ -500,6 +501,8 @@ export default class AircraftFlightManagementSystem { * Return Data Format: ["KSFO.OFFSH9.SXC", "SXC.V458.IPL", "IPL.J2.JCT", "LLO", "ACT", "KACT"] */ formatRoute(data) { + const routeModel = new RouteModel(data); + // Format the user's input let route = []; // const ap = airport_get; @@ -571,28 +574,39 @@ export default class AircraftFlightManagementSystem { * replace the current contents of 'this.legs' */ customRoute(route, fullRouteClearance) { + // save the current waypoint + const curr = this.currentWaypoint; + const legs = []; - const curr = this.currentWaypoint; // save the current waypoint + // const legs = this._generateLegsForFixOrStandardRoute(route); for (let i = 0; i < route.length; i++) { - let pieces; + const routeSections = route[i].split('.'); // just a fix/navaid - if (route[i].split('.').length === 1) { - legs.push(new Leg({ type: FP_LEG_TYPE.FIX, route: route[i] }, this)); - } else if (route[i].split('.').length === 3) { - // is an instrument procedure - pieces = route[i].split('.'); + if (routeSections.length === 1) { + const legToAdd = new Leg({ type: FP_LEG_TYPE.FIX, route: route[i] }, this); + + legs.push(legToAdd); + } else if (routeSections.length === 3) { + const routeModel = new RouteModel(route[i]); + const currentAirport = window.airportController.airport_get(); - if (Object.keys(window.airportController.airport_get().sids).indexOf(pieces[1]) > -1) { + if (typeof currentAirport.sidCollection.findRouteByIcao(routeModel.procedure) !== 'undefined') { // it's a SID! - legs.push(new Leg({ type: FP_LEG_TYPE.SID, route: route[i] }, this)); - } else if (Object.keys(window.airportController.airport_get().stars).indexOf(pieces[1]) > -1) { + const legToAdd = new Leg({ type: FP_LEG_TYPE.SID, route: routeModel.routeString }, this); + + legs.push(legToAdd); + } else if (typeof currentAirport.starCollection.findRouteByIcao(routeModel.procedure) !== 'undefined') { // it's a STAR! - legs.push(new Leg({ type: FP_LEG_TYPE.STAR, route: route[i] }, this)); - } else if (Object.keys(window.airportController.airport_get().airways).indexOf(pieces[1]) > -1) { + const legToAdd = new Leg({ type: FP_LEG_TYPE.STAR, route: routeModel.routeString }, this); + + legs.push(legToAdd); + } else if (Object.keys(window.airportController.airport_get().airways).indexOf(routeModel.procedure) > -1) { // it's an airway! - legs.push(new Leg({ type: FP_LEG_TYPE.AWY, route: route[i] }, this)); + const legToAdd = new Leg({ type: FP_LEG_TYPE.AWY, route: routeModel.routeString }, this); + + legs.push(legToAdd); } } else { // neither formatted like "JAN" nor "JAN.V18.MLU" @@ -601,8 +615,8 @@ export default class AircraftFlightManagementSystem { } } - // TODO: this could be simplified. there is a lot of brnaching logic here that makes this block - // tough to follow. + // TODO: this should be its own method + // TODO: this could be simplified. there is a lot of brnaching logic here that makes this block tough to follow. // insert user's route to the legs if (!fullRouteClearance) { // Check if user's route hooks up to the current Legs anywhere @@ -637,6 +651,7 @@ export default class AircraftFlightManagementSystem { this.nextLeg(); } } else { + // TODO: move up and return early // replace all legs with the legs we've built here in this function this.legs = legs; this.current = [0, 0]; // look to beginning of route diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index a435c02f..90a394d0 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -44,43 +44,6 @@ import { } from '../constants/aircraftConstants'; import { SELECTORS } from '../constants/selectors'; -// TODO: these constants don't belong in this class. they should probably live on their own in a different file. -// /** -// * @property FLIGHT_MODES -// * @type {Object} -// * @final -// */ -// export const FLIGHT_MODES = { -// APRON: 'apron', -// TAXI: 'taxi', -// WAITING: 'waiting', -// TAKEOFF: 'takeoff', -// CRUISE: 'cruise', -// LANDING: 'landing' -// }; -// -// /** -// * @property FLIGHT_CATEGORY -// * @type {Object} -// * @final -// */ -// export const FLIGHT_CATEGORY = { -// ARRIVAL: 'arrival', -// DEPARTURE: 'departure' -// }; -// -// /** -// * @property WAYPOINT_NAV_MODE -// * @type {Object} -// * @final -// */ -// export const WAYPOINT_NAV_MODE = { -// FIX: 'fix', -// HEADING: 'heading', -// HOLD: 'hold', -// RWY: 'rwy' -// }; - /** * Enum of commands and thier corresponding function. * diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index d2bf3c1d..2ed77eac 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -1,10 +1,13 @@ /* eslint-disable no-multi-spaces, no-undef */ import _forEach from 'lodash/forEach'; import _get from 'lodash/get'; -import _has from 'lodash/has'; +import _head from 'lodash/head'; +import FixCollection from '../airport/Fix/FixCollection'; +import { WAYPOINT_NAV_MODE } from '../constants/aircraftConstants'; /** * Build a waypoint object + * * Note that .prependLeg() or .appendLeg() or .insertLeg() * should be called in order to add waypoints to the fms, based on which * you want. This function serves only to build the waypoint object; it is @@ -15,17 +18,21 @@ import _has from 'lodash/has'; export default class Waypoint { /** * Initialize Waypoint with empty values, then call the parser + * + * @for Waypoint + * @constructor */ constructor(data = {}, fms) { this.altitude = null; - this.fix = null; - this.navmode = null; - this.heading = null; - this.turn = null; + this.fix = null; + this.navmode = null; + this.heading = null; + this.turn = null; this.location = null; this.expedite = false; - this.speed = null; - this.hold = { + this.speed = null; + + this.hold = { dirTurns: null, fixName: null, fixPos: null, @@ -33,6 +40,7 @@ export default class Waypoint { legLength: null, timer: 0 }; + this.fixRestrictions = { alt: null, spd: null @@ -45,33 +53,33 @@ export default class Waypoint { /** * Parse input data and apply to this waypoint + * + * @for Waypoint + * @method parse */ parse(data, fms) { + this.route = _get(data, 'route', this.route); + this.fixRestrictions = _get(data, 'fixRestrictions', this.fixRestrictions); + // Populate Waypoint with data if (data.fix) { this.navmode = 'fix'; this.fix = data.fix; - this.location = window.airportController.airport_get().getFixPosition(data.fix); + this.location = FixCollection.getFixPositionCoordinates(data.fix); } - this.route = _get(data, 'route', this.route); - - _forEach(data, (value, key) => { - if (_has(this, key)) { - console.log(key); - // this[key] = data[key]; - } - }); - - // for aircraft that don't yet have proper guidance (eg SID/STAR, for example) + // for aircraft that don't yet have proper guidance (eg: SID/STAR, or departing aircraft) if (!this.navmode) { - this.navmode = 'heading'; - const apt = window.airportController.airport_get(); + this.navmode = WAYPOINT_NAV_MODE.HEADING; + const airport = window.airportController.airport_get(); + const firstRouteSegment = _head(this.route.split('.')); - if (this.route.split('.')[0] === apt.icao && this.heading === null) { + if (firstRouteSegment === airport.icao && this.heading === null) { // aim departure along runway heading - this.heading = apt.getRunway(apt.runway).angle; - } else if (this.route.split('.')[0] === 'KDBG' && this.heading === null) { + const { angle } = airport.getRunway(airport.runway); + + this.heading = angle; + } else if (firstRouteSegment === 'KDBG' && this.heading === null) { // aim arrival @ middle of airspace this.heading = this.radial + Math.PI; } diff --git a/src/assets/scripts/airport/Route/RouteModel.js b/src/assets/scripts/airport/Route/RouteModel.js index 730f83ac..8aa1a37a 100644 --- a/src/assets/scripts/airport/Route/RouteModel.js +++ b/src/assets/scripts/airport/Route/RouteModel.js @@ -18,12 +18,22 @@ const SEGMENT_SEPARATION_SYMBOL = '.'; */ const MAXIMUM_ROUTE_SEGMENT_LENGTH = 3; +// TODO: this class needs a better name /** * @class RouteModel */ export default class RouteModel { /** + * example `routeString` + * + * ``` * 'BETHL.GRNPA1.KLAS' + * ``` + * + * // TODO: should be able to support input of: + * - KSFO.OFFSH9.SXC.V458.IPL.J2.JCT..LLO..ACT..KACT + * which can be returned as: + * - ['KSFO.OFFSH9.SXC', 'SXC.V458.IPL', 'IPL.J2.JCT', 'LLO', 'ACT', 'KACT'] * * @for RouteModel * @constructor From f93adc34e387f46b954274da0c4ba35de4ddf922 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sat, 5 Nov 2016 11:44:45 -0500 Subject: [PATCH 06/25] feature/ATC-53 - Adds docblocs to Leg * Adds aircraftConstants to Leg * Adds new method to get waypoint models for sid in AirportModel --- .../AircraftFlightManagementSystem.js | 3 +- src/assets/scripts/aircraft/Leg.js | 146 +++++++++++------- src/assets/scripts/airport/AirportModel.js | 16 ++ .../scripts/constants/aircraftConstants.js | 16 ++ 4 files changed, 122 insertions(+), 59 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index ca33da6b..71694349 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -2,9 +2,10 @@ import $ from 'jquery'; import _last from 'lodash/last'; import _map from 'lodash/map'; import Waypoint from './Waypoint'; -import Leg, { FP_LEG_TYPE } from './Leg'; +import Leg from './Leg'; import RouteModel from '../airport/Route/RouteModel'; import { clamp } from '../math/core'; +import { FP_LEG_TYPE } from '../constants/aircraftConstants'; import { LOG } from '../constants/logLevel'; /** diff --git a/src/assets/scripts/aircraft/Leg.js b/src/assets/scripts/aircraft/Leg.js index f665d7bf..54736fdb 100644 --- a/src/assets/scripts/aircraft/Leg.js +++ b/src/assets/scripts/aircraft/Leg.js @@ -2,61 +2,65 @@ import _get from 'lodash/get'; import _has from 'lodash/has'; import _map from 'lodash/map'; import Waypoint from './Waypoint'; +import RouteModel from '../airport/Route/RouteModel'; +import { FP_LEG_TYPE } from '../constants/aircraftConstants'; import { LOG } from '../constants/logLevel'; -// can be 'sid', 'star', 'iap', 'awy', 'fix', '[manual]' -/** - * Enumeration of possibl FLight Plan Leg types. - * - * @property FP_LEG_TYPE - * @type {Object} - * @final - */ -export const FP_LEG_TYPE = { - SID: 'sid', - STAR: 'star', - IAP: 'iap', - AWY: 'awy', - FIX: 'fix', - MANUAL: '[manual]' -}; - /** * Build a 'leg' of the route (contains series of waypoints) * - * @param {object} data = {route: "KSFO.OFFSH9.SXC", either a fix, or with format 'start.procedure.end', or - * "[RNAV/GPS]" for custom positions - * type: "sid", can be 'sid', 'star', 'iap', 'awy', 'fix' - * firstIndex: 0} the position in fms.legs to insert this leg */ export default class Leg { /** + * @param {object} route: "KSFO.OFFSH9.SXC", either a fix, or with format 'start.procedure.end', or + * "[RNAV/GPS]" for custom positions + * type: "sid", can be 'sid', 'star', 'iap', 'awy', 'fix' + * firstIndex: 0 the position (index) in fms.legs to insert this leg + * + * @for Leg * @constructor + * @param data */ constructor(data = {}, fms) { - this.route = ''; // eg 'KSFO.OFFSH9.SXC' or 'FAITH' + /** + * + * + * - 'KSFO.OFFSH9.SXC' + * - 'FAITH' + * + * @property route + * @type {string} + * @default '' + */ + this.route = ''; + + /** + * @property type + * @type {string} + * @default '' + */ this.type = ''; - // TODO: possibly implement as waypointCollection - this.waypoints = []; // an array of zlsa.atc.Waypoint objects to follow - - // // Fill data with default Leg properties if they aren't specified (prevents wp constructor from getting confused) - // if (!data.route) { - // data.route = this.route; - // } - // - // if (!data.type) { - // data.type = this.type; - // } - // - // if (!data.waypoints) { - // data.waypoints = this.waypoints; - // } + + // TODO: possibly implement as a waypointCollection + /** + * Waypoint objects to follow + * + * @property waypoints + * @type {Array} + * @default [] + */ + this.waypoints = []; this.parse(data, fms); } /** * Parse input data and apply to this leg + * + * @for Leg + * @method parse + * @param data {object} + * @param fms {AircraftFlightManagementSystem} */ parse(data, fms) { this.route = _get(data, 'route', '[radar vectors]'); @@ -65,15 +69,16 @@ export default class Leg { if (this.waypoints.length === 0) { this.generateWaypoints(data, fms); - - if (this.waypoints.length === 0) { - this.waypoints = [new Waypoint({ route: '' }, fms)]; - } } } /** - * Adds Waypoint objects to this Leg, based on the route & type + * Adds Waypoint objects to this Leg based on the route type + * + * @for Leg + * @method generateWaypoints + * @param data {object} + * @param fms {AircraftFlightManagementSystem} */ generateWaypoints(data, fms) { if (!this.type) { @@ -82,10 +87,14 @@ export default class Leg { switch (this.type) { case FP_LEG_TYPE.SID: + // TODO: this is gross. we instantiate route with a string and new mutate it here to a RouteModel. + this.route = new RouteModel(data.route); this._generateWaypointsForSid(data, fms); break; case FP_LEG_TYPE.STAR: + // TODO: this is gross. we instantiate route with a string and new mutate it here to a RouteModel. + this.route = new RouteModel(data.route); this._generateWaypointsForStar(data, fms); break; @@ -95,6 +104,8 @@ export default class Leg { break; case FP_LEG_TYPE.AWY: + // TODO: this is gross. we instantiate route with a string and new mutate it here to a RouteModel. + this.route = new RouteModel(data.route); this._generateWaypointsForAirway(data, fms); break; @@ -102,11 +113,14 @@ export default class Leg { this._generateWaypointForFix(fms); break; - default: + case FP_LEG_TYPE.MANUAL: this._generateManualWaypoint(fms); break; + default: + this._generateEmptyWaypoint(fms); + break; } } @@ -117,11 +131,11 @@ export default class Leg { return; } - const apt = this.route.split('.')[0]; - const sid = this.route.split('.')[1]; - const exit = this.route.split('.')[2]; - const rwy = fms.my_aircraft.rwy_dep; this.waypoints = []; + // const apt = this.route.split('.')[0]; + // const sid = this.route.split('.')[1]; + // const exit = this.route.split('.')[2]; + const rwy = fms.my_aircraft.rwy_dep; // Generate the waypoints if (!rwy) { @@ -134,7 +148,10 @@ export default class Leg { return; } - const pairs = window.airportController.airport_get(apt).getSID(sid, exit, rwy); + const airport = window.airportController.airport_get(this.route.entry); + const pairs = airport.getSID(this.route.procedure, this.route.exit, rwy); + const sid = airport.findWaypointModelsForSid(this.route.procedure, this.route.exit, rwy); + // Remove the placeholder leg (if present) if (fms.my_aircraft.wow() && fms.legs.length > 0 @@ -146,8 +163,11 @@ export default class Leg { // for each fix/restr pair for (let i = 0; i < pairs.length; i++) { + // fix const f = pairs[i][0]; + // altitude let a = null; + // speed let s = null; if (pairs[i][1]) { @@ -187,14 +207,14 @@ export default class Leg { return; } - const entry = this.route.split('.')[0]; - const star = this.route.split('.')[1]; - const apt = this.route.split('.')[2]; - const rwy = fms.my_aircraft.rwy_arr; this.waypoints = []; - - // Generate the waypoints - const pairs = window.airportController.airport_get(apt).getSTAR(star, entry, rwy); + // const entry = this.route.split('.')[0]; + // const star = this.route.split('.')[1]; + // const apt = this.route.split('.')[2]; + const rwy = fms.my_aircraft.rwy_arr; + const airport = window.airportController.airport_get(this.route.exit); + const pairs = airport.getSTAR(this.route.procedure, this.route.entry, rwy); + const star = airport.findWaypointModelsForStar(this.route.procedure, this.route.entry, rwy); // Create a new WaypointModel for each fix found in the Star. @@ -275,17 +295,27 @@ export default class Leg { // Add list of fixes to this.waypoints this.waypoints = []; - this.waypoints = _map(fixes, (f) => new Waypoint({ fix: f }, fms)); + this.waypoints = _map(fixes, (fix) => new Waypoint({ fix }, fms)); } _generateWaypointForFix(fms) { this.waypoints = []; - this.waypoints.push(new Waypoint({ fix: this.route }, fms)); + const waypointToAdd = new Waypoint({ fix: this.route }, fms); + + this.waypoints.push(waypointToAdd); } _generateManualWaypoint(fms) { - this.waypoints.push(new Waypoint(this.route, fms)); + const waypointToAdd = new Waypoint(this.route, fms); + + this.waypoints.push(waypointToAdd); + } + + _generateEmptyWaypoint(fms) { + const waypointToAdd = new Waypoint({ route: '' }, fms); + + this.waypoints = [waypointToAdd]; } } diff --git a/src/assets/scripts/airport/AirportModel.js b/src/assets/scripts/airport/AirportModel.js index 417cd475..c1aa5856 100644 --- a/src/assets/scripts/airport/AirportModel.js +++ b/src/assets/scripts/airport/AirportModel.js @@ -649,6 +649,7 @@ export default class AirportModel { * @return {array} */ getFixPosition(fixName) { + // TODO: if possible, replace with FoxCollection.getFixPositionCoordinates const fixModel = FixCollection.findFixByName(fixName); return fixModel.position; @@ -665,6 +666,20 @@ export default class AirportModel { return this.sidCollection.findFixesForSidByRunwayAndExit(id, exit, runway); } + /** + * + * @for AirportModel + * @method findWaypointModelsForSid + * @param id {string} + * @param entry {string} + * @param runway {string} + * @param isPreSpawn {boolean} flag used to determine if distances between waypoints should be calculated + * @return {array} + */ + findWaypointModelsForSid(id, entry, runway, isPreSpawn = false) { + return this.sidCollection.findFixModelsForRouteByEntryAndExit(id, entry, runway, isPreSpawn); + } + /** * @for AirportModel * @method getSIDExitPoint @@ -715,6 +730,7 @@ export default class AirportModel { /** * * @for AirportModel + * @method findWaypointModelsForStar * @param id {string} * @param entry {string} * @param runway {string} diff --git a/src/assets/scripts/constants/aircraftConstants.js b/src/assets/scripts/constants/aircraftConstants.js index 5c6d3c2c..394c7658 100644 --- a/src/assets/scripts/constants/aircraftConstants.js +++ b/src/assets/scripts/constants/aircraftConstants.js @@ -33,3 +33,19 @@ export const WAYPOINT_NAV_MODE = { HOLD: 'hold', RWY: 'rwy' }; + +/** + * Enumeration of possible FLight Plan Leg types. + * + * @property FP_LEG_TYPE + * @type {Object} + * @final + */ +export const FP_LEG_TYPE = { + SID: 'sid', + STAR: 'star', + IAP: 'iap', + AWY: 'awy', + FIX: 'fix', + MANUAL: '[manual]' +}; From cf98892ebc285801316b0e2d74e12dc9dd380870 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sat, 5 Nov 2016 22:19:41 -0500 Subject: [PATCH 07/25] feature/ATC-53 - Adds restriction parsing to StandardRouteWaypointModel --- .../StandardRouteWaypointModel.js | 84 +++++++++++++++++-- .../StandardRouteWaypointModel.spec.js | 42 +++++++++- 2 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js index e27d2bbf..694e9caa 100644 --- a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js +++ b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js @@ -1,4 +1,7 @@ import FixCollection from '../Fix/FixCollection'; +import _head from 'lodash/head'; +import _last from 'lodash/last'; +import _isNil from 'lodash/isNil'; import _uniqId from 'lodash/uniqueId'; /** @@ -15,6 +18,27 @@ const NAME_INDEX = 0; */ const RESTRICTION_INDEX = 1; +/** + * @property RESTRICTION_SEPARATOR + * @type {string} + * @final + */ +const RESTRICTION_SEPARATOR = '|'; + +/** + * @property ALTITUDE_RESTRICTION_PREFIX + * @type {string} + * @final + */ +const ALTITUDE_RESTRICTION_PREFIX = 'A'; + +/** + * @property SPEED_RESTRICTION_PREFIX + * @type {string} + * @final + */ +const SPEED_RESTRICTION_PREFIX = 'S'; + /** * A route waypoint describes a `fixName` and any altitude or speed restrictions for that fix. * @@ -104,12 +128,12 @@ export default class StandardRouteWaypointModel { * * Speed constraint, if any, for a fix. * - * @property _speedConstraint (optional) + * @property _speed (optional) * @type {string} * @default -1 * @private */ - this._speedConstraint = -1; + this._speed = -1; /** * Positon information for the current waypoint @@ -229,7 +253,7 @@ export default class StandardRouteWaypointModel { this._restrictions = null; this._alititude = -1000; this._alititudeConstraint = ''; - this._speedConstraint = -1; + this._speed = -1; return this; } @@ -257,14 +281,12 @@ export default class StandardRouteWaypointModel { } /** - * NOT IN USE - * // TODO: implement this method. altitude and speed should be parsed into real numbers so - * they can be used elsewhere in the app. + * Parse any waypoint restrictions * * Parse a single string into: * - `this._alititude` = expressed in feet * - `this._alititudeConstraint` = {BELOW|AT|ABOVE} - * - `this._speedConstraint` = expressed in kts + * - `this._speed` = expressed in kts * * Exapmles: * - "A80+|S210" @@ -278,6 +300,52 @@ export default class StandardRouteWaypointModel { * @private */ _parseWaypointRestrictions(waypointRestrictions) { - return this; + if (_isNil(waypointRestrictions)) { + return; + } + + const restrictionPieces = this._extractRestrictionPieces(waypointRestrictions); + + for (let i = 0; i < restrictionPieces.length; i++) { + const restriction = restrictionPieces[i]; + + // looking at the first letter of a restrictionPiece here. + if (restriction[0] === ALTITUDE_RESTRICTION_PREFIX) { + this._setAltitudeRestriction(restriction); + } else if (restriction[0] === SPEED_RESTRICTION_PREFIX) { + this._setSpeedRestriction(restriction); + } + } + } + + /** + * @for StandardRouteWaypointModel + * @method _setAltitudeRestriction + * @param altitudeRestriction {string} + * @private + */ + _setAltitudeRestriction(altitudeRestriction) { + this._alititude = altitudeRestriction.substr(1); + } + + /** + * @for StandardRouteWaypointModel + * @method _setSpeedRestriction + * @param speedRestriction {string} + * @private + */ + _setSpeedRestriction(speedRestriction) { + this._speed = speedRestriction.substr(1); + } + + /** + * @for StandardRouteWaypointModel + * @method _extractRestrictionPieces + * @param waypointRestrictions {array} + * @@return {string} + * @private + */ + _extractRestrictionPieces(waypointRestrictions) { + return waypointRestrictions.split(RESTRICTION_SEPARATOR); } } diff --git a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js index e197f2a8..40c0959e 100644 --- a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js +++ b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js @@ -30,7 +30,7 @@ ava('sets only `name` when provided a string', t => { t.true(model.name === NAME_MOCK); t.true(model._alititude === -1000); t.true(model._alititudeConstraint === ''); - t.true(model._speedConstraint === -1); + t.true(model._speed === -1) }); ava('.clonePoisitonFromFix() does not throw when no fix exists', t => { @@ -56,3 +56,43 @@ ava('calls ._parseWaypointRestrictions() when provided and array', t => { t.true(spy.callCount === 1); }); + +ava('._parseWaypointRestrictions() extracts alititude and speed restrictions from a waypointRestrictions string', t => { + const model = new StandardRouteWaypointModel(ROUTE_WAYPOINT_MOCK); + + model._parseWaypointRestrictions(RESTRICTIONS_MOCK); + + t.true(model._alititude === '80+'); + t.true(model._speed === '250') +}); + +ava('._parseWaypointRestrictions() extracts an alititude restriction from a waypointRestrictions string by calling ._setAltitudeRestriction()', t => { + const model = new StandardRouteWaypointModel(['BAKRR', 'A80+']); + const spy = sinon.spy(model, '_setAltitudeRestriction'); + + model._parseWaypointRestrictions('A80+'); + + t.true(spy.callCount === 1); + t.true(model._alititude === '80+'); + t.true(model._speed === -1); +}); + +ava('._parseWaypointRestrictions() extracts a speed restriction from a waypointRestrictions string by calling ._setSpeedRestriction()', t => { + const model = new StandardRouteWaypointModel(['BAKRR', 'S280']); + const spy = sinon.spy(model, '_setSpeedRestriction'); + + model._parseWaypointRestrictions('XYZ|S280'); + + t.true(spy.callCount === 1); + t.true(model._alititude === -1000); + t.true(model._speed === '280') +}); + +ava('._parseWaypointRestrictions() returns early if no paramater is received', t => { + const model = new StandardRouteWaypointModel(['BAKRR']); + + model._parseWaypointRestrictions(); + + t.true(model._alititude === -1000); + t.true(model._speed === -1) +}); From c2a945f33edddffb4d822299a28b2a61501dc42e Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 6 Nov 2016 09:41:16 -0600 Subject: [PATCH 08/25] feature/ATC-53 - Adds .gemerateFmsWaypoint() to StandardWaypointModel. --- .../StandardRouteWaypointModel.js | 46 +++++++++++++------ .../StandardRouteWaypointModel.spec.js | 30 ++++++++---- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js index 694e9caa..c1a35c47 100644 --- a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js +++ b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js @@ -1,8 +1,9 @@ -import FixCollection from '../Fix/FixCollection'; import _head from 'lodash/head'; import _last from 'lodash/last'; import _isNil from 'lodash/isNil'; import _uniqId from 'lodash/uniqueId'; +import FixCollection from '../Fix/FixCollection'; +import Waypoint from '../../aircraft/Waypoint'; /** * @property NAME_INDEX @@ -103,12 +104,12 @@ export default class StandardRouteWaypointModel { * * Required altitude for a fix * - * @property _alititude (optional) + * @property _altitude (optional) * @type {number} - * @default -1000 + * @default null * @private */ - this._alititude = -1000; + this._altitude = null; // TODO: This will need to be implemented in the future as an emuneration. Something to the effect of: {BELOW|AT|ABOVE} /** @@ -116,12 +117,12 @@ export default class StandardRouteWaypointModel { * * Altitude constraints, if any, for a fix. * - * @property _alititudeConstraint (options) + * @property _altitudeConstraint (options) * @type {string} * @default '' * @private */ - this._alititudeConstraint = ''; + this._altitudeConstraint = ''; /** * NOT IN USE @@ -130,10 +131,10 @@ export default class StandardRouteWaypointModel { * * @property _speed (optional) * @type {string} - * @default -1 + * @default null * @private */ - this._speed = -1; + this._speed = null; /** * Positon information for the current waypoint @@ -251,9 +252,9 @@ export default class StandardRouteWaypointModel { this._id = ''; this.name = ''; this._restrictions = null; - this._alititude = -1000; - this._alititudeConstraint = ''; - this._speed = -1; + this._altitude = null; + this._altitudeConstraint = ''; + this._speed = null; return this; } @@ -280,12 +281,29 @@ export default class StandardRouteWaypointModel { return this; } + /** + * @for StandardRouteWaypointModel + * @method generateFmsWaypoint + * @return {Waypoint} + */ + generateFmsWaypoint(fms) { + const fmsWaypoint = { + fix: this.name, + fixRestrictions: { + alt: this._altitude, + spd: this._speed + } + } + + return new Waypoint(fmsWaypoint, fms); + } + /** * Parse any waypoint restrictions * * Parse a single string into: - * - `this._alititude` = expressed in feet - * - `this._alititudeConstraint` = {BELOW|AT|ABOVE} + * - `this._altitude` = expressed in feet + * - `this._altitudeConstraint` = {BELOW|AT|ABOVE} * - `this._speed` = expressed in kts * * Exapmles: @@ -325,7 +343,7 @@ export default class StandardRouteWaypointModel { * @private */ _setAltitudeRestriction(altitudeRestriction) { - this._alititude = altitudeRestriction.substr(1); + this._altitude = altitudeRestriction.substr(1); } /** diff --git a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js index 40c0959e..7bb4b24b 100644 --- a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js +++ b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js @@ -4,6 +4,7 @@ import sinon from 'sinon'; import StandardRouteWaypointModel from '../../../src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel'; import FixCollection from '../../../src/assets/scripts/airport/Fix/FixCollection'; +import Waypoint from '../../../src/assets/scripts/aircraft/Waypoint'; import { airportPositionFixture } from '../../fixtures/airportFixtures'; import { FIX_LIST_MOCK } from '../Fix/_mocks/fixMocks'; @@ -28,9 +29,9 @@ ava('sets only `name` when provided a string', t => { t.true(typeof model._id === 'string'); t.true(model.name === NAME_MOCK); - t.true(model._alititude === -1000); - t.true(model._alititudeConstraint === ''); - t.true(model._speed === -1) + t.true(model._altitude === null); + t.true(model._altitudeConstraint === ''); + t.true(model._speed === null) }); ava('.clonePoisitonFromFix() does not throw when no fix exists', t => { @@ -57,12 +58,23 @@ ava('calls ._parseWaypointRestrictions() when provided and array', t => { t.true(spy.callCount === 1); }); +ava('.generateFmsWaypoint() returns a new instance of an FMS Waypoint object', t => { + const fmsMock = {}; + const model = new StandardRouteWaypointModel(ROUTE_WAYPOINT_MOCK); + const result = model.generateFmsWaypoint(fmsMock); + + t.true(result instanceof Waypoint); + t.true(model.name === result.fix); + t.true(model._altitude === result.fixRestrictions.alt); + t.true(model._speed === result.fixRestrictions.spd); +}); + ava('._parseWaypointRestrictions() extracts alititude and speed restrictions from a waypointRestrictions string', t => { const model = new StandardRouteWaypointModel(ROUTE_WAYPOINT_MOCK); model._parseWaypointRestrictions(RESTRICTIONS_MOCK); - t.true(model._alititude === '80+'); + t.true(model._altitude === '80+'); t.true(model._speed === '250') }); @@ -73,8 +85,8 @@ ava('._parseWaypointRestrictions() extracts an alititude restriction from a wayp model._parseWaypointRestrictions('A80+'); t.true(spy.callCount === 1); - t.true(model._alititude === '80+'); - t.true(model._speed === -1); + t.true(model._altitude === '80+'); + t.true(model._speed === null); }); ava('._parseWaypointRestrictions() extracts a speed restriction from a waypointRestrictions string by calling ._setSpeedRestriction()', t => { @@ -84,7 +96,7 @@ ava('._parseWaypointRestrictions() extracts a speed restriction from a waypointR model._parseWaypointRestrictions('XYZ|S280'); t.true(spy.callCount === 1); - t.true(model._alititude === -1000); + t.true(model._altitude === null); t.true(model._speed === '280') }); @@ -93,6 +105,6 @@ ava('._parseWaypointRestrictions() returns early if no paramater is received', t model._parseWaypointRestrictions(); - t.true(model._alititude === -1000); - t.true(model._speed === -1) + t.true(model._altitude === null); + t.true(model._speed === null) }); From f7565395186534c182ff11b8d60256fc7d70979e Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 6 Nov 2016 09:41:37 -0600 Subject: [PATCH 09/25] feature/ATC-53 - Updates Leg to use the RouteModel and StandardRouteCollections when dealing with sids and stars. * Removes inline string transformation logic from Leg --- .../AircraftFlightManagementSystem.js | 12 +- src/assets/scripts/aircraft/Leg.js | 108 ++++++------------ 2 files changed, 38 insertions(+), 82 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 71694349..44d07dae 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -333,16 +333,16 @@ export default class AircraftFlightManagementSystem { case FP_LEG_TYPE.SID: // TODO: this split logic and string building should live in a helper function or or class method // departure airport - flightPlanRoute.push(leg.route.split('.')[0]); + flightPlanRoute.push(leg.route.entry); // 'sidname.exitPoint' - flightPlanRoute.push(leg.route.split('.')[1] + '.' + leg.route.split('.')[2]); + flightPlanRoute.push(`${leg.route.procedure}.${leg.route.exit}`); break; case FP_LEG_TYPE.STAR: // 'entryPoint.starname.exitPoint' - flightPlanRoute.push(leg.route.split('.')[0] + '.' + leg.route.split('.')[1]); + flightPlanRoute.push(`${leg.route.entry}.${leg.route.procedure}`); // arrival airport - flightPlanRoute.push(leg.route.split('.')[2]); + flightPlanRoute.push(leg.route.exit); break; case FP_LEG_TYPE.IAP: @@ -388,10 +388,10 @@ export default class AircraftFlightManagementSystem { // tODO replace the string splitting with the `RouteModel` switch (leg.type) { case FP_LEG_TYPE.SID: - this.following.sid = leg.route.split('.')[1]; + this.following.sid = leg.route.procedure; break; case FP_LEG_TYPE.STAR: - this.following.star = leg.route.split('.')[1]; + this.following.star = leg.route.procedure; break; case FP_LEG_TYPE.IAP: // *******NEEDS TO BE FINISHED*************************** diff --git a/src/assets/scripts/aircraft/Leg.js b/src/assets/scripts/aircraft/Leg.js index 54736fdb..3630fd78 100644 --- a/src/assets/scripts/aircraft/Leg.js +++ b/src/assets/scripts/aircraft/Leg.js @@ -63,6 +63,7 @@ export default class Leg { * @param fms {AircraftFlightManagementSystem} */ parse(data, fms) { + // TODO: move radar vectors to constants file this.route = _get(data, 'route', '[radar vectors]'); this.type = _get(data, 'type', FP_LEG_TYPE.MANUAL); this.waypoints = _get(data, 'waypoints', []); @@ -131,15 +132,13 @@ export default class Leg { return; } - this.waypoints = []; - // const apt = this.route.split('.')[0]; - // const sid = this.route.split('.')[1]; - // const exit = this.route.split('.')[2]; + this._resetWaypoints(); + const rwy = fms.my_aircraft.rwy_dep; - // Generate the waypoints if (!rwy) { const isWarning = true; + window.uiController.ui_log( `${fms.my_aircraft.getCallsign()} unable to fly SID, we haven't been assigned a departure runway!`, isWarning @@ -150,48 +149,21 @@ export default class Leg { const airport = window.airportController.airport_get(this.route.entry); const pairs = airport.getSID(this.route.procedure, this.route.exit, rwy); - const sid = airport.findWaypointModelsForSid(this.route.procedure, this.route.exit, rwy); - + const waypointsForSid = airport.findWaypointModelsForSid(this.route.procedure, this.route.exit, rwy); + // TODO: refactor this if // Remove the placeholder leg (if present) if (fms.my_aircraft.wow() && fms.legs.length > 0 - && fms.legs[0].route === window.airportController.airport_get().icao && pairs.length > 0 + && fms.legs[0].route === airport.icao && pairs.length > 0 ) { // remove the placeholder leg, to be replaced below with SID Leg fms.legs.splice(0, 1); } - // for each fix/restr pair - for (let i = 0; i < pairs.length; i++) { - // fix - const f = pairs[i][0]; - // altitude - let a = null; - // speed - let s = null; - - if (pairs[i][1]) { - const a_n_s = pairs[i][1].toUpperCase().split('|'); - - for (const j in a_n_s) { - if (a_n_s[j][0] === 'A') { - a = a_n_s[j].substr(1); - } else if (a_n_s[j][0] === 'S') { - s = a_n_s[j].substr(1); - } - } - } + for (let i = 0; i < waypointsForSid.length; i++) { + const waypointToAdd = waypointsForSid[i].generateFmsWaypoint(fms); - this.waypoints.push(new Waypoint( - { - fix: f, - fixRestrictions: { - alt: a, - spd: s - } - }, - fms - )); + this.waypoints.push(waypointToAdd); } if (!this.waypoints[0].speed) { @@ -199,7 +171,6 @@ export default class Leg { } } - _generateWaypointsForStar(data, fms) { if (!fms) { log('Attempted to generate waypoints for STAR, but cannot because fms ref not passed!', LOG.WARNING); @@ -207,45 +178,16 @@ export default class Leg { return; } - this.waypoints = []; - // const entry = this.route.split('.')[0]; - // const star = this.route.split('.')[1]; - // const apt = this.route.split('.')[2]; + this._resetWaypoints(); + const rwy = fms.my_aircraft.rwy_arr; const airport = window.airportController.airport_get(this.route.exit); - const pairs = airport.getSTAR(this.route.procedure, this.route.entry, rwy); - const star = airport.findWaypointModelsForStar(this.route.procedure, this.route.entry, rwy); - - // Create a new WaypointModel for each fix found in the Star. - - // for each fix/restr pair - for (let i = 0; i < pairs.length; i++) { - const f = pairs[i][0]; - let a = null; - let s = null; - - if (pairs[i][1]) { - const a_n_s = pairs[i][1].toUpperCase().split('|'); - - for (const j in a_n_s) { - if (a_n_s[j][0] === 'A') { - a = a_n_s[j].substr(1); - } else if (a_n_s[j][0] === 'S') { - s = a_n_s[j].substr(1); - } - } - } + const waypointsForStar = airport.findWaypointModelsForStar(this.route.procedure, this.route.entry, rwy); + + for (let i = 0; i < waypointsForStar.length; i++) { + const waypointToAdd = waypointsForStar[i].generateFmsWaypoint(fms); - this.waypoints.push(new Waypoint( - { - fix: f, - fixRestrictions: { - alt: a, - spd: s - } - }, - fms - )); + this.waypoints.push(waypointToAdd); } if (!this.waypoints[0].speed) { @@ -294,7 +236,7 @@ export default class Leg { } // Add list of fixes to this.waypoints - this.waypoints = []; + this._resetWaypoints(); this.waypoints = _map(fixes, (fix) => new Waypoint({ fix }, fms)); } @@ -318,4 +260,18 @@ export default class Leg { this.waypoints = [waypointToAdd]; } + + /** + * Reset the waypoint property to an empty array. + * + * Provides a single method that encapsulates common functionality that + * can be used throughout the class. + * + * @for Leg + * @method _resetWaypoints + * @private + */ + _resetWaypoints() { + this.waypoints = []; + } } From 2d7349c326c4c69d055471ba22724b99246e01fa Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 6 Nov 2016 20:19:51 -0600 Subject: [PATCH 10/25] feature/ATC-53 - Updates FixCollection.findFixByName() to uppercase the passed fixName before searching. --- src/assets/scripts/airport/Fix/FixCollection.js | 2 +- test/airport/fix/FixCollection.spec.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/assets/scripts/airport/Fix/FixCollection.js b/src/assets/scripts/airport/Fix/FixCollection.js index 5c35c989..5b73db53 100644 --- a/src/assets/scripts/airport/Fix/FixCollection.js +++ b/src/assets/scripts/airport/Fix/FixCollection.js @@ -118,7 +118,7 @@ class FixCollection { * @return {FixModel|null} */ findFixByName(fixName) { - const fixModel = _find(this._items, { name: fixName }); + const fixModel = _find(this._items, { name: fixName.toUpperCase() }); // if a fix is not found, _find() returns `undefined` so we specifically return null here if a fix is not found return fixModel || null; diff --git a/test/airport/fix/FixCollection.spec.js b/test/airport/fix/FixCollection.spec.js index 5212efc9..cc4e1462 100644 --- a/test/airport/fix/FixCollection.spec.js +++ b/test/airport/fix/FixCollection.spec.js @@ -33,6 +33,13 @@ ava.serial('.findFixByName() returns a FixModel if it exists within the collecti t.true(result instanceof FixModel); }); +ava.serial('.findFixByName() returns a FixModel if it is passed as lowerCase and exists within the collection', t => { + const result = FixCollection.findFixByName('bakrr'); + + t.true(result.name === 'BAKRR'); + t.true(result instanceof FixModel); +}); + ava.serial('.findFixByName() returns null if a FixModel does not exist within the collection', t => { const result = FixCollection.findFixByName(''); From 26a1b4d564cbdaebf0272fbc0371ec6582556f83 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 6 Nov 2016 22:26:06 -0600 Subject: [PATCH 11/25] feature/ATC-53 - Begins implementing sid and starCollection with RouteModel within the AircraftInstanceModel - Adds doc blocks to Leg - Adds new methods to Leg to reset the current waypoint array and for adding a new Waypoint to the waypoint array. --- .../AircraftFlightManagementSystem.js | 23 ++-- .../scripts/aircraft/AircraftInstanceModel.js | 75 +++++++------ src/assets/scripts/aircraft/Leg.js | 103 ++++++++++++++---- 3 files changed, 130 insertions(+), 71 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 44d07dae..defe35b3 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -58,15 +58,20 @@ const WAYPOINT_WITHIN_LEG = 1; export default class AircraftFlightManagementSystem { constructor(options) { this.my_aircrafts_eid = options.aircraft.eid; + // TODO: we should remove this reference and instead supply methods that the aircraft can call via the fms this.my_aircraft = options.aircraft; + this.legs = []; + this.current = [0, 0]; // [current_Leg, current_Waypoint_within_that_Leg] + // TODO: possible model object here this.fp = { altitude: null, route: [] }; + // TODO: possible model object here this.following = { sid: null, // Standard Instrument Departure Procedure @@ -331,7 +336,6 @@ export default class AircraftFlightManagementSystem { // FIXME: replace the string splitting with the `RouteModel` class methods switch (leg.type) { case FP_LEG_TYPE.SID: - // TODO: this split logic and string building should live in a helper function or or class method // departure airport flightPlanRoute.push(leg.route.entry); // 'sidname.exitPoint' @@ -359,6 +363,7 @@ export default class AircraftFlightManagementSystem { break; case FP_LEG_TYPE.FIX: + // this is just a fixname flightPlanRoute.push(leg.route); break; @@ -758,6 +763,7 @@ export default class AircraftFlightManagementSystem { wp[i].speed = spd; } + // TODO; this completely replaces an existing Waypoint, why not just update the instance? // change fms waypoints to wp (which contains the altitudes and speeds) this.legs[this.current[LEG]].waypoints = wp; @@ -949,19 +955,6 @@ export default class AircraftFlightManagementSystem { return currentLeg.waypoints[this.current[WAYPOINT_WITHIN_LEG]]; } - /** - * Return the current waypoint - */ - // currentWaypoint() { - // if (this.legs.length < 1) { - // return null; - // } - // - // const currentLeg = this.currentLeg(); - // - // return currentLeg.waypoints[this.current[WAYPOINT_WITHIN_LEG]]; - // } - /** * Returns an array of all fixes along the flightplan route */ @@ -1047,7 +1040,7 @@ export default class AircraftFlightManagementSystem { } // TODO: use the `RouteModel` for this - return `${this.following.sid}.${this.currentLeg.route.split('.')[2]}`; + return `${this.following.sid}.${this.currentLeg.route.exit}`; } /** diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index 90a394d0..823b38d3 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -40,7 +40,8 @@ import { km, radiansToDegrees, degreesToRadians, heading_to_string } from '../ut import { FLIGHT_MODES, FLIGHT_CATEGORY, - WAYPOINT_NAV_MODE + WAYPOINT_NAV_MODE, + FP_LEG_TYPE } from '../constants/aircraftConstants'; import { SELECTORS } from '../constants/selectors'; @@ -933,66 +934,72 @@ export default class Aircraft { /** * @for AircraftInstanceModel * @method runClearedAsFiled + * @return {array} */ runClearedAsFiled() { - if (this.fms.clearedAsFiled()) { - const readback = {}; + if (!this.fms.clearedAsFiled()) { + return [true, 'unable to clear as filed']; + } - readback.log = `cleared to destination via the ${window.airportController.airport_get().sids[this.destination].icao} ` + - `departure, then as filed. Climb and maintain ${window.airportController.airport_get().initial_alt}, ` + - `expect ${this.fms.fp.altitude} 10 minutes after departure `; - readback.say = `cleared to destination via the ${window.airportController.airport_get().sids[this.destination].name} ` + - `departure, then as filed. Climb and maintain ${radio_altitude(window.airportController.airport_get().initial_alt)}, ` + - `expect ${radio_altitude(this.fms.fp.altitude)}, ${radio_spellOut(' 10 ')} minutes after departure'`; + const airport = window.airportController.airport_get(); + const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.dstination); + const readback = {}; - return ['ok', readback]; - } + readback.log = `cleared to destination via the ${this.destination} ` + + `departure, then as filed. Climb and maintain ${airport.initial_alt}, ` + + `expect ${this.fms.fp.altitude} 10 minutes after departure `; + readback.say = `cleared to destination via the ${procedureName} ` + + `departure, then as filed. Climb and maintain ${radio_altitude(airport.initial_alt)}, ` + + `expect ${radio_altitude(this.fms.fp.altitude)}, ${radio_spellOut(' 10 ')} minutes after departure'`; - return [true, 'unable to clear as filed']; + return ['ok', readback]; } /** * @for AircraftInstanceModel * @method runClimbViaSID - * @param data */ runClimbViaSID() { - let fail = false; + if (this.fms.currentLeg.type !== FP_LEG_TYPE.SID || !this.fms.climbViaSID()) { + const isWarning = true; - if (!(this.fms.currentLeg.type === 'sid')) { - fail = true; - } else if (this.fms.climbViaSID()) { - const readback = { - log: `climb via the ${this.fms.currentLeg.route.split('.')[1]} departure`, - say: `climb via the ${window.airportController.airport_get().sids[this.fms.currentLeg.route.split('.')[1]].name} departure` - }; + window.uiController.ui_log(`${this.getCallsign()} unable to climb via SID`, isWarning); - return ['ok', readback]; + return; } - if (fail) { - const isWarning = true; - window.uiController.ui_log(`${this.getCallsign()} unable to climb via SID`, isWarning); - } + const airport = window.airportController.airport_get(); + const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.fms.currentLeg.route.procedure); + const readback = { + log: `climb via the ${this.fms.currentLeg.route.procedure} departure`, + say: `climb via the ${procedureName} departure` + }; + + return ['ok', readback]; } /** * @for AircraftInstanceModel * @method runDescendViaSTAR * @param data + * @return {boolean|undefined} */ runDescendViaSTAR() { - if (this.fms.descendViaSTAR() && this.fms.following.star) { - const readback = { - log: `descend via the ${this.fms.following.star} arrival`, - say: `descend via the ${window.airportController.airport_get().stars[this.fms.following.star].name} arrival` - }; + if (!this.fms.descendViaSTAR() || !this.fms.following.star) { + const isWarning = true; + window.uiController.ui_log(`${this.getCallsign()}, unable to descend via STAR`, isWarning); - return ['ok', readback]; + return; } - const isWarning = true; - window.uiController.ui_log(`${this.getCallsign()}, unable to descend via STAR`, isWarning); + const airport = window.airportController.airport_get(); + const { name: procedureName } = airport.starCollection.findRouteByIcao(this.fms.currentLeg.route.procedure); + const readback = { + log: `descend via the ${this.fms.following.star} arrival`, + say: `descend via the ${procedureName} arrival` + }; + + return ['ok', readback]; } /** diff --git a/src/assets/scripts/aircraft/Leg.js b/src/assets/scripts/aircraft/Leg.js index 3630fd78..e55902a2 100644 --- a/src/assets/scripts/aircraft/Leg.js +++ b/src/assets/scripts/aircraft/Leg.js @@ -1,3 +1,4 @@ +import _forEach from 'lodash/forEach'; import _get from 'lodash/get'; import _has from 'lodash/has'; import _map from 'lodash/map'; @@ -7,26 +8,28 @@ import { FP_LEG_TYPE } from '../constants/aircraftConstants'; import { LOG } from '../constants/logLevel'; /** - * Build a 'leg' of the route (contains series of waypoints) + * This class acts as a collection of Waypoint model objects. * + * @class Leg */ export default class Leg { /** - * @param {object} route: "KSFO.OFFSH9.SXC", either a fix, or with format 'start.procedure.end', or - * "[RNAV/GPS]" for custom positions - * type: "sid", can be 'sid', 'star', 'iap', 'awy', 'fix' - * firstIndex: 0 the position (index) in fms.legs to insert this leg * * @for Leg * @constructor * @param data + * @param {object} route: "KSFO.OFFSH9.SXC", either a fix, or with format 'start.procedure.end', or + * "[RNAV/GPS]" for custom positions + * type: "sid", can be 'sid', 'star', 'iap', 'awy', 'fix' + * firstIndex: 0 the position (index) in fms.legs to insert this leg */ constructor(data = {}, fms) { /** + * String representation of a route. * - * - * - 'KSFO.OFFSH9.SXC' - * - 'FAITH' + * May be a single fix or a route expressed in dot notation. ex: + * - `KSFO.OFFSH9.SXC` + * - `FAITH` * * @property route * @type {string} @@ -43,7 +46,7 @@ export default class Leg { // TODO: possibly implement as a waypointCollection /** - * Waypoint objects to follow + * A collection of Waypoint instances * * @property waypoints * @type {Array} @@ -125,6 +128,27 @@ export default class Leg { } } + /** + * Add a new Waypoint to the collection + * + * @method addWaypointToLeg + * @param waypointToAdd {Waypoint} + */ + addWaypointToLeg(waypointToAdd) { + if (!(waypointToAdd instanceof Waypoint)) { + throw new TypeError('Invalid parameter, expecte waypointToAdd to be an instanceof the Waypoint class'); + } + + this.waypoints.push(waypointToAdd); + } + + /** + * @for Leg + * @method _generateWaypointsForSid + * @param data {object} + * @param fms {AircraftFlightManagementSystem} + * @private + */ _generateWaypointsForSid(data, fms) { if (!fms) { log('Attempted to generate waypoints for SID, but cannot because fms ref not passed!', LOG.WARNING); @@ -163,7 +187,7 @@ export default class Leg { for (let i = 0; i < waypointsForSid.length; i++) { const waypointToAdd = waypointsForSid[i].generateFmsWaypoint(fms); - this.waypoints.push(waypointToAdd); + this.addWaypointToLeg(waypointToAdd); } if (!this.waypoints[0].speed) { @@ -171,6 +195,13 @@ export default class Leg { } } + /** + * @for Leg + * @method _generateWaypointsForStar + * @param data {object} + * @param fms {AircraftFlightManagementSystem} + * @private + */ _generateWaypointsForStar(data, fms) { if (!fms) { log('Attempted to generate waypoints for STAR, but cannot because fms ref not passed!', LOG.WARNING); @@ -187,7 +218,7 @@ export default class Leg { for (let i = 0; i < waypointsForStar.length; i++) { const waypointToAdd = waypointsForStar[i].generateFmsWaypoint(fms); - this.waypoints.push(waypointToAdd); + this.addWaypointToLeg(waypointToAdd); } if (!this.waypoints[0].speed) { @@ -195,13 +226,19 @@ export default class Leg { } } - + // NOT IN USE _generateWaypointsForIap(data, fms) { - // NOT IN USE return; } - + // NOT IN USE + /** + * @for Leg + * @method _generateWaypointsForAirway + * @param data {object} + * @param fms {AircraftFlightManagementSystem} + * @private + */ _generateWaypointsForAirway(data, fms) { const start = this.route.split('.')[0]; const airway = this.route.split('.')[1]; @@ -221,6 +258,7 @@ export default class Leg { return; } + // TODO: abstract this logic // Build list of fixes, depending on direction traveling along airway const fixes = []; const readFwd = (awy.indexOf(end) > awy.indexOf(start)); @@ -235,30 +273,51 @@ export default class Leg { } } - // Add list of fixes to this.waypoints this._resetWaypoints(); - this.waypoints = _map(fixes, (fix) => new Waypoint({ fix }, fms)); - } + _forEach(fixes, (fix) => { + const waypointToAdd = new Waypoint({ fix }, fms); + this.addWaypointToLeg(waypointToAdd); + }); + } + + /** + * @for Leg + * @method _generateWaypointForFix + * @param fms {AircraftFlightManagementSystem} + * @private + */ _generateWaypointForFix(fms) { - this.waypoints = []; + this._resetWaypoints(); + const waypointToAdd = new Waypoint({ fix: this.route }, fms); - this.waypoints.push(waypointToAdd); + this.addWaypointToLeg(waypointToAdd); } - + /** + * @for Leg + * @method _generateManualWaypoint + * @param fms {AircraftFlightManagementSystem} + * @private + */ _generateManualWaypoint(fms) { const waypointToAdd = new Waypoint(this.route, fms); - this.waypoints.push(waypointToAdd); + this.addWaypointToLeg(waypointToAdd); } + /** + * @for Leg + * @method _generateEmptyWaypoint + * @param fms {AircraftFlightManagementSystem} + * @private + */ _generateEmptyWaypoint(fms) { const waypointToAdd = new Waypoint({ route: '' }, fms); - this.waypoints = [waypointToAdd]; + this.addWaypointToLeg(waypointToAdd); } /** From 0809408bd17478872008f9805f2aa9ea00131d18 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Mon, 7 Nov 2016 17:10:43 -0600 Subject: [PATCH 12/25] feature/ATC-53 - Updates comments and removes old TODOs --- .../scripts/aircraft/AircraftInstanceModel.js | 36 ++++--------------- .../scripts/constants/aircraftConstants.js | 17 +++++++++ 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index 823b38d3..8dfe4347 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -48,7 +48,7 @@ import { SELECTORS } from '../constants/selectors'; /** * Enum of commands and thier corresponding function. * - * Used to build a call to the correct function when an UI command, or commands, + * Used to build a call to the correct function when a UI command, or commands, * for an aircraft have been issued. * * @property COMMANDS @@ -140,7 +140,6 @@ export default class Aircraft { this.aircraftStripView = null; this.$html = null; - // TODO: this initialization should live in a `_init()` init method and not the constructor this.$strips = $(SELECTORS.DOM_SELECTORS.STRIPS); /* eslint-enable multi-spaces*/ @@ -151,25 +150,7 @@ export default class Aircraft { this.position_history = []; this.category = options.category; // 'arrival' or 'departure' - this.mode = FLIGHT_MODES.CRUISE; // 'apron', 'taxi', 'waiting', 'takeoff', 'cruise', or 'landing' - // where: - // - 'apron' is the initial status of a new departing plane. After - // the plane is issued the 'taxi' command, the plane transitions to - // 'taxi' mode - // - 'taxi' describes the process of getting ready for takeoff. After - // a delay, the plane becomes ready and transitions into 'waiting' mode - // - 'waiting': the plane is ready for takeoff and awaits clearence to - // take off - // - 'takeoff' is assigned to planes in the process of taking off. These - // planes are still on the ground or have not yet reached the minimum - // altitude - // - 'cruse' describes, that a plane is currently in flight and - // not following an ILS path. Planes of category 'arrival' entering the - // playing field also have this state. If an ILS path is picked up, the - // plane transitions to 'landing' - // - 'landing' the plane is following an ILS path or is on the runway in - // the process of stopping. If an ILS approach or a landing is aborted, - // the plane reenters 'cruise' mode + this.mode = FLIGHT_MODES.CRUISE; /* * the following diagram illustrates all allowed mode transitions: @@ -270,13 +251,6 @@ export default class Aircraft { } parse(data) { - // const keys = ['position', 'model', 'airline', 'callsign', 'category', 'heading', 'altitude', 'speed']; - // _forEach(keys, (key) => { - // if (_has(data, key)) { - // this[key] = data[key]; - // } - // }); - this.position = _get(data, 'position', this.position); this.model = _get(data, 'model', this.model); this.airline = _get(data, 'airline', this.airline); @@ -304,6 +278,7 @@ export default class Aircraft { this.speed = 0; } + // TODO: combine these two to asingle constant if (data.heading) { this.fms.setCurrent({ heading: data.heading }); } @@ -312,7 +287,7 @@ export default class Aircraft { this.fms.setCurrent({ altitude: data.altitude }); } - const speed = data.speed || this.model.speed.cruise; + const speed = _get(data, 'speed', this.model.speed.cruise); this.fms.setCurrent({ speed: speed }); if (data.route) { @@ -328,7 +303,6 @@ export default class Aircraft { } setArrivalWaypoints(waypoints) { - // TODO: change to _forEach // add arrival fixes to fms for (let i = 0; i < waypoints.length; i++) { this.fms.appendLeg({ @@ -617,6 +591,8 @@ export default class Aircraft { this.$html.hide(600); } + + // TODO: move aircraftCommands to a new class /** * @for AircraftInstanceModel * @method runCommands diff --git a/src/assets/scripts/constants/aircraftConstants.js b/src/assets/scripts/constants/aircraftConstants.js index 394c7658..d96b837d 100644 --- a/src/assets/scripts/constants/aircraftConstants.js +++ b/src/assets/scripts/constants/aircraftConstants.js @@ -4,6 +4,23 @@ * @final */ export const FLIGHT_MODES = { + // - 'apron' is the initial status of a new departing plane. After + // the plane is issued the 'taxi' command, the plane transitions to + // 'taxi' mode + // - 'taxi' describes the process of getting ready for takeoff. After + // a delay, the plane becomes ready and transitions into 'waiting' mode + // - 'waiting': the plane is ready for takeoff and awaits clearence to + // take off + // - 'takeoff' is assigned to planes in the process of taking off. These + // planes are still on the ground or have not yet reached the minimum + // altitude + // - 'cruse' describes, that a plane is currently in flight and + // not following an ILS path. Planes of category 'arrival' entering the + // playing field also have this state. If an ILS path is picked up, the + // plane transitions to 'landing' + // - 'landing' the plane is following an ILS path or is on the runway in + // the process of stopping. If an ILS approach or a landing is aborted, + // the plane reenters 'cruise' mode APRON: 'apron', TAXI: 'taxi', WAITING: 'waiting', From 01093435f902ba681dfe723552fa418d6eb16551 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 13 Nov 2016 21:08:29 -0600 Subject: [PATCH 13/25] feature/ATC-53 - Abstracts restrictions logic to Waypoint * Removes fms.clearedAsFiled() method * Consolidates runSID() and climbViaSid() logic --- .../AircraftFlightManagementSystem.js | 81 ++------- .../scripts/aircraft/AircraftInstanceModel.js | 32 ++-- src/assets/scripts/aircraft/Waypoint.js | 168 ++++++++++++++++-- 3 files changed, 179 insertions(+), 102 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index defe35b3..99daf4b7 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -677,22 +677,6 @@ export default class AircraftFlightManagementSystem { return true; } - /** - * Invokes flySID() for the SID in the flightplan (fms.fp.route) - */ - clearedAsFiled() { - // FIXME: why keep a reference to the aircraft id if we just get it from the aircraftController? Also, - // if this bit of logic is simply getting the aircraft instance, why not use `this.my_aircraft` for - // the whole thing? - const retval = this.my_aircraft.runSID([window.aircraftController.aircraft_get(this.my_aircrafts_eid).destination]); - // TODO: this method could simply return the logic being set to `ok` - const ok = !(Array.isArray(retval) && retval[0] === 'fail'); - - return ok; - } - - // FIXME the logic in this method is remarkably similiar to the logic in .descendViaSID(). perhpas there - // are opportunities for abstraction here. /** * Climbs aircraft in compliance with the SID they're following * Adds altitudes and speeds to each waypoint that are as high as @@ -704,65 +688,23 @@ export default class AircraftFlightManagementSystem { * - (spd) waypoint's speed restriction */ climbViaSID() { - if (!this.currentLeg.type === FP_LEG_TYPE.SID) { - return; + if (this.currentLeg.type !== FP_LEG_TYPE.SID) { + return false; } - let wp = this.currentLeg.waypoints; - let cruise_alt = this.fp.altitude; - let cruise_spd = this.my_aircraft.model.speed.cruise; + const wp = this.currentLeg.waypoints; + const cruise_alt = this.fp.altitude; + const cruise_spd = this.my_aircraft.model.speed.cruise; for (let i = 0; i < wp.length; i++) { - let altitude = wp[i].fixRestrictions.alt; - let speed = wp[i].fixRestrictions.spd; - let minAlt; - let alt; - let maxAlt; - - // TODO: use `StandardWaypointModel` methods for this logic - // Altitude Control - if (altitude) { - if (altitude.indexOf('+') !== -1) { - // at-or-above altitude restriction - minAlt = parseInt(altitude.replace('+', ''), 10) * 100; - alt = Math.min(window.airportController.airport_get().ctr_ceiling, cruise_alt); - } else if (altitude.indexOf('-') !== -1) { - maxAlt = parseInt(altitude.replace('-', ''), 10) * 100; - // climb as high as restrictions permit - alt = Math.min(maxAlt, cruise_alt); - } else { - // cross AT this altitude - alt = parseInt(altitude, 10) * 100; - } - } else { - alt = Math.min(window.airportController.airport_get().ctr_ceiling, cruise_alt); - } - - wp[i].altitude = alt; // add altitudes to wp + const waypoint = wp[i]; + const { ctr_ceiling } = window.airportController.airport_get(); - let minSpd; - let spd = cruise_spd; - let maxSpd; - // Speed Control - if (speed) { - if (speed.indexOf('+') !== -1) { - // at-or-above speed restriction - minSpd = parseInt(speed.replace('+', ''), 10); - spd = Math.min(minSpd, cruise_spd); - } else if (speed.indexOf('-') !== -1) { - maxSpd = parseInt(speed.replace('-', ''), 10); - // go as fast as restrictions permit - spd = Math.min(maxSpd, cruise_spd); - } else { - // cross AT this speed - spd = parseInt(speed, 10); - } - } - - // add speeds to wp - wp[i].speed = spd; + waypoint.setAltitude(ctr_ceiling, cruise_alt); + waypoint.setSpeed(cruise_spd); } + // TODO: this may not be needed anymore now that we are operating on the Waypoint model itself. // TODO; this completely replaces an existing Waypoint, why not just update the instance? // change fms waypoints to wp (which contains the altitudes and speeds) this.legs[this.current[LEG]].waypoints = wp; @@ -810,7 +752,7 @@ export default class AircraftFlightManagementSystem { let alt; let maxAlt; - // TODO: use `StandardWaypointModel` methods for this logic + // TODO: use `Waypoint` model methods for this logic // Altitude Control if (a) { if (a.indexOf('+') !== -1) { @@ -835,6 +777,7 @@ export default class AircraftFlightManagementSystem { let spd; let maxSpd; + // TODO: use `Waypoint` model methods for this logic // Speed Control if (s) { if (s.indexOf('+') !== -1) { diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index 7498d579..3aabbd0e 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import $ from 'jquery'; import _forEach from 'lodash/forEach'; import _get from 'lodash/get'; @@ -921,20 +922,21 @@ export default class Aircraft { * @return {array} */ runClearedAsFiled() { - if (!this.fms.clearedAsFiled()) { + // TODO: the `runSID` method does not always return a boolean, in some cases it returns readbacks + // which look to never be used? + if (!this.runSID()) { return [true, 'unable to clear as filed']; } const airport = window.airportController.airport_get(); - const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.dstination); + const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.destination); const readback = {}; - readback.log = `cleared to destination via the ${this.destination} ` + - `departure, then as filed. Climb and maintain ${airport.initial_alt}, ` + - `expect ${this.fms.fp.altitude} 10 minutes after departure `; + readback.log = `cleared to destination via the ${this.destination} departure, then as filed. Climb and ` + + `maintain ${airport.initial_alt}, expect ${this.fms.fp.altitude} 10 minutes after departure `; readback.say = `cleared to destination via the ${procedureName} ` + `departure, then as filed. Climb and maintain ${radio_altitude(airport.initial_alt)}, ` + - `expect ${radio_altitude(this.fms.fp.altitude)}, ${radio_spellOut(' 10 ')} minutes after departure'`; + `expect ${radio_altitude(this.fms.fp.altitude)}, ${radio_spellOut('10')} minutes after departure'`; return ['ok', readback]; } @@ -1239,16 +1241,16 @@ export default class Aircraft { /** * @for AircraftInstanceModel * @method runSID - * @param data */ - runSID(data) { + runSID() { const apt = window.airportController.airport_get(); - const sid_id = data[0].toUpperCase(); + const sid_id = this.destination; if (!_has(apt.sids, sid_id)) { - return; + return ['fail', 'SID name not understood']; } + // TODO: refactor to use `StandardRouteCollection` const sid_name = apt.sids[sid_id].name; const exit = apt.getSIDExitPoint(sid_id); const route = `${apt.icao}.${sid_id}.${exit}`; @@ -1257,12 +1259,8 @@ export default class Aircraft { return ['fail', 'unable to fly SID, we are an inbound']; } - if (data[0].length === 0 || !_has(apt.sids, sid_id)) { - return ['fail', 'SID name not understood']; - } - if (!this.rwy_dep) { - this.setDepartureRunway(window.airportController.airport_get().runway); + this.setDepartureRunway(apt.runway); } if (!_has(apt.sids[sid_id].rwy, this.rwy_dep)) { @@ -1276,7 +1274,9 @@ export default class Aircraft { say: `cleared to destination via the ${sid_name} departure, then as filed` }; - return ['ok', readback]; + // TODO: this return format is never used by the calling method. the calling method expects a boolean + // return ['ok', readback]; + return true; } /** diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index 2ed77eac..a905812d 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -1,10 +1,55 @@ -/* eslint-disable no-multi-spaces, no-undef */ -import _forEach from 'lodash/forEach'; import _get from 'lodash/get'; import _head from 'lodash/head'; +import _isNil from 'lodash/isNil'; import FixCollection from '../airport/Fix/FixCollection'; import { WAYPOINT_NAV_MODE } from '../constants/aircraftConstants'; +/** + * Symbol denoting a greater than restriction + * + * @property ABOVE_SYMBOL + * @type {string} + * @final + */ +const ABOVE_SYMBOL = '+'; + +/** + * Symbol denoting a less than restriction + * + * @property ABOVE_SYMBOL + * @type {string} + * @final + */ +const BELOW_SYMBOL = '-'; + +// TODO: there should be a helper function for this +/** + * Number to used to cnovert a FL altitude to an altitude in thousands + * + * @property ABOVE_SYMBOL + * @type {string} + * @final + */ +const FL_TO_THOUSANDS_MULTIPLIER = 100; + +/** + * Enemuration for an invalid index number. + * + * @property INVALID_INDEX + * @type {number} + * @final + */ +const INVALID_INDEX = -1; + +/** + * Enumeration for the radix value of `parseInt` + * + * @proeprty DECIMAL_RADIX + * @type {number} + * @final + */ +const DECIMAL_RADIX = 10; + /** * Build a waypoint object * @@ -56,10 +101,13 @@ export default class Waypoint { * * @for Waypoint * @method parse + * @param data {object} + * @param fms {AircraftFlightManagementSystem} */ parse(data, fms) { + this.extractFixRestrictions(data); + this.route = _get(data, 'route', this.route); - this.fixRestrictions = _get(data, 'fixRestrictions', this.fixRestrictions); // Populate Waypoint with data if (data.fix) { @@ -68,21 +116,107 @@ export default class Waypoint { this.location = FixCollection.getFixPositionCoordinates(data.fix); } + if (this.navmode) { + return; + } + // for aircraft that don't yet have proper guidance (eg: SID/STAR, or departing aircraft) - if (!this.navmode) { - this.navmode = WAYPOINT_NAV_MODE.HEADING; - const airport = window.airportController.airport_get(); - const firstRouteSegment = _head(this.route.split('.')); - - if (firstRouteSegment === airport.icao && this.heading === null) { - // aim departure along runway heading - const { angle } = airport.getRunway(airport.runway); - - this.heading = angle; - } else if (firstRouteSegment === 'KDBG' && this.heading === null) { - // aim arrival @ middle of airspace - this.heading = this.radial + Math.PI; - } + this.setInitialNavMode(); + } + + /** + * @for Waypoint + * @method extractFixRestrictions + * @param fixRestrictions {object} + */ + extractFixRestrictions({ fixRestrictions }) { + if (_isNil(fixRestrictions)) { + return; + } + + this.fixRestrictions = fixRestrictions; + } + + /** + * If there isn't a navmode set, set one here + * + * @for Waypoint + * @setInitialNavMode + */ + setInitialNavMode() { + this.navmode = WAYPOINT_NAV_MODE.HEADING; + const airport = window.airportController.airport_get(); + const firstRouteSegment = _head(this.route.split('.')); + + if (firstRouteSegment === airport.icao && this.heading === null) { + // aim departure along runway heading + const { angle } = airport.getRunway(airport.runway); + + this.heading = angle; + } else if (firstRouteSegment === 'KDBG' && this.heading === null) { + // aim arrival @ middle of airspace + this.heading = this.radial + Math.PI; + } + } + + /** + * @for Waypoint + * @method setAltitude + * @param centerCeiling {number} ceiling of the airspace in feet + * @param cruiseAltitude {number} cruiseAltitude of the current aircraft + */ + setAltitude(centerCeiling, cruiseAltitude) { + const { alt: altitudeRestriction } = this.fixRestrictions; + + if (!altitudeRestriction) { + this.altitude = Math.min(centerCeiling, cruiseAltitude); + + return; + } + + // TODO: enumerate symbols + // TODO: enumerate magic numbers + if (altitudeRestriction.indexOf(ABOVE_SYMBOL) !== INVALID_INDEX) { + // at-or-above altitudeRestriction restriction + const minAlt = parseInt(altitudeRestriction.replace(ABOVE_SYMBOL, ''), DECIMAL_RADIX) * FL_TO_THOUSANDS_MULTIPLIER; + this.altitude = Math.min(centerCeiling, cruiseAltitude); + } else if (altitudeRestriction.indexOf(BELOW_SYMBOL) !== INVALID_INDEX) { + const maxAlt = parseInt(altitudeRestriction.replace(BELOW_SYMBOL, ''), DECIMAL_RADIX) * FL_TO_THOUSANDS_MULTIPLIER; + // climb as high as restrictions permit + this.altitude = Math.min(maxAlt, cruiseAltitude); + } else { + // cross AT this altitudeRestriction + this.altitude = parseInt(altitudeRestriction, DECIMAL_RADIX) * FL_TO_THOUSANDS_MULTIPLIER; + } + } + + /** + * @for Waypoint + * @method setSpeed + * @param cruiseSpeed {number} cruiseSpeed of the current aircraft + */ + setSpeed(cruiseSpeed) { + const { spd: speedRestriction } = this.fixRestrictions; + + if (!speedRestriction) { + this.speed = cruiseSpeed; + + return; + } + + // TODO: enumerate symbols + // TODO: enumerate magic numbers + if (speedRestriction.indexOf(ABOVE_SYMBOL) !== INVALID_INDEX) { + // at-or-above speed restriction + const minSpd = parseInt(speedRestriction.replace(ABOVE_SYMBOL, ''), DECIMAL_RADIX); + this.speed = Math.min(minSpd, cruiseSpeed); + } else if (speedRestriction.indexOf(BELOW_SYMBOL) !== INVALID_INDEX) { + const maxSpd = parseInt(speedRestriction.replace(BELOW_SYMBOL, ''), DECIMAL_RADIX); + // go as fast as restrictions permit + this.speed = Math.min(maxSpd, cruiseSpeed); + } else { + // cross AT this speed + this.speed = parseInt(speedRestriction, DECIMAL_RADIX); } } } From 733547d30c88d6ba8f5ad76136748037d0159b4f Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Mon, 14 Nov 2016 07:21:56 -0600 Subject: [PATCH 14/25] feature/ATC-53 - Simplifies .descendViaSTAR() * Adds .getStarLegWaypoints() * Removes dead code --- .../AircraftFlightManagementSystem.js | 113 ++++-------------- src/assets/scripts/aircraft/Waypoint.js | 39 +++--- 2 files changed, 47 insertions(+), 105 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 99daf4b7..67aba737 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import _find from 'lodash/find'; import _last from 'lodash/last'; import _map from 'lodash/map'; import Waypoint from './Waypoint'; @@ -704,105 +705,37 @@ export default class AircraftFlightManagementSystem { waypoint.setSpeed(cruise_spd); } - // TODO: this may not be needed anymore now that we are operating on the Waypoint model itself. - // TODO; this completely replaces an existing Waypoint, why not just update the instance? - // change fms waypoints to wp (which contains the altitudes and speeds) - this.legs[this.current[LEG]].waypoints = wp; - return true; } - // FIXME the logic in this method is remarkably similiar to the logic in .climbViaSID(). perhaps there - // are opportunities for abstraction here. /** * Descends aircraft in compliance with the STAR they're following * Adds altitudes and speeds to each waypoint in accordance with the STAR */ descendViaSTAR() { - // Find the STAR leg - let wp; - let legIndex; - - // TODO: if this.legs is an array this should be a for and not a for/in loop - for (const l in this.legs) { - if (this.legs[l].type === FP_LEG_TYPE.STAR) { - legIndex = l; - wp = this.legs[l].waypoints; + const waypointList = this.getStarLegWaypoints(); - break; - } - } - - if (!wp) { + // TODO: would a star leg ever not have waypoints? + if (!waypointList) { return; } let start_alt = this.currentWaypoint.altitude || this.my_aircraft.altitude; let start_spd = this.currentWaypoint.speed || this.my_aircraft.model.speed.cruise; - for (let i = 0; i < wp.length; i++) { - if (i >= 1) { - start_alt = wp[i - 1].altitude; - start_spd = wp[i - 1].speed; - } - - const a = wp[i].fixRestrictions.alt; - const s = wp[i].fixRestrictions.spd; - let minAlt; - let alt; - let maxAlt; - - // TODO: use `Waypoint` model methods for this logic - // Altitude Control - if (a) { - if (a.indexOf('+') !== -1) { - // at-or-above altitude restriction - minAlt = parseInt(a.replace('+', ''), 10) * 100; - alt = Math.max(minAlt, start_alt); - } else if (a.indexOf('-') !== -1) { - maxAlt = parseInt(a.replace('-', '')) * 100; - // climb as high as restrictions permit - alt = Math.min(maxAlt, start_alt); - } else { - // cross AT this altitude - alt = parseInt(a) * 100; - } - } else { - alt = start_alt; - } + for (let i = 0; i < waypointList.length; i++) { + const waypoint = waypointList[i]; + const previousWaypoint = waypointList[i - 1]; - wp[i].altitude = alt; // add altitudes to wp - - let minSpd; - let spd; - let maxSpd; - - // TODO: use `Waypoint` model methods for this logic - // Speed Control - if (s) { - if (s.indexOf('+') !== -1) { - // at-or-above speed restriction - minSpd = parseInt(s.replace('+', '')); - spd = Math.min(minSpd, start_spd); - } else if (s.indexOf('-') !== -1) { - maxSpd = parseInt(s.replace('-', '')); - // go as fast as restrictions permit - spd = Math.min(maxSpd, start_spd); - } else { - // cross AT this speed - spd = parseInt(s); - } - } else { - spd = start_spd; + if (i >= 1) { + start_alt = previousWaypoint.altitude; + start_spd = previousWaypoint.speed; } - // add speeds to wp - wp[i].speed = spd; + waypoint.setAltitude(null, start_alt); + waypoint.setSpeed(start_spd); } - // change fms waypoints to wp (which contains the altitudes and speeds) - this.legs[legIndex].waypoints = wp; - return true; } @@ -874,20 +807,10 @@ export default class AircraftFlightManagementSystem { /** ************************* FMS GET FUNCTIONS ***************************/ - // TODO: this set of methods could be used as getters instead - // ex: `get currentLeg()` and then used like `this.fms.currentLeg` get currentLeg() { return this.legs[this.current[LEG]]; } - /** - * Return the current leg - * @deprecated - */ - // currentLeg() { - // return this.legs[this.current[LEG]]; - // } - get currentWaypoint() { if (this.legs.length < 1) { return null; @@ -947,6 +870,18 @@ export default class AircraftFlightManagementSystem { return; } + /** + * Find a leg with type `star` and return that leg's waypoints. + * + * @method getSt + * @return {array} + */ + getStarLegWaypoints() { + const starLeg = _find(this.legs, { type: FP_LEG_TYPE.STAR }); + + return starLeg.waypoints || []; + } + /** * Returns all waypoints in fms, in order */ diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index a905812d..163e087a 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -111,16 +111,11 @@ export default class Waypoint { // Populate Waypoint with data if (data.fix) { - this.navmode = 'fix'; + this.navmode = WAYPOINT_NAV_MODE.FIX; this.fix = data.fix; this.location = FixCollection.getFixPositionCoordinates(data.fix); } - if (this.navmode) { - return; - } - - // for aircraft that don't yet have proper guidance (eg: SID/STAR, or departing aircraft) this.setInitialNavMode(); } @@ -140,10 +135,16 @@ export default class Waypoint { /** * If there isn't a navmode set, set one here * + * For aircraft that don't yet have proper guidance (eg: SID/STAR, or departing aircraft) + * * @for Waypoint * @setInitialNavMode */ setInitialNavMode() { + if (this.navmode) { + return; + } + this.navmode = WAYPOINT_NAV_MODE.HEADING; const airport = window.airportController.airport_get(); const firstRouteSegment = _head(this.route.split('.')); @@ -154,36 +155,43 @@ export default class Waypoint { this.heading = angle; } else if (firstRouteSegment === 'KDBG' && this.heading === null) { + // FIXME: radial is not defined or set anywhere in this class // aim arrival @ middle of airspace this.heading = this.radial + Math.PI; } } + // TODO: rename centerCeiling and make this method more flexible /** * @for Waypoint * @method setAltitude * @param centerCeiling {number} ceiling of the airspace in feet * @param cruiseAltitude {number} cruiseAltitude of the current aircraft */ - setAltitude(centerCeiling, cruiseAltitude) { + setAltitude(centerCeiling = null, cruiseAltitude) { const { alt: altitudeRestriction } = this.fixRestrictions; if (!altitudeRestriction) { - this.altitude = Math.min(centerCeiling, cruiseAltitude); + this.altitude = !_isNil(centerCeiling) + ? Math.min(centerCeiling, cruiseAltitude) + : cruiseAltitude; return; } - // TODO: enumerate symbols - // TODO: enumerate magic numbers + // TODO: there has to be an easier way to do this logic. if (altitudeRestriction.indexOf(ABOVE_SYMBOL) !== INVALID_INDEX) { // at-or-above altitudeRestriction restriction - const minAlt = parseInt(altitudeRestriction.replace(ABOVE_SYMBOL, ''), DECIMAL_RADIX) * FL_TO_THOUSANDS_MULTIPLIER; - this.altitude = Math.min(centerCeiling, cruiseAltitude); + const minAlt = parseInt(altitudeRestriction.replace(ABOVE_SYMBOL, ''), DECIMAL_RADIX); + const minimumAltitudeWithoutSymbol = minAlt * FL_TO_THOUSANDS_MULTIPLIER; + + this.altitude = Math.min(minimumAltitudeWithoutSymbol, cruiseAltitude); } else if (altitudeRestriction.indexOf(BELOW_SYMBOL) !== INVALID_INDEX) { - const maxAlt = parseInt(altitudeRestriction.replace(BELOW_SYMBOL, ''), DECIMAL_RADIX) * FL_TO_THOUSANDS_MULTIPLIER; + const maxAlt = parseInt(altitudeRestriction.replace(BELOW_SYMBOL, ''), DECIMAL_RADIX); + const maximumAltitudeWithoutSymbol = maxAlt * FL_TO_THOUSANDS_MULTIPLIER; + // climb as high as restrictions permit - this.altitude = Math.min(maxAlt, cruiseAltitude); + this.altitude = Math.min(maximumAltitudeWithoutSymbol, cruiseAltitude); } else { // cross AT this altitudeRestriction this.altitude = parseInt(altitudeRestriction, DECIMAL_RADIX) * FL_TO_THOUSANDS_MULTIPLIER; @@ -204,8 +212,7 @@ export default class Waypoint { return; } - // TODO: enumerate symbols - // TODO: enumerate magic numbers + // TODO: there has to be an easier way to do this logic. if (speedRestriction.indexOf(ABOVE_SYMBOL) !== INVALID_INDEX) { // at-or-above speed restriction const minSpd = parseInt(speedRestriction.replace(ABOVE_SYMBOL, ''), DECIMAL_RADIX); From 20041e4a855bc4a6581b536f4f5484d0da8d882e Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Tue, 15 Nov 2016 15:09:12 -0600 Subject: [PATCH 15/25] feature/ATC-53 - Replaces fms ref with airportInstanceModel ref in Waypoint * Adds tests and supporting fixutes and mocks for Waypoint --- .../AircraftFlightManagementSystem.js | 6 +- .../scripts/aircraft/AircraftInstanceModel.js | 16 ++- src/assets/scripts/aircraft/Leg.js | 40 +++--- src/assets/scripts/aircraft/Waypoint.js | 19 ++- src/assets/scripts/airport/AirportModel.js | 1 + .../StandardRouteWaypointModel.js | 5 +- test/aircraft/Waypoint.spec.js | 129 ++++++++++++++++++ test/aircraft/_mocks/waypointMocks.js | 30 ++++ test/airport/_fixtures/airportModelFixture.js | 43 ++++++ .../StandardRouteWaypointModel.spec.js | 7 +- 10 files changed, 255 insertions(+), 41 deletions(-) create mode 100644 test/aircraft/Waypoint.spec.js create mode 100644 test/aircraft/_mocks/waypointMocks.js create mode 100644 test/airport/_fixtures/airportModelFixture.js diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 67aba737..22d842ba 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -125,10 +125,11 @@ export default class AircraftFlightManagementSystem { * Insert a waypoint at current position and immediately activate it */ insertWaypointHere(data) { + const airport = window.airportController.airport_get(); const prev = this.currentWaypoint; // TODO: split this up into smaller chunks - this.currentLeg.waypoints.splice(this.current[WAYPOINT_WITHIN_LEG], 0, new Waypoint(data, this)); + this.currentLeg.waypoints.splice(this.current[WAYPOINT_WITHIN_LEG], 0, new Waypoint(data, airport)); this.update_fp_route(); // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? @@ -201,8 +202,9 @@ export default class AircraftFlightManagementSystem { * Insert a waypoint after the *current* waypoint */ appendWaypoint(data) { + const airport = window.airportController.airport_get(); // TODO: split this up into smaller chunks - this.currentLeg.waypoints.splice(this.current[WAYPOINT_WITHIN_LEG] + 1, 0, new Waypoint(data, this)); + this.currentLeg.waypoints.splice(this.current[WAYPOINT_WITHIN_LEG] + 1, 0, new Waypoint(data, airport)); this.update_fp_route(); } diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index 3aabbd0e..f43843af 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -721,6 +721,7 @@ export default class Aircraft { * @param data */ runHeading(data) { + const airport = window.airportController.airport_get(); const direction = data[0]; let heading = data[1]; const incremental = data[2]; @@ -775,7 +776,7 @@ export default class Aircraft { turn: direction, hold: false }, - this.fms + airport ); // add new Leg after hold leg @@ -796,7 +797,7 @@ export default class Aircraft { turn: direction, hold: false }, - this.fms + airport ); // TODO: this should be an FMS class method that accepts a new `waypointLeg` @@ -814,7 +815,7 @@ export default class Aircraft { turn: direction, hold: false }, - this.fms + airport ); this.fms.appendLeg({ @@ -832,7 +833,7 @@ export default class Aircraft { turn: direction, hold: false }, - this.fms + airport ); this.fms.insertLegHere({ @@ -1022,6 +1023,7 @@ export default class Aircraft { * @param data */ runHold(data) { + const airport = window.airportController.airport_get(); let dirTurns = data[0]; let legLength = data[1]; let holdFix = data[2]; @@ -1068,7 +1070,7 @@ export default class Aircraft { altitude: this.fms.altitudeForCurrentWaypoint(), speed: this.fms.currentWaypoint.speed }, - this.fms + airport ), // then enter the hold new Waypoint( @@ -1086,11 +1088,12 @@ export default class Aircraft { timer: null } }, - this.fms + airport ) ] }); } else { + // TODO: this should be a `Waypoint` // already currently going to the hold fix // Force the initial turn to outbound heading when entering the hold this.fms.appendWaypoint({ @@ -1113,6 +1116,7 @@ export default class Aircraft { holdFixLocation = this.position; // make a/c hold over their present position inboundHdg = this.heading; + // TODO: these aren't `Waypoints` and they should be this.fms.insertLegHere({ type: 'fix', waypoints: [ diff --git a/src/assets/scripts/aircraft/Leg.js b/src/assets/scripts/aircraft/Leg.js index e55902a2..e4b4ba45 100644 --- a/src/assets/scripts/aircraft/Leg.js +++ b/src/assets/scripts/aircraft/Leg.js @@ -89,6 +89,8 @@ export default class Leg { return; } + const airport = window.airportController.airport_get(); + switch (this.type) { case FP_LEG_TYPE.SID: // TODO: this is gross. we instantiate route with a string and new mutate it here to a RouteModel. @@ -104,25 +106,25 @@ export default class Leg { break; case FP_LEG_TYPE.IAP: // FUTURE FUNCTIONALITY - this._generateWaypointsForIap(data, fms); + this._generateWaypointsForIap(data, airport); break; case FP_LEG_TYPE.AWY: // TODO: this is gross. we instantiate route with a string and new mutate it here to a RouteModel. this.route = new RouteModel(data.route); - this._generateWaypointsForAirway(data, fms); + this._generateWaypointsForAirway(data, airport); break; case FP_LEG_TYPE.FIX: - this._generateWaypointForFix(fms); + this._generateWaypointForFix(airport); break; case FP_LEG_TYPE.MANUAL: - this._generateManualWaypoint(fms); + this._generateManualWaypoint(airport); break; default: - this._generateEmptyWaypoint(fms); + this._generateEmptyWaypoint(airport); break; } @@ -185,7 +187,7 @@ export default class Leg { } for (let i = 0; i < waypointsForSid.length; i++) { - const waypointToAdd = waypointsForSid[i].generateFmsWaypoint(fms); + const waypointToAdd = waypointsForSid[i].generateFmsWaypoint(airport); this.addWaypointToLeg(waypointToAdd); } @@ -216,7 +218,7 @@ export default class Leg { const waypointsForStar = airport.findWaypointModelsForStar(this.route.procedure, this.route.entry, rwy); for (let i = 0; i < waypointsForStar.length; i++) { - const waypointToAdd = waypointsForStar[i].generateFmsWaypoint(fms); + const waypointToAdd = waypointsForStar[i].generateFmsWaypoint(airport); this.addWaypointToLeg(waypointToAdd); } @@ -227,7 +229,7 @@ export default class Leg { } // NOT IN USE - _generateWaypointsForIap(data, fms) { + _generateWaypointsForIap(data, airport) { return; } @@ -239,7 +241,7 @@ export default class Leg { * @param fms {AircraftFlightManagementSystem} * @private */ - _generateWaypointsForAirway(data, fms) { + _generateWaypointsForAirway(data, airport) { const start = this.route.split('.')[0]; const airway = this.route.split('.')[1]; const end = this.route.split('.')[2]; @@ -276,7 +278,7 @@ export default class Leg { this._resetWaypoints(); _forEach(fixes, (fix) => { - const waypointToAdd = new Waypoint({ fix }, fms); + const waypointToAdd = new Waypoint({ fix }, airport); this.addWaypointToLeg(waypointToAdd); }); @@ -285,13 +287,13 @@ export default class Leg { /** * @for Leg * @method _generateWaypointForFix - * @param fms {AircraftFlightManagementSystem} + * @param airport {AirportInstanceModel} * @private */ - _generateWaypointForFix(fms) { + _generateWaypointForFix(airport) { this._resetWaypoints(); - const waypointToAdd = new Waypoint({ fix: this.route }, fms); + const waypointToAdd = new Waypoint({ fix: this.route }, airport); this.addWaypointToLeg(waypointToAdd); } @@ -299,11 +301,11 @@ export default class Leg { /** * @for Leg * @method _generateManualWaypoint - * @param fms {AircraftFlightManagementSystem} + * @param airport {AirportInstanceModel} * @private */ - _generateManualWaypoint(fms) { - const waypointToAdd = new Waypoint(this.route, fms); + _generateManualWaypoint(airport) { + const waypointToAdd = new Waypoint({ fix: this.route }, airport); this.addWaypointToLeg(waypointToAdd); } @@ -311,11 +313,11 @@ export default class Leg { /** * @for Leg * @method _generateEmptyWaypoint - * @param fms {AircraftFlightManagementSystem} + * @param airport {AirportInstanceModel} * @private */ - _generateEmptyWaypoint(fms) { - const waypointToAdd = new Waypoint({ route: '' }, fms); + _generateEmptyWaypoint(airport) { + const waypointToAdd = new Waypoint({ route: '' }, airport); this.addWaypointToLeg(waypointToAdd); } diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index 163e087a..b1022067 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -67,7 +67,7 @@ export default class Waypoint { * @for Waypoint * @constructor */ - constructor(data = {}, fms) { + constructor(data = {}, airport) { this.altitude = null; this.fix = null; this.navmode = null; @@ -93,7 +93,7 @@ export default class Waypoint { this.route = ''; - this.parse(data, fms); + this.parse(data, airport); } /** @@ -102,11 +102,9 @@ export default class Waypoint { * @for Waypoint * @method parse * @param data {object} - * @param fms {AircraftFlightManagementSystem} */ - parse(data, fms) { - this.extractFixRestrictions(data); - + parse(data, airport) { + // TODO: is this used? this.route = _get(data, 'route', this.route); // Populate Waypoint with data @@ -116,7 +114,8 @@ export default class Waypoint { this.location = FixCollection.getFixPositionCoordinates(data.fix); } - this.setInitialNavMode(); + this.extractFixRestrictions(data); + this.setInitialNavMode(airport); } /** @@ -138,15 +137,14 @@ export default class Waypoint { * For aircraft that don't yet have proper guidance (eg: SID/STAR, or departing aircraft) * * @for Waypoint - * @setInitialNavMode + * @method setInitialNavMode */ - setInitialNavMode() { + setInitialNavMode(airport) { if (this.navmode) { return; } this.navmode = WAYPOINT_NAV_MODE.HEADING; - const airport = window.airportController.airport_get(); const firstRouteSegment = _head(this.route.split('.')); if (firstRouteSegment === airport.icao && this.heading === null) { @@ -162,6 +160,7 @@ export default class Waypoint { } // TODO: rename centerCeiling and make this method more flexible + // TODO: use a default constant for cruiseAltitude /** * @for Waypoint * @method setAltitude diff --git a/src/assets/scripts/airport/AirportModel.js b/src/assets/scripts/airport/AirportModel.js index 17710563..6d40f973 100644 --- a/src/assets/scripts/airport/AirportModel.js +++ b/src/assets/scripts/airport/AirportModel.js @@ -63,6 +63,7 @@ export default class AirportModel { this.level = null; this.position = null; this.runways = []; + // TODO: rename to `runwayName` this.runway = null; this.fixes = {}; this.sids = {}; diff --git a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js index c1a35c47..869ee532 100644 --- a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js +++ b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js @@ -284,9 +284,10 @@ export default class StandardRouteWaypointModel { /** * @for StandardRouteWaypointModel * @method generateFmsWaypoint + * @param airport {AirportInstanceModel} * @return {Waypoint} */ - generateFmsWaypoint(fms) { + generateFmsWaypoint(airport) { const fmsWaypoint = { fix: this.name, fixRestrictions: { @@ -295,7 +296,7 @@ export default class StandardRouteWaypointModel { } } - return new Waypoint(fmsWaypoint, fms); + return new Waypoint(fmsWaypoint, airport); } /** diff --git a/test/aircraft/Waypoint.spec.js b/test/aircraft/Waypoint.spec.js new file mode 100644 index 00000000..8c21dd10 --- /dev/null +++ b/test/aircraft/Waypoint.spec.js @@ -0,0 +1,129 @@ + /* eslint-disable import/no-extraneous-dependencies, arrow-parens, max-len */ +import ava from 'ava'; + +import Waypoint from '../../src/assets/scripts/aircraft/Waypoint'; +import { airportModelFixtureForWaypoint } from '../airport/_fixtures/airportModelFixture'; +import { + MINIMAL_WAYPOINT_MOCK, + BASIC_WAYPOINT_MOCK, + ENROUTE_TO_HOLD_WAYPOINT_MOCK, + EXPANDED_WAYPOINT_MOCK +} from './_mocks/waypointMocks'; + +ava('should not throw if instantiated with a string as an arguemnt', t => { + t.notThrows(() => new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint)); + + const result = new Waypoint(MINIMAL_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + + t.true(result instanceof Waypoint); +}); + +ava('accepts and object as a parameter and sets its internal properties', t => { + const result = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + + t.true(result.fix === BASIC_WAYPOINT_MOCK.fix); + t.true(result.fixRestrictions.alt === BASIC_WAYPOINT_MOCK.fixRestrictions.alt); + t.true(result.fixRestrictions.spd === BASIC_WAYPOINT_MOCK.fixRestrictions.spd); +}); + +ava('.extractFixRestrictions() does not set fixRestrictions if none are provided', t => { + const model = new Waypoint(MINIMAL_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + + t.true(model.fixRestrictions.alt === null); + t.true(model.fixRestrictions.spd === null); +}); + +ava('.extractFixRestrictions() sets fixRestrictions if any are provided', t => { + const model = new Waypoint(MINIMAL_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.extractFixRestrictions(BASIC_WAYPOINT_MOCK); + + t.true(model.fixRestrictions.alt === BASIC_WAYPOINT_MOCK.fixRestrictions.alt); + t.true(model.fixRestrictions.spd === BASIC_WAYPOINT_MOCK.fixRestrictions.spd); +}); + +ava('.setInitialNavMode() sets navmode to heading if a fix name is not provided', t => { + const result = new Waypoint(MINIMAL_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + + t.true(result.navmode === 'heading'); +}); + +ava('.setInitialNavMode() sets navmode to fix if a fix name is provided', t => { + const result = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + + t.true(result.navmode === 'fix'); +}); + +ava('.setAltitude() sets the altitude if no restrictions exist and no centerCeiling is supplied', t => { + const model = new Waypoint(MINIMAL_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.setAltitude(null, 23000); + + t.true(model.altitude === 23000); +}); + +ava('.setAltitude() if fix restrictions do not exist, sets the altitude by the min of centerCeiling and cruiseAltitude', t => { + const model = new Waypoint(MINIMAL_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.setAltitude(10000, 23000); + + t.true(model.altitude === 10000); +}); + +ava.skip('.setAltitude() sets the altitude from existing fixRestrictions and cruiseAltitude when restriction is at or above', t => { + const centerCeilingMock = 10000; + const cruiseAltitudeMock = 23000; + const expectedResult = 27000; + const model = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.setAltitude(centerCeilingMock, cruiseAltitudeMock); + + t.true(model.altitude === expectedResult); +}); + +ava('.setAltitude() sets the altitude from existing fixRestrictions and cruiseAltitude when restriction is at or below', t => { + const centerCeilingMock = 10000; + const cruiseAltitudeMock = 23000; + const expectedResult = 23000; + const model = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.fixRestrictions.alt = '270-'; + model.setAltitude(centerCeilingMock, cruiseAltitudeMock); + + t.true(model.altitude === expectedResult); +}); + +ava('.setAltitude() sets the altitude from existing fixRestrictions and cruiseAltitude', t => { + const centerCeilingMock = 10000; + const cruiseAltitudeMock = 23000; + const expectedResult = 21000; + const model = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.fixRestrictions.alt = '210'; + model.setAltitude(centerCeilingMock, cruiseAltitudeMock); + + t.true(model.altitude === expectedResult); +}); + +ava.skip('.setSpeed() sets the speed from existing fixRestrictions and cruiseSpeed when restriction is at or above', t => { + const cruiseSpeedMock = 300; + const expectedResult = 300; + const model = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.fixRestrictions.spd = '200+'; + model.setSpeed(cruiseSpeedMock); + + t.true(model.speed === expectedResult); +}); + +ava('.setSpeed() sets the speed from existing fixRestrictions and cruiseSpeed when restriction is at or below', t => { + const cruiseSpeedMock = 300; + const expectedResult = 270; + const model = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.fixRestrictions.spd = '270-'; + model.setSpeed(cruiseSpeedMock); + + t.true(model.speed === expectedResult); +}); + +ava('.setSpeed() sets the speed from existing fixRestrictions and cruiseSpeed', t => { + const cruiseSpeedMock = 300; + const expectedResult = 270; + const model = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + model.setSpeed(cruiseSpeedMock); + + t.true(model.speed === expectedResult); +}); diff --git a/test/aircraft/_mocks/waypointMocks.js b/test/aircraft/_mocks/waypointMocks.js new file mode 100644 index 00000000..eb42760e --- /dev/null +++ b/test/aircraft/_mocks/waypointMocks.js @@ -0,0 +1,30 @@ +export const MINIMAL_WAYPOINT_MOCK = 'klas'; + +export const BASIC_WAYPOINT_MOCK = { + fix: 'SNORA', + fixRestrictions: { + alt: '270+', + spd: '280' + } +}; + +export const ENROUTE_TO_HOLD_WAYPOINT_MOCK = { + fix: 'SNORA', + altitude: 7000, + speed: 230 +}; + +export const EXPANDED_WAYPOINT_MOCK = { + navmode: 'hold', + speed: 230, + altitude: 7000, + fix: null, + hold: { + fixName: 'BOLDR', + fixPos: [37.28695678169094, -42.26087965200279], + dirTurns: 'right', + legLength: '1min', + inboundHdg: 2.697288004800421, + timer: null + } +}; diff --git a/test/airport/_fixtures/airportModelFixture.js b/test/airport/_fixtures/airportModelFixture.js new file mode 100644 index 00000000..cdac19f1 --- /dev/null +++ b/test/airport/_fixtures/airportModelFixture.js @@ -0,0 +1,43 @@ +import AirportModel from '../../../src/assets/scripts/airport/AirportModel'; + +export const airportModelFixtureForWaypoint = new AirportModel({ + icao: 'KLAS', + iata: 'LAS', + magnetic_north: 11.9, + ctr_radius: 80, + ctr_ceiling: 19000, + initial_alt: 19000, + position: ['N36.080056', 'W115.15225', '2181ft'], + rr_radius_nm: 5.0, + rr_center: ['N36.080056', 'W115.15225'], + wind: { + angle: 220, + speed: 6 + }, + runways: [ + { + name: ['07L', '25R'], + end: [['N36d4m34.82', 'W115d10m16.98', '2179ft'], ['N36d4m35.05', 'W115d7m15.93', '2033ft']], + delay: [5, 5], + ils: [false, true] + }, + { + name: ['07R', '25L'], + end: [['N36d4m25.04', 'W115d9m41.15', '2157ft'], ['N36d4m25.17', 'W115d7m32.96', '2049ft']], + delay: [3, 5], + ils: [false, true] + }, + { + name: ['01R', '19L'], + end: [['N36d4m27.19', 'W115d10m3.00', '2175ft'], ['N36d5m54.85', 'W115d9m12.79', '2078ft']], + delay: [3, 6], + ils: [false, false] + }, + { + name: ['01L', '19R'], + end: [['N36d4m31.19', 'W115d10m13.31', '2181ft'], ['N36d5m58.77', 'W115d9m23.12', '2089ft']], + delay: [4, 7], + ils: [true, false] + } + ], +}); diff --git a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js index 7bb4b24b..fed37c0a 100644 --- a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js +++ b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js @@ -6,7 +6,10 @@ import StandardRouteWaypointModel from '../../../src/assets/scripts/airport/Stan import FixCollection from '../../../src/assets/scripts/airport/Fix/FixCollection'; import Waypoint from '../../../src/assets/scripts/aircraft/Waypoint'; -import { airportPositionFixture } from '../../fixtures/airportFixtures'; +import { + airportPositionFixture, + airportModelFixtureForWaypoint +} from '../../fixtures/airportFixtures' import { FIX_LIST_MOCK } from '../Fix/_mocks/fixMocks'; const NAME_MOCK = 'BIKKR'; @@ -61,7 +64,7 @@ ava('calls ._parseWaypointRestrictions() when provided and array', t => { ava('.generateFmsWaypoint() returns a new instance of an FMS Waypoint object', t => { const fmsMock = {}; const model = new StandardRouteWaypointModel(ROUTE_WAYPOINT_MOCK); - const result = model.generateFmsWaypoint(fmsMock); + const result = model.generateFmsWaypoint(airportModelFixtureForWaypoint); t.true(result instanceof Waypoint); t.true(model.name === result.fix); From 0a3900fe03e0b8a6e092f0b125fe6652955e7c4c Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Tue, 15 Nov 2016 19:56:47 -0600 Subject: [PATCH 16/25] feature/ATC-53 - Adds _get to Waypoint for populating class properties, adds additional test case. --- src/assets/scripts/aircraft/Waypoint.js | 9 +++++++++ test/aircraft/Waypoint.spec.js | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index b1022067..bc331625 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -106,6 +106,15 @@ export default class Waypoint { parse(data, airport) { // TODO: is this used? this.route = _get(data, 'route', this.route); + this.altitude = _get(data, 'altitude', this.altitude); + this.navmode = _get(data, 'navmode', this.navmode); + this.heading = _get(data, 'heading', this.heading); + this.turn = _get(data, 'turn', this.turn); + this.location = _get(data, 'location', this.location); + this.expedite = _get(data, 'expedite', this.expedite); + this.speed = _get(data, 'speed', this.speed); + this.hold = _get(data, 'hold', this.hold); + // Populate Waypoint with data if (data.fix) { diff --git a/test/aircraft/Waypoint.spec.js b/test/aircraft/Waypoint.spec.js index 8c21dd10..44da0225 100644 --- a/test/aircraft/Waypoint.spec.js +++ b/test/aircraft/Waypoint.spec.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies, arrow-parens, max-len */ import ava from 'ava'; - +import _isEqual from 'lodash/isEqual'; import Waypoint from '../../src/assets/scripts/aircraft/Waypoint'; import { airportModelFixtureForWaypoint } from '../airport/_fixtures/airportModelFixture'; import { @@ -26,6 +26,17 @@ ava('accepts and object as a parameter and sets its internal properties', t => { t.true(result.fixRestrictions.spd === BASIC_WAYPOINT_MOCK.fixRestrictions.spd); }); +ava('accepts and object as a parameter and sets its internal properties', t => { + const result = new Waypoint(EXPANDED_WAYPOINT_MOCK, airportModelFixtureForWaypoint); + + t.true(result.fix === EXPANDED_WAYPOINT_MOCK.fix); + t.true(result.navmode === EXPANDED_WAYPOINT_MOCK.navmode); + t.true(result.speed === EXPANDED_WAYPOINT_MOCK.speed); + t.true(result.altitude === EXPANDED_WAYPOINT_MOCK.altitude); + t.true(result.fix === EXPANDED_WAYPOINT_MOCK.fix); + t.true(_isEqual(result.fix, EXPANDED_WAYPOINT_MOCK.fix)); +}); + ava('.extractFixRestrictions() does not set fixRestrictions if none are provided', t => { const model = new Waypoint(MINIMAL_WAYPOINT_MOCK, airportModelFixtureForWaypoint); From 3643da1c916f8cdf4947d794fd121fcdb1812559 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sat, 19 Nov 2016 18:11:40 -0600 Subject: [PATCH 17/25] feature/ATC-53 - Fixes merge conflict wonkiness, several items were merged incorrectly causing failing tests. --- .../airport/StandardRoute/StandardRouteWaypointModel.js | 4 +++- test/airport/standardRoute/StandardRouteWaypointModel.spec.js | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js index f19ad8da..167f75b8 100644 --- a/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js +++ b/src/assets/scripts/airport/StandardRoute/StandardRouteWaypointModel.js @@ -1,5 +1,7 @@ +import _isNil from 'lodash/isNil'; import BaseModel from '../../base/BaseModel'; import FixCollection from '../Fix/FixCollection'; +import Waypoint from '../../aircraft/Waypoint'; /** * @property NAME_INDEX @@ -283,7 +285,7 @@ export default class StandardRouteWaypointModel extends BaseModel { alt: this._altitude, spd: this._speed } - } + }; return new Waypoint(fmsWaypoint, airport); } diff --git a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js index d42ac9b3..0581285b 100644 --- a/test/airport/standardRoute/StandardRouteWaypointModel.spec.js +++ b/test/airport/standardRoute/StandardRouteWaypointModel.spec.js @@ -62,7 +62,6 @@ ava('calls ._parseWaypointRestrictions() when provided and array', t => { }); ava('.generateFmsWaypoint() returns a new instance of an FMS Waypoint object', t => { - const fmsMock = {}; const model = new StandardRouteWaypointModel(ROUTE_WAYPOINT_MOCK); const result = model.generateFmsWaypoint(airportModelFixtureForWaypoint); From 29931de7584cef64c8c6447094500c1147c61da6 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sat, 19 Nov 2016 20:03:11 -0600 Subject: [PATCH 18/25] feature/ATC-53 - Updates Waypoint to use ternay assignment inside of .setAltitude() and .setSpeed(). removes ava.skip from skipped tests in Waypoint.spec --- src/assets/scripts/aircraft/Waypoint.js | 14 +++++++++++--- test/aircraft/Waypoint.spec.js | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index bc331625..2228d1eb 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -162,9 +162,10 @@ export default class Waypoint { this.heading = angle; } else if (firstRouteSegment === 'KDBG' && this.heading === null) { + console.warn('It was determined that this else block is unused. If you see this message, it is in use and should be refactored.'); // FIXME: radial is not defined or set anywhere in this class // aim arrival @ middle of airspace - this.heading = this.radial + Math.PI; + // this.heading = this.radial + Math.PI; } } @@ -193,7 +194,10 @@ export default class Waypoint { const minAlt = parseInt(altitudeRestriction.replace(ABOVE_SYMBOL, ''), DECIMAL_RADIX); const minimumAltitudeWithoutSymbol = minAlt * FL_TO_THOUSANDS_MULTIPLIER; - this.altitude = Math.min(minimumAltitudeWithoutSymbol, cruiseAltitude); + // not a fan of this ternary, but I don't think there is a better way to do it + this.altitude = minimumAltitudeWithoutSymbol > cruiseAltitude + ? minimumAltitudeWithoutSymbol + : cruiseAltitude; } else if (altitudeRestriction.indexOf(BELOW_SYMBOL) !== INVALID_INDEX) { const maxAlt = parseInt(altitudeRestriction.replace(BELOW_SYMBOL, ''), DECIMAL_RADIX); const maximumAltitudeWithoutSymbol = maxAlt * FL_TO_THOUSANDS_MULTIPLIER; @@ -224,9 +228,13 @@ export default class Waypoint { if (speedRestriction.indexOf(ABOVE_SYMBOL) !== INVALID_INDEX) { // at-or-above speed restriction const minSpd = parseInt(speedRestriction.replace(ABOVE_SYMBOL, ''), DECIMAL_RADIX); - this.speed = Math.min(minSpd, cruiseSpeed); + + this.speed = minSpd > cruiseSpeed + ? minSpd + : cruiseSpeed; } else if (speedRestriction.indexOf(BELOW_SYMBOL) !== INVALID_INDEX) { const maxSpd = parseInt(speedRestriction.replace(BELOW_SYMBOL, ''), DECIMAL_RADIX); + // go as fast as restrictions permit this.speed = Math.min(maxSpd, cruiseSpeed); } else { diff --git a/test/aircraft/Waypoint.spec.js b/test/aircraft/Waypoint.spec.js index 44da0225..0d2680d5 100644 --- a/test/aircraft/Waypoint.spec.js +++ b/test/aircraft/Waypoint.spec.js @@ -78,7 +78,7 @@ ava('.setAltitude() if fix restrictions do not exist, sets the altitude by the m t.true(model.altitude === 10000); }); -ava.skip('.setAltitude() sets the altitude from existing fixRestrictions and cruiseAltitude when restriction is at or above', t => { +ava('.setAltitude() sets the altitude from existing fixRestrictions and cruiseAltitude when restriction is at or above', t => { const centerCeilingMock = 10000; const cruiseAltitudeMock = 23000; const expectedResult = 27000; @@ -110,7 +110,7 @@ ava('.setAltitude() sets the altitude from existing fixRestrictions and cruiseAl t.true(model.altitude === expectedResult); }); -ava.skip('.setSpeed() sets the speed from existing fixRestrictions and cruiseSpeed when restriction is at or above', t => { +ava('.setSpeed() sets the speed from existing fixRestrictions and cruiseSpeed when restriction is at or above', t => { const cruiseSpeedMock = 300; const expectedResult = 300; const model = new Waypoint(BASIC_WAYPOINT_MOCK, airportModelFixtureForWaypoint); From fa548e2fbeb6105ba9d19e7934be7bdd6992df82 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sat, 19 Nov 2016 23:16:46 -0600 Subject: [PATCH 19/25] feature/ATC-53 - updates _generateManualWaypoint() to pass route not fix --- src/assets/scripts/aircraft/Leg.js | 2 +- src/assets/scripts/aircraft/Waypoint.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/scripts/aircraft/Leg.js b/src/assets/scripts/aircraft/Leg.js index e4b4ba45..22c83a9d 100644 --- a/src/assets/scripts/aircraft/Leg.js +++ b/src/assets/scripts/aircraft/Leg.js @@ -305,7 +305,7 @@ export default class Leg { * @private */ _generateManualWaypoint(airport) { - const waypointToAdd = new Waypoint({ fix: this.route }, airport); + const waypointToAdd = new Waypoint({ route: this.route }, airport); this.addWaypointToLeg(waypointToAdd); } diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index 2228d1eb..c231486c 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -165,7 +165,7 @@ export default class Waypoint { console.warn('It was determined that this else block is unused. If you see this message, it is in use and should be refactored.'); // FIXME: radial is not defined or set anywhere in this class // aim arrival @ middle of airspace - // this.heading = this.radial + Math.PI; + this.heading = this.radial + Math.PI; } } From a4ee77df1e8ef808df58034e40ccc0b7d91eefcb Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sat, 19 Nov 2016 23:24:21 -0600 Subject: [PATCH 20/25] feature/ATC-53 - Adds enum for flight category - Replaces KDGB with UNASSIGNED --- .../AircraftFlightManagementSystem.js | 20 ++++++++++--------- src/assets/scripts/aircraft/Waypoint.js | 7 ++++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 6d3039b3..06c5ef83 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -6,7 +6,11 @@ import Waypoint from './Waypoint'; import Leg from './Leg'; import RouteModel from '../airport/Route/RouteModel'; import { clamp } from '../math/core'; -import { FP_LEG_TYPE } from '../constants/aircraftConstants'; +import { + FP_LEG_TYPE, + FLIGHT_CATEGORY, + WAYPOINT_NAV_MODE +} from '../constants/aircraftConstants'; import { LOG } from '../constants/logLevel'; /** @@ -87,11 +91,9 @@ export default class AircraftFlightManagementSystem { // set initial this.fp.altitude = clamp(1000, options.model.ceiling, 60000); - if (options.aircraft.category === 'arrival') { - // TODO: why KDBG? - this.prependLeg({ route: 'KDBG' }); - } else if (options.aircraft.category === 'departure') { - // TODO: if we already have a reference to the aircraft with, this.my_aircraft, whay are we getting it again here? + if (options.aircraft.category === FLIGHT_CATEGORY.ARRIVAL) { + this.prependLeg({ route: 'UNASSIGNED' }); + } else if (options.aircraft.category === FLIGHT_CATEGORY.DEPARTURE) { this.prependLeg({ route: window.airportController.airport_get().icao }); } @@ -236,7 +238,7 @@ export default class AircraftFlightManagementSystem { curr.speed = prev.speed; } - if (!curr.heading && curr.navmode === 'heading') { + if (!curr.heading && curr.navmode === WAYPOINT_NAV_MODE.HEADING) { curr.heading = prev.heading; } } @@ -260,7 +262,7 @@ export default class AircraftFlightManagementSystem { curr.speed = prev.speed; } - if (!curr.heading && curr.navmode === 'heading') { + if (!curr.heading && curr.navmode === WAYPOINT_NAV_MODE.HEADING) { curr.heading = prev.heading; } } @@ -483,7 +485,7 @@ export default class AircraftFlightManagementSystem { }); this.setAll({ - altitude: Math.max(window.airportController.airport_get().initial_alt, this.my_aircraft.altitude) + altitude: Math.max(window.airportController.airport_get().initial_alt, this.my_aircraft.altitude) }); } diff --git a/src/assets/scripts/aircraft/Waypoint.js b/src/assets/scripts/aircraft/Waypoint.js index c231486c..b29819d5 100644 --- a/src/assets/scripts/aircraft/Waypoint.js +++ b/src/assets/scripts/aircraft/Waypoint.js @@ -161,9 +161,10 @@ export default class Waypoint { const { angle } = airport.getRunway(airport.runway); this.heading = angle; - } else if (firstRouteSegment === 'KDBG' && this.heading === null) { - console.warn('It was determined that this else block is unused. If you see this message, it is in use and should be refactored.'); - // FIXME: radial is not defined or set anywhere in this class + } else if (firstRouteSegment === 'UNASSIGNED' && this.heading === null) { + // FIXME: radial is not defined or set anywhere in this class. this block DOES get hit for + // every arriving aircraft + // aim arrival @ middle of airspace this.heading = this.radial + Math.PI; } From 4776baa4ce2fa92d44cd4d83ff87f463ee351d9e Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 20 Nov 2016 19:44:23 -0600 Subject: [PATCH 21/25] feature/ATC-53 - Uncomments ga block in index.html --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 09c940ce..f10731d2 100644 --- a/index.html +++ b/index.html @@ -75,7 +75,7 @@ - + From d0fb9f98a801b1edd38bf4975f2929ad65f520e0 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 20 Nov 2016 20:43:05 -0600 Subject: [PATCH 22/25] feature/ATC-53 - adds _isNil to fms --- .../aircraft/AircraftFlightManagementSystem.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 06c5ef83..502ea335 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import _find from 'lodash/find'; import _last from 'lodash/last'; import _map from 'lodash/map'; +import _isNil from 'lodash/isNil'; import Waypoint from './Waypoint'; import Leg from './Leg'; import RouteModel from '../airport/Route/RouteModel'; @@ -329,8 +330,6 @@ export default class AircraftFlightManagementSystem { update_fp_route() { const flightPlanRoute = []; - // TODO: simplify this - // FIXME: is this.legs an array? for (let i = 0; i < this.legs.length; i++) { const leg = this.legs[i]; @@ -589,7 +588,6 @@ export default class AircraftFlightManagementSystem { const curr = this.currentWaypoint; const legs = []; - // const legs = this._generateLegsForFixOrStandardRoute(route); for (let i = 0; i < route.length; i++) { const routeSections = route[i].split('.'); @@ -603,12 +601,12 @@ export default class AircraftFlightManagementSystem { const routeModel = new RouteModel(route[i]); const currentAirport = window.airportController.airport_get(); - if (typeof currentAirport.sidCollection.findRouteByIcao(routeModel.procedure) !== 'undefined') { + if (!_isNil(currentAirport.sidCollection.findRouteByIcao(routeModel.procedure))) { // it's a SID! const legToAdd = new Leg({ type: FP_LEG_TYPE.SID, route: routeModel.routeString }, this); legs.push(legToAdd); - } else if (typeof currentAirport.starCollection.findRouteByIcao(routeModel.procedure) !== 'undefined') { + } else if (!_isNil(currentAirport.starCollection.findRouteByIcao(routeModel.procedure))) { // it's a STAR! const legToAdd = new Leg({ type: FP_LEG_TYPE.STAR, route: routeModel.routeString }, this); @@ -627,7 +625,7 @@ export default class AircraftFlightManagementSystem { } // TODO: this should be its own method - // TODO: this could be simplified. there is a lot of brnaching logic here that makes this block tough to follow. + // TODO: this could be simplified. there is a lot of branching logic here that makes this block tough to follow. // insert user's route to the legs if (!fullRouteClearance) { // Check if user's route hooks up to the current Legs anywhere @@ -808,7 +806,6 @@ export default class AircraftFlightManagementSystem { }; } - /** ************************* FMS GET FUNCTIONS ***************************/ get currentLeg() { @@ -921,7 +918,6 @@ export default class AircraftFlightManagementSystem { return null; } - // TODO: use the `RouteModel` for this return `${this.following.sid}.${this.currentLeg.route.exit}`; } From b8fda4bdeb9b3dafd54b533fd03c382afebf3ddd Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Mon, 21 Nov 2016 21:01:46 -0600 Subject: [PATCH 23/25] feature/ATC-53 - Removes _runwayCollection from StandardRouteModel * Refactors StandardRouteModel to use _entryCollection, _bodySegmentModel and _exitCollection for both SIDs and STARs * Updates tests * Adds istanbul-ignore to constructors inheriting one of the Base classes --- .../AircraftFlightManagementSystem.js | 3 +- .../scripts/aircraft/AircraftInstanceModel.js | 2 +- src/assets/scripts/aircraft/Leg.js | 11 +- .../StandardRoute/RouteSegmentCollection.js | 3 +- .../StandardRoute/RouteSegmentModel.js | 1 + .../StandardRoute/StandardRouteCollection.js | 3 +- .../StandardRoute/StandardRouteModel.js | 110 ++++++++++-------- .../standardRoute/StandardRouteModel.spec.js | 52 +++++---- 8 files changed, 101 insertions(+), 84 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index 502ea335..cb06d25b 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -108,8 +108,9 @@ export default class AircraftFlightManagementSystem { */ prependLeg(data) { const prev = this.currentWaypoint; + const legToAdd = new Leg(data, this); - this.legs.unshift(new Leg(data, this)); + this.legs.unshift(legToAdd); this.update_fp_route(); // TODO: these if blocks a repeated elsewhere, perhaps currentWaypoint can handle this logic? diff --git a/src/assets/scripts/aircraft/AircraftInstanceModel.js b/src/assets/scripts/aircraft/AircraftInstanceModel.js index d78aa0d1..bbdf650b 100644 --- a/src/assets/scripts/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/aircraft/AircraftInstanceModel.js @@ -1258,7 +1258,7 @@ export default class Aircraft { // TODO: refactor to use `StandardRouteCollection` const sid_name = apt.sids[sid_id].name; const exit = apt.getSIDExitPoint(sid_id); - const route = `${apt.icao}.${sid_id}.${exit}`; + const route = `${apt.icao.toUpperCase()}.${sid_id}.${exit}`; if (this.category !== FLIGHT_CATEGORY.DEPARTURE) { return ['fail', 'unable to fly SID, we are an inbound']; diff --git a/src/assets/scripts/aircraft/Leg.js b/src/assets/scripts/aircraft/Leg.js index 22c83a9d..e2dbc8a1 100644 --- a/src/assets/scripts/aircraft/Leg.js +++ b/src/assets/scripts/aircraft/Leg.js @@ -174,13 +174,14 @@ export default class Leg { } const airport = window.airportController.airport_get(this.route.entry); - const pairs = airport.getSID(this.route.procedure, this.route.exit, rwy); - const waypointsForSid = airport.findWaypointModelsForSid(this.route.procedure, this.route.exit, rwy); + const waypointsForSid = airport.findWaypointModelsForSid(this.route.procedure, rwy, this.route.exit); - // TODO: refactor this if + // TODO: refactor/abstract this boolean logic // Remove the placeholder leg (if present) - if (fms.my_aircraft.wow() && fms.legs.length > 0 - && fms.legs[0].route === airport.icao && pairs.length > 0 + if (fms.my_aircraft.wow() && + fms.legs.length > 0 && + fms.legs[0].route === airport.icao && + pairs.length > 0 ) { // remove the placeholder leg, to be replaced below with SID Leg fms.legs.splice(0, 1); diff --git a/src/assets/scripts/airport/StandardRoute/RouteSegmentCollection.js b/src/assets/scripts/airport/StandardRoute/RouteSegmentCollection.js index c16ae641..9ec127a4 100644 --- a/src/assets/scripts/airport/StandardRoute/RouteSegmentCollection.js +++ b/src/assets/scripts/airport/StandardRoute/RouteSegmentCollection.js @@ -32,8 +32,9 @@ export default class RouteSegmentCollection extends BaseCollection { * @constructor * @param routeSegments {object} */ + /* istanbul ignore next */ constructor(routeSegments) { - super(routeSegments); + super(); if (typeof routeSegments === 'undefined' || !_isObject(routeSegments) || _isArray(routeSegments)) { throw new TypeError(`Expected routeSegments to be an object. Instead received ${typeof routeSegments}`); diff --git a/src/assets/scripts/airport/StandardRoute/RouteSegmentModel.js b/src/assets/scripts/airport/StandardRoute/RouteSegmentModel.js index 4a9a6efe..8931000c 100644 --- a/src/assets/scripts/airport/StandardRoute/RouteSegmentModel.js +++ b/src/assets/scripts/airport/StandardRoute/RouteSegmentModel.js @@ -18,6 +18,7 @@ export default class RouteSegmentModel extends BaseModel { * @param name {string} Icao of particular waypoint * @param segmentWaypoints {array} a mixed array of strings or arrays of strings */ + /* istanbul ignore next */ constructor(name, segmentWaypoints = []) { super(); diff --git a/src/assets/scripts/airport/StandardRoute/StandardRouteCollection.js b/src/assets/scripts/airport/StandardRoute/StandardRouteCollection.js index 62cfdce1..a19ce4be 100644 --- a/src/assets/scripts/airport/StandardRoute/StandardRouteCollection.js +++ b/src/assets/scripts/airport/StandardRoute/StandardRouteCollection.js @@ -17,8 +17,9 @@ export default class StandardRouteCollection extends BaseCollection { * @constructor * @param standardRouteEnum {object} */ + /* istanbul ignore next */ constructor(standardRouteEnum) { - super(standardRouteEnum); + super(); if (typeof standardRouteEnum === 'undefined') { return; diff --git a/src/assets/scripts/airport/StandardRoute/StandardRouteModel.js b/src/assets/scripts/airport/StandardRoute/StandardRouteModel.js index c2ea89dd..7d2ee8dc 100644 --- a/src/assets/scripts/airport/StandardRoute/StandardRouteModel.js +++ b/src/assets/scripts/airport/StandardRoute/StandardRouteModel.js @@ -1,6 +1,7 @@ import _compact from 'lodash/compact'; import _forEach from 'lodash/forEach'; import _get from 'lodash/get'; +import _has from 'lodash/has'; import _isArray from 'lodash/isArray'; import _isEmpty from 'lodash/isEmpty'; import _isObject from 'lodash/isObject'; @@ -53,6 +54,7 @@ export default class StandardRouteModel extends BaseModel { * @constructor * @param standardRoute {object} */ + /* istanbul ignore next */ constructor(standardRoute) { super(); @@ -121,16 +123,6 @@ export default class StandardRouteModel extends BaseModel { */ this.exitPoints = {}; - /** - * Collection object of the `rwy` route segments - * - * @property _runwayCollection - * @type {RouteSegmentCollection} - * @default null - * @private - */ - this._runwayCollection = null; - /** * `RouteSegmentModel` for the fixes belonging to the `body` segment * @@ -182,10 +174,9 @@ export default class StandardRouteModel extends BaseModel { this.body = standardRoute.body; this.exitPoints = _get(standardRoute, 'exitPoints', {}); this.entryPoints = _get(standardRoute, 'entryPoints', {}); - this._runwayCollection = this._buildSegmentCollection(standardRoute.rwy); this._bodySegmentModel = this._buildSegmentModel(standardRoute.body); - this._exitCollection = this._buildSegmentCollection(standardRoute.exitPoints); - this._entryCollection = this._buildSegmentCollection(standardRoute.entryPoints); + + this._buildEntryAndExitCollections(standardRoute); } /** @@ -201,7 +192,6 @@ export default class StandardRouteModel extends BaseModel { this.body = []; this.exitPoints = []; this.draw = []; - this._runwayCollection = null; this._bodySegmentModel = null; this._exitCollection = null; this._entryCollection = null; @@ -328,8 +318,6 @@ export default class StandardRouteModel extends BaseModel { * @private */ _buildSegmentCollection(segment) { - // SIDS have `exitPoints` while STARs have `entryPoints`. one or the other will be `undefined` - // depending on the route type. if (typeof segment === 'undefined' || _isEmpty(segment)) { return null; } @@ -339,6 +327,28 @@ export default class StandardRouteModel extends BaseModel { return segmentCollection; } + /** + * Determine if the `standardRoute` is a sid or a star and build the entry/exit collections + * with the correct data. + * + * STARS will have `entryPoints` defined so `rwy` becomes the `_exitCollection` + * SIDS will have `exitPoints` defined so `rwy` becomes the `_entryCollection` + * + * @for StandardRouteModel + * @method _buildEntryAndExitCollections + * @param standardRoute + * @private + */ + _buildEntryAndExitCollections(standardRoute) { + if (_has(standardRoute, 'entryPoints')) { + this._entryCollection = this._buildSegmentCollection(standardRoute.entryPoints); + this._exitCollection = this._buildSegmentCollection(standardRoute.rwy); + } else if (_has(standardRoute, 'exitPoints')) { + this._entryCollection = this._buildSegmentCollection(standardRoute.rwy); + this._exitCollection = this._buildSegmentCollection(standardRoute.exitPoints); + } + } + /** * Given three functions, spread their result in an array then return the compacted result. * @@ -348,21 +358,21 @@ export default class StandardRouteModel extends BaseModel { * * @for StandardRouteModel * @method _generateFixList - * @param originSegment {function} + * @param entrySegment {function} * @param bodySegment {function} - * @param destinationSegment {function} + * @param exitSegment {function} * @return {array} * @private */ - _generateFixList = (originSegment, bodySegment, destinationSegment) => { + _generateFixList = (entrySegment, bodySegment, exitSegment) => { // in the event that one of these functions doesnt find a result set it will return an empty array. // we leverage then `lodash.compact()` below to remove any empty values from the array before // returning the `fixList`. // These functions are called synchronously and order of operation is very important here. const fixList = [ - ...originSegment, + ...entrySegment, ...bodySegment, - ...destinationSegment + ...exitSegment ]; return _compact(fixList); @@ -379,7 +389,7 @@ export default class StandardRouteModel extends BaseModel { * @private */ _findFixListForSidByRunwayAndExit = (runwayName, exitFixName) => this._generateFixList( - this._findFixListInByCollectionAndSegmentName('rwy', '_runwayCollection', runwayName), + this._findFixListInByCollectionAndSegmentName('rwy', '_entryCollection', runwayName), this._findBodyFixList(), this._findFixListInByCollectionAndSegmentName('exitPoints', '_exitCollection', exitFixName) ); @@ -397,9 +407,33 @@ export default class StandardRouteModel extends BaseModel { _findFixListForStarByEntryAndRunway = (entryFixName, runwayName) => this._generateFixList( this._findFixListInByCollectionAndSegmentName('entryPoints', '_entryCollection', entryFixName), this._findBodyFixList(), - this._findFixListInByCollectionAndSegmentName('rwy', '_runwayCollection', runwayName) + this._findFixListInByCollectionAndSegmentName('rwy', '_exitCollection', runwayName) ); + /** + * Given an `originalCollectionName`, `collectionName` and a `segmentName`, return a normalized list of + * fixes with restrictions. + * + * @for StandardRouteModel + * @method _findFixListInByCollectionAndSegmentName + * @param originalCollectionName {string} the name of the original collection from airport json, + * one of: [entryPoints, rwy, exitPoints] + * @param collectionName {string} collectionName as defined here, one of: [_entryCollection, _exitCollection] + * @segmentName {string} name of the segment to search for + * @return array {array} + */ + _findFixListInByCollectionAndSegmentName(originalCollectionName, collectionName, segmentName) { + const originalCollection = _get(this, originalCollectionName, null); + const collection = _get(this, collectionName, null); + + // specifically checking for an empty string here because this param gets a default of '' when + // it is received in to the public method + if (!originalCollection || !collection || segmentName === '') { + return []; + } + + return collection.findWaypointsForSegmentName(segmentName); + } /** * Gather a list of `StandardWaypointModel` objects for a particular route. @@ -419,8 +453,8 @@ export default class StandardRouteModel extends BaseModel { entrySegmentItems = entrySegment.items; } - if (this._runwayCollection) { - const exitSegment = this._runwayCollection.findSegmentByName(exit); + if (this._exitCollection) { + const exitSegment = this._exitCollection.findSegmentByName(exit); exitSegmentItems = exitSegment.items; } @@ -431,32 +465,6 @@ export default class StandardRouteModel extends BaseModel { ); } - /** - * Given an `originalCollectionName`, `collectionName` and a `segmentName`, return a normalized list of - * fixes with restrictions. - * - * @for StandardRouteModel - * @method _findFixListInByCollectionAndSegmentName - * @param originalCollectionName {string} the name of the original collection from airport json, - * one of: [entryPoints, rwy, exitPoints] - * @param collectionName {string} collectionName as defined here, one of: - * [_runwayCollection, _entryCollection, _exitCollection] - * @segmentName {string} name of the segment to search for - * @return array {array} - */ - _findFixListInByCollectionAndSegmentName(originalCollectionName, collectionName, segmentName) { - const originalCollection = _get(this, originalCollectionName, null); - const collection = _get(this, collectionName, null); - - // specifically checking for an empty string here because this param gets a default of '' when - // it is received in to the public method - if (!originalCollection || !collection || segmentName === '') { - return []; - } - - return collection.findWaypointsForSegmentName(segmentName); - } - /** * Find list of waypoints for the `body` segment * diff --git a/test/airport/standardRoute/StandardRouteModel.spec.js b/test/airport/standardRoute/StandardRouteModel.spec.js index 3210ca3a..4a75f3d4 100644 --- a/test/airport/standardRoute/StandardRouteModel.spec.js +++ b/test/airport/standardRoute/StandardRouteModel.spec.js @@ -3,6 +3,8 @@ import ava from 'ava'; import sinon from 'sinon'; import _isArray from 'lodash/isArray'; import _isEqual from 'lodash/isEqual'; +import _keys from 'lodash/keys'; +import _map from 'lodash/map'; import StandardRouteModel from '../../../src/assets/scripts/airport/StandardRoute/StandardRouteModel'; import RouteSegmentCollection from '../../../src/assets/scripts/airport/StandardRoute/RouteSegmentCollection'; @@ -11,7 +13,7 @@ import StandardRouteWaypointModel from '../../../src/assets/scripts/airport/Stan import FixCollection from '../../../src/assets/scripts/airport/Fix/FixCollection'; import { airportPositionFixture } from '../../fixtures/airportFixtures'; -import { FIX_LIST_MOCK } from '../Fix/_mocks/fixMocks'; +import { FIX_LIST_MOCK } from '../fix/_mocks/fixMocks'; import { STAR_LIST_MOCK, @@ -47,7 +49,7 @@ ava('does not throw when instantiated with vaild parameters', t => { t.notThrows(() => new StandardRouteModel(STAR_WITHOUT_RWY)); t.true(result.name === SID_MOCK.name); t.true(result.icao === SID_MOCK.icao); - t.true(result._runwayCollection instanceof RouteSegmentCollection); + t.true(result._entryCollection instanceof RouteSegmentCollection); t.true(result._bodySegmentModel instanceof RouteSegmentModel); t.true(result._exitCollection instanceof RouteSegmentCollection); }); @@ -158,7 +160,7 @@ ava('.findStandardWaypointModelsForEntryAndExit() returns a list of `StandardRou ava('.findStandardWaypointModelsForEntryAndExit() does call ._updateWaypointsWithPreviousWaypointData() if isPreSpawn is true', t => { const model = new StandardRouteModel(STAR_LIST_MOCK.GRNPA1); const spy = sinon.spy(model, '_updateWaypointsWithPreviousWaypointData'); - const isPreSpawn = true + const isPreSpawn = true; model.findStandardWaypointModelsForEntryAndExit('MLF', '19R', isPreSpawn); @@ -168,7 +170,7 @@ ava('.findStandardWaypointModelsForEntryAndExit() does call ._updateWaypointsWit ava('.findStandardWaypointModelsForEntryAndExit() does not call ._updateWaypointsWithPreviousWaypointData() if isPreSpawn is false', t => { const model = new StandardRouteModel(STAR_LIST_MOCK.GRNPA1); const spy = sinon.spy(model, '_updateWaypointsWithPreviousWaypointData'); - const isPreSpawn = false + const isPreSpawn = false; model.findStandardWaypointModelsForEntryAndExit('MLF', '19R', isPreSpawn); @@ -224,6 +226,24 @@ ava('._buildSegmentCollection() returns null if segment is an empty object', t = t.true(result === null); }); +ava('._buildEntryAndExitCollections() maps rwy fixes to _exitCollection when entryPoints is present', t => { + const model = new StandardRouteModel(STAR_MOCK); + model._buildEntryAndExitCollections(STAR_MOCK); + + const segmentModelNames = _map(model._exitCollection._items, (segmentModel) => segmentModel.name); + + t.true(_isEqual(segmentModelNames, _keys(STAR_MOCK.rwy))); +}); + +ava('._buildEntryAndExitCollections() maps rwy fixes to _entryCollection when exitPoints is present', t => { + const model = new StandardRouteModel(SID_MOCK); + model._buildEntryAndExitCollections(SID_MOCK); + + const segmentModelNames = _map(model._entryCollection._items, (segmentModel) => segmentModel.name); + + t.true(_isEqual(segmentModelNames, _keys(SID_MOCK.rwy))); +}); + ava('._findBodyFixList() returns an empty array when ._bodySegmentModel is undefined', t => { const model = new StandardRouteModel(SID_WITHOUT_BODY_MOCK); @@ -235,7 +255,7 @@ ava('._findBodyFixList() returns an empty array when ._bodySegmentModel is undef t.true(result.length === 0); }); -ava('._findStandardWaypointModelsForRoute() returns a list of StandardRouteWaypointModels when _entryCollection and _runwayCollection exist', t => { +ava('._findStandardWaypointModelsForRoute() returns a list of StandardRouteWaypointModels when _entryCollection and _exitCollection exist', t => { const model = new StandardRouteModel(STAR_MOCK); const result = model._findStandardWaypointModelsForRoute(ENTRY_FIXNAME_MOCK, RUNWAY_NAME_MOCK); @@ -244,12 +264,12 @@ ava('._findStandardWaypointModelsForRoute() returns a list of StandardRouteWaypo ava('._findStandardWaypointModelsForRoute() returns a list of StandardRouteWaypointModels when _bodySegmentModel does not exist', t => { const model = new StandardRouteModel(SID_WITHOUT_BODY_MOCK); - const result = model._findStandardWaypointModelsForRoute(ENTRY_FIXNAME_MOCK, RUNWAY_NAME_MOCK); + const result = model._findStandardWaypointModelsForRoute(RUNWAY_NAME_MOCK, 'MLF'); - t.true(result.length === 5); + t.true(result.length === 6); }); -ava('._findStandardWaypointModelsForRoute() returns a list of StandardRouteWaypointModels when _runwayCollection does not exist', t => { +ava('._findStandardWaypointModelsForRoute() returns a list of StandardRouteWaypointModels when _exitCollection does not exist', t => { const model = new StandardRouteModel(STAR_WITHOUT_RWY); const result = model._findStandardWaypointModelsForRoute('BETHL', ''); @@ -271,22 +291,6 @@ ava('._findFixListInByCollectionAndSegmentName() returns an array of normalized t.true(_isEqual(result, expectedResult)); }); -ava('._findFixListInByCollectionAndSegmentName() returns an array of normalized fixes from the _runwayCollection', t => { - const expectedResult = [ - ['PIRMD', null], - ['ROPPR', 'A70'], - ['MDDOG', 'A90'], - ['TARRK', 'A110'] - ]; - const model = new StandardRouteModel(SID_MOCK); - - t.notThrows(() => model._findFixListInByCollectionAndSegmentName('rwy', '_runwayCollection', RUNWAY_NAME_MOCK)); - - const result = model._findFixListInByCollectionAndSegmentName('rwy', '_runwayCollection', RUNWAY_NAME_MOCK); - - t.true(_isEqual(result, expectedResult)); -}); - ava('._findFixListInByCollectionAndSegmentName() returns an array of normalized fixes from the _exitCollection', t => { const expectedResult = [ ['DBIGE', 'A210+'], From 2a92c41a8d9f1b8bd61adc858e033d2d38b4eef9 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Wed, 23 Nov 2016 18:23:40 -0600 Subject: [PATCH 24/25] feature/ATC-53 - Adds doc blocks to AircraftFlightManagementSystem class properties --- .../AircraftFlightManagementSystem.js | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js index cb06d25b..0a0497c1 100644 --- a/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/aircraft/AircraftFlightManagementSystem.js @@ -62,23 +62,60 @@ const WAYPOINT_WITHIN_LEG = 1; * @class AircraftFlightManagementSystem */ export default class AircraftFlightManagementSystem { + /** + * @for AircraftFlightManagementSystem + * @constructor + * @param options {object} + */ constructor(options) { + /** + * @property may_aircrafts_eid + * @type {number} + * @default options.aircraft.eid + */ this.my_aircrafts_eid = options.aircraft.eid; // TODO: we should remove this reference and instead supply methods that the aircraft can call via the fms + /** + * @property my_aircraft + * @type {AircrafInstanceModel} + * @default options.aircraft + */ this.my_aircraft = options.aircraft; + /** + * @property legs + * @type {array} + * @default [] + */ this.legs = []; - this.current = [0, 0]; // [current_Leg, current_Waypoint_within_that_Leg] + /** + * Current indicies for Leg and Waypoint within that Leg. + * + * [current_Leg, current_Waypoint_within_that_Leg] + * + * @property current + * @type {array} + * @default [0, 0] + */ + this.current = [0, 0]; // TODO: possible model object here + /** + * @property fp + * @type {object} + */ this.fp = { altitude: null, route: [] }; // TODO: possible model object here + /** + * @property following + * @type {object} + */ this.following = { sid: null, // Standard Instrument Departure Procedure star: null, // Standard Terminal Arrival Route Procedure @@ -88,8 +125,9 @@ export default class AircraftFlightManagementSystem { anything: false // T/F flag for if anything is being "followed" }; + // TODO: this doesn't belong in the constructor // TODO: enumerate the magic numbers - // set initial + // set initial altitude this.fp.altitude = clamp(1000, options.model.ceiling, 60000); if (options.aircraft.category === FLIGHT_CATEGORY.ARRIVAL) { From cbfd296b8b543ceba017b7ae634609b62ce40f6f Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Wed, 23 Nov 2016 21:16:42 -0600 Subject: [PATCH 25/25] feature/ATC-53 - Updates changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 959d04c1..d252fefc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ## 3.2.0 (December 20, 2016) --- ### Major +* Integrates `sidCollection` and `starCollection` with `RouteModel` within `AircraftInstanceModel` [#53](https://github.com/n8rzz/atc/issues/53) + - Creates getters for `currentLeg` and `currentWaypoint` + - Abstracts restrictions logic to live within `Waypoint` + - Consolidates `runSID()` and `climbViaSid()` logic