/**
 * Types of subscribable array change event.
 */
 var SubscribableArrayEventType;
 (function (SubscribableArrayEventType) {
     /** An element was added. */
     SubscribableArrayEventType["Added"] = "Added";
     /** An element was removed. */
     SubscribableArrayEventType["Removed"] = "Removed";
     /** The array was cleared. */
     SubscribableArrayEventType["Cleared"] = "Cleared";
 })(SubscribableArrayEventType || (SubscribableArrayEventType = {}));
 /**
  * An abstract implementation of a subscribable which allows adding, removing, and notifying subscribers.
  */
 class AbstractSubscribable {
     constructor() {
         this.subs = [];
     }
     /** @inheritdoc */
     sub(fn, initialNotify) {
         this.subs.push(fn);
         if (initialNotify) {
             fn(this.get());
         }
     }
     /** @inheritdoc */
     unsub(fn) {
         const index = this.subs.indexOf(fn);
         if (index >= 0) {
             this.subs.splice(index, 1);
         }
     }
     /**
      * Notifies subscribers that this subscribable's value has changed.
      */
     notify() {
         const subLen = this.subs.length;
         for (let i = 0; i < subLen; i++) {
             try {
                 this.subs[i](this.get());
             }
             catch (error) {
                 console.error(`AbstractSubscribable: error in handler: ${error}`);
                 if (error instanceof Error) {
                     console.error(error.stack);
                 }
             }
         }
     }
 }
 /**
  * Checks if two values are equal using the strict equality operator.
  * @param a The first value.
  * @param b The second value.
  * @returns whether a and b are equal.
  */
 AbstractSubscribable.DEFAULT_EQUALITY_FUNC = (a, b) => a === b;
 
 /**
  * A subscribable subject whose value can be freely manipulated.
  */
 class Subject extends AbstractSubscribable {
     /**
      * Constructs an observable Subject.
      * @param value The initial value.
      * @param equalityFunc The function to use to check for equality.
      * @param mutateFunc The function to use to mutate the subject's value.
      */
     constructor(value, equalityFunc, mutateFunc) {
         super();
         this.value = value;
         this.equalityFunc = equalityFunc;
         this.mutateFunc = mutateFunc;
     }
     /**
      * Creates and returns a new Subject.
      * @param v The initial value of the subject.
      * @param equalityFunc The function to use to check for equality between subject values. Defaults to the strict
      * equality comparison (`===`).
      * @param mutateFunc The function to use to change the subject's value. If not defined, new values will replace
      * old values by variable assignment.
      * @returns A Subject instance.
      */
     static create(v, equalityFunc, mutateFunc) {
         return new Subject(v, equalityFunc !== null && equalityFunc !== void 0 ? equalityFunc : Subject.DEFAULT_EQUALITY_FUNC, mutateFunc);
     }
     /**
      * Sets the value of this subject and notifies subscribers if the value changed.
      * @param value The new value.
      */
     set(value) {
         if (!this.equalityFunc(value, this.value)) {
             if (this.mutateFunc) {
                 this.mutateFunc(this.value, value);
             }
             else {
                 this.value = value;
             }
             this.notify();
         }
     }
     /**
      * Applies a partial set of properties to this subject's value and notifies subscribers if the value changed as a
      * result.
      * @param value The properties to apply.
      */
     apply(value) {
         let changed = false;
         for (const prop in value) {
             changed = value[prop] !== this.value[prop];
             if (changed) {
                 break;
             }
         }
         Object.assign(this.value, value);
         changed && this.notify();
     }
     /** @inheritdoc */
     notify() {
         super.notify();
     }
     /**
      * Gets the value of this subject.
      * @returns The value of this subject.
      */
     get() {
         return this.value;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     map(fn, equalityFunc, mutateFunc, initialVal) {
         const mapFunc = (inputs, previousVal) => fn(inputs[0], previousVal);
         return mutateFunc
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             ? MappedSubject.create(mapFunc, equalityFunc, mutateFunc, initialVal, this)
             : MappedSubject.create(mapFunc, equalityFunc !== null && equalityFunc !== void 0 ? equalityFunc : AbstractSubscribable.DEFAULT_EQUALITY_FUNC, this);
     }
 }
 /**
  * A subscribable subject that is a mapped stream from one or more input subscribables.
  */
 class MappedSubject extends AbstractSubscribable {
     /**
      * Creates a new MappedSubject.
      * @param mapFunc The function which maps this subject's inputs to a value.
      * @param equalityFunc The function which this subject uses to check for equality between values.
      * @param mutateFunc The function which this subject uses to change its value.
      * @param initialVal The initial value of this subject.
      * @param inputs The subscribables which provide the inputs to this subject.
      */
     constructor(mapFunc, equalityFunc, mutateFunc, initialVal, ...inputs) {
         super();
         this.mapFunc = mapFunc;
         this.equalityFunc = equalityFunc;
         this.inputs = inputs;
         this.inputValues = inputs.map(input => input.get());
         if (initialVal && mutateFunc) {
             this.value = initialVal;
             mutateFunc(this.value, this.mapFunc(this.inputValues));
             this.mutateFunc = (newVal) => { mutateFunc(this.value, newVal); };
         }
         else {
             this.value = this.mapFunc(this.inputValues);
             this.mutateFunc = (newVal) => { this.value = newVal; };
         }
         this.inputHandlers = this.inputs.map((input, index) => this.updateValue.bind(this, index));
         for (let i = 0; i < inputs.length; i++) {
             inputs[i].sub(this.inputHandlers[i]);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static create(mapFunc, ...args) {
         let equalityFunc, mutateFunc, initialVal;
         if (typeof args[0] === 'function') {
             equalityFunc = args.shift();
         }
         else {
             equalityFunc = MappedSubject.DEFAULT_EQUALITY_FUNC;
         }
         if (typeof args[0] === 'function') {
             mutateFunc = args.shift();
             initialVal = args.shift();
         }
         return new MappedSubject(mapFunc, equalityFunc, mutateFunc, initialVal, ...args);
     }
     /**
      * Updates an input value, then re-maps this subject's value, and notifies subscribers if this results in a change to
      * the mapped value according to this subject's equality function.
      * @param index The index of the input value to update.
      */
     updateValue(index) {
         this.inputValues[index] = this.inputs[index].get();
         const value = this.mapFunc(this.inputValues, this.value);
         if (!this.equalityFunc(this.value, value)) {
             this.mutateFunc(value);
             this.notify();
         }
     }
     /**
      * Gets the current value of the subject.
      * @returns The current value.
      */
     get() {
         return this.value;
     }
     /**
      * Destroys the subscription to the parent subscribable.
      */
     destroy() {
         for (let i = 0; i < this.inputs.length; i++) {
             this.inputs[i].unsub(this.inputHandlers[i]);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     map(fn, equalityFunc, mutateFunc, initialVal) {
         const mapFunc = (inputs, previousVal) => fn(inputs[0], previousVal);
         return new MappedSubject(mapFunc, equalityFunc !== null && equalityFunc !== void 0 ? equalityFunc : MappedSubject.DEFAULT_EQUALITY_FUNC, mutateFunc, initialVal, this);
     }
 }
 
 /**
  * A class for subjects that return a computed value.
  * @class ComputedSubject
  * @template I The type of the input value.
  * @template T The type of the computed output value.
  */
 class ComputedSubject {
     /**
      * Creates an instance of ComputedSubject.
      * @param value The initial value.
      * @param computeFn The computation function.
      */
     constructor(value, computeFn) {
         this.computeFn = computeFn;
         this._subs = [];
         this._value = value;
         this._computedValue = computeFn(value);
     }
     /**
      * Creates and returns a new ComputedSubject.
      * @param v The initial value of the Subject.
      * @param fn A function which transforms raw values to computed values.
      * @returns A ComputedSubject instance.
      */
     static create(v, fn) {
         return new ComputedSubject(v, fn);
     }
     /**
      * Sets the new value and notifies the subscribers when value changed.
      * @param value The new value.
      */
     set(value) {
         this._value = value;
         const compValue = this.computeFn(value);
         if (compValue !== this._computedValue) {
             this._computedValue = compValue;
             const subLen = this._subs.length;
             for (let i = 0; i < subLen; i++) {
                 this._subs[i](this._computedValue, this._value);
             }
         }
     }
     /**
      * Gets the computed value of the Subject.
      * @returns The computed value.
      */
     get() {
         return this._computedValue;
     }
     /**
      * Gets the raw value of the Subject.
      * @returns The raw value.
      */
     getRaw() {
         return this._value;
     }
     /**
      * Subscribes to the subject with a callback function. The function will be called whenever the computed value of
      * this Subject changes.
      * @param fn A callback function.
      * @param initialNotify Whether to immediately notify the callback function with the current compured and raw values
      * of this Subject after it is subscribed. False by default.
      */
     sub(fn, initialNotify) {
         this._subs.push(fn);
         if (initialNotify) {
             fn(this._computedValue, this._value);
         }
     }
     /**
      * Unsubscribes a callback function from this Subject.
      * @param fn The callback function to unsubscribe.
      */
     unsub(fn) {
         const index = this._subs.indexOf(fn);
         if (index >= 0) {
             this._subs.splice(index, 1);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     map(fn, equalityFunc, mutateFunc, initialVal) {
         const mapFunc = (inputs, previousVal) => fn(inputs[0], previousVal);
         return mutateFunc
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             ? MappedSubject.create(mapFunc, equalityFunc, mutateFunc, initialVal, this)
             : MappedSubject.create(mapFunc, equalityFunc !== null && equalityFunc !== void 0 ? equalityFunc : AbstractSubscribable.DEFAULT_EQUALITY_FUNC, this);
     }
 }
 
 /* eslint-disable no-inner-declarations */
 /** A releative render position. */
 var RenderPosition;
 (function (RenderPosition) {
     RenderPosition[RenderPosition["Before"] = 0] = "Before";
     RenderPosition[RenderPosition["After"] = 1] = "After";
     RenderPosition[RenderPosition["In"] = 2] = "In";
 })(RenderPosition || (RenderPosition = {}));
 /**
  * A display component in the component framework.
  * @typedef P The type of properties for this component.
  * @typedef C The type of context that this component might have.
  */
 class DisplayComponent {
     /**
      * Creates an instance of a DisplayComponent.
      * @param props The propertis of the component.
      */
     constructor(props) {
         /** The context on this component, if any. */
         this.context = undefined;
         /** The type of context for this component, if any. */
         this.contextType = undefined;
         this.props = props;
     }
     /**
      * A callback that is called before the component is rendered.
      */
     onBeforeRender() { return; }
     /**
      * A callback that is called after the component is rendered.
      * @param node The component's VNode.
      */
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     onAfterRender(node) { return; }
     /**
      * Destroys this component.
      */
     destroy() { return; }
     /**
      * Gets a context data subscription from the context collection.
      * @param context The context to get the subscription for.
      * @returns The requested context.
      * @throws An error if no data for the specified context type could be found.
      */
     getContext(context) {
         if (this.context !== undefined && this.contextType !== undefined) {
             const index = this.contextType.indexOf(context);
             return this.context[index];
         }
         throw new Error('Could not find the provided context type.');
     }
 }
 /**
  * A reference to a component or element node.
  */
 class NodeReference {
     constructor() {
         /** The internal reference instance. */
         this._instance = null;
     }
     /**
      * The instance of the element or component.
      * @returns The instance of the element or component.
      */
     get instance() {
         if (this._instance !== null) {
             return this._instance;
         }
         throw new Error('Instance was null.');
     }
     /**
      * Sets the value of the instance.
      */
     set instance(val) {
         this._instance = val;
     }
     /**
      * Gets the instance, or null if the instance is not populated.
      * @returns The component or element instance.
      */
     getOrDefault() {
         return this._instance;
     }
 }
 /**
  * Provides a context of data that can be passed down to child components via a provider.
  */
 class Context {
     /**
      * Creates an instance of a Context.
      * @param defaultValue The default value of this context.
      */
     constructor(defaultValue) {
         this.defaultValue = defaultValue;
         /**
          * The provider component that can be set to a specific context value.
          * @param props The props of the provider component.
          * @returns A new context provider.
          */
         this.Provider = (props) => new ContextProvider(props, this);
     }
 }
 /**
  * A provider component that can be set to a specific context value.
  */
 class ContextProvider extends DisplayComponent {
     /**
      * Creates an instance of a ContextProvider.
      * @param props The props on the component.
      * @param parent The parent context instance for this provider.
      */
     constructor(props, parent) {
         super(props);
         this.parent = parent;
     }
     /** @inheritdoc */
     render() {
         var _a;
         const children = (_a = this.props.children) !== null && _a !== void 0 ? _a : [];
         return FSComponent.buildComponent(FSComponent.Fragment, this.props, ...children);
     }
 }
 /**
  * The FS component namespace.
  */
 // eslint-disable-next-line @typescript-eslint/no-namespace
 var FSComponent;
 (function (FSComponent) {
     /**
      * Valid SVG element tags.
      */
     const svgTags = {
         'circle': true,
         'clipPath': true,
         'color-profile': true,
         'cursor': true,
         'defs': true,
         'desc': true,
         'ellipse': true,
         'g': true,
         'image': true,
         'line': true,
         'linearGradient': true,
         'marker': true,
         'mask': true,
         'path': true,
         'pattern': true,
         'polygon': true,
         'polyline': true,
         'radialGradient': true,
         'rect': true,
         'stop': true,
         'svg': true,
         'text': true
     };
     /**
      * A fragment of existing elements with no specific root.
      * @param props The fragment properties.
      * @returns The fragment children.
      */
     function Fragment(props) {
         return props.children;
     }
     FSComponent.Fragment = Fragment;
     /**
      * Builds a JSX based FSComponent.
      * @param type The DOM element tag that will be built.
      * @param props The properties to apply to the DOM element.
      * @param children Any children of this DOM element.
      * @returns The JSX VNode for the component or element.
      */
     // eslint-disable-next-line no-inner-declarations
     function buildComponent(type, props, ...children) {
         let vnode = null;
         if (typeof type === 'string') {
             let element;
             if (svgTags[type] !== undefined) {
                 element = document.createElementNS('http://www.w3.org/2000/svg', type);
             }
             else {
                 element = document.createElement(type);
             }
             if (props !== null) {
                 for (const key in props) {
                     if (key === 'ref' && props.ref !== undefined) {
                         props.ref.instance = element;
                     }
                     else {
                         const prop = props[key];
                         if (prop instanceof Subject || prop instanceof MappedSubject || prop instanceof ComputedSubject) {
                             element.setAttribute(key, prop.get());
                             prop.sub((v) => {
                                 element.setAttribute(key, v);
                             });
                         }
                         else {
                             element.setAttribute(key, prop);
                         }
                     }
                 }
             }
             vnode = {
                 instance: element,
                 props: props,
                 children: null
             };
             vnode.children = createChildNodes(vnode, children);
         }
         else if (typeof type === 'function') {
             if (children !== null && props === null) {
                 props = {
                     children: children
                 };
             }
             else if (props !== null) {
                 props.children = children;
             }
             if (typeof type === 'function' && type.name === 'Fragment') {
                 let childNodes = type(props);
                 //Handle the case where the single fragment children is an array of nodes passsed down from above
                 if (childNodes !== null && childNodes.length > 0 && Array.isArray(childNodes[0])) {
                     childNodes = childNodes[0];
                 }
                 vnode = {
                     instance: null,
                     props,
                     children: childNodes
                 };
             }
             else {
                 let instance;
                 try {
                     instance = type(props);
                 }
                 catch (_a) {
                     instance = new type(props);
                 }
                 if (props !== null && props.ref !== null && props.ref !== undefined) {
                     props.ref.instance = instance;
                 }
                 if (instance.contextType !== undefined) {
                     instance.context = instance.contextType.map(c => Subject.create(c.defaultValue));
                 }
                 vnode = {
                     instance,
                     props,
                     children: [instance.render()]
                 };
             }
         }
         return vnode;
     }
     FSComponent.buildComponent = buildComponent;
     /**
      * Creates the collection of child VNodes.
      * @param parent The parent VNode.
      * @param children The JSX children to convert to nodes.
      * @returns A collection of child VNodes.
      */
     function createChildNodes(parent, children) {
         let vnodes = null;
         if (children !== null && children !== undefined && children.length > 0) {
             vnodes = [];
             for (const child of children) {
                 if (child !== null) {
                     if (child instanceof Subject || child instanceof MappedSubject || child instanceof ComputedSubject) {
                         const subjectValue = child.get().toString();
                         const node = {
                             instance: subjectValue === '' ? ' ' : subjectValue,
                             children: null,
                             props: null,
                             root: undefined,
                         };
                         child.sub((v) => {
                             if (node.root !== undefined) {
                                 // TODO workaround. gotta find a solution for the text node vanishing when text is empty
                                 node.root.nodeValue = v === '' ? ' ' : v.toString();
                             }
                         });
                         vnodes.push(node);
                     }
                     else if (child instanceof Array) {
                         const arrayNodes = createChildNodes(parent, child);
                         if (arrayNodes !== null) {
                             vnodes.push(...arrayNodes);
                         }
                     }
                     else if (typeof child === 'string' || typeof child === 'number') {
                         vnodes.push(createStaticContentNode(child));
                     }
                     else if (typeof child === 'object') {
                         vnodes.push(child);
                     }
                 }
             }
         }
         return vnodes;
     }
     FSComponent.createChildNodes = createChildNodes;
     /**
      * Creates a static content VNode.
      * @param content The content to create a node for.
      * @returns A static content VNode.
      */
     function createStaticContentNode(content) {
         return {
             instance: content,
             children: null,
             props: null
         };
     }
     FSComponent.createStaticContentNode = createStaticContentNode;
     /**
      * Renders a VNode to a DOM element.
      * @param node The node to render.
      * @param element The DOM element to render to.
      * @param position The RenderPosition to put the item in.
      */
     function render(node, element, position = RenderPosition.In) {
         if (node.children && node.children.length > 0 && element !== null) {
             const componentInstance = node.instance;
             if (componentInstance !== null && componentInstance.onBeforeRender !== undefined) {
                 componentInstance.onBeforeRender();
             }
             if (node.instance instanceof HTMLElement || node.instance instanceof SVGElement) {
                 insertNode(node, position, element);
             }
             else {
                 for (const child of node.children) {
                     insertNode(child, position, element);
                 }
             }
             const instance = node.instance;
             if (instance instanceof ContextProvider) {
                 visitNodes(node, (n) => {
                     const nodeInstance = n.instance;
                     if (nodeInstance !== null && nodeInstance.contextType !== undefined) {
                         const contextSlot = nodeInstance.contextType.indexOf(instance.parent);
                         if (contextSlot >= 0) {
                             if (nodeInstance.context === undefined) {
                                 nodeInstance.context = [];
                             }
                             nodeInstance.context[contextSlot].set(instance.props.value);
                         }
                         if (nodeInstance instanceof ContextProvider && nodeInstance !== instance && nodeInstance.parent === instance.parent) {
                             return true;
                         }
                     }
                     return false;
                 });
             }
             if (componentInstance !== null && componentInstance.onAfterRender !== undefined) {
                 componentInstance.onAfterRender(node);
             }
         }
     }
     FSComponent.render = render;
     /**
      * Inserts a node into the DOM.
      * @param node The node to insert.
      * @param position The position to insert the node in.
      * @param element The element to insert relative to.
      */
     function insertNode(node, position, element) {
         var _a, _b, _c, _d, _e, _f;
         if (node.instance instanceof HTMLElement || node.instance instanceof SVGElement) {
             switch (position) {
                 case RenderPosition.In:
                     element.appendChild(node.instance);
                     node.root = (_a = element.lastChild) !== null && _a !== void 0 ? _a : undefined;
                     break;
                 case RenderPosition.Before:
                     element.insertAdjacentElement('beforebegin', node.instance);
                     node.root = (_b = element.previousSibling) !== null && _b !== void 0 ? _b : undefined;
                     break;
                 case RenderPosition.After:
                     element.insertAdjacentElement('afterend', node.instance);
                     node.root = (_c = element.nextSibling) !== null && _c !== void 0 ? _c : undefined;
                     break;
             }
             if (node.children !== null) {
                 for (const child of node.children) {
                     insertNode(child, RenderPosition.In, node.instance);
                 }
             }
         }
         else if (typeof node.instance === 'string') {
             switch (position) {
                 case RenderPosition.In:
                     element.insertAdjacentHTML('beforeend', node.instance);
                     node.root = (_d = element.lastChild) !== null && _d !== void 0 ? _d : undefined;
                     break;
                 case RenderPosition.Before:
                     element.insertAdjacentHTML('beforebegin', node.instance);
                     node.root = (_e = element.previousSibling) !== null && _e !== void 0 ? _e : undefined;
                     break;
                 case RenderPosition.After:
                     element.insertAdjacentHTML('afterend', node.instance);
                     node.root = (_f = element.nextSibling) !== null && _f !== void 0 ? _f : undefined;
                     break;
             }
         }
         else if (Array.isArray(node)) {
             for (let i = 0; i < node.length; i++) {
                 render(node[i], element);
             }
         }
         else {
             render(node, element);
         }
     }
     /**
      * Render a node before a DOM element.
      * @param node The node to render.
      * @param element The element to render boeore.
      */
     function renderBefore(node, element) {
         render(node, element, RenderPosition.Before);
     }
     FSComponent.renderBefore = renderBefore;
     /**
      * Render a node after a DOM element.
      * @param node The node to render.
      * @param element The element to render after.
      */
     function renderAfter(node, element) {
         render(node, element, RenderPosition.After);
     }
     FSComponent.renderAfter = renderAfter;
     /**
      * Remove a previously rendered element.  Currently, this is just a simple
      * wrapper so that all of our high-level "component maniuplation" state is kept
      * in the FSComponent API, but it's not doing anything other than a simple
      * remove() on the element.   This can probably be enhanced.
      * @param element The element to remove.
      */
     function remove(element) {
         if (element !== null) {
             element.remove();
         }
     }
     FSComponent.remove = remove;
     /**
      * Creates a component or element node reference.
      * @returns A new component or element node reference.
      */
     function createRef() {
         return new NodeReference();
     }
     FSComponent.createRef = createRef;
     /**
      * Creates a new context to hold data for passing to child components.
      * @param defaultValue The default value of this context.
      * @returns A new context.
      */
     function createContext(defaultValue) {
         return new Context(defaultValue);
     }
     FSComponent.createContext = createContext;
     /**
      * Visits VNodes with a supplied visitor function within the given children tree.
      * @param node The node to visit.
      * @param visitor The visitor function to inspect VNodes with. Return true if the search should stop at the visited
      * node and not proceed any further down the node's children.
      * @returns True if the visitation should break, or false otherwise.
      */
     function visitNodes(node, visitor) {
         const stopVisitation = visitor(node);
         if (!stopVisitation && node.children !== null) {
             for (let i = 0; i < node.children.length; i++) {
                 visitNodes(node.children[i], visitor);
             }
         }
         return true;
     }
     FSComponent.visitNodes = visitNodes;
     /**
      * An empty callback handler.
      */
     FSComponent.EmptyHandler = () => { return; };
 })(FSComponent || (FSComponent = {}));
 FSComponent.Fragment;
 
 /**
  * A number with an associated unit. Each NumberUnit is created with a reference unit type,
  * which cannot be changed after instantiation. The reference unit type determines how the
  * value of the NumberUnit is internally represented. Each NumberUnit also maintains an
  * active unit type, which can be dynamically changed at any time.
  */
 class NumberUnit {
     /**
      * Constructor.
      * @param number - the initial numeric value of the new NumberUnit.
      * @param unit - the unit type of the new NumberUnit.
      */
     constructor(number, unit) {
         this._number = number;
         this._unit = unit;
         this.readonly = new NumberUnitReadOnly(this);
     }
     /**
      * Gets this NumberUnit's numeric value.
      * @returns This NumberUnit's numeric value.
      */
     get number() {
         return this._number;
     }
     /**
      * Gets this NumberUnit's unit type.
      * @returns This NumberUnit's unit type.
      */
     get unit() {
         return this._unit;
     }
     /**
      * Converts a value to a numeric value with this NumberUnit's unit type.
      * @param value - the value.
      * @param unit - the unit type of the new value. Defaults to this NumberUnit's unit type. This argument is ignored if
      * value is a NumberUnit.
      * @returns the numeric of the value with this NumberUnit's unit type.
      */
     toNumberOfThisUnit(value, unit) {
         if ((typeof value !== 'number') && this.unit.canConvert(value.unit)) {
             return this.unit.convertFrom(value.number, value.unit);
         }
         if (typeof value === 'number' && (!unit || this.unit.canConvert(unit))) {
             return unit ? this.unit.convertFrom(value, unit) : value;
         }
         return undefined;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     set(arg1, arg2) {
         const converted = this.toNumberOfThisUnit(arg1, arg2);
         if (converted !== undefined) {
             this._number = converted;
             return this;
         }
         throw new Error('Invalid unit conversion attempted.');
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     add(arg1, arg2, arg3) {
         const isArg2NumberUnit = arg2 instanceof NumberUnit;
         const converted = this.toNumberOfThisUnit(arg1, isArg2NumberUnit ? undefined : arg2);
         if (converted !== undefined) {
             let out = isArg2NumberUnit ? arg2 : arg3;
             if (out) {
                 out.set(this.number + converted, this.unit);
             }
             else {
                 out = this;
                 this._number += converted;
             }
             return out;
         }
         throw new Error('Invalid unit conversion attempted.');
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     subtract(arg1, arg2, arg3) {
         const isArg2NumberUnit = arg2 instanceof NumberUnit;
         const converted = this.toNumberOfThisUnit(arg1, isArg2NumberUnit ? undefined : arg2);
         if (converted !== undefined) {
             let out = isArg2NumberUnit ? arg2 : arg3;
             if (out) {
                 out.set(this.number - converted, this.unit);
             }
             else {
                 out = this;
                 this._number -= converted;
             }
             return out;
         }
         throw new Error('Invalid unit conversion attempted.');
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     scale(factor, out) {
         if (out) {
             return out.set(this.number * factor, this.unit);
         }
         else {
             this._number *= factor;
             return this;
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     ratio(value, unit) {
         const converted = this.toNumberOfThisUnit(value, unit);
         if (converted) {
             return this.number / converted;
         }
         throw new Error('Invalid unit conversion attempted.');
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     abs(out) {
         if (out) {
             return out.set(Math.abs(this.number), this.unit);
         }
         else {
             this._number = Math.abs(this._number);
             return this;
         }
     }
     /**
      * Returns the numeric value of this NumberUnit after conversion to a specified unit.
      * @param unit The unit to which to convert.
      * @returns The converted numeric value.
      * @throws Error if this NumberUnit's unit type cannot be converted to the specified unit.
      */
     asUnit(unit) {
         return this.unit.convertTo(this.number, unit);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     compare(value, unit) {
         const converted = this.toNumberOfThisUnit(value, unit);
         if (converted === undefined) {
             throw new Error('Invalid unit conversion attempted.');
         }
         const diff = this.number - converted;
         if (Math.abs(diff) < 1e-14) {
             return 0;
         }
         return Math.sign(diff);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     equals(value, unit) {
         const converted = this.toNumberOfThisUnit(value, unit);
         if (converted === undefined) {
             return false;
         }
         const diff = this.number - converted;
         return Math.abs(diff) < 1e-14;
     }
     /**
      * Checks whether this NumberUnit has a numeric value of NaN.
      * @returns Whether this NumberUnit has a numeric value of NaN.
      */
     isNaN() {
         return isNaN(this.number);
     }
     /**
      * Copies this NumberUnit.
      * @returns A copy of this NumberUnit.
      */
     copy() {
         return new NumberUnit(this.number, this.unit);
     }
 }
 /**
  * A read-only interface for a WT_NumberUnit.
  */
 class NumberUnitReadOnly {
     /**
      * Constructor.
      * @param source - the source of the new read-only NumberUnit.
      */
     constructor(source) {
         this.source = source;
     }
     /**
      * Gets this NumberUnit's numeric value.
      * @returns This NumberUnit's numeric value.
      */
     get number() {
         return this.source.number;
     }
     /**
      * Gets this NumberUnit's unit type.
      * @returns This NumberUnit's unit type.
      */
     get unit() {
         return this.source.unit;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     add(arg1, arg2, arg3) {
         const isArg2NumberUnit = arg2 instanceof NumberUnit;
         const out = (isArg2NumberUnit ? arg2 : arg3);
         if (typeof arg1 === 'number') {
             return this.source.add(arg1, arg2, out);
         }
         else {
             return this.source.add(arg1, out);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     subtract(arg1, arg2, arg3) {
         const isArg2NumberUnit = arg2 instanceof NumberUnit;
         const out = (isArg2NumberUnit ? arg2 : arg3);
         if (typeof arg1 === 'number') {
             return this.source.subtract(arg1, arg2, out);
         }
         else {
             return this.source.subtract(arg1, out);
         }
     }
     /**
      * Scales this NumberUnit by a unit-less factor and returns the result.
      * @param factor The factor by which to scale.
      * @param out The NumberUnit to which to write the result.
      * @returns The scaled value.
      */
     scale(factor, out) {
         return this.source.scale(factor, out);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     ratio(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.ratio(arg1, arg2);
         }
         else {
             return this.source.ratio(arg1);
         }
     }
     /**
      * Calculates the absolute value of this NumberUnit and returns the result.
      * @param out The NumberUnit to which to write the result.
      * @returns The absolute value.
      */
     abs(out) {
         return this.source.abs(out);
     }
     /**
      * Returns the numeric value of this NumberUnit after conversion to a specified unit.
      * @param unit The unit to which to convert.
      * @returns The converted numeric value.
      * @throws Error if this NumberUnit's unit type cannot be converted to the specified unit.
      */
     asUnit(unit) {
         return this.source.asUnit(unit);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     compare(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.compare(arg1, arg2);
         }
         else {
             return this.source.compare(arg1);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     equals(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.equals(arg1, arg2);
         }
         else {
             return this.source.equals(arg1);
         }
     }
     /**
      * Checks whether this NumberUnit has a numeric value of NaN.
      * @returns Whether this NumberUnit has a numeric value of NaN.
      */
     isNaN() {
         return this.source.isNaN();
     }
     /**
      * Copies this NumberUnit.
      * @returns A copy of this NumberUnit.
      */
     copy() {
         return this.source.copy();
     }
 }
 /**
  * A unit of measurement.
  */
 class AbstractUnit {
     /**
      * Constructor.
      * @param name The name of this unit.
      */
     constructor(name) {
         this.name = name;
     }
     /** @inheritdoc */
     canConvert(otherUnit) {
         return this.family === otherUnit.family;
     }
     /** @inheritdoc */
     createNumber(value) {
         return new NumberUnit(value, this);
     }
     /** @inheritdoc */
     equals(other) {
         return this.family === other.family && this.name === other.name;
     }
 }
 /**
  * A unit that can be converted to another unit of the same type via a fixed linear transformation.
  */
 class SimpleUnit extends AbstractUnit {
     /**
      * Constructor.
      * @param family The family to which this unit belongs.
      * @param name The name of this unit.
      * @param scaleFactor The relative linear scale of the new unit compared to the standard unit of the same family.
      * @param zeroOffset The zero offset of the new unit compared to the standard unit of the same family.
      */
     constructor(family, name, scaleFactor, zeroOffset = 0) {
         super(name);
         this.family = family;
         this.scaleFactor = scaleFactor;
         this.zeroOffset = zeroOffset;
     }
     /** @inheritdoc */
     canConvert(otherUnit) {
         return otherUnit instanceof SimpleUnit && super.canConvert(otherUnit);
     }
     /** @inheritdoc */
     convertTo(value, toUnit) {
         if (!this.canConvert(toUnit)) {
             throw new Error(`Invalid conversion from ${this.name} to ${toUnit.name}.`);
         }
         return (value + this.zeroOffset) * (this.scaleFactor / toUnit.scaleFactor) - toUnit.zeroOffset;
     }
     /** @inheritdoc */
     convertFrom(value, fromUnit) {
         if (!this.canConvert(fromUnit)) {
             throw new Error(`Invalid conversion from ${fromUnit.name} to ${this.name}.`);
         }
         return (value + fromUnit.zeroOffset) * (fromUnit.scaleFactor / this.scaleFactor) - this.zeroOffset;
     }
 }
 /**
  * A unit of measure composed of the multiplicative combination of multiple elementary units.
  */
 class CompoundUnit extends AbstractUnit {
     /**
      * Constructor.
      * @param family The family to which this unit belongs.
      * @param numerator An array of CompoundableUnits containing all the units in the numerator of the compound unit.
      * @param denominator An array of CompoundableUnits containing all the units in the denominator of the compound unit.
      * @param name The name of this unit. If not defined, one will be automatically generated.
      */
     constructor(family, numerator, denominator, name) {
         // if not specified, build name from component units.
         if (name === undefined) {
             name = '';
             let i = 0;
             while (i < numerator.length - 1) {
                 name += `${numerator[i].name}-`;
             }
             name += `${numerator[i].name}`;
             if (denominator.length > 0) {
                 name += ' per ';
                 i = 0;
                 while (i < denominator.length - 1) {
                     name += `${denominator[i].name}-`;
                 }
                 name += `${denominator[i].name}`;
             }
         }
         super(name);
         this.family = family;
         this.numerator = Array.from(numerator);
         this.denominator = Array.from(denominator);
         this.numerator.sort((a, b) => a.family.localeCompare(b.family));
         this.denominator.sort((a, b) => a.family.localeCompare(b.family));
         this.scaleFactor = this.getScaleFactor();
     }
     /**
      * Gets the scale factor for this unit.
      * @returns the scale factor for this unit.
      */
     getScaleFactor() {
         let factor = 1;
         factor = this.numerator.reduce((prev, curr) => prev * curr.scaleFactor, factor);
         factor = this.denominator.reduce((prev, curr) => prev / curr.scaleFactor, factor);
         return factor;
     }
     /** @inheritdoc */
     canConvert(otherUnit) {
         return otherUnit instanceof CompoundUnit && super.canConvert(otherUnit);
     }
     /** @inheritdoc */
     convertTo(value, toUnit) {
         if (!this.canConvert(toUnit)) {
             throw new Error(`Invalid conversion from ${this.name} to ${toUnit.name}.`);
         }
         return value * (this.scaleFactor / toUnit.scaleFactor);
     }
     /** @inheritdoc */
     convertFrom(value, fromUnit) {
         if (!this.canConvert(fromUnit)) {
             throw new Error(`Invalid conversion from ${fromUnit.name} to ${this.name}.`);
         }
         return value * (fromUnit.scaleFactor / this.scaleFactor);
     }
 }
 /**
  * Predefined unit families.
  */
 var UnitFamily;
 (function (UnitFamily) {
     UnitFamily["Distance"] = "distance";
     UnitFamily["Angle"] = "angle";
     UnitFamily["Duration"] = "duration";
     UnitFamily["Weight"] = "weight";
     UnitFamily["Volume"] = "volume";
     UnitFamily["Pressure"] = "pressure";
     UnitFamily["Temperature"] = "temperature";
     UnitFamily["Speed"] = "speed";
     UnitFamily["WeightFlux"] = "weight_flux";
     UnitFamily["VolumeFlux"] = "volume_flux";
 })(UnitFamily || (UnitFamily = {}));
 /**
  * Predefined unit types.
  */
 class UnitType {
 }
 UnitType.METER = new SimpleUnit(UnitFamily.Distance, 'meter', 1);
 UnitType.FOOT = new SimpleUnit(UnitFamily.Distance, 'foot', 0.3048);
 UnitType.KILOMETER = new SimpleUnit(UnitFamily.Distance, 'kilometer', 1000);
 UnitType.MILE = new SimpleUnit(UnitFamily.Distance, 'mile', 1609.34);
 UnitType.NMILE = new SimpleUnit(UnitFamily.Distance, 'nautical mile', 1852);
 UnitType.GA_RADIAN = new SimpleUnit(UnitFamily.Distance, 'great arc radian', 6378100);
 UnitType.RADIAN = new SimpleUnit(UnitFamily.Angle, 'radian', 1);
 UnitType.DEGREE = new SimpleUnit(UnitFamily.Angle, 'degree', Math.PI / 180);
 UnitType.ARC_MIN = new SimpleUnit(UnitFamily.Angle, 'minute', Math.PI / 180 / 60);
 UnitType.ARC_SEC = new SimpleUnit(UnitFamily.Angle, 'second', Math.PI / 180 / 3600);
 UnitType.MILLISECOND = new SimpleUnit(UnitFamily.Duration, 'millisecond', 0.001);
 UnitType.SECOND = new SimpleUnit(UnitFamily.Duration, 'second', 1);
 UnitType.MINUTE = new SimpleUnit(UnitFamily.Duration, 'minute', 60);
 UnitType.HOUR = new SimpleUnit(UnitFamily.Duration, 'hour', 3600);
 UnitType.KILOGRAM = new SimpleUnit(UnitFamily.Weight, 'kilogram', 1);
 UnitType.POUND = new SimpleUnit(UnitFamily.Weight, 'pound', 0.453592);
 UnitType.TON = new SimpleUnit(UnitFamily.Weight, 'ton', 907.185);
 UnitType.TONNE = new SimpleUnit(UnitFamily.Weight, 'tonne', 1000);
 // the following fuel units use the generic conversion factor of 1 gal = 6.7 lbs
 UnitType.LITER_FUEL = new SimpleUnit(UnitFamily.Weight, 'liter', 0.80283679);
 UnitType.GALLON_FUEL = new SimpleUnit(UnitFamily.Weight, 'gallon', 3.0390664);
 UnitType.LITER = new SimpleUnit(UnitFamily.Volume, 'liter', 1);
 UnitType.GALLON = new SimpleUnit(UnitFamily.Volume, 'gallon', 3.78541);
 UnitType.HPA = new SimpleUnit(UnitFamily.Pressure, 'hectopascal', 1);
 UnitType.ATM = new SimpleUnit(UnitFamily.Pressure, 'atmosphere', 1013.25);
 UnitType.IN_HG = new SimpleUnit(UnitFamily.Pressure, 'inch of mercury', 33.8639);
 UnitType.MM_HG = new SimpleUnit(UnitFamily.Pressure, 'millimeter of mercury', 1.33322);
 UnitType.CELSIUS = new SimpleUnit(UnitFamily.Temperature, '° Celsius', 1, 273.15);
 UnitType.FAHRENHEIT = new SimpleUnit(UnitFamily.Temperature, '° Fahrenheit', 5 / 9, 459.67);
 UnitType.KNOT = new CompoundUnit(UnitFamily.Speed, [UnitType.NMILE], [UnitType.HOUR], 'knot');
 UnitType.KPH = new CompoundUnit(UnitFamily.Speed, [UnitType.KILOMETER], [UnitType.HOUR]);
 UnitType.MPM = new CompoundUnit(UnitFamily.Speed, [UnitType.METER], [UnitType.MINUTE]);
 UnitType.MPS = new CompoundUnit(UnitFamily.Speed, [UnitType.METER], [UnitType.SECOND]);
 UnitType.FPM = new CompoundUnit(UnitFamily.Speed, [UnitType.FOOT], [UnitType.MINUTE]);
 UnitType.FPS = new CompoundUnit(UnitFamily.Speed, [UnitType.FOOT], [UnitType.SECOND]);
 UnitType.KGH = new CompoundUnit(UnitFamily.WeightFlux, [UnitType.KILOGRAM], [UnitType.HOUR]);
 UnitType.PPH = new CompoundUnit(UnitFamily.WeightFlux, [UnitType.POUND], [UnitType.HOUR]);
 UnitType.LPH_FUEL = new CompoundUnit(UnitFamily.WeightFlux, [UnitType.LITER_FUEL], [UnitType.HOUR]);
 UnitType.GPH_FUEL = new CompoundUnit(UnitFamily.WeightFlux, [UnitType.GALLON_FUEL], [UnitType.HOUR]);
 
 /**
  * A class for conversions of degree units.
  */
 class Degrees {
     constructor() {
         /**
          * Converts degrees to radians.
          * @param degrees The degrees to convert.
          * @returns The result as radians.
          */
         this.toRadians = (degrees) => degrees * (Math.PI / 180);
     }
 }
 /**
  * A class for conversions of foot units.
  */
 class Feet {
     constructor() {
         /**
          * Converts feet to meters.
          * @param feet The feet to convert.
          * @returns The result as meters.
          */
         this.toMeters = (feet) => feet * 0.3048;
         /**
          * Converts feet to nautical miles.
          * @param feet The feet to convert.
          * @returns The result as nautical miles.
          */
         this.toNauticalMiles = (feet) => feet / 6076.11549;
     }
 }
 /**
  * A class for conversions of meter units.
  */
 class Meters {
     constructor() {
         /**
          * Converts meters to feet.
          * @param meters The meters to convert.
          * @returns The result as feet.
          */
         this.toFeet = (meters) => meters / 0.3048;
         /**
          * Converts meters to nautical miles.
          * @param meters The meters to convert.
          * @returns The result as nautical miles.
          */
         this.toNauticalMiles = (meters) => meters / 1852;
     }
 }
 /**
  * A class for conversions of nautical mile units.
  */
 class NauticalMiles {
     constructor() {
         /**
          * Converts nautical miles to feet.
          * @param nm The nautical miles to convert.
          * @returns The result as feet.
          */
         this.toFeet = (nm) => nm * 6076.11549;
         /**
          * Converts nautical miles to meters.
          * @param nm The nautical miles to convert.
          * @returns The result as meters.
          */
         this.toMeters = (nm) => nm * 1852;
     }
 }
 /**
  * A class for conversions of radian units.
  */
 class Radians {
     constructor() {
         /**
          * Converts radians to degrees.
          * @param radians The radians to convert.
          * @returns The result as degrees.
          */
         this.toDegrees = (radians) => radians * 180 / Math.PI;
     }
 }
 /**
  * A class for unit conversions.
  */
 class Units {
 }
 /** The degrees unit. */
 Units.Degrees = new Degrees();
 /** The radians unit. */
 Units.Radians = new Radians();
 /** The feet unit. */
 Units.Feet = new Feet();
 /** The meters unit. */
 Units.Meters = new Meters();
 /** The nautical miles unit. */
 Units.NauticalMiles = new NauticalMiles();
 
 /**
  * 2D vector mathematical opertaions.
  */
 /**
  * 3D vector mathematical opertaions.
  */
 class Vec3Math {
     /**
      * Gets the spherical angle theta of a vector in radians.
      * @param vec - a vector.
      * @returns the spherical angle theta of the vector.
      */
     static theta(vec) {
         return Math.atan2(Math.hypot(vec[0], vec[1]), vec[2]);
     }
     /**
      * Gets the spherical angle phi of a vector in radians.
      * @param vec - a vector.
      * @returns the spherical angle phi of the vector.
      */
     static phi(vec) {
         return Math.atan2(vec[1], vec[0]);
     }
     /**
      * Sets the components of a vector.
      * @param x - the new x-component.
      * @param y - the new y-component.
      * @param z - the new z-component.
      * @param vec - the vector to change.
      * @returns the vector after it has been changed.
      */
     static set(x, y, z, vec) {
         vec[0] = x;
         vec[1] = y;
         vec[2] = z;
         return vec;
     }
     /**
      * Sets the spherical components of a vector.
      * @param r - the new length (magnitude).
      * @param theta - the new spherical angle theta, in radians.
      * @param phi - the new spherical angle phi, in radians.
      * @param vec - the vector to change.
      * @returns the vector after it has been changed.
      */
     static setFromSpherical(r, theta, phi, vec) {
         const sinTheta = Math.sin(theta);
         vec[0] = sinTheta * Math.cos(phi);
         vec[1] = sinTheta * Math.sin(phi);
         vec[2] = Math.cos(theta);
         return vec;
     }
     /**
      * Add one vector to another.
      * @param v1 The first vector.
      * @param v2 The second vector.
      * @param out The vector to write the results to.
      * @returns the vector sum.
      */
     static add(v1, v2, out) {
         out[0] = v1[0] + v2[0];
         out[1] = v1[1] + v2[1];
         out[2] = v1[2] + v2[2];
         return out;
     }
     /**
      * Subtracts one vector from another.
      * @param v1 The first vector.
      * @param v2 The second vector.
      * @param out The vector to write the results to.
      * @returns the vector difference.
      */
     static sub(v1, v2, out) {
         out[0] = v1[0] - v2[0];
         out[1] = v1[1] - v2[1];
         out[2] = v1[2] - v2[2];
         return out;
     }
     /**
      * Gets the dot product of two vectors.
      * @param v1 The first vector.
      * @param v2 The second vector.
      * @returns The dot product of the vectors.
      */
     static dot(v1, v2) {
         return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
     }
     /**
      * Gets the cross product of two vectors.
      * @param v1 - the first vector.
      * @param v2 - the second vector.
      * @param out - the vector to which to write the result.
      * @returns the cross product.
      */
     static cross(v1, v2, out) {
         const x1 = v1[0];
         const y1 = v1[1];
         const z1 = v1[2];
         const x2 = v2[0];
         const y2 = v2[1];
         const z2 = v2[2];
         out[0] = y1 * z2 - z1 * y2;
         out[1] = z1 * x2 - x1 * z2;
         out[2] = x1 * y2 - y1 * x2;
         return out;
     }
     /**
      * Multiplies a vector by a scalar.
      * @param v1 The vector to multiply.
      * @param scalar The scalar to apply.
      * @param out The vector to write the results to.
      * @returns The scaled vector.
      */
     static multScalar(v1, scalar, out) {
         out[0] = v1[0] * scalar;
         out[1] = v1[1] * scalar;
         out[2] = v1[2] * scalar;
         return out;
     }
     /**
      * Gets the magnitude of a vector.
      * @param v1 The vector to get the magnitude for.
      * @returns the vector's magnitude.
      */
     static abs(v1) {
         return Math.hypot(v1[0], v1[1], v1[2]);
     }
     /**
      * Normalizes the vector to a unit vector.
      * @param v1 The vector to normalize.
      * @param out The vector to write the results to.
      * @returns the normalized vector.
      */
     static normalize(v1, out) {
         const mag = Vec3Math.abs(v1);
         out[0] = v1[0] / mag;
         out[1] = v1[1] / mag;
         out[2] = v1[2] / mag;
         return out;
     }
     /**
      * Gets the Euclidean distance between two vectors.
      * @param vec1 The first vector.
      * @param vec2 The second vector.
      * @returns the Euclidean distance between the two vectors.
      */
     static distance(vec1, vec2) {
         return Math.hypot(vec2[0] - vec1[0], vec2[1] - vec1[0], vec2[2] - vec1[2]);
     }
     /**
      * Checks if two vectors are equal.
      * @param vec1 - the first vector.
      * @param vec2 - the second vector.
      * @returns whether the two vectors are equal.
      */
     static equals(vec1, vec2) {
         return vec1.length === vec2.length && vec1.every((element, index) => element === vec2[index]);
     }
     /**
      * Copies one vector to another.
      * @param from - the vector from which to copy.
      * @param to - the vector to which to copy.
      * @returns the changed vector.
      */
     static copy(from, to) {
         return Vec3Math.set(from[0], from[1], from[2], to);
     }
 }
 
 /**
  * A read-only wrapper for a GeoPoint.
  */
 class GeoPointReadOnly {
     /**
      * Constructor.
      * @param source - the source of the new read-only point.
      */
     constructor(source) {
         this.source = source;
     }
     /**
      * The latitude of this point, in degrees.
      * @returns the latitude of this point.
      */
     get lat() {
         return this.source.lat;
     }
     /**
      * The longitude of this point, in degrees.
      * @returns the longitude of this point.
      */
     get lon() {
         return this.source.lon;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     distance(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.distance(arg1, arg2);
         }
         else {
             return this.source.distance(arg1);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     distanceRhumb(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.distanceRhumb(arg1, arg2);
         }
         else {
             return this.source.distanceRhumb(arg1);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     bearingTo(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.bearingTo(arg1, arg2);
         }
         else {
             return this.source.bearingTo(arg1);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     bearingFrom(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.bearingFrom(arg1, arg2);
         }
         else {
             return this.source.bearingFrom(arg1);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     bearingRhumb(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return this.source.bearingRhumb(arg1, arg2);
         }
         else {
             return this.source.bearingRhumb(arg1);
         }
     }
     /**
      * Offsets this point by an initial bearing and distance along a great circle.
      * @param bearing - the initial bearing (forward azimuth) by which to offset.
      * @param distance - the distance, in great-arc radians, by which to offset.
      * @param out - the GeoPoint to which to write the results. If not supplied, a new GeoPoint object is created.
      * @returns the offset point.
      * @throws {Error} argument out cannot be undefined.
      */
     offset(bearing, distance, out) {
         if (!out) {
             throw new Error('Cannot mutate a read-only GeoPoint.');
         }
         return this.source.offset(bearing, distance, out);
     }
     /**
      * Offsets this point by a constant bearing and distance along a rhumb line.
      * @param bearing - the bearing by which to offset.
      * @param distance - the distance, in great-arc radians, by which to offset.
      * @param out - the GeoPoint to which to write the results. If not supplied, a new GeoPoint object is created.
      * @returns the offset point.
      * @throws {Error} argument out cannot be undefined.
      */
     offsetRhumb(bearing, distance, out) {
         if (!out) {
             throw new Error('Cannot mutate a read-only GeoPoint.');
         }
         return this.source.offsetRhumb(bearing, distance, out);
     }
     /**
      * Calculates the cartesian (x, y, z) representation of this point, in units of great-arc radians. By convention,
      * in the cartesian coordinate system the origin is at the center of the Earth, the positive x-axis passes through
      * 0 degrees N, 0 degrees E, and the positive z-axis passes through the north pole.
      * @param out - the vector array to which to write the result.
      * @returns the cartesian representation of this point.
      */
     toCartesian(out) {
         return this.source.toCartesian(out);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     equals(arg1, arg2, arg3) {
         if (typeof arg1 === 'number') {
             return this.source.equals(arg1, arg2, arg3);
         }
         else {
             return this.source.equals(arg1, arg2);
         }
     }
     /**
      * Copies this point.
      * @param to - an optional point to which to copy this point. If this argument is not supplied, a new GeoPoint object
      * will be created.
      * @returns a copy of this point.
      */
     copy(to) {
         return this.source.copy(to);
     }
 }
 /**
  * A point on Earth's surface. This class uses a spherical Earth model.
  */
 class GeoPoint {
     /**
      * Constructor.
      * @param lat - the latitude, in degrees.
      * @param lon - the longitude, in degrees.
      */
     constructor(lat, lon) {
         this._lat = 0;
         this._lon = 0;
         this.set(lat, lon);
         this.readonly = new GeoPointReadOnly(this);
     }
     /**
      * The latitude of this point, in degrees.
      * @returns the latitude of this point.
      */
     get lat() {
         return this._lat;
     }
     /**
      * The longitude of this point, in degrees.
      * @returns the longitude of this point.
      */
     get lon() {
         return this._lon;
     }
     /**
      * Converts an argument list consisting of either a LatLonInterface or lat/lon coordinates into an equivalent
      * LatLonInterface.
      * @param arg1 Argument 1.
      * @param arg2 Argument 2.
      * @returns a LatLonInterface.
      */
     static asLatLonInterface(arg1, arg2) {
         if (typeof arg1 === 'number') {
             return GeoPoint.tempGeoPoint.set(arg1, arg2);
         }
         else {
             return arg1;
         }
     }
     /**
      * Converts an argument list consisting of either a 3D vector or x, y, z components into an equivalent 3D vector.
      * @param arg1 Argument 1.
      * @param arg2 Argument 2.
      * @param arg3 Argument 3.
      * @returns a 3D vector.
      */
     static asVec3(arg1, arg2, arg3) {
         if (typeof arg1 === 'number') {
             return Vec3Math.set(arg1, arg2, arg3, GeoPoint.tempVec3);
         }
         else {
             return arg1;
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     set(arg1, arg2) {
         let lat, lon;
         if (typeof arg1 === 'number') {
             lat = arg1;
             lon = arg2;
         }
         else {
             lat = arg1.lat;
             lon = arg1.lon;
         }
         lat = GeoPoint.toPlusMinus180(lat);
         lon = GeoPoint.toPlusMinus180(lon);
         if (Math.abs(lat) > 90) {
             lat = 180 - lat;
             lat = GeoPoint.toPlusMinus180(lat);
             lon += 180;
             lon = GeoPoint.toPlusMinus180(lon);
         }
         this._lat = lat;
         this._lon = lon;
         return this;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     setFromCartesian(arg1, arg2, arg3) {
         const vec = GeoPoint.asVec3(arg1, arg2, arg3);
         const theta = Vec3Math.theta(vec);
         const phi = Vec3Math.phi(vec);
         return this.set(90 - theta * Avionics.Utils.RAD2DEG, phi * Avionics.Utils.RAD2DEG);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     distance(arg1, arg2) {
         const other = GeoPoint.asLatLonInterface(arg1, arg2);
         const lat1 = this.lat * Avionics.Utils.DEG2RAD;
         const lat2 = other.lat * Avionics.Utils.DEG2RAD;
         const lon1 = this.lon * Avionics.Utils.DEG2RAD;
         const lon2 = other.lon * Avionics.Utils.DEG2RAD;
         // haversine formula
         const sinHalfDeltaLat = Math.sin((lat2 - lat1) / 2);
         const sinHalfDeltaLon = Math.sin((lon2 - lon1) / 2);
         const a = sinHalfDeltaLat * sinHalfDeltaLat + Math.cos(lat1) * Math.cos(lat2) * sinHalfDeltaLon * sinHalfDeltaLon;
         return 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     distanceRhumb(arg1, arg2) {
         const other = GeoPoint.asLatLonInterface(arg1, arg2);
         const lat1 = this.lat * Avionics.Utils.DEG2RAD;
         const lat2 = other.lat * Avionics.Utils.DEG2RAD;
         const lon1 = this.lon * Avionics.Utils.DEG2RAD;
         const lon2 = other.lon * Avionics.Utils.DEG2RAD;
         const deltaLat = lat2 - lat1;
         let deltaLon = lon2 - lon1;
         const deltaPsi = GeoPoint.deltaPsi(lat1, lat2);
         const correction = GeoPoint.rhumbCorrection(deltaPsi, lat1, lat2);
         if (Math.abs(deltaLon) > Math.PI) {
             deltaLon += -Math.sign(deltaLon) * 2 * Math.PI;
         }
         return Math.sqrt(deltaLat * deltaLat + correction * correction * deltaLon * deltaLon);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     bearingTo(arg1, arg2) {
         const other = GeoPoint.asLatLonInterface(arg1, arg2);
         return GeoPoint.calculateInitialBearing(this, other);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     bearingFrom(arg1, arg2) {
         const other = GeoPoint.asLatLonInterface(arg1, arg2);
         return (GeoPoint.calculateInitialBearing(this, other) + 180) % 360;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     bearingRhumb(arg1, arg2) {
         const other = GeoPoint.asLatLonInterface(arg1, arg2);
         const lat1 = this.lat * Avionics.Utils.DEG2RAD;
         const lon1 = this.lon * Avionics.Utils.DEG2RAD;
         const lat2 = other.lat * Avionics.Utils.DEG2RAD;
         const lon2 = other.lon * Avionics.Utils.DEG2RAD;
         let deltaLon = lon2 - lon1;
         const deltaPsi = GeoPoint.deltaPsi(lat1, lat2);
         if (Math.abs(deltaLon) > Math.PI) {
             deltaLon += -Math.sign(deltaLon) * 2 * Math.PI;
         }
         return Math.atan2(deltaLon, deltaPsi) * Avionics.Utils.RAD2DEG;
     }
     /**
      * Offsets this point by an initial bearing and distance along a great circle.
      * @param bearing - the initial bearing (forward azimuth) by which to offset.
      * @param distance - the distance, in great-arc radians, by which to offset.
      * @param out - the GeoPoint to which to write the results. By default this point.
      * @returns the offset point.
      */
     offset(bearing, distance, out) {
         const latRad = this.lat * Avionics.Utils.DEG2RAD;
         const lonRad = this.lon * Avionics.Utils.DEG2RAD;
         const sinLat = Math.sin(latRad);
         const cosLat = Math.cos(latRad);
         const sinBearing = Math.sin(bearing * Avionics.Utils.DEG2RAD);
         const cosBearing = Math.cos(bearing * Avionics.Utils.DEG2RAD);
         const angularDistance = distance;
         const sinAngularDistance = Math.sin(angularDistance);
         const cosAngularDistance = Math.cos(angularDistance);
         const offsetLatRad = Math.asin(sinLat * cosAngularDistance + cosLat * sinAngularDistance * cosBearing);
         const offsetLonDeltaRad = Math.atan2(sinBearing * sinAngularDistance * cosLat, cosAngularDistance - sinLat * Math.sin(offsetLatRad));
         const offsetLat = offsetLatRad * Avionics.Utils.RAD2DEG;
         const offsetLon = (lonRad + offsetLonDeltaRad) * Avionics.Utils.RAD2DEG;
         return (out !== null && out !== void 0 ? out : this).set(offsetLat, offsetLon);
     }
     /**
      * Offsets this point by a constant bearing and distance along a rhumb line.
      * @param bearing - the bearing by which to offset.
      * @param distance - the distance, in great-arc radians, by which to offset.
      * @param out - the GeoPoint to which to write the results. By default this point.
      * @returns the offset point.
      */
     offsetRhumb(bearing, distance, out) {
         const latRad = this.lat * Avionics.Utils.DEG2RAD;
         const lonRad = this.lon * Avionics.Utils.DEG2RAD;
         const bearingRad = bearing * Avionics.Utils.DEG2RAD;
         const deltaLat = distance * Math.cos(bearingRad);
         let offsetLat = latRad + deltaLat;
         let offsetLon;
         if (Math.abs(offsetLat) >= Math.PI / 2) {
             // you can't technically go past the poles along a rhumb line, so we will simply terminate the path at the pole
             offsetLat = Math.sign(offsetLat) * 90;
             offsetLon = 0; // since longitude is meaningless at the poles, we'll arbitrarily pick a longitude of 0 degrees.
         }
         else {
             const deltaPsi = GeoPoint.deltaPsi(latRad, offsetLat);
             const correction = GeoPoint.rhumbCorrection(deltaPsi, latRad, offsetLat);
             const deltaLon = distance * Math.sin(bearingRad) / correction;
             offsetLon = lonRad + deltaLon;
             offsetLat *= Avionics.Utils.RAD2DEG;
             offsetLon *= Avionics.Utils.RAD2DEG;
         }
         return (out !== null && out !== void 0 ? out : this).set(offsetLat, offsetLon);
     }
     /**
      * Calculates the cartesian (x, y, z) representation of this point, in units of great-arc radians. By convention,
      * in the cartesian coordinate system the origin is at the center of the Earth, the positive x-axis passes through
      * 0 degrees N, 0 degrees E, and the positive z-axis passes through the north pole.
      * @param out - the vector array to which to write the result.
      * @returns the cartesian representation of this point.
      */
     toCartesian(out) {
         return GeoPoint.sphericalToCartesian(this, out);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     equals(arg1, arg2, arg3) {
         const other = GeoPoint.asLatLonInterface(arg1, arg2);
         const tolerance = typeof arg1 === 'number' ? arg3 : arg2;
         if (other) {
             return this.distance(other) <= (tolerance !== null && tolerance !== void 0 ? tolerance : GeoPoint.EQUALITY_TOLERANCE);
         }
         else {
             return false;
         }
     }
     /**
      * Copies this point.
      * @param to - an optional point to which to copy this point. If this argument is not supplied, a new GeoPoint object
      * will be created.
      * @returns a copy of this point.
      */
     copy(to) {
         return to ? to.set(this.lat, this.lon) : new GeoPoint(this.lat, this.lon);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static sphericalToCartesian(arg1, arg2, arg3) {
         const point = GeoPoint.asLatLonInterface(arg1, arg2);
         const theta = (90 - point.lat) * Avionics.Utils.DEG2RAD;
         const phi = point.lon * Avionics.Utils.DEG2RAD;
         return Vec3Math.setFromSpherical(1, theta, phi, arg3 !== null && arg3 !== void 0 ? arg3 : arg2);
     }
     /**
      * Converts an angle, in degrees, to an equivalent value in the range [-180, 180).
      * @param angle - an angle in degrees.
      * @returns the angle's equivalent in the range [-180, 180).
      */
     static toPlusMinus180(angle) {
         return ((angle % 360) + 540) % 360 - 180;
     }
     /**
      * Calculates the initial bearing (forward azimuth) from an origin point to a destination point.
      * @param origin - the origin point.
      * @param destination - the destination point.
      * @returns the initial bearing from the origin to destination.
      */
     static calculateInitialBearing(origin, destination) {
         const lat1 = origin.lat * Avionics.Utils.DEG2RAD;
         const lat2 = destination.lat * Avionics.Utils.DEG2RAD;
         const lon1 = origin.lon * Avionics.Utils.DEG2RAD;
         const lon2 = destination.lon * Avionics.Utils.DEG2RAD;
         const cosLat2 = Math.cos(lat2);
         const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * cosLat2 * Math.cos(lon2 - lon1);
         const y = Math.sin(lon2 - lon1) * cosLat2;
         const bearing = Math.atan2(y, x) * Avionics.Utils.RAD2DEG;
         return (bearing + 360) % 360; // enforce range [0, 360)
     }
     /**
      * Calculates the difference in isometric latitude from a pair of geodetic (geocentric) latitudes.
      * @param latRad1 - geodetic latitude 1, in radians.
      * @param latRad2 - geodetic latitude 2, in radians.
      * @returns the difference in isometric latitude from latitude 1 to latitude 2, in radians.
      */
     static deltaPsi(latRad1, latRad2) {
         return Math.log(Math.tan(latRad2 / 2 + Math.PI / 4) / Math.tan(latRad1 / 2 + Math.PI / 4));
     }
     /**
      * Calculates the rhumb correction factor between two latitudes.
      * @param deltaPsi - the difference in isometric latitude beween the two latitudes.
      * @param latRad1 - geodetic latitude 1, in radians.
      * @param latRad2 - geodetic latitude 2, in radians.
      * @returns the rhumb correction factor between the two latitudes.
      */
     static rhumbCorrection(deltaPsi, latRad1, latRad2) {
         return Math.abs(deltaPsi) > 1e-12 ? ((latRad2 - latRad1) / deltaPsi) : Math.cos(latRad1);
     }
 }
 /**
  * The default equality tolerance, defined as the maximum allowed distance between two equal points in great-arc
  * radians.
  */
 GeoPoint.EQUALITY_TOLERANCE = 1e-7; // ~61 cm
 GeoPoint.tempVec3 = new Float64Array(3);
 GeoPoint.tempGeoPoint = new GeoPoint(0, 0);
 
 /**
  * A circle on Earth's surface, defined as the set of points on the Earth's surface equidistant (as measured
  * geodetically) from a central point.
  */
 class GeoCircle {
     /**
      * Constructor.
      * @param center The center of the new small circle, represented as a position vector in the standard geographic
      * cartesian reference system.
      * @param radius The radius of the new small circle in great-arc radians.
      */
     constructor(center, radius) {
         this._center = new Float64Array(3);
         this._radius = 0;
         this.set(center, radius);
     }
     // eslint-disable-next-line jsdoc/require-returns
     /**
      * The center of this circle.
      */
     get center() {
         return this._center;
     }
     // eslint-disable-next-line jsdoc/require-returns
     /**
      * The radius of this circle, in great-arc radians.
      */
     get radius() {
         return this._radius;
     }
     /**
      * Checks whether this circle is a great circle, or equivalently, whether its radius is equal to pi / 2 great-arc
      * radians.
      * @returns Whether this circle is a great circle.
      */
     isGreatCircle() {
         return this._radius === Math.PI / 2;
     }
     /**
      * Calculates the length of an arc along this circle subtended by a central angle.
      * @param angle A central angle, in radians.
      * @returns the length of the arc subtended by the angle, in great-arc radians.
      */
     arcLength(angle) {
         return Math.sin(this._radius) * angle;
     }
     /**
      * Sets the center and radius of this circle.
      * @param center The new center.
      * @param radius The new radius in great-arc radians.
      * @returns this circle, after it has been changed.
      */
     set(center, radius) {
         if (center instanceof Float64Array) {
             if (Vec3Math.abs(center) === 0) {
                 // if center has no direction, arbitrarily set the center to 0 N, 0 E.
                 Vec3Math.set(1, 0, 0, this._center);
             }
             else {
                 Vec3Math.normalize(center, this._center);
             }
         }
         else {
             GeoPoint.sphericalToCartesian(center, this._center);
         }
         this._radius = Math.abs(radius) % Math.PI;
         return this;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     setAsGreatCircle(arg1, arg2) {
         this.set(GeoCircle._getGreatCircleNormal(arg1, arg2, GeoCircle.vec3Cache[0]), Math.PI / 2);
         return this;
     }
     /**
      * Gets the distance from a point to the center of this circle, in great-arc radians.
      * @param point The point to which to measure the distance.
      * @returns the distance from the point to the center of this circle.
      */
     distanceToCenter(point) {
         if (point instanceof Float64Array) {
             point = Vec3Math.normalize(point, GeoCircle.vec3Cache[0]);
         }
         else {
             point = GeoPoint.sphericalToCartesian(point, GeoCircle.vec3Cache[0]);
         }
         const dot = Vec3Math.dot(point, this._center);
         return Math.acos(Utils.Clamp(dot, -1, 1));
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     closest(point, out) {
         if (!(point instanceof Float64Array)) {
             point = GeoPoint.sphericalToCartesian(point, GeoCircle.vec3Cache[0]);
         }
         const offset = Vec3Math.multScalar(this._center, Math.cos(this._radius), GeoCircle.vec3Cache[1]);
         const dot = Vec3Math.dot(Vec3Math.sub(point, offset, GeoCircle.vec3Cache[2]), this._center);
         const planeProjected = Vec3Math.sub(point, Vec3Math.multScalar(this._center, dot, GeoCircle.vec3Cache[2]), GeoCircle.vec3Cache[2]);
         if (Vec3Math.dot(planeProjected, planeProjected) === 0 || Math.abs(Vec3Math.dot(planeProjected, this._center)) === 1) {
             // the point is equidistant from all points on this circle
             return out instanceof GeoPoint ? out.set(NaN, NaN) : Vec3Math.set(NaN, NaN, NaN, out);
         }
         const displacement = Vec3Math.multScalar(Vec3Math.normalize(Vec3Math.sub(planeProjected, offset, GeoCircle.vec3Cache[2]), GeoCircle.vec3Cache[2]), Math.sin(this._radius), GeoCircle.vec3Cache[2]);
         const closest = Vec3Math.add(offset, displacement, GeoCircle.vec3Cache[2]);
         return out instanceof Float64Array ? Vec3Math.normalize(closest, out) : out.setFromCartesian(closest);
     }
     /**
      * Calculates and returns the great-circle distance from a specified point to the closest point that lies on this
      * circle. In other words, calculates the shortest distance from a point to this circle. The distance is signed, with
      * positive distances representing deviation away from the center of the circle, and negative distances representing
      * deviation toward the center of the circle.
      * @param point A point, represented as either a position vector or lat/long coordinates.
      * @returns the great circle distance, in great-arc radians, from the point to the closest point on this circle.
      */
     distance(point) {
         const distanceToCenter = this.distanceToCenter(point);
         return distanceToCenter - this._radius;
     }
     /**
      * Checks whether a point lies on this circle.
      * @param point A point, represented as either a position vector or lat/long coordinates.
      * @param tolerance The error tolerance, in great-arc radians, of this operation. Defaults to
      * `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns whether the point lies on this circle.
      */
     includes(point, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         const distance = this.distance(point);
         return Math.abs(distance) < tolerance;
     }
     /**
      * Checks whether a point lies within the boundary defined by this circle. This is equivalent to checking whether
      * the distance of the point from the center of this circle is less than or equal to this circle's radius.
      * @param point A point, represented as either a position vector or lat/long coordinates.
      * @param inclusive Whether points that lie on this circle should pass the check. True by default.
      * @param tolerance The error tolerance, in great-arc radians, of this operation. Defaults to
      * `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns whether the point lies within the boundary defined by this circle.
      */
     encircles(point, inclusive = true, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         const distance = this.distance(point);
         return inclusive
             ? distance <= tolerance
             : distance < -tolerance;
     }
     /**
      * Gets the angular distance along an arc between two points that lie on this circle. The arc extends from the first
      * point to the second in a counterclockwise direction when viewed from above the center of the circle.
      * @param start A point on this circle which marks the beginning of an arc.
      * @param end A point on this circle which marks the end of an arc.
      * @param tolerance The error tolerance, in great-arc radians, when checking if `start` and `end` lie on this circle.
      * Defaults to `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns the angular width of the arc between the two points, in radians.
      * @throws Error if either point does not lie on this circle.
      */
     angleAlong(start, end, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         if (!(start instanceof Float64Array)) {
             start = GeoPoint.sphericalToCartesian(start, GeoCircle.vec3Cache[1]);
         }
         if (!(end instanceof Float64Array)) {
             end = GeoPoint.sphericalToCartesian(end, GeoCircle.vec3Cache[2]);
         }
         if (!this.includes(start, tolerance) || !this.includes(end, tolerance)) {
             throw new Error(`GeoCircle: at least one of the two specified arc end points does not lie on this circle (start point distance of ${this.distance(start)}, end point distance of ${this.distance(end)}, vs tolerance of ${tolerance}).`);
         }
         if (this._radius <= GeoCircle.ANGULAR_TOLERANCE) {
             return 0;
         }
         const startRadialNormal = Vec3Math.normalize(Vec3Math.cross(this._center, start, GeoCircle.vec3Cache[3]), GeoCircle.vec3Cache[3]);
         const endRadialNormal = Vec3Math.normalize(Vec3Math.cross(this._center, end, GeoCircle.vec3Cache[4]), GeoCircle.vec3Cache[4]);
         const angularDistance = Math.acos(Utils.Clamp(Vec3Math.dot(startRadialNormal, endRadialNormal), -1, 1));
         const isArcGreaterThanSemi = Vec3Math.dot(startRadialNormal, end) < 0;
         return isArcGreaterThanSemi ? 2 * Math.PI - angularDistance : angularDistance;
     }
     /**
      * Gets the distance along an arc between two points that lie on this circle. The arc extends from the first point
      * to the second in a counterclockwise direction when viewed from above the center of the circle.
      * @param start A point on this circle which marks the beginning of an arc.
      * @param end A point on this circle which marks the end of an arc.
      * @param tolerance The error tolerance, in great-arc radians, when checking if `start` and `end` lie on this circle.
      * Defaults to `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns the length of the arc between the two points, in great-arc radians.
      * @throws Error if either point does not lie on this circle.
      */
     distanceAlong(start, end, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         return this.arcLength(this.angleAlong(start, end, tolerance));
     }
     /**
      * Calculates the true bearing along this circle at a point on the circle.
      * @param point A point on this circle.
      * @param tolerance The error tolerance, in great-arc radians, when checking if `point` lies on this circle. Defaults
      * to `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns the bearing along this circle at the point.
      * @throws Error if the point does not lie on this circle.
      */
     bearingAt(point, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         if (!(point instanceof Float64Array)) {
             point = GeoPoint.sphericalToCartesian(point, GeoCircle.vec3Cache[1]);
         }
         if (!this.includes(point, tolerance)) {
             throw new Error(`GeoCircle: the specified point does not lie on this circle (distance of ${Math.abs(this.distance(point))} vs tolerance of ${tolerance}).`);
         }
         if (this._radius <= GeoCircle.ANGULAR_TOLERANCE || 1 - Math.abs(Vec3Math.dot(point, GeoCircle.NORTH_POLE)) <= GeoCircle.ANGULAR_TOLERANCE) {
             // Meaningful bearings cannot be defined along a circle with 0 radius (effectively a point) and at the north and south poles.
             return NaN;
         }
         const radialNormal = Vec3Math.normalize(Vec3Math.cross(this._center, point, GeoCircle.vec3Cache[2]), GeoCircle.vec3Cache[2]);
         const northNormal = Vec3Math.normalize(Vec3Math.cross(point, GeoCircle.NORTH_POLE, GeoCircle.vec3Cache[3]), GeoCircle.vec3Cache[3]);
         return (Math.acos(Utils.Clamp(Vec3Math.dot(radialNormal, northNormal), -1, 1)) * (radialNormal[2] >= 0 ? 1 : -1) * Avionics.Utils.RAD2DEG - 90 + 360) % 360;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     offsetDistanceAlong(point, distance, out, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         const angle = distance / Math.sin(this.radius);
         return this._offsetAngleAlong(point, angle, out, tolerance);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     offsetAngleAlong(point, angle, out, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         return this._offsetAngleAlong(point, angle, out, tolerance);
     }
     /**
      * Offsets a point on this circle by a specified angular distance. The direction of the offset for positive distances
      * is counterclockwise when viewed from above the center of this circle.
      * @param point The point to offset.
      * @param angle The angular distance by which to offset, in radians.
      * @param out A Float64Array or GeoPoint object to which to write the result.
      * @param tolerance The error tolerance, in great-arc radians, when checking if `point` lies on this circle. Defaults
      * to `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns The offset point.
      * @throws Error if the point does not lie on this circle.
      */
     _offsetAngleAlong(point, angle, out, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         if (!(point instanceof Float64Array)) {
             point = GeoPoint.sphericalToCartesian(point, GeoCircle.vec3Cache[3]);
         }
         if (!this.includes(point, tolerance)) {
             throw new Error(`GeoCircle: the specified point does not lie on this circle (distance of ${Math.abs(this.distance(point))} vs tolerance of ${tolerance}).`);
         }
         if (this.radius === 0) {
             return out instanceof GeoPoint ? out.setFromCartesian(point) : Vec3Math.copy(point, out);
         }
         // Since point may not lie exactly on this circle due to error tolerance, project point onto this circle to ensure
         // the offset point lies exactly on this circle.
         point = this.closest(point, GeoCircle.vec3Cache[3]);
         const sin = Math.sin(angle / 2);
         const q0 = Math.cos(angle / 2);
         const q1 = sin * this._center[0];
         const q2 = sin * this._center[1];
         const q3 = sin * this._center[2];
         const q0Sq = q0 * q0;
         const q1Sq = q1 * q1;
         const q2Sq = q2 * q2;
         const q3Sq = q3 * q3;
         const q01 = q0 * q1;
         const q02 = q0 * q2;
         const q03 = q0 * q3;
         const q12 = q1 * q2;
         const q13 = q1 * q3;
         const q23 = q2 * q3;
         const rot_11 = q0Sq + q1Sq - q2Sq - q3Sq;
         const rot_12 = 2 * (q12 - q03);
         const rot_13 = 2 * (q13 + q02);
         const rot_21 = 2 * (q12 + q03);
         const rot_22 = q0Sq - q1Sq + q2Sq - q3Sq;
         const rot_23 = 2 * (q23 - q01);
         const rot_31 = 2 * (q13 - q02);
         const rot_32 = 2 * (q23 + q01);
         const rot_33 = (q0Sq - q1Sq - q2Sq + q3Sq);
         const x = point[0];
         const y = point[1];
         const z = point[2];
         const rotX = rot_11 * x + rot_12 * y + rot_13 * z;
         const rotY = rot_21 * x + rot_22 * y + rot_23 * z;
         const rotZ = rot_31 * x + rot_32 * y + rot_33 * z;
         return out instanceof Float64Array
             ? Vec3Math.set(rotX, rotY, rotZ, out)
             : out.setFromCartesian(Vec3Math.set(rotX, rotY, rotZ, GeoCircle.vec3Cache[2]));
     }
     /**
      * Calculates and returns the set of intersection points between this circle and another one, and writes the results
      * to an array of position vectors.
      * @param other The other circle to test for intersections.
      * @param out An array in which to store the results. The results will be stored at indexes 0 and 1. If these indexes
      * are empty, then new Float64Array objects will be created and inserted into the array.
      * @returns The number of solutions written to the out array. Either 0, 1, or 2.
      */
     intersection(other, out) {
         const center1 = this._center;
         const center2 = other._center;
         const radius1 = this._radius;
         const radius2 = other._radius;
         /**
          * Theory: We can model geo circles as the intersection between a sphere and the unit sphere (Earth's surface).
          * Therefore, the intersection of two geo circles is the intersection between two spheres AND the unit sphere.
          * First, we find the intersection of the two non-Earth spheres (which can either be a sphere, a circle, or a
          * point), then we find the intersection of that geometry with the unit sphere.
          */
         const dot = Vec3Math.dot(center1, center2);
         const dotSquared = dot * dot;
         if (dotSquared === 1) {
             // the two circles are concentric; either there are zero solutions or infinite solutions; either way we don't
             // write any solutions to the array.
             return 0;
         }
         // find the position vector to the center of the circle which defines the intersection of the two geo circle
         // spheres.
         const a = (Math.cos(radius1) - dot * Math.cos(radius2)) / (1 - dotSquared);
         const b = (Math.cos(radius2) - dot * Math.cos(radius1)) / (1 - dotSquared);
         const intersection = Vec3Math.add(Vec3Math.multScalar(center1, a, GeoCircle.vec3Cache[0]), Vec3Math.multScalar(center2, b, GeoCircle.vec3Cache[1]), GeoCircle.vec3Cache[0]);
         const intersectionLengthSquared = Vec3Math.dot(intersection, intersection);
         if (intersectionLengthSquared > 1) {
             // the two geo circle spheres do not intersect.
             return 0;
         }
         const cross = Vec3Math.cross(center1, center2, GeoCircle.vec3Cache[1]);
         const crossLengthSquared = Vec3Math.dot(cross, cross);
         if (crossLengthSquared === 0) {
             // this technically can't happen (since we already check if center1 dot center2 === +/-1 above, but just in
             // case...)
             return 0;
         }
         const offset = Math.sqrt((1 - intersectionLengthSquared) / crossLengthSquared);
         let solutionCount = 1;
         if (!out[0]) {
             out[0] = new Float64Array(3);
         }
         out[0].set(cross);
         Vec3Math.multScalar(out[0], offset, out[0]);
         Vec3Math.add(out[0], intersection, out[0]);
         if (offset > 0) {
             if (!out[1]) {
                 out[1] = new Float64Array(3);
             }
             out[1].set(cross);
             Vec3Math.multScalar(out[1], -offset, out[1]);
             Vec3Math.add(out[1], intersection, out[1]);
             solutionCount++;
         }
         return solutionCount;
     }
     /**
      * Calculates and returns the set of intersection points between this circle and another one, and writes the results
      * to an array of GeoPoint objects.
      * @param other The other circle to test for intersections.
      * @param out An array in which to store the results. The results will be stored at indexes 0 and 1. If these indexes
      * are empty, then new GeoPoint objects will be created and inserted into the array.
      * @returns The number of solutions written to the out array. Either 0, 1, or 2.
      */
     intersectionGeoPoint(other, out) {
         const solutionCount = this.intersection(other, GeoCircle.intersectionCache);
         for (let i = 0; i < solutionCount; i++) {
             if (!out[i]) {
                 out[i] = new GeoPoint(0, 0);
             }
             out[i].setFromCartesian(GeoCircle.intersectionCache[i]);
         }
         return solutionCount;
     }
     /**
      * Calculates and returns the number of intersection points between this circle and another one. Returns NaN if there
      * are an infinite number of intersection points.
      * @param other The other circle to test for intersections.
      * @param tolerance The error tolerance, in great-arc radians, of this operation. Defaults to
      * `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns the number of intersection points between this circle and the other one.
      */
     numIntersectionPoints(other, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         const center1 = this.center;
         const center2 = other.center;
         const radius1 = this.radius;
         const radius2 = other.radius;
         const dot = Vec3Math.dot(center1, center2);
         const dotSquared = dot * dot;
         if (dotSquared === 1) {
             // the two circles are concentric; if they are the same circle there are an infinite number of intersections,
             // otherwise there are none.
             if (dot === 1) {
                 // centers are the same
                 return (Math.abs(this.radius - other.radius) <= tolerance) ? NaN : 0;
             }
             else {
                 // centers are antipodal
                 return (Math.abs(Math.PI - this.radius - other.radius) <= tolerance) ? NaN : 0;
             }
         }
         const a = (Math.cos(radius1) - dot * Math.cos(radius2)) / (1 - dotSquared);
         const b = (Math.cos(radius2) - dot * Math.cos(radius1)) / (1 - dotSquared);
         const intersection = Vec3Math.add(Vec3Math.multScalar(center1, a, GeoCircle.vec3Cache[0]), Vec3Math.multScalar(center2, b, GeoCircle.vec3Cache[1]), GeoCircle.vec3Cache[1]);
         const intersectionLengthSquared = Vec3Math.dot(intersection, intersection);
         if (intersectionLengthSquared > 1) {
             return 0;
         }
         const cross = Vec3Math.cross(center1, center2, GeoCircle.vec3Cache[1]);
         const crossLengthSquared = Vec3Math.dot(cross, cross);
         if (crossLengthSquared === 0) {
             return 0;
         }
         const sinTol = Math.sin(tolerance);
         return ((1 - intersectionLengthSquared) / crossLengthSquared > sinTol * sinTol) ? 2 : 1;
     }
     /**
      * Creates a new small circle from a lat/long coordinate pair and radius.
      * @param point The center of the new small circle.
      * @param radius The radius of the new small circle, in great-arc radians.
      * @returns a small circle.
      */
     static createFromPoint(point, radius) {
         return new GeoCircle(GeoPoint.sphericalToCartesian(point, GeoCircle.vec3Cache[0]), radius);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static createGreatCircle(arg1, arg2) {
         return new GeoCircle(GeoCircle._getGreatCircleNormal(arg1, arg2, GeoCircle.vec3Cache[0]), Math.PI / 2);
     }
     /**
      * Creates a new great circle defined by one point and a bearing offset. The new great circle will be equivalent to
      * the path projected from the point with the specified initial bearing (forward azimuth).
      * @param point A point that lies on the new great circle.
      * @param bearing The initial bearing from the point.
      * @returns a great circle.
      */
     static createGreatCircleFromPointBearing(point, bearing) {
         return new GeoCircle(GeoCircle.getGreatCircleNormalFromPointBearing(point, bearing, GeoCircle.vec3Cache[0]), Math.PI / 2);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static getGreatCircleNormal(arg1, arg2, out) {
         return GeoCircle._getGreatCircleNormal(arg1, arg2, out);
     }
     /**
      * Calculates a normal vector for a great circle given two points which lie on the circle, or a point and initial bearing.
      * @param arg1 A point that lies on the great circle.
      * @param arg2 A second point that lies on the great circle, or an initial bearing from the first point.
      * @param out The vector to which to write the result.
      * @returns the normal vector for the great circle.
      */
     static _getGreatCircleNormal(arg1, arg2, out) {
         if (typeof arg2 === 'number') {
             return GeoCircle.getGreatCircleNormalFromPointBearing(arg1, arg2, out);
         }
         else {
             return GeoCircle.getGreatCircleNormalFromPoints(arg1, arg2, out);
         }
     }
     /**
      * Calculates a normal vector for a great circle given two points which lie on the cirlce.
      * @param point1 The first point that lies on the great circle.
      * @param point2 The second point that lies on the great circle.
      * @param out The vector to which to write the result.
      * @returns the normal vector for the great circle.
      */
     static getGreatCircleNormalFromPoints(point1, point2, out) {
         if (!(point1 instanceof Float64Array)) {
             point1 = GeoPoint.sphericalToCartesian(point1, GeoCircle.vec3Cache[0]);
         }
         if (!(point2 instanceof Float64Array)) {
             point2 = GeoPoint.sphericalToCartesian(point2, GeoCircle.vec3Cache[1]);
         }
         return Vec3Math.normalize(Vec3Math.cross(point1, point2, out), out);
     }
     /**
      * Calculates a normal vector for a great circle given a point and initial bearing.
      * @param point A point that lies on the great circle.
      * @param bearing The initial bearing from the point.
      * @param out The vector to which to write the result.
      * @returns the normal vector for the great circle.
      */
     static getGreatCircleNormalFromPointBearing(point, bearing, out) {
         if (point instanceof Float64Array) {
             point = GeoCircle.tempGeoPoint.setFromCartesian(point);
         }
         const lat = point.lat * Avionics.Utils.DEG2RAD;
         const long = point.lon * Avionics.Utils.DEG2RAD;
         bearing *= Avionics.Utils.DEG2RAD;
         const sinLat = Math.sin(lat);
         const sinLon = Math.sin(long);
         const cosLon = Math.cos(long);
         const sinBearing = Math.sin(bearing);
         const cosBearing = Math.cos(bearing);
         const x = sinLon * cosBearing - sinLat * cosLon * sinBearing;
         const y = -cosLon * cosBearing - sinLat * sinLon * sinBearing;
         const z = Math.cos(lat) * sinBearing;
         return Vec3Math.set(x, y, z, out);
     }
 }
 GeoCircle.ANGULAR_TOLERANCE = 1e-7; // ~61cm
 GeoCircle.NORTH_POLE = new Float64Array([0, 0, 1]);
 GeoCircle.tempGeoPoint = new GeoPoint(0, 0);
 GeoCircle.vec3Cache = [new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3)];
 GeoCircle.intersectionCache = [new Float64Array(3), new Float64Array(3)];
 
 /**
  * Navigational mathematics functions.
  */
 class NavMath {
     /**
      * Clamps a value to a min and max.
      * @param val The value to clamp.
      * @param min The minimum value to clamp to.
      * @param max The maximum value to clamp to.
      * @returns The clamped value.
      */
     static clamp(val, min, max) {
         return Math.min(Math.max(val, min), max);
     }
     /**
      * Normalizes a heading to a 0-360 range.
      * @param heading The heading to normalize.
      * @returns The normalized heading.
      */
     static normalizeHeading(heading) {
         if (isFinite(heading)) {
             return (heading % 360 + 360) % 360;
         }
         else {
             console.error(`normalizeHeading: Invalid heading: ${heading}`);
             return NaN;
         }
     }
     /**
      * Gets the turn radius for a given true airspeed.
      * @param airspeedTrue The true airspeed of the plane.
      * @param bankAngle The bank angle of the plane, in degrees.
      * @returns The airplane turn radius.
      */
     static turnRadius(airspeedTrue, bankAngle) {
         return (Math.pow(airspeedTrue, 2) / (11.26 * Math.tan(bankAngle * NavMath.DEG2RAD)))
             / 3.2808399;
     }
     /**
      * Gets the required bank angle for a given true airspeed and turn radius.
      * @param airspeedTrue The true airspeed of the plane.
      * @param radius The airplane turn radius.
      * @returns The required bank angle, in degrees.
      */
     static bankAngle(airspeedTrue, radius) {
         const airspeedMS = airspeedTrue * 0.51444444;
         return Units.Radians.toDegrees(Math.atan(Math.pow(airspeedMS, 2) / (radius * 9.80665)));
     }
     /**
      * Get the turn direction for a given course change.
      * @param startCourse The start course.
      * @param endCourse The end course.
      * @returns The turn direction for the course change.
      */
     static getTurnDirection(startCourse, endCourse) {
         return NavMath.normalizeHeading(endCourse - startCourse) > 180 ? 'left' : 'right';
     }
     /**
      * Converts polar radians to degrees north.
      * @param radians The radians to convert.
      * @returns The angle, in degrees north.
      */
     static polarToDegreesNorth(radians) {
         return NavMath.normalizeHeading((180 / Math.PI) * (Math.PI / 2 - radians));
     }
     /**
      * Converts degrees north to polar radians.
      * @param degrees The degrees to convert.
      * @returns The angle radians, in polar.
      */
     static degreesNorthToPolar(degrees) {
         return NavMath.normalizeHeading(degrees - 90) / (180 / Math.PI);
     }
     /**
      * Calculates the distance along an arc on Earth's surface. The arc begins at the intersection of the great circle
      * passing through the center of a circle of radius `radius` meters in the direction of 'startBearing', and ends at
      * the intersection of the great circle passing through the center of the circle in the direction of 'endBearing',
      * proceeding clockwise (as viewed from above).
      * @param startBearing The degrees of the start of the arc.
      * @param endBearing The degrees of the end of the arc.
      * @param radius The radius of the arc, in meters.
      * @returns The arc distance.
      */
     static calculateArcDistance(startBearing, endBearing, radius) {
         const angularWidth = ((endBearing - startBearing + 360) % 360) * Avionics.Utils.DEG2RAD;
         const conversion = UnitType.GA_RADIAN.convertTo(1, UnitType.METER);
         return angularWidth * Math.sin(radius / conversion) * conversion;
     }
     /**
      * Calculates the intersection of a line and a circle.
      * @param x1 The start x of the line.
      * @param y1 The start y of the line.
      * @param x2 The end x of the line.
      * @param y2 The end y of the line.
      * @param cx The circle center x.
      * @param cy The circle center y.
      * @param r The radius of the circle.
      * @param sRef The reference to the solution object to write the solution to.
      * @returns The number of solutions (0, 1 or 2).
      */
     static circleIntersection(x1, y1, x2, y2, cx, cy, r, sRef) {
         const dx = x2 - x1;
         const dy = y2 - y1;
         const a = dx * dx + dy * dy;
         const b = 2 * (dx * (x1 - cx) + dy * (y1 - cy));
         const c = (x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy) - r * r;
         const det = b * b - 4 * a * c;
         if (a < 0.0000001 || det < 0) {
             sRef.x1 = NaN;
             sRef.x2 = NaN;
             sRef.y1 = NaN;
             sRef.y2 = NaN;
             return 0;
         }
         else if (det == 0) {
             const t = -b / (2 * a);
             sRef.x1 = x1 + t * dx;
             sRef.y1 = y1 + t * dy;
             sRef.x2 = NaN;
             sRef.y2 = NaN;
             return 1;
         }
         else {
             const t1 = ((-b + Math.sqrt(det)) / (2 * a));
             sRef.x1 = x1 + t1 * dx;
             sRef.y1 = y1 + t1 * dy;
             const t2 = ((-b - Math.sqrt(det)) / (2 * a));
             sRef.x2 = x1 + t2 * dx;
             sRef.y2 = y1 + t2 * dy;
             return 2;
         }
     }
     /**
      * Gets the degrees north that a point lies on a circle.
      * @param cx The x point of the center of the circle.
      * @param cy The y point of the center of the circle.
      * @param x The x point to get the bearing for.
      * @param y The y point to get the bearing for.
      * @returns The angle in degrees north that the point is relative to the center.
      */
     static northAngle(cx, cy, x, y) {
         return NavMath.polarToDegreesNorth(Math.atan2(y - cy, x - cx));
     }
     /**
      * Checks if a degrees north bearing is between two other degrees north bearings.
      * @param bearing The bearing in degrees north to check.
      * @param start The start bearing in degrees north.
      * @param end The end bearing, in degrees north.
      * @returns True if the bearing is between the two provided bearings, false otherwise.
      */
     static bearingIsBetween(bearing, start, end) {
         const range = this.normalizeHeading(end - start);
         const relativeBearing = this.normalizeHeading(bearing - start);
         return relativeBearing >= 0 && relativeBearing <= range;
     }
     /**
      * Converts a degrees north heading to a degrees north turn circle angle.
      * @param heading The heading to convert.
      * @param turnDirection The direction of the turn.
      * @returns A degrees north turn circle angle.
      */
     static headingToAngle(heading, turnDirection) {
         return NavMath.normalizeHeading(heading + (turnDirection === 'left' ? 90 : -90));
     }
     /**
      * Converts a degrees north turn circle angle to a degrees north heading.
      * @param angle The turn circle angle to convert.
      * @param turnDirection The direction of the turn.
      * @returns A degrees north heading.
      */
     static angleToHeading(angle, turnDirection) {
         return NavMath.normalizeHeading(angle + (turnDirection === 'left' ? -90 : 90));
     }
     /**
      * Calculates the wind correction angle.
      * @param course The current plane true course.
      * @param airspeedTrue The current plane true airspeed.
      * @param windDirection The direction of the wind, in degrees true.
      * @param windSpeed The current speed of the wind.
      * @returns The calculated wind correction angle.
      */
     static windCorrectionAngle(course, airspeedTrue, windDirection, windSpeed) {
         const currCrosswind = windSpeed * (Math.sin((course * Math.PI / 180) - (windDirection * Math.PI / 180)));
         const windCorrection = 180 * Math.asin(currCrosswind / airspeedTrue) / Math.PI;
         return windCorrection;
     }
     /**
      * Calculates the cross track deviation from the provided leg fixes.
      * @param start The location of the starting fix of the leg.
      * @param end The location of the ending fix of the leg.
      * @param pos The current plane location coordinates.
      * @returns The amount of cross track deviation, in nautical miles.
      */
     static crossTrack(start, end, pos) {
         const path = NavMath.geoCircleCache[0].setAsGreatCircle(start, end);
         if (isNaN(path.center[0])) {
             return NaN;
         }
         return UnitType.GA_RADIAN.convertTo(path.distance(pos), UnitType.NMILE);
     }
     /**
      * Calculates the along-track distance from a starting point to another point along a great-circle track running
      * through the starting point.
      * @param start The start of the great-circle track.
      * @param end The end of the great-circle track.
      * @param pos The point for which to calculate the along-track distance.
      * @returns The along-track distance, in nautical miles.
      */
     static alongTrack(start, end, pos) {
         const path = NavMath.geoCircleCache[0].setAsGreatCircle(start, end);
         if (isNaN(path.center[0])) {
             return NaN;
         }
         const distance = path.distanceAlong(start, path.closest(pos, NavMath.vec3Cache[0]));
         return UnitType.GA_RADIAN.convertTo((distance + Math.PI) % (2 * Math.PI) - Math.PI, UnitType.NMILE);
     }
     /**
      * Calculates the desired track from the provided leg fixes.
      * @param start The location of the starting fix of the leg.
      * @param end The location of the ending fix of the leg.
      * @param pos The current plane location coordinates.
      * @returns The desired track, in degrees true.
      */
     static desiredTrack(start, end, pos) {
         const path = NavMath.geoCircleCache[0].setAsGreatCircle(start, end);
         if (isNaN(path.center[0])) {
             return NaN;
         }
         return path.bearingAt(path.closest(pos, NavMath.vec3Cache[0]));
     }
     /**
      * Gets the desired track for a given arc.
      * @param center The center of the arc.
      * @param turnDirection The direction of the turn.
      * @param pos The current plane position.
      * @returns The desired track.
      */
     static desiredTrackArc(center, turnDirection, pos) {
         const northAngle = NavMath.geoPointCache[0].set(pos).bearingFrom(center);
         //TODO: Clamp the arc angle to the start and end angles
         return NavMath.angleToHeading(northAngle, turnDirection);
     }
     /**
      * Gets the percentage along the arc path that the plane currently is.
      * @param start The start of the arc, in degrees north.
      * @param end The end of the arc, in degrees north.
      * @param center The center location of the arc.
      * @param turnDirection The direction of the turn.
      * @param pos The current plane position.
      * @returns The percentage along the arc the plane is.
      */
     static percentAlongTrackArc(start, end, center, turnDirection, pos) {
         const bearingFromCenter = NavMath.geoPointCache[0].set(center).bearingTo(pos);
         const sign = turnDirection === 'right' ? 1 : -1;
         const alpha = ((end - start) * sign + 360) % 360;
         const mid = (start + alpha / 2 * sign + 360) % 360;
         const rotBearing = ((bearingFromCenter - mid) + 540) % 360 - 180;
         const frac = rotBearing * sign / alpha + 0.5;
         return frac;
     }
     /**
      * Gets a position given an arc and a distance from the arc start.
      * @param start The start bearing of the arc.
      * @param center The center of the arc.
      * @param radius The radius of the arc.
      * @param turnDirection The turn direction for the arc.
      * @param distance The distance along the arc to get the position for.
      * @param out The position to write to.
      * @returns The position along the arc that was written to.
      */
     static positionAlongArc(start, center, radius, turnDirection, distance, out) {
         const convertedRadius = UnitType.GA_RADIAN.convertTo(Math.sin(UnitType.METER.convertTo(radius, UnitType.GA_RADIAN)), UnitType.METER);
         const theta = UnitType.RADIAN.convertTo(distance / convertedRadius, UnitType.DEGREE);
         const bearing = turnDirection === 'right' ? start + theta : start - theta;
         center.offset(NavMath.normalizeHeading(bearing), UnitType.METER.convertTo(radius, UnitType.GA_RADIAN), out);
         return out;
     }
     /**
      * Gets the cross track distance for a given arc.
      * @param center The center of the arc.
      * @param radius The radius of the arc, in meters.
      * @param pos The current plane position.
      * @returns The cross track distance, in NM.
      */
     static crossTrackArc(center, radius, pos) {
         return UnitType.METER.convertTo(radius, UnitType.NMILE) - UnitType.GA_RADIAN.convertTo(NavMath.geoPointCache[0].set(pos).distance(center), UnitType.NMILE);
     }
     /**
      * Gets the total difference in degrees between two angles.
      * @param a The first angle.
      * @param b The second angle.
      * @returns The difference between the two angles, in degrees.
      */
     static diffAngle(a, b) {
         let diff = b - a;
         while (diff > 180) {
             diff -= 360;
         }
         while (diff <= -180) {
             diff += 360;
         }
         return diff;
     }
     /**
      * Finds side a given sides b, c, and angles beta, gamma.
      * @param b The length of side b, as a trigonometric ratio.
      * @param c The length of side c, as a trigonometric ratio.
      * @param beta The angle, in radians, of the opposite of side b.
      * @param gamma The angle, in radians, of the opposite of side c
      * @returns The length of side a, as a trigonometric ratio.
      */
     static napierSide(b, c, beta, gamma) {
         return 2 * Math.atan(Math.tan(0.5 * (b - c))
             * (Math.sin(0.5 * (beta + gamma)) / Math.sin(0.5 * (beta - gamma))));
     }
     /**
      * Calculates a normal vector to a provided course in degrees north.
      * @param course The course in degrees north.
      * @param turnDirection The direction of the turn to orient the normal.
      * @param outVector The normal vector for the provided course.
      */
     static normal(course, turnDirection, outVector) {
         const normalCourse = NavMath.headingToAngle(course, turnDirection);
         const polarCourse = NavMath.degreesNorthToPolar(normalCourse);
         outVector[0] = Math.cos(polarCourse);
         outVector[1] = Math.sin(polarCourse);
     }
 }
 NavMath.DEG2RAD = Math.PI / 180;
 NavMath.RAD2DEG = 180 / Math.PI;
 NavMath.vec3Cache = [new Float64Array(3)];
 NavMath.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0)];
 NavMath.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 
 /// <reference types="msfstypes/Coherent/Facilities" />
 /**
  * A utility class for working with magnetic variation (magnetic declination).
  */
 class MagVar {
     // eslint-disable-next-line jsdoc/require-jsdoc
     static get(arg1, arg2) {
         return MagVar.getMagVar(arg1, arg2);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static magneticToTrue(bearing, arg1, arg2) {
         return NavMath.normalizeHeading(bearing + (typeof arg1 === 'number' && arg2 === undefined ? arg1 : MagVar.getMagVar(arg1, arg2)));
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static trueToMagnetic(bearing, arg1, arg2) {
         return NavMath.normalizeHeading(bearing - (typeof arg1 === 'number' && arg2 === undefined ? arg1 : MagVar.getMagVar(arg1, arg2)));
     }
     /**
      * Gets the magnetic variation (magnetic declination) at a specific point on Earth.
      * @param arg1 The query point, or the latitude of the query point.
      * @param arg2 The longitude of the query point.
      * @returns The magnetic variation (magnetic declination) at the point.
      */
     static getMagVar(arg1, arg2) {
         if (typeof Facilities === 'undefined') {
             // In case this code is executed before the Facilities class is created.
             return 0;
         }
         let lat, lon;
         if (typeof arg1 === 'number') {
             lat = arg1;
             lon = arg2;
         }
         else {
             lat = arg1.lat;
             lon = arg1.lon;
         }
         return Facilities.getMagVar(lat, lon);
     }
 }
 
 /**
  * A utitlity class for basic math.
  */
 class MathUtils {
     /**
      * Clamps a numerical value to the min/max range.
      * @param value The value to be clamped.
      * @param min The minimum.
      * @param max The maximum.
      *
      * @returns The clamped numerical value..
      */
     static clamp(value, min, max) {
         return Math.max(min, Math.min(max, value));
     }
 }
 
 /**
  * Applies time-weighted exponential smoothing (i.e. an exponential moving average) to a sequence of raw values. When
  * a new raw value is added to the sequence, it and the last smoothed value are weighted according to the time elapsed
  * since the last smoothed value was calculated (i.e. since the last raw value was added) and averaged. The calculation
  * of the weighting is such that the weight of each raw value in the sequence decays exponentially with the "age"
  * (i.e. time elapsed between when that value was added to the sequence and when the latest value was added to the
  * sequence) of the value.
  */
 class ExpSmoother {
     /**
      * Constructor.
      * @param tau This smoother's time constant. The larger the constant, the greater the smoothing effect.
      * @param initial The initial smoothed value of this smoother. Defaults to null.
      * @param dtThreshold The elapsed time threshold, in seconds, above which this smoother will not smooth a new raw
      * value. Defaults to infinity.
      */
     constructor(tau, initial = null, dtThreshold = Infinity) {
         this.tau = tau;
         this.dtThreshold = dtThreshold;
         this.lastValue = initial;
     }
     /**
      * Gets the last smoothed value.
      * @returns the last smoothed value, or null if none exists.
      */
     last() {
         return this.lastValue;
     }
     /**
      * Adds a new raw value and gets the next smoothed value. If the new raw value is the first to be added since this
      * smoother was created or reset with no initial smoothed value, the returned smoothed value will be equal to the
      * raw value.
      * @param raw The new raw value.
      * @param dt The elapsed time, in seconds, since the last raw value was added.
      * @returns The next smoothed value.
      */
     next(raw, dt) {
         let next;
         if (this.lastValue !== null) {
             const factor = this.calculateFactor(dt);
             next = ExpSmoother.smooth(raw, this.lastValue, factor);
         }
         else {
             next = raw;
         }
         this.lastValue = next;
         return next;
     }
     /**
      * Calculates the smoothing factor for a given time interval.
      * @param dt A time interval, in seconds.
      * @returns the smoothing factor for the given time interval.
      */
     calculateFactor(dt) {
         if (dt > this.dtThreshold) {
             return 0;
         }
         else {
             return Math.exp(-dt / this.tau);
         }
     }
     /**
      * Resets the "history" of this smoother and optionally sets the initial smoothed value.
      * @param value The new initial smoothed value. Defaults to null.
      * @returns The reset smoothed value.
      */
     reset(value) {
         return this.lastValue = (value !== null && value !== void 0 ? value : null);
     }
     /**
      * Applies exponential smoothing.
      * @param value The value to smooth.
      * @param last The last smoothed value.
      * @param factor The smoothing factor.
      * @returns A smoothed value.
      */
     static smooth(value, last, factor) {
         return value * (1 - factor) + last * factor;
     }
 }
 
 /**
  * The possible reference norths for navigation angle units.
  */
 var NavAngleUnitReferenceNorth;
 (function (NavAngleUnitReferenceNorth) {
     NavAngleUnitReferenceNorth["True"] = "true";
     NavAngleUnitReferenceNorth["Magnetic"] = "magnetic";
 })(NavAngleUnitReferenceNorth || (NavAngleUnitReferenceNorth = {}));
 /**
  * A navigation angle unit, which is a measure of angular degrees relative to either true or magnetic north.
  *
  * Unlike most other unit types, each instance of navigation angle unit contains state specific to that instance,
  * namely the location used to retrieve magnetic variation for conversions. Therefore, it is generally recommended
  * not to re-use the same NavAngleUnit instance to instantiate multiple NumberUnits.
  *
  * Conversions use the location of the NavAngleUnit instance whose conversion method is called; this also means that
  * when using `NumberUnit.asUnit()`, the location of the unit of the NumberUnit whose `asUnit()` method was called
  * will be used.
  */
 class NavAngleUnit extends AbstractUnit {
     // eslint-disable-next-line jsdoc/require-jsdoc
     constructor(type, arg1, arg2) {
         super(type === NavAngleUnitReferenceNorth.True ? 'true bearing' : 'magnetic bearing');
         /** @inheritdoc */
         this.family = NavAngleUnit.FAMILY;
         /** This location used to retrieve magnetic variation for conversions related to this unit. */
         this.location = new GeoPoint(0, 0);
         typeof arg1 === 'number' ? this.location.set(arg1, arg2) : this.location.set(arg1);
     }
     /**
      * Checks whether this nav angle unit is relative to magnetic north.
      * @returns Whether this nav angle unit is relative to magnetic north.
      */
     isMagnetic() {
         return this.name === 'magnetic bearing';
     }
     /**
      * Converts a value of this unit to another unit. This unit's location is used for the conversion.
      * @param value The value to convert.
      * @param toUnit The unit to which to convert.
      * @returns The converted value.
      * @throws Error if attempting an invalid conversion.
      */
     convertTo(value, toUnit) {
         if (!this.canConvert(toUnit)) {
             throw new Error(`Invalid conversion from ${this.name} to ${toUnit.name}.`);
         }
         if (!isFinite(value)) {
             return NaN;
         }
         if (toUnit.name === this.name) {
             return value;
         }
         return this.isMagnetic() ? MagVar.magneticToTrue(value, this.location) : MagVar.trueToMagnetic(value, this.location);
     }
     /**
      * Converts a value of another unit to this unit. This unit's location is used for the conversion.
      * @param value The value to convert.
      * @param fromUnit The unit from which to convert.
      * @returns The converted value.
      * @throws Error if attempting an invalid conversion.
      */
     convertFrom(value, fromUnit) {
         if (!this.canConvert(fromUnit)) {
             throw new Error(`Invalid conversion from ${fromUnit.name} to ${this.name}.`);
         }
         if (!isFinite(value)) {
             return NaN;
         }
         if (fromUnit.name === this.name) {
             return value;
         }
         return this.isMagnetic() ? MagVar.trueToMagnetic(value, this.location) : MagVar.magneticToTrue(value, this.location);
     }
     /** @inheritdoc */
     equals(other) {
         return other instanceof NavAngleUnit && this.name === other.name && this.location.equals(other.location);
     }
     /**
      * Creates an instance of NavAngleUnit. The location of the unit is initialized to {0 N, 0 E}.
      * @param isMagnetic Whether the new unit is relative to magnetic north.
      * @returns An instance of NavAngleUnit.
      */
     static create(isMagnetic) {
         return new NavAngleUnit(isMagnetic ? NavAngleUnitReferenceNorth.Magnetic : NavAngleUnitReferenceNorth.True, 0, 0);
     }
 }
 NavAngleUnit.FAMILY = 'navangle';
 
 /**
  * Bitflags describing the relative location of a point with respect to a rectangular bounding box.
  */
 var Outcode;
 (function (Outcode) {
     Outcode[Outcode["Inside"] = 0] = "Inside";
     Outcode[Outcode["Left"] = 1] = "Left";
     Outcode[Outcode["Top"] = 2] = "Top";
     Outcode[Outcode["Right"] = 4] = "Right";
     Outcode[Outcode["Bottom"] = 8] = "Bottom";
 })(Outcode || (Outcode = {}));
 Array.from({ length: 8 }, () => {
     return { point: new Float64Array(2), radial: 0 };
 });
 
 [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
 
 var DmsDirection;
 (function (DmsDirection) {
     DmsDirection["NORTH"] = "N";
     DmsDirection["SOUTH"] = "S";
     DmsDirection["WEST"] = "W";
     DmsDirection["EAST"] = "E";
 })(DmsDirection || (DmsDirection = {}));
 
 // eslint-disable-next-line @typescript-eslint/no-namespace
 var Wait;
 (function (Wait) {
     /**
      * Waits for a set amount of time.
      * @param delay The amount of time to wait in milliseconds.
      * @returns a Promise which is fulfilled after the delay.
      */
     // eslint-disable-next-line no-inner-declarations
     function awaitDelay(delay) {
         return new Promise(resolve => setTimeout(() => resolve(), delay));
     }
     Wait.awaitDelay = awaitDelay;
     /**
      * Waits for a condition to be satisfied.
      * @param predicate A function which evaluates whether the condition is satisfied.
      * @param interval The interval, in milliseconds, at which to evaluate the condition. A zero or negative value
      * causes the condition to be evaluated every frame. Defaults to 0.
      * @param timeout The amount of time, in milliseconds, before the returned Promise is rejected if the condition is
      * not satisfied. A zero or negative value causes the Promise to never be rejected and the condition to be
      * continually evaluated until it is satisfied. Defaults to 0.
      * @returns a Promise which is fulfilled when the condition is satisfied.
      */
     // eslint-disable-next-line no-inner-declarations
     function awaitCondition(predicate, interval = 0, timeout = 0) {
         const t0 = Date.now();
         if (interval <= 0) {
             const loopFunc = (resolve, reject) => {
                 if (timeout > 0 && Date.now() - t0 >= timeout) {
                     reject('Await condition timed out.');
                 }
                 else {
                     predicate() ? resolve() : requestAnimationFrame(loopFunc.bind(undefined, resolve, reject));
                 }
             };
             return new Promise((resolve, reject) => { loopFunc(resolve, reject); });
         }
         else {
             return new Promise((resolve, reject) => {
                 const timer = setInterval(() => {
                     if (timeout > 0 && Date.now() - t0 > timeout) {
                         clearInterval(timer);
                         reject('Await condition timed out.');
                     }
                     else if (predicate()) {
                         clearInterval(timer);
                         resolve();
                     }
                 }, interval);
             });
         }
     }
     Wait.awaitCondition = awaitCondition;
 })(Wait || (Wait = {}));
 
 /**
  * Can be used in classes to provide support for custom events.
  * @class SubEvent
  */
 class SubEvent {
     constructor() {
         this.handlers = [];
     }
     /**
      * Subscribe to this event.
      * @param handler The handler to be called when the event is emitted.
      */
     on(handler) {
         this.handlers.push(handler);
     }
     /**
      * Unsubscribe from this event.
      * @param handler The handler to be called when the event is emitted.
      */
     off(handler) {
         this.handlers = this.handlers.filter(h => h !== handler);
     }
     /**
      * Clears all subscriptions to this event.
      */
     clear() {
         this.handlers.length = 0;
     }
     /**
      * Emit event to subscribers.
      * @param sender The object emitting the event.
      * @param [data] The event arguments.
      */
     notify(sender, data) {
         const handlers = [...this.handlers];
         for (let i = 0; i < handlers.length; i++) {
             handlers[i](sender, data);
         }
     }
 }
 
 /**
  * Utility class for manipulating bit flags.
  */
 class BitFlags {
     /**
      * Generates a bit flag with a boolean value of true at a specified index.
      * @param index The index of the flag. Must be between 0 and 32, inclusive.
      * @returns a bit flag.
      * @throws Error if index is out of bounds.
      */
     static createFlag(index) {
         if (index < 0 || index > 32) {
             throw new Error(`Invalid index ${index} for bit flag. Index must be between 0 and 32.`);
         }
         return 1 << index;
     }
     /**
      * Gets the inverse of some bit flags.
      * @param flags The bit flag group containing the flags to invert.
      * @param mask An optional bit mask to use when applying the inverse operation. The operation will only be performed
      * at the indexes where the mask has a value of 1 (true). If a mask is not specified, the operation will be performed
      * at all indexes.
      * @returns the inverse
      */
     static not(flags, mask = ~0) {
         return flags ^ mask;
     }
     /**
      * Gets the union of zero or more bit flags.
      * @param flags A list of bit flags.
      * @returns the union of the bit flags.
      */
     static union(...flags) {
         let result = 0;
         const len = flags.length;
         for (let i = 0; i < len; i++) {
             result |= flags[i];
         }
         return result;
     }
     /**
      * Gets the intersection of zero or more bit flags.
      * @param flags A list of bit flags.
      * @returns the intersection of the bit flags.
      */
     static intersection(...flags) {
         const len = flags.length;
         if (len === 0) {
             return 0;
         }
         let result = flags[0];
         for (let i = 1; i < len; i++) {
             result &= flags[i];
         }
         return result;
     }
     /**
      * Checks if a bit flag group meets at least one condition from a list of conditions.
      * @param flags A bit flag group.
      * @param conditions The conditions to meet, as a bit flag group.
      * @returns whether the bit flag group meets at least one condition.
      */
     static isAny(flags, conditions) {
         return (flags & conditions) !== 0;
     }
     /**
      * Checks if a bit flag group meets all the conditions from a list of conditions.
      * @param flags A bit flag group.
      * @param conditions The conditions to meet, as a bit flag group.
      * @returns whether the bit flag group meets all the conditions.
      */
     static isAll(flags, conditions) {
         return (flags & conditions) === conditions;
     }
     /**
      * Iterates through a bit flag group and executes a callback function once for each flag.
      * @param flags A bit flag group.
      * @param callback A function which will be called once for each flag.
      * @param valueFilter The value on which to filter. If defined, only flags with values equal to the filter will be
      * iterated, otherwise all flags will be iterated regardless of their values.
      * @param startIndex The index of the flag at which to start (inclusive). Defaults to 0.
      * @param endIndex The index of the flag at which to end (exclusive). Defaults to 32.
      */
     static forEach(flags, callback, valueFilter, startIndex, endIndex) {
         startIndex = Utils.Clamp(startIndex !== null && startIndex !== void 0 ? startIndex : (startIndex = 0), 0, 32);
         endIndex = Utils.Clamp(endIndex !== null && endIndex !== void 0 ? endIndex : (endIndex = 32), 0, 32);
         for (let i = startIndex; i < endIndex; i++) {
             const value = (flags & (1 << i)) !== 0;
             if (valueFilter === undefined || valueFilter === value) {
                 callback(value, i, flags);
             }
         }
     }
 }
 
 /** A class that linearly drives a SimVar value towards a given set point. */
 class LinearServo {
     /**
      * Creates an instance of a LinearServo.
      * @param rate The rate, in units per second, to drive the servo.
      */
     constructor(rate) {
         this.rate = rate;
     }
     /**
      * Drives the servo towards the set point.
      * @param currentValue The current value.
      * @param setValue The value to drive towards.
      * @returns The output value.
      */
     drive(currentValue, setValue) {
         if (this.currentTime === undefined) {
             this.currentTime = new Date().appTime();
             return currentValue;
         }
         const currentTime = new Date().appTime();
         const deltaTime = currentTime - this.currentTime;
         this.currentTime = currentTime;
         const deltaValue = setValue - currentValue;
         const maximumDrive = this.rate * (deltaTime / 1000);
         const output = Math.abs(deltaValue) > maximumDrive
             ? currentValue + (Math.sign(deltaValue) * maximumDrive)
             : setValue;
         return output;
     }
 }
 
 /** A PID controller. */
 class PidController {
     /**
      * Creates a new PidController.
      * @param kP The proportional gain of the controller.
      * @param kI The integral gain of the controller.
      * @param kD The differential gain of the controller.
      * @param maxOut The maximum output of the controller.
      * @param minOut The minumum output of the controller.
      * @param maxI The maximum integral gain.
      * @param minI The minimum integral gain.
      */
     constructor(kP, kI, kD, maxOut, minOut, maxI = Number.MAX_SAFE_INTEGER, minI = Number.MIN_SAFE_INTEGER) {
         this.kP = kP;
         this.kI = kI;
         this.kD = kD;
         this.maxOut = maxOut;
         this.minOut = minOut;
         this.maxI = maxI;
         this.minI = minI;
         /** The previously sampled error. */
         this.previousError = 0;
         /** The previously generated output. */
         this.previousOutput = 0;
         /** The currently accumulated integral. */
         this.integral = 0;
     }
     /**
      * Gets the output of the PID controller at a given time.
      * @param deltaTime The difference in time between the previous sample and this sample.
      * @param error The amount of error seen between the desired output and the current output.
      * @returns The PID output.
      */
     getOutput(deltaTime, error) {
         const p = this.kP * error;
         if (Math.sign(error) === Math.sign(this.previousError)) {
             this.integral += ((error * deltaTime) + ((deltaTime * (error - this.previousError)) / 2)) * this.kI;
             this.integral = PidController.clamp(this.integral, this.maxI, this.minI);
         }
         else {
             this.integral = 0;
         }
         const i = this.integral;
         const d = this.kD * ((error - this.previousError) / deltaTime);
         const output = PidController.clamp(p + i + d, this.maxOut, this.minOut);
         this.previousError = error;
         this.previousOutput = output;
         return output;
     }
     /** Resets the controller. */
     reset() {
         this.previousError = 0;
         this.previousOutput = 0;
         this.integral = 0;
     }
     /**
      * Clamps a number to maximum and minimum values.
      * @param value The value to clamp.
      * @param max The maximum value.
      * @param min The minumum value.
      * @returns The clamped value.
      */
     static clamp(value, max, min) {
         return Math.min(Math.max(value, min), max);
     }
 }
 
 /**
  * A simple timer for handling debounce.
  */
 class DebounceTimer {
     constructor() {
         this.timer = null;
     }
     /**
      * Checks whether an action is pending on this timer.
      * @returns Whether an action is pending on this timer.
      */
     isPending() {
         return this.timer !== null;
     }
     /**
      * Schedules an action. Waits for a specified amount of time, and executes the action only if no other action is
      * scheduled on this timer during the delay.
      * @param action The action to schedule.
      * @param delay The debounce delay, in milliseconds.
      */
     schedule(action, delay) {
         this.clear();
         this.timer = setTimeout(() => {
             this.timer = null;
             action();
         }, delay);
     }
     /**
      * Clears this timer of any pending actions. Actions that are cleared will not be executed.
      */
     clear() {
         if (this.timer === null) {
             return;
         }
         clearTimeout(this.timer);
         this.timer = null;
     }
 }
 
 /**
  * An event bus consumer for a specific topic.
  */
 class Consumer {
     /**
      * Creates an instance of a Consumer.
      * @param bus The event bus to subscribe to.
      * @param topic The topic of the subscription.
      * @param state The state for the consumer to track.
      * @param currentHandler The current build filter handler stack, if any.
      */
     constructor(bus, topic, state = {}, currentHandler) {
         this.bus = bus;
         this.topic = topic;
         this.state = state;
         this.currentHandler = currentHandler;
     }
     /**
      * Handles an event using the provided event handler.
      * @param handler The event handler for the event.
      */
     handle(handler) {
         if (this.currentHandler !== undefined) {
             /**
              * The handler reference to store.
              * @param data The input data to the handler.
              */
             this.handlerReference = (data) => {
                 if (this.currentHandler !== undefined) {
                     this.currentHandler(data, this.state, handler);
                 }
             };
             this.bus.on(this.topic, this.handlerReference);
         }
         else {
             this.bus.on(this.topic, handler);
         }
     }
     /**
      * Disables handling of the event.
      * @param handler The handler to disable.
      */
     off(handler) {
         if (this.handlerReference !== undefined) {
             this.bus.off(this.topic, this.handlerReference);
         }
         else {
             this.bus.off(this.topic, handler);
         }
     }
     /**
      * Caps the event subscription to a specified frequency, in Hz.
      * @param frequency The frequency, in Hz, to cap to.
      * @returns A new consumer with the applied frequency filter.
      */
     atFrequency(frequency) {
         const deltaTimeTrigger = 1000 / frequency;
         return new Consumer(this.bus, this.topic, { previousTime: Date.now() }, (data, state, next) => {
             const currentTime = Date.now();
             const deltaTime = currentTime - state.previousTime;
             if (deltaTimeTrigger <= deltaTime) {
                 while ((state.previousTime + deltaTimeTrigger) < currentTime) {
                     state.previousTime += deltaTimeTrigger;
                 }
                 this.with(data, next);
             }
         });
     }
     /**
      * Quantizes the numerical event data to consume only at the specified decimal precision.
      * @param precision The decimal precision to snap to.
      * @returns A new consumer with the applied precision filter.
      */
     withPrecision(precision) {
         return new Consumer(this.bus, this.topic, { lastValue: 0 }, (data, state, next) => {
             const dataValue = data;
             const multiplier = Math.pow(10, precision);
             const currentValueAtPrecision = Math.round(dataValue * multiplier) / multiplier;
             if (currentValueAtPrecision !== state.lastValue) {
                 state.lastValue = currentValueAtPrecision;
                 this.with(currentValueAtPrecision, next);
             }
         });
     }
     /**
      * Filter the subscription to consume only when the value has changed by a minimum amount.
      * @param amount The minimum amount threshold below which the consumer will not consume.
      * @returns A new consumer with the applied change threshold filter.
      */
     whenChangedBy(amount) {
         return new Consumer(this.bus, this.topic, { lastValue: 0 }, (data, state, next) => {
             const dataValue = data;
             const diff = Math.abs(dataValue - state.lastValue);
             if (diff >= amount) {
                 state.lastValue = dataValue;
                 this.with(data, next);
             }
         });
     }
     /**
      * Filter the subscription to consume only if the value has changed. At all.  Really only
      * useful for strings or other events that don't change much.
      * @returns A new consumer with the applied change threshold filter.
      */
     whenChanged() {
         return new Consumer(this.bus, this.topic, { lastValue: '' }, (data, state, next) => {
             if (state.lastValue !== data) {
                 state.lastValue = data;
                 this.with(data, next);
             }
         });
     }
     /**
      * Filters events by time such that events will not be consumed until a minimum duration
      * has passed since the previous event.
      * @param deltaTime The minimum delta time between events.
      * @returns A new consumer with the applied change threshold filter.
      */
     onlyAfter(deltaTime) {
         return new Consumer(this.bus, this.topic, { previousTime: Date.now() }, (data, state, next) => {
             const currentTime = Date.now();
             const timeDiff = currentTime - state.previousTime;
             if (timeDiff > deltaTime) {
                 state.previousTime += deltaTime;
                 this.with(data, next);
             }
         });
     }
     /**
      * Builds a handler stack from the current handler.
      * @param data The data to send in to the handler.
      * @param handler The handler to use for processing.
      */
     with(data, handler) {
         if (this.currentHandler !== undefined) {
             this.currentHandler(data, this.state, handler);
         }
         else {
             handler(data);
         }
     }
 }
 
 /**
  * A typed container for subscribers interacting with the Event Bus.
  */
 class EventSubscriber {
     /**
      * Creates an instance of an EventSubscriber.
      * @param bus The EventBus that is the parent of this instance.
      */
     constructor(bus) {
         this.bus = bus;
     }
     /**
      * Subscribes to a topic on the bus.
      * @param topic The topic to subscribe to.
      * @returns A consumer to bind the event handler to.
      */
     on(topic) {
         return new Consumer(this.bus, topic);
     }
 }
 
 /// <reference types="msfstypes/JS/common" />
 
     /**
      * A {@link Subscription} which executes a handler function every time it receives a notification.
      */
      class HandlerSubscription {
         /**
          * Constructor.
          * @param handler This subscription's handler. The handler will be called each time this subscription receives a
          * notification from its source.
          * @param initialNotifyFunc A function which sends initial notifications to this subscription. If not defined, this
          * subscription will not support initial notifications.
          * @param onDestroy A function which is called when this subscription is destroyed.
          */
         constructor(handler, initialNotifyFunc, onDestroy) {
             this.handler = handler;
             this.initialNotifyFunc = initialNotifyFunc;
             this.onDestroy = onDestroy;
             this._isAlive = true;
             this._isPaused = false;
             this.canInitialNotify = initialNotifyFunc !== undefined;
         }
         /** @inheritdoc */
         get isAlive() {
             return this._isAlive;
         }
         /** @inheritdoc */
         get isPaused() {
             return this._isPaused;
         }
         /**
          * Sends an initial notification to this subscription.
          * @throws Error if this subscription is not alive.
          */
         initialNotify() {
             if (!this._isAlive) {
                 throw new Error('HandlerSubscription: cannot notify a dead Subscription.');
             }
             this.initialNotifyFunc && this.initialNotifyFunc(this);
         }
         /** @inheritdoc */
         pause() {
             if (!this._isAlive) {
                 throw new Error('Subscription: cannot pause a dead Subscription.');
             }
             this._isPaused = true;
         }
         /** @inheritdoc */
         resume(initialNotify = false) {
             if (!this._isAlive) {
                 throw new Error('Subscription: cannot resume a dead Subscription.');
             }
             if (!this._isPaused) {
                 return;
             }
             this._isPaused = false;
             if (initialNotify) {
                 this.initialNotify();
             }
         }
         /** @inheritdoc */
         destroy() {
             if (!this._isAlive) {
                 return;
             }
             this._isAlive = false;
             this.onDestroy && this.onDestroy(this);
         }
     }
  
  /// <reference types="msfstypes/JS/common" />
     /**
      * An event bus that can be used to publish data from backend
      * components and devices to consumers.
      */
      class EventBus {
         /**
          * Creates an instance of an EventBus.
          * @param useAlternativeEventSync Whether or not to use coherent event sync (optional, default false).
          * If true, FlowEventSync will only work for gauges.
          */
         constructor(useAlternativeEventSync = false) {
             this._topicSubsMap = new Map();
             this._wildcardSubs = new Array();
             this._notifyDepthMap = new Map();
             this._wildcardNotifyDepth = 0;
             this._eventCache = new Map();
             this.onWildcardSubDestroyedFunc = this.onWildcardSubDestroyed.bind(this);
             this._busId = Math.floor(Math.random() * 2147483647);
             // fallback to flowevent when genericdatalistener not avail (su9)
             useAlternativeEventSync = (typeof RegisterGenericDataListener === 'undefined');
             const syncFunc = useAlternativeEventSync ? EventBusCoherentSync : EventBusListenerSync;
             this._busSync = new syncFunc(this.pub.bind(this), this._busId);
             this.syncEvent('event_bus', 'resync_request', false);
             this.on('event_bus', (data) => {
                 if (data == 'resync_request') {
                     this.resyncEvents();
                 }
             });
         }
         /**
          * Subscribes to a topic on the bus.
          * @param topic The topic to subscribe to.
          * @param handler The handler to be called when an event happens.
          * @param paused Whether the new subscription should be initialized as paused. Defaults to `false`.
          * @returns The new subscription.
          */
         on(topic, handler, paused = false) {
             let subs = this._topicSubsMap.get(topic);
             if (subs === undefined) {
                 this._topicSubsMap.set(topic, subs = []);
                 this.pub('event_bus_topic_first_sub', topic, false);
             }
             const initialNotifyFunc = (sub) => {
                 const lastState = this._eventCache.get(topic);
                 if (lastState !== undefined) {
                     sub.handler(lastState.data);
                 }
             };
             const onDestroyFunc = (sub) => {
                 var _a;
                 // If we are not in the middle of a notify operation, remove the subscription.
                 // Otherwise, do nothing and let the post-notify clean-up code handle it.
                 if (((_a = this._notifyDepthMap.get(topic)) !== null && _a !== void 0 ? _a : 0) === 0) {
                     const subsToSplice = this._topicSubsMap.get(topic);
                     if (subsToSplice) {
                         subsToSplice.splice(subsToSplice.indexOf(sub), 1);
                     }
                 }
             };
             const sub = new HandlerSubscription(handler, initialNotifyFunc, onDestroyFunc);
             subs.push(sub);
             if (paused) {
                 sub.pause();
             }
             else {
                 sub.initialNotify();
             }
             return sub;
         }
         /**
          * Unsubscribes a handler from the topic's events.
          * @param topic The topic to unsubscribe from.
          * @param handler The handler to unsubscribe from topic.
          * @deprecated This method has been deprecated in favor of using the {@link Subscription} object returned by `.on()`
          * to manage subscriptions.
          */
         off(topic, handler) {
             const handlers = this._topicSubsMap.get(topic);
             const toDestroy = handlers === null || handlers === void 0 ? void 0 : handlers.find(sub => sub.handler === handler);
             toDestroy === null || toDestroy === void 0 ? void 0 : toDestroy.destroy();
         }
         /**
          * Subscribes to all topics.
          * @param handler The handler to subscribe to all events.
          * @returns The new subscription.
          */
         onAll(handler) {
             const sub = new HandlerSubscription(handler, undefined, this.onWildcardSubDestroyedFunc);
             this._wildcardSubs.push(sub);
             return sub;
         }
         /**
          * Unsubscribe the handler from all topics.
          * @param handler The handler to unsubscribe from all events.
          * @deprecated This method has been deprecated in favor of using the {@link Subscription} object returned by
          * `.onAll()` to manage subscriptions.
          */
         offAll(handler) {
             const toDestroy = this._wildcardSubs.find(sub => sub.handler === handler);
             toDestroy === null || toDestroy === void 0 ? void 0 : toDestroy.destroy();
         }
         /**
          * Publishes an event to the topic on the bus.
          * @param topic The topic to publish to.
          * @param data The data portion of the event.
          * @param sync Whether or not this message needs to be synced on local stoage.
          * @param isCached Whether or not this message will be resync'd across the bus on load.
          */
         pub(topic, data, sync = false, isCached = true) {
             var _a;
             if (isCached) {
                 this._eventCache.set(topic, { data: data, synced: sync });
             }
             const subs = this._topicSubsMap.get(topic);
             if (subs !== undefined) {
                 let needCleanUpSubs = false;
                 const notifyDepth = (_a = this._notifyDepthMap.get(topic)) !== null && _a !== void 0 ? _a : 0;
                 this._notifyDepthMap.set(topic, notifyDepth + 1);
                 const len = subs.length;
                 for (let i = 0; i < len; i++) {
                     try {
                         const sub = subs[i];
                         if (sub.isAlive && !sub.isPaused) {
                             sub.handler(data);
                         }
                         needCleanUpSubs || (needCleanUpSubs = !sub.isAlive);
                     }
                     catch (error) {
                         console.error(`EventBus: error in handler: ${error}`);
                         if (error instanceof Error) {
                             console.error(error.stack);
                         }
                     }
                 }
                 this._notifyDepthMap.set(topic, notifyDepth);
                 if (needCleanUpSubs && notifyDepth === 0) {
                     const filteredSubs = subs.filter(sub => sub.isAlive);
                     this._topicSubsMap.set(topic, filteredSubs);
                 }
             }
             // We don't know if anything is subscribed on busses in other instruments,
             // so we'll unconditionally sync if sync is true and trust that the
             // publisher knows what it's doing.
             if (sync) {
                 this.syncEvent(topic, data, isCached);
             }
             // always push to wildcard handlers
             let needCleanUpSubs = false;
             this._wildcardNotifyDepth++;
             const wcLen = this._wildcardSubs.length;
             for (let i = 0; i < wcLen; i++) {
                 const sub = this._wildcardSubs[i];
                 if (sub.isAlive && !sub.isPaused) {
                     sub.handler(topic, data);
                 }
                 needCleanUpSubs || (needCleanUpSubs = !sub.isAlive);
             }
             this._wildcardNotifyDepth--;
             if (needCleanUpSubs && this._wildcardNotifyDepth === 0) {
                 this._wildcardSubs = this._wildcardSubs.filter(sub => sub.isAlive);
             }
         }
         /**
          * Responds to when a wildcard subscription is destroyed.
          * @param sub The destroyed subscription.
          */
         onWildcardSubDestroyed(sub) {
             // If we are not in the middle of a notify operation, remove the subscription.
             // Otherwise, do nothing and let the post-notify clean-up code handle it.
             if (this._wildcardNotifyDepth === 0) {
                 this._wildcardSubs.splice(this._wildcardSubs.indexOf(sub), 1);
             }
         }
         /**
          * Re-sync all synced events
          */
         resyncEvents() {
             for (const [topic, event] of this._eventCache) {
                 if (event.synced) {
                     this.syncEvent(topic, event.data, true);
                 }
             }
         }
         /**
          * Publish an event to the sync bus.
          * @param topic The topic to publish to.
          * @param data The data to publish.
          * @param isCached Whether or not this message will be resync'd across the bus on load.
          */
         syncEvent(topic, data, isCached) {
             this._busSync.sendEvent(topic, data, isCached);
         }
         /**
          * Gets a typed publisher from the event bus..
          * @returns The typed publisher.
          */
         getPublisher() {
             return this;
         }
         /**
          * Gets a typed subscriber from the event bus.
          * @returns The typed subscriber.
          */
         getSubscriber() {
             return new EventSubscriber(this);
         }
         /**
          * Get the number of subscribes for a given topic.
          * @param topic The name of the topic.
          * @returns The number of subscribers.
          **/
         getTopicSubscriberCount(topic) {
             var _a, _b;
             return (_b = (_a = this._topicSubsMap.get(topic)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
         }
     }
 // /**
 //  * A class that manages event bus synchronization via Coherent notifications.
 //  */
 // class EventBusCoherentSync {
 //     /**
 //      * Creates an instance of EventBusCoherentSync.
 //      * @param recvEventCb A callback to execute when an event is received on the bus.
 //      * @param busId The ID of the bus.  Derp.
 //      */
 //     constructor(recvEventCb, busId) {
 //         this.evtNum = 0;
 //         this.lastEventSynced = -1;
 //         this.recvEventCb = recvEventCb;
 //         this.busId = busId;
 //         this.listener = RegisterViewListener(EventBusCoherentSync.EB_LISTENER_KEY);
 //         this.listener.on(EventBusCoherentSync.EB_KEY, this.receiveEvent.bind(this));
 //     }
 //     /**
 //      * Sends an event via Coherent events.
 //      * @param topic The topic to send data on.
 //      * @param data The data to send.
 //      * @param isCached Whether or not this event is cached.
 //      */
 //     sendEvent(topic, data, isCached) {
 //         this.listener.triggerToAllSubscribers(EventBusCoherentSync.EB_KEY, { topic, data, isCached, busId: this.busId, evtNum: this.evtNum++ });
 //     }
 //     /**
 //      * Receives an event via Coherent and syncs onto the bus.
 //      * @param e The storage event that was received.
 //      */
 //     receiveEvent(e) {
 //         // If we've sent this event, don't act on it.
 //         if (e.busId == this.busId) {
 //             return;
 //         }
 //         if (this.lastEventSynced !== e.evtNum) {
 //             // TODO only react on topics that have subscribers
 //             this.lastEventSynced = e.evtNum;
 //             this.recvEventCb(e['topic'], e['data'], undefined, e['isCached']);
 //         }
 //     }
 // }
 // EventBusCoherentSync.EMPTY_DATA = '{}';
 // EventBusCoherentSync.EB_KEY = 'eb.evt';
 // EventBusCoherentSync.EB_LISTENER_KEY = 'JS_LISTENER_SIMVARS';
 
 
 //PM Modif updated the EventBusCoherentSync with the new way
 /**
 * An abstract class for bus sync implementations.
 */
 class EventBusSyncBase {
     /**
      * Creates an instance of EventBusFlowEventSync.
      * @param recvEventCb A callback to execute when an event is received on the bus.
      * @param busId The ID of the bus.
      */
     constructor(recvEventCb, busId) {
         this.isPaused = false;
         this.lastEventSynced = -1;
         this.dataPackageQueue = [];
         this.recvEventCb = recvEventCb;
         this.busId = busId;
         this.hookReceiveEvent();
         /** Sends the queued up data packages */
         const sendFn = () => {
             if (!this.isPaused && this.dataPackageQueue.length > 0) {
                 // console.log(`Sending ${this.dataPackageQueue.length} packages`);
                 const syncDataPackage = {
                     busId: this.busId,
                     packagedId: Math.floor(Math.random() * 1000000000),
                     data: this.dataPackageQueue
                 };
                 if (this.executeSync(syncDataPackage)) {
                     this.dataPackageQueue.length = 0;
                 }
                 else {
                     console.warn('Failed to send sync data package');
                 }
             }
             requestAnimationFrame(sendFn);
         };
         requestAnimationFrame(sendFn);
     }
     /**
      * Processes events received and sends them onto the local bus.
      * @param syncData The data package to process.
      */
     processEventsReceived(syncData) {
         if (this.busId !== syncData.busId) {
             // HINT: coherent events are still received twice, so check for this
             if (this.lastEventSynced !== syncData.packagedId) {
                 this.lastEventSynced = syncData.packagedId;
                 syncData.data.forEach((data) => {
                     try {
                         this.recvEventCb(data.topic, data.data !== undefined ? JSON.parse(data.data) : undefined, false, data.isCached);
                     }
                     catch (e) {
                         console.error(e);
                         if (e instanceof Error) {
                             console.error(e.stack);
                         }
                     }
                 });
             }
         }
     }
     /**
      * Sends an event via flow events.
      * @param topic The topic to send data on.
      * @param data The data to send.
      * @param isCached Whether or not this event is cached.
      */
     sendEvent(topic, data, isCached) {
         // stringify data
         const dataObj = JSON.stringify(data);
         // build a data package
         const dataPackage = {
             topic: topic,
             data: dataObj,
             isCached: isCached
         };
         // queue data package
         this.dataPackageQueue.push(dataPackage);
     }
 }
 /**
  * A class that manages event bus synchronization via Flow Event Triggers.
  */
  class EventBusCoherentSync extends EventBusSyncBase {
     /** @inheritdoc */
     executeSync(syncDataPackage) {
         // HINT: Stringifying the data again to circumvent the bad perf on Coherent interop
         try {
             this.listener.triggerToAllSubscribers(EventBusCoherentSync.EB_KEY, JSON.stringify(syncDataPackage));
             return true;
         }
         catch (error) {
             return false;
         }
     }
     /** @inheritdoc */
     hookReceiveEvent() {
         this.listener = RegisterViewListener(EventBusCoherentSync.EB_LISTENER_KEY, undefined, true);
         this.listener.on(EventBusCoherentSync.EB_KEY, (e) => {
             try {
                 const evt = JSON.parse(e);
                 this.processEventsReceived(evt);
             }
             catch (error) {
                 console.error(error);
             }
         });
     }
 }
 EventBusCoherentSync.EB_KEY = 'eb.evt';
 EventBusCoherentSync.EB_LISTENER_KEY = 'JS_LISTENER_SIMVARS';
 
 /**
  * A class that manages event bus synchronization via the Generic Data Listener.
  */
     class EventBusListenerSync extends EventBusSyncBase {
     /** @inheritdoc */
     executeSync(syncDataPackage) {
         try {
             this.listener.send(EventBusListenerSync.EB_KEY, syncDataPackage);
             return true;
         }
         catch (error) {
             return false;
         }
     }
     /** @inheritdoc */
     hookReceiveEvent() {
         // pause the sync until the listener is ready
         this.isPaused = true;
         this.listener = RegisterGenericDataListener(() => {
             this.listener.onDataReceived(EventBusListenerSync.EB_KEY, (data) => {
                 try {
                     this.processEventsReceived(data);
                 }
                 catch (error) {
                     console.error(error);
                 }
             });
             this.isPaused = false;
         });
     }
 }
 EventBusListenerSync.EB_KEY = 'wt.eb.evt';
 EventBusListenerSync.EB_LISTENER_KEY = 'JS_LISTENER_GENERICDATA';
 
 /**
  * A basic event-bus publisher.
  */
 class BasePublisher {
     /**
      * Creates an instance of BasePublisher.
      * @param bus The common event bus.
      * @param pacer An optional pacer to control the rate of publishing.
      */
     constructor(bus, pacer = undefined) {
         this.bus = bus;
         this.publisher = this.bus.getPublisher();
         this.publishActive = false;
         this.pacer = pacer;
     }
     /**
      * Start publishing.
      */
     startPublish() {
         this.publishActive = true;
     }
     /**
      * Stop publishing.
      */
     stopPublish() {
         this.publishActive = false;
     }
     /**
      * Tells whether or not the publisher is currently active.
      * @returns True if the publisher is active, false otherwise.
      */
     isPublishing() {
         return this.publishActive;
     }
     /**
      * A callback called when the publisher receives an update cycle.
      */
     onUpdate() {
         return;
     }
     /**
      * Publish a message if publishing is acpive
      * @param topic The topic key to publish to.
      * @param data The data type for chosen topic.
      * @param sync Whether or not the event should be synced via local storage.
      * @param isCached Whether or not the event should be cached.
      */
     publish(topic, data, sync = false, isCached = true) {
         if (this.publishActive && (!this.pacer || this.pacer.canPublish(topic, data))) {
             this.publisher.pub(topic, data, sync, isCached);
         }
     }
 }
 /**
  * A base class for publishers that need to handle simvars with built-in
  * support for pacing callbacks.
  */
 class SimVarPublisher extends BasePublisher {
     /**
      * Create a SimVarPublisher
      * @param simVarMap A map of simvar event type keys to a SimVarDefinition.
      * @param bus The EventBus to use for publishing.
      * @param pacer An optional pacer to control the rate of publishing.
      */
     constructor(simVarMap, bus, pacer = undefined) {
         super(bus, pacer);
         this.simvars = simVarMap;
         this.subscribed = new Set();
     }
     /**
      * Subscribe to an event type to begin publishing.
      * @param data Key of the event type in the simVarMap
      */
     subscribe(data) {
         this.subscribed.add(data);
     }
     /**
      * Unsubscribe to an event to stop publishing.
      * @param data Key of the event type in the simVarMap
      */
     unsubscribe(data) {
         // TODO If we have multiple subscribers we may want to add reference counting here.
         this.subscribed.delete(data);
     }
     /**
      * Read the value of a given simvar by its key.
      * @param key The key of the simvar in simVarMap
      * @returns The value returned by SimVar.GetSimVarValue()
      */
     getValue(key) {
         const simvar = this.simvars.get(key);
         if (simvar === undefined) {
             return undefined;
         }
         return SimVar.GetSimVarValue(simvar.name, simvar.type);
     }
     /**
      * Publish all subscribed data points to the bus.
      */
     onUpdate() {
         for (const data of this.subscribed.values()) {
             const value = this.getValue(data);
             if (value !== undefined) {
                 this.publish(data, value);
             }
         }
     }
     /**
      * Change the simvar read for a given key.
      * @param key The key of the simvar in simVarMap
      * @param value The new value to set the simvar to.
      */
     updateSimVarSource(key, value) {
         this.simvars.set(key, value);
     }
 }
 
 /**
  * A publisher for publishing H:Events on the bus.
  */
 class HEventPublisher extends BasePublisher {
     /**
      * Dispatches an H:Event to the event bus.
      * @param hEvent The H:Event to dispatch.
      * @param sync Whether this event should be synced (optional, default false)
      */
     dispatchHEvent(hEvent, sync = false) {
         //console.log(`dispaching hevent:  ${hEvent}`);
         this.publish('hEvent', hEvent, sync, false);
     }
 }
 
 /**
  * Valid type arguments for Set/GetSimVarValue
  */
 var SimVarValueType;
 (function (SimVarValueType) {
     SimVarValueType["Number"] = "number";
     SimVarValueType["Degree"] = "degrees";
     SimVarValueType["Knots"] = "knots";
     SimVarValueType["Feet"] = "feet";
     SimVarValueType["Meters"] = "meters";
     SimVarValueType["FPM"] = "feet per minute";
     SimVarValueType["Radians"] = "radians";
     SimVarValueType["InHG"] = "inches of mercury";
     SimVarValueType["MB"] = "Millibars";
     SimVarValueType["Bool"] = "Bool";
     SimVarValueType["Celsius"] = "celsius";
     SimVarValueType["MHz"] = "MHz";
     SimVarValueType["KHz"] = "KHz";
     SimVarValueType["NM"] = "nautical mile";
     SimVarValueType["String"] = "string";
     SimVarValueType["RPM"] = "Rpm";
     SimVarValueType["PPH"] = "Pounds per hour";
     SimVarValueType["GPH"] = "gph";
     SimVarValueType["Farenheit"] = "farenheit";
     SimVarValueType["PSI"] = "psi";
     SimVarValueType["GAL"] = "gallons";
     SimVarValueType["Hours"] = "Hours";
     SimVarValueType["Volts"] = "Volts";
     SimVarValueType["Amps"] = "Amperes";
     SimVarValueType["Seconds"] = "seconds";
     SimVarValueType["Enum"] = "Enum";
     SimVarValueType["LLA"] = "latlonalt";
     SimVarValueType["MetersPerSecond"] = "meters per second";
 })(SimVarValueType || (SimVarValueType = {}));
 
 /**
  * A publisher for control interactions.
  * This is meant to handle the events for which there aren't existing HEvents
  * in the sim to allow us to maintain a decoupled, event-driven architecture.
  */
 class ControlPublisher extends BasePublisher {
     /**
      * Create a ControlPublisher.
      * @param bus The EventBus to publish to.
      * @param pacer An optional pacer to use to control the rate of publishing.
      */
     constructor(bus, pacer = undefined) {
         super(bus, pacer);
     }
     /**
      * Publish a control event.
      * @param event The event from ControlEvents.
      * @param value The value of the event.
      */
     publishEvent(event, value) {
         this.publish(event, value, true);
     }
     /** debug logger */
     startPublish() {
         super.startPublish();
         // console.log('control publisher started.');
     }
 }
 
 /// <reference types="msfstypes/Pages/VCockpit/Instruments/Shared/utils/XMLLogic" />
 /** The kind of data to return. */
 var CompositeLogicXMLValueType;
 (function (CompositeLogicXMLValueType) {
     CompositeLogicXMLValueType[CompositeLogicXMLValueType["Any"] = 0] = "Any";
     CompositeLogicXMLValueType[CompositeLogicXMLValueType["Number"] = 1] = "Number";
     CompositeLogicXMLValueType[CompositeLogicXMLValueType["String"] = 2] = "String";
 })(CompositeLogicXMLValueType || (CompositeLogicXMLValueType = {}));
 /**
  *
  */
 class CompositeLogicXMLHost {
     constructor() {
         this.anyHandlers = new Array();
         this.stringHandlers = new Array();
         this.numberHandlers = new Array();
         this.anyResultCache = new Array();
         this.stringResultCache = new Array();
         this.numberResultCache = new Array();
         this.context = new LogicXMLContext();
     }
     /**
      * Add a new logic element to calcluate a number or a string.
      * @param logic A CompositeLogicXMLElement.
      * @param handler A callback hander to take new values of either type.
      * @returns The current value of the logic.
      */
     addLogic(logic, handler) {
         this.anyHandlers.push({ logic: logic, handler: handler });
         return logic.getValue(this.context);
     }
     /**
      * Add a new logic element to calcluate a number.
      * @param logic A CompositeLogicXMLElement.
      * @param handler A callback hander to take new values as numbers.
      * @param precision An optional precision to require for updates to be sent.
      * @param smoothFactor An optional linear smoothing factor to apply to the value when updating.
      * @returns The current value of the logic.
      */
     addLogicAsNumber(logic, handler, precision, smoothFactor) {
         this.numberHandlers.push({ logic: logic, handler: handler, precision: precision, smoothFactor: smoothFactor });
         return logic.getValueAsNumber(this.context);
     }
     /**
      * Add a new logic element to calcluate a string.
      * @param logic A CompositeLogicXMLElement.
      * @param handler A callback hander to take new values as strings.
      * @returns The current value of the logic.
      */
     addLogicAsString(logic, handler) {
         this.stringHandlers.push({ logic: logic, handler: handler });
         return logic.getValueAsString(this.context);
     }
     /**
      * Add a function to the logic context.
      * @param funcSpec The XMLFunction configuration.
      * @returns The function's current value.
      */
     addFunction(funcSpec) {
         const func = new LogicXMLFunction();
         func.name = funcSpec.name;
         func.callback = funcSpec.logic;
         this.context.addFunction(func);
         return funcSpec.logic.getValue(this.context);
     }
     /**
      * Update every logic element and publish updates.
      * @param deltaTime The time since the last update, in ms.
      */
     update(deltaTime) {
         for (let i = 0; i < this.anyHandlers.length; i++) {
             const newVal = this.anyHandlers[i].logic.getValue(this.context);
             if (newVal !== this.anyResultCache[i]) {
                 this.anyResultCache[i] = newVal;
                 this.anyHandlers[i].handler(newVal);
             }
         }
         for (let i = 0; i < this.stringHandlers.length; i++) {
             const newVal = this.stringHandlers[i].logic.getValueAsString(this.context);
             if (newVal !== this.stringResultCache[i]) {
                 this.stringResultCache[i] = newVal;
                 this.stringHandlers[i].handler(newVal);
             }
         }
         for (let i = 0; i < this.numberHandlers.length; i++) {
             let newVal = this.numberHandlers[i].logic.getValueAsNumber(this.context);
             let precision = this.numberHandlers[i].precision;
             if (precision !== undefined) {
                 precision = Math.pow(10, precision);
                 newVal = Math.round(newVal * precision) / precision;
             }
             if (this.numberHandlers[i].smoothFactor !== undefined && this.numberHandlers[i].smoothFactor !== 0) {
                 // A smoothFactor of 0 means no smoothing.  We don't trigger this update if the factor is
                 // undefined or 0, but typescript still thinks is could be undefined due to the array indexing.
                 // The 'or-0' here is just to get around that without having to do a temporary assignment.
                 newVal = Utils.SmoothLinear(this.numberResultCache[i], newVal, this.numberHandlers[i].smoothFactor || 0, deltaTime);
             }
             if (newVal !== this.numberResultCache[i]) {
                 this.numberResultCache[i] = newVal;
                 this.numberHandlers[i].handler(newVal);
             }
         }
         this.context.update();
     }
 }
 
 /// <reference types="msfstypes/JS/dataStorage" />
 /* eslint-disable no-inner-declarations */
 // eslint-disable-next-line @typescript-eslint/no-namespace
 var DataStore;
 (function (DataStore) {
     /**
      * Writes a keyed value to the data store.
      * @param key A key.
      * @param value The value to set.
      */
     function set(key, value) {
         SetStoredData(key, JSON.stringify(value));
     }
     DataStore.set = set;
     /**
      * Retrieves a keyed value from the data store.
      * @param key A key.
      * @returns the value stored under the key, or undefined if one could not be retrieved.
      */
     function get(key) {
         try {
             const string = GetStoredData(key);
             return JSON.parse(string);
         }
         catch (e) {
             return undefined;
         }
     }
     DataStore.get = get;
     /**
      * Removes a key from the data store.
      * @param key The key to remove.
      */
     function remove(key) {
         DeleteStoredData(key);
     }
     DataStore.remove = remove;
 })(DataStore || (DataStore = {}));
 
 /**
  * A subscribable subject which derives its value from an event consumer.
  */
 class ConsumerSubject extends AbstractSubscribable {
     /**
      * Constructor.
      * @param consumer The event consumer from which this subject obtains its value.
      * @param initialVal This subject's initial value.
      * @param equalityFunc The function this subject uses check for equality between values.
      * @param mutateFunc The function this subject uses to change its value. If not defined, variable assignment is used
      * instead.
      */
     constructor(consumer, initialVal, equalityFunc, mutateFunc) {
         super();
         this.consumer = consumer;
         this.equalityFunc = equalityFunc;
         this.mutateFunc = mutateFunc;
         this.consumerHandler = this.onEventConsumed.bind(this);
         this.value = initialVal;
         consumer.handle(this.consumerHandler);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static create(consumer, initialVal, equalityFunc, mutateFunc) {
         return new ConsumerSubject(consumer, initialVal, equalityFunc !== null && equalityFunc !== void 0 ? equalityFunc : AbstractSubscribable.DEFAULT_EQUALITY_FUNC, mutateFunc);
     }
     /**
      * Consumes an event.
      * @param value The value of the event.
      */
     onEventConsumed(value) {
         if (!this.equalityFunc(this.value, value)) {
             if (this.mutateFunc) {
                 this.mutateFunc(this.value, value);
             }
             else {
                 this.value = value;
             }
             this.notify();
         }
     }
     /** @inheritdoc */
     get() {
         return this.value;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     map(fn, equalityFunc, mutateFunc, initialVal) {
         const mapFunc = (inputs, previousVal) => fn(inputs[0], previousVal);
         return mutateFunc
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             ? MappedSubject.create(mapFunc, equalityFunc, mutateFunc, initialVal, this)
             : MappedSubject.create(mapFunc, equalityFunc !== null && equalityFunc !== void 0 ? equalityFunc : AbstractSubscribable.DEFAULT_EQUALITY_FUNC, this);
     }
     /**
      * Destroys this subject. Once destroyed, it will no longer consume events to update its value.
      */
     destroy() {
         this.consumer.off(this.consumerHandler);
     }
 }
 
 /**
  * A manager for key intercepts. Allows key events to be intercepted and publishes intercepted key events on the event
  * bus.
  */
 class KeyInterceptManager {
     /**
      * Constructor.
      * @param keyListener The Coherent key intercept view listener.
      * @param bus The event bus.
      */
     constructor(keyListener, bus) {
         this.keyListener = keyListener;
         this.bus = bus;
         Coherent.on('keyIntercepted', this.onKeyIntercepted.bind(this));
     }
     /**
      * Responds to key intercept events.
      * @param key The key that was intercepted.
      * @param index The index of the key event.
      * @param value The value of the key event.
      */
     onKeyIntercepted(key, index, value) {
         this.bus.pub('key_intercept', { key, index, value }, false, false);
     }
     /**
      * Enables interception for a key.
      * @param key The key to intercept.
      * @param passThrough Whether to pass the event through to the sim after it has been intercepted.
      */
     interceptKey(key, passThrough) {
         Coherent.call('INTERCEPT_KEY_EVENT', key, passThrough ? 0 : 1);
     }
     /**
      * Gets an instance of KeyInterceptManager. If an instance does not already exist, a new one will be created.
      * @param bus The event bus.
      * @returns A Promise which will be fulfilled with an instance of KeyInterceptManager.
      */
     static getManager(bus) {
         if (KeyInterceptManager.INSTANCE) {
             return Promise.resolve(KeyInterceptManager.INSTANCE);
         }
         if (!KeyInterceptManager.isCreatingInstance) {
             KeyInterceptManager.createInstance(bus);
         }
         return new Promise(resolve => {
             KeyInterceptManager.pendingPromiseResolves.push(resolve);
         });
     }
     /**
      * Creates an instance of KeyInterceptManager and fulfills all pending Promises to get the manager instance once
      * the instance is created.
      * @param bus The event bus.
      */
     static async createInstance(bus) {
         KeyInterceptManager.isCreatingInstance = true;
         KeyInterceptManager.INSTANCE = await KeyInterceptManager.create(bus);
         KeyInterceptManager.isCreatingInstance = false;
         for (let i = 0; i < KeyInterceptManager.pendingPromiseResolves.length; i++) {
             KeyInterceptManager.pendingPromiseResolves[i](KeyInterceptManager.INSTANCE);
         }
     }
     /**
      * Creates an instance of KeyInterceptManager.
      * @param bus The event bus.
      * @returns A Promise which is fulfilled with a new instance of KeyInterceptManager after it has been created.
      */
     static async create(bus) {
         // HINT: we do this purely to try avoid the weird CTD
         await Wait.awaitDelay(3000);
         return new Promise(resolve => {
             const keyListener = RegisterViewListener('JS_LISTENER_KEYEVENT', () => {
                 resolve(new KeyInterceptManager(keyListener, bus));
             });
         });
     }
 }
 KeyInterceptManager.isCreatingInstance = false;
 KeyInterceptManager.pendingPromiseResolves = [];
 
 /**
  * Types of airspaces.
  */
 var AirspaceType;
 (function (AirspaceType) {
     AirspaceType[AirspaceType["None"] = 0] = "None";
     AirspaceType[AirspaceType["Center"] = 1] = "Center";
     AirspaceType[AirspaceType["ClassA"] = 2] = "ClassA";
     AirspaceType[AirspaceType["ClassB"] = 3] = "ClassB";
     AirspaceType[AirspaceType["ClassC"] = 4] = "ClassC";
     AirspaceType[AirspaceType["ClassD"] = 5] = "ClassD";
     AirspaceType[AirspaceType["ClassE"] = 6] = "ClassE";
     AirspaceType[AirspaceType["ClassF"] = 7] = "ClassF";
     AirspaceType[AirspaceType["ClassG"] = 8] = "ClassG";
     AirspaceType[AirspaceType["Tower"] = 9] = "Tower";
     AirspaceType[AirspaceType["Clearance"] = 10] = "Clearance";
     AirspaceType[AirspaceType["Ground"] = 11] = "Ground";
     AirspaceType[AirspaceType["Departure"] = 12] = "Departure";
     AirspaceType[AirspaceType["Approach"] = 13] = "Approach";
     AirspaceType[AirspaceType["MOA"] = 14] = "MOA";
     AirspaceType[AirspaceType["Restricted"] = 15] = "Restricted";
     AirspaceType[AirspaceType["Prohibited"] = 16] = "Prohibited";
     AirspaceType[AirspaceType["Warning"] = 17] = "Warning";
     AirspaceType[AirspaceType["Alert"] = 18] = "Alert";
     AirspaceType[AirspaceType["Danger"] = 19] = "Danger";
     AirspaceType[AirspaceType["Nationalpark"] = 20] = "Nationalpark";
     AirspaceType[AirspaceType["ModeC"] = 21] = "ModeC";
     AirspaceType[AirspaceType["Radar"] = 22] = "Radar";
     AirspaceType[AirspaceType["Training"] = 23] = "Training";
     AirspaceType[AirspaceType["Max"] = 24] = "Max";
 })(AirspaceType || (AirspaceType = {}));
 
 /**
  * A viewlistener that gets autopilot mode information.
  */
 // eslint-disable-next-line @typescript-eslint/no-namespace
 var APController;
 (function (APController) {
 })(APController || (APController = {}));
 var MSFSAPStates;
 (function (MSFSAPStates) {
     MSFSAPStates[MSFSAPStates["LogicOn"] = 1] = "LogicOn";
     MSFSAPStates[MSFSAPStates["APOn"] = 2] = "APOn";
     MSFSAPStates[MSFSAPStates["FDOn"] = 4] = "FDOn";
     MSFSAPStates[MSFSAPStates["FLC"] = 8] = "FLC";
     MSFSAPStates[MSFSAPStates["Alt"] = 16] = "Alt";
     MSFSAPStates[MSFSAPStates["AltArm"] = 32] = "AltArm";
     MSFSAPStates[MSFSAPStates["GS"] = 64] = "GS";
     MSFSAPStates[MSFSAPStates["GSArm"] = 128] = "GSArm";
     MSFSAPStates[MSFSAPStates["Pitch"] = 256] = "Pitch";
     MSFSAPStates[MSFSAPStates["VS"] = 512] = "VS";
     MSFSAPStates[MSFSAPStates["Heading"] = 1024] = "Heading";
     MSFSAPStates[MSFSAPStates["Nav"] = 2048] = "Nav";
     MSFSAPStates[MSFSAPStates["NavArm"] = 4096] = "NavArm";
     MSFSAPStates[MSFSAPStates["WingLevel"] = 8192] = "WingLevel";
     MSFSAPStates[MSFSAPStates["Attitude"] = 16384] = "Attitude";
     MSFSAPStates[MSFSAPStates["ThrottleSpd"] = 32768] = "ThrottleSpd";
     MSFSAPStates[MSFSAPStates["ThrottleMach"] = 65536] = "ThrottleMach";
     MSFSAPStates[MSFSAPStates["ATArm"] = 131072] = "ATArm";
     MSFSAPStates[MSFSAPStates["YD"] = 262144] = "YD";
     MSFSAPStates[MSFSAPStates["EngineRPM"] = 524288] = "EngineRPM";
     MSFSAPStates[MSFSAPStates["TOGAPower"] = 1048576] = "TOGAPower";
     MSFSAPStates[MSFSAPStates["Autoland"] = 2097152] = "Autoland";
     MSFSAPStates[MSFSAPStates["TOGAPitch"] = 4194304] = "TOGAPitch";
     MSFSAPStates[MSFSAPStates["Bank"] = 8388608] = "Bank";
     MSFSAPStates[MSFSAPStates["FBW"] = 16777216] = "FBW";
     MSFSAPStates[MSFSAPStates["AvionicsManaged"] = 33554432] = "AvionicsManaged";
     MSFSAPStates[MSFSAPStates["None"] = -2147483648] = "None";
 })(MSFSAPStates || (MSFSAPStates = {}));
 
 /// <reference types="msfstypes/JS/Simplane" />
 /**
  * The available facility frequency types.
  */
 var FacilityFrequencyType;
 (function (FacilityFrequencyType) {
     FacilityFrequencyType[FacilityFrequencyType["None"] = 0] = "None";
     FacilityFrequencyType[FacilityFrequencyType["ATIS"] = 1] = "ATIS";
     FacilityFrequencyType[FacilityFrequencyType["Multicom"] = 2] = "Multicom";
     FacilityFrequencyType[FacilityFrequencyType["Unicom"] = 3] = "Unicom";
     FacilityFrequencyType[FacilityFrequencyType["CTAF"] = 4] = "CTAF";
     FacilityFrequencyType[FacilityFrequencyType["Ground"] = 5] = "Ground";
     FacilityFrequencyType[FacilityFrequencyType["Tower"] = 6] = "Tower";
     FacilityFrequencyType[FacilityFrequencyType["Clearance"] = 7] = "Clearance";
     FacilityFrequencyType[FacilityFrequencyType["Approach"] = 8] = "Approach";
     FacilityFrequencyType[FacilityFrequencyType["Departure"] = 9] = "Departure";
     FacilityFrequencyType[FacilityFrequencyType["Center"] = 10] = "Center";
     FacilityFrequencyType[FacilityFrequencyType["FSS"] = 11] = "FSS";
     FacilityFrequencyType[FacilityFrequencyType["AWOS"] = 12] = "AWOS";
     FacilityFrequencyType[FacilityFrequencyType["ASOS"] = 13] = "ASOS";
     /** Clearance Pre-Taxi*/
     FacilityFrequencyType[FacilityFrequencyType["CPT"] = 14] = "CPT";
     /** Remote Clearance Delivery */
     FacilityFrequencyType[FacilityFrequencyType["GCO"] = 15] = "GCO";
 })(FacilityFrequencyType || (FacilityFrequencyType = {}));
 /** Additional Approach Types (additive to those defined in simplane). */
 var AdditionalApproachType;
 (function (AdditionalApproachType) {
     AdditionalApproachType[AdditionalApproachType["APPROACH_TYPE_VISUAL"] = 99] = "APPROACH_TYPE_VISUAL";
 })(AdditionalApproachType || (AdditionalApproachType = {}));
 /**
  * Flags indicating the approach fix type.
  */
 var FixTypeFlags;
 (function (FixTypeFlags) {
     FixTypeFlags[FixTypeFlags["None"] = 0] = "None";
     FixTypeFlags[FixTypeFlags["IAF"] = 1] = "IAF";
     FixTypeFlags[FixTypeFlags["IF"] = 2] = "IF";
     FixTypeFlags[FixTypeFlags["MAP"] = 4] = "MAP";
     FixTypeFlags[FixTypeFlags["FAF"] = 8] = "FAF";
     FixTypeFlags[FixTypeFlags["MAHP"] = 16] = "MAHP";
 })(FixTypeFlags || (FixTypeFlags = {}));
 /**
  * Flags indicating the rnav approach type.
  */
 var RnavTypeFlags;
 (function (RnavTypeFlags) {
     RnavTypeFlags[RnavTypeFlags["None"] = 0] = "None";
     RnavTypeFlags[RnavTypeFlags["LNAV"] = 1] = "LNAV";
     RnavTypeFlags[RnavTypeFlags["LNAVVNAV"] = 2] = "LNAVVNAV";
     RnavTypeFlags[RnavTypeFlags["LP"] = 4] = "LP";
     RnavTypeFlags[RnavTypeFlags["LPV"] = 8] = "LPV";
 })(RnavTypeFlags || (RnavTypeFlags = {}));
 /**
  * The class of airport facility.
  */
 var AirportClass;
 (function (AirportClass) {
     /** No other airport class could be identified. */
     AirportClass[AirportClass["None"] = 0] = "None";
     /** The airport has at least one hard surface runway. */
     AirportClass[AirportClass["HardSurface"] = 1] = "HardSurface";
     /** The airport has no hard surface runways. */
     AirportClass[AirportClass["SoftSurface"] = 2] = "SoftSurface";
     /** The airport has only water surface runways. */
     AirportClass[AirportClass["AllWater"] = 3] = "AllWater";
     /** The airport has no runways, but does contain helipads. */
     AirportClass[AirportClass["HeliportOnly"] = 4] = "HeliportOnly";
     /** The airport is a non-public use airport. */
     AirportClass[AirportClass["Private"] = 5] = "Private";
 })(AirportClass || (AirportClass = {}));
 /**
  * The class of an airport facility, expressed as a mask for nearest airport search session filtering.
  */
 var AirportClassMask;
 (function (AirportClassMask) {
     /** No other airport class could be identified. */
     AirportClassMask[AirportClassMask["None"] = 0] = "None";
     /** The airport has at least one hard surface runway. */
     AirportClassMask[AirportClassMask["HardSurface"] = 2] = "HardSurface";
     /** The airport has no hard surface runways. */
     AirportClassMask[AirportClassMask["SoftSurface"] = 4] = "SoftSurface";
     /** The airport has only water surface runways. */
     AirportClassMask[AirportClassMask["AllWater"] = 8] = "AllWater";
     /** The airport has no runways, but does contain helipads. */
     AirportClassMask[AirportClassMask["HeliportOnly"] = 16] = "HeliportOnly";
     /** The airport is a non-public use airport. */
     AirportClassMask[AirportClassMask["Private"] = 32] = "Private";
 })(AirportClassMask || (AirportClassMask = {}));
 var UserFacilityType;
 (function (UserFacilityType) {
     UserFacilityType[UserFacilityType["RADIAL_RADIAL"] = 0] = "RADIAL_RADIAL";
     UserFacilityType[UserFacilityType["RADIAL_DISTANCE"] = 1] = "RADIAL_DISTANCE";
     UserFacilityType[UserFacilityType["LAT_LONG"] = 2] = "LAT_LONG";
 })(UserFacilityType || (UserFacilityType = {}));
 /**
  * ARINC 424 Leg Types
  */
 var LegType;
 (function (LegType) {
     /** An unknown leg type. */
     LegType[LegType["Unknown"] = 0] = "Unknown";
     /** An arc-to-fix leg. This indicates a DME arc leg to a specified fix.*/
     LegType[LegType["AF"] = 1] = "AF";
     /** A course-to-altitude leg. */
     LegType[LegType["CA"] = 2] = "CA";
     /**
      * A course-to-DME-distance leg. This leg is flown on a wind corrected course
      * to a specific DME distance from another fix.
      */
     LegType[LegType["CD"] = 3] = "CD";
     /** A course-to-fix leg.*/
     LegType[LegType["CF"] = 4] = "CF";
     /** A course-to-intercept leg. */
     LegType[LegType["CI"] = 5] = "CI";
     /** A course-to-radial intercept leg. */
     LegType[LegType["CR"] = 6] = "CR";
     /** A direct-to-fix leg, from an unspecified starting position. */
     LegType[LegType["DF"] = 7] = "DF";
     /**
      * A fix-to-altitude leg. A FA leg is flown on a track from a fix to a
      * specified altitude.
      */
     LegType[LegType["FA"] = 8] = "FA";
     /**
      * A fix-to-distance leg. This leg is flown on a track from a fix to a
      * specific distance from the fix.
      */
     LegType[LegType["FC"] = 9] = "FC";
     /**
      * A fix to DME distance leg. This leg is flown on a track from a fix to
      * a specific DME distance from another fix.
      */
     LegType[LegType["FD"] = 10] = "FD";
     /** A course-to-manual-termination leg. */
     LegType[LegType["FM"] = 11] = "FM";
     /** A hold-to-altitude leg. The hold is flown until a specified altitude is reached. */
     LegType[LegType["HA"] = 12] = "HA";
     /**
      * A hold-to-fix leg. This indicates one time around the hold circuit and
      * then an exit.
      */
     LegType[LegType["HF"] = 13] = "HF";
     /** A hold-to-manual-termination leg. */
     LegType[LegType["HM"] = 14] = "HM";
     /** Initial procedure fix. */
     LegType[LegType["IF"] = 15] = "IF";
     /** A procedure turn leg. */
     LegType[LegType["PI"] = 16] = "PI";
     /** A radius-to-fix leg, with endpoint fixes, a center fix, and a radius. */
     LegType[LegType["RF"] = 17] = "RF";
     /** A track-to-fix leg, from the previous fix to the terminator. */
     LegType[LegType["TF"] = 18] = "TF";
     /** A heading-to-altitude leg. */
     LegType[LegType["VA"] = 19] = "VA";
     /** A heading-to-DME-distance leg. */
     LegType[LegType["VD"] = 20] = "VD";
     /** A heading-to-intercept leg. */
     LegType[LegType["VI"] = 21] = "VI";
     /** A heading-to-manual-termination leg. */
     LegType[LegType["VM"] = 22] = "VM";
     /** A heading-to-radial intercept leg. */
     LegType[LegType["VR"] = 23] = "VR";
     /** A leg representing a discontinuity in the flight plan. */
     LegType[LegType["Discontinuity"] = 99] = "Discontinuity";
 })(LegType || (LegType = {}));
 /**
  * Types of altitude restrictions on procedure legs.
  */
 var AltitudeRestrictionType;
 (function (AltitudeRestrictionType) {
     AltitudeRestrictionType[AltitudeRestrictionType["Unused"] = 0] = "Unused";
     AltitudeRestrictionType[AltitudeRestrictionType["At"] = 1] = "At";
     AltitudeRestrictionType[AltitudeRestrictionType["AtOrAbove"] = 2] = "AtOrAbove";
     AltitudeRestrictionType[AltitudeRestrictionType["AtOrBelow"] = 3] = "AtOrBelow";
     AltitudeRestrictionType[AltitudeRestrictionType["Between"] = 4] = "Between";
 })(AltitudeRestrictionType || (AltitudeRestrictionType = {}));
 var LegTurnDirection;
 (function (LegTurnDirection) {
     LegTurnDirection[LegTurnDirection["None"] = 0] = "None";
     LegTurnDirection[LegTurnDirection["Left"] = 1] = "Left";
     LegTurnDirection[LegTurnDirection["Right"] = 2] = "Right";
     LegTurnDirection[LegTurnDirection["Either"] = 3] = "Either";
 })(LegTurnDirection || (LegTurnDirection = {}));
 var AirwayType;
 (function (AirwayType) {
     AirwayType[AirwayType["None"] = 0] = "None";
     AirwayType[AirwayType["Victor"] = 1] = "Victor";
     AirwayType[AirwayType["Jet"] = 2] = "Jet";
     AirwayType[AirwayType["Both"] = 3] = "Both";
 })(AirwayType || (AirwayType = {}));
 var NdbType;
 (function (NdbType) {
     NdbType[NdbType["CompassPoint"] = 0] = "CompassPoint";
     NdbType[NdbType["MH"] = 1] = "MH";
     NdbType[NdbType["H"] = 2] = "H";
     NdbType[NdbType["HH"] = 3] = "HH";
 })(NdbType || (NdbType = {}));
 var VorType;
 (function (VorType) {
     VorType[VorType["Unknown"] = 0] = "Unknown";
     VorType[VorType["VOR"] = 1] = "VOR";
     VorType[VorType["VORDME"] = 2] = "VORDME";
     VorType[VorType["DME"] = 3] = "DME";
     VorType[VorType["TACAN"] = 4] = "TACAN";
     VorType[VorType["VORTAC"] = 5] = "VORTAC";
     VorType[VorType["ILS"] = 6] = "ILS";
     VorType[VorType["VOT"] = 7] = "VOT";
 })(VorType || (VorType = {}));
 var RunwaySurfaceType;
 (function (RunwaySurfaceType) {
     RunwaySurfaceType[RunwaySurfaceType["Concrete"] = 0] = "Concrete";
     RunwaySurfaceType[RunwaySurfaceType["Grass"] = 1] = "Grass";
     RunwaySurfaceType[RunwaySurfaceType["WaterFSX"] = 2] = "WaterFSX";
     RunwaySurfaceType[RunwaySurfaceType["GrassBumpy"] = 3] = "GrassBumpy";
     RunwaySurfaceType[RunwaySurfaceType["Asphalt"] = 4] = "Asphalt";
     RunwaySurfaceType[RunwaySurfaceType["ShortGrass"] = 5] = "ShortGrass";
     RunwaySurfaceType[RunwaySurfaceType["LongGrass"] = 6] = "LongGrass";
     RunwaySurfaceType[RunwaySurfaceType["HardTurf"] = 7] = "HardTurf";
     RunwaySurfaceType[RunwaySurfaceType["Snow"] = 8] = "Snow";
     RunwaySurfaceType[RunwaySurfaceType["Ice"] = 9] = "Ice";
     RunwaySurfaceType[RunwaySurfaceType["Urban"] = 10] = "Urban";
     RunwaySurfaceType[RunwaySurfaceType["Forest"] = 11] = "Forest";
     RunwaySurfaceType[RunwaySurfaceType["Dirt"] = 12] = "Dirt";
     RunwaySurfaceType[RunwaySurfaceType["Coral"] = 13] = "Coral";
     RunwaySurfaceType[RunwaySurfaceType["Gravel"] = 14] = "Gravel";
     RunwaySurfaceType[RunwaySurfaceType["OilTreated"] = 15] = "OilTreated";
     RunwaySurfaceType[RunwaySurfaceType["SteelMats"] = 16] = "SteelMats";
     RunwaySurfaceType[RunwaySurfaceType["Bituminous"] = 17] = "Bituminous";
     RunwaySurfaceType[RunwaySurfaceType["Brick"] = 18] = "Brick";
     RunwaySurfaceType[RunwaySurfaceType["Macadam"] = 19] = "Macadam";
     RunwaySurfaceType[RunwaySurfaceType["Planks"] = 20] = "Planks";
     RunwaySurfaceType[RunwaySurfaceType["Sand"] = 21] = "Sand";
     RunwaySurfaceType[RunwaySurfaceType["Shale"] = 22] = "Shale";
     RunwaySurfaceType[RunwaySurfaceType["Tarmac"] = 23] = "Tarmac";
     RunwaySurfaceType[RunwaySurfaceType["WrightFlyerTrack"] = 24] = "WrightFlyerTrack";
     //SURFACE_TYPE_LAST_FSX
     RunwaySurfaceType[RunwaySurfaceType["Ocean"] = 26] = "Ocean";
     RunwaySurfaceType[RunwaySurfaceType["Water"] = 27] = "Water";
     RunwaySurfaceType[RunwaySurfaceType["Pond"] = 28] = "Pond";
     RunwaySurfaceType[RunwaySurfaceType["Lake"] = 29] = "Lake";
     RunwaySurfaceType[RunwaySurfaceType["River"] = 30] = "River";
     RunwaySurfaceType[RunwaySurfaceType["WasteWater"] = 31] = "WasteWater";
     RunwaySurfaceType[RunwaySurfaceType["Paint"] = 32] = "Paint";
     // UNUSED
     // SURFACE_TYPE_ERASE_GRASS
 })(RunwaySurfaceType || (RunwaySurfaceType = {}));
 var RunwayLightingType;
 (function (RunwayLightingType) {
     RunwayLightingType[RunwayLightingType["Unknown"] = 0] = "Unknown";
     RunwayLightingType[RunwayLightingType["None"] = 1] = "None";
     RunwayLightingType[RunwayLightingType["PartTime"] = 2] = "PartTime";
     RunwayLightingType[RunwayLightingType["FullTime"] = 3] = "FullTime";
     RunwayLightingType[RunwayLightingType["Frequency"] = 4] = "Frequency";
 })(RunwayLightingType || (RunwayLightingType = {}));
 var AirportPrivateType;
 (function (AirportPrivateType) {
     AirportPrivateType[AirportPrivateType["Uknown"] = 0] = "Uknown";
     AirportPrivateType[AirportPrivateType["Public"] = 1] = "Public";
     AirportPrivateType[AirportPrivateType["Military"] = 2] = "Military";
     AirportPrivateType[AirportPrivateType["Private"] = 3] = "Private";
 })(AirportPrivateType || (AirportPrivateType = {}));
 var GpsBoolean;
 (function (GpsBoolean) {
     GpsBoolean[GpsBoolean["Unknown"] = 0] = "Unknown";
     GpsBoolean[GpsBoolean["No"] = 1] = "No";
     GpsBoolean[GpsBoolean["Yes"] = 2] = "Yes";
 })(GpsBoolean || (GpsBoolean = {}));
 var VorClass;
 (function (VorClass) {
     VorClass[VorClass["Unknown"] = 0] = "Unknown";
     VorClass[VorClass["Terminal"] = 1] = "Terminal";
     VorClass[VorClass["LowAlt"] = 2] = "LowAlt";
     VorClass[VorClass["HighAlt"] = 3] = "HighAlt";
     VorClass[VorClass["ILS"] = 4] = "ILS";
     VorClass[VorClass["VOT"] = 5] = "VOT";
 })(VorClass || (VorClass = {}));
 var FacilityType;
 (function (FacilityType) {
     FacilityType["Airport"] = "LOAD_AIRPORT";
     FacilityType["Intersection"] = "LOAD_INTERSECTION";
     FacilityType["VOR"] = "LOAD_VOR";
     FacilityType["NDB"] = "LOAD_NDB";
     FacilityType["USR"] = "USR";
     FacilityType["RWY"] = "RWY";
     FacilityType["VIS"] = "VIS";
 })(FacilityType || (FacilityType = {}));
 var FacilitySearchType;
 (function (FacilitySearchType) {
     FacilitySearchType[FacilitySearchType["None"] = 0] = "None";
     FacilitySearchType[FacilitySearchType["Airport"] = 1] = "Airport";
     FacilitySearchType[FacilitySearchType["Intersection"] = 2] = "Intersection";
     FacilitySearchType[FacilitySearchType["Vor"] = 3] = "Vor";
     FacilitySearchType[FacilitySearchType["Ndb"] = 4] = "Ndb";
     FacilitySearchType[FacilitySearchType["Boundary"] = 5] = "Boundary";
     FacilitySearchType[FacilitySearchType["User"] = 6] = "User";
 })(FacilitySearchType || (FacilitySearchType = {}));
 /**
  * A type of airspace boundary.
  */
 var BoundaryType;
 (function (BoundaryType) {
     BoundaryType[BoundaryType["None"] = 0] = "None";
     BoundaryType[BoundaryType["Center"] = 1] = "Center";
     BoundaryType[BoundaryType["ClassA"] = 2] = "ClassA";
     BoundaryType[BoundaryType["ClassB"] = 3] = "ClassB";
     BoundaryType[BoundaryType["ClassC"] = 4] = "ClassC";
     BoundaryType[BoundaryType["ClassD"] = 5] = "ClassD";
     BoundaryType[BoundaryType["ClassE"] = 6] = "ClassE";
     BoundaryType[BoundaryType["ClassF"] = 7] = "ClassF";
     BoundaryType[BoundaryType["ClassG"] = 8] = "ClassG";
     BoundaryType[BoundaryType["Tower"] = 9] = "Tower";
     BoundaryType[BoundaryType["Clearance"] = 10] = "Clearance";
     BoundaryType[BoundaryType["Ground"] = 11] = "Ground";
     BoundaryType[BoundaryType["Departure"] = 12] = "Departure";
     BoundaryType[BoundaryType["Approach"] = 13] = "Approach";
     BoundaryType[BoundaryType["MOA"] = 14] = "MOA";
     BoundaryType[BoundaryType["Restricted"] = 15] = "Restricted";
     BoundaryType[BoundaryType["Prohibited"] = 16] = "Prohibited";
     BoundaryType[BoundaryType["Warning"] = 17] = "Warning";
     BoundaryType[BoundaryType["Alert"] = 18] = "Alert";
     BoundaryType[BoundaryType["Danger"] = 19] = "Danger";
     BoundaryType[BoundaryType["NationalPark"] = 20] = "NationalPark";
     BoundaryType[BoundaryType["ModeC"] = 21] = "ModeC";
     BoundaryType[BoundaryType["Radar"] = 22] = "Radar";
     BoundaryType[BoundaryType["Training"] = 23] = "Training";
 })(BoundaryType || (BoundaryType = {}));
 /**
  * A type of airspace boundary altitude maxima.
  */
 var BoundaryAltitudeType;
 (function (BoundaryAltitudeType) {
     BoundaryAltitudeType[BoundaryAltitudeType["Unknown"] = 0] = "Unknown";
     BoundaryAltitudeType[BoundaryAltitudeType["MSL"] = 1] = "MSL";
     BoundaryAltitudeType[BoundaryAltitudeType["AGL"] = 2] = "AGL";
     BoundaryAltitudeType[BoundaryAltitudeType["Unlimited"] = 3] = "Unlimited";
 })(BoundaryAltitudeType || (BoundaryAltitudeType = {}));
 /**
  * A type of boundary geometry vector.
  */
 var BoundaryVectorType;
 (function (BoundaryVectorType) {
     BoundaryVectorType[BoundaryVectorType["None"] = 0] = "None";
     BoundaryVectorType[BoundaryVectorType["Start"] = 1] = "Start";
     BoundaryVectorType[BoundaryVectorType["Line"] = 2] = "Line";
     BoundaryVectorType[BoundaryVectorType["Origin"] = 3] = "Origin";
     BoundaryVectorType[BoundaryVectorType["ArcCW"] = 4] = "ArcCW";
     BoundaryVectorType[BoundaryVectorType["ArcCCW"] = 5] = "ArcCCW";
     BoundaryVectorType[BoundaryVectorType["Circle"] = 6] = "Circle";
 })(BoundaryVectorType || (BoundaryVectorType = {}));
 /**
  * Wind speed units used by METAR.
  */
 var MetarWindSpeedUnits;
 (function (MetarWindSpeedUnits) {
     MetarWindSpeedUnits[MetarWindSpeedUnits["Knot"] = 0] = "Knot";
     MetarWindSpeedUnits[MetarWindSpeedUnits["MeterPerSecond"] = 1] = "MeterPerSecond";
     MetarWindSpeedUnits[MetarWindSpeedUnits["KilometerPerHour"] = 2] = "KilometerPerHour";
 })(MetarWindSpeedUnits || (MetarWindSpeedUnits = {}));
 /** Visibility distance units used by METAR. */
 var MetarVisibilityUnits;
 (function (MetarVisibilityUnits) {
     MetarVisibilityUnits[MetarVisibilityUnits["Meter"] = 0] = "Meter";
     MetarVisibilityUnits[MetarVisibilityUnits["StatuteMile"] = 1] = "StatuteMile";
 })(MetarVisibilityUnits || (MetarVisibilityUnits = {}));
 /**
  * METAR cloud layer coverage/sky condition.
  */
 var MetarCloudLayerCoverage;
 (function (MetarCloudLayerCoverage) {
     MetarCloudLayerCoverage[MetarCloudLayerCoverage["SkyClear"] = 0] = "SkyClear";
     MetarCloudLayerCoverage[MetarCloudLayerCoverage["Clear"] = 1] = "Clear";
     MetarCloudLayerCoverage[MetarCloudLayerCoverage["NoSignificant"] = 2] = "NoSignificant";
     MetarCloudLayerCoverage[MetarCloudLayerCoverage["Few"] = 3] = "Few";
     MetarCloudLayerCoverage[MetarCloudLayerCoverage["Scattered"] = 4] = "Scattered";
     MetarCloudLayerCoverage[MetarCloudLayerCoverage["Broken"] = 5] = "Broken";
     MetarCloudLayerCoverage[MetarCloudLayerCoverage["Overcast"] = 6] = "Overcast";
 })(MetarCloudLayerCoverage || (MetarCloudLayerCoverage = {}));
 /**
  * METAR significant cloud types.
  */
 var MetarCloudLayerType;
 (function (MetarCloudLayerType) {
     MetarCloudLayerType[MetarCloudLayerType["Unspecified"] = -1] = "Unspecified";
     MetarCloudLayerType[MetarCloudLayerType["ToweringCumulus"] = 0] = "ToweringCumulus";
     MetarCloudLayerType[MetarCloudLayerType["Cumulonimbus"] = 1] = "Cumulonimbus";
     MetarCloudLayerType[MetarCloudLayerType["AltocumulusCastellanus"] = 2] = "AltocumulusCastellanus";
 })(MetarCloudLayerType || (MetarCloudLayerType = {}));
 /** METAR phenomenon types. */
 var MetarPhenomenonType;
 (function (MetarPhenomenonType) {
     MetarPhenomenonType[MetarPhenomenonType["None"] = 0] = "None";
     MetarPhenomenonType[MetarPhenomenonType["Mist"] = 1] = "Mist";
     MetarPhenomenonType[MetarPhenomenonType["Duststorm"] = 2] = "Duststorm";
     MetarPhenomenonType[MetarPhenomenonType["Dust"] = 3] = "Dust";
     MetarPhenomenonType[MetarPhenomenonType["Drizzle"] = 4] = "Drizzle";
     MetarPhenomenonType[MetarPhenomenonType["FunnelCloud"] = 5] = "FunnelCloud";
     MetarPhenomenonType[MetarPhenomenonType["Fog"] = 6] = "Fog";
     MetarPhenomenonType[MetarPhenomenonType["Smoke"] = 7] = "Smoke";
     MetarPhenomenonType[MetarPhenomenonType["Hail"] = 8] = "Hail";
     MetarPhenomenonType[MetarPhenomenonType["SmallHail"] = 9] = "SmallHail";
     MetarPhenomenonType[MetarPhenomenonType["Haze"] = 10] = "Haze";
     MetarPhenomenonType[MetarPhenomenonType["IceCrystals"] = 11] = "IceCrystals";
     MetarPhenomenonType[MetarPhenomenonType["IcePellets"] = 12] = "IcePellets";
     MetarPhenomenonType[MetarPhenomenonType["DustSandWhorls"] = 13] = "DustSandWhorls";
     MetarPhenomenonType[MetarPhenomenonType["Spray"] = 14] = "Spray";
     MetarPhenomenonType[MetarPhenomenonType["Rain"] = 15] = "Rain";
     MetarPhenomenonType[MetarPhenomenonType["Sand"] = 16] = "Sand";
     MetarPhenomenonType[MetarPhenomenonType["SnowGrains"] = 17] = "SnowGrains";
     MetarPhenomenonType[MetarPhenomenonType["Shower"] = 18] = "Shower";
     MetarPhenomenonType[MetarPhenomenonType["Snow"] = 19] = "Snow";
     MetarPhenomenonType[MetarPhenomenonType["Squalls"] = 20] = "Squalls";
     MetarPhenomenonType[MetarPhenomenonType["Sandstorm"] = 21] = "Sandstorm";
     MetarPhenomenonType[MetarPhenomenonType["UnknownPrecip"] = 22] = "UnknownPrecip";
     MetarPhenomenonType[MetarPhenomenonType["VolcanicAsh"] = 23] = "VolcanicAsh";
 })(MetarPhenomenonType || (MetarPhenomenonType = {}));
 /** METAR phenomenon intensities. */
 var MetarPhenomenonIntensity;
 (function (MetarPhenomenonIntensity) {
     MetarPhenomenonIntensity[MetarPhenomenonIntensity["Light"] = -1] = "Light";
     MetarPhenomenonIntensity[MetarPhenomenonIntensity["Normal"] = 0] = "Normal";
     MetarPhenomenonIntensity[MetarPhenomenonIntensity["Heavy"] = 1] = "Heavy";
 })(MetarPhenomenonIntensity || (MetarPhenomenonIntensity = {}));
 /**
  * Methods for working with FS ICAO strings.
  */
 class ICAO {
     /**
      * Gets the facility type from an ICAO.
      * @param icao The icao to get the facility type for.
      * @returns The ICAO facility type.
      * @throws An error if the facility type cannot be determined.
      */
     static getFacilityType(icao) {
         switch (icao[0]) {
             case 'A':
                 return FacilityType.Airport;
             case 'W':
                 return FacilityType.Intersection;
             case 'V':
                 return FacilityType.VOR;
             case 'N':
                 return FacilityType.NDB;
             case 'U':
                 return FacilityType.USR;
             case 'R':
                 return FacilityType.RWY;
             case 'S':
                 return FacilityType.VIS;
             default:
                 throw new Error(`ICAO ${icao} has unknown type: ${icao[0]}`);
         }
     }
     /**
      * Gets whether an icao is a facility type.
      * @param icao The icao to get the facility type for.
      * @returns a bool whether or not this icao is a facility type.
      */
     static isFacility(icao) {
         switch (icao[0]) {
             case 'A':
             case 'W':
             case 'V':
             case 'N':
             case 'U':
             case 'R':
             case 'S':
                 return true;
         }
         return false;
     }
     /**
      * Gets the ident for a given ICAO string.
      * @param icao The FS ICAO to get the ident for.
      * @returns The ICAO ident.
      */
     static getIdent(icao) {
         return icao.substr(7).trim();
     }
 }
 /**
  * An empty ICAO.
  */
 ICAO.emptyIcao = '            ';
 /**
  * Utility functions for working with user facilities.
  */
 class UserFacilityUtils {
     /**
      * Creates a user facility from latitude/longitude coordinates.
      * @param icao The ICAO string of the new facility.
      * @param lat The latitude of the new facility.
      * @param lon The longitude of the new facility.
      * @param isTemporary Whether the new facility is temporary.
      * @param name The name of the new facility.
      * @returns A new user facility.
      */
     static createFromLatLon(icao, lat, lon, isTemporary = false, name) {
         const fac = {
             icao,
             name: name !== null && name !== void 0 ? name : '',
             lat,
             lon,
             userFacilityType: UserFacilityType.LAT_LONG,
             isTemporary,
             region: '',
             city: '',
             magvar: MagVar.get(lat, lon)
         };
         return fac;
     }
 }
 
 /**
  * Methods for working with Runways and Runway Designations.
  */
 class RunwayUtils {
     /**
      * Gets the letter for a runway designator.
      * @param designator A runway designator.
      * @param lowerCase Whether the letter should be lower case. False by default.
      * @returns The letter for the specified runway designator.
      */
     static getDesignatorLetter(designator, lowerCase = false) {
         const letter = RunwayUtils.RUNWAY_DESIGNATOR_LETTERS[designator];
         return lowerCase
             ? letter.toLowerCase()
             : letter;
     }
     /**
      * Creates an empty one-way runway.
      * @returns an empty one-way runway.
      */
     static createEmptyOneWayRunway() {
         return {
             parentRunwayIndex: -1,
             designation: '',
             direction: 36,
             runwayDesignator: RunwayDesignator.RUNWAY_DESIGNATOR_NONE,
             course: 0,
             elevation: 0,
             latitude: 0,
             longitude: 0
         };
     }
     /**
      * Utility method to return two one-way runways from a single runway facility
      * @param runway is the AirportRunway object to evaluate
      * @param index is the index of the AirportRunway in the Facility
      * @returns splitRunways array of OneWayRunway objects
      */
     static getOneWayRunways(runway, index) {
         const splitRunways = [];
         const designations = runway.designation.split('-');
         for (let i = 0; i < designations.length; i++) {
             const runwayNumber = parseInt(designations[i]);
             let designator = RunwayDesignator.RUNWAY_DESIGNATOR_NONE;
             let course = 0;
             let thresholdDistanceFromCenter = 0;
             let thresholdElevation = 0;
             let ilsFrequency;
             if (i === 0) {
                 designator = runway.designatorCharPrimary;
                 course = runway.direction;
                 thresholdDistanceFromCenter = (runway.length / 2) - runway.primaryThresholdLength;
                 thresholdElevation = runway.primaryElevation;
                 ilsFrequency = runway.primaryILSFrequency.freqMHz === 0 ? undefined : runway.primaryILSFrequency;
             }
             else if (i === 1) {
                 designator = runway.designatorCharSecondary;
                 course = NavMath.normalizeHeading(runway.direction + 180);
                 thresholdDistanceFromCenter = (runway.length / 2) - runway.secondaryThresholdLength;
                 thresholdElevation = runway.secondaryElevation;
                 ilsFrequency = runway.secondaryILSFrequency.freqMHz === 0 ? undefined : runway.secondaryILSFrequency;
             }
             const designation = RunwayUtils.getRunwayNameString(runwayNumber, designator);
             const coordinates = RunwayUtils.tempGeoPoint
                 .set(runway.latitude, runway.longitude)
                 .offset(course - 180, UnitType.METER.convertTo(thresholdDistanceFromCenter, UnitType.GA_RADIAN));
             splitRunways.push({
                 parentRunwayIndex: index,
                 designation,
                 direction: runwayNumber,
                 runwayDesignator: designator,
                 course,
                 elevation: thresholdElevation,
                 latitude: coordinates.lat,
                 longitude: coordinates.lon,
                 ilsFrequency
             });
         }
         return splitRunways;
     }
     /**
      * Utility method to return the runway name from the number and designator (L/R/C/W)
      * @param runwayNumber is the integer part of a runway name (18, 26, 27, etc)
      * @param designator is the RunwayDesignator enum for the runway
      * @param padded Whether single-char runways should be 0-padded.
      * @param prefix A prefix to put before the runway name.
      * @returns the runway name string
      */
     static getRunwayNameString(runwayNumber, designator, padded = true, prefix = '') {
         let numberText = `${runwayNumber}`;
         if (padded) {
             numberText = numberText.padStart(2, '0');
         }
         return prefix + numberText + RunwayUtils.getDesignatorLetter(designator);
     }
     /**
      * Gets a one-way runway from an airport that matches a runway designation by number and designator.
      * @param airport The airport facility in which to search for the match.
      * @param runwayNumber A runway number to match.
      * @param runwayDesignator A runway designator to match.
      * @returns The one-way runway which matches the designation, or undefined if no match could be found.
      */
     static matchOneWayRunway(airport, runwayNumber, runwayDesignator) {
         const length = airport.runways.length;
         for (let r = 0; r < length; r++) {
             const runway = airport.runways[r];
             const designation = runway.designation;
             const primaryRunwayNumber = parseInt(designation.split('-')[0]);
             const secondaryRunwayNumber = parseInt(designation.split('-')[1]);
             if (primaryRunwayNumber === runwayNumber && runway.designatorCharPrimary === runwayDesignator) {
                 const oneWayRunways = RunwayUtils.getOneWayRunways(runway, r);
                 return oneWayRunways[0];
             }
             else if (secondaryRunwayNumber === runwayNumber && runway.designatorCharSecondary === runwayDesignator) {
                 const oneWayRunways = RunwayUtils.getOneWayRunways(runway, r);
                 return oneWayRunways[1];
             }
         }
         return undefined;
     }
     /**
      * Gets a one-way runway from an airport that matches a runway designation string.
      * @param airport The airport facility in which to search for the match.
      * @param designation A runway designation.
      * @returns The one-way runway which matches the designation, or undefined if no match could be found.
      */
     static matchOneWayRunwayFromDesignation(airport, designation) {
         const length = airport.runways.length;
         for (let i = 0; i < length; i++) {
             const match = RunwayUtils.getOneWayRunways(airport.runways[i], i).find((r) => {
                 return (r.designation === designation);
             });
             if (match) {
                 return match;
             }
         }
         return undefined;
     }
     /**
      * Gets a one-way runway from an airport that matches a runway ident.
      * @param airport The airport facility in which to search for the match.
      * @param ident A runway ident.
      * @returns The one-way runway which matches the ident, or undefined if no match could be found.
      */
     static matchOneWayRunwayFromIdent(airport, ident) {
         return RunwayUtils.matchOneWayRunwayFromDesignation(airport, ident.substr(2).trim());
     }
     /**
      * Utility method to return the procedures for a given runway.
      * @param procedures The procedures for the airport.
      * @param runway The given runway to find procedures for.
      * @returns A list of approach procedures for the given runway.
      */
     static getProceduresForRunway(procedures, runway) {
         const oneways = new Array();
         // TODO Make the designation splitting logic a common routine too.
         const designations = runway.designation.split('-');
         for (let i = 0; i < designations.length; i++) {
             const runwayNumber = parseInt(designations[i]);
             let runwayName;
             if (i === 0) {
                 runwayName = RunwayUtils.getRunwayNameString(runwayNumber, runway.designatorCharPrimary, false, '');
             }
             else {
                 runwayName = RunwayUtils.getRunwayNameString(runwayNumber, runway.designatorCharSecondary, false, '');
             }
             oneways.push(runwayName);
         }
         const found = new Array();
         for (const procedure of procedures) {
             if (oneways.includes(procedure.runway.trim())) {
                 found.push(procedure);
             }
             else if (procedure.runwayNumber === 0) {
                 found.push(procedure);
             }
         }
         return found;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     static getLocFrequency(airport, arg1, arg2) {
         let runway;
         if (typeof arg1 === 'string') {
             const matchedRunway = RunwayUtils.matchOneWayRunwayFromDesignation(airport, arg1);
             if (!matchedRunway) {
                 return undefined;
             }
             runway = matchedRunway;
         }
         else if (typeof arg1 === 'number') {
             const matchedRunway = RunwayUtils.matchOneWayRunway(airport, arg1, arg2);
             if (!matchedRunway) {
                 return undefined;
             }
             runway = matchedRunway;
         }
         else {
             runway = arg1;
         }
         const runwayDesignation = runway.designation;
         if (runway.ilsFrequency) {
             return runway.ilsFrequency;
         }
         for (let i = 0; i < airport.frequencies.length; i++) {
             // Note: drop the leading zero in the runway designation for the search because some third-party sceneries
             // format the frequency names without the leading zero.
             const match = airport.frequencies[i].name.search(runwayDesignation.replace(/^0/, ''));
             if (match > -1) {
                 return airport.frequencies[i];
             }
         }
         return undefined;
     }
     /**
      * A comparer for sorting runways by number, and then by L, C, and R.
      * @param r1 The first runway to compare.
      * @param r2 The second runway to compare.
      * @returns -1 if the first is before, 0 if equal, 1 if the first is after.
      */
     static sortRunways(r1, r2) {
         if (r1.direction === r2.direction) {
             let v1 = 0;
             if (r1.designation.indexOf('L') != -1) {
                 v1 = 1;
             }
             else if (r1.designation.indexOf('C') != -1) {
                 v1 = 2;
             }
             else if (r1.designation.indexOf('R') != -1) {
                 v1 = 3;
             }
             let v2 = 0;
             if (r2.designation.indexOf('L') != -1) {
                 v2 = 1;
             }
             else if (r2.designation.indexOf('C') != -1) {
                 v2 = 2;
             }
             else if (r2.designation.indexOf('R') != -1) {
                 v2 = 3;
             }
             return v1 - v2;
         }
         return r1.direction - r2.direction;
     }
     /**
      * Gets the ICAO string for the runway facility associated with a one-way runway.
      * @param airport The runway's parent airport.
      * @param runway A one-way runway.
      * @returns the ICAO string for the runway facility associated with the one-way runway.
      */
     static getRunwayFacilityIcao(airport, runway) {
         return `R  ${airport.icao.substr(7, 4)}RW${runway.designation.padEnd(3, ' ')}`;
     }
     /**
      * Creates a runway waypoint facility from a runway.
      * @param airport The runway's parent airport.
      * @param runway A one-way runway.
      * @returns A runway waypoint facility corresponding to the runway.
      */
     static createRunwayFacility(airport, runway) {
         return {
             icao: RunwayUtils.getRunwayFacilityIcao(airport, runway),
             name: `Runway ${runway.designation}`,
             region: airport.region,
             city: airport.city,
             lat: runway.latitude,
             lon: runway.longitude,
             magvar: airport.magvar,
             runway
         };
     }
     /**
      * Gets an alpha code from a runway number.
      * @param number is the runway number.
      * @returns a letter.
      */
     static getRunwayCode(number) {
         const n = Math.round(number);
         return String.fromCharCode(48 + n + (n > 9 ? 7 : 0));
     }
 }
 RunwayUtils.RUNWAY_DESIGNATOR_LETTERS = {
     [RunwayDesignator.RUNWAY_DESIGNATOR_NONE]: '',
     [RunwayDesignator.RUNWAY_DESIGNATOR_LEFT]: 'L',
     [RunwayDesignator.RUNWAY_DESIGNATOR_RIGHT]: 'R',
     [RunwayDesignator.RUNWAY_DESIGNATOR_CENTER]: 'C',
     [RunwayDesignator.RUNWAY_DESIGNATOR_WATER]: 'W',
     [RunwayDesignator.RUNWAY_DESIGNATOR_A]: 'A',
     [RunwayDesignator.RUNWAY_DESIGNATOR_B]: 'B',
 };
 RunwayUtils.tempGeoPoint = new GeoPoint(0, 0);
 
 /// <reference types="msfstypes/JS/common" />
 /**
  * A type map of facility type to facility search type.
  */
 ({
     /** Airport facility type. */
     [FacilityType.Airport]: FacilitySearchType.Airport,
     /** Intersection facility type. */
     [FacilityType.Intersection]: FacilitySearchType.Intersection,
     /** NDB facility type. */
     [FacilityType.NDB]: FacilitySearchType.Ndb,
     /** VOR facility type. */
     [FacilityType.VOR]: FacilitySearchType.Vor,
     /** USR facility type. */
     [FacilityType.USR]: FacilitySearchType.User
 });
 /**
  * A class that handles loading facility data from the simulator.
  */
 class FacilityLoader {
     /**
      * Creates an instance of the FacilityLoader.
      * @param facilityRepo A local facility repository.
      * @param onInitialized A callback to call when the facility loader has completed initialization.
      */
     constructor(facilityRepo, onInitialized = () => { }) {
         this.facilityRepo = facilityRepo;
         this.onInitialized = onInitialized;
         if (FacilityLoader.facilityListener === undefined) {
             FacilityLoader.facilityListener = RegisterViewListener('JS_LISTENER_FACILITY', () => {
                 FacilityLoader.facilityListener.on('SendAirport', FacilityLoader.onFacilityReceived);
                 FacilityLoader.facilityListener.on('SendIntersection', FacilityLoader.onFacilityReceived);
                 FacilityLoader.facilityListener.on('SendVor', FacilityLoader.onFacilityReceived);
                 FacilityLoader.facilityListener.on('SendNdb', FacilityLoader.onFacilityReceived);
                 FacilityLoader.facilityListener.on('NearestSearchCompleted', FacilityLoader.onNearestSearchCompleted);
                 setTimeout(() => this.onInitialized(), 2000);
             }, true);
         }
         else {
             setTimeout(() => this.onInitialized(), 2000);
         }
     }
     /**
      * Retrieves a facility.
      * @param type The type of facility to retrieve.
      * @param icao The ICAO of the facility to retrieve.
      * @returns A Promise which will be fulfilled with the requested facility, or rejected if the facility could not be
      * retrieved.
      */
     getFacility(type, icao) {
         switch (type) {
             case FacilityType.USR:
             case FacilityType.RWY:
             case FacilityType.VIS:
                 return this.getFacilityFromRepo(type, icao);
             default:
                 return this.getFacilityFromCoherent(type, icao);
         }
     }
     // eslint-disable-next-line jsdoc/require-throws
     /**
      * Retrieves a facility from the local facility repository.
      * @param type The type of facility to retrieve.
      * @param icao The ICAO of the facility to retrieve.
      * @returns A Promise which will be fulfilled with the requested facility, or rejected if the facility could not be
      * retrieved.
      */
     async getFacilityFromRepo(type, icao) {
         const fac = this.facilityRepo.get(icao);
         if (fac) {
             return fac;
         }
         else if (type === FacilityType.RWY) {
             try {
                 const airport = await this.getFacility(FacilityType.Airport, `A      ${icao.substr(3, 4)} `);
                 const runway = RunwayUtils.matchOneWayRunwayFromIdent(airport, ICAO.getIdent(icao));
                 if (runway) {
                     const runwayFac = RunwayUtils.createRunwayFacility(airport, runway);
                     this.facilityRepo.add(runwayFac);
                     return runwayFac;
                 }
             }
             catch (e) {
                 // noop
             }
         }
         throw `Facility ${icao} could not be found.`;
     }
     /**
      * Retrieves a facility from Coherent.
      * @param type The type of facility to retrieve.
      * @param icao The ICAO of the facility to retrieve.
      * @returns A Promise which will be fulfilled with the requested facility, or rejected if the facility could not be
      * retrieved.
      */
     getFacilityFromCoherent(type, icao) {
         const isMismatch = ICAO.getFacilityType(icao) !== type;
         const currentTime = Date.now();
         let queue = FacilityLoader.requestQueue;
         let cache = FacilityLoader.facCache;
         if (isMismatch) {
             queue = FacilityLoader.mismatchRequestQueue;
             cache = FacilityLoader.typeMismatchFacCache;
         }
         let request = queue.get(icao);
         if (request === undefined || currentTime - request.timeStamp > 10000) {
             let resolve = undefined;
             let reject = undefined;
             const promise = new Promise((resolution, rejection) => {
                 resolve = resolution;
                 reject = rejection;
                 const cachedFac = cache.get(icao);
                 if (cachedFac === undefined) {
                     Coherent.call(type, icao).then((isValid) => {
                         if (!isValid) {
                             rejection(`Facility ${icao} could not be found.`);
                             FacilityLoader.requestQueue.delete(icao);
                         }
                     });
                 }
                 else {
                     resolve(cachedFac);
                 }
             });
             if (request) {
                 request.reject(`Facility request for ${icao} has timed out.`);
             }
             request = { promise, timeStamp: currentTime, resolve: resolve, reject: reject };
             FacilityLoader.requestQueue.set(icao, request);
         }
         return request.promise;
     }
     /**
      * Gets airway data from the sim.
      * @param airwayName The airway name.
      * @param airwayType The airway type.
      * @param icao The 12 character FS ICAO of at least one intersection in the airway.
      * @returns The retrieved airway.
      * @throws an error if no airway is returned
      */
     async getAirway(airwayName, airwayType, icao) {
         if (FacilityLoader.airwayCache.has(airwayName)) {
             const cachedAirway = FacilityLoader.airwayCache.get(airwayName);
             const match = cachedAirway === null || cachedAirway === void 0 ? void 0 : cachedAirway.waypoints.find((w) => {
                 w.icao === icao;
             });
             if (match !== undefined && cachedAirway !== undefined) {
                 return cachedAirway;
             }
         }
         const fac = await this.getFacility(FacilityType.Intersection, icao);
         const route = fac.routes.find((r) => r.name === airwayName);
         if (route !== undefined) {
             const airwayBuilder = new AirwayBuilder(fac, route, this);
             const status = await airwayBuilder.startBuild();
             if (status === AirwayStatus.COMPLETE) {
                 const waypoints = airwayBuilder.waypoints;
                 if (waypoints !== null) {
                     const airway = new AirwayObject(airwayName, airwayType);
                     airway.waypoints = [...waypoints];
                     FacilityLoader.addToAirwayCache(airway);
                     return airway;
                 }
             }
         }
         throw new Error('Airway could not be found.');
     }
     /**
      * Starts a nearest facilities search session.
      * @param type The type of facilities to search for.
      * @returns The new nearest search session.
      */
     async startNearestSearchSession(type) {
         const sessionId = await Coherent.call('START_NEAREST_SEARCH_SESSION', type);
         let session;
         switch (type) {
             case FacilitySearchType.Airport:
                 session = new NearestAirportSearchSession(sessionId);
                 break;
             case FacilitySearchType.Intersection:
                 session = new NearestIntersectionSearchSession(sessionId);
                 break;
             case FacilitySearchType.Vor:
                 session = new NearestIntersectionSearchSession(sessionId);
                 break;
             case FacilitySearchType.Boundary:
                 session = new NearestBoundarySearchSession(sessionId);
                 break;
             default:
                 session = new NearestSearchSession(sessionId);
                 break;
         }
         FacilityLoader.searchSessions.set(sessionId, session);
         return session;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     async getMetar(arg) {
         const ident = typeof arg === 'string' ? arg : ICAO.getIdent(arg.icao);
         const metar = await Coherent.call('GET_METAR_BY_IDENT', ident);
         return FacilityLoader.cleanMetar(metar);
     }
     /**
      * Searches for the METAR issued for the closest airport to a given location.
      * @param lat The latitude of the center of the search, in degrees.
      * @param lon The longitude of the center of the search, in degrees.
      * @returns The METAR issued for the closest airport to the given location, or undefined if none could be found.
      */
     async searchMetar(lat, lon) {
         const metar = await Coherent.call('GET_METAR_BY_LATLON', lat, lon);
         return FacilityLoader.cleanMetar(metar);
     }
     /**
      * Cleans up a raw METAR object.
      * @param raw A raw METAR object.
      * @returns A cleaned version of the raw METAR object, or undefined if the raw METAR is empty.
      */
     static cleanMetar(raw) {
         if (raw.icao === '') {
             return undefined;
         }
         raw.gust < 0 && delete raw.gust;
         raw.vertVis < 0 && delete raw.vertVis;
         isNaN(raw.altimeterA) && delete raw.altimeterA;
         raw.altimeterQ < 0 && delete raw.altimeterQ;
         isNaN(raw.slp) && delete raw.slp;
         return raw;
     }
     /**
      * Searches for ICAOs by their ident portion only.
      * @param filter The type of facility to filter by. Selecting NONE will search all facility type ICAOs.
      * @param ident The partial or complete ident to search for.
      * @param maxItems The max number of matches to return.
      * @returns A collection of matched ICAOs.
      */
     async searchByIdent(filter, ident, maxItems = 40) {
         const results = await Coherent.call('SEARCH_BY_IDENT', ident, filter, maxItems);
         if (filter === FacilitySearchType.User || filter === FacilitySearchType.None) {
             this.facilityRepo.forEach(fac => {
                 const facIdent = ICAO.getIdent(fac.icao);
                 if (facIdent === ident) {
                     results.unshift(fac.icao);
                 }
                 else if (facIdent.startsWith(ident)) {
                     results.push(fac.icao);
                 }
             }, FacilityLoader.facRepositorySearchTypes);
         }
         return results;
     }
     /**
      * A callback called when a facility is received from the simulator.
      * @param facility The received facility.
      */
     static onFacilityReceived(facility) {
         const request = FacilityLoader.requestQueue.get(facility.icao);
         if (request !== undefined) {
             request.resolve(facility);
             FacilityLoader.addToFacilityCache(facility, facility['__Type'] === 'JS_FacilityIntersection' && facility.icao[0] !== 'W');
             FacilityLoader.requestQueue.delete(facility.icao);
         }
     }
     /**
      * A callback called when a search completes.
      * @param results The results of the search.
      */
     static onNearestSearchCompleted(results) {
         const session = FacilityLoader.searchSessions.get(results.sessionId);
         if (session !== undefined) {
             session.onSearchCompleted(results);
         }
     }
     /**
      * Adds a facility to the cache.
      * @param fac The facility to add.
      * @param isTypeMismatch Whether to add the facility to the type mismatch cache.
      */
     static addToFacilityCache(fac, isTypeMismatch) {
         const cache = isTypeMismatch ? FacilityLoader.typeMismatchFacCache : FacilityLoader.facCache;
         cache.set(fac.icao, fac);
         if (cache.size > FacilityLoader.MAX_FACILITY_CACHE_ITEMS) {
             cache.delete(cache.keys().next().value);
         }
     }
     /**
      * Adds an airway to the airway cache.
      * @param airway The airway to add.
      */
     static addToAirwayCache(airway) {
         FacilityLoader.airwayCache.set(airway.name, airway);
         if (FacilityLoader.airwayCache.size > FacilityLoader.MAX_AIRWAY_CACHE_ITEMS) {
             FacilityLoader.airwayCache.delete(FacilityLoader.airwayCache.keys().next().value);
         }
     }
 }
 FacilityLoader.MAX_FACILITY_CACHE_ITEMS = 1000;
 FacilityLoader.MAX_AIRWAY_CACHE_ITEMS = 1000;
 FacilityLoader.requestQueue = new Map();
 FacilityLoader.mismatchRequestQueue = new Map();
 FacilityLoader.facCache = new Map();
 FacilityLoader.typeMismatchFacCache = new Map();
 FacilityLoader.airwayCache = new Map();
 FacilityLoader.searchSessions = new Map();
 FacilityLoader.facRepositorySearchTypes = [FacilityType.USR];
 /**
  * A session for searching for nearest facilities.
  */
 class NearestSearchSession {
     /**
      * Creates an instance of a NearestSearchSession.
      * @param sessionId The ID of the session.
      */
     constructor(sessionId) {
         this.sessionId = sessionId;
         this.searchQueue = new Map();
     }
     /**
      * Searches for nearest facilities from the specified point.
      * @param lat The latitude, in degrees.
      * @param lon The longitude, in degrees.
      * @param radius The radius around the point to search, in meters.
      * @param maxItems The maximum number of items.
      * @returns The nearest search results.
      */
     searchNearest(lat, lon, radius, maxItems) {
         const promise = new Promise((resolve) => {
             Coherent.call('SEARCH_NEAREST', this.sessionId, lat, lon, radius, maxItems)
                 .then((searchId) => {
                 this.searchQueue.set(searchId, { promise, resolve });
             });
         });
         return promise;
     }
     /**
      * A callback called by the facility loader when a nearest search has completed.
      * @param results The search results.
      */
     onSearchCompleted(results) {
         const request = this.searchQueue.get(results.searchId);
         if (request !== undefined) {
             request.resolve(results);
             this.searchQueue.delete(results.searchId);
         }
     }
 }
 /**
  * A session for searching for nearest airports.
  */
 class NearestAirportSearchSession extends NearestSearchSession {
     /**
      * Sets the filter for the airport nearest search.
      * @param showClosed Whether or not to show closed airports.
      * @param classMask A bitmask to determine which JS airport classes to show.
      */
     setAirportFilter(showClosed, classMask) {
         Coherent.call('SET_NEAREST_AIRPORT_FILTER', this.sessionId, showClosed ? 1 : 0, classMask);
     }
 }
 /**
  * A session for searching for nearest intersections.
  */
 class NearestIntersectionSearchSession extends NearestSearchSession {
     /**
      * Sets the filter for the intersection nearest search.
      * @param typeMask A bitmask to determine which JS intersection types to show.
      */
     setIntersectionFilter(typeMask) {
         Coherent.call('SET_NEAREST_INTERSECTION_FILTER', this.sessionId, typeMask);
     }
 }
 /**
  * A session for searching for nearest airspace boundaries.
  */
 class NearestBoundarySearchSession extends NearestSearchSession {
     /**
      * Sets the filter for the boundary nearest search.
      * @param classMask A bitmask to determine which boundary classes to show.
      */
     setBoundaryFilter(classMask) {
         Coherent.call('SET_NEAREST_BOUNDARY_FILTER', this.sessionId, classMask);
     }
 }
 /**
  * An airway.
  */
 class AirwayObject {
     /** Builds a Airway
      * @param name - the name of the new airway.
      * @param type - the type of the new airway.
      */
     constructor(name, type) {
         this._waypoints = [];
         this._name = name;
         this._type = type;
     }
     /**
      * Gets the name of the airway
      * @returns the airway name
      */
     get name() {
         return this._name;
     }
     /**
      * Gets the type of the airway
      * @returns the airway type
      */
     get type() {
         return this._type;
     }
     /**
      * Gets the waypoints of this airway.
      * @returns the waypoints of this airway.
      */
     get waypoints() {
         return this._waypoints;
     }
     /**
      * Sets the waypoints of this airway.
      * @param waypoints is the array of waypoints.
      */
     set waypoints(waypoints) {
         this._waypoints = waypoints;
     }
 }
 /**
  * WT Airway Status Enum
  */
 var AirwayStatus;
 (function (AirwayStatus) {
     /**
      * @readonly
      * @property {number} INCOMPLETE - indicates waypoints have not been loaded yet.
      */
     AirwayStatus[AirwayStatus["INCOMPLETE"] = 0] = "INCOMPLETE";
     /**
      * @readonly
      * @property {number} COMPLETE - indicates all waypoints have been successfully loaded.
      */
     AirwayStatus[AirwayStatus["COMPLETE"] = 1] = "COMPLETE";
     /**
      * @readonly
      * @property {number} PARTIAL - indicates some, but not all, waypoints have been successfully loaded.
      */
     AirwayStatus[AirwayStatus["PARTIAL"] = 2] = "PARTIAL";
 })(AirwayStatus || (AirwayStatus = {}));
 /**
  * The Airway Builder.
  */
 class AirwayBuilder {
     /** Creates an instance of the AirwayBuilder
      * @param _initialWaypoint is the initial intersection facility
      * @param _initialData is the intersection route to build from
      * @param facilityLoader is an instance of the facility loader
      */
     constructor(_initialWaypoint, _initialData, facilityLoader) {
         this._initialWaypoint = _initialWaypoint;
         this._initialData = _initialData;
         this.facilityLoader = facilityLoader;
         this._waypointsArray = [];
         this._hasStarted = false;
         this._isDone = false;
     }
     // constructor(private _initialWaypoint: IntersectionFacility, private _requestEntry: (entry: string) => Promise<IntersectionFacility>) {
     // }
     /**
      * Get whether this builder has started loading waypoints
      * @returns whether this builder has started
      */
     get hasStarted() {
         return this._hasStarted;
     }
     /**
      * Get whether this builder is done loading waypoints
      * @returns whether this builder is done loading waypoints
      */
     get isDone() {
         return this._isDone;
     }
     /**
      * Get the airway waypoints
      * @returns the airway waypoints, or null
      */
     get waypoints() {
         return this._waypointsArray;
     }
     /** Steps through the airway waypoints
      * @param stepForward is the direction to step; true = forward, false = backward
      * @param arrayInsertFunc is the arrayInsertFunc
      */
     async _step(stepForward, arrayInsertFunc) {
         let isDone = false;
         let current = this._initialData;
         while (!isDone && current) {
             const nextICAO = stepForward ? current.nextIcao : current.prevIcao;
             if (nextICAO && nextICAO.length > 0 && nextICAO[0] != ' ' && this._waypointsArray !== null
                 && !this._waypointsArray.find(waypoint => waypoint.icao === nextICAO)) {
                 const fac = await this.facilityLoader.getFacility(FacilityType.Intersection, nextICAO);
                 arrayInsertFunc(fac);
                 const next = fac.routes.find((route) => route.name === current.name);
                 if (next !== undefined) {
                     current = next;
                 }
                 else {
                     isDone = true;
                 }
             }
             else {
                 isDone = true;
             }
         }
     }
     /** Steps Forward through the airway waypoints
      * @returns the step forward function
      */
     async _stepForward() {
         if (this._waypointsArray !== null) {
             return this._step(true, this._waypointsArray.push.bind(this._waypointsArray));
         }
     }
     /** Steps Backward through the airway waypoints
      * @returns the step backward function
      */
     async _stepBackward() {
         if (this._waypointsArray !== null) {
             return this._step(false, this._waypointsArray.unshift.bind(this._waypointsArray));
         }
     }
     /**
      * Sets the array into which this builder will load waypoints.
      * @param array is the array into which the builder will load waypoints
      */
     setWaypointsArray(array) {
         this._waypointsArray = array;
     }
     /**
      * Begins loading waypoints for this builder's parent airway.
      * @returns a Promise to return a status code corresponding to Airway.Status when this builder has
      * finished loading waypoints.
      */
     startBuild() {
         if (this.hasStarted) {
             return Promise.reject(new Error('Airway builder has already started building.'));
         }
         return new Promise(resolve => {
             this._hasStarted = true;
             if (this._waypointsArray !== null) {
                 this._waypointsArray.push(this._initialWaypoint);
                 Promise.all([
                     this._stepForward(),
                     this._stepBackward()
                 ]).then(() => {
                     this._isDone = true;
                     resolve(AirwayStatus.COMPLETE);
                 }).catch(() => {
                     this._isDone = true;
                     resolve(AirwayStatus.PARTIAL);
                 });
             }
         });
     }
 }
 
 var FacilityRepositorySyncType;
 (function (FacilityRepositorySyncType) {
     FacilityRepositorySyncType[FacilityRepositorySyncType["Add"] = 0] = "Add";
     FacilityRepositorySyncType[FacilityRepositorySyncType["Remove"] = 1] = "Remove";
     FacilityRepositorySyncType[FacilityRepositorySyncType["DumpRequest"] = 2] = "DumpRequest";
     FacilityRepositorySyncType[FacilityRepositorySyncType["DumpResponse"] = 3] = "DumpResponse";
 })(FacilityRepositorySyncType || (FacilityRepositorySyncType = {}));
 /**
  * A repository of facilities.
  */
 class FacilityRespository {
     /**
      * Constructor.
      * @param bus The event bus.
      */
     constructor(bus) {
         this.bus = bus;
         this.repos = {};
         this.ignoreSync = false;
         bus.getSubscriber().on(FacilityRespository.SYNC_TOPIC).handle(this.onSyncEvent.bind(this));
         this.pubSyncEvent(FacilityRepositorySyncType.DumpRequest);
     }
     /**
      * Retrieves a facility from this repository.
      * @param icao The ICAO of the facility to retrieve.
      * @returns The requested user facility, or undefined if it was not found in this repository.
      */
     get(icao) {
         var _a;
         if (!ICAO.isFacility(icao)) {
             return undefined;
         }
         return (_a = this.repos[ICAO.getFacilityType(icao)]) === null || _a === void 0 ? void 0 : _a.get(icao);
     }
     /**
      * Adds a facility to this repository and all other repositories synced with this one.
      * @param fac The facility to add.
      */
     add(fac) {
         if (!ICAO.isFacility(fac.icao)) {
             return;
         }
         this.addToRepo(fac);
         this.pubSyncEvent(FacilityRepositorySyncType.Add, [fac]);
     }
     /**
      * Removes a facility from this repository and all other repositories synced with this one.
      * @param fac The facility to remove.
      */
     remove(fac) {
         if (!ICAO.isFacility(fac.icao)) {
             return;
         }
         this.removeFromRepo(fac);
         this.pubSyncEvent(FacilityRepositorySyncType.Remove, [fac]);
     }
     /**
      * Iterates over every facility in this respository with a visitor function.
      * @param fn A visitor function.
      * @param types The types of facilities over which to iterate. Defaults to all facility types.
      */
     forEach(fn, types) {
         var _a;
         const keys = types !== null && types !== void 0 ? types : Object.keys(this.repos);
         const len = keys.length;
         for (let i = 0; i < len; i++) {
             (_a = this.repos[keys[i]]) === null || _a === void 0 ? void 0 : _a.forEach(fn);
         }
     }
     /**
      * Adds a facility to this repository.
      * @param fac The facility to add.
      */
     addToRepo(fac) {
         var _a;
         var _b, _c;
         ((_a = (_b = this.repos)[_c = ICAO.getFacilityType(fac.icao)]) !== null && _a !== void 0 ? _a : (_b[_c] = new Map())).set(fac.icao, fac);
     }
     /**
      * Removes a facility from this repository.
      * @param fac The facility to remove.
      */
     removeFromRepo(fac) {
         var _a;
         (_a = this.repos[ICAO.getFacilityType(fac.icao)]) === null || _a === void 0 ? void 0 : _a.delete(fac.icao);
     }
     /**
      * Publishes a sync event over the event bus.
      * @param type The type of sync event.
      * @param facs The event's user facilities.
      */
     pubSyncEvent(type, facs) {
         this.ignoreSync = true;
         this.bus.pub(FacilityRespository.SYNC_TOPIC, { type, facs }, true, false);
         this.ignoreSync = false;
     }
     /**
      * A callback which is called when a sync event occurs.
      * @param data The event data.
      */
     onSyncEvent(data) {
         if (this.ignoreSync) {
             return;
         }
         switch (data.type) {
             case FacilityRepositorySyncType.Add:
             case FacilityRepositorySyncType.DumpResponse:
                 data.facs.forEach(fac => this.addToRepo(fac));
                 break;
             case FacilityRepositorySyncType.Remove:
                 data.facs.forEach(fac => this.removeFromRepo(fac));
                 break;
             case FacilityRepositorySyncType.DumpRequest:
                 {
                     const facs = [];
                     this.forEach(fac => facs.push(fac));
                     this.pubSyncEvent(FacilityRepositorySyncType.DumpResponse, facs);
                 }
                 break;
         }
     }
     /**
      * Gets an instance of FacilityRespository.
      * @param bus The event bus.
      * @returns an instance of FacilityRespository.
      */
     static getRepository(bus) {
         var _a;
         return (_a = FacilityRespository.INSTANCE) !== null && _a !== void 0 ? _a : (FacilityRespository.INSTANCE = new FacilityRespository(bus));
     }
 }
 FacilityRespository.SYNC_TOPIC = 'facilityrepo_sync';
 
 /**
  * A type map of search type to concrete facility loader query type.
  */
 new Map([
     [FacilitySearchType.Airport, FacilityType.Airport],
     [FacilitySearchType.Intersection, FacilityType.Intersection],
     [FacilitySearchType.Vor, FacilityType.VOR],
     [FacilitySearchType.Ndb, FacilityType.NDB],
     [FacilitySearchType.User, FacilityType.USR]
 ]);
 
 [new GeoCircle(new Float64Array(3), 0)];
 
 var IcaoSearchFilter;
 (function (IcaoSearchFilter) {
     IcaoSearchFilter[IcaoSearchFilter["ALL"] = 0] = "ALL";
     IcaoSearchFilter[IcaoSearchFilter["AIRPORT"] = 1] = "AIRPORT";
     IcaoSearchFilter[IcaoSearchFilter["VOR"] = 2] = "VOR";
     IcaoSearchFilter[IcaoSearchFilter["NDB"] = 3] = "NDB";
     IcaoSearchFilter[IcaoSearchFilter["INTERSECTION"] = 4] = "INTERSECTION";
     IcaoSearchFilter[IcaoSearchFilter["USR"] = 5] = "USR";
 })(IcaoSearchFilter || (IcaoSearchFilter = {}));
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A publisher for basic ADC/AHRS information.
  */
 class ADCPublisher extends SimVarPublisher {
     /**
      * Create an ADCPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the rate of publishing
      */
     constructor(bus, pacer = undefined) {
         super(ADCPublisher.simvars, bus, pacer);
     }
     /**
      * Updates the ADC publisher.
      */
     onUpdate() {
         super.onUpdate();
     }
 }
 ADCPublisher.simvars = new Map([
     ['ias', { name: 'AIRSPEED INDICATED', type: SimVarValueType.Knots }],
     ['tas', { name: 'AIRSPEED TRUE', type: SimVarValueType.Knots }],
     ['alt', { name: 'INDICATED ALTITUDE', type: SimVarValueType.Feet }],
     ['vs', { name: 'VERTICAL SPEED', type: SimVarValueType.FPM }],
     ['hdg_deg', { name: 'PLANE HEADING DEGREES MAGNETIC', type: SimVarValueType.Degree }],
     ['pitch_deg', { name: 'PLANE PITCH DEGREES', type: SimVarValueType.Degree }],
     ['roll_deg', { name: 'PLANE BANK DEGREES', type: SimVarValueType.Degree }],
     ['hdg_deg_true', { name: 'PLANE HEADING DEGREES TRUE', type: SimVarValueType.Degree }],
     ['kohlsman_setting_hg_1', { name: 'KOHLSMAN SETTING HG', type: SimVarValueType.InHG }],
     ['turn_coordinator_ball', { name: 'TURN COORDINATOR BALL', type: SimVarValueType.Number }],
     ['delta_heading_rate', { name: 'DELTA HEADING RATE', type: SimVarValueType.Degree }],
     ['ambient_temp_c', { name: 'AMBIENT TEMPERATURE', type: SimVarValueType.Celsius }],
     ['ambient_wind_velocity', { name: 'AMBIENT WIND VELOCITY', type: SimVarValueType.Knots }],
     ['ambient_wind_direction', { name: 'AMBIENT WIND DIRECTION', type: SimVarValueType.Degree }],
     ['kohlsman_setting_mb_1', { name: 'KOHLSMAN SETTING MB', type: SimVarValueType.MB }],
     ['baro_units_hpa_1', { name: 'L:XMLVAR_Baro_Selector_HPA_1', type: SimVarValueType.Bool }],
     ['on_ground', { name: 'SIM ON GROUND', type: SimVarValueType.Bool }],
     ['aoa', { name: 'INCIDENCE ALPHA', type: SimVarValueType.Degree }]
 ]);
 
 /**
  * InstrumentBackplane provides a common control point for aggregating and
  * managing any number of publishers.  This can be used as an "update loop"
  * corral", amongst other things.
  */
 class InstrumentBackplane {
     /**
      * Create an InstrumentBackplane
      */
     constructor() {
         this.publishers = new Map();
         this.instruments = new Map();
     }
     /**
      * Initialize all the things. This is initially just a proxy for the
      * private initPublishers() and initInstruments() methods.
      *
      * This should be simplified.
      */
     init() {
         this.initPublishers();
         this.initInstruments();
     }
     /**
      * Update all the things.  This is initially just a proxy for the private
      * updatePublishers() and updateInstruments() methods.
      *
      * This should be simplified.
      */
     onUpdate() {
         this.updatePublishers();
         this.updateInstruments();
     }
     /**
      * Add a publisher to the backplane.
      * @param name - a symbolic name for the publisher for reference
      * @param publisher - a publisher extending BasePublisher
      */
     addPublisher(name, publisher) {
         this.publishers.set(name, publisher);
     }
     /**
      * Add an instrument to the backplane.
      * @param name - a symbolic name for the publisher for reference
      * @param instrument - an instrument implementing Instrment
      */
     addInstrument(name, instrument) {
         this.instruments.set(name, instrument);
     }
     /**
      * Initialize all of the publishers that you hold.
      */
     initPublishers() {
         for (const publisher of this.publishers.values()) {
             publisher.startPublish();
         }
     }
     /**
      * Initialize all of the instruments that you hold.
      */
     initInstruments() {
         for (const instrument of this.instruments.values()) {
             instrument.init();
         }
     }
     /**
      * Update all of the publishers that you hold.
      */
     updatePublishers() {
         for (const publisher of this.publishers.values()) {
             publisher.onUpdate();
         }
     }
     /**
      * Update all of the instruments that you hold.
      */
     updateInstruments() {
         for (const instrument of this.instruments.values()) {
             instrument.onUpdate();
         }
     }
 }
 
 /// <reference types="msfstypes/JS/SimPlane" />
 /**
  * A publisher for basic GNSS information.
  */
 class GNSSPublisher extends BasePublisher {
     /**
      * Create an GNSSPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the rate of publishing
      */
     constructor(bus, pacer = undefined) {
         super(bus, pacer);
     }
     /**
      * A callback called when the publisher updates.
      */
     onUpdate() {
         this.publishPosition();
         this.publishTime();
         this.publishTrack();
         this.publishGroundSpeed();
         this.publishMagVar();
     }
     /**
      * Publishes the gps-position event.
      */
     publishPosition() {
         const lat = SimVar.GetSimVarValue('PLANE LATITUDE', SimVarValueType.Degree);
         const lon = SimVar.GetSimVarValue('PLANE LONGITUDE', SimVarValueType.Degree);
         const alt = SimVar.GetSimVarValue('PLANE ALTITUDE', SimVarValueType.Meters);
         this.publish('gps-position', new LatLongAlt(lat, lon, alt));
     }
     /**
      * Publishes the zulu_time and time_of_day events.
      */
     publishTime() {
         const zuluTime = SimVar.GetGlobalVarValue('ZULU TIME', 'seconds');
         const timeOfDay = SimVar.GetSimVarValue('E:TIME OF DAY', 'number');
         this.publish('zulu_time', zuluTime);
         this.publish('time_of_day', timeOfDay);
     }
     /**
      * Publishes the track_deg_true and track_deg_magnetic events.
      */
     publishTrack() {
         const headingTrue = SimVar.GetSimVarValue('PLANE HEADING DEGREES TRUE', SimVarValueType.Degree);
         const velocityEW = SimVar.GetSimVarValue('VELOCITY WORLD X', SimVarValueType.Knots);
         const velocityNS = SimVar.GetSimVarValue('VELOCITY WORLD Z', SimVarValueType.Knots);
         let track = headingTrue;
         if (velocityEW !== 0 || velocityNS !== 0) {
             track = NavMath.normalizeHeading(UnitType.RADIAN.convertTo(Math.atan2(velocityEW, velocityNS), UnitType.DEGREE));
         }
         const magvar = SimVar.GetSimVarValue('MAGVAR', SimVarValueType.Degree);
         const magneticTrack = NavMath.normalizeHeading(track - magvar);
         this.publish('track_deg_true', track);
         this.publish('track_deg_magnetic', magneticTrack);
     }
     /**
      * Publishes the ground_speed event.
      */
     publishGroundSpeed() {
         const gs = SimVar.GetSimVarValue('GROUND VELOCITY', SimVarValueType.Knots);
         this.publish('ground_speed', gs);
     }
     /**
      * Publishes the ground_speed event.
      */
     publishMagVar() {
         const magvar = SimVar.GetSimVarValue('MAGVAR', SimVarValueType.Degree);
         this.publish('magvar', magvar);
     }
 }
 
 // Common definitions relevant to all radio types.
 /** The basic radio types. */
 var RadioType;
 (function (RadioType) {
     RadioType["Com"] = "COM";
     RadioType["Nav"] = "NAV";
     RadioType["Adf"] = "ADF";
 })(RadioType || (RadioType = {}));
 /** The two frequency "banks", active and standby. */
 var FrequencyBank;
 (function (FrequencyBank) {
     FrequencyBank[FrequencyBank["Active"] = 0] = "Active";
     FrequencyBank[FrequencyBank["Standby"] = 1] = "Standby";
 })(FrequencyBank || (FrequencyBank = {}));
 /** COM frequency spacing on COM radios. */
 var ComSpacing;
 (function (ComSpacing) {
     /** 25Khz spacing */
     ComSpacing[ComSpacing["Spacing25Khz"] = 0] = "Spacing25Khz";
     /** 8.33Khz spacing */
     ComSpacing[ComSpacing["Spacing833Khz"] = 1] = "Spacing833Khz";
 })(ComSpacing || (ComSpacing = {}));
 
 /// <reference types="msfstypes/JS/simvar" />
 /** Publish simvars for ourselves */
 class NavProcSimVarPublisher extends SimVarPublisher {
     /**
      * Create a NavProcSimVarPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the pace of publishing
      */
     constructor(bus, pacer = undefined) {
         super(NavProcSimVarPublisher.simvars, bus, pacer);
     }
 }
 NavProcSimVarPublisher.simvars = new Map([
     ['nav1_obs', { name: 'NAV OBS:1', type: SimVarValueType.Degree }],
     ['nav1_cdi', { name: 'NAV CDI:1', type: SimVarValueType.Number }],
     ['nav1_dme', { name: 'NAV DME:1', type: SimVarValueType.NM }],
     ['nav1_has_dme', { name: 'NAV HAS DME:1', type: SimVarValueType.Bool }],
     ['nav1_has_nav', { name: 'NAV HAS NAV:1', type: SimVarValueType.Bool }],
     ['nav1_radial', { name: 'NAV RADIAL:1', type: SimVarValueType.Radians }],
     ['nav1_signal', { name: 'NAV SIGNAL:1', type: SimVarValueType.Number }],
     ['nav1_ident', { name: 'NAV IDENT:1', type: SimVarValueType.String }],
     ['nav1_to_from', { name: 'NAV TOFROM:1', type: SimVarValueType.Enum }],
     ['nav1_localizer', { name: 'NAV HAS LOCALIZER:1', type: SimVarValueType.Bool }],
     ['nav1_localizer_crs', { name: 'NAV LOCALIZER:1', type: SimVarValueType.Number }],
     ['nav1_glideslope', { name: 'NAV HAS GLIDE SLOPE:1', type: SimVarValueType.Bool }],
     ['nav1_gs_error', { name: 'NAV GLIDE SLOPE ERROR:1', type: SimVarValueType.Degree }],
     ['nav1_raw_gs', { name: 'NAV RAW GLIDE SLOPE:1', type: SimVarValueType.Degree }],
     ['nav1_gs_lla', { name: 'NAV GS LATLONALT:1', type: SimVarValueType.LLA }],
     ['nav1_lla', { name: 'NAV VOR LATLONALT:1', type: SimVarValueType.LLA }],
     ['nav1_magvar', { name: 'NAV MAGVAR:1', type: SimVarValueType.Number }],
     ['nav2_obs', { name: 'NAV OBS:2', type: SimVarValueType.Degree }],
     ['nav2_cdi', { name: 'NAV CDI:2', type: SimVarValueType.Number }],
     ['nav2_dme', { name: 'NAV DME:2', type: SimVarValueType.NM }],
     ['nav2_has_dme', { name: 'NAV HAS DME:2', type: SimVarValueType.Bool }],
     ['nav2_has_nav', { name: 'NAV HAS NAV:2', type: SimVarValueType.Bool }],
     ['nav2_radial', { name: 'NAV RADIAL:2', type: SimVarValueType.Radians }],
     ['nav2_signal', { name: 'NAV SIGNAL:2', type: SimVarValueType.Number }],
     ['nav2_ident', { name: 'NAV IDENT:2', type: SimVarValueType.String }],
     ['nav2_to_from', { name: 'NAV TOFROM:2', type: SimVarValueType.Enum }],
     ['nav2_localizer', { name: 'NAV HAS LOCALIZER:2', type: SimVarValueType.Bool }],
     ['nav2_localizer_crs', { name: 'NAV LOCALIZER:2', type: SimVarValueType.Number }],
     ['nav2_glideslope', { name: 'NAV HAS GLIDE SLOPE:2', type: SimVarValueType.Bool }],
     ['nav2_gs_error', { name: 'NAV GLIDE SLOPE ERROR:2', type: SimVarValueType.Degree }],
     ['nav2_raw_gs', { name: 'NAV RAW GLIDE SLOPE:2', type: SimVarValueType.Degree }],
     ['nav2_gs_lla', { name: 'NAV GS LATLONALT:2', type: SimVarValueType.LLA }],
     ['nav2_lla', { name: 'NAV VOR LATLONALT:2', type: SimVarValueType.LLA }],
     ['nav2_magvar', { name: 'NAV MAGVAR:2', type: SimVarValueType.Number }],
     ['gps_dtk', { name: 'GPS WP DESIRED TRACK', type: SimVarValueType.Degree }],
     ['gps_xtk', { name: 'GPS WP CROSS TRK', type: SimVarValueType.NM }],
     ['gps_wp', { name: 'GPS WP NEXT ID', type: SimVarValueType.NM }],
     ['gps_wp_bearing', { name: 'GPS WP BEARING', type: SimVarValueType.Degree }],
     ['gps_wp_distance', { name: 'GPS WP DISTANCE', type: SimVarValueType.NM }],
     ['adf1_bearing', { name: 'ADF RADIAL:1', type: SimVarValueType.Radians }],
     ['adf1_signal', { name: 'ADF SIGNAL:1', type: SimVarValueType.Number }],
     ['mkr_bcn_state_simvar', { name: 'MARKER BEACON STATE', type: SimVarValueType.Number }],
     ['gps_obs_active_simvar', { name: 'GPS OBS ACTIVE', type: SimVarValueType.Bool }],
     ['gps_obs_value_simvar', { name: 'GPS OBS VALUE', type: SimVarValueType.Degree }]
 ]);
 //
 // Navigation event configurations
 //
 var NavSourceType;
 (function (NavSourceType) {
     NavSourceType[NavSourceType["Nav"] = 0] = "Nav";
     NavSourceType[NavSourceType["Gps"] = 1] = "Gps";
     NavSourceType[NavSourceType["Adf"] = 2] = "Adf";
 })(NavSourceType || (NavSourceType = {}));
 //* ENUM for VOR To/From Flag */
 var VorToFrom;
 (function (VorToFrom) {
     VorToFrom[VorToFrom["OFF"] = 0] = "OFF";
     VorToFrom[VorToFrom["TO"] = 1] = "TO";
     VorToFrom[VorToFrom["FROM"] = 2] = "FROM";
 })(VorToFrom || (VorToFrom = {}));
 /** Marker beacon signal state. */
 var MarkerBeaconState;
 (function (MarkerBeaconState) {
     MarkerBeaconState[MarkerBeaconState["Inactive"] = 0] = "Inactive";
     MarkerBeaconState[MarkerBeaconState["Outer"] = 1] = "Outer";
     MarkerBeaconState[MarkerBeaconState["Middle"] = 2] = "Middle";
     MarkerBeaconState[MarkerBeaconState["Inner"] = 3] = "Inner";
 })(MarkerBeaconState || (MarkerBeaconState = {}));
 /**
  * Encapsulation of the logic for a generic nav source.
  */
 class NavSourceBase {
     /**
      * Create a bearing pointer
      * @param id The navsourceid.
      */
     constructor(id) {
         this._ident = null;
         this._bearing = null;
         this._distance = null;
         this._obs = 0;
         this._deviation = null;
         this._toFrom = VorToFrom.OFF;
         this._glideslopeDeviation = null;
         this._glideslopeAngle = null;
         this._localizerCourse = null;
         this._magneticVariation = null;
         this._isLocalizerFrequency = null;
         this.validHandler = undefined;
         this.identHandler = undefined;
         this.brgHandler = undefined;
         this.distHandler = undefined;
         this.obsHandler = undefined;
         this.deviationHandler = undefined;
         this.toFromHandler = undefined;
         this.glideslopeDeviationHandler = undefined;
         this.glideslopeAngleHandler = undefined;
         this.localizerCourseHandler = undefined;
         this.magvarHandler = undefined;
         this.isLocalizerFrequencyHandler = undefined;
         this._valid = false;
         this._activeBrg = false;
         this._activeCdi = false;
         this._hasCdi = false;
         this._hasDme = false;
         this._hasLocalizer = false;
         this._hasGlideslope = false;
         this._signal = 0;
         this._activeForCount = 0;
         if (id.type !== null && id.type in [NavSourceType.Nav, NavSourceType.Gps]) {
             this._hasCdi = true;
         }
         this.valid = false;
         this.srcId = id;
     }
     /**
      * Do we support CDI?
      * @returns A boolean with our CDI support state.
      */
     get hasCdi() {
         return this._hasCdi;
     }
     /**
      * Set a new ident.
      * @param ident The new ident string.
      */
     set ident(ident) {
         this._ident = ident;
         if (this.valid && this.activeBrg && this.identHandler !== undefined) {
             this.identHandler(ident, this.srcId);
         }
     }
     /**
      * Get an ident.
      * @returns A string identifying the nav source.
      */
     get ident() {
         if (this._signal > 0) {
             return this._ident;
         }
         else {
             return null;
         }
     }
     /**
      * Set a new bearing.
      * @param bearing The new bearing in degrees.
      */
     set bearing(bearing) {
         if (bearing !== null) {
             bearing *= 57.2958;
             bearing = (bearing + 180) % 360;
         }
         this._bearing = bearing;
         if (this.valid && this.activeBrg && this.brgHandler !== undefined) {
             this.brgHandler(bearing, this.srcId);
         }
     }
     /**
      * Get abearing.
      * @returns Bearing to the source in degrees.
      */
     get bearing() {
         return this._bearing;
     }
     /**
      * Set a new distance
      * @param distance The distance in NM.
      */
     set distance(distance) {
         this._distance = distance;
         if (this.valid && this.activeBrg && this.distHandler !== undefined) {
             this.distHandler(this.distance, this.srcId);
         }
     }
     /**
      * Get the distance to a source..
      * @returns Distance to the source in degrees.
      */
     get distance() {
         if (this.hasDme) {
             return this._distance;
         }
         else {
             return null;
         }
     }
     /**
      * Set a new OBS
      * @param obs the new bearing in degrees
      */
     set obs(obs) {
         this._obs = obs;
         if (this.activeCdi && this.obsHandler !== undefined) {
             this.obsHandler(obs, this.srcId);
         }
     }
     /**
      * Get the OBS setting.
      * @returns OBS in degrees.
      */
     get obs() {
         return this._obs;
     }
     /**
      * Set a new deviation
      * @param deviation The new deviation in points.
      */
     set deviation(deviation) {
         this._deviation = deviation;
         if (this.activeCdi && this.deviationHandler !== undefined) {
             this.deviationHandler(deviation, this.srcId);
         }
     }
     /**
      * Get the deviation.
      * @returns The deviation in points.
      */
     get deviation() {
         return this._deviation;
     }
     /**
      * Set a new VOR to/from value
      * @param toFrom The to/from value.
      */
     set toFrom(toFrom) {
         if (this.activeCdi && this.toFromHandler !== undefined) {
             this.toFromHandler(toFrom, this.srcId);
         }
         this._toFrom = toFrom;
     }
     /**
      * Get the VOR to/from value.
      * @returns The VOR to/from value.
      */
     get toFrom() {
         return this._toFrom;
     }
     /**
      * Set whether the nav radio has a loc signal
      * @param valid If the loc exists.
      */
     set hasLocalizer(valid) {
         this._hasLocalizer = valid;
         if (!this._hasLocalizer) {
             this.localizerCourse = -1;
         }
         else if (this._hasLocalizer && this.localizerCourseHandler && this.localizerCourseHandler !== null
             && this._localizerCourse !== null && this._localizerCourse >= 0) {
             this.localizerCourseHandler(this._localizerCourse, this.srcId);
         }
     }
     /**
      * Get if the radio has a loc.
      * @returns The loc bool.
      */
     get hasLocalizer() {
         return this._hasLocalizer;
     }
     /**
      * Set a new localizerCourse
      * @param course The new localizer course.
      */
     set localizerCourse(course) {
         this._localizerCourse = course !== null ? course * (180 / Math.PI) : -1;
         if (this._localizerCourse !== -1 && this.localizerCourseHandler !== undefined) {
             this.localizerCourseHandler(this._localizerCourse, this.srcId);
         }
     }
     /**
      * Get the localizerCourse.
      * @returns The glideslopeDeviation in degrees.
      */
     get localizerCourse() {
         return this._localizerCourse;
     }
     /**
      * Set whether the nav radio has a GS signal
      * @param valid If the GS exists.
      */
     set hasGlideslope(valid) {
         this._hasGlideslope = valid;
         if (this.glideslopeDeviationHandler !== undefined) {
             this.glideslopeDeviationHandler(this.glideslopeDeviation, this.srcId);
         }
     }
     /**
      * Get if the radio has a glideslope.
      * @returns The glideslope bool.
      */
     get hasGlideslope() {
         return this._hasGlideslope;
     }
     /**
      * Set a new _glideslopeDeviation
      * @param deviation The new deviation in points.
      */
     set glideslopeDeviation(deviation) {
         this._glideslopeDeviation = deviation;
         if (this.valid && this.activeCdi && this.hasGlideslope && this.glideslopeDeviationHandler !== undefined) {
             this.glideslopeDeviationHandler(deviation, this.srcId);
         }
     }
     /**
      * Get the glideslopeDeviation.
      * @returns The glideslopeDeviation in degrees.
      */
     get glideslopeDeviation() {
         return this._glideslopeDeviation;
     }
     /**
      * Set a new _glideslopeAngle
      * @param angle The new angle in degrees.
      */
     set glideslopeAngle(angle) {
         this._glideslopeAngle = angle;
         if (this.valid && this.activeCdi && this.hasGlideslope && this.glideslopeAngleHandler !== undefined) {
             this.glideslopeAngleHandler(angle, this.srcId);
         }
     }
     /**
      * Get the glideslopeAngle.
      * @returns The glideslopeAngle in degrees.
      */
     get glideslopeAngle() {
         return this._glideslopeAngle;
     }
     /**
      * Set whether there's a valid DME signal.
      * @param hasDme Whether a nav signal is present or not.
      */
     set hasDme(hasDme) {
         this._hasDme = hasDme;
         if (this.distHandler !== undefined) {
             // If hasDme state is changing, we need to publish a new distance event.
             // The getter for this.distance handles whether there's a DME signal or not.
             this.distHandler(this.distance, this.srcId);
         }
     }
     /**
      * Get whether there's a valid DME signal.
      * @returns A boolean indicatind presence of DME.
      */
     get hasDme() {
         return this._hasDme;
     }
     /**
      * Get the nav radio magvar.
      * @returns The magvar value.
      */
     get magneticVariation() {
         return this._magneticVariation;
     }
     /**
      * Set a new nav radio magvar
      * @param magvar The new nav radio magvar.
      */
     set magneticVariation(magvar) {
         this._magneticVariation = magvar;
         if (this.valid && this.activeCdi && this.magvarHandler !== undefined) {
             this.magvarHandler(magvar, this.srcId);
         }
     }
     /**
      * Get if the frequency is for a localizer.
      * @returns a bool of whether the freq is for a localizer.
      */
     get isLocalizerFrequency() {
         return this._isLocalizerFrequency;
     }
     /**
      * Sets if a frequency is for a localizer
      * @param isLocFreq whether the freq is a localizer freq.
      */
     set isLocalizerFrequency(isLocFreq) {
         if (this.isLocalizerFrequencyHandler !== undefined) {
             this.isLocalizerFrequencyHandler(isLocFreq, this.srcId);
         }
         this._isLocalizerFrequency = isLocFreq;
     }
     /**
      * Set the nav signal strength.
      * @param signal The signal strength as a number.
      */
     set signal(signal) {
         if ((this._signal == 0) != (signal == 0)) {
             // if we gain or lose signal, we need to update our ident.
             this._signal = signal;
             if (this.identHandler !== undefined) {
                 // The getter for ident will handle whether we have signal or not.
                 this.identHandler(this.ident, this.srcId);
             }
         }
         else {
             // repaet this from above to avoid a needless temporary assigment.
             this._signal = signal;
         }
     }
     /**
      * Set validity.
      * @param valid Whether we are valid or not.
      */
     set valid(valid) {
         // TODO Make sure this matches up with new activeBrg logic
         this._valid = valid;
         this.validHandler && this.validHandler(valid, this.srcId);
         if (valid && this.activeBrg) {
             if (this.identHandler !== undefined) {
                 this.identHandler(this._ident, this.srcId);
             }
             if (this.brgHandler !== undefined) {
                 this.brgHandler(this._bearing, this.srcId);
             }
             if (this.distHandler !== undefined) {
                 this.distHandler(this._distance, this.srcId);
             }
             if (this.toFromHandler !== undefined) {
                 this.toFromHandler(this._toFrom, this.srcId);
             }
             if (this.localizerCourseHandler !== undefined) {
                 this.localizerCourseHandler(this._localizerCourse, this.srcId);
             }
             if (this.glideslopeDeviationHandler !== undefined) {
                 this.glideslopeDeviationHandler(this._glideslopeDeviation, this.srcId);
             }
             if (this.magvarHandler !== undefined) {
                 this.magvarHandler(this._magneticVariation, this.srcId);
             }
             if (this.isLocalizerFrequencyHandler !== undefined) {
                 this.isLocalizerFrequencyHandler(this._isLocalizerFrequency, this.srcId);
             }
             if (this.obsHandler !== undefined) {
                 this.obsHandler(this._obs, this.srcId);
             }
         }
         else if (this.activeBrg) {
             if (this.identHandler !== undefined) {
                 this.identHandler(null, this.srcId);
             }
             if (this.brgHandler !== undefined) {
                 this.brgHandler(null, this.srcId);
             }
             if (this.distHandler !== undefined) {
                 this.distHandler(null, this.srcId);
             }
             if (this.toFromHandler !== undefined) {
                 this.toFromHandler(this._toFrom, this.srcId);
             }
             if (this.localizerCourseHandler !== undefined) {
                 this.localizerCourseHandler(null, this.srcId);
             }
             if (this.glideslopeDeviationHandler !== undefined) {
                 this.glideslopeDeviationHandler(null, this.srcId);
             }
             if (this.magvarHandler !== undefined) {
                 this.magvarHandler(null, this.srcId);
             }
             if (this.isLocalizerFrequencyHandler !== undefined) {
                 this.isLocalizerFrequencyHandler(this._isLocalizerFrequency, this.srcId);
             }
             if (this.obsHandler !== undefined) {
                 this.obsHandler(this._obs, this.srcId);
             }
         }
     }
     /**
      * Get validity
      * @returns A boolean indicating whether this is valid
      */
     get valid() {
         return this._valid;
     }
     /**
      * Set as active for bearing information
      * @param active Whether we are active for bearing info.
      */
     set activeBrg(active) {
         if (active) {
             this._activeForCount++;
         }
         else if (this._activeForCount > 0) {
             this._activeForCount--;
         }
         this.validHandler && this.validHandler(this.valid, this.srcId);
         if (!this.activeBrg || !this.valid) {
             if (this.identHandler !== undefined) {
                 this.identHandler(null, this.srcId);
             }
             if (this.brgHandler !== undefined) {
                 this.brgHandler(null, this.srcId);
             }
             if (this.distHandler !== undefined) {
                 this.distHandler(null, this.srcId);
             }
             if (this.toFromHandler !== undefined) {
                 this.toFromHandler(this._toFrom, this.srcId);
             }
             if (this.isLocalizerFrequencyHandler !== undefined) {
                 this.isLocalizerFrequencyHandler(this._isLocalizerFrequency, this.srcId);
             }
         }
         else {
             if (this.identHandler !== undefined) {
                 this.identHandler(this.ident, this.srcId);
             }
             if (this.brgHandler !== undefined) {
                 this.brgHandler(this.bearing, this.srcId);
             }
             if (this.distHandler !== undefined) {
                 this.distHandler(this.distance, this.srcId);
             }
             if (this.toFromHandler !== undefined) {
                 this.toFromHandler(this._toFrom, this.srcId);
             }
             if (this.isLocalizerFrequencyHandler !== undefined) {
                 this.isLocalizerFrequencyHandler(this._isLocalizerFrequency, this.srcId);
             }
         }
     }
     /**
      * Are we active for bearing information?
      * @returns Our active state
      */
     get activeBrg() {
         return this._activeForCount > 0;
     }
     /**
      * Set this at the active deviation source or not
      * @param active Whether we are active for publishing deviation info.
      */
     set activeCdi(active) {
         this._activeCdi = active;
         if (active && this.deviationHandler !== undefined) {
             this.deviationHandler(this._deviation, this.srcId);
         }
     }
     /**
      * Are we active for CDI data?
      * @returns Boolean of our CDI active state
      */
     get activeCdi() {
         return this._activeCdi;
     }
 }
 /**
  * A utility for controlling and optimizing simvar subscriptions for multiple pointers.
  */
 class PooledSubscriber {
     /**
      * Create a pooled subscriber
      * @param publisher The simvar publisher to manage.
      */
     constructor(publisher) {
         this.publisher = publisher;
         this.subCount = new Map();
         for (const key of PooledSubscriber.simvarMap.keys()) {
             this.subCount.set(key, 0);
         }
     }
     /**
      * Add a subscriber to one of the tracked types
      * @param type the type to add
      */
     addSub(type) {
         const subCount = this.subCount.get(type);
         if (subCount == undefined) {
             return;
         }
         if (subCount == 0) {
             const simvars = PooledSubscriber.simvarMap.get(type);
             if (simvars) {
                 for (const simvar of simvars) {
                     this.publisher.subscribe(simvar);
                 }
             }
         }
         this.subCount.set(type, subCount + 1);
     }
     /**
      * Remove a subscriber of a tracked type
      * @param type The type to remove.
      */
     delSub(type) {
         const subCount = this.subCount.get(type);
         if (subCount == undefined) {
             return;
         }
         if (subCount == 1) {
             for (const simvar in PooledSubscriber.simvarMap.get(type)) {
                 this.publisher.unsubscribe(simvar);
             }
         }
         this.subCount.set(type, subCount - 1);
     }
 }
 PooledSubscriber.simvarMap = new Map([
     ['nav1', [
             'nav1_cdi', 'nav1_dme', 'nav1_glideslope', 'nav1_gs_error', 'nav1_has_dme', 'nav1_has_nav',
             'nav1_ident', 'nav1_localizer', 'nav1_localizer_crs', 'nav1_magvar', 'nav1_obs', 'nav1_radial',
             'nav1_raw_gs', 'nav1_signal', 'nav1_to_from'
         ]],
     ['nav2', [
             'nav2_cdi', 'nav2_dme', 'nav2_glideslope', 'nav2_gs_error', 'nav2_has_dme', 'nav2_has_nav',
             'nav2_ident', 'nav2_localizer', 'nav2_localizer_crs', 'nav2_magvar', 'nav2_obs', 'nav2_radial',
             'nav2_raw_gs', 'nav2_signal', 'nav2_to_from'
         ]],
     ['gps', ['gps_wp', 'gps_wp_bearing', 'gps_wp_distance']],
     ['adf', ['adf1_bearing', 'adf1_signal']]
 ]);
 /**
  * A convenience class for creating a navproc configuration set.
  *
  * Implementers should instantiate this and then populate the sets with the
  * HEvents that their radio sends for various actions.
  */
 class NavProcessorConfig {
     constructor() {
         this.numNav = 2;
         this.numGps = 1;
         this.numAdf = 1;
         this.initialCdiIndex = 3;
         this.courseIncEvents = new Set();
         this.courseDecEvents = new Set();
         this.additionalSources = new Array();
     }
 }
 /**
  * A publisher for navigation processor events.
  */
 class NavProcPublisher extends BasePublisher {
     /**
      * Creates a NavProcPublisher
      * @param bus The event bus to publish to
      * @param pacer An optional pacer to use to control the rate of publishing
      */
     constructor(bus, pacer) {
         super(bus, pacer);
     }
     /**
      * Publish a new CDI source selection.
      * @param source The selected NavSource.
      */
     publishCdiSelect(source) {
         this.publish('cdi_select', source, true);
     }
     /**
      * Publish a new CDI deviation
      * @param deviation The deviation
      * @param source the source for thes deviation
      */
     publishDeviation(deviation, source) {
         this.publish('cdi_deviation', { source: source, deviation: deviation });
     }
     /**
      * Publish a new OBS heading
      * @param heading The heading
      * @param source The source for this heading.
      */
     publishObsHeading(heading, source) {
         this.publish('obs_set', { source: source, heading: heading });
     }
     /**
      * Publish new validity information.
      * @param index The bearing index number to update.
      * @param valid The validity state of that berign source.
      */
     publishBrgValidity(index, valid) {
         this.publish('brg_validity', { index: index, valid: valid }, true);
     }
     /**
      * Publish a new bearing source
      * @param index The source number.
      * @param source The source name.
      */
     publishBrgSrc(index, source) {
         this.publish('brg_source', { index: index, source: source });
     }
     /**
      * Publish heading of a bearing source.
      * @param index The index number to update.
      * @param direction The direction.
      */
     publishBrgDir(index, direction) {
         this.publish('brg_direction', { index: index, direction: direction });
     }
     /**
      * Publish distance to a bearing source.
      * @param index The source number.
      * @param distance The distance in NMs.
      */
     publishBrgDist(index, distance) {
         this.publish('brg_distance', { index: index, distance: distance });
     }
     /**
      * Publish distance to a bearing source.
      * @param index The source number.
      * @param ident The ident as a string.
      * @param isLoc is this source a loc.
      */
     publishBrgIdent(index, ident, isLoc) {
         this.publish('brg_ident', { index: index, ident: ident, isLoc: isLoc });
     }
     // /**
     //  * Publish distance to a bearing source.
     //  * @param index The source number.
     //  * @param isLoc The ident as a string.
     //  */
     // public publishBrgIsLoc(index: number, isLoc: boolean | null): void {
     //     this.publish('brg_is_loc', { index: index, isLoc: isLoc });
     // }
     /**
      * Publish to/from value for a nav source.
      * @param toFrom The to/from enum value.
      * @param source The nav radio source.
      */
     publishToFrom(toFrom, source) {
         this.publish('vor_to_from', { toFrom: toFrom, source: source });
     }
     /**
      * Publish localizer value for a nav source.
      * @param localizer is the localizer data
      * @param source The nav radio source.
      */
     publishLocalizer(localizer, source) {
         this.publish('localizer', { isValid: localizer.isValid, course: localizer.course, source: source });
     }
     /**
      * Publish if the nav source is tuned to a localizer frequency.
      * @param isLoc is a bool whether or not the nav source is a loc frequency
      * @param source The nav radio source.
      */
     publishIsLocalizerFrequency(isLoc, source) {
         this.publish('is_localizer_frequency', { isLocalizer: isLoc.isLocalizer, source: source });
     }
     /**
      * Publish gliseslope value for a nav source.
      * @param glideslope is the localizer data
      * @param source The nav radio source.
      */
     publishGlideslope(glideslope, source) {
         this.publish('glideslope', { isValid: glideslope.isValid, deviation: glideslope.deviation, gsAngle: glideslope.gsAngle, source: source });
     }
     /**
      * Publish magvar value for a nav source.
      * @param variation is the magnetic variation
      * @param source The nav radio source.
      */
     publishMagvar(variation, source) {
         if (variation !== null) {
             this.publish('mag_variation', { variation: variation, source: source });
         }
     }
     /**
      * Publish marker beacon state value.
      * @param state is the marker beacon state value
      */
     publishMarkerBeacon(state) {
         this.publish('mkr_bcn_state', state);
     }
     /**
      * Publish DME state.
      * @param state is the has_dme state value
      * @param distance is the dme distance value.
      * @param source The nav radio source.
      */
     publishDmeState(state, distance, source) {
         this.publish('dme_state', { hasDme: state, dmeDistance: distance, source: source });
     }
     /**
      * Publish GPS OBS State.
      * @param state is the GPS OBS Active State
      */
     publishGpsObsState(state) {
         this.publish('gps_obs_active', state, true);
     }
     /**
      * Publish GPS OBS Value.
      * @param value is the GPS OBS heading value
      */
     publishGpsObsValue(value) {
         this.publish('gps_obs_value', value, true);
     }
 }
 /**
  * The core of tne nav processor
  */
 class NavProcessor {
     /**
      * Create a NavProcessor.
      * @param bus The event bus to publish to.
      * @param config A config object defining our radio options.
      */
     constructor(bus, config) {
         /**
          * Handle HEvents
          * @param event The hEvent name
          */
         this.eventHandler = (event) => {
             if (this.config.courseIncEvents.has(event)) {
                 this.handleCrsInc();
             }
             else if (this.config.courseDecEvents.has(event)) {
                 this.handleCrsDec();
             }
         };
         /**
          * Toggles CDI between GPS and NAV1.
          */
         this.onCdiGpsToggle = () => {
             const src = this.navSources[this.cdiSourceIdx];
             if (src.srcId.type === NavSourceType.Gps) {
                 this.switchCdiSrc();
             }
             else {
                 this.switchCdiSrc(3);
             }
         };
         this.bus = bus;
         this.config = config;
         this.publisher = new NavProcPublisher(bus);
         this.simVarPublisher = config.simVarPublisher ? config.simVarPublisher : new NavProcSimVarPublisher(this.bus);
         this.subController = new PooledSubscriber(this.simVarPublisher);
         this.hEvents = bus.getSubscriber();
         this.controlSubscriber = bus.getSubscriber();
         this.simVarSubscriber = new EventSubscriber(bus);
         this.navComSubscriber = bus.getSubscriber();
         this.simVarPublisher.subscribe('mkr_bcn_state_simvar');
         this.simVarPublisher.subscribe('gps_obs_active_simvar');
         this.simVarPublisher.subscribe('gps_obs_value_simvar');
         this.navSources = new Array();
         this.bearingSourceIdxs = [-1, -1];
         this.cdiSourceIdx = 0;
     }
     /**
      * Initialize a nav processor
      */
     init() {
         this.publisher.startPublish();
         this.simVarPublisher.startPublish();
         this.hEvents.on('hEvent').handle(this.eventHandler);
         this.controlSubscriber.on('cdi_src_switch').handle(() => {
             this.switchCdiSrc();
         });
         this.controlSubscriber.on('cdi_src_set').handle((src) => {
             if (src.type === NavSourceType.Gps) {
                 this.switchCdiSrc(3);
             }
             else if (src.type === NavSourceType.Nav) {
                 this.switchCdiSrc(src.index - 1);
             }
         });
         this.controlSubscriber.on('cdi_src_gps_toggle').handle(this.onCdiGpsToggle);
         this.controlSubscriber.on('init_cdi').handle(this.initCdi.bind(this));
         this.controlSubscriber.on('brg_src_switch').handle(this.switchBrgSrc.bind(this));
         // TODO Determine why this throttle doesn't work but does on the client end.
         this.simVarSubscriber.on('mkr_bcn_state_simvar').whenChanged().handle((state) => {
             this.publisher.publishMarkerBeacon(state);
         });
         this.simVarSubscriber.on('gps_obs_active_simvar').whenChanged().handle((state) => {
             this.publisher.publishGpsObsState(state);
         });
         this.simVarSubscriber.on('gps_obs_value_simvar').whenChangedBy(1).handle((value) => {
             this.publisher.publishGpsObsValue(value);
         });
         for (let i = 1; i <= this.config.numNav; i++) {
             const src = new NavSourceBase({ type: NavSourceType.Nav, index: i });
             src.deviationHandler = this.publisher.publishDeviation.bind(this.publisher);
             src.obsHandler = this.publisher.publishObsHeading.bind(this.publisher);
             src.distHandler = this.onBrgDistance.bind(this);
             src.brgHandler = this.onBrgDirection.bind(this);
             src.identHandler = this.onBrgIdent.bind(this);
             src.toFromHandler = this.publisher.publishToFrom.bind(this.publisher);
             src.localizerCourseHandler = this.onLocalizerCourse.bind(this);
             src.glideslopeDeviationHandler = this.onGlideslopeDeviation.bind(this);
             src.magvarHandler = this.publisher.publishMagvar.bind(this.publisher);
             src.isLocalizerFrequencyHandler = this.onIsLocalizerFrequency.bind(this);
             src.glideslopeAngleHandler = this.onGlideslopeAngle.bind(this);
             src.validHandler = this.onBrgValidity.bind(this);
             this.simVarSubscriber.on(`nav${i}_cdi`).whenChangedBy(1).handle((deviation) => {
                 src.deviation = deviation;
             });
             this.simVarSubscriber.on(`nav${i}_obs`).whenChangedBy(1).handle((obs) => {
                 src.obs = obs;
             });
             this.simVarSubscriber.on(`nav${i}_dme`).whenChangedBy(0.1).handle((distance) => {
                 src.distance = distance;
                 // TODO Fold DME logic into the distance handler.
                 this.onDme(src.hasDme, src.distance, src.srcId);
             });
             this.simVarSubscriber.on(`nav${i}_radial`).handle((bearing) => {
                 src.bearing = bearing;
             });
             this.simVarSubscriber.on(`nav${i}_ident`).whenChanged().handle((ident) => {
                 src.ident = ident;
             });
             this.simVarSubscriber.on(`nav${i}_signal`).withPrecision(0).handle((signal) => {
                 src.signal = signal;
             });
             this.simVarSubscriber.on(`nav${i}_has_nav`).whenChanged().handle((valid) => {
                 src.valid = valid == 0 ? false : true;
             });
             this.simVarSubscriber.on(`nav${i}_has_dme`).whenChanged().handle((dme) => {
                 src.hasDme = dme == 0 ? false : true;
                 // TODO Fold DME logic into the distance handler.
                 this.onDme(src.hasDme, src.distance, src.srcId);
             });
             this.simVarSubscriber.on(`nav${i}_to_from`).whenChanged().handle((value) => {
                 src.toFrom = value;
             });
             this.simVarSubscriber.on(`nav${i}_localizer`).whenChanged().handle((localizer) => {
                 src.hasLocalizer = localizer;
             });
             this.simVarSubscriber.on(`nav${i}_localizer_crs`).whenChanged().handle((locCourse) => {
                 src.localizerCourse = locCourse;
             });
             this.simVarSubscriber.on(`nav${i}_glideslope`).whenChanged().handle((gs) => {
                 src.hasGlideslope = gs;
             });
             this.simVarSubscriber.on(`nav${i}_gs_error`).whenChanged().handle((gsDev) => {
                 src.glideslopeDeviation = gsDev;
             });
             this.simVarSubscriber.on(`nav${i}_raw_gs`).whenChanged().handle((rawGs) => {
                 src.glideslopeAngle = rawGs;
             });
             this.simVarSubscriber.on(`nav${i}_magvar`).whenChanged().handle((magvar) => {
                 src.magneticVariation = magvar;
             });
             this.navComSubscriber.on('setRadioState').handle((radioState) => {
                 if (radioState.radioType === RadioType.Nav && radioState.index == i && radioState.activeFrequency) {
                     src.isLocalizerFrequency = this.frequencyIsLocalizer(radioState.activeFrequency);
                 }
             });
             this.navComSubscriber.on('setFrequency').handle((setFrequency) => {
                 if (setFrequency.radio.radioType === RadioType.Nav && setFrequency.radio.index == i
                     && setFrequency.bank == FrequencyBank.Active) {
                     src.isLocalizerFrequency = this.frequencyIsLocalizer(setFrequency.frequency);
                 }
             });
             this.navSources.push(src);
             this.subController.addSub(`nav${i}`);
         }
         // TODO Add support for multiple GPSes
         if (this.config.numGps > 0) {
             // Set the CDI source to the first GPS (which, since we're zero-indexed, is the
             // same as the number of nav radios.
             this.cdiSourceIdx = this.config.numNav;
             const src = new NavSourceBase({ type: NavSourceType.Gps, index: 1 });
             src.valid = true;
             src.deviationHandler = this.publisher.publishDeviation.bind(this.publisher);
             src.obsHandler = this.publisher.publishObsHeading.bind(this.publisher);
             src.distHandler = this.onBrgDistance.bind(this);
             src.brgHandler = this.onBrgDirection.bind(this);
             src.identHandler = this.onBrgIdent.bind(this);
             this.simVarSubscriber.on('gps_xtk').whenChangedBy(1).handle((deviation) => {
                 src.deviation = deviation;
             });
             this.simVarSubscriber.on('gps_dtk').whenChangedBy(1).handle((obs) => {
                 src.obs = obs;
             });
             this.simVarSubscriber.on('gps_wp_bearing').withPrecision(2).handle((brg) => {
                 // The nav source bearing expects to be a radial, not the bearing to the
                 // waypoint.  When we get the bearing from the GPS, we need to invert it
                 // so the NavSource knows how to handle it correctly.
                 // TODO Make bearing handling in NavSourceBase more consistent.
                 brg = (brg + Math.PI) % (2 * Math.PI);
                 src.bearing = brg;
             });
             this.navSources.push(src);
             this.subController.addSub('gps');
         }
         if (this.config.numAdf > 0) {
             const src = new NavSourceBase({ type: NavSourceType.Adf, index: 1 });
             src.valid = false;
             src.signal = 0;
             src.brgHandler = this.onBrgDirection.bind(this);
             src.identHandler = this.onBrgIdent.bind(this);
             src.validHandler = this.onBrgValidity.bind(this);
             this.simVarSubscriber.on('adf1_signal').withPrecision(0).handle((signal) => {
                 src.signal = signal;
                 if (signal > 0) {
                     if (!src.valid) {
                         src.valid = true;
                     }
                 }
                 else {
                     if (src.valid) {
                         src.valid = false;
                     }
                 }
             });
             this.navComSubscriber.on('adf1ActiveFreq').handle((freq) => {
                 if (src.ident !== freq.toFixed(1)) {
                     src.ident = freq.toFixed(1);
                 }
             });
             this.simVarSubscriber.on('adf1_bearing').withPrecision(2).handle((brg) => {
                 brg = (brg + Math.PI) % (2 * Math.PI);
                 src.bearing = brg;
             });
             this.navSources.push(src);
             this.subController.addSub('adf');
         }
         for (const source of this.config.additionalSources) {
             this.addNavSource(source);
         }
         this.cdiSourceIdx = this.config.initialCdiIndex;
         SimVar.SetSimVarValue('GPS DRIVES NAV1', SimVarValueType.Bool, this.navSources[this.cdiSourceIdx].srcId.type === NavSourceType.Gps);
     }
     /**
      * Add a custom nav source to the processor.
      * @param source The implementation of NavSourceBase to add.
      */
     addNavSource(source) {
         // TODO Add remaining handlers here for other custom nav sources.
         if (source.validHandler) {
             source.validHandler = this.onBrgValidity.bind(this);
         }
         if (source.brgHandler) {
             source.brgHandler = this.onBrgDirection.bind(this);
         }
         if (source.distHandler) {
             source.distHandler = this.onBrgDistance.bind(this);
         }
         if (source.identHandler) {
             source.identHandler = this.onBrgIdent.bind(this);
         }
         this.navSources.push(source);
     }
     /**
      * Process a CDI source change event.
      * @param index is specified if a specific cdiSourceIdx is requested
      */
     switchCdiSrc(index) {
         let src = this.navSources[this.cdiSourceIdx];
         src.activeCdi = false;
         if (index !== undefined && index < this.navSources.length && this.navSources[index].hasCdi) {
             this.cdiSourceIdx = index;
         }
         else if (index === undefined) {
             do {
                 this.cdiSourceIdx = this.cdiSourceIdx < this.navSources.length - 1 ? this.cdiSourceIdx + 1 : 0;
             } while (!this.navSources[this.cdiSourceIdx].hasCdi);
         }
         else {
             console.warn(`switchCdiSrc: Unable to set CDI Source index ${index}`);
             return;
         }
         src = this.navSources[this.cdiSourceIdx];
         src.activeCdi = true;
         this.publisher.publishCdiSelect(src.srcId);
         if (src.obs) {
             this.publisher.publishObsHeading(src.obs, src.srcId);
         }
         if (src.toFrom) {
             this.publisher.publishToFrom(src.toFrom, src.srcId);
         }
         this.publisher.publishDmeState(src.hasDme, src.distance, src.srcId);
         SimVar.SetSimVarValue('GPS DRIVES NAV1', SimVarValueType.Bool, src.srcId.type === NavSourceType.Gps);
         if (src.srcId.type === NavSourceType.Nav) {
             SimVar.SetSimVarValue('AUTOPILOT NAV SELECTED', SimVarValueType.Number, src.srcId.index);
         }
     }
     /**srcent.
      */
     initCdi() {
         const src = this.navSources[this.cdiSourceIdx];
         src.activeCdi = true;
         this.publisher.publishCdiSelect(src.srcId);
         if (src.obs) {
             this.publisher.publishObsHeading(src.obs, src.srcId);
         }
         if (src.toFrom) {
             this.publisher.publishToFrom(src.toFrom, src.srcId);
         }
         this.publisher.publishDmeState(src.hasDme, src.distance, src.srcId);
         SimVar.SetSimVarValue('GPS DRIVES NAV1', SimVarValueType.Bool, src.srcId.type === NavSourceType.Gps);
     }
     /**
      * Process a bearing source change event.
      * @param index The index of the source to change
      */
     switchBrgSrc(index) {
         index--;
         const oldSrc = this.navSources[this.bearingSourceIdxs[index]];
         if (oldSrc !== undefined) {
             oldSrc.activeBrg = false;
         }
         if (this.bearingSourceIdxs[index] == this.navSources.length - 1) {
             this.bearingSourceIdxs[index] = -1;
         }
         else {
             this.bearingSourceIdxs[index]++;
         }
         const newSrc = this.navSources[this.bearingSourceIdxs[index]];
         this.publisher.publishBrgSrc(index, newSrc !== undefined ? newSrc.srcId : null);
         if (newSrc !== undefined) {
             newSrc.activeBrg = true;
         }
         newSrc && this.publisher.publishBrgValidity(index, newSrc.valid);
         if (newSrc === undefined) {
             this.publisher.publishBrgIdent(index, null, false);
             this.publisher.publishBrgDir(index, null);
             this.publisher.publishBrgDist(index, null);
             //this.publisher.publishBrgIsLoc(index, false);
         }
     }
     /**
      * Handle a course inc event if we have a nav radio as our active CDI source.
      */
     handleCrsInc() {
         if (this.navSources[this.cdiSourceIdx].srcId.type != NavSourceType.Nav) {
             return;
         }
         switch (this.navSources[this.cdiSourceIdx].srcId.index) {
             case 1:
                 SimVar.SetSimVarValue('K:VOR1_OBI_INC', 'number', 0);
                 break;
             case 2:
                 SimVar.SetSimVarValue('K:VOR2_OBI_INC', 'number', 0);
         }
     }
     /**
      * Handle a course dec event if we have a nav radio as our active CDI source.
      */
     handleCrsDec() {
         if (this.navSources[this.cdiSourceIdx].srcId.type != NavSourceType.Nav) {
             return;
         }
         switch (this.navSources[this.cdiSourceIdx].srcId.index) {
             case 1:
                 SimVar.SetSimVarValue('K:VOR1_OBI_DEC', 'number', 0);
                 break;
             case 2:
                 SimVar.SetSimVarValue('K:VOR2_OBI_DEC', 'number', 0);
                 break;
         }
     }
     /**
      * Handle a bearing validity change.
      * @param valid The new bearing validity
      * @param source The source of
      */
     onBrgValidity(valid, source) {
         if (this.bearingSourceIdxs) {
             for (let i = 0; i < this.bearingSourceIdxs.length; i++) {
                 if (this.navSources[this.bearingSourceIdxs[i]] &&
                     this.navSources[this.bearingSourceIdxs[i]].srcId == source &&
                     !this.navSources[this.bearingSourceIdxs[i]].isLocalizerFrequency) {
                     this.publisher.publishBrgValidity(i, valid);
                 }
             }
         }
     }
     // TODO Unify the next two functions
     /**
      * Handle a bearing distance change.
      * @param distance The distance to the source.
      * @param source The nav source ID.
      */
     onBrgDistance(distance, source) {
         for (let i = 0; i < this.bearingSourceIdxs.length; i++) {
             if (this.navSources[this.bearingSourceIdxs[i]] &&
                 this.navSources[this.bearingSourceIdxs[i]].srcId == source &&
                 !this.navSources[this.bearingSourceIdxs[i]].isLocalizerFrequency) {
                 this.publisher.publishBrgDist(i, distance);
             }
         }
     }
     /**
      * Handle a bearing direction change.
      * @param direction The distance to the source.
      * @param source The nav source ID.
      */
     onBrgDirection(direction, source) {
         for (let i = 0; i < this.bearingSourceIdxs.length; i++) {
             if (this.navSources[this.bearingSourceIdxs[i]] &&
                 this.navSources[this.bearingSourceIdxs[i]].srcId == source &&
                 !this.navSources[this.bearingSourceIdxs[i]].isLocalizerFrequency) {
                 this.publisher.publishBrgDir(i, direction);
             }
         }
     }
     /**
      * Handle a bearing ident change.
      * @param ident The ident of the source.
      * @param source The nav source ID.
      */
     onBrgIdent(ident, source) {
         for (let i = 0; i < this.bearingSourceIdxs.length; i++) {
             if (this.navSources[this.bearingSourceIdxs[i]] &&
                 this.navSources[this.bearingSourceIdxs[i]].srcId == source) {
                 this.publisher.publishBrgIdent(i, ident, this.navSources[this.bearingSourceIdxs[i]].isLocalizerFrequency);
                 //this.publisher.publishBrgIsLoc(i, this.navSources[this.bearingSourceIdxs[i]].isLocalizerFrequency);
                 if (this.navSources[this.bearingSourceIdxs[i]].isLocalizerFrequency) {
                     this.publisher.publishBrgDir(i, null);
                     this.publisher.publishBrgDist(i, null);
                 }
                 else {
                     this.publisher.publishBrgSrc(i, source);
                 }
             }
         }
     }
     /**
      * Handle a localizer course change.
      * @param course The localizer course of the source.
      * @param source The nav source ID.
      */
     onLocalizerCourse(course, source) {
         for (let i = 0; i < this.navSources.length; i++) {
             if (this.navSources[i] && this.navSources[i].srcId == source && this.navSources[i].hasLocalizer && course !== null) {
                 const localizer = { isValid: true, course: course, source: source };
                 this.publisher.publishLocalizer(localizer, source);
             }
         }
     }
     /**
      * Handle a glideslope deviation change.
      * @param deviation The glideslope deviation of the source.
      * @param source The nav source ID.
      */
     onGlideslopeDeviation(deviation, source) {
         for (let i = 0; i < this.navSources.length; i++) {
             const navSource = this.navSources[i];
             if (navSource && navSource.srcId == source && deviation !== null && navSource.glideslopeAngle) {
                 const glideslope = { isValid: this.navSources[i].hasGlideslope, deviation: deviation, gsAngle: navSource.glideslopeAngle, source: source };
                 this.publisher.publishGlideslope(glideslope, source);
             }
         }
     }
     /**
      * Handle a glideslope angle change.
      * @param angle The glideslope angle of the source.
      * @param source The nav source ID.
      */
     onGlideslopeAngle(angle, source) {
         for (let i = 0; i < this.navSources.length; i++) {
             const navSource = this.navSources[i];
             if (navSource && navSource.srcId == source && navSource.deviation && angle !== null) {
                 const glideslope = { isValid: this.navSources[i].hasGlideslope, deviation: navSource.deviation, gsAngle: angle, source: source };
                 this.publisher.publishGlideslope(glideslope, source);
             }
         }
     }
     /**
      * Determine whether a set frequency is a localizer frequency.
      * @param frequency The frequency to evaluate.
      * @returns a bool true if the frequency is a loc freq.
      */
     frequencyIsLocalizer(frequency) {
         let isLoc = false;
         if (Math.floor(frequency) < 112) {
             const roundedFreq = Math.round(frequency * 100) / 100;
             const integer = Math.floor(roundedFreq);
             const remainder = roundedFreq - integer;
             const decimalValue = Math.round(remainder * 100);
             const tenthsDigit = Math.trunc(decimalValue / 10);
             if (tenthsDigit % 2 != 0) {
                 isLoc = true;
             }
         }
         return isLoc;
     }
     /**
      * Publishers whether a set frequency is a localizer frequency.
      * @param isLoc whether the freq is a loc.
      * @param source the selected nav source.
      */
     onIsLocalizerFrequency(isLoc, source) {
         if (isLoc !== null) {
             const loc = { isLocalizer: isLoc, source: source };
             this.publisher.publishIsLocalizerFrequency(loc, source);
         }
     }
     /**
      * Publishers dme distance info.  This should be replaced by a generalization of
      * BearingDistance that provides the distance to any nav source if it has DME.
      * @param hasDme whether the radio has dme.
      * @param distance is the dme distance.
      * @param source the selected nav source.
      */
     onDme(hasDme, distance, source) {
         this.publisher.publishDmeState(hasDme ? hasDme : false, distance ? distance : -1, source);
     }
     /**
      * Perform events for the update loop.
      */
     onUpdate() {
         this.simVarPublisher.onUpdate();
     }
 }
 
 /** A publisher to poll and publish nav/com simvars. */
 class NavComSimVarPublisher extends SimVarPublisher {
     /**
      * Create a NavComSimVarPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the pace of publishing
      */
     constructor(bus, pacer = undefined) {
         super(NavComSimVarPublisher.simvars, bus, pacer);
     }
 }
 NavComSimVarPublisher.simvars = new Map([
     ['nav1ActiveFreq', { name: 'NAV ACTIVE FREQUENCY:1', type: SimVarValueType.MHz }],
     ['nav1StandbyFreq', { name: 'NAV STANDBY FREQUENCY:1', type: SimVarValueType.MHz }],
     ['nav1Ident', { name: 'NAV IDENT:1', type: SimVarValueType.String }],
     ['nav2ActiveFreq', { name: 'NAV ACTIVE FREQUENCY:2', type: SimVarValueType.MHz }],
     ['nav2StandbyFreq', { name: 'NAV STANDBY FREQUENCY:2', type: SimVarValueType.MHz }],
     ['nav2Ident', { name: 'NAV IDENT:2', type: SimVarValueType.String }],
     ['com1ActiveFreq', { name: 'COM ACTIVE FREQUENCY:1', type: SimVarValueType.MHz }],
     ['com1StandbyFreq', { name: 'COM STANDBY FREQUENCY:1', type: SimVarValueType.MHz }],
     ['com2ActiveFreq', { name: 'COM ACTIVE FREQUENCY:2', type: SimVarValueType.MHz }],
     ['com2StandbyFreq', { name: 'COM STANDBY FREQUENCY:2', type: SimVarValueType.MHz }],
     ['adf1StandbyFreq', { name: 'ADF STANDBY FREQUENCY:1', type: SimVarValueType.KHz }],
     ['adf1ActiveFreq', { name: 'ADF ACTIVE FREQUENCY:1', type: SimVarValueType.KHz }]
 ]);
 
 /// <reference types="msfstypes/JS/simvar" />
 var APLockType;
 (function (APLockType) {
     APLockType[APLockType["Heading"] = 0] = "Heading";
     APLockType[APLockType["Nav"] = 1] = "Nav";
     APLockType[APLockType["Alt"] = 2] = "Alt";
     APLockType[APLockType["Bank"] = 3] = "Bank";
     APLockType[APLockType["WingLevel"] = 4] = "WingLevel";
     APLockType[APLockType["Vs"] = 5] = "Vs";
     APLockType[APLockType["Flc"] = 6] = "Flc";
     APLockType[APLockType["Pitch"] = 7] = "Pitch";
     APLockType[APLockType["Approach"] = 8] = "Approach";
     APLockType[APLockType["Backcourse"] = 9] = "Backcourse";
     APLockType[APLockType["Glideslope"] = 10] = "Glideslope";
     APLockType[APLockType["VNav"] = 11] = "VNav";
 })(APLockType || (APLockType = {}));
 /** base publisher for simvars */
 class APSimVarPublisher extends SimVarPublisher {
     /**
      * Create an APSimVarPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the pace of publishing
      */
     constructor(bus, pacer = undefined) {
         super(APSimVarPublisher.simvars, bus, pacer);
     }
 }
 APSimVarPublisher.simvars = new Map([
     // TODO extend the next two to handle multiple APs?
     ['selected_heading', { name: 'AUTOPILOT HEADING LOCK DIR:1', type: SimVarValueType.Degree }],
     ['selected_altitude', { name: 'AUTOPILOT ALTITUDE LOCK VAR:1', type: SimVarValueType.Feet }],
     ['ap_master_status', { name: 'AUTOPILOT MASTER', type: SimVarValueType.Bool }],
     ['ap_heading_lock', { name: 'AUTOPILOT HEADING LOCK', type: SimVarValueType.Bool }],
     ['ap_nav_lock', { name: 'AUTOPILOT NAV1 LOCK', type: SimVarValueType.Bool }],
     ['ap_bank_hold', { name: 'AUTOPILOT BANK HOLD', type: SimVarValueType.Bool }],
     ['ap_wing_lvl', { name: 'AUTOPILOT WING LEVELER', type: SimVarValueType.Bool }],
     ['ap_approach_hold', { name: 'AUTOPILOT APPROACH HOLD', type: SimVarValueType.Bool }],
     ['ap_backcourse_hold', { name: 'AUTOPILOT BACKCOURSE HOLD', type: SimVarValueType.Bool }],
     ['ap_vs_hold', { name: 'AUTOPILOT VERTICAL HOLD', type: SimVarValueType.Bool }],
     ['ap_flc_hold', { name: 'AUTOPILOT FLIGHT LEVEL CHANGE', type: SimVarValueType.Bool }],
     ['ap_alt_lock', { name: 'AUTOPILOT ALTITUDE LOCK', type: SimVarValueType.Bool }],
     ['ap_glideslope_hold', { name: 'AUTOPILOT GLIDESLOPE HOLD', type: SimVarValueType.Bool }],
     ['ap_pitch_hold', { name: 'AUTOPILOT PITCH HOLD', type: SimVarValueType.Bool }],
     ['vs_hold_fpm', { name: 'AUTOPILOT VERTICAL HOLD VAR:1', type: SimVarValueType.FPM }],
     ['flc_hold_knots', { name: 'AUTOPILOT AIRSPEED HOLD VAR', type: SimVarValueType.Knots }],
     ['flight_director_bank', { name: 'AUTOPILOT FLIGHT DIRECTOR BANK', type: SimVarValueType.Degree }],
     ['flight_director_pitch', { name: 'AUTOPILOT FLIGHT DIRECTOR PITCH', type: SimVarValueType.Degree }],
     ['flight_director_lock', { name: 'AUTOPILOT FLIGHT DIRECTOR ACTIVE', type: SimVarValueType.Bool }],
     ['vnav_active', { name: 'L:XMLVAR_VNAVButtonValue', type: SimVarValueType.Bool }],
     ['alt_lock', { name: 'AUTOPILOT ALTITUDE LOCK', type: SimVarValueType.Bool }],
     ['pitch_ref', { name: 'AUTOPILOT PITCH HOLD REF', type: SimVarValueType.Degree }],
     ['kap_140_simvar', { name: 'L:WT1000_AP_KAP140_INSTALLED', type: SimVarValueType.Bool }]
 ]);
 /**
  * Publishes autopilot data
  */
 class AutopilotPublisher extends BasePublisher {
     /**
      * Creates an AutopilotPublisher
      * @param bus The event bus to publish to.
      * @param pacer An optional pacer to use to control the rate of publishing.
      */
     constructor(bus, pacer) {
         super(bus, pacer);
     }
     /**
      * Publish an AP master engage event
      */
     publishMasterEngage() {
         this.publish('ap_master_engage', true);
     }
     /**
      * Publish an AP master disengage event
      */
     publishMasterDisengage() {
         this.publish('ap_master_disengage', true);
     }
     /**
      * Publish a selected alt change
      * @param newAlt The new selected altitude.
      */
     publishAltChange(newAlt) {
         this.publish('alt_select', newAlt);
     }
     /**
      * Publish a selected hdg change
      * @param newHdg The new selected heading.
      */
     publishHdgChange(newHdg) {
         this.publish('heading_select', newHdg);
     }
     /**
      * Publish a selected vs change
      * @param newVs The new selected vs.
      */
     publishVsChange(newVs) {
         this.publish('vs_hold_fpm', newVs);
     }
     /**
      * Publish a selected flc ias change
      * @param newIas The new selected ias.
      */
     publishFlcIasChange(newIas) {
         this.publish('flc_hold_knots', newIas);
     }
     /**
      * Publish a lock set event
      * @param lock The lock/hold set
      */
     publishLockSet(lock) {
         this.publish('ap_lock_set', lock);
     }
     /**
      * Publish a lock release event
      * @param lock The lock/hold released
      */
     publishLockRelease(lock) {
         this.publish('ap_lock_release', lock);
     }
     /**
      * Publish a pitch ref value.
      * @param newPitch The new commanded pitch angle.
      */
     publishPitchRefChange(newPitch) {
         this.publish('pitch_ref', newPitch);
     }
     /**
      * Publish a flight director pitch value.
      * @param newPitch The new commanded pitch angle.
      */
     publishFlightDirectorPitchChange(newPitch) {
         this.publish('flight_director_pitch', newPitch);
     }
     /**
      * Publish a flight director bank value
      * @param newBank The new commanded bank angle.
      */
     publishFlightDirectorBankChange(newBank) {
         this.publish('flight_director_bank', newBank);
     }
     /**
      * Publish an FD lock event
      * @param state is the flight director state (true = on, false = off).
      */
     publishFlightDirectorState(state) {
         this.publish('flight_director_state', state);
     }
     /**
      * Publish alt lock
      * @param locked is if it is locked.
      */
     publishAltLock(locked) {
         this.publish('alt_lock', locked);
     }
     /**
      * Publish KAP140 Installed State
      * @param state is the state of the simvar.
      */
     publishKAP140State(state) {
         this.publish('kap_140_installed', state);
     }
 }
 /**
  * Manages an autopilot system
  */
 class AutopilotInstrument {
     /**
      * Create an AutopilotInstrument
      * @param bus The event bus to publish to
      */
     constructor(bus) {
         this.bus = bus;
         // this.hEvents = this.bus.getSubscriber<HEvent>();
         this.publisher = new AutopilotPublisher(bus);
         this.simVarPublisher = new APSimVarPublisher(bus);
         this.simVarSubscriber = new EventSubscriber(bus);
         this.simVarPublisher.subscribe('ap_master_status');
         this.simVarPublisher.subscribe('selected_altitude');
         this.simVarPublisher.subscribe('selected_heading');
         this.simVarPublisher.subscribe('ap_alt_lock');
         this.simVarPublisher.subscribe('ap_bank_hold');
         this.simVarPublisher.subscribe('ap_wing_lvl');
         this.simVarPublisher.subscribe('ap_flc_hold');
         this.simVarPublisher.subscribe('ap_heading_lock');
         this.simVarPublisher.subscribe('ap_nav_lock');
         this.simVarPublisher.subscribe('ap_approach_hold');
         this.simVarPublisher.subscribe('ap_backcourse_hold');
         this.simVarPublisher.subscribe('ap_pitch_hold');
         this.simVarPublisher.subscribe('ap_glideslope_hold');
         this.simVarPublisher.subscribe('ap_vs_hold');
         this.simVarPublisher.subscribe('vs_hold_fpm');
         this.simVarPublisher.subscribe('flc_hold_knots');
         this.simVarPublisher.subscribe('flight_director_bank');
         this.simVarPublisher.subscribe('flight_director_pitch');
         this.simVarPublisher.subscribe('flight_director_lock');
         this.simVarPublisher.subscribe('vnav_active');
         this.simVarPublisher.subscribe('alt_lock');
         this.simVarPublisher.subscribe('pitch_ref');
         this.simVarPublisher.subscribe('kap_140_simvar');
     }
     /**
      * Initialize the instrument
      */
     init() {
         this.publisher.startPublish();
         this.simVarPublisher.startPublish();
         // console.log('initting autopilot');
         this.simVarSubscriber.on('ap_master_status').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishMasterEngage();
             }
             else {
                 this.publisher.publishMasterDisengage();
             }
         });
         this.simVarSubscriber.on('selected_altitude').whenChangedBy(1).handle((newAlt) => {
             this.publisher.publishAltChange(newAlt);
         });
         this.simVarSubscriber.on('selected_heading').whenChangedBy(1).handle((newHdg) => {
             this.publisher.publishHdgChange(newHdg);
         });
         this.simVarSubscriber.on('vs_hold_fpm').whenChangedBy(1).handle((newVs) => {
             this.publisher.publishVsChange(newVs);
         });
         this.simVarSubscriber.on('flc_hold_knots').whenChangedBy(1).handle((newIas) => {
             this.publisher.publishFlcIasChange(newIas);
         });
         this.simVarSubscriber.on('ap_alt_lock').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Alt);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Alt);
             }
         });
         this.simVarSubscriber.on('ap_pitch_hold').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Pitch);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Pitch);
             }
         });
         this.simVarSubscriber.on('ap_heading_lock').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Heading);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Heading);
             }
         });
         this.simVarSubscriber.on('ap_nav_lock').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Nav);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Nav);
             }
         });
         this.simVarSubscriber.on('ap_approach_hold').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Approach);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Approach);
             }
         });
         this.simVarSubscriber.on('ap_backcourse_hold').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Backcourse);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Backcourse);
             }
         });
         this.simVarSubscriber.on('ap_bank_hold').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Bank);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Bank);
             }
         });
         this.simVarSubscriber.on('ap_wing_lvl').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.WingLevel);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.WingLevel);
             }
         });
         this.simVarSubscriber.on('ap_flc_hold').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Flc);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Flc);
             }
         });
         this.simVarSubscriber.on('ap_vs_hold').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Vs);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Vs);
             }
         });
         this.simVarSubscriber.on('ap_glideslope_hold').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.Glideslope);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.Glideslope);
             }
         });
         this.simVarSubscriber.on('flight_director_bank').whenChangedBy(.25).handle((newBank) => {
             this.publisher.publishFlightDirectorBankChange(newBank);
         });
         this.simVarSubscriber.on('flight_director_pitch').whenChangedBy(.25).handle((newPitch) => {
             this.publisher.publishFlightDirectorPitchChange(newPitch);
         });
         this.simVarSubscriber.on('flight_director_lock').whenChanged().handle((state) => {
             this.publisher.publishFlightDirectorState(state);
         });
         this.simVarSubscriber.on('vnav_active').whenChangedBy(1).handle((engaged) => {
             if (engaged) {
                 this.publisher.publishLockSet(APLockType.VNav);
             }
             else {
                 this.publisher.publishLockRelease(APLockType.VNav);
             }
         });
         this.simVarSubscriber.on('alt_lock').whenChanged().handle((lock) => {
             this.publisher.publishAltLock(lock);
         });
         this.simVarSubscriber.on('pitch_ref').whenChangedBy(0.1).handle((val) => {
             this.publisher.publishPitchRefChange(val);
         });
         this.simVarSubscriber.on('kap_140_simvar').whenChanged().handle((val) => {
             this.publisher.publishKAP140State(val);
         });
     }
     /** update our publishers */
     onUpdate() {
         this.simVarPublisher.onUpdate();
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A publisher for Engine information.
  */
 class EISPublisher extends SimVarPublisher {
     /**
      * Create an EISPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the rate of publishing
      */
     constructor(bus, pacer = undefined) {
         const simvars = new Map(EISPublisher.simvars);
         // add engine-indexed simvars
         const engineCount = SimVar.GetSimVarValue('NUMBER OF ENGINES', SimVarValueType.Number);
         for (let i = 1; i <= engineCount; i++) {
             simvars.set(`fuel_flow_${i}`, { name: `ENG FUEL FLOW GPH:${i}`, type: SimVarValueType.GPH });
         }
         super(simvars, bus, pacer);
         this.engineCount = engineCount;
     }
     /** @inheritdoc */
     onUpdate() {
         super.onUpdate();
         if (this.subscribed.has('fuel_flow_total')) {
             let totalFuelFlow = 0;
             for (let i = 1; i <= this.engineCount; i++) {
                 totalFuelFlow += SimVar.GetSimVarValue(`ENG FUEL FLOW GPH:${i}`, SimVarValueType.GPH);
             }
             this.publish('fuel_flow_total', totalFuelFlow);
         }
     }
 }
 EISPublisher.simvars = new Map([
     ['rpm_1', { name: 'GENERAL ENG RPM:1', type: SimVarValueType.RPM }],
     ['recip_ff_1', { name: 'RECIP ENG FUEL FLOW:1', type: SimVarValueType.PPH }],
     ['oil_press_1', { name: 'ENG OIL PRESSURE:1', type: SimVarValueType.PSI }],
     ['oil_temp_1', { name: 'ENG OIL TEMPERATURE:1', type: SimVarValueType.Farenheit }],
     ['egt_1', { name: 'ENG EXHAUST GAS TEMPERATURE:1', type: SimVarValueType.Farenheit }],
     ['vac', { name: 'SUCTION PRESSURE', type: SimVarValueType.InHG }],
     ['fuel_total', { name: 'FUEL TOTAL QUANTITY', type: SimVarValueType.GAL }],
     ['fuel_left', { name: 'FUEL LEFT QUANTITY', type: SimVarValueType.GAL }],
     ['fuel_right', { name: 'FUEL RIGHT QUANTITY', type: SimVarValueType.GAL }],
     ['eng_hours_1', { name: 'GENERAL ENG ELAPSED TIME:1', type: SimVarValueType.Hours }],
     ['elec_bus_main_v', { name: 'ELECTRICAL MAIN BUS VOLTAGE', type: SimVarValueType.Volts }],
     ['elec_bus_main_a', { name: 'ELECTRICAL MAIN BUS AMPS', type: SimVarValueType.Amps }],
     ['elec_bus_avionics_v', { name: 'ELECTRICAL AVIONICS BUS VOLTAGE', type: SimVarValueType.Volts }],
     ['elec_bus_avionics_a', { name: 'ELECTRICAL AVIONICS BUS AMPS', type: SimVarValueType.Amps }],
     ['elec_bus_genalt_1_v', { name: 'ELECTRICAL GENALT BUS VOLTAGE:1', type: SimVarValueType.Volts }],
     ['elec_bus_genalt_1_a', { name: 'ELECTRICAL GENALT BUS AMPS:1', type: SimVarValueType.Amps }],
     ['elec_bat_a', { name: 'ELECTRICAL BATTERY LOAD', type: SimVarValueType.Amps }],
     ['elec_bat_v', { name: 'ELECTRICAL BATTERY VOLTAGE', type: SimVarValueType.Volts }]
 ]);
 
 /**
  * A publisher of clock events.
  */
 class ClockPublisher extends BasePublisher {
     // eslint-disable-next-line jsdoc/require-jsdoc
     onUpdate() {
         this.publish('realTime', Date.now());
         this.publish('simTime', ClockPublisher.absoluteTimeToUNIXTime(SimVar.GetSimVarValue('E:ABSOLUTE TIME', 'seconds')));
     }
     /**
      * Converts the sim's absolute time to a UNIX timestamp. The sim's absolute time value is equivalent to a .NET
      * DateTime.Ticks value (epoch = 00:00:00 01 Jan 0001).
      * @param absoluteTime an absolute time value, in units of seconds.
      * @returns the UNIX timestamp corresponding to the absolute time value.
      */
     static absoluteTimeToUNIXTime(absoluteTime) {
         return (absoluteTime - 62135596800) * 1000;
     }
 }
 /**
  * A clock which keeps track of real-world and sim time.
  */
 class Clock {
     /**
      * Constructor.
      * @param bus The event bus to use to publish events from this clock.
      */
     constructor(bus) {
         this.publisher = new ClockPublisher(bus);
     }
     /**
      * Initializes this clock.
      */
     init() {
         this.publisher.startPublish();
     }
     /**
      * Updates this clock.
      */
     onUpdate() {
         this.publisher.onUpdate();
     }
 }
 
 /** Transponder modes. */
 var XPDRMode;
 (function (XPDRMode) {
     XPDRMode[XPDRMode["OFF"] = 0] = "OFF";
     XPDRMode[XPDRMode["STBY"] = 1] = "STBY";
     XPDRMode[XPDRMode["TEST"] = 2] = "TEST";
     XPDRMode[XPDRMode["ON"] = 3] = "ON";
     XPDRMode[XPDRMode["ALT"] = 4] = "ALT";
     XPDRMode[XPDRMode["GROUND"] = 5] = "GROUND";
 })(XPDRMode || (XPDRMode = {}));
 /** A publiher to poll transponder simvars. */
 class XPDRSimVarPublisher extends SimVarPublisher {
     /**
      * Create an XPDRSimVarPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the pace of publishing
      */
     constructor(bus, pacer = undefined) {
         super(XPDRSimVarPublisher.simvars, bus, pacer);
     }
 }
 XPDRSimVarPublisher.simvars = new Map([
     ['xpdrMode1', { name: 'TRANSPONDER STATE:1', type: SimVarValueType.Number }],
     ['xpdrCode1', { name: 'TRANSPONDER CODE:1', type: SimVarValueType.Number }],
     ['xpdrIdent', { name: 'TRANSPONDER IDENT:1', type: SimVarValueType.Bool }]
 ]);
 
 new GeoPoint(0, 0);
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A publisher for electrical information.
  */
 class ElectricalPublisher extends SimVarPublisher {
     /**
      * Create an ElectricalPublisher
      * @param bus The EventBus to publish to
      * @param pacer An optional pacer to use to control the rate of publishing
      */
     constructor(bus, pacer = undefined) {
         super(ElectricalPublisher.simvars, bus, pacer);
     }
     /** @inheritdoc */
     onUpdate() {
         super.onUpdate();
         if (this.av1BusLogic && this.subscribed.has('elec_av1_bus')) {
             this.publish('elec_av1_bus', this.av1BusLogic.getValue() !== 0);
         }
         if (this.av2BusLogic && this.subscribed.has('elec_av2_bus')) {
             this.publish('elec_av2_bus', this.av2BusLogic.getValue() !== 0);
         }
     }
     /**
      * Sets the logic element to use for the avionics 1 bus.
      * @param logicElement The logic element to use.
      */
     setAv1Bus(logicElement) {
         this.av1BusLogic = logicElement;
     }
     /**
      * Sets the logic element to use for the avionics 2 bus.
      * @param logicElement The logic element to use.
      */
     setAv2Bus(logicElement) {
         this.av2BusLogic = logicElement;
     }
 }
 ElectricalPublisher.simvars = new Map([
     ['elec_master_battery', { name: 'ELECTRICAL MASTER BATTERY', type: SimVarValueType.Bool }],
     ['elec_circuit_avionics_on', { name: 'CIRCUIT AVIONICS ON', type: SimVarValueType.Bool }],
     ['elec_circuit_navcom1_on', { name: 'CIRCUIT NAVCOM1 ON', type: SimVarValueType.Bool }],
     ['elec_circuit_navcom2_on', { name: 'CIRCUIT NAVCOM2 ON', type: SimVarValueType.Bool }],
     ['elec_circuit_navcom3_on', { name: 'CIRCUIT NAVCOM3 ON', type: SimVarValueType.Bool }]
 ]);
 
 /**
  * The transition type to which a flight path vector belongs.
  */
 var FlightPathVectorFlags;
 (function (FlightPathVectorFlags) {
     FlightPathVectorFlags[FlightPathVectorFlags["None"] = 0] = "None";
     FlightPathVectorFlags[FlightPathVectorFlags["TurnToCourse"] = 1] = "TurnToCourse";
     FlightPathVectorFlags[FlightPathVectorFlags["Arc"] = 2] = "Arc";
     FlightPathVectorFlags[FlightPathVectorFlags["HoldEntry"] = 4] = "HoldEntry";
     FlightPathVectorFlags[FlightPathVectorFlags["HoldLeg"] = 8] = "HoldLeg";
     FlightPathVectorFlags[FlightPathVectorFlags["CourseReversal"] = 16] = "CourseReversal";
     FlightPathVectorFlags[FlightPathVectorFlags["LegToLegTurn"] = 32] = "LegToLegTurn";
     FlightPathVectorFlags[FlightPathVectorFlags["AnticipatedTurn"] = 64] = "AnticipatedTurn";
 })(FlightPathVectorFlags || (FlightPathVectorFlags = {}));
 /**
  * The details of procedures selected in the flight plan.
  */
 class ProcedureDetails {
     constructor() {
         /** The origin runway object, consisting of the index of the origin runway
          * in the origin runway information and the direction */
         this.originRunway = undefined;
         /** The index of the departure in the origin airport information. */
         this.departureIndex = -1;
         /** The index of the departure transition in the origin airport departure information. */
         this.departureTransitionIndex = -1;
         /** The index of the selected runway in the original airport departure information. */
         this.departureRunwayIndex = -1;
         /** The index of the arrival in the destination airport information. */
         this.arrivalIndex = -1;
         /** The index of the arrival transition in the destination airport arrival information. */
         this.arrivalTransitionIndex = -1;
         /** The index of the selected runway transition at the destination airport arrival information. */
         this.arrivalRunwayTransitionIndex = -1;
         /** The index of the apporach in the destination airport information.*/
         this.approachIndex = -1;
         /** The index of the approach transition in the destination airport approach information.*/
         this.approachTransitionIndex = -1;
         /**
          * The destination runway object, consisting of the index of the destination runway
          * in the destination runway information and the direction
          */
         this.destinationRunway = undefined;
     }
 }
 /**
  * A prototype for signalling application-specific type metadata for plan segments.
  */
 var FlightPlanSegmentType;
 (function (FlightPlanSegmentType) {
     FlightPlanSegmentType["Origin"] = "Origin";
     FlightPlanSegmentType["Departure"] = "Departure";
     FlightPlanSegmentType["Enroute"] = "Enroute";
     FlightPlanSegmentType["Arrival"] = "Arrival";
     FlightPlanSegmentType["Approach"] = "Approach";
     FlightPlanSegmentType["Destination"] = "Destination";
     FlightPlanSegmentType["MissedApproach"] = "MissedApproach";
     FlightPlanSegmentType["RandomDirectTo"] = "RandomDirectTo";
 })(FlightPlanSegmentType || (FlightPlanSegmentType = {}));
 /**
  * A segment of a flight plan.
  */
 class FlightPlanSegment {
     /**
      * Creates a new FlightPlanSegment.
      * @param segmentIndex The index of the segment within the flight plan.
      * @param offset The leg offset within the original flight plan that
      * the segment starts at.
      * @param legs The legs in the flight plan segment.
      * @param segmentType The type of segment this is.
      * @param airway The airway associated with this segment, if any.
      */
     constructor(segmentIndex, offset, legs, segmentType = FlightPlanSegmentType.Enroute, airway) {
         this.segmentIndex = segmentIndex;
         this.offset = offset;
         this.legs = legs;
         this.segmentType = segmentType;
         this.airway = airway;
     }
 }
 /** An empty flight plan segment. */
 FlightPlanSegment.Empty = new FlightPlanSegment(-1, -1, []);
 /**
  * Bitflags describing a leg definition.
  */
 var LegDefinitionFlags;
 (function (LegDefinitionFlags) {
     LegDefinitionFlags[LegDefinitionFlags["None"] = 0] = "None";
     LegDefinitionFlags[LegDefinitionFlags["DirectTo"] = 1] = "DirectTo";
     LegDefinitionFlags[LegDefinitionFlags["MissedApproach"] = 2] = "MissedApproach";
     LegDefinitionFlags[LegDefinitionFlags["Obs"] = 4] = "Obs";
     LegDefinitionFlags[LegDefinitionFlags["VectorsToFinal"] = 8] = "VectorsToFinal";
 })(LegDefinitionFlags || (LegDefinitionFlags = {}));
 
 /**
  * Utility class for working with flight path calculations.
  */
 class FlightPathUtils {
     /**
      * Creates an empty arc vector.
      * @returns An empty arc vector.
      */
     static createEmptyCircleVector() {
         return {
             vectorType: 'circle',
             flags: FlightPathVectorFlags.None,
             radius: 0,
             centerX: 1,
             centerY: 0,
             centerZ: 0,
             startLat: 0,
             startLon: 0,
             endLat: 0,
             endLon: 0,
             distance: 0
         };
     }
     /**
      * Sets the parameters of a circle vector.
      * @param vector The circle vector to set.
      * @param circle The GeoCircle defining the vector's path.
      * @param start The start of the vector.
      * @param end The end of the vector.
      * @param flags The flags to set on the vector.
      * @returns The circle vector, after its parameters have been set.
      */
     static setCircleVector(vector, circle, start, end, flags) {
         vector.flags = flags;
         vector.radius = circle.radius;
         vector.centerX = circle.center[0];
         vector.centerY = circle.center[1];
         vector.centerZ = circle.center[2];
         vector.distance = UnitType.GA_RADIAN.convertTo(circle.distanceAlong(start, end, Math.PI), UnitType.METER);
         start instanceof Float64Array && (start = FlightPathUtils.geoPointCache[0].setFromCartesian(start));
         end instanceof Float64Array && (end = FlightPathUtils.geoPointCache[1].setFromCartesian(end));
         vector.startLat = start.lat;
         vector.startLon = start.lon;
         vector.endLat = end.lat;
         vector.endLon = end.lon;
         return vector;
     }
     /**
      * Checks whether a circle vector describes a great-circle path.
      * @param vector A flight path circle vector.
      * @returns Whether the vector describes a great-circle path.
      */
     static isVectorGreatCircle(vector) {
         return vector.radius === Math.PI / 2;
     }
     /**
      * Sets the parameters of a GeoCircle from a flight path circle vector.
      * @param vector A flight path circle vector.
      * @param out The GeoCircle to set.
      * @returns The GeoCircle, after its parameters have been set.
      */
     static setGeoCircleFromVector(vector, out) {
         return out.set(Vec3Math.set(vector.centerX, vector.centerY, vector.centerZ, FlightPathUtils.vec3Cache[0]), vector.radius);
     }
     /**
      * Gets the initial true course bearing of a flight path vector.
      * @param vector A flight path vector.
      * @returns The initial true course bearing of the vector, or undefined if one could not be calculated.
      */
     static getVectorInitialCourse(vector) {
         return FlightPathUtils.setGeoCircleFromVector(vector, FlightPathUtils.geoCircleCache[0]).bearingAt(FlightPathUtils.geoPointCache[0].set(vector.startLat, vector.startLon), Math.PI);
     }
     /**
      * Gets the final true course bearing of a flight path vector.
      * @param vector A flight path vector.
      * @returns The final true course bearing of the vector, or undefined if one could not be calculated.
      */
     static getVectorFinalCourse(vector) {
         return FlightPathUtils.setGeoCircleFromVector(vector, FlightPathUtils.geoCircleCache[0]).bearingAt(FlightPathUtils.geoPointCache[0].set(vector.endLat, vector.endLon), Math.PI);
     }
     /**
      * Gets the final position of a calculated leg.
      * @param legCalc A set of leg calculations.
      * @param out The GeoPoint object to which to write the result.
      * @returns the final position of the leg, or undefined if one could not be obtained.
      */
     static getLegFinalPosition(legCalc, out) {
         if (legCalc.endLat !== undefined && legCalc.endLon !== undefined) {
             return out.set(legCalc.endLat, legCalc.endLon);
         }
         return undefined;
     }
     /**
      * Gets the final course of a calculated leg.
      * @param legCalc A set of leg calculations.
      * @returns the final course of the leg, or undefined if one could not be obtained.
      */
     static getLegFinalCourse(legCalc) {
         if (legCalc.flightPath.length > 0) {
             const vector = legCalc.flightPath[legCalc.flightPath.length - 1];
             return this.getVectorFinalCourse(vector);
         }
         return undefined;
     }
     /**
      * Gets the circle describing the path of a turn.
      * @param center The center of the turn.
      * @param radius The radius of the turn, in great-arc radians.
      * @param turnDirection The direction of the turn.
      * @param out A GeoCircle object to which to write the result.
      * @returns The circle describing the path of the turn.
      */
     static getTurnCircle(center, radius, turnDirection, out) {
         out.set(center, radius);
         if (turnDirection === 'right') {
             out.set(Vec3Math.multScalar(out.center, -1, FlightPathUtils.vec3Cache[0]), Math.PI - out.radius);
         }
         return out;
     }
     /**
      * Gets the direction of a turn described by a circle.
      * @param circle The geo circle describing the turn.
      * @returns The direction of the turn described by the circle.
      */
     static getTurnDirectionFromCircle(circle) {
         return circle.radius > Math.PI / 2 ? 'right' : 'left';
     }
     /**
      * Gets the radius of a turn described by a circle.
      * @param circle The geo circle describing the turn.
      * @returns The radius of the turn described by the circle, in great-arc radians.
      */
     static getTurnRadiusFromCircle(circle) {
         return Math.min(circle.radius, Math.PI - circle.radius);
     }
     /**
      * Gets the center of a turn described by a circle.
      * @param circle The geo circle describing the turn.
      * @param out A GeoPoint or 3D vector object to which to write the result.
      * @returns The center of a turn described by the circle.
      */
     static getTurnCenterFromCircle(circle, out) {
         return (circle.radius > Math.PI / 2
             ? out instanceof Float64Array
                 ? Vec3Math.multScalar(circle.center, -1, out)
                 : out.setFromCartesian(-circle.center[0], -circle.center[1], -circle.center[2])
             : out instanceof Float64Array
                 ? Vec3Math.copy(circle.center, out)
                 : out.setFromCartesian(circle.center));
     }
     /**
      * Gets the signed distance along an arc from a defined start point to a query point. The start, query, and end
      * points will be projected onto the arc's parent circle if they do not already lie on it. A negative distance
      * indicates that the query point lies somewhere before the start of the arc but after the point on the arc's parent
      * circle that is diametrically opposed to the midpoint of the arc.
      * @param circle The arc's parent circle.
      * @param start The start point of the arc.
      * @param end The end point of the arc.
      * @param pos The query point.
      * @param tolerance The error tolerance, in great-arc radians, when checking if `start` and `query` are equal.
      * Defaults to `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns The signed distance along the arc from the start point to the query point, in great-arc radians.
      */
     static getAlongArcSignedDistance(circle, start, end, pos, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         const posAngularDistance = circle.angleAlong(start, pos, Math.PI);
         if (Math.min(posAngularDistance, 2 * Math.PI - posAngularDistance) <= tolerance) {
             return 0;
         }
         const endAngularDistance = circle.angleAlong(start, end, Math.PI);
         return circle.arcLength((posAngularDistance - (endAngularDistance / 2) + Math.PI) % (2 * Math.PI) - Math.PI + endAngularDistance / 2);
     }
     /**
      * Gets the normalized distance along an arc from a defined start point to a query point. The start, query, and end
      * points will be projected onto the arc's parent circle if they do not already lie on it. The distance is normalized
      * such that 1 equals the arc length from the start point to the end point. A negative distance indicates that the
      * query point lies somewhere before the start of the arc but after the point on the arc's parent circle that is
      * diametrically opposed to the midpoint of the arc.
      * @param circle The arc's parent circle.
      * @param start The start point of the arc.
      * @param end The end point of the arc.
      * @param pos The query point.
      * @param tolerance The error tolerance, in great-arc radians, when checking if `start` and `query` are equal.
      * Defaults to `GeoCircle.ANGULAR_TOLERANCE` if not specified.
      * @returns The normalized distance along the arc from the start point to the query point.
      */
     static getAlongArcNormalizedDistance(circle, start, end, pos, tolerance = GeoCircle.ANGULAR_TOLERANCE) {
         const posAngularDistance = circle.angleAlong(start, pos, Math.PI);
         if (Math.min(posAngularDistance, 2 * Math.PI - posAngularDistance) <= tolerance) {
             return 0;
         }
         const endAngularDistance = circle.angleAlong(start, end, Math.PI);
         if (Math.min(endAngularDistance, 2 * Math.PI - endAngularDistance) <= tolerance) {
             return posAngularDistance >= Math.PI ? -Infinity : Infinity;
         }
         return ((posAngularDistance - (endAngularDistance / 2) + Math.PI) % (2 * Math.PI) - Math.PI) / endAngularDistance + 0.5;
     }
     /**
      * Resolves the ingress to egress vectors for a set of flight plan leg calculations. This operation will populate the
      * `ingressToEgress` array with a sequence of vectors connecting the ingress transition to the egress transition
      * while following the flight path defined by the vectors in the `flightPath` array.
      * @param legCalc A set of flight plan leg calculations.
      * @returns The flight plan leg calculations, after the ingress to egress vectors have been resolved.
      */
 //PM Modif
     // static resolveIngressToEgress(legCalc) {
     //     var _a, _b, _c, _d;
     //     const vectors = legCalc.ingressToEgress;
     //     let vectorIndex = 0;
     //     let flightPathVectorIndex = Math.max(0, legCalc.ingressJoinIndex);
     //     const lastIngressVector = legCalc.ingress[legCalc.ingress.length - 1];
     //     const ingressJoinVector = legCalc.flightPath[legCalc.ingressJoinIndex];
     //     const firstEgressVector = legCalc.egress[0];
     //     const egressJoinVector = legCalc.flightPath[legCalc.egressJoinIndex];
     //     if (lastIngressVector && ingressJoinVector) {
     //         const ingressEnd = FlightPathUtils.geoPointCache[0].set(lastIngressVector.endLat, lastIngressVector.endLon);
     //         const vectorEnd = legCalc.ingressJoinIndex === legCalc.egressJoinIndex && firstEgressVector
     //             ? FlightPathUtils.geoPointCache[1].set(firstEgressVector.startLat, firstEgressVector.startLon)
     //             : FlightPathUtils.geoPointCache[1].set(ingressJoinVector.endLat, ingressJoinVector.endLon);
     //         if (!ingressEnd.equals(vectorEnd)) {
     //             const ingressJoinVectorCircle = FlightPathUtils.setGeoCircleFromVector(ingressJoinVector, FlightPathUtils.geoCircleCache[0]);
     //             FlightPathUtils.setCircleVector((_a = vectors[vectorIndex]) !== null && _a !== void 0 ? _a : (vectors[vectorIndex] = FlightPathUtils.createEmptyCircleVector()), ingressJoinVectorCircle, ingressEnd, vectorEnd, ingressJoinVector.flags);
     //             vectorIndex++;
     //         }
     //         flightPathVectorIndex++;
     //     }
     //     const end = Math.min(legCalc.flightPath.length, legCalc.egressJoinIndex < 0 ? Infinity : legCalc.egressJoinIndex);
     //     for (let i = flightPathVectorIndex; i < end; i++) {
     //         Object.assign((_b = vectors[vectorIndex]) !== null && _b !== void 0 ? _b : (vectors[vectorIndex] = FlightPathUtils.createEmptyCircleVector()), legCalc.flightPath[i]);
     //         vectorIndex++;
     //         flightPathVectorIndex++;
     //     }
     //     if (flightPathVectorIndex === legCalc.egressJoinIndex && egressJoinVector) {
     //         if (firstEgressVector) {
     //             const egressStart = FlightPathUtils.geoPointCache[0].set(firstEgressVector.startLat, firstEgressVector.startLon);
     //             const egressJoinVectorStart = FlightPathUtils.geoPointCache[1].set(egressJoinVector.startLat, egressJoinVector.startLon);
     //             if (!egressStart.equals(egressJoinVectorStart)) {
     //                 const egressJoinVectorCircle = FlightPathUtils.setGeoCircleFromVector(egressJoinVector, FlightPathUtils.geoCircleCache[0]);
     //                 FlightPathUtils.setCircleVector((_c = vectors[vectorIndex]) !== null && _c !== void 0 ? _c : (vectors[vectorIndex] = FlightPathUtils.createEmptyCircleVector()), egressJoinVectorCircle, egressJoinVectorStart, egressStart, egressJoinVector.flags);
     //                 vectorIndex++;
     //             }
     //         }
     //         else {
     //             Object.assign((_d = vectors[vectorIndex]) !== null && _d !== void 0 ? _d : (vectors[vectorIndex] = FlightPathUtils.createEmptyCircleVector()), egressJoinVector);
     //             vectorIndex++;
     //         }
     //     }
     //     vectors.length = vectorIndex;
     //     return legCalc;
     // }
     static resolveIngressToEgress(legCalc) {
         var _a, _b, _c, _d, _e, _f;
         var _g, _h, _j, _k, _l, _m;
         const vectors = legCalc.ingressToEgress;
         let vectorIndex = 0;
         let flightPathVectorIndex = Math.max(0, legCalc.ingressJoinIndex);
         const lastIngressVector = legCalc.ingress[legCalc.ingress.length - 1];
         const ingressJoinVector = legCalc.flightPath[legCalc.ingressJoinIndex];
         const firstEgressVector = legCalc.egress[0];
         const egressJoinVector = legCalc.flightPath[legCalc.egressJoinIndex];
         if (lastIngressVector && ingressJoinVector) {
             // Check if the last ingress vector joins the base flight path before the end of a vector. If so, we need to
             // replace the base flight path vector the ingress joins with a shortened version starting where the ingress
             // ends.
             const ingressEnd = FlightPathUtils.geoPointCache[0].set(lastIngressVector.endLat, lastIngressVector.endLon);
             const ingressJoinVectorStart = FlightPathUtils.geoPointCache[1].set(ingressJoinVector.startLat, ingressJoinVector.startLon);
             const ingressJoinVectorEnd = legCalc.ingressJoinIndex === legCalc.egressJoinIndex && firstEgressVector
                 ? FlightPathUtils.geoPointCache[2].set(firstEgressVector.startLat, firstEgressVector.startLon)
                 : FlightPathUtils.geoPointCache[2].set(ingressJoinVector.endLat, ingressJoinVector.endLon);
             const ingressJoinVectorCircle = FlightPathUtils.setGeoCircleFromVector(ingressJoinVector, FlightPathUtils.geoCircleCache[0]);
             const ingressEndAlongVectorDistance = FlightPathUtils.getAlongArcNormalizedDistance(ingressJoinVectorCircle, ingressJoinVectorStart, ingressJoinVectorEnd, ingressEnd);
             const normalizedTolerance = GeoCircle.ANGULAR_TOLERANCE / UnitType.METER.convertTo(ingressJoinVector.distance, UnitType.GA_RADIAN);
             if (ingressEndAlongVectorDistance < 1 - normalizedTolerance) {
                 // Ingress joins the base flight path before the end of the joined vector.
                 if (ingressEndAlongVectorDistance > normalizedTolerance) {
                     // Ingress joins the base flight path after the start of the joined vector.
                     ingressJoinVectorCircle.closest(ingressEnd, ingressEnd);
                     FlightPathUtils.setCircleVector((_a = vectors[_g = vectorIndex++]) !== null && _a !== void 0 ? _a : (vectors[_g] = FlightPathUtils.createEmptyCircleVector()), ingressJoinVectorCircle, ingressEnd, ingressJoinVectorEnd, ingressJoinVector.flags);
                 }
                 else {
                     // Ingress joins the base flight path at or before the start of the joined vector.
                     Object.assign((_b = vectors[_h = vectorIndex++]) !== null && _b !== void 0 ? _b : (vectors[_h] = FlightPathUtils.createEmptyCircleVector()), ingressJoinVector);
                 }
             }
             flightPathVectorIndex++;
         }
         const end = Math.min(legCalc.flightPath.length, legCalc.egressJoinIndex < 0 ? Infinity : legCalc.egressJoinIndex);
         for (let i = flightPathVectorIndex; i < end; i++) {
             Object.assign((_c = vectors[_j = vectorIndex++]) !== null && _c !== void 0 ? _c : (vectors[_j] = FlightPathUtils.createEmptyCircleVector()), legCalc.flightPath[i]);
             flightPathVectorIndex++;
         }
         if (flightPathVectorIndex === legCalc.egressJoinIndex && egressJoinVector) {
             if (firstEgressVector) {
                 // Check if the first egress vector joins the base flight path in after the start of a vector. If so, we need
                 // to replace the base flight path vector the egress joins with a shortened version starting where the egress
                 // starts.
                 const egressStart = FlightPathUtils.geoPointCache[0].set(firstEgressVector.startLat, firstEgressVector.startLon);
                 const egressJoinVectorStart = FlightPathUtils.geoPointCache[1].set(egressJoinVector.startLat, egressJoinVector.startLon);
                 const egressJoinVectorEnd = FlightPathUtils.geoPointCache[2].set(egressJoinVector.endLat, egressJoinVector.endLon);
                 const egressJoinVectorCircle = FlightPathUtils.setGeoCircleFromVector(egressJoinVector, FlightPathUtils.geoCircleCache[0]);
                 const egressStartAlongVectorDistance = FlightPathUtils.getAlongArcNormalizedDistance(egressJoinVectorCircle, egressJoinVectorStart, egressJoinVectorEnd, egressStart);
                 const normalizedTolerance = GeoCircle.ANGULAR_TOLERANCE / UnitType.METER.convertTo(egressJoinVector.distance, UnitType.GA_RADIAN);
                 if (egressStartAlongVectorDistance > normalizedTolerance) {
                     // Egress joins the base flight path after the start of the joined vector.
                     if (egressStartAlongVectorDistance < 1 - normalizedTolerance) {
                         // Egress joins the base flight path before the end of the joined vector.
                         egressJoinVectorCircle.closest(egressStart, egressStart);
                         FlightPathUtils.setCircleVector((_d = vectors[_k = vectorIndex++]) !== null && _d !== void 0 ? _d : (vectors[_k] = FlightPathUtils.createEmptyCircleVector()), egressJoinVectorCircle, egressJoinVectorStart, egressStart, egressJoinVector.flags);
                     }
                     else {
                         // Egress joins the base flight path at or after the end of the joined vector.
                         Object.assign((_e = vectors[_l = vectorIndex++]) !== null && _e !== void 0 ? _e : (vectors[_l] = FlightPathUtils.createEmptyCircleVector()), egressJoinVector);
                     }
                 }
             }
             else {
                 // There is no egress, but there is a base flight path vector flagged as the vector with which the egress
                 // joins. This is technically an invalid state, but we can easily just treat this as a regular "no-egress"
                 // case and copy the entire egress join vector into the resolved vectors array.
                 Object.assign((_f = vectors[_m = vectorIndex++]) !== null && _f !== void 0 ? _f : (vectors[_m] = FlightPathUtils.createEmptyCircleVector()), egressJoinVector);
             }
         }
         vectors.length = vectorIndex;
         return legCalc;
     }
 }
 //PM Modif
 // FlightPathUtils.vec3Cache = [new Float64Array(3)];
 // FlightPathUtils.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0)];
 // FlightPathUtils.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 FlightPathUtils.vec3Cache = [new Float64Array(3), new Float64Array(3)];
 FlightPathUtils.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
 FlightPathUtils.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 
 /* eslint-disable @typescript-eslint/no-non-null-assertion */
 /**
  * Builds circle vectors.
  */
 class CircleVectorBuilder {
     // eslint-disable-next-line jsdoc/require-jsdoc
     build(vectors, index, ...args) {
         if (args[0] instanceof GeoCircle) {
             this.setFromCircle(vectors, index, ...args);
         }
         else {
             this.setFromPoints(vectors, index, ...args);
         }
         return 1;
     }
     /**
      * Sets the parameters for a circle vector in a flight path vector sequence. If a circle vector does not exist at the
      * specified index in the sequence, a new one will be created.
      * @param vectors A flight path vector sequence.
      * @param index The index in the sequence at which to set the circle vector.
      * @param direction The direction of the circle.
      * @param radius The radius of the circle, in meters.
      * @param center The center of the circle.
      * @param start The start point.
      * @param end The end point.
      * @param flags The flags to set on the vector. Defaults to none (0).
      * @returns The set circle vector.
      */
     setFromPoints(vectors, index, direction, radius, center, start, end, flags = 0) {
         const circle = FlightPathUtils.getTurnCircle(center, UnitType.METER.convertTo(radius, UnitType.GA_RADIAN), direction, CircleVectorBuilder.geoCircleCache[0]);
         return this.setFromCircle(vectors, index, circle, start, end, flags);
     }
     /**
      * Sets the parameters for a circle vector in a flight path vector sequence. If a circle vector does not exist at the
      * specified index in the sequence, a new one will be created.
      * @param vectors A flight path vector sequence.
      * @param index The index in the sequence at which to set the circle vector.
      * @param circle The circle which defines the vector path.
      * @param start The start point.
      * @param end The end point.
      * @param flags The flags to set on the vector. Defaults to none (0).
      * @returns The set circle vector.
      */
     setFromCircle(vectors, index, circle, start, end, flags = 0) {
         var _a;
         const vector = (((_a = vectors[index]) === null || _a === void 0 ? void 0 : _a.vectorType) === 'circle' ? vectors[index] : (vectors[index] = FlightPathUtils.createEmptyCircleVector()));
         return FlightPathUtils.setCircleVector(vector, circle, start, end, flags);
     }
 }
 CircleVectorBuilder.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0)];
 CircleVectorBuilder.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 /**
  * Builds great-circle paths between defined start and end points.
  */
 class GreatCircleBuilder {
     constructor() {
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     build(vectors, index, start, pathArg, endArg, flags) {
         if (pathArg instanceof GeoCircle) {
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             return this.buildFromPath(vectors, index, start, pathArg, endArg, flags);
         }
         else {
             return this.buildFromEndpoints(vectors, index, start, pathArg, endArg, flags);
         }
     }
     /**
      * Builds a sequence of vectors representing the shortest great-circle path between two points.
      * @param vectors The flight path vector sequence to which to add the vectors.
      * @param index The index in the sequence at which to add the vectors.
      * @param start The start point.
      * @param end The end point.
      * @param initialCourse The initial true course bearing. Used to define a unique great-circle path when `start` and
      * `end` are antipodal.
      * @param flags The flags to set on the vector. Defaults to none (0).
      * @returns The number of vectors added to the sequence.
      * @throws Error if `start` and `end` are antipodal and `initialCourse` is undefined.
      */
     buildFromEndpoints(vectors, index, start, end, initialCourse, flags) {
         const startPoint = start instanceof Float64Array
             ? GreatCircleBuilder.geoPointCache[0].setFromCartesian(start)
             : GreatCircleBuilder.geoPointCache[0].set(start);
         const endPoint = end instanceof Float64Array
             ? GreatCircleBuilder.geoPointCache[1].setFromCartesian(end)
             : GreatCircleBuilder.geoPointCache[1].set(end);
         const distance = startPoint.distance(endPoint);
         const path = GreatCircleBuilder.geoCircleCache[0];
         if (distance >= Math.PI - GeoPoint.EQUALITY_TOLERANCE) {
             if (initialCourse === undefined) {
                 throw new Error('GreatCircleVectorBuilder: cannot build a unique direct track from antipodal endpoints.');
             }
             else {
                 path.setAsGreatCircle(start, initialCourse);
             }
         }
         else {
             path.setAsGreatCircle(start, end);
         }
         return this.buildFromPath(vectors, index, start, path, end, flags);
     }
     /**
      * Builds a sequence of vectors representing a great-circle path from a start point to either a defined endpoint
      * or a distance offset.
      * @param vectors The flight path vector sequence to which to add the vectors.
      * @param index The index in the sequence at which to add the vectors.
      * @param start The start point.
      * @param path The great-circle path.
      * @param endArg The end point or distance offset.
      * @param flags The flags to set on the vector. Defaults to none (0).
      * @returns The number of vectors added to the sequence.
      * @throws Error if `path` is not a great circle.
      */
     buildFromPath(vectors, index, start, path, endArg, flags = 0) {
         if (!path.isGreatCircle()) {
             throw new Error(`GreatCircleVectorBuilder: expected GeoCircle radius of pi / 2; instead was ${path.radius}`);
         }
         const end = typeof endArg === 'number'
             ? path.offsetDistanceAlong(start, UnitType.METER.convertTo(endArg, UnitType.GA_RADIAN), GreatCircleBuilder.vec3Cache[0], Math.PI)
             : endArg;
         return this.circleVectorBuilder.build(vectors, index, path, start, end, flags);
     }
 }
 GreatCircleBuilder.vec3Cache = [new Float64Array(3)];
 GreatCircleBuilder.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
 GreatCircleBuilder.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 /**
  * Builds constant-radius turns toward specified course bearings.
  */
 class TurnToCourseBuilder {
     constructor() {
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     /**
      * Adds a turn from a defined start point and initial course to a specific final course to a flight path vector
      * sequence.
      * @param vectors The flight path vector sequence to which to add the turn.
      * @param index The index in the sequence at which to add the turn.
      * @param start The start point of the turn.
      * @param radius The radius of the turn, in meters.
      * @param direction The direction of the turn.
      * @param fromCourse The initial true course at the start of the turn.
      * @param toCourse The final true course at the end of the turn.
      * @param flags The flags to set on the turn vector. Defaults to the `TurnToCourse` flag.
      * @returns The number of vectors added to the sequence.
      */
     build(vectors, index, start, radius, direction, fromCourse, toCourse, flags = FlightPathVectorFlags.TurnToCourse) {
         if (start instanceof Float64Array) {
             start = TurnToCourseBuilder.geoPointCache[0].setFromCartesian(start);
         }
         const radiusRad = UnitType.METER.convertTo(radius, UnitType.GA_RADIAN);
         const turnCenterPoint = TurnToCourseBuilder.geoPointCache[1].set(start).offset(fromCourse + (direction === 'left' ? -90 : 90), radiusRad);
         const turnStartBearing = turnCenterPoint.bearingTo(start);
         const turnEndBearing = NavMath.normalizeHeading(turnStartBearing + (toCourse - fromCourse));
         const turnEndPoint = turnCenterPoint.offset(turnEndBearing, radiusRad, TurnToCourseBuilder.geoPointCache[2]);
         return this.circleVectorBuilder.build(vectors, index, direction, radius, turnCenterPoint, start, turnEndPoint, flags);
     }
 }
 TurnToCourseBuilder.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
 /**
  * Builds great-circle paths to intercept other geo circles.
  */
 class CircleInterceptBuilder {
     constructor() {
         this.greatCircleBuilder = new GreatCircleBuilder();
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     build(vectors, index, start, pathArg, circle, flags = 0) {
         if (circle.includes(start)) {
             return 0;
         }
         let startPath;
         if (pathArg instanceof GeoCircle) {
             if (!pathArg.includes(start)) {
                 throw new Error('CircleInterceptBuilder: the starting point does not lie on the starting path.');
             }
             startPath = pathArg;
         }
         else {
             startPath = CircleInterceptBuilder.geoCircleCache[0].setAsGreatCircle(start, pathArg);
         }
         const intersections = CircleInterceptBuilder.intersectionCache;
         const numIntersections = startPath.intersection(circle, intersections);
         if (numIntersections === 0) {
             return 0;
         }
         const endVec = intersections[(numIntersections === 1 || circle.encircles(start)) ? 0 : 1];
         return startPath.isGreatCircle()
             ? this.greatCircleBuilder.build(vectors, index, start, startPath, endVec, flags)
             : this.circleVectorBuilder.build(vectors, index, startPath, start, endVec, flags);
     }
 }
 CircleInterceptBuilder.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 CircleInterceptBuilder.intersectionCache = [new Float64Array(3), new Float64Array(3)];
 /**
  * Builds constant-radius turns to join great-circle paths.
  */
 class TurnToJoinGreatCircleBuilder {
     constructor() {
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     build(vectors, index, start, startPathArg, endPath, radius, flags = FlightPathVectorFlags.TurnToCourse) {
         if (!endPath.isGreatCircle()) {
             throw new Error(`TurnToJoinPathBuilder: expected GeoCircle radius of pi / 2; instead was ${endPath.radius}`);
         }
         let startPath;
         if (startPathArg instanceof GeoCircle) {
             if (!startPathArg.isGreatCircle()) {
                 throw new Error(`TurnToJoinPathBuilder: expected GeoCircle radius of pi / 2; instead was ${startPathArg.radius}`);
             }
             else if (!startPathArg.includes(start)) {
                 throw new Error('TurnToJoinPathBuilder: the starting point does not lie on the starting path.');
             }
             startPath = startPathArg;
         }
         else {
             startPath = TurnToJoinGreatCircleBuilder.geoCircleCache[0].setAsGreatCircle(start, startPathArg);
         }
         if (!(start instanceof Float64Array)) {
             start = GeoPoint.sphericalToCartesian(start, TurnToJoinGreatCircleBuilder.vec3Cache[0]);
         }
         const turnDirection = endPath.encircles(start) ? 'left' : 'right';
         const radiusRad = turnDirection === 'left'
             ? UnitType.METER.convertTo(radius, UnitType.GA_RADIAN)
             : Math.PI - UnitType.METER.convertTo(radius, UnitType.GA_RADIAN);
         const turnStartToCenterNormal = Vec3Math.cross(start, startPath.center, TurnToJoinGreatCircleBuilder.vec3Cache[1]);
         const turnStartToCenterPath = TurnToJoinGreatCircleBuilder.geoCircleCache[1].set(turnStartToCenterNormal, Math.PI / 2);
         const turnCenter = turnStartToCenterPath.offsetDistanceAlong(start, radiusRad, TurnToJoinGreatCircleBuilder.vec3Cache[2]);
         const turnCircle = TurnToJoinGreatCircleBuilder.geoCircleCache[1].set(turnCenter, radiusRad);
         const end = turnCircle.closest(Vec3Math.multScalar(endPath.center, turnDirection === 'left' ? -1 : 1, TurnToJoinGreatCircleBuilder.vec3Cache[3]), TurnToJoinGreatCircleBuilder.vec3Cache[1]);
         return this.circleVectorBuilder.build(vectors, index, turnCircle, start, end, flags);
     }
 }
 TurnToJoinGreatCircleBuilder.vec3Cache = [new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3)];
 TurnToJoinGreatCircleBuilder.geoCircleCache = [new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0)];
 /**
  * Builds paths to connect two geo circles.
  */
 class ConnectCirclesBuilder {
     constructor() {
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     /**
      * Builds a sequence of vectors representing a path which consists of a single geo circle which connects two other
      * circles and optionally paths to link the connecting circle with a start point on the from circle and an end point
      * on the to circle.
      * @param vectors The flight path vector sequence to which to add the vectors.
      * @param index The index in the sequence at which to add the vectors.
      * @param fromCircle The circle from which to add the connecting circle.
      * @param toCircle The circle to which to add the connecting circle.
      * @param radius The radius, in meters, of the circle to join the two circles. If not defined, defaults to pi / 2
      * times the radius of the Earth (and therefore the connecting circle will be a great circle).
      * @param from The starting point along `fromCircle`. If not defined, this will be assumed to be equal to the
      * point where the connecting circle meets `fromCircle`.
      * @param to The ending point along `toCircle`. If not defined, this will be assumed to be equal to the point where
      * the connecting circle meets `toCircle`.
      * @param fromCircleVectorFlags The flags to set on the vector along `fromCircle`. Defaults to none (0).
      * @param toCircleVectorFlags The flags to set on the vector along the `toCircle`. Defaults to none (0).
      * @param connectVectorFlags The flags to set on the vector connecting `fromCircle` to `toCircle`. Defaults to none
      * (0).
      * @returns The number of vectors added to the sequence.
      */
     build(vectors, index, fromCircle, toCircle, radius, from, to, fromCircleVectorFlags = 0, toCircleVectorFlags = 0, connectVectorFlags = 0) {
         if (radius === 0) {
             return 0;
         }
         const angle = Math.acos(Vec3Math.dot(fromCircle.center, toCircle.center));
         if ((angle <= GeoPoint.EQUALITY_TOLERANCE && fromCircle.radius === toCircle.radius)
             || (Math.PI - angle <= GeoPoint.EQUALITY_TOLERANCE && Math.PI - fromCircle.radius - toCircle.radius <= GeoPoint.EQUALITY_TOLERANCE)) {
             return 0;
         }
         if (from && !(from instanceof Float64Array)) {
             from = GeoPoint.sphericalToCartesian(from, ConnectCirclesBuilder.vec3Cache[0]);
         }
         if (to && !(to instanceof Float64Array)) {
             to = GeoPoint.sphericalToCartesian(to, ConnectCirclesBuilder.vec3Cache[1]);
         }
         const radiusRad = Math.min(Math.PI / 2, radius ? UnitType.METER.convertTo(radius, UnitType.GA_RADIAN) : Infinity);
         const joinCircle = this.findCircleToJoinCircles(fromCircle, toCircle, radiusRad, ConnectCirclesBuilder.geoCircleCache[0], from, to);
         if (!joinCircle) {
             return 0;
         }
         let vectorIndex = index;
         const joinStart = joinCircle.closest(FlightPathUtils.getTurnCenterFromCircle(fromCircle, ConnectCirclesBuilder.vec3Cache[2]), ConnectCirclesBuilder.vec3Cache[2]);
         const joinEnd = joinCircle.closest(FlightPathUtils.getTurnCenterFromCircle(toCircle, ConnectCirclesBuilder.vec3Cache[3]), ConnectCirclesBuilder.vec3Cache[3]);
         if (from && Math.acos(Vec3Math.dot(from, joinStart)) > GeoPoint.EQUALITY_TOLERANCE) {
             vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, fromCircle, from, joinStart, fromCircleVectorFlags);
         }
         vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, joinCircle, joinStart, joinEnd, connectVectorFlags);
         if (to && Math.acos(Vec3Math.dot(to, joinEnd)) > GeoPoint.EQUALITY_TOLERANCE) {
             vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, toCircle, joinEnd, to, toCircleVectorFlags);
         }
         return vectorIndex - index;
     }
     /**
      * Finds a GeoCircle which connects (is tangent to) two other circles.
      * @param fromCircle The circle at the beginning of the connecting circle.
      * @param toCircle The circle at the end of the connecting circle.
      * @param radius The desired radius of the connecting circle, in great-arc radians.
      * @param out A GeoCircle object to which to write the result.
      * @param from The starting point along `fromCircle`. If not defined, this will be assumed to be equal to the
      * point where the connecting circle meets `fromCircle`.
      * @param to The ending point along `toCircle`. If not defined, this will be assumed to be equal to the point where
      * the connecting circle meets `toCircle`.
      * @returns a GeoCircle which connects the two circles, or null if one could not be found.
      */
     findCircleToJoinCircles(fromCircle, toCircle, radius, out, from, to) {
         /*
          * Theory: the locus of all centers of circle of radius r tangent to circle with center C and radius R is
          * equivalent to the set of circles S(C) with center C and positive radius |r +/- R|. If we further restrict the
          * set of tangent circles to those where both the original and tangent circle run in the same direction at the
          * tangent point, the locus of centers can be further reduced to the single circle Sd(C) with center C and
          * positive radius |r - R|. Therefore, to find the centers of the circles of radius r connecting the circles C1 and
          * C2, we need only find the intersections of Sd(C1) and Sd(C2).
          */
         const solutions = [];
         const intersections = ConnectCirclesBuilder.intersectionCache;
         const leftTurnRadius = radius;
         let fromCircleOffsetRadius = Math.abs(leftTurnRadius - fromCircle.radius);
         let toCircleOffsetRadius = Math.abs(leftTurnRadius - toCircle.radius);
         let fromCircleOffset = ConnectCirclesBuilder.geoCircleCache[1].set(fromCircle.center, fromCircleOffsetRadius);
         let toCircleOffset = ConnectCirclesBuilder.geoCircleCache[2].set(toCircle.center, toCircleOffsetRadius);
         const numLeftTurnSolutions = fromCircleOffset.intersection(toCircleOffset, intersections);
         if (numLeftTurnSolutions === 1) {
             solutions.push(ConnectCirclesBuilder.geoCircleCache[1].set(intersections[0], leftTurnRadius));
         }
         else if (numLeftTurnSolutions === 2) {
             solutions.push(ConnectCirclesBuilder.geoCircleCache[1].set(intersections[0], leftTurnRadius));
             solutions.push(ConnectCirclesBuilder.geoCircleCache[2].set(intersections[1], leftTurnRadius));
         }
         if (radius !== Math.PI / 2) {
             const rightTurnRadius = Math.PI - radius;
             fromCircleOffsetRadius = Math.abs(rightTurnRadius - fromCircle.radius);
             toCircleOffsetRadius = Math.abs(rightTurnRadius - toCircle.radius);
             fromCircleOffset = ConnectCirclesBuilder.geoCircleCache[3].set(fromCircle.center, fromCircleOffsetRadius);
             toCircleOffset = ConnectCirclesBuilder.geoCircleCache[4].set(toCircle.center, toCircleOffsetRadius);
             const numRightTurnSolutions = fromCircleOffset.intersection(toCircleOffset, intersections);
             if (numRightTurnSolutions === 1) {
                 solutions.push(ConnectCirclesBuilder.geoCircleCache[3].set(intersections[0], rightTurnRadius));
             }
             else if (numRightTurnSolutions === 2) {
                 solutions.push(ConnectCirclesBuilder.geoCircleCache[3].set(intersections[0], rightTurnRadius));
                 solutions.push(ConnectCirclesBuilder.geoCircleCache[4].set(intersections[1], rightTurnRadius));
             }
         }
         if (solutions.length === 0) {
             return null;
         }
         else if (solutions.length === 1) {
             return out.set(solutions[0].center, solutions[0].radius);
         }
         else {
             // choose the solution that results in the shortest path from fromVec to toVec
             let circle = solutions[0];
             let minDistance = this.calculateJoinCirclesPathDistance(fromCircle, toCircle, solutions[0], from, to);
             for (let i = 1; i < solutions.length; i++) {
                 const distance = this.calculateJoinCirclesPathDistance(fromCircle, toCircle, solutions[i], from, to);
                 if (distance < minDistance) {
                     circle = solutions[i];
                     minDistance = distance;
                 }
             }
             return out.set(circle.center, circle.radius);
         }
     }
     /**
      * Calculates the total distance along the joining path between two circles.
      * @param fromCircle The circle at the beginning of the connecting circle.
      * @param toCircle The circle at the end of the connecting circle.
      * @param joinCircle The connecting circle.
      * @param from The starting point along `fromCircle`. If not defined, this will be assumed to be equal to the
      * point where the connecting circle meets `fromCircle`.
      * @param to The ending point along `toCircle`. If not defined, this will be assumed to be equal to the point where
      * the connecting circle meets `toCircle`.
      * @returns the total distance along the joining path, in great-arc radians.
      */
     calculateJoinCirclesPathDistance(fromCircle, toCircle, joinCircle, from, to) {
         let distance = 0;
         const joinStartVec = joinCircle.closest(FlightPathUtils.getTurnCenterFromCircle(fromCircle, ConnectCirclesBuilder.vec3Cache[6]), ConnectCirclesBuilder.vec3Cache[6]);
         const joinEndVec = joinCircle.closest(FlightPathUtils.getTurnCenterFromCircle(toCircle, ConnectCirclesBuilder.vec3Cache[7]), ConnectCirclesBuilder.vec3Cache[7]);
         if (from) {
             distance += fromCircle.distanceAlong(from, joinStartVec, Math.PI);
         }
         distance += joinCircle.distanceAlong(joinStartVec, joinEndVec, Math.PI);
         if (to) {
             distance += toCircle.distanceAlong(joinEndVec, to, Math.PI);
         }
         return distance;
     }
 }
 ConnectCirclesBuilder.vec3Cache = [
     new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3),
     new Float64Array(3), new Float64Array(3), new Float64Array(3)
 ];
 ConnectCirclesBuilder.geoCircleCache = [
     new GeoCircle(new Float64Array(3), 0),
     new GeoCircle(new Float64Array(3), 0),
     new GeoCircle(new Float64Array(3), 0),
     new GeoCircle(new Float64Array(3), 0),
     new GeoCircle(new Float64Array(3), 0)
 ];
 ConnectCirclesBuilder.intersectionCache = [new Float64Array(3), new Float64Array(3)];
 /**
  * Builds paths connecting initial great circle paths to final great circle paths via a turn starting at the start
  * point and a turn ending at the end point, connected by a great-circle path.
  */
 class TurnToJoinGreatCircleAtPointBuilder {
     constructor() {
         this.connectCirclesBuilder = new ConnectCirclesBuilder();
     }
     /**
      * Builds a sequence of vectors representing a path from a defined start point and initial course which turns and
      * connects with another turn via a great-circle path to terminate at a defined end point and final course.
      * @param vectors The flight path vector sequence to which to add the vectors.
      * @param index The index in the sequence at which to add the vectors.
      * @param start The start point.
      * @param startPath The great-circle path defining the initial course.
      * @param startTurnRadius The radius of the initial turn, in meters.
      * @param startTurnDirection The direction of the initial turn.
      * @param end The end point.
      * @param endPath The great-circle path defining the final course.
      * @param endTurnRadius The radius of the final turn, in meters.
      * @param endTurnDirection The direction of the final turn.
      * @param startTurnVectorFlags The flags to set on the initial turn vector. Defaults to none (0).
      * @param endTurnVectorFlags The flags to set on the final turn vector. Defaults to none (0).
      * @param connectVectorFlags The flags to set on the vector along the great-circle path connecting the turns.
      * Defaults to none (0).
      * @returns The number of vectors added to the sequence.
      */
     build(vectors, index, start, startPath, startTurnRadius, startTurnDirection, end, endPath, endTurnRadius, endTurnDirection, startTurnVectorFlags = 0, endTurnVectorFlags = 0, connectVectorFlags = 0) {
         if (!(start instanceof Float64Array)) {
             start = GeoPoint.sphericalToCartesian(start, TurnToJoinGreatCircleAtPointBuilder.vec3Cache[0]);
         }
         if (!(end instanceof Float64Array)) {
             end = GeoPoint.sphericalToCartesian(end, TurnToJoinGreatCircleAtPointBuilder.vec3Cache[1]);
         }
         const startTurnRadiusRad = UnitType.METER.convertTo(startTurnRadius, UnitType.GA_RADIAN);
         const startTurnOffsetPath = TurnToJoinGreatCircleAtPointBuilder.geoCircleCache[0].setAsGreatCircle(start, startPath.center);
         const startTurnCircleRadiusRad = startTurnDirection === 'left' ? startTurnRadiusRad : Math.PI - startTurnRadiusRad;
         const startTurnCircleCenter = startTurnOffsetPath.offsetDistanceAlong(start, startTurnCircleRadiusRad, TurnToJoinGreatCircleAtPointBuilder.vec3Cache[3]);
         const startTurnCircle = TurnToJoinGreatCircleAtPointBuilder.geoCircleCache[0].set(startTurnCircleCenter, startTurnCircleRadiusRad);
         const endTurnRadiusRad = UnitType.METER.convertTo(endTurnRadius, UnitType.GA_RADIAN);
         const endTurnOffsetPath = TurnToJoinGreatCircleAtPointBuilder.geoCircleCache[1].setAsGreatCircle(end, endPath.center);
         const endTurnCircleRadiusRad = endTurnDirection === 'left' ? endTurnRadiusRad : Math.PI - endTurnRadiusRad;
         const endTurnCircleCenter = endTurnOffsetPath.offsetDistanceAlong(end, endTurnCircleRadiusRad, TurnToJoinGreatCircleAtPointBuilder.vec3Cache[3]);
         const endTurnCircle = TurnToJoinGreatCircleAtPointBuilder.geoCircleCache[1].set(endTurnCircleCenter, endTurnCircleRadiusRad);
         return this.connectCirclesBuilder.build(vectors, index, startTurnCircle, endTurnCircle, undefined, start, end, startTurnVectorFlags, endTurnVectorFlags, connectVectorFlags);
     }
 }
 TurnToJoinGreatCircleAtPointBuilder.vec3Cache = [new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3)];
 TurnToJoinGreatCircleAtPointBuilder.geoCircleCache = [new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0)];
 /**
  * Builds paths connecting initial great-circle paths to final great-circle paths terminating at defined end points.
  */
 class JoinGreatCircleToPointBuilder {
     constructor() {
         this.circleVectorBuilder = new CircleVectorBuilder();
         this.greatCircleBuilder = new GreatCircleBuilder();
         this.connectCirclesBuilder = new ConnectCirclesBuilder();
         this.turnToJoinGreatCircleBuilder = new TurnToJoinGreatCircleBuilder();
         this.turnToJoinGreatCircleAtPointBuilder = new TurnToJoinGreatCircleAtPointBuilder();
     }
     /**
      * Builds a sequence of vectors representing a path from a defined start point and initial course which turns and
      * joins a great-circle path which terminates at a defined end point.
      * @param vectors The flight path vector sequence to which to add the vectors.
      * @param index The index in the sequence at which to add the vectors.
      * @param start The start point.
      * @param startPath The great-circle path defining the initial course.
      * @param end The end point.
      * @param endPath The great-circle path defining the final course.
      * @param desiredTurnDirection The desired initial turn direction. If not defined, the most efficient turn direction
      * that satisfies the constraints will be chosen.
      * @param minTurnRadius The minimum turn radius, in meters. Defaults to 0.
      * @param preferSingleTurn Whether to prefer flight path solutions that consist of a single constant-radius turn
      * from the initial to final course. False by default.
      * @param intersection The point of intersection between the start and end paths closest to the start point. If
      * not defined, it will be calculated.
      * @param flags The flags to set on the vectors. Defaults to none (0).
      * @param includeTurnToCourseFlag Whether to include the `TurnToCourse` flag on the turn vectors. True by default.
      * @returns The number of vectors added to the sequence.
      */
     build(vectors, index, start, startPath, end, endPath, desiredTurnDirection, minTurnRadius, preferSingleTurn = false, intersection, flags = 0, includeTurnToCourseFlag = true) {
         let vectorIndex = index;
         if (!(start instanceof Float64Array)) {
             start = GeoPoint.sphericalToCartesian(start, JoinGreatCircleToPointBuilder.vec3Cache[0]);
         }
         if (!(end instanceof Float64Array)) {
             end = GeoPoint.sphericalToCartesian(end, JoinGreatCircleToPointBuilder.vec3Cache[1]);
         }
         if (!intersection) {
             const intersections = JoinGreatCircleToPointBuilder.intersectionCache;
             const solutionCount = startPath.intersection(endPath, intersections);
             if (solutionCount === 0) {
                 return 0;
             }
             // choose the intersection closest to the start point.
             intersection = Vec3Math.copy(Vec3Math.dot(intersections[0], start) > 0
                 ? intersections[0]
                 : intersections[1], JoinGreatCircleToPointBuilder.vec3Cache[2]);
         }
         const turnFlags = flags | (includeTurnToCourseFlag ? FlightPathVectorFlags.TurnToCourse : 0);
         // Calculate the relative directions of the start point, intersection point, and end point.
         const intersectionToStartDot = Vec3Math.dot(Vec3Math.cross(startPath.center, intersection, JoinGreatCircleToPointBuilder.vec3Cache[3]), start);
         // positive -> start point lies after the intersection (with respect to the direction of start path)
         const intersectionToStartSign = intersectionToStartDot < -GeoPoint.EQUALITY_TOLERANCE ? -1
             : intersectionToStartDot > GeoPoint.EQUALITY_TOLERANCE ? 1 : 0;
         const intersectionToEndDot = Vec3Math.dot(Vec3Math.cross(endPath.center, intersection, JoinGreatCircleToPointBuilder.vec3Cache[3]), end);
         // positive -> end point lies after the intersection (with respect to the direction of end path)
         const intersectionToEndSign = intersectionToEndDot < -GeoPoint.EQUALITY_TOLERANCE ? -1
             : intersectionToEndDot > GeoPoint.EQUALITY_TOLERANCE ? 1 : 0;
         const isEndForwardOfIntersection = intersectionToEndSign > 0;
         minTurnRadius !== null && minTurnRadius !== void 0 ? minTurnRadius : (minTurnRadius = 0);
         const minTurnRadiusRad = UnitType.METER.convertTo(minTurnRadius, UnitType.GA_RADIAN);
         const pathDot = Vec3Math.dot(startPath.center, endPath.center);
         const theta = Math.acos(-pathDot);
         const tanHalfTheta = Math.tan(theta / 2);
         // along-track distance from the intersection point to the start/end of the minimum-radius turn from start path to end path
         let minD = Math.asin(Math.tan(minTurnRadiusRad) / tanHalfTheta);
         if (isNaN(minD)) {
             // Turn radius is too large for any turn to join the start and end paths
             minD = Infinity;
         }
         const intersectionStartDistance = intersectionToStartSign === 0 ? 0 : Math.acos(Vec3Math.dot(intersection, start));
         const intersectionEndDistance = intersectionToEndSign === 0 ? 0 : Math.acos(Vec3Math.dot(intersection, end));
         const intersectionStartOffset = intersectionToStartSign * intersectionStartDistance;
         const intersectionEndOffset = intersectionToEndSign * intersectionEndDistance;
         const towardEndPointTurnDirection = startPath.encircles(end, false) ? 'left' : 'right';
         let needCalculateTwoTurnPath = false;
         let needCalculateOneTurnPath = false;
         if (isEndForwardOfIntersection) {
             if (desiredTurnDirection === undefined || desiredTurnDirection === towardEndPointTurnDirection) {
                 const isStartPastRequiredTurnStart = intersectionStartOffset > -minD;
                 const isEndBeforeRequiredTurnEnd = intersectionEndOffset < minD;
                 if (isStartPastRequiredTurnStart || isEndBeforeRequiredTurnEnd) {
                     // The minimum turn radius is too large to intercept the final path before the end point
                     needCalculateTwoTurnPath = !preferSingleTurn || desiredTurnDirection === towardEndPointTurnDirection;
                     needCalculateOneTurnPath = !needCalculateTwoTurnPath;
                 }
                 else {
                     // Make a single constant-radius turn either starting at the start point, or ending at the end point,
                     // depending on which is closer to the intersection point.
                     const turnRadius = UnitType.GA_RADIAN.convertTo(Math.atan(tanHalfTheta * Math.sin(Math.min(intersectionStartDistance, intersectionEndDistance))), UnitType.METER);
                     if (intersectionStartDistance <= intersectionEndDistance) {
                         // start turn at start point
                         vectorIndex += this.turnToJoinGreatCircleBuilder.build(vectors, vectorIndex, start, startPath, endPath, turnRadius, turnFlags);
                         if (intersectionEndDistance - intersectionStartDistance > GeoPoint.EQUALITY_TOLERANCE) {
                             const turnEnd = endPath.offsetDistanceAlong(intersection, intersectionStartDistance, JoinGreatCircleToPointBuilder.vec3Cache[3], Math.PI);
                             vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, turnEnd, endPath, end, flags);
                         }
                     }
                     else {
                         // end turn at end point
                         let turnStart = start;
                         if (intersectionStartDistance - intersectionEndDistance > GeoPoint.EQUALITY_TOLERANCE) {
                             turnStart = startPath.offsetDistanceAlong(intersection, -intersectionEndDistance, JoinGreatCircleToPointBuilder.vec3Cache[3], Math.PI);
                             vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, start, startPath, turnStart, flags);
                         }
                         vectorIndex += this.turnToJoinGreatCircleBuilder.build(vectors, vectorIndex, turnStart, startPath, endPath, turnRadius, turnFlags);
                     }
                 }
             }
             else {
                 if (pathDot >= 0) {
                     // The start and end paths intersect at an angle <= 90 degrees. This means that for a turn away from the end
                     // point, the total flight path distance is minimized when the turn joins the start and end paths directly.
                     if (intersectionStartOffset <= minD) {
                         // The start point lies at or before the required turn start point to minimize the flight path distance.
                         needCalculateOneTurnPath = true;
                     }
                     else if (!preferSingleTurn) {
                         // The start point lies after the required turn start point to minimize the flight path distance, so we
                         // will make a turn immediately and connect it with another turn to join the final path at the end point.
                         vectorIndex += this.turnToJoinGreatCircleAtPointBuilder.build(vectors, vectorIndex, start, startPath, minTurnRadius, desiredTurnDirection, end, endPath, minTurnRadius, desiredTurnDirection, 0, turnFlags);
                     }
                     else {
                         needCalculateOneTurnPath = true;
                     }
                 }
                 else {
                     // The start and end paths intersect at an angle > 90 degrees.
                     needCalculateTwoTurnPath = !preferSingleTurn;
                     needCalculateOneTurnPath = preferSingleTurn;
                 }
             }
         }
         else {
             if ((desiredTurnDirection === undefined || desiredTurnDirection === towardEndPointTurnDirection)
                 && intersectionStartOffset <= GeoPoint.EQUALITY_TOLERANCE
                 && !preferSingleTurn) {
                 // Attempt a "side step".
                 const endTurnOffsetPath = JoinGreatCircleToPointBuilder.geoCircleCache[0].setAsGreatCircle(end, endPath.center);
                 const endTurnCircleRadiusRad = towardEndPointTurnDirection === 'right' ? minTurnRadiusRad : Math.PI - minTurnRadiusRad;
                 const endTurnCircleCenter = endTurnOffsetPath.offsetDistanceAlong(end, endTurnCircleRadiusRad, JoinGreatCircleToPointBuilder.vec3Cache[3]);
                 const endTurnCircle = JoinGreatCircleToPointBuilder.geoCircleCache[1].set(endTurnCircleCenter, endTurnCircleRadiusRad);
                 const startTurnOffsetPath = JoinGreatCircleToPointBuilder.geoCircleCache[2].setAsGreatCircle(start, startPath.center);
                 const startTurnCenter = startTurnOffsetPath.offsetDistanceAlong(start, minTurnRadiusRad * (towardEndPointTurnDirection === 'left' ? 1 : -1), JoinGreatCircleToPointBuilder.vec3Cache[4]);
                 if (Math.abs(endTurnCircle.distance(startTurnCenter)) >= minTurnRadiusRad - GeoPoint.EQUALITY_TOLERANCE) {
                     const startTurnCircle = FlightPathUtils.getTurnCircle(startTurnCenter, minTurnRadiusRad, towardEndPointTurnDirection, JoinGreatCircleToPointBuilder.geoCircleCache[2]);
                     vectorIndex += this.connectCirclesBuilder.build(vectors, vectorIndex, startTurnCircle, endTurnCircle, undefined, start, end, flags, turnFlags, flags);
                 }
                 else {
                     needCalculateTwoTurnPath = true;
                 }
             }
             else if (desiredTurnDirection !== undefined && desiredTurnDirection !== towardEndPointTurnDirection) {
                 needCalculateTwoTurnPath = true;
             }
             else {
                 needCalculateTwoTurnPath = !preferSingleTurn;
                 needCalculateOneTurnPath = preferSingleTurn;
             }
         }
         if (needCalculateTwoTurnPath) {
             // Calculate a flight path which begins with an initial turn from the start path connected by a great-circle
             // path to a final turn to join the end path at the end point.
             // Attempt to place the start of the first turn at either the start point or the intersection point between
             // the start and end paths, whichever one is farther along the start path.
             const turnStartIntersectionOffset = Math.max(0, intersectionStartOffset);
             desiredTurnDirection !== null && desiredTurnDirection !== void 0 ? desiredTurnDirection : (desiredTurnDirection = towardEndPointTurnDirection);
             const endTurnDirectionThreshold = minD * (desiredTurnDirection === towardEndPointTurnDirection ? -1 : 1);
             const endTurnDirection = ((towardEndPointTurnDirection === 'left')
                 === (turnStartIntersectionOffset < endTurnDirectionThreshold)
                 === isEndForwardOfIntersection)
                 ? 'left'
                 : 'right';
             const endTurnOffsetPath = JoinGreatCircleToPointBuilder.geoCircleCache[0].setAsGreatCircle(end, endPath.center);
             const endTurnCircleRadiusRad = endTurnDirection === 'left' ? minTurnRadiusRad : Math.PI - minTurnRadiusRad;
             const endTurnCircleCenter = endTurnOffsetPath.offsetDistanceAlong(end, endTurnCircleRadiusRad, JoinGreatCircleToPointBuilder.vec3Cache[3]);
             const endTurnCircle = JoinGreatCircleToPointBuilder.geoCircleCache[0].set(endTurnCircleCenter, endTurnCircleRadiusRad);
             const startTurnStart = turnStartIntersectionOffset - intersectionStartOffset > GeoPoint.EQUALITY_TOLERANCE
                 ? startPath.offsetDistanceAlong(intersection, turnStartIntersectionOffset, JoinGreatCircleToPointBuilder.vec3Cache[3])
                 : Vec3Math.copy(start, JoinGreatCircleToPointBuilder.vec3Cache[3]);
             const startTurnOffsetPath = JoinGreatCircleToPointBuilder.geoCircleCache[1].setAsGreatCircle(startTurnStart, startPath.center);
             const startTurnCenter = startTurnOffsetPath.offsetDistanceAlong(startTurnStart, minTurnRadiusRad * (desiredTurnDirection === 'left' ? 1 : -1), JoinGreatCircleToPointBuilder.vec3Cache[4]);
             if (Math.abs(endTurnCircle.distance(startTurnCenter)) < minTurnRadiusRad - GeoPoint.EQUALITY_TOLERANCE && desiredTurnDirection !== endTurnDirection) {
                 // The start and end turn circles are secant, making it impossible to join the two with a great-circle path.
                 // Thus we will shift the start turn forward (with respect to the direction of the start path) until the
                 // two circles are tangent.
                 const endTurnOffsetCircle = JoinGreatCircleToPointBuilder.geoCircleCache[1].set(FlightPathUtils.getTurnCenterFromCircle(endTurnCircle, JoinGreatCircleToPointBuilder.vec3Cache[5]), minTurnRadiusRad * 2);
                 const startPathOffsetCircle = JoinGreatCircleToPointBuilder.geoCircleCache[2].set(startPath.center, startPath.radius + minTurnRadiusRad * (desiredTurnDirection === 'left' ? -1 : 1));
                 const intersections = JoinGreatCircleToPointBuilder.intersectionCache;
                 const numSolutions = startPathOffsetCircle.intersection(endTurnOffsetCircle, intersections);
                 if (numSolutions > 0) {
                     Vec3Math.copy(intersections[0], startTurnCenter);
                     startPath.closest(startTurnCenter, startTurnStart);
                     const turnCircleTangent = endTurnCircle.closest(startTurnCenter, JoinGreatCircleToPointBuilder.vec3Cache[5]);
                     if (Math.acos(Vec3Math.dot(startTurnStart, start)) > GeoPoint.EQUALITY_TOLERANCE) {
                         vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, start, startPath, startTurnStart, flags);
                     }
                     vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, desiredTurnDirection, minTurnRadius, startTurnCenter, startTurnStart, turnCircleTangent, flags);
                     vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, endTurnCircle, turnCircleTangent, end, turnFlags);
                 }
             }
             else {
                 const startTurnCircle = FlightPathUtils.getTurnCircle(startTurnCenter, minTurnRadiusRad, desiredTurnDirection, JoinGreatCircleToPointBuilder.geoCircleCache[1]);
                 if (Math.acos(Vec3Math.dot(startTurnStart, start)) > GeoPoint.EQUALITY_TOLERANCE) {
                     vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, start, startPath, startTurnStart, flags);
                 }
                 vectorIndex += this.connectCirclesBuilder.build(vectors, vectorIndex, startTurnCircle, endTurnCircle, undefined, startTurnStart, end, flags, turnFlags, flags);
             }
         }
         else if (needCalculateOneTurnPath) {
             // Make a single constant-radius turn from the start path to join the end path. The turn must start after the
             // intersection of the start and end paths (the only case where the turn starts before the intersection is
             // handled above).
             minD = Math.min(minD, Math.PI / 2);
             const minTurnEndOffset = Math.min(intersectionEndOffset, -minD);
             const turnStartOffset = Math.max(-minTurnEndOffset, intersectionStartOffset);
             const turnRadius = UnitType.GA_RADIAN.convertTo(Math.atan(tanHalfTheta * Math.sin(turnStartOffset)), UnitType.METER);
             const turnStart = startPath.offsetDistanceAlong(intersection, turnStartOffset, JoinGreatCircleToPointBuilder.vec3Cache[3]);
             if (turnStartOffset - intersectionStartOffset > GeoPoint.EQUALITY_TOLERANCE) {
                 vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, start, startPath, turnStart);
             }
             vectorIndex += this.turnToJoinGreatCircleBuilder.build(vectors, vectorIndex, turnStart, startPath, endPath, turnRadius, turnFlags);
             if (intersectionEndOffset + turnStartOffset > GeoPoint.EQUALITY_TOLERANCE) {
                 const turnEnd = endPath.offsetDistanceAlong(intersection, -turnStartOffset, JoinGreatCircleToPointBuilder.vec3Cache[4]);
                 vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, turnEnd, endPath, end);
             }
         }
         return vectorIndex - index;
     }
 }
 JoinGreatCircleToPointBuilder.vec3Cache = [new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3)];
 JoinGreatCircleToPointBuilder.geoCircleCache = [new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0)];
 JoinGreatCircleToPointBuilder.intersectionCache = [new Float64Array(3), new Float64Array(3)];
 /**
  * Builds procedure turns.
  */
 class ProcedureTurnBuilder {
     constructor() {
         this.greatCircleBuilder = new GreatCircleBuilder();
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     /**
      * Builds a sequence of vectors representing a procedure turn from a defined starting point and initial course to a
      * defined end point and final course. A procedure turn begins with a variable-length leg from the start point along
      * the initial course followed by an initial turn to intercept the outbound leg of the procedure turn, then a
      * variable-length outbound leg, a 180-degree turn, a variable-length inbound leg, and finally a turn to intercept
      * the final course at the end point. If a full set of vectors cannot be computed given the restraints imposed by the
      * path geometry and the desired turn radius, parts of the turn beginning with the inbound leg of the procedure turn
      * may be altered or omitted entirely.
      * @param vectors The flight path vector sequence to which to add the vectors.
      * @param index The index in the sequence at which to add the vectors.
      * @param start The start point in cartesian form.
      * @param startPath The great-circle path defining the initial course.
      * @param end The end point in cartesian form.
      * @param endPath The great-circle path defining the final course.
      * @param outboundCourse The true course, in degrees, of the outbound leg of the turn.
      * @param desiredTurnRadius The desired turn radius, in meters.
      * @param desiredTurnDirection The desired turn direction.
      * @param initialCourse The initial course. If not defined, it will be calculated from `startPath` and `start`.
      * @param finalCourse The final course. If not defined, it will be calculated from `endPath` and `end`.
      * @param flags The flags to set on the vectors. Defaults to the `CourseReversal` flag.
      * @param includeTurnToCourseFlag Whether to include the `TurnToCourse` flag on the turn vectors. True by default.
      * @returns The number of vectors added to the sequence.
      */
     build(vectors, index, start, startPath, end, endPath, outboundCourse, desiredTurnRadius, desiredTurnDirection, initialCourse, finalCourse, flags = FlightPathVectorFlags.CourseReversal, includeTurnToCourseFlag = true) {
         let vectorIndex = index;
         if (!(start instanceof Float64Array)) {
             start = GeoPoint.sphericalToCartesian(start, ProcedureTurnBuilder.vec3Cache[0]);
         }
         if (!(end instanceof Float64Array)) {
             end = GeoPoint.sphericalToCartesian(end, ProcedureTurnBuilder.vec3Cache[1]);
         }
         /*
          * We need to calculate two parameters: (1) the distance to stay on the initial outbound segment, and (2) the
          * distance to stay on the outbound segment of the turn. We ideally would like to choose these parameters such
          * that the procedure turn ends at a location where it can immediately make another turn to intercept the next
          * leg. However, this may not be possible since we are constrained by the fact that the two distance parameters
          * cannot be negative. To simplify the math, we will do an approximated calculation based on a pseudo-Euclidean
          * geometry instead of spherical geometry. The error is proportional to the angle between the outbound path and the
          * the path to intercept; if they are exactly antiparallel the error is zero.
          */
         initialCourse !== null && initialCourse !== void 0 ? initialCourse : (initialCourse = startPath.bearingAt(start, Math.PI));
         finalCourse !== null && finalCourse !== void 0 ? finalCourse : (finalCourse = endPath.bearingAt(end, Math.PI));
         const startPoint = ProcedureTurnBuilder.geoPointCache[0].setFromCartesian(start);
         const initialTurnDirection = NavMath.getTurnDirection(initialCourse, outboundCourse);
         const isInitialTurnTowardEndPath = startPath.encircles(end) === (initialTurnDirection === 'left');
         const deltaOutbound = Math.abs(NavMath.diffAngle(initialCourse, outboundCourse)) * Avionics.Utils.DEG2RAD;
         const thetaOutbound = (Math.PI - deltaOutbound) / 2;
         const desiredTurnRadiusRad = UnitType.METER.convertTo(desiredTurnRadius, UnitType.GA_RADIAN);
         // If there is a desired turn direction, honor it. Otherwise choose the direction that results in the shortest path
         // to intercept the next leg.
         const turnDirection = desiredTurnDirection !== null && desiredTurnDirection !== void 0 ? desiredTurnDirection : (((initialTurnDirection === 'left') === deltaOutbound < Math.PI) ? 'right' : 'left');
         const endPointToStartPathXTrackDistance = Math.abs(startPath.distance(end));
         let desiredAlongTurnOutboundPathDistance = Math.abs(deltaOutbound - Math.PI / 2) > 1e-10
             ? Math.asin(Math.tan(2 * desiredTurnRadiusRad) / Math.tan(deltaOutbound)) * (turnDirection === initialTurnDirection ? -1 : 1)
             : 0;
         desiredAlongTurnOutboundPathDistance += Math.asin(Math.sin(endPointToStartPathXTrackDistance) / Math.sin(deltaOutbound))
             * (isInitialTurnTowardEndPath ? 1 : -1);
         const alongTurnOutboundPathDistance = Math.max(0, desiredAlongTurnOutboundPathDistance);
         let desiredAlongStartPathDistance = (startPath.distanceAlong(start, end, Math.PI) + Math.PI) % (2 * Math.PI) - Math.PI;
         desiredAlongStartPathDistance -= desiredAlongTurnOutboundPathDistance === 0 ? 0 : Math.atan(Math.cos(deltaOutbound) * Math.tan(desiredAlongTurnOutboundPathDistance));
         desiredAlongStartPathDistance += Math.asin(Math.sin(deltaOutbound) * Math.sin(2 * desiredTurnRadiusRad)) * (turnDirection === initialTurnDirection ? 1 : -1);
         const alongStartPathDistance = Math.max(0, desiredAlongStartPathDistance);
         const initialTurnStartPoint = alongStartPathDistance > 0
             ? startPath.offsetDistanceAlong(start, alongStartPathDistance, ProcedureTurnBuilder.geoPointCache[1])
             : startPoint;
         const initialTurnCenterPoint = initialTurnStartPoint.offset(initialCourse + (initialTurnDirection === 'left' ? -90 : 90), desiredTurnRadiusRad, ProcedureTurnBuilder.geoPointCache[2]);
         const initialTurnHalfAngularWidth = Math.acos(Math.sin(thetaOutbound) * Math.cos(desiredTurnRadiusRad)) * Avionics.Utils.RAD2DEG;
         const initialTurnStartBearing = initialTurnCenterPoint.bearingTo(initialTurnStartPoint);
         const initialTurnEndBearing = NavMath.normalizeHeading(initialTurnStartBearing + initialTurnHalfAngularWidth * 2 * (initialTurnDirection === 'left' ? -1 : 1));
         const initialTurnEndPoint = initialTurnCenterPoint.offset(initialTurnEndBearing, desiredTurnRadiusRad, ProcedureTurnBuilder.geoPointCache[3]);
         const turnStartPoint = alongTurnOutboundPathDistance > 0
             ? initialTurnEndPoint.offset(outboundCourse, alongTurnOutboundPathDistance, ProcedureTurnBuilder.geoPointCache[4])
             : initialTurnEndPoint;
         const turnCenterPoint = turnStartPoint.offset(outboundCourse + (turnDirection === 'left' ? -90 : 90), desiredTurnRadiusRad, ProcedureTurnBuilder.geoPointCache[5]);
         const turnStartBearing = turnCenterPoint.bearingTo(turnStartPoint);
         let turnEndBearing = NavMath.normalizeHeading(turnStartBearing + 180);
         const turnEndPoint = turnCenterPoint.offset(turnEndBearing, desiredTurnRadiusRad, ProcedureTurnBuilder.geoPointCache[6]);
         const turnEndVec = turnEndPoint.toCartesian(ProcedureTurnBuilder.vec3Cache[2]);
         let finalTurnDirection;
         let finalTurnRadius;
         let finalTurnStartPoint;
         let finalTurnCenterPoint;
         let finalTurnEndPoint;
         let endPoint;
         if (endPath.encircles(turnEndVec) === (initialTurnDirection === 'left')) {
             // the end of the turn lies beyond the path to intercept due to approximation error, so we need to end the turn early.
             const turnCircle = ProcedureTurnBuilder.geoCircleCache[0].set(turnCenterPoint, desiredTurnRadiusRad);
             const intersections = ProcedureTurnBuilder.intersectionCache;
             const numIntersections = turnCircle.intersection(endPath, intersections);
             if (numIntersections === 0) {
                 // the path to intersect is completely outside of the turn, which can only happen if there is a major deviation
                 // from the pseudo-Euclidean approximation. There is no easy way to recover from this state, so we just bail
                 // out with a track directly to the endpoint.
                 endPoint = ProcedureTurnBuilder.geoPointCache[7].setFromCartesian(end);
             }
             else {
                 if (numIntersections === 2) {
                     // choose the intersection point which gives the smallest angle between the end of the turn and the path to intercept
                     const headingAdjustment = turnDirection === 'left' ? -90 : 90;
                     const angleDiff_0 = Math.abs(NavMath.diffAngle(Math.acos(Vec3Math.dot(GeoCircle.getGreatCircleNormal(turnCenterPoint, intersections[0], ProcedureTurnBuilder.vec3Cache[3]), endPath.center)) * Avionics.Utils.RAD2DEG + headingAdjustment, 0));
                     const angleDiff_1 = Math.abs(NavMath.diffAngle(Math.acos(Vec3Math.dot(GeoCircle.getGreatCircleNormal(turnCenterPoint, intersections[1], ProcedureTurnBuilder.vec3Cache[3]), endPath.center)) * Avionics.Utils.RAD2DEG + headingAdjustment, 0));
                     turnEndPoint.setFromCartesian(intersections[angleDiff_0 < angleDiff_1 ? 0 : 1]);
                 }
                 else {
                     turnEndPoint.setFromCartesian(intersections[0]);
                 }
                 turnEndBearing = turnCenterPoint.bearingTo(turnEndPoint);
             }
         }
         else {
             const turnFinalCourse = NavMath.normalizeHeading(outboundCourse + 180);
             const turnInboundPath = ProcedureTurnBuilder.geoCircleCache[0].setAsGreatCircle(turnEndPoint, turnFinalCourse);
             const intersections = ProcedureTurnBuilder.intersectionCache;
             const numIntersections = turnInboundPath.intersection(endPath, intersections);
             // Only move forward if the end of the turn does not lie on the path to intercept.
             if (numIntersections !== 0 && !endPath.includes(turnEndVec)) {
                 const intersection = intersections[(numIntersections === 1 || endPath.encircles(turnEndVec)) ? 0 : 1];
                 // Only move forward if the intersection lies before the endpoint, otherwise we will just end the leg at the
                 // end of the procedure turn.
                 if (Vec3Math.dot(GeoCircle.getGreatCircleNormal(intersection, end, ProcedureTurnBuilder.vec3Cache[3]), endPath.center) > 0) {
                     // Because we used an approximation to place the procedure turn, the inbound segment of the turn may not
                     // allow for a turn of the desired radius to perfectly intercept the final path. Therefore, we need to
                     // explicitly calculate the maximum allowed turn radius for this final turn and adjust the turn radius as
                     // needed. Note that if the initial and final paths are antiparallel, these calculations are not strictly
                     // necessary, but we will carry them out in all cases to account for floating point errors that may have
                     // accrued during previous calculations.
                     const deltaInbound = Math.acos(Vec3Math.dot(endPath.center, turnInboundPath.center));
                     const thetaInbound = (Math.PI - deltaInbound) / 2;
                     const tanThetaInbound = Math.tan(thetaInbound);
                     const desiredFinalTurnAlongTrackDistance = Math.asin(Math.tan(desiredTurnRadiusRad) / tanThetaInbound);
                     const finalTurnAlongTrackDistance = Math.min(desiredFinalTurnAlongTrackDistance, Math.acos(Vec3Math.dot(intersection, turnEndVec)), Math.acos(Vec3Math.dot(intersection, end)));
                     const finalTurnRadiusRad = finalTurnAlongTrackDistance === desiredFinalTurnAlongTrackDistance
                         ? desiredTurnRadiusRad
                         : Math.atan(Math.sin(finalTurnAlongTrackDistance) * tanThetaInbound);
                     finalTurnDirection = NavMath.getTurnDirection(turnFinalCourse, finalCourse);
                     finalTurnRadius = UnitType.GA_RADIAN.convertTo(finalTurnRadiusRad, UnitType.METER);
                     finalTurnStartPoint = turnInboundPath.offsetDistanceAlong(intersection, -finalTurnAlongTrackDistance, ProcedureTurnBuilder.geoPointCache[7], Math.PI);
                     finalTurnCenterPoint = finalTurnStartPoint.offset(turnFinalCourse + (finalTurnDirection === 'left' ? -90 : 90), finalTurnRadiusRad, ProcedureTurnBuilder.geoPointCache[8]);
                     finalTurnEndPoint = endPath.offsetDistanceAlong(intersection, finalTurnAlongTrackDistance, ProcedureTurnBuilder.geoPointCache[9], Math.PI);
                 }
             }
         }
         // Set vectors.
         const turnFlags = flags | (includeTurnToCourseFlag ? FlightPathVectorFlags.TurnToCourse : 0);
         if (initialTurnStartPoint !== startPoint) {
             vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, startPoint, initialTurnStartPoint, undefined, flags);
         }
         vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, initialTurnDirection, desiredTurnRadius, initialTurnCenterPoint, initialTurnStartPoint, initialTurnEndPoint, turnFlags);
         if (turnStartPoint !== initialTurnEndPoint) {
             vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, initialTurnEndPoint, turnStartPoint, undefined, flags);
         }
         vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, turnDirection, desiredTurnRadius, turnCenterPoint, turnStartPoint, turnEndPoint, turnFlags);
         if (finalTurnCenterPoint) {
             if (!finalTurnStartPoint.equals(turnEndPoint)) {
                 vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, turnEndPoint, finalTurnStartPoint, undefined, flags);
             }
             vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, finalTurnDirection, finalTurnRadius, finalTurnCenterPoint, finalTurnStartPoint, finalTurnEndPoint, turnFlags);
         }
         else {
             if (endPoint) {
                 vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, turnEndPoint, endPoint, undefined, flags);
             }
         }
         return vectorIndex - index;
     }
 }
 ProcedureTurnBuilder.vec3Cache = [new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3)];
 ProcedureTurnBuilder.geoPointCache = [
     new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0),
     new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)
 ];
 ProcedureTurnBuilder.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 ProcedureTurnBuilder.intersectionCache = [new Float64Array(3), new Float64Array(3)];
 
 /**
  * Abstract implementation of FlightPathLegCalculator.
  */
 class AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      * @param skipWhenActive Whether this calculator will skip calculations for active legs when the leg has already
      * been calculated. False by default.
      */
     constructor(facilityCache, skipWhenActive = false) {
         this.facilityCache = facilityCache;
         this.skipWhenActive = skipWhenActive;
     }
     /**
      * Gets a geographical position from an ICAO string.
      * @param icao An ICAO string.
      * @param out A GeoPoint object to which to write the result.
      * @returns The geographical position corresponding to the ICAO string, or undefined if one could not be obtained.
      */
     getPositionFromIcao(icao, out) {
         const facility = this.facilityCache.get(icao);
         return facility ? out.set(facility) : undefined;
     }
     /**
      * Gets the geographic position for a flight plan leg terminator.
      * @param leg A flight plan leg.
      * @param icao The ICAO string of the leg's terminator fix.
      * @param out A GeoPoint object to which to write the result.
      * @returns The position of the leg terminator, or undefined if it could not be determined.
      */
     getTerminatorPosition(leg, icao, out) {
         if (leg.lat !== undefined && leg.lon !== undefined) {
             return out.set(leg.lat, leg.lon);
         }
         else {
             const facility = this.facilityCache.get(icao);
             return facility ? out.set(facility.lat, facility.lon) : undefined;
         }
     }
     /**
      * Gets the true course for a flight plan leg. If the leg defines an origin or fix VOR facility, then the magnetic
      * variation defined at the VOR is used to adjust magnetic course, otherwise the computed magnetic variation for the
      * specified point is used.
      * @param leg A flight plan leg.
      * @param point The location from which to get magnetic variation, if an origin VOR is not found.
      * @returns the true course for the flight plan leg.
      */
     getLegTrueCourse(leg, point) {
         if (leg.trueDegrees) {
             return leg.course;
         }
         const facIcao = (leg.originIcao && ICAO.isFacility(leg.originIcao) && ICAO.getFacilityType(leg.originIcao) === FacilityType.VOR) ? leg.originIcao
             : (leg.fixIcao && ICAO.isFacility(leg.fixIcao) && ICAO.getFacilityType(leg.fixIcao) === FacilityType.VOR) ? leg.fixIcao
                 : undefined;
         const facility = facIcao ? this.facilityCache.get(facIcao) : undefined;
         const magVar = facility
             ? -facility.magneticVariation
             : Facilities.getMagVar(point.lat, point.lon);
         return NavMath.normalizeHeading(leg.course + magVar);
     }
     /** @inheritdoc */
     calculate(legs, calculateIndex, activeLegIndex, state, resolveIngressToEgress = true) {
         var _a, _b, _c;
         var _d;
         const calcs = (_a = (_d = legs[calculateIndex]).calculated) !== null && _a !== void 0 ? _a : (_d.calculated = {
             startLat: undefined,
             startLon: undefined,
             endLat: undefined,
             endLon: undefined,
             distance: 0,
             distanceWithTransitions: 0,
             initialDtk: undefined,
             cumulativeDistance: 0,
             cumulativeDistanceWithTransitions: 0,
             flightPath: [],
             ingress: [],
             ingressJoinIndex: -1,
             ingressToEgress: [],
             egressJoinIndex: -1,
             egress: []
         });
         const vectors = calcs.flightPath;
         if (this.skipWhenActive && activeLegIndex === calculateIndex && this.shouldSkipWhenActive(legs, calculateIndex, activeLegIndex, state)) {
             state.currentPosition = FlightPathUtils.getLegFinalPosition(calcs, (_b = state.currentPosition) !== null && _b !== void 0 ? _b : new GeoPoint(0, 0));
             state.currentCourse = (_c = FlightPathUtils.getLegFinalCourse(calcs)) !== null && _c !== void 0 ? _c : state.currentCourse;
             return calcs;
         }
         try {
             this.calculateVectors(legs, calculateIndex, activeLegIndex, state);
             resolveIngressToEgress && this.resolveIngressToEgress(calcs);
         }
         catch (e) {
             console.error(e);
             if (e instanceof Error) {
                 console.error(e.stack);
             }
             vectors.length = 0;
             calcs.ingress.length = 0;
             calcs.ingressJoinIndex = 0;
             calcs.egress.length = 0;
             calcs.egressJoinIndex = -1;
             calcs.ingressToEgress.length = 0;
         }
         return calcs;
     }
     /**
      * Checks whether vector calculations should be skipped when the leg to calculate is the active leg.
      * @param legs A sequence of flight plan legs.
      * @param calculateIndex The index of the leg to calculate.
      * @param activeLegIndex The index of the active leg.
      * @param state The current flight path state.
      * @returns Whether to skip vector calculations.
      */
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     shouldSkipWhenActive(legs, calculateIndex, activeLegIndex, state) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         return legs[calculateIndex].calculated.flightPath.length > 0;
     }
     /**
      * Calculates the ingress to egress vectors for a flight plan leg and adds them to a leg calculation.
      * @param legCalc The calculations for a flight plan leg.
      */
     resolveIngressToEgress(legCalc) {
         FlightPathUtils.resolveIngressToEgress(legCalc);
     }
 }
 /**
  * Calculates flight path vectors for discontinuity legs.
  */
 class DiscontinuityLegCalculator extends AbstractFlightPathLegCalculator {
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         legs[calculateIndex].calculated.flightPath.length = 0;
         state.currentCourse = undefined;
         state.currentPosition = undefined;
     }
 }
 /**
  * Calculates flight path vectors for track to fix legs.
  */
 class TrackToFixLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, false);
         this.geoPointCache = [new GeoPoint(0, 0)];
         this.vectorBuilder = new GreatCircleBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a, _b;
         const prevLeg = (_a = legs[calculateIndex - 1]) === null || _a === void 0 ? void 0 : _a.leg;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const terminatorPos = this.getTerminatorPosition(leg, leg.fixIcao, this.geoPointCache[0]);
         if (!terminatorPos) {
             vectors.length = 0;
             return;
         }
         (_b = state.currentPosition) !== null && _b !== void 0 ? _b : (state.currentPosition = terminatorPos.copy());
         const distance = state.currentPosition.distance(terminatorPos);
         if ((!prevLeg || (prevLeg.type !== LegType.FM && prevLeg.type !== LegType.VM)) && distance > GeoPoint.EQUALITY_TOLERANCE) {
             vectorIndex += this.vectorBuilder.build(vectors, vectorIndex, state.currentPosition, terminatorPos, state.currentCourse);
             state.currentCourse = FlightPathUtils.getVectorFinalCourse(vectors[vectorIndex - 1]);
         }
         state.currentPosition.set(terminatorPos);
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for direct to fix legs.
  */
 class DirectToFixLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, true);
         this.vec3Cache = [new Float64Array(3), new Float64Array(3), new Float64Array(3)];
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.greatCircleVectorBuilder = new GreatCircleBuilder();
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a, _b;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const terminatorPos = this.getTerminatorPosition(leg, leg.fixIcao, this.geoPointCache[1]);
         if (!terminatorPos) {
             vectors.length = 0;
             return;
         }
         (_a = state.currentPosition) !== null && _a !== void 0 ? _a : (state.currentPosition = terminatorPos.copy());
         const startPoint = this.geoPointCache[0].set(state.currentPosition);
         const initialCourse = leg.course !== 0
             ? leg.course % 360
             : (_b = state.currentCourse) !== null && _b !== void 0 ? _b : state.planeHeading;
         const distanceToTerminator = state.currentPosition.distance(terminatorPos);
         if (distanceToTerminator < GeoPoint.EQUALITY_TOLERANCE) {
             state.currentPosition.set(terminatorPos);
             vectors.length = 0;
             return;
         }
         else if (Math.abs(distanceToTerminator - Math.PI) < GeoPoint.EQUALITY_TOLERANCE) {
             // terminator is antipodal to current position
             const path = this.geoCircleCache[0].setAsGreatCircle(state.currentPosition, initialCourse);
             vectorIndex += this.greatCircleVectorBuilder.build(vectors, vectorIndex, state.currentPosition, path, terminatorPos);
             state.currentCourse = path.bearingAt(terminatorPos, Math.PI);
             state.currentPosition.set(terminatorPos);
             vectors.length = vectorIndex;
             return;
         }
         const startVec = startPoint.toCartesian(this.vec3Cache[0]);
         const terminatorVec = terminatorPos.toCartesian(this.vec3Cache[1]);
         const startPath = this.geoCircleCache[0].setAsGreatCircle(startPoint, initialCourse);
         state.currentCourse = initialCourse;
         const startPathEncirclesTerminator = startPath.encircles(terminatorVec);
         const startPathIncludesTerminator = startPath.includes(terminatorVec);
         const turnDirection = leg.turnDirection === LegTurnDirection.Left ? 'left'
             : leg.turnDirection === LegTurnDirection.Right ? 'right'
                 : startPathEncirclesTerminator && !startPathIncludesTerminator ? 'left' : 'right';
         const startToTurnCenterPath = this.geoCircleCache[0].setAsGreatCircle(startPoint, initialCourse + (turnDirection === 'left' ? -90 : 90));
         let maxTurnRadiusRad;
         if (!startPathIncludesTerminator && startPathEncirclesTerminator === (turnDirection === 'left')) {
             // terminator lies on the same side as the turn, which means there is the possibility that the turn circle can
             // encircle the terminator, which would make defining a great circle intersecting the terminator fix and also
             // tangent to the turn circle impossible. Therefore, we compute the maximum allowed turn radius, defined as the
             // radius such that the terminator fix lies exactly on the turn circle.
             const startToTerminatorPathNormal = GeoCircle.getGreatCircleNormal(startVec, terminatorVec, this.vec3Cache[2]);
             // the angle between the great-circle path from the start point to the turn center and the path from the start
             // point to the terminator fix
             const theta = Math.acos(Vec3Math.dot(startToTurnCenterPath.center, startToTerminatorPathNormal));
             maxTurnRadiusRad = Math.atan(Math.sin(distanceToTerminator) / (Math.cos(theta) * (1 + Math.cos(distanceToTerminator))));
         }
         else {
             // terminator lies on the starting path or on the opposite side as the turn. Either way, no turn can encircle the
             // terminator, and so there is no maximum turn radius.
             maxTurnRadiusRad = Math.PI / 2;
         }
         const turnRadiusRad = Math.min(maxTurnRadiusRad, state.desiredTurnRadius.asUnit(UnitType.GA_RADIAN));
         const turnCenterVec = startToTurnCenterPath.offsetDistanceAlong(startVec, turnRadiusRad, this.vec3Cache[2]);
         const turnCenterPoint = this.geoPointCache[2].setFromCartesian(turnCenterVec);
         // Find the great-circle path from the terminator fix that is tangent to the turn circle. There are guaranteed to
         // be two such paths. We choose between the two based on the initial turn direction.
         const turnCenterToTerminatorDistance = Math.acos(Vec3Math.dot(turnCenterVec, terminatorVec));
         // the angle between the the great-circle path from the terminator fix to the turn center and the two
         // great-circle paths from the terminator fix that are tangent to the turn circle.
         const alpha = Math.asin(Math.min(1, Math.sin(turnRadiusRad) / Math.sin(turnCenterToTerminatorDistance)));
         const terminatorFixBearingToTurnCenter = terminatorPos.bearingTo(turnCenterPoint);
         const finalPathCourse = NavMath.normalizeHeading(terminatorFixBearingToTurnCenter + alpha * Avionics.Utils.RAD2DEG * (turnDirection === 'left' ? -1 : 1) + 180);
         const finalPath = this.geoCircleCache[0].setAsGreatCircle(terminatorPos, finalPathCourse);
         const turnEndPoint = finalPath.closest(turnCenterPoint, this.geoPointCache[3]);
         if (!turnEndPoint.equals(startPoint)) {
             vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, turnDirection, UnitType.GA_RADIAN.convertTo(turnRadiusRad, UnitType.METER), turnCenterPoint, startPoint, turnEndPoint, FlightPathVectorFlags.TurnToCourse);
         }
         state.currentPosition.set(turnEndPoint);
         state.currentCourse = finalPathCourse;
         if (!state.currentPosition.equals(terminatorPos)) {
             vectorIndex += this.greatCircleVectorBuilder.build(vectors, vectorIndex, state.currentPosition, terminatorPos);
         }
         state.currentPosition.set(terminatorPos);
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for legs which define a turn ending at a defined terminator fix.
  */
 class TurnToFixLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, false);
         this.vec3Cache = [new Float64Array(3), new Float64Array(3)];
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.circleVectorBuilder = new CircleVectorBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a, _b;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const terminatorPos = this.getTerminatorPosition(leg, leg.fixIcao, this.geoPointCache[0]);
         const turnCenter = this.getTurnCenter(leg);
         if (!terminatorPos || !turnCenter) {
             vectors.length = vectorIndex;
             return;
         }
         if (state.currentPosition && !state.currentPosition.equals(terminatorPos)) {
             const direction = leg.turnDirection === LegTurnDirection.Left ? 'left' : 'right';
             const radius = this.getTurnRadius(leg, turnCenter);
             if (radius) {
                 const circle = FlightPathUtils.getTurnCircle(turnCenter, radius, direction, this.geoCircleCache[0]);
                 const currentVec = circle.closest(state.currentPosition, this.vec3Cache[0]);
                 const terminatorVec = circle.closest(terminatorPos, this.vec3Cache[1]);
                 vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, circle, currentVec, terminatorVec, FlightPathVectorFlags.Arc);
                 state.currentCourse = circle.bearingAt(terminatorVec);
                 const turnVector = vectors[vectorIndex - 1];
                 ((_a = state.currentPosition) !== null && _a !== void 0 ? _a : (state.currentPosition = new GeoPoint(0, 0))).set(turnVector.endLat, turnVector.endLon);
             }
         }
         (_b = state.currentPosition) !== null && _b !== void 0 ? _b : (state.currentPosition = terminatorPos.copy());
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for radius to fix legs.
  */
 class RadiusToFixLegCalculator extends TurnToFixLegCalculator {
     constructor() {
         super(...arguments);
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getTurnCenter(leg) {
         return this.facilityCache.get(leg.arcCenterFixIcao);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getTurnRadius(leg, center) {
         var _a;
         return (_a = this.getPositionFromIcao(leg.fixIcao, this.geoPointCache[2])) === null || _a === void 0 ? void 0 : _a.distance(center);
     }
 }
 /**
  * Calculates flight path vectors for arc to fix legs.
  */
 class ArcToFixLegCalculator extends TurnToFixLegCalculator {
     // eslint-disable-next-line jsdoc/require-jsdoc
     getTurnCenter(leg) {
         return this.facilityCache.get(leg.originIcao);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc, @typescript-eslint/no-unused-vars
     getTurnRadius(leg, center) {
         return UnitType.METER.convertTo(leg.rho, UnitType.GA_RADIAN);
     }
 }
 /**
  * Calculates flight path vectors for legs which define a great-circle path terminating at an intercept with another
  * geo circle.
  */
 class CircleInterceptLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      * @param includeInitialTurn Whether this calculator should calculate an initial turn toward the intercept course.
      */
     constructor(facilityCache, includeInitialTurn) {
         super(facilityCache, includeInitialTurn);
         this.includeInitialTurn = includeInitialTurn;
         this.vec3Cache = [new Float64Array(3), new Float64Array(3)];
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.turnBuilder = new TurnToCourseBuilder();
         this.interceptBuilder = new CircleInterceptBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const course = this.getInterceptCourse(legs, calculateIndex, state);
         const circleToIntercept = this.getCircleToIntercept(legs, calculateIndex, state, this.geoCircleCache[0]);
         if (course === undefined || !circleToIntercept || !state.currentPosition) {
             vectors.length = vectorIndex;
             return;
         }
         const startCourse = (_a = state.currentCourse) !== null && _a !== void 0 ? _a : course;
         if (this.includeInitialTurn && Math.abs(NavMath.diffAngle(course, startCourse)) >= 1) {
             const turnDirection = leg.turnDirection === LegTurnDirection.Left ? 'left'
                 : leg.turnDirection === LegTurnDirection.Right ? 'right'
                     : NavMath.getTurnDirection(startCourse, course);
             vectorIndex += this.turnBuilder.build(vectors, vectorIndex, state.currentPosition, state.desiredTurnRadius.asUnit(UnitType.METER), turnDirection, startCourse, course);
             const turnVector = vectors[vectorIndex - 1];
             state.currentCourse = FlightPathUtils.getVectorFinalCourse(turnVector);
             state.currentPosition.set(turnVector.endLat, turnVector.endLon);
         }
         else {
             state.currentCourse = course;
         }
         const numVectorsAdded = this.interceptBuilder.build(vectors, vectorIndex, state.currentPosition, course, circleToIntercept);
         if (numVectorsAdded > 0) {
             vectorIndex += numVectorsAdded;
             const lastVector = vectors[vectorIndex - 1];
             state.currentCourse = FlightPathUtils.getVectorFinalCourse(lastVector);
             state.currentPosition.set(lastVector.endLat, lastVector.endLon);
         }
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for course to DME legs.
  */
 class CourseToDMELegCalculator extends CircleInterceptLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, true);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getInterceptCourse(legs, index) {
         const leg = legs[index].leg;
         const dmeFacility = this.facilityCache.get(leg.originIcao);
         return dmeFacility ? this.getLegTrueCourse(leg, dmeFacility) : undefined;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getCircleToIntercept(legs, index, state, out) {
         const leg = legs[index].leg;
         const dmeFacility = this.facilityCache.get(leg.originIcao);
         return dmeFacility ? out.set(dmeFacility, UnitType.METER.convertTo(leg.distance, UnitType.GA_RADIAN)) : undefined;
     }
 }
 /**
  * Calculates flight path vectors for course to radial intercept legs.
  */
 class CourseToRadialLegCalculator extends CircleInterceptLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, true);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getInterceptCourse(legs, index) {
         const leg = legs[index].leg;
         const radialFacility = this.facilityCache.get(leg.originIcao);
         return radialFacility ? this.getLegTrueCourse(leg, radialFacility) : undefined;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getCircleToIntercept(legs, index, state, out) {
         const leg = legs[index].leg;
         const radialFacility = this.facilityCache.get(leg.originIcao);
         if (!radialFacility) {
             return undefined;
         }
         const magVar = (ICAO.getFacilityType(radialFacility.icao) === FacilityType.VOR)
             ? -radialFacility.magneticVariation
             : MagVar.get(radialFacility);
         return out.setAsGreatCircle(radialFacility, leg.theta + magVar);
     }
 }
 /**
  * Calculates flight path vectors for fix to DME legs.
  */
 class FixToDMELegCalculator extends CircleInterceptLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, false);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getInterceptCourse(legs, index) {
         const leg = legs[index].leg;
         const startFacility = this.facilityCache.get(leg.fixIcao);
         return startFacility ? this.getLegTrueCourse(leg, startFacility) : undefined;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getCircleToIntercept(legs, index, state, out) {
         const leg = legs[index].leg;
         const dmeFacility = this.facilityCache.get(leg.originIcao);
         return dmeFacility ? out.set(dmeFacility, UnitType.METER.convertTo(leg.distance, UnitType.GA_RADIAN)) : undefined;
     }
 }
 /**
  * Calculates flight path vectors for course to intercept legs.
  */
 class CourseToInterceptLegCalculator extends CircleInterceptLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, true);
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getInterceptCourse(legs, index, state) {
         const leg = legs[index].leg;
         return state.currentPosition ? this.getLegTrueCourse(leg, state.currentPosition) : undefined;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getCircleToIntercept(legs, index, state, out) {
         return this.predictLegPath(legs, index + 1, out);
     }
     /**
      * Predicts the path of a leg. If a prediction cannot be made, NaN will be written to all fields of the result.
      * @param legs A leg sequence.
      * @param index The index of the leg in the sequence.
      * @param out A GeoCircle to which to write the result.
      * @returns the predicted path of the leg.
      */
     predictLegPath(legs, index, out) {
         var _a;
         const leg = (_a = legs[index]) === null || _a === void 0 ? void 0 : _a.leg;
         if (!leg) {
             return undefined;
         }
         switch (leg.type) {
             case LegType.CF:
                 {
                     const terminator = this.getTerminatorPosition(leg, leg.fixIcao, this.geoPointCache[2]);
                     return terminator ? out.setAsGreatCircle(terminator, this.getLegTrueCourse(leg, terminator)) : undefined;
                 }
             case LegType.AF:
                 {
                     const facility = this.facilityCache.get(leg.originIcao);
                     return facility ? out.set(facility, UnitType.METER.convertTo(leg.rho, UnitType.GA_RADIAN)) : undefined;
                 }
             case LegType.RF:
                 {
                     const terminator = this.getTerminatorPosition(leg, leg.fixIcao, this.geoPointCache[2]);
                     const centerFacility = this.facilityCache.get(leg.arcCenterFixIcao);
                     return terminator && centerFacility ? out.set(centerFacility, terminator.distance(centerFacility)) : undefined;
                 }
             case LegType.FM:
             case LegType.VM:
                 {
                     const origin = this.facilityCache.get(leg.originIcao);
                     return origin ? out.setAsGreatCircle(origin, this.getLegTrueCourse(leg, origin)) : undefined;
                 }
             default:
                 return undefined;
         }
     }
 }
 /**
  * Calculates flight path vectors for track from fix legs.
  */
 class TrackFromFixLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, false);
         this.geoPointCache = [new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.vectorBuilder = new GreatCircleBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const fixIcaoPoint = this.getPositionFromIcao(leg.fixIcao, this.geoPointCache[0]);
         const startPoint = fixIcaoPoint !== null && fixIcaoPoint !== void 0 ? fixIcaoPoint : state.currentPosition;
         if (!startPoint) {
             vectors.length = vectorIndex;
             return;
         }
         const course = this.getLegTrueCourse(leg, startPoint);
         const path = this.geoCircleCache[0].setAsGreatCircle(startPoint, course);
         vectorIndex += this.vectorBuilder.build(vectors, vectorIndex, startPoint, path, leg.distance);
         if (vectorIndex > 0) {
             const lastVector = vectors[vectorIndex - 1];
             state.currentCourse = FlightPathUtils.getVectorFinalCourse(lastVector);
             ((_a = state.currentPosition) !== null && _a !== void 0 ? _a : (state.currentPosition = new GeoPoint(0, 0))).set(lastVector.endLat, lastVector.endLon);
         }
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for course to fix legs.
  */
 class CourseToFixLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, true);
         this.vec3Cache = [new Float64Array(3), new Float64Array(3)];
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.geoCircleCache = [
             new GeoCircle(new Float64Array(3), 0),
             new GeoCircle(new Float64Array(3), 0),
             new GeoCircle(new Float64Array(3), 0)
         ];
         this.greatCircleBuilder = new GreatCircleBuilder();
         this.joinGreatCircleToPointBuilder = new JoinGreatCircleToPointBuilder();
         this.procTurnBuilder = new ProcedureTurnBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a, _b, _c;
         const leg = legs[calculateIndex].leg;
         const prevLeg = legs[calculateIndex - 1];
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const startPoint = state.currentPosition ? this.geoPointCache[0].set(state.currentPosition) : undefined;
         const endPoint = this.getPositionFromIcao(leg.fixIcao, this.geoPointCache[1]);
         if (!endPoint) {
             vectors.length = vectorIndex;
             return;
         }
         const endCourse = this.getLegTrueCourse(leg, endPoint);
         const endVec = endPoint.toCartesian(this.vec3Cache[1]);
         const endPath = this.geoCircleCache[1].setAsGreatCircle(endPoint, endCourse);
         if (!startPoint || (prevLeg && (prevLeg.leg.type === LegType.FM || prevLeg.leg.type === LegType.VM))) {
             // Begins at a discontinuity OR previous leg is a manual termination leg.
             // Default to a track with start arbitrarily placed 5 NM from the terminator fix.
             const midPoint = endPath.offsetDistanceAlong(endVec, UnitType.NMILE.convertTo(-5, UnitType.GA_RADIAN), this.geoPointCache[2]);
             vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, midPoint, endPoint);
         }
         else {
             const startVec = startPoint.toCartesian(this.vec3Cache[0]);
             const currentCourse = (_a = state.currentCourse) !== null && _a !== void 0 ? _a : startPoint.bearingTo(endPoint);
             const startPath = this.geoCircleCache[0].setAsGreatCircle(startPoint, currentCourse);
             const startToEndPath = this.geoCircleCache[2].setAsGreatCircle(startVec, endVec);
             const isStartEqualToEnd = startPoint.equals(endPoint);
             const pathDot = Vec3Math.dot(startPath.center, endPath.center);
             if (-pathDot > 1 - GeoCircle.ANGULAR_TOLERANCE) {
                 // The start and end paths are anti-parallel, which means we need to execute a procedure turn to do a 180.
                 // Favor right turn unless we are explicitly told to turn left.
                 const desiredTurnDirection = leg.turnDirection === LegTurnDirection.Left ? 'left' : 'right';
                 vectorIndex += this.procTurnBuilder.build(vectors, vectorIndex, startVec, startPath, endVec, endPath, currentCourse + 45 * (desiredTurnDirection === 'left' ? -1 : 1), state.desiredTurnRadius.asUnit(UnitType.METER), desiredTurnDirection, currentCourse, endCourse);
             }
             else if ((endPath.angleAlong(startVec, endVec, Math.PI) + GeoCircle.ANGULAR_TOLERANCE) % (2 * Math.PI) < Math.PI + GeoCircle.ANGULAR_TOLERANCE
                 && (pathDot > 1 - GeoCircle.ANGULAR_TOLERANCE
                     || (!isStartEqualToEnd
                         && (Vec3Math.dot(startToEndPath.center, endPath.center) >= 0.996194698 // 5 degrees
                             || (((_b = prevLeg === null || prevLeg === void 0 ? void 0 : prevLeg.calculated) === null || _b === void 0 ? void 0 : _b.flightPath.length) && endPath.includes(startVec, UnitType.METER.convertTo(10, UnitType.GA_RADIAN))))))) {
                 /*
                  * The start and end paths are parallel, so we can just connect the start and end with a track.
                  *
                  * OR the start point lies on the final course path (within a generous tolerance) and the previous leg has at
                  * least one calculated vector. In this case we will simply create a track from the start to end and let turn
                  * anticipation handle the initial turn into the final course.
                  */
                 if (!isStartEqualToEnd) {
                     vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, startPoint, endPoint);
                 }
             }
             else {
                 const desiredTurnDirection = leg.turnDirection === LegTurnDirection.Left ? 'left'
                     : leg.turnDirection === LegTurnDirection.Right ? 'right'
                         : undefined;
                 vectorIndex += this.joinGreatCircleToPointBuilder.build(vectors, vectorIndex, startVec, startPath, endVec, endPath, desiredTurnDirection, state.desiredTurnRadius.asUnit(UnitType.METER));
             }
         }
         ((_c = state.currentPosition) !== null && _c !== void 0 ? _c : (state.currentPosition = new GeoPoint(0, 0))).set(endPoint);
         state.currentCourse = endCourse;
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for procedure turn legs.
  */
 class ProcedureTurnLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, true);
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0)];
         this.greatCircleBuilder = new GreatCircleBuilder();
         this.joinGreatCircleToPointBuilder = new JoinGreatCircleToPointBuilder();
         this.procTurnBuilder = new ProcedureTurnBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a, _b, _c;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const origin = this.getPositionFromIcao(leg.fixIcao, this.geoPointCache[0]);
         if (!origin) {
             vectors.length = vectorIndex;
             return;
         }
         // If current lat/lon is not defined, then set it to the origin's location, simulating an IF at the leg origin.
         (_a = state.currentPosition) !== null && _a !== void 0 ? _a : (state.currentPosition = origin.copy());
         const nextLeg = (_b = legs[calculateIndex + 1]) === null || _b === void 0 ? void 0 : _b.leg;
         if (!origin.equals(state.currentPosition)) {
             vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, state.currentPosition, origin, state.currentCourse);
             state.currentCourse = origin.bearingFrom(state.currentPosition);
             state.currentPosition.set(origin);
         }
         if (!nextLeg) {
             vectors.length = vectorIndex;
             return;
         }
         const nextLegTerminatorFix = this.getTerminatorPosition(nextLeg, nextLeg.fixIcao, this.geoPointCache[1]);
         if (!nextLegTerminatorFix) {
             vectors.length = vectorIndex;
             return;
         }
         const inboundCourse = (_c = this.predictLegFinalTrueCourse(legs, calculateIndex + 1, nextLegTerminatorFix)) !== null && _c !== void 0 ? _c : 0;
         const outboundCourse = NavMath.normalizeHeading(inboundCourse + 180);
         const turnInitialCourse = leg.trueDegrees ? leg.course : MagVar.magneticToTrue(leg.course, origin);
         if (outboundCourse === turnInitialCourse) {
             vectors.length = vectorIndex;
             return;
         }
         // must intercept the next leg at least 1 NM from the terminator fix
         const inboundPathEndpoint = nextLegTerminatorFix.offset(inboundCourse + 180, UnitType.NMILE.convertTo(1, UnitType.GA_RADIAN));
         const outboundPath = this.geoCircleCache[0].setAsGreatCircle(origin, outboundCourse);
         const inboundPath = this.geoCircleCache[1].setAsGreatCircle(inboundPathEndpoint, inboundCourse);
         const desiredTurnDirection = leg.turnDirection === LegTurnDirection.Left ? 'left'
             : leg.turnDirection === LegTurnDirection.Right ? 'right'
                 : undefined;
         vectorIndex += this.procTurnBuilder.build(vectors, vectorIndex, origin, outboundPath, inboundPathEndpoint, inboundPath, turnInitialCourse, state.desiredTurnRadius.asUnit(UnitType.METER), desiredTurnDirection, outboundCourse, inboundCourse);
         // addVectorsForProcTurn() is guaranteed to add at least one vector.
         const lastVector = vectors[vectorIndex - 1];
         state.currentPosition.set(lastVector.endLat, lastVector.endLon);
         state.currentCourse = FlightPathUtils.getVectorFinalCourse(lastVector);
         vectors.length = vectorIndex;
     }
     /**
      * Predicts the final true course of a leg at its terminator fix.
      * @param legs A leg sequence.
      * @param index The index of the leg in the sequence.
      * @param terminator The location of the leg's terminator fix.
      * @returns the predicted final course of a leg at its terminator fix, or undefined if a prediction cannot be made.
      */
     predictLegFinalTrueCourse(legs, index, terminator) {
         var _a;
         const leg = (_a = legs[index]) === null || _a === void 0 ? void 0 : _a.leg;
         if (!leg) {
             return undefined;
         }
         switch (leg.type) {
             case LegType.IF:
                 return this.predictLegInitialTrueCourse(legs, index + 1, terminator);
             case LegType.CF:
                 return this.getLegTrueCourse(leg, terminator);
             default:
                 return undefined;
         }
     }
     /**
      * Predicts the initial true course of a leg at its origin fix.
      * @param legs A leg sequence.
      * @param index The index of the leg in the sequence.
      * @param origin The location of the leg's origin.
      * @returns the predicted final course of a leg at its terminator fix, or undefined if a prediction cannot be made.
      */
     predictLegInitialTrueCourse(legs, index, origin) {
         var _a;
         const leg = (_a = legs[index]) === null || _a === void 0 ? void 0 : _a.leg;
         if (!leg) {
             return undefined;
         }
         try {
             switch (leg.type) {
                 case LegType.IF:
                 case LegType.TF:
                 case LegType.DF:
                 case LegType.CF:
                     {
                         const terminator = this.getTerminatorPosition(leg, leg.fixIcao, this.geoPointCache[2]);
                         return terminator ? origin.bearingTo(terminator) : undefined;
                     }
                 case LegType.CD:
                 case LegType.VD:
                 case LegType.CR:
                 case LegType.VR:
                 case LegType.FC:
                 case LegType.FD:
                     return this.getLegTrueCourse(leg, origin);
                 case LegType.FA:
                 case LegType.CA:
                 case LegType.VA:
                 case LegType.FM:
                 case LegType.VM:
                 case LegType.CI:
                 case LegType.VI:
                     return leg.trueDegrees ? leg.course : MagVar.magneticToTrue(leg.course, origin);
                 default:
                     return undefined;
             }
         }
         catch (e) {
             return undefined;
         }
     }
 }
 /**
  * Calculates flight path vectors for course to manual legs.
  */
 class CourseToManualLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, false);
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.greatCircleBuilder = new GreatCircleBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         const startPoint = (_a = state.currentPosition) !== null && _a !== void 0 ? _a : this.getPositionFromIcao(leg.fixIcao, this.geoPointCache[0]);
         if (!startPoint) {
             vectors.length = vectorIndex;
             return;
         }
         const course = leg.trueDegrees ? leg.course : MagVar.magneticToTrue(leg.course, startPoint);
         const normalizedEnd = startPoint.offset(course, UnitType.NMILE.convertTo(1, UnitType.GA_RADIAN), this.geoPointCache[1]);
         vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, startPoint, normalizedEnd);
         state.currentPosition = undefined;
         state.currentCourse = undefined;
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for course to altitude legs.
  */
 class CourseToAltitudeLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, false);
         this.vec3Cache = [new Float64Array(3), new Float64Array(3)];
         this.geoPointCache = [new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.greatCircleBuilder = new GreatCircleBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const vectors = legs[calculateIndex].calculated.flightPath;
         let vectorIndex = 0;
         if (!state.currentPosition) {
             const fixPosition = this.getPositionFromIcao(leg.fixIcao, this.geoPointCache[0]);
             if (fixPosition) {
                 state.currentPosition = new GeoPoint(fixPosition.lat, fixPosition.lon);
             }
         }
         if (!state.currentPosition) {
             vectors.length = vectorIndex;
             return;
         }
         const course = this.getLegTrueCourse(leg, state.currentPosition);
         const path = this.geoCircleCache[0].setAsGreatCircle(state.currentPosition, course);
         const originVec = state.currentPosition.toCartesian(this.vec3Cache[0]);
         const climbStartVec = activeLegIndex === calculateIndex
             ? path.closest(state.planePosition, this.vec3Cache[1])
             : originVec;
         const originToClimbStartDistance = (path.distanceAlong(originVec, climbStartVec) + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // -pi to +pi
         const targetFeet = UnitType.METER.convertTo(leg.altitude1, UnitType.FOOT);
         const deltaAltitude = Math.max(0, targetFeet - state.planeAltitude.asUnit(UnitType.FOOT));
         const distanceRemaining = UnitType.NMILE.convertTo((deltaAltitude / state.planeClimbRate.asUnit(UnitType.FPM)) / 60 * state.planeSpeed.asUnit(UnitType.KNOT), UnitType.GA_RADIAN);
         const offsetDistance = Math.max(UnitType.FOOT.convertTo(100, UnitType.GA_RADIAN), originToClimbStartDistance + distanceRemaining);
         const legEndVec = path.offsetDistanceAlong(originVec, offsetDistance, this.vec3Cache[1]);
         vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, originVec, path, legEndVec);
         state.currentPosition.setFromCartesian(legEndVec);
         state.currentCourse = path.bearingAt(legEndVec);
         vectors.length = vectorIndex;
     }
 }
 /**
  * Calculates flight path vectors for hold legs.
  */
 class HoldLegCalculator extends AbstractFlightPathLegCalculator {
     /**
      * Constructor.
      * @param facilityCache This calculator's cache of facilities.
      */
     constructor(facilityCache) {
         super(facilityCache, true);
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0)];
         this.greatCircleBuilder = new GreatCircleBuilder();
         this.circleVectorBuilder = new CircleVectorBuilder();
         this.turnToCourseBuilder = new TurnToCourseBuilder();
         this.joinGreatCircleToPointBuilder = new JoinGreatCircleToPointBuilder();
         this.procTurnBuilder = new ProcedureTurnBuilder();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     calculateVectors(legs, calculateIndex, activeLegIndex, state) {
         var _a, _b, _c, _d, _e, _f;
         const leg = legs[calculateIndex].leg;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const calcs = legs[calculateIndex].calculated;
         const vectors = calcs.flightPath;
         const ingress = calcs.ingress;
         let vectorIndex = 0, ingressVectorIndex = 0;
         const holdPos = this.getPositionFromIcao(leg.fixIcao, this.geoPointCache[0]);
         if (!holdPos) {
             vectors.length = 0;
             ingress.length = 0;
             calcs.ingressJoinIndex = -1;
             return;
         }
         // If current lat/lon is not defined, then set it to the facility's location, simulating an IF at the hold's
         // facility.
         (_a = state.currentPosition) !== null && _a !== void 0 ? _a : (state.currentPosition = holdPos.copy());
         if (!state.currentPosition.equals(holdPos)) {
             ingressVectorIndex += this.greatCircleBuilder.build(ingress, ingressVectorIndex, state.currentPosition, holdPos, state.currentCourse, FlightPathVectorFlags.HoldEntry);
             state.currentCourse = holdPos.bearingFrom(state.currentPosition);
         }
         const course = this.getLegTrueCourse(leg, holdPos);
         const distance = leg.distanceMinutes
             ? UnitType.NMILE.convertTo(leg.distance * (state.planeSpeed.asUnit(UnitType.KNOT) / 60), UnitType.GA_RADIAN)
             : UnitType.METER.convertTo(leg.distance, UnitType.GA_RADIAN);
         const turnDirection = leg.turnDirection === LegTurnDirection.Right ? 'right' : 'left';
         const turnDirectionSign = turnDirection === 'left' ? -1 : 1;
         const turnRadiusMeters = state.desiredTurnRadius.asUnit(UnitType.METER);
         const inboundPath = this.geoCircleCache[0].setAsGreatCircle(holdPos, course);
         const outboundTurnCenterCourse = NavMath.normalizeHeading(course + 90 * turnDirectionSign);
         const turnRadiusRad = state.desiredTurnRadius.asUnit(UnitType.GA_RADIAN);
         const outboundTurnCenter = holdPos.offset(outboundTurnCenterCourse, turnRadiusRad, this.geoPointCache[1]);
         const outboundTurnEnd = holdPos.offset(outboundTurnCenterCourse, turnRadiusRad * 2, this.geoPointCache[2]);
         const oppositeCourse = NavMath.normalizeHeading(course + 180);
         const outboundEnd = outboundTurnEnd.offset(oppositeCourse, distance, this.geoPointCache[3]);
         // Handle hold entry
         state.currentPosition.set(holdPos);
         (_b = state.currentCourse) !== null && _b !== void 0 ? _b : (state.currentCourse = course);
         const normalizedEntryCourse = ((state.currentCourse - course) + 540) % 360 - 180; // -180 to +180
         const directionalEntryCourse = normalizedEntryCourse * turnDirectionSign;
         const isDirectEntry = directionalEntryCourse >= -70 && directionalEntryCourse <= 135;
         const skipRacetrack = leg.type === LegType.HF && !isDirectEntry;
         if (isDirectEntry) {
             // direct entry
             if (directionalEntryCourse > 0) {
                 // The entry course is toward the outbound leg, so we just intercept the outbound leg directly, bypassing
                 // the turn from the inbound to outbound leg.
                 ingressVectorIndex += this.joinGreatCircleToPointBuilder.build(ingress, ingressVectorIndex, state.currentPosition, this.geoCircleCache[1].setAsGreatCircle(state.currentPosition, state.currentCourse), outboundEnd, this.geoCircleCache[2].setAsGreatCircle(outboundTurnEnd, oppositeCourse), turnDirection, turnRadiusMeters, false, undefined, FlightPathVectorFlags.HoldEntry);
                 calcs.ingressJoinIndex = 1;
             }
             else if (BitFlags.isAny((_d = (_c = ingress[0]) === null || _c === void 0 ? void 0 : _c.flags) !== null && _d !== void 0 ? _d : 0, FlightPathVectorFlags.AnticipatedTurn)) {
                 // Don't erase turn anticipation for direct entries
                 ingressVectorIndex = ingress.length;
             }
         }
         else if (directionalEntryCourse > 110) {
             // teardrop entry
             if (directionalEntryCourse > 135) {
                 // need to make initial turn to get a 45-degree outbound leg
                 const outboundCourse = course + 135 * turnDirectionSign;
                 ingressVectorIndex += this.turnToCourseBuilder.build(ingress, ingressVectorIndex, holdPos, turnRadiusMeters, turnDirection === 'left' ? 'right' : 'left', state.currentCourse, outboundCourse, FlightPathVectorFlags.HoldEntry | FlightPathVectorFlags.TurnToCourse);
                 const turnVector = ingress[ingressVectorIndex - 1];
                 state.currentPosition.set(turnVector.endLat, turnVector.endLon);
                 state.currentCourse = (_e = FlightPathUtils.getVectorFinalCourse(turnVector)) !== null && _e !== void 0 ? _e : outboundCourse;
             }
             ingressVectorIndex += this.joinGreatCircleToPointBuilder.build(ingress, ingressVectorIndex, state.currentPosition, this.geoCircleCache[1].setAsGreatCircle(state.currentPosition, state.currentCourse), holdPos, inboundPath, turnDirection, turnRadiusMeters, true, undefined, FlightPathVectorFlags.HoldEntry);
             if (skipRacetrack) {
                 // If we skip the racetrack, remove the part of the hold entry that is coincident with the inbound leg
                 const lastEntryVector = ingress[ingressVectorIndex - 1];
                 if (lastEntryVector && FlightPathUtils.isVectorGreatCircle(lastEntryVector) && holdPos.equals(lastEntryVector.endLat, lastEntryVector.endLon)) {
                     if (UnitType.METER.convertTo(lastEntryVector.distance, UnitType.GA_RADIAN) > distance + GeoPoint.EQUALITY_TOLERANCE) {
                         const lastEntryVectorEnd = holdPos.offset(course + 180, distance, this.geoPointCache[1]);
                         lastEntryVector.endLat = lastEntryVectorEnd.lat;
                         lastEntryVector.endLon = lastEntryVectorEnd.lon;
                         lastEntryVector.distance -= UnitType.GA_RADIAN.convertTo(distance, UnitType.METER);
                     }
                     else {
                         ingressVectorIndex--;
                     }
                 }
             }
             calcs.ingressJoinIndex = 0;
         }
         else if (directionalEntryCourse < -70) {
             // parallel entry
             const parallelCourse = course + 180;
             ingressVectorIndex += this.turnToCourseBuilder.build(ingress, ingressVectorIndex, holdPos, turnRadiusMeters, turnDirection === 'left' ? 'right' : 'left', state.currentCourse, parallelCourse, FlightPathVectorFlags.HoldEntry | FlightPathVectorFlags.TurnToCourse);
             const turnVector = ingress[ingressVectorIndex - 1];
             state.currentPosition.set(turnVector.endLat, turnVector.endLon);
             state.currentCourse = (_f = FlightPathUtils.getVectorFinalCourse(turnVector)) !== null && _f !== void 0 ? _f : parallelCourse;
             ingressVectorIndex += this.procTurnBuilder.build(ingress, ingressVectorIndex, state.currentPosition, this.geoCircleCache[1].setAsGreatCircle(state.currentPosition, state.currentCourse), holdPos, inboundPath, course + 135 * turnDirectionSign, turnRadiusMeters, turnDirection === 'left' ? 'right' : 'left', state.currentCourse, course, FlightPathVectorFlags.HoldEntry);
             calcs.ingressJoinIndex = 0;
         }
         ingress.length = ingressVectorIndex;
         if (ingress.length === 0) {
             calcs.ingressJoinIndex = -1;
         }
         let inboundStart;
         if (skipRacetrack) {
             inboundStart = holdPos.offset(course + 180, distance, this.geoPointCache[1]);
         }
         else {
             vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, turnDirection, turnRadiusMeters, outboundTurnCenter, holdPos, outboundTurnEnd, FlightPathVectorFlags.TurnToCourse);
             vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, outboundTurnEnd, outboundEnd, undefined, FlightPathVectorFlags.HoldLeg);
             const inboundTurnCenterCourse = NavMath.normalizeHeading(oppositeCourse + 90 * turnDirectionSign);
             const inboundTurnCenter = outboundEnd.offset(inboundTurnCenterCourse, turnRadiusRad, this.geoPointCache[1]);
             const inboundTurnEnd = outboundEnd.offset(inboundTurnCenterCourse, turnRadiusRad * 2, this.geoPointCache[2]);
             vectorIndex += this.circleVectorBuilder.build(vectors, vectorIndex, turnDirection, turnRadiusMeters, inboundTurnCenter, outboundEnd, inboundTurnEnd, FlightPathVectorFlags.TurnToCourse);
             inboundStart = inboundTurnEnd;
         }
         vectorIndex += this.greatCircleBuilder.build(vectors, vectorIndex, inboundStart, holdPos, undefined, FlightPathVectorFlags.HoldLeg);
         state.currentPosition.set(holdPos);
         state.currentCourse = course;
         vectors.length = vectorIndex;
     }
 }
 
 /**
  * A flight path calculator for turns between legs.
  */
 class FlightPathTurnCalculator {
     constructor() {
         this.procTurnBuilder = new ProcedureTurnBuilder();
     }
     /**
      * Computes leg to leg turns for a given sequence of legs. Turns will only be calculated between legs with defined
      * flight path vectors and no pre-existing egress/ingress transition (unless it is a leg-to-leg turn) at the junction
      * of the turn.
      * @param legs A sequence of legs.
      * @param startIndex The index of the first leg for which to compute turns.
      * @param count The total number of legs for which to compute turns.
      * @param desiredTurnRadius The desired turn radius, in meters.
      */
     computeTurns(legs, startIndex, count, desiredTurnRadius) {
         var _a, _b, _c, _d, _e, _f;
         const end = startIndex + count;
         let currentIndex = startIndex;
         while (currentIndex < end) {
             const fromLegCalc = (_a = legs[currentIndex]) === null || _a === void 0 ? void 0 : _a.calculated;
             const toLegCalc = (_b = legs[currentIndex + 1]) === null || _b === void 0 ? void 0 : _b.calculated;
             if (fromLegCalc && toLegCalc) {
                 const fromVector = fromLegCalc.flightPath[fromLegCalc.flightPath.length - 1];
                 const toVector = toLegCalc.flightPath[0];
                 if (fromVector && toVector
                     && (fromLegCalc.egress.length === 0 || BitFlags.isAll(fromLegCalc.egress[0].flags, FlightPathVectorFlags.LegToLegTurn))
                     && (toLegCalc.ingress.length === 0 || BitFlags.isAll(toLegCalc.ingress[0].flags, FlightPathVectorFlags.LegToLegTurn))) {
                     if (fromVector.radius === Math.PI / 2 && toVector.radius === Math.PI / 2) {
                         currentIndex = this.computeTrackTrackTurn(legs, currentIndex, currentIndex + 1, fromVector, toVector, desiredTurnRadius, true);
                         continue;
                     }
                     else if (toVector.radius === Math.PI / 2) {
                         currentIndex = this.computeArcTrackTurn(legs, currentIndex, currentIndex + 1, fromVector, toVector, true, desiredTurnRadius);
                         continue;
                     }
                     else if (fromVector.radius === Math.PI / 2) {
                         currentIndex = this.computeArcTrackTurn(legs, currentIndex, currentIndex + 1, toVector, fromVector, false, desiredTurnRadius);
                         continue;
                     }
                 }
             }
             if (fromLegCalc && BitFlags.isAll((_d = (_c = fromLegCalc.egress[0]) === null || _c === void 0 ? void 0 : _c.flags) !== null && _d !== void 0 ? _d : 0, FlightPathVectorFlags.LegToLegTurn)) {
                 fromLegCalc.egress.length = 0;
                 fromLegCalc.egressJoinIndex = -1;
             }
             if (toLegCalc && BitFlags.isAll((_f = (_e = toLegCalc.ingress[0]) === null || _e === void 0 ? void 0 : _e.flags) !== null && _f !== void 0 ? _f : 0, FlightPathVectorFlags.LegToLegTurn)) {
                 toLegCalc.ingress.length = 0;
                 toLegCalc.ingressJoinIndex = -1;
             }
             currentIndex++;
         }
     }
     /**
      * Calculates a leg-to-leg turn between two track vectors.
      * @param legs The sequence of legs to which the turn belongs.
      * @param fromIndex The index of the leg on which the turn begins.
      * @param toIndex The index of the leg on which the turn ends.
      * @param fromTrack The track vector on which the turn begins.
      * @param toTrack The track vector on which the turn ends.
      * @param desiredTurnRadius The desired turn radius, in meters.
      * @param isRestrictedByPrevTurn Whether turn anticipation is restricted by the previous leg-to-leg turn. If `true`,
      * turn anticipation will be restricted so that the turn does not overlap the previous turn if they share a common
      * flight path vector.
      * @param previousTanTheta The tangent of the theta value of the previous turn. Theta is defined as the (acute)
      * angle between either `fromTrack` or `toTrack` and the great circle passing through the turn vertex (where the two
      * tracks meet) and the center of the turn. If this value is defined and `isRestrictedByPrevTurn` is `true`, the
      * anticipation of both turns will be adjusted if necessary such that the turns do not overlap if they share a common
      * flight path vector. If the value is undefined, the anticipation of the current turn will be restricted by the
      * previous turn, if necessary, without changing the anticipation of the previous turn.
      * @returns The index of the last leg in the sequence for which a turn ending on that leg was computed.
      */
     computeTrackTrackTurn(legs, fromIndex, toIndex, fromTrack, toTrack, desiredTurnRadius, isRestrictedByPrevTurn, previousTanTheta) {
         var _a;
         let lastComputedIndex = toIndex;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const fromLegCalc = legs[fromIndex].calculated;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const toLegCalc = legs[toIndex].calculated;
         const turnVertexPoint = FlightPathTurnCalculator.geoPointCache[0].set(fromTrack.endLat, fromTrack.endLon);
         const fromTrackBearing = turnVertexPoint.bearingFrom(fromTrack.startLat, fromTrack.startLon);
         const toTrackBearing = turnVertexPoint.bearingTo(toTrack.endLat, toTrack.endLon);
         const trackAngleDiff = Math.abs(NavMath.diffAngle(fromTrackBearing, toTrackBearing));
         if (trackAngleDiff < 1
             || fromTrack.distance === 0
             || toTrack.distance === 0
             || !turnVertexPoint.equals(toTrack.startLat, toTrack.startLon, 1e-5)) {
             this.setEmptyTurn(fromLegCalc, toLegCalc);
             return lastComputedIndex;
         }
         if (trackAngleDiff > 175) {
             return this.computeTrackTrackCourseReversal(legs, fromIndex, toIndex, fromTrack, toTrack, fromTrackBearing, toTrackBearing, desiredTurnRadius);
         }
         const theta = (180 - trackAngleDiff) / 2;
         const tanTheta = Math.tan(theta * Avionics.Utils.DEG2RAD);
         // D is defined as the distance from the start/end of the turn to the turn vertex along the from- and to- tracks
         // (i.e. the anticipation).
         const desiredD = Math.asin(Math.tan(UnitType.METER.convertTo(desiredTurnRadius, UnitType.GA_RADIAN)) / tanTheta);
         let restrictedD = Infinity;
         if (isRestrictedByPrevTurn) {
             if (previousTanTheta === undefined) {
                 // Check to see if there is a ingress transition on the from leg and if it shares a common flight path vector
                 // with the one involved in the turn currently being calculated.
                 if (fromLegCalc.ingress.length > 0 && fromLegCalc.ingressJoinIndex === fromLegCalc.flightPath.length - 1) {
                     const lastIngressVector = fromLegCalc.ingress[fromLegCalc.ingress.length - 1];
                     restrictedD = turnVertexPoint.distance(lastIngressVector.endLat, lastIngressVector.endLon);
                 }
             }
             else {
                 // D is restricted by a previous turn. The values of D_current and D_previous are restricted such that their sum
                 // cannot exceed the total length of their shared vector (the from- vector for this turn). Therefore, we set the
                 // maximum value of D_current such that at D_current(max), the radius of this turn equals the radius of the
                 // previous turn. This will maximize min(radius_current, radius_prev).
                 const tanThetaRatio = previousTanTheta / tanTheta;
                 const totalD = UnitType.METER.convertTo(fromTrack.distance, UnitType.GA_RADIAN);
                 const cosTotalD = Math.cos(totalD);
                 let prevTurnRestrictedD = Math.acos((tanThetaRatio * cosTotalD + 1) / Math.sqrt(tanThetaRatio * tanThetaRatio + 2 * tanThetaRatio * cosTotalD + 1));
                 if (prevTurnRestrictedD > totalD) {
                     prevTurnRestrictedD = Math.PI - prevTurnRestrictedD;
                 }
                 restrictedD = prevTurnRestrictedD;
             }
         }
         // We need to scan forward in the leg sequence to compute any restrictions on D imposed by later turns.
         if (toLegCalc.flightPath.length === 1 && (toLegCalc.egress.length === 0 || BitFlags.isAll(toLegCalc.egress[0].flags, FlightPathVectorFlags.LegToLegTurn))) {
             const nextLegCalc = (_a = legs[toIndex + 1]) === null || _a === void 0 ? void 0 : _a.calculated;
             const nextVector = nextLegCalc === null || nextLegCalc === void 0 ? void 0 : nextLegCalc.flightPath[0];
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             if (nextVector && (nextLegCalc.ingress.length === 0 || BitFlags.isAll(nextLegCalc.ingress[0].flags, FlightPathVectorFlags.LegToLegTurn))) {
                 let nextTurnRestrictedD;
                 if (!FlightPathUtils.isVectorGreatCircle(nextVector)) {
                     nextTurnRestrictedD = UnitType.METER.convertTo(toTrack.distance / 2, UnitType.GA_RADIAN);
                 }
                 else {
                     // if the next turn to share a vector with this turn is to a track vector, we need to recursively compute
                     // future turns since the next turn may be restricted by the turn after that, etc.
                     lastComputedIndex = this.computeTrackTrackTurn(legs, toIndex, toIndex + 1, toTrack, nextVector, desiredTurnRadius, true, tanTheta);
                     turnVertexPoint.set(fromTrack.endLat, fromTrack.endLon);
                     const nextTurnEgress = toLegCalc.egress[0];
                     nextTurnRestrictedD = nextTurnEgress ? turnVertexPoint.distance(nextTurnEgress.startLat, nextTurnEgress.startLon) : Infinity;
                 }
                 restrictedD = Math.min(restrictedD, nextTurnRestrictedD);
             }
         }
         const D = Math.min(desiredD, restrictedD, UnitType.METER.convertTo(fromTrack.distance, UnitType.GA_RADIAN), UnitType.METER.convertTo(toTrack.distance, UnitType.GA_RADIAN));
         // distance from the turn vertex to the center of the turn
         const H = Math.atan(Math.tan(D) / Math.cos(theta * Avionics.Utils.DEG2RAD));
         const turnRadiusRad = desiredD === D
             ? UnitType.METER.convertTo(desiredTurnRadius, UnitType.GA_RADIAN)
             : Math.atan(Math.sin(D) * tanTheta);
         const turnDirection = NavMath.getTurnDirection(fromTrackBearing, toTrackBearing);
         const turnBisectorBearing = toTrackBearing + theta * (turnDirection === 'left' ? -1 : 1);
         const turnCenter = turnVertexPoint.offset(turnBisectorBearing, H, FlightPathTurnCalculator.geoPointCache[1]);
         const fromTrackPath = FlightPathTurnCalculator.geoCircleCache[0].setAsGreatCircle(turnVertexPoint, fromTrackBearing);
         const toTrackPath = FlightPathTurnCalculator.geoCircleCache[1].setAsGreatCircle(turnVertexPoint, toTrackBearing);
         const turnStart = fromTrackPath.closest(turnCenter, FlightPathTurnCalculator.geoPointCache[2]);
         const turnMiddle = turnVertexPoint.offset(turnBisectorBearing, H - turnRadiusRad, FlightPathTurnCalculator.geoPointCache[3]);
         const turnEnd = toTrackPath.closest(turnCenter, FlightPathTurnCalculator.geoPointCache[4]);
         this.setAnticipatedTurn(fromLegCalc, toLegCalc, turnDirection, UnitType.GA_RADIAN.convertTo(turnRadiusRad, UnitType.METER), turnCenter, turnStart, turnMiddle, turnEnd);
         return lastComputedIndex;
     }
     /**
      * Computes a leg-to-leg course reversal.
      * @param legs The sequence of legs to which the turn belongs.
      * @param fromIndex The index of the leg on which the turn begins.
      * @param toIndex The index of the leg on which the turn ends.
      * @param fromTrack The track vector on which the turn begins.
      * @param toTrack The track vector on which the turn ends.
      * @param fromTrackBearing The true course bearing of the track vector on which the turn begins, at the end of the vector.
      * @param toTrackBearing The true course bearing of the track vector on which the turn ends, at the beginning of the vector.
      * @param desiredTurnRadius The desired turn radius, in meters.
      * @returns The index of the last leg in the sequence for which a turn ending on that leg was computed.
      */
     computeTrackTrackCourseReversal(legs, fromIndex, toIndex, fromTrack, toTrack, fromTrackBearing, toTrackBearing, desiredTurnRadius) {
         var _a;
         let lastComputedIndex = toIndex;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const fromLegCalc = legs[fromIndex].calculated;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const toLegCalc = legs[toIndex].calculated;
         const turnVertexPoint = FlightPathTurnCalculator.geoPointCache[0].set(fromTrack.endLat, fromTrack.endLon);
         fromLegCalc.egress.length = 0;
         fromLegCalc.egressJoinIndex = -1;
         // Let the course reversal "cut"
         let courseReversalEndDistance = UnitType.METER.convertTo(toLegCalc.flightPath[0].distance, UnitType.GA_RADIAN);
         if (toLegCalc.flightPath.length === 1 && (toLegCalc.egress.length === 0 || BitFlags.isAll(toLegCalc.egress[0].flags, FlightPathVectorFlags.LegToLegTurn))) {
             const nextLegCalc = (_a = legs[toIndex + 1]) === null || _a === void 0 ? void 0 : _a.calculated;
             const nextVector = nextLegCalc === null || nextLegCalc === void 0 ? void 0 : nextLegCalc.flightPath[0];
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             if (nextVector && (nextLegCalc.ingress.length === 0 || BitFlags.isAll(nextLegCalc.ingress[0].flags, FlightPathVectorFlags.LegToLegTurn))) {
                 if (!FlightPathUtils.isVectorGreatCircle(nextVector)) {
                     courseReversalEndDistance = UnitType.METER.convertTo(toTrack.distance / 2, UnitType.GA_RADIAN);
                 }
                 else {
                     // if the next turn to share a vector with this turn is to a track vector, we need to recursively compute
                     // future turns since the next turn may be restricted by the turn after that, etc.
                     lastComputedIndex = this.computeTrackTrackTurn(legs, toIndex, toIndex + 1, toTrack, nextVector, desiredTurnRadius, false);
                     turnVertexPoint.set(fromTrack.endLat, fromTrack.endLon);
                     const nextTurnEgress = toLegCalc.egress[0];
                     courseReversalEndDistance = nextTurnEgress ? turnVertexPoint.distance(nextTurnEgress.startLat, nextTurnEgress.startLon) : courseReversalEndDistance;
                 }
             }
         }
         const fromTrackPath = FlightPathTurnCalculator.geoCircleCache[0].setAsGreatCircle(turnVertexPoint, fromTrackBearing);
         const toTrackPath = FlightPathTurnCalculator.geoCircleCache[1].setAsGreatCircle(turnVertexPoint, toTrackBearing);
         const courseReversalEnd = toTrackPath.offsetDistanceAlong(turnVertexPoint, courseReversalEndDistance, FlightPathTurnCalculator.vector3Cache[0]);
         const turnDirection = NavMath.diffAngle(toTrackBearing, fromTrackBearing) < 0 ? 'left' : 'right';
         const length = this.procTurnBuilder.build(toLegCalc.ingress, 0, turnVertexPoint, fromTrackPath, courseReversalEnd, toTrackPath, fromTrackBearing + 45 * (turnDirection === 'left' ? -1 : 1), desiredTurnRadius, turnDirection, fromTrackBearing, toTrackBearing, FlightPathVectorFlags.LegToLegTurn | FlightPathVectorFlags.CourseReversal);
         toLegCalc.ingress.length = length;
         toLegCalc.ingressJoinIndex = 0;
         return lastComputedIndex;
     }
     /**
      * Calculates a leg to leg turn between an arc vector and a track vector.
      * @param legs The sequence of legs to which the turn belongs.
      * @param fromIndex The index of the leg on which the turn begins.
      * @param toIndex The index of the leg on which the turn ends.
      * @param arc The arc vector.
      * @param track The track vector.
      * @param isArcFirst Whether the arc vector precedes the track vector (i.e. whether the arc vector is the vector on
      * which the turn begins).
      * @param desiredTurnRadius The desired turn radius, in meters.
      * @returns the index of the last leg in the sequence for which a turn ending on that leg was computed.
      */
     computeArcTrackTurn(legs, fromIndex, toIndex, arc, track, isArcFirst, desiredTurnRadius) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const fromLegCalc = legs[fromIndex].calculated;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const toLegCalc = legs[toIndex].calculated;
         if (arc.distance === 0 || track.distance === 0) {
             this.setEmptyTurn(fromLegCalc, toLegCalc);
             return toIndex;
         }
         /*
          * Theory: find the center of the turn circle. Since the turn circle is tangent to both the arc circle and the
          * great circle defined by the track path, it follows that the center of the turn circle lies at a distance equal
          * to the turn radius from both the arc circle and great circle. Therefore, we can find the center by applying a
          * radial offset equal to +/-[turn radius] to both the arc circle and great circle (the sign of the offset depends
          * on the direction of the arc and track path) and solving for the points of intersection between the offset
          * circles.
          */
         const fromVector = isArcFirst ? arc : track;
         const toVector = isArcFirst ? track : arc;
         const fromVectorEndPoint = FlightPathTurnCalculator.geoPointCache[0].set(fromVector.endLat, fromVector.endLon);
         const toVectorStartPoint = FlightPathTurnCalculator.geoPointCache[1].set(toVector.startLat, toVector.startLon);
         if (!fromVectorEndPoint.equals(toVectorStartPoint, 1e-5)) {
             return toIndex;
         }
         const fromVectorEndBearing = FlightPathUtils.getVectorFinalCourse(fromVector);
         const toVectorStartBearing = FlightPathUtils.getVectorInitialCourse(toVector);
         const vectorBearingDiff = Math.abs(NavMath.diffAngle(fromVectorEndBearing, toVectorStartBearing));
         if (vectorBearingDiff < 1) {
             this.setEmptyTurn(fromLegCalc, toLegCalc);
             return toIndex;
         }
         const circle = FlightPathUtils.setGeoCircleFromVector(arc, FlightPathTurnCalculator.geoCircleCache[0]);
         const arcCenter = FlightPathUtils.getTurnCenterFromCircle(circle, FlightPathTurnCalculator.geoPointCache[2]);
         const arcRadius = FlightPathUtils.getTurnRadiusFromCircle(circle);
         const arcDirection = FlightPathUtils.getTurnDirectionFromCircle(circle);
         // define the circles
         const arcCircle = FlightPathTurnCalculator.geoCircleCache[0].set(arcCenter, arcRadius);
         const trackPath = FlightPathUtils.setGeoCircleFromVector(track, FlightPathTurnCalculator.geoCircleCache[1]);
         const trackPathNormalPoint = FlightPathTurnCalculator.geoPointCache[3].setFromCartesian(trackPath.center);
         const arcStartRadial = arcCircle.bearingAt(FlightPathTurnCalculator.geoPointCache[4].set(arc.startLat, arc.startLon), Math.PI) + 90;
         const arcEndRadial = arcCircle.bearingAt(FlightPathTurnCalculator.geoPointCache[4].set(arc.endLat, arc.endLon), Math.PI) + 90;
         // calculate whether the arc intersects the track; if they don't (or if they are entirely coincident), something
         // has gone wrong!
         const arcTrackIntersectionCount = arcCircle.numIntersectionPoints(trackPath);
         if (arcTrackIntersectionCount === 0 || isNaN(arcTrackIntersectionCount)) {
             this.setEmptyTurn(fromLegCalc, toLegCalc);
             return toIndex;
         }
         const turnDirection = NavMath.getTurnDirection(fromVectorEndBearing, toVectorStartBearing);
         let isInside;
         let turnRadiusRad;
         let arcCircleOffsetSign;
         let trackPathOffsetSign;
         if (arcTrackIntersectionCount === 1) {
             // arc circle and track path are tangent
             const isForward = Math.abs(NavMath.diffAngle(fromVectorEndBearing, toVectorStartBearing)) < 90;
             if (isForward) {
                 this.setEmptyTurn(fromLegCalc, toLegCalc);
                 return toIndex;
             }
             else {
                 // in this case, the plane effectively needs to make a 180...
                 isInside = false;
                 turnRadiusRad = UnitType.METER.convertTo(desiredTurnRadius, UnitType.GA_RADIAN);
                 arcCircleOffsetSign = 1;
                 trackPathOffsetSign = trackPath.encircles(arcCenter) ? -1 : 1;
             }
         }
         else {
             const desiredTurnRadiusRad = UnitType.METER.convertTo(desiredTurnRadius, UnitType.GA_RADIAN);
             isInside = isArcFirst
                 ? Math.abs(NavMath.diffAngle(fromVectorEndPoint.bearingFrom(arcCenter), toVectorStartBearing)) >= 90
                 : Math.abs(NavMath.diffAngle(fromVectorEndPoint.bearingFrom(arcCenter), fromVectorEndBearing)) < 90;
             /**
              * Now we must calculate the maximum allowed turn radius such that the turn does not start or end beyond the
              * limits of the arc or track. First, we convert the track limit to a pseudo-arc limit, then take the more
              * restrictive of the pseudo-arc limit and the actual arc limit. This maximally restrictive arc limit is then
              * used to compute the turn radius that would result in a turn which has an endpoint exactly at the limit.
              */
             const turnVertexRadialNormal = GeoCircle.getGreatCircleNormal(arcCenter, fromVectorEndPoint, FlightPathTurnCalculator.vector3Cache[0]);
             // if the turn is inside the arc, then clamp track limit distance to half the length of the track path within the
             // arc, since that is the point at which turn radius is maximized.
             const maxTrackLimitDistance = isInside
                 ? Math.atan(Math.abs(Vec3Math.dot(trackPath.center, turnVertexRadialNormal)) * Math.tan(arcRadius))
                 : Infinity;
             const trackLimitDistance = Math.min(UnitType.METER.convertTo(track.distance / 2, UnitType.GA_RADIAN), maxTrackLimitDistance);
             const trackLimitPoint = isArcFirst
                 ? toVectorStartPoint.offset(toVectorStartBearing, trackLimitDistance, FlightPathTurnCalculator.geoPointCache[4])
                 : fromVectorEndPoint.offset(fromVectorEndBearing + 180, trackLimitDistance, FlightPathTurnCalculator.geoPointCache[4]);
             // the great circle which passes through the center of the arc and is perpendicular to the track
             const trackPerpendicularDiameter = FlightPathTurnCalculator.geoCircleCache[2].set(Vec3Math.cross(Vec3Math.multScalar(trackPath.center, (isArcFirst === isInside ? -1 : 1), FlightPathTurnCalculator.vector3Cache[1]), arcCircle.center, FlightPathTurnCalculator.vector3Cache[1]), Math.PI / 2);
             const antipodes = FlightPathTurnCalculator.intersectionVecArrayCache;
             trackPerpendicularDiameter.intersection(arcCircle, antipodes);
             // compute the great circle which passes through the appropriate antipode and the track limit endpoint. The
             // intersection of this great circle with the arc that is NOT the antipode is the pseudo-arc limit endpoint.
             const intersectingPath = FlightPathTurnCalculator.geoCircleCache[3].setAsGreatCircle(isInside === (turnDirection === 'left') ? antipodes[0] : antipodes[1], trackLimitPoint);
             const arcIntersections = FlightPathTurnCalculator.intersectionGeoPointArrayCache;
             const numArcIntersections = intersectingPath.intersectionGeoPoint(arcCircle, arcIntersections);
             let arcLimitAngularWidth = Infinity;
             if (numArcIntersections > 0) {
                 const pseudoArcLimitPoint = arcIntersections[0];
                 const pseudoArcLimitPointAngle = arcCenter.bearingTo(pseudoArcLimitPoint);
                 arcLimitAngularWidth = Math.abs(NavMath.diffAngle((isArcFirst ? arcEndRadial : arcStartRadial), pseudoArcLimitPointAngle));
             }
             let arcTurnRadiusLimit = 0;
             const arcAngularWidth = ((arcDirection === 'left' ? (arcStartRadial - arcEndRadial) : (arcEndRadial - arcStartRadial)) + 360) % 360;
             arcLimitAngularWidth = Math.min(arcLimitAngularWidth, arcAngularWidth / 2);
             if (arcLimitAngularWidth > 0) {
                 const arcLimitPointAngle = arcStartRadial + arcLimitAngularWidth * (arcDirection === 'left' ? -1 : 1);
                 const arcLimitPoint = arcCenter.offset(arcLimitPointAngle, arcRadius, FlightPathTurnCalculator.geoPointCache[4]);
                 const arcLimitRadialPath = FlightPathTurnCalculator.geoCircleCache[2].setAsGreatCircle(arcCenter, arcLimitPoint);
                 // the angle between the radial to the arc endpoint and the track path (directed away from the arc at the point of intersection)
                 const theta = Math.acos(Vec3Math.dot(arcLimitRadialPath.center, trackPath.center) * (isArcFirst === isInside ? -1 : 1));
                 if (theta >= Math.PI / 2) {
                     if (isInside) {
                         const d = Math.asin(Math.sin(Math.acos(Math.abs(Vec3Math.dot(trackPath.center, turnVertexRadialNormal)))) * Math.sin(arcRadius));
                         arcTurnRadiusLimit = (arcRadius - d) / 2;
                     }
                     else {
                         arcTurnRadiusLimit = Infinity;
                     }
                 }
                 else {
                     const arcLimitRadialTrackIntersections = FlightPathTurnCalculator.intersectionVecArrayCache;
                     arcLimitRadialPath.intersection(trackPath, arcLimitRadialTrackIntersections);
                     const arcLimitPointVec = arcLimitPoint.toCartesian(FlightPathTurnCalculator.vector3Cache[0]);
                     const thresholdNormal = Vec3Math.normalize(Vec3Math.cross(arcLimitRadialPath.center, arcLimitPointVec, FlightPathTurnCalculator.vector3Cache[1]), FlightPathTurnCalculator.vector3Cache[1]);
                     const arcLimitRadialTrackIntersection = arcLimitRadialTrackIntersections[Vec3Math.dot(arcLimitRadialTrackIntersections[0], thresholdNormal) >= 0 ? 0 : 1];
                     // cosine of the distance from the arc endpoint to the intersection of the radial to the arc endpoint and the track path
                     const cosD = Vec3Math.dot(arcLimitRadialTrackIntersection, arcLimitPointVec);
                     const sinTheta = Math.sin(theta);
                     const sign = isInside ? -1 : 1;
                     arcTurnRadiusLimit = Math.acos((1 + sinTheta * cosD * sign) / Math.sqrt(1 + 2 * sinTheta * cosD * sign + sinTheta * sinTheta));
                 }
             }
             turnRadiusRad = Math.min(desiredTurnRadiusRad, arcTurnRadiusLimit);
             arcCircleOffsetSign = isInside ? -1 : 1;
             trackPathOffsetSign = turnDirection === 'left' ? -1 : 1;
         }
         if (turnRadiusRad === 0) {
             this.setEmptyTurn(fromLegCalc, toLegCalc);
             return toIndex;
         }
         const arcCircleOffset = FlightPathTurnCalculator.geoCircleCache[2].set(arcCircle.center, arcCircle.radius + turnRadiusRad * arcCircleOffsetSign);
         const trackPathOffset = FlightPathTurnCalculator.geoCircleCache[3].set(trackPath.center, trackPath.radius + turnRadiusRad * trackPathOffsetSign);
         const intersections = FlightPathTurnCalculator.intersectionGeoPointArrayCache;
         const intersectionCount = arcCircleOffset.intersectionGeoPoint(trackPathOffset, FlightPathTurnCalculator.intersectionGeoPointArrayCache);
         if (intersectionCount === 0) {
             this.setEmptyTurn(fromLegCalc, toLegCalc);
             return toIndex;
         }
         let turnCenter;
         if (intersectionCount === 2) {
             if (arcTrackIntersectionCount === 1 || fromVectorEndPoint.distance(intersections[0]) >= fromVectorEndPoint.distance(intersections[1])) {
                 turnCenter = intersections[1];
             }
             else {
                 turnCenter = intersections[0];
             }
         }
         else {
             turnCenter = intersections[0];
         }
         const arcTangentBearing = (turnCenter.bearingTo(arcCenter) + (arcCircleOffsetSign === 1 ? 0 : 180)) % 360;
         const trackTangentBearing = (turnCenter.bearingTo(trackPathNormalPoint) + (trackPathOffsetSign === 1 ? 0 : 180)) % 360;
         const turnStartBearing = isArcFirst ? arcTangentBearing : trackTangentBearing;
         const turnEndBearing = isArcFirst ? trackTangentBearing : arcTangentBearing;
         let turnAngularDelta = turnEndBearing - turnStartBearing;
         if (turnDirection === 'right' && turnEndBearing <= turnStartBearing) {
             turnAngularDelta += 360;
         }
         else if (turnDirection === 'left' && turnEndBearing >= turnStartBearing) {
             turnAngularDelta -= 360;
         }
         const turnMiddleBearing = ((turnStartBearing + turnAngularDelta / 2) + 360) % 360;
         const turnStart = turnCenter.offset(turnStartBearing, turnRadiusRad, FlightPathTurnCalculator.geoPointCache[0]);
         const turnEnd = turnCenter.offset(turnEndBearing, turnRadiusRad, FlightPathTurnCalculator.geoPointCache[1]);
         const turnMiddle = turnCenter.offset(turnMiddleBearing, turnRadiusRad, FlightPathTurnCalculator.geoPointCache[2]);
         turnRadiusRad = UnitType.GA_RADIAN.convertTo(turnRadiusRad, UnitType.METER);
         this.setAnticipatedTurn(fromLegCalc, toLegCalc, turnDirection, turnRadiusRad, turnCenter, turnStart, turnMiddle, turnEnd);
         return toIndex;
     }
     /**
      * Removes all ingress and egress flight path vectors from a pair of legs at their junction.
      * @param fromLegCalc The calculations for the leg on which the turn begins.
      * @param toLegCalc The calculations for the leg on which the turn ends.
      */
     setEmptyTurn(fromLegCalc, toLegCalc) {
         fromLegCalc.egress.length = 0;
         fromLegCalc.egressJoinIndex = -1;
         toLegCalc.ingress.length = 0;
         toLegCalc.ingressJoinIndex = -1;
     }
     /**
      * Adds flight path vectors to a pair of legs for an anticipated leg to leg turn.
      * @param fromLegCalc The calculations for the leg on which the turn begins.
      * @param toLegCalc The calculations for the leg on which the turn ends.
      * @param direction The direction of the turn.
      * @param radius The radius of the turn, in meters.
      * @param center The location of the center of the turn.
      * @param start The location of the start of the turn.
      * @param middle The location of the midpoint of the turn.
      * @param end The location of the end of the turn.
      */
     setAnticipatedTurn(fromLegCalc, toLegCalc, direction, radius, center, start, middle, end) {
         var _a, _b;
         var _c, _d;
         const egress = (_a = (_c = fromLegCalc.egress)[0]) !== null && _a !== void 0 ? _a : (_c[0] = FlightPathUtils.createEmptyCircleVector());
         const ingress = (_b = (_d = toLegCalc.ingress)[0]) !== null && _b !== void 0 ? _b : (_d[0] = FlightPathUtils.createEmptyCircleVector());
         fromLegCalc.egress.length = 1;
         toLegCalc.ingress.length = 1;
         const circle = FlightPathUtils.getTurnCircle(center, UnitType.METER.convertTo(radius, UnitType.GA_RADIAN), direction, FlightPathTurnCalculator.setAnticipatedTurnCache.geoCircle[0]);
         const flags = FlightPathVectorFlags.LegToLegTurn | FlightPathVectorFlags.AnticipatedTurn;
         FlightPathUtils.setCircleVector(egress, circle, start, middle, flags);
         FlightPathUtils.setCircleVector(ingress, circle, middle, end, flags);
         fromLegCalc.egressJoinIndex = fromLegCalc.flightPath.length - 1;
         toLegCalc.ingressJoinIndex = 0;
     }
 }
 FlightPathTurnCalculator.vector3Cache = [new Float64Array(3), new Float64Array(3)];
 FlightPathTurnCalculator.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
 FlightPathTurnCalculator.geoCircleCache = [
     new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0),
     new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0)
 ];
 FlightPathTurnCalculator.intersectionVecArrayCache = [new Float64Array(3), new Float64Array(3)];
 FlightPathTurnCalculator.intersectionGeoPointArrayCache = [new GeoPoint(0, 0), new GeoPoint(0, 0)];
 FlightPathTurnCalculator.setAnticipatedTurnCache = {
     geoPoint: [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)],
     geoCircle: [new GeoCircle(new Float64Array(3), 0), new GeoCircle(new Float64Array(3), 0)]
 };
 
 /* eslint-disable @typescript-eslint/no-non-null-assertion */
 /**
  * Calculates the flight path vectors for a given set of legs.
  */
 class FlightPathCalculator {
     /**
      * Creates an instance of the FlightPathCalculator.
      * @param facilityLoader The facility loader to use with this instance.
      * @param options The options to use with this flight path calculator.
      */
     constructor(facilityLoader, options) {
         this.facilityLoader = facilityLoader;
         this.options = options;
         this.facilityCache = new Map();
         this.legCalculatorMap = this.createLegCalculatorMap();
         this.turnCalculator = new FlightPathTurnCalculator();
         this.state = new FlightPathStateClass();
     }
     /**
      * Creates a map from leg types to leg calculators.
      * @returns A map from leg types to leg calculators.
      */
     createLegCalculatorMap() {
         let calc;
         return {
             [LegType.Unknown]: calc = new TrackToFixLegCalculator(this.facilityCache),
             [LegType.IF]: calc,
             [LegType.TF]: calc,
             [LegType.AF]: new ArcToFixLegCalculator(this.facilityCache),
             [LegType.CD]: calc = new CourseToDMELegCalculator(this.facilityCache),
             [LegType.VD]: calc,
             [LegType.CF]: new CourseToFixLegCalculator(this.facilityCache),
             [LegType.CR]: calc = new CourseToRadialLegCalculator(this.facilityCache),
             [LegType.VR]: calc,
             [LegType.FC]: new TrackFromFixLegCalculator(this.facilityCache),
             [LegType.FD]: new FixToDMELegCalculator(this.facilityCache),
             [LegType.RF]: new RadiusToFixLegCalculator(this.facilityCache),
             [LegType.DF]: new DirectToFixLegCalculator(this.facilityCache),
             [LegType.FA]: calc = new CourseToAltitudeLegCalculator(this.facilityCache),
             [LegType.CA]: calc,
             [LegType.VA]: calc,
             [LegType.FM]: calc = new CourseToManualLegCalculator(this.facilityCache),
             [LegType.VM]: calc,
             [LegType.CI]: calc = new CourseToInterceptLegCalculator(this.facilityCache),
             [LegType.VI]: calc,
             [LegType.PI]: new ProcedureTurnLegCalculator(this.facilityCache),
             [LegType.HA]: calc = new HoldLegCalculator(this.facilityCache),
             [LegType.HM]: calc,
             [LegType.HF]: calc,
             [LegType.Discontinuity]: new DiscontinuityLegCalculator(this.facilityCache)
         };
     }
     /**
      * Calculates a flight path for a given set of flight plan legs.
      * @param legs The legs of the flight plan and/or procedure.
      * @param activeLegIndex The index of the active leg.
      * @param initialIndex The index of the leg to start at.
      * @param count The number of legs to calculate.
      */
     async calculateFlightPath(legs, activeLegIndex, initialIndex = 0, count = Number.POSITIVE_INFINITY) {
         count = Math.min(legs.length - initialIndex, count);
         this.state.updatePlaneState(this.options);
         await this.loadFacilities(legs, initialIndex, count);
         this.initCurrentLatLon(legs, initialIndex);
         this.initCurrentCourse(legs, initialIndex);
         this.calculateLegPaths(legs, activeLegIndex, initialIndex, count);
         this.turnCalculator.computeTurns(legs, initialIndex, count, this.state.desiredTurnRadius.asUnit(UnitType.METER));
         this.resolveLegsIngressToEgress(legs, initialIndex, count);
         this.updateLegDistances(legs, initialIndex, count);
     }
     /**
      * Loads facilities required for flight path calculations from the flight plan.
      * @param legs The legs of the flight plan to calculate.
      * @param initialIndex The index of the first leg to calculate.
      * @param count The number of legs to calculate.
      */
     async loadFacilities(legs, initialIndex, count) {
         const facilityPromises = [];
         for (let i = initialIndex; i < initialIndex + count; i++) {
             this.stageFacilityLoad(legs[i].leg.fixIcao, facilityPromises);
             this.stageFacilityLoad(legs[i].leg.originIcao, facilityPromises);
             this.stageFacilityLoad(legs[i].leg.arcCenterFixIcao, facilityPromises);
         }
         if (facilityPromises.length > 0) {
             await Promise.all(facilityPromises);
         }
     }
     /**
      * Initializes the current lat/lon.
      * @param legs The legs of the flight plan to calculate.
      * @param initialIndex The index of the first leg to calculate.
      */
     initCurrentLatLon(legs, initialIndex) {
         var _a;
         var _b;
         let index = Math.min(initialIndex, legs.length);
         while (--index >= 0) {
             const leg = legs[index];
             if (leg.leg.type === LegType.Discontinuity) {
                 break;
             }
             const calc = leg.calculated;
             if (calc && calc.endLat !== undefined && calc.endLon !== undefined) {
                 ((_a = (_b = this.state).currentPosition) !== null && _a !== void 0 ? _a : (_b.currentPosition = new GeoPoint(0, 0))).set(calc.endLat, calc.endLon);
                 return;
             }
         }
         this.state.currentPosition = undefined;
     }
     /**
      * Initializes the current course.
      * @param legs The legs of the flight plan to calculate.
      * @param initialIndex The index of the first leg to calculate.
      */
     initCurrentCourse(legs, initialIndex) {
         let index = Math.min(initialIndex, legs.length);
         while (--index >= 0) {
             const leg = legs[index];
             if (leg.leg.type === LegType.Discontinuity) {
                 return;
             }
             const legCalc = leg.calculated;
             if (legCalc && legCalc.flightPath.length > 0) {
                 this.state.currentCourse = FlightPathUtils.getLegFinalCourse(legCalc);
                 if (this.state.currentCourse !== undefined) {
                     return;
                 }
             }
         }
         this.state.currentCourse = undefined;
     }
     /**
      * Calculates flight paths for a sequence of flight plan legs.
      * @param legs A sequence of flight plan legs.
      * @param activeLegIndex The index of the active leg.
      * @param initialIndex The index of the first leg to calculate.
      * @param count The number of legs to calculate.
      */
     calculateLegPaths(legs, activeLegIndex, initialIndex, count) {
         const end = initialIndex + count;
         for (let i = initialIndex; i < end; i++) {
             this.calculateLegPath(legs, i, activeLegIndex);
         }
     }
     /**
      * Calculates a flight path for a leg in a sequence of legs.
      * @param legs A sequence of flight plan legs.
      * @param calculateIndex The index of the leg to calculate.
      * @param activeLegIndex The index of the active leg.
      */
     calculateLegPath(legs, calculateIndex, activeLegIndex) {
         const definition = legs[calculateIndex];
         const calcs = this.legCalculatorMap[definition.leg.type].calculate(legs, calculateIndex, activeLegIndex, this.state, false);
         const start = calcs.flightPath[0];
         const end = calcs.flightPath[calcs.flightPath.length - 1];
         calcs.initialDtk = undefined;
         if (start !== undefined) {
             const trueDtk = FlightPathUtils.getVectorInitialCourse(start);
             if (!isNaN(trueDtk)) {
                 calcs.initialDtk = MagVar.trueToMagnetic(trueDtk, start.startLat, start.startLon);
             }
         }
         calcs.startLat = start === null || start === void 0 ? void 0 : start.startLat;
         calcs.startLon = start === null || start === void 0 ? void 0 : start.startLon;
         calcs.endLat = end === null || end === void 0 ? void 0 : end.endLat;
         calcs.endLon = end === null || end === void 0 ? void 0 : end.endLon;
         if (!end && this.state.currentPosition) {
             calcs.endLat = this.state.currentPosition.lat;
             calcs.endLon = this.state.currentPosition.lon;
         }
     }
     /**
      * Resolves the ingress to egress vectors for a set of flight plan legs.
      * @param legs A sequence of flight plan legs.
      * @param initialIndex The index of the first leg to resolve.
      * @param count The number of legs to resolve.
      */
     resolveLegsIngressToEgress(legs, initialIndex, count) {
         const end = initialIndex + count;
         for (let i = initialIndex; i < end; i++) {
             const legCalc = legs[i].calculated;
             legCalc && FlightPathUtils.resolveIngressToEgress(legCalc);
         }
     }
     /**
      * Updates leg distances with turn anticipation.
      * @param legs A sequence of flight plan legs.
      * @param initialIndex The index of the first leg to update.
      * @param count The number of legs to update.
      */
     updateLegDistances(legs, initialIndex, count) {
         var _a, _b, _c, _d, _e, _f;
         const end = initialIndex + count;
         for (let i = initialIndex; i < end; i++) {
             const leg = legs[i];
             const calc = leg.calculated;
             // Calculate distance without transitions
             calc.distance = 0;
             const len = calc.flightPath.length;
             for (let j = 0; j < len; j++) {
                 calc.distance += calc.flightPath[j].distance;
             }
             calc.cumulativeDistance = calc.distance + ((_c = (_b = (_a = legs[i - 1]) === null || _a === void 0 ? void 0 : _a.calculated) === null || _b === void 0 ? void 0 : _b.cumulativeDistance) !== null && _c !== void 0 ? _c : 0);
             // Calculate distance with transitions
             calc.distanceWithTransitions = 0;
             const ingressLen = calc.ingress.length;
             for (let j = 0; j < ingressLen; j++) {
                 calc.distanceWithTransitions += calc.ingress[j].distance;
             }
             const ingressToEgressLen = calc.ingressToEgress.length;
             for (let j = 0; j < ingressToEgressLen; j++) {
                 calc.distanceWithTransitions += calc.ingressToEgress[j].distance;
             }
             const egressLen = calc.egress.length;
             for (let j = 0; j < egressLen; j++) {
                 calc.distanceWithTransitions += calc.egress[j].distance;
             }
             calc.cumulativeDistanceWithTransitions = calc.distanceWithTransitions + ((_f = (_e = (_d = legs[i - 1]) === null || _d === void 0 ? void 0 : _d.calculated) === null || _e === void 0 ? void 0 : _e.cumulativeDistanceWithTransitions) !== null && _f !== void 0 ? _f : 0);
         }
     }
     /**
      * Stages a facility to be loaded.
      * @param icao The ICAO of the facility.
      * @param facilityPromises The array of facility load promises to push to.
      */
     stageFacilityLoad(icao, facilityPromises) {
         if (icao !== ICAO.emptyIcao && !this.facilityCache.has(icao)) {
             let facilityType;
             try {
                 facilityType = ICAO.getFacilityType(icao);
             }
             catch (err) {
                 //console.log(err);
             }
             if (facilityType !== undefined) {
                 try {
                     facilityPromises.push(this.facilityLoader.getFacility(ICAO.getFacilityType(icao), icao)
                         .then(facility => {
                         this.facilityCache.set(facility.icao, facility);
                         return true;
                     })
                         .catch(() => false));
                 }
                 catch (err) {
                     //console.log(err);
                 }
             }
         }
     }
 }
 /**
  *
  */
 class FlightPathStateClass {
     constructor() {
         this._planePosition = new GeoPoint(0, 0);
         this.planePosition = this._planePosition.readonly;
         this._planeHeading = 0;
         this._planeAltitude = UnitType.FOOT.createNumber(0);
         this.planeAltitude = this._planeAltitude.readonly;
         this._planeSpeed = UnitType.KNOT.createNumber(0);
         this.planeSpeed = this._planeSpeed.readonly;
         this._planeClimbRate = UnitType.FPM.createNumber(0);
         this.planeClimbRate = this._planeClimbRate.readonly;
         this._desiredTurnRadius = UnitType.METER.createNumber(0);
         this.desiredTurnRadius = this._desiredTurnRadius.readonly;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     get planeHeading() {
         return this._planeHeading;
     }
     /**
      * Updates this state with the latest information on the airplane.
      * @param options Flight path calculator options.
      */
     updatePlaneState(options) {
         this._planePosition.set(SimVar.GetSimVarValue('PLANE LATITUDE', SimVarValueType.Degree), SimVar.GetSimVarValue('PLANE LONGITUDE', SimVarValueType.Degree));
         this._planeAltitude.set(SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet'));
         this._planeHeading = SimVar.GetSimVarValue('PLANE HEADING DEGREES TRUE', 'degree');
         this._planeSpeed.set(Math.max(SimVar.GetSimVarValue('GROUND VELOCITY', SimVarValueType.Knots), options.defaultSpeed));
         this._planeClimbRate.set(Math.max(SimVar.GetSimVarValue('VERTICAL SPEED', 'feet per minute'), options.defaultClimbRate));
         this._desiredTurnRadius.set(NavMath.turnRadius(this._planeSpeed.asUnit(UnitType.KNOT), options.bankAngle));
     }
 }
 
 var PlanChangeType;
 (function (PlanChangeType) {
     PlanChangeType["Added"] = "Added";
     PlanChangeType["Inserted"] = "Inserted";
     PlanChangeType["Removed"] = "Removed";
     PlanChangeType["Changed"] = "Changed";
     PlanChangeType["Cleared"] = "Cleared";
 })(PlanChangeType || (PlanChangeType = {}));
 var ActiveLegType;
 (function (ActiveLegType) {
     ActiveLegType["Lateral"] = "Lateral";
     ActiveLegType["Vertical"] = "Vertical";
     ActiveLegType["Calculating"] = "Calculating";
 })(ActiveLegType || (ActiveLegType = {}));
 var OriginDestChangeType;
 (function (OriginDestChangeType) {
     OriginDestChangeType["OriginAdded"] = "OriginAdded";
     OriginDestChangeType["OriginRemoved"] = "OriginRemoved";
     OriginDestChangeType["DestinationAdded"] = "DestinationAdded";
     OriginDestChangeType["DestinationRemoved"] = "DestinationRemoved";
 })(OriginDestChangeType || (OriginDestChangeType = {}));
 /**
  * A flight plan managed by the flight plan system.
  */
 class FlightPlan {
     /**
      * Creates an instance of a FlightPlan.
      * @param planIndex The index within the flight planner of this flight plan.
      * @param calculator The flight path calculator to use to calculate the flight path.
      */
     constructor(planIndex, calculator) {
         this.planIndex = planIndex;
         this.calculator = calculator;
         this._activeLateralLeg = 0;
         this._activeVerticalLeg = 0;
         this._activeCalculatingLeg = 0;
         /** The direct to metadata for this plan. */
         this.directToData = { segmentIndex: -1, segmentLegIndex: -1 };
         /** Events fired when the plan is modified. */
         this.events = {};
         /** A callback fired when a flight plan leg is to be named. */
         this.onLegNameRequested = this.buildDefaultLegName;
         /** The details about the selected procedures. */
         this.procedureDetails = new ProcedureDetails();
         /** The flight plan segments that make up this flight plan. */
         this.planSegments = [];
         /** User assignable data. */
         this.userData = {};
     }
     // eslint-disable-next-line jsdoc/require-returns
     /** The ICAO of the origin airport in the flight plan, if any. */
     get originAirport() {
         return this._originAirport;
     }
     // eslint-disable-next-line jsdoc/require-returns
     /** The ICAO of the destination airport in the flight plan, if any. */
     get destinationAirport() {
         return this._destinationAirport;
     }
     // eslint-disable-next-line jsdoc/require-returns
     /** The global index of the currently active lateral navigation leg. */
     get activeLateralLeg() {
         return this._activeLateralLeg;
     }
     // eslint-disable-next-line jsdoc/require-returns
     /** The global index of the currently active vertical navigation leg. */
     get activeVerticalLeg() {
         return this._activeVerticalLeg;
     }
     // eslint-disable-next-line jsdoc/require-returns
     /** The global index of the currently active calculating leg. */
     get activeCalculatingLeg() {
         return this._activeCalculatingLeg;
     }
     /**
      * Gets the current number of legs in the flight plan.
      * @returns The number of legs in the plan.
      */
     get length() {
         const segment = this.planSegments[this.planSegments.length - 1];
         if (segment !== undefined) {
             return segment.offset + segment.legs.length;
         }
         return 0;
     }
     /**
      * Gets the current number of segments in the flight plan.
      * @returns The number of legs in the plan.
      */
     get segmentCount() {
         return this.planSegments.length;
     }
     /**
      * Gets this flight plan's legs.
      * @param reverse Whether to get the legs in reverse order. False by default.
      * @param startIndex The global leg index of the leg with which to start. Defaults to 0 if `reverse` is false or
      * `this.length` if `reverse` is true.
      * @returns A generator which yields this flight plan's legs.
      */
     legs(reverse = false, startIndex) {
         return reverse ? this._legsReverse(startIndex) : this._legs(startIndex);
     }
     /**
      * Gets this flight plan's legs in forward order.
      * @param startIndex The global leg index of the leg with which to start. Defaults to 0.
      * @yields This flight plan's legs in forward order.
      */
     *_legs(startIndex) {
         startIndex !== null && startIndex !== void 0 ? startIndex : (startIndex = 0);
         for (let i = 0; i < this.planSegments.length; i++) {
             const segment = this.planSegments[i];
             if (segment !== undefined) {
                 for (let l = Math.max(0, startIndex - segment.offset); l < segment.legs.length; l++) {
                     yield segment.legs[l];
                 }
             }
         }
     }
     /**
      * Gets this flight plan's legs in reverse order.
      * @param startIndex The global leg index of the leg with which to start. Defaults to `this.length - 1`.
      * @yields This flight plan's legs in reverse order.
      */
     *_legsReverse(startIndex) {
         startIndex !== null && startIndex !== void 0 ? startIndex : (startIndex = this.length);
         for (let i = this.planSegments.length - 1; i >= 0; i--) {
             const segment = this.planSegments[i];
             if (segment) {
                 for (let l = Math.min(segment.legs.length - 1, startIndex - segment.offset); l >= 0; l--) {
                     yield segment.legs[l];
                 }
             }
         }
     }
     /**
      * Gets the segments for the flightplan.
      * @yields The flight plan segments.
      */
     *segments() {
         for (let i = 0; i < this.planSegments.length; i++) {
             const segment = this.planSegments[i];
             if (segment !== undefined) {
                 yield segment;
             }
         }
     }
     /**
      * Gets all of the segments of a given type.
      * @param segmentType The type of the segments to retrieve.
      * @yields The segments of the requested type.
      */
     *segmentsOfType(segmentType) {
         for (const segment of this.segments()) {
             if (segment.segmentType == segmentType) {
                 yield segment;
             }
         }
     }
     /**
      * Adds a segment to the flight plan at a specified index.
      * @param segmentIndex The index of the flight plan segment.
      * @param segmentType The type of segment this will be.
      * @param airway The airway this segment is made up of, if any.
      * @param notify Whether or not to send notifications after the operation.
      * @returns The new flight plan segment.
      */
     addSegment(segmentIndex, segmentType = FlightPlanSegmentType.Enroute, airway, notify = true) {
         const segment = new FlightPlanSegment(segmentIndex, -1, [], segmentType, airway);
         this.planSegments[segmentIndex] = segment;
         this.reflowSegmentOffsets();
         notify && this.events.onSegmentChanged && this.events.onSegmentChanged(segmentIndex, PlanChangeType.Added, segment);
         return segment;
     }
     /**
      * Inserts a segment into the flight plan at the specified index and
      * reflows the subsequent segments.
      * @param segmentIndex The index to insert the flight plan segment.
      * @param segmentType The type of segment this will be.
      * @param airway The airway this segment is made up of, if any
      * @param notify Whether or not to send notifications after the operation.
      * @returns The new flight plan segment.
      */
     insertSegment(segmentIndex, segmentType = FlightPlanSegmentType.Enroute, airway, notify = true) {
         const segment = this.planSegments[segmentIndex];
         if (segment !== undefined) {
             const newSegment = new FlightPlanSegment(segmentIndex, -1, [], segmentType, airway);
             this.planSegments.splice(segmentIndex, 0, newSegment);
             this.reflowSegments();
             this.reflowSegmentOffsets();
             notify && this.events.onSegmentChanged && this.events.onSegmentChanged(segmentIndex, PlanChangeType.Inserted, newSegment);
             return newSegment;
         }
         else {
             return this.addSegment(segmentIndex, segmentType, airway, notify);
         }
     }
     /**
      * Reflows the flight plan segments after an insert.
      */
     reflowSegments() {
         for (let i = 0; i < this.planSegments.length; i++) {
             const segment = this.planSegments[i];
             if (segment !== undefined && segment.segmentIndex !== i) {
                 segment.segmentIndex = i;
             }
         }
     }
     /**
      * Deletes a segment from the flight plan, leaving an empty segment at the specified index.
      * @param segmentIndex The index of the segment to remove.
      * @param notify Whether or not to send notifications after the operation.
      */
     deleteSegment(segmentIndex, notify = true) {
         const segment = this.planSegments[segmentIndex];
         if (segmentIndex === this.planSegments.length - 1) {
             this.planSegments.splice(segmentIndex, 1);
         }
         else {
             delete this.planSegments[segmentIndex];
         }
         if (this.directToData.segmentIndex === segmentIndex) ;
         this.reflowSegmentOffsets();
         notify && this.events.onSegmentChanged && this.events.onSegmentChanged(segmentIndex, PlanChangeType.Removed, segment);
     }
     /**
      * Removes a segment from the flight plan and reflows the segments following
      * the removed segment, not leaving an empty segment at the specified index.
      * @param segmentIndex The index of the segment to remove.
      * @param notify Whether or not to send notifications after the operation.
      */
     removeSegment(segmentIndex, notify = true) {
         const segment = this.planSegments[segmentIndex];
         this.planSegments.splice(segmentIndex, 1);
         this.reflowSegments();
         this.reflowSegmentOffsets();
         notify && this.events.onSegmentChanged && this.events.onSegmentChanged(segmentIndex, PlanChangeType.Removed, segment);
     }
     /**
      * Gets a flight plan segment from the plan.
      * @param segmentIndex The index of the segment to get; if not specified returns the active segment.
      * @returns The requested flight plan segment.
      * @throws An error if the flight plan segment could not be found.
      */
     getSegment(segmentIndex) {
         if (segmentIndex === undefined) {
             let calculatedSegmentIndex = 0;
             for (const segment of this.segments()) {
                 if (this.activeLateralLeg == 0 && segment.legs.length == 0) {
                     calculatedSegmentIndex++;
                 }
                 else if (this.activeLateralLeg > segment.offset + segment.legs.length) {
                     calculatedSegmentIndex++;
                 }
                 else {
                     break;
                 }
             }
             const segment = this.planSegments[calculatedSegmentIndex];
             if (segment !== undefined) {
                 return segment;
             }
         }
         else if (segmentIndex >= 0) {
             const segment = this.planSegments[segmentIndex];
             if (segment !== undefined) {
                 return segment;
             }
         }
         throw new Error(`Flight plan segment with segment index ${segmentIndex} could not be found.`);
     }
     /**
      * Adds a leg to the flight plan.
      * @param segmentIndex The segment to add the leg to.
      * @param leg The leg to add to the plan.
      * @param index The index of the leg in the segment to insert. Will add to the end of the segment if omitted.
      * @param flags Leg definition flags to apply to the new leg. Defaults to `None` (0).
      * @param notify Whether or not to send notifications after the operation.
      * @returns the leg that was added.
      */
     addLeg(segmentIndex, leg, index, flags = 0, notify = true) {
         const segment = this.getSegment(segmentIndex);
         const legDefinition = { leg, flags, verticalData: { altDesc: AltitudeRestrictionType.Unused, altitude1: 0, altitude2: 0 } };
         if (index === undefined) {
             segment.legs.push(legDefinition);
             index = segment.legs.length - 1;
         }
         else {
             segment.legs.splice(index, 0, legDefinition);
         }
         legDefinition.name = this.onLegNameRequested(leg);
         this.reflowSegmentOffsets();
         notify && this.events.onLegChanged && this.events.onLegChanged(segmentIndex, index, PlanChangeType.Added, legDefinition);
         return legDefinition;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     getLeg(arg1, arg2) {
         const leg = this._tryGetLeg(arg1, arg2);
         if (leg) {
             return leg;
         }
         throw new Error(`Leg with ${arg2 === undefined ? `index ${arg1}` : `segmentIndex ${arg1}, segmentLegIndex ${arg2}`} could not be found.`);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     tryGetLeg(arg1, arg2) {
         return this._tryGetLeg(arg1, arg2);
     }
     /**
      * Attempts to get a leg from the flight plan.
      * @param arg1 The global leg index of the leg to get, or the index of the segment containing the leg to get.
      * @param arg2 The index of the leg to get in its segment.
      * @returns A flight plan leg, or `null` if one could not be found at the specified index.
      */
     _tryGetLeg(arg1, arg2) {
         var _a, _b;
         if (arg2 === undefined) {
             const legIndex = arg1;
             for (const segment of this.segments()) {
                 if (segment.offset <= legIndex && legIndex < segment.offset + segment.legs.length) {
                     return segment.legs[legIndex - segment.offset];
                 }
             }
             return null;
         }
         else {
             const segmentIndex = arg1;
             const segmentLegIndex = arg2;
             return (_b = (_a = this.planSegments[segmentIndex]) === null || _a === void 0 ? void 0 : _a.legs[segmentLegIndex]) !== null && _b !== void 0 ? _b : null;
         }
     }
     /**
      * Removes a leg from the flight plan.
      * @param segmentIndex The segment to add the leg to.
      * @param index The index of the leg in the segment to remove. Will remove from the end of the segment if ommitted.
      * @param notify Whether or not to send notifications after the operation.
      * @returns the leg that was removed, or null if a leg was not removed.
      */
     removeLeg(segmentIndex, index, notify = true) {
         const segment = this.getSegment(segmentIndex);
         let legDefinition;
         if (index === undefined) {
             legDefinition = segment.legs.pop();
             index = segment.legs.length;
         }
         else {
             const deleted = segment.legs.splice(index, 1);
             legDefinition = deleted[0];
         }
         if (this.directToData.segmentIndex === segmentIndex && this.directToData.segmentLegIndex === index) ;
         this.reflowSegmentOffsets();
         notify && legDefinition && this.events.onLegChanged && this.events.onLegChanged(segmentIndex, index, PlanChangeType.Removed, legDefinition);
         return legDefinition !== null && legDefinition !== void 0 ? legDefinition : null;
     }
     /**
      * Calculates the flight path for the plan.
      * @param index The leg index to start calculating from.
      * @param notify Whether or not to send notifications after the operation.
      */
     async calculate(index, notify = true) {
         const legs = [...this.legs()];
         await this.calculator.calculateFlightPath(legs, this.activeLateralLeg, index === undefined ? this.activeCalculatingLeg : index);
         notify && this.events.onCalculated && this.events.onCalculated(index);
     }
     /**
      * Sets the origin airport in the flight plan.
      * @param facilityIcao The origin airport to set.
      * @param notify Whether or not to send notifications after the operation.
      */
     setOriginAirport(facilityIcao, notify = true) {
         this._originAirport = facilityIcao;
         notify && this.events.onOriginDestChanged && this.events.onOriginDestChanged(OriginDestChangeType.OriginAdded, facilityIcao);
     }
     /**
      * Removes the origin airport from the flight plan.
      * @param notify Whether or not to send notifications after the operation.
      */
     removeOriginAirport(notify = true) {
         const facilityIcao = this._originAirport;
         this._originAirport = undefined;
         this.procedureDetails.departureIndex = -1;
         this.procedureDetails.departureRunwayIndex = -1;
         this.procedureDetails.departureTransitionIndex = -1;
         this.procedureDetails.originRunway = undefined;
         notify && this.events.onOriginDestChanged && this.events.onOriginDestChanged(OriginDestChangeType.OriginRemoved, facilityIcao);
     }
     /**
      * Sets the destination airport in the flight plan.
      * @param facilityIcao The destination airport to set.
      * @param notify Whether or not to send notifications after the operation.
      */
     setDestinationAirport(facilityIcao, notify = true) {
         this._destinationAirport = facilityIcao;
         notify && this.events.onOriginDestChanged && this.events.onOriginDestChanged(OriginDestChangeType.DestinationAdded, facilityIcao);
     }
     /**
      * Removes the destination airport from the flight plan.
      * @param notify Whether or not to send notifications after the operation.
      */
     removeDestinationAirport(notify = true) {
         const facilityIcao = this._destinationAirport;
         this._destinationAirport = undefined;
         this.procedureDetails.approachIndex = -1;
         this.procedureDetails.approachTransitionIndex = -1;
         this.procedureDetails.arrivalIndex = -1;
         this.procedureDetails.arrivalRunwayTransitionIndex = -1;
         this.procedureDetails.arrivalTransitionIndex = -1;
         this.procedureDetails.destinationRunway = undefined;
         notify && this.events.onOriginDestChanged && this.events.onOriginDestChanged(OriginDestChangeType.DestinationRemoved, facilityIcao);
     }
     /**
      * Sets the active lateral leg index in the flight plan.
      * @param index The index to set.
      * @param notify Whether or not to send notifications after the operation.
      */
     setLateralLeg(index, notify = true) {
         let previousLegIndex = -1;
         let previousSegmentIndex = -1;
         let segmentIndex = -1;
         let segmentLegIndex = -1;
         if (this.length > 0) {
             previousSegmentIndex = this.getSegmentIndex(this._activeLateralLeg);
             if (previousSegmentIndex > -1) {
                 previousLegIndex = this._activeLateralLeg - this.getSegment(previousSegmentIndex).offset;
             }
             this._activeLateralLeg = Utils.Clamp(index, 0, this.length - 1);
             segmentIndex = this.getSegmentIndex(this._activeLateralLeg);
             if (segmentIndex > -1) {
                 segmentLegIndex = this._activeLateralLeg - this.getSegment(segmentIndex).offset;
             }
         }
         else {
             this._activeLateralLeg = 0;
         }
         notify
             && this.events.onActiveLegChanged
             && this.events.onActiveLegChanged(this._activeLateralLeg, segmentIndex, segmentLegIndex, previousSegmentIndex, previousLegIndex, ActiveLegType.Lateral);
     }
     /**
      * Sets the active lateral leg index in the flight plan.
      * @param index The index to set.
      * @param notify Whether or not to send notifications after the operation.
      */
     setVerticalLeg(index, notify = true) {
         let previousLegIndex = -1;
         let previousSegmentIndex = -1;
         let segmentIndex = -1;
         let segmentLegIndex = -1;
         if (this.length > 0) {
             previousSegmentIndex = this.getSegmentIndex(this._activeVerticalLeg);
             if (previousSegmentIndex > -1) {
                 previousLegIndex = this._activeVerticalLeg - this.getSegment(previousSegmentIndex).offset;
             }
             this._activeVerticalLeg = Utils.Clamp(index, 0, this.length - 1);
             segmentIndex = this.getSegmentIndex(this._activeVerticalLeg);
             if (segmentIndex > -1) {
                 segmentLegIndex = this._activeVerticalLeg - this.getSegment(segmentIndex).offset;
             }
         }
         else {
             this._activeVerticalLeg = 0;
         }
         notify
             && this.events.onActiveLegChanged
             && this.events.onActiveLegChanged(this._activeVerticalLeg, segmentIndex, segmentLegIndex, previousSegmentIndex, previousLegIndex, ActiveLegType.Vertical);
     }
     /**
      * Sets the active calculating leg index in the flight plan.
      * @param index The index to set.
      * @param notify Whether or not to send notifications after the operation.
      */
     setCalculatingLeg(index, notify = true) {
         let previousLegIndex = -1;
         let previousSegmentIndex = -1;
         let segmentIndex = -1;
         let segmentLegIndex = -1;
         if (this.length > 0) {
             previousSegmentIndex = this.getSegmentIndex(this._activeCalculatingLeg);
             if (previousSegmentIndex > -1) {
                 previousLegIndex = this._activeCalculatingLeg - this.getSegment(previousSegmentIndex).offset;
             }
             this._activeCalculatingLeg = Utils.Clamp(index, 0, this.length - 1);
             segmentIndex = this.getSegmentIndex(this._activeCalculatingLeg);
             if (segmentIndex > -1) {
                 segmentLegIndex = this._activeCalculatingLeg - this.getSegment(segmentIndex).offset;
             }
         }
         else {
             this._activeCalculatingLeg = 0;
         }
         notify
             && this.events.onActiveLegChanged
             && this.events.onActiveLegChanged(this._activeCalculatingLeg, segmentIndex, segmentLegIndex, previousSegmentIndex, previousLegIndex, ActiveLegType.Calculating);
     }
     /**
      * Sets the flight plan procedure details.
      * @param details The details of the flight plan's procedures.
      * @param notify Whether or not to send notifications after the operation.
      */
     setProcedureDetails(details, notify = true) {
         Object.assign(this.procedureDetails, details);
         notify && this.events.onProcedureDetailsChanged && this.events.onProcedureDetailsChanged(this.procedureDetails);
     }
     /**
      * Gets the index of a flight plan leg in this flight plan.
      * @param leg A flight plan leg definition.
      * @returns the index of the leg, or -1 if the leg is not in this flight plan.
      */
     getLegIndexFromLeg(leg) {
         let index = 0;
         for (const toCompare of this.legs()) {
             if (toCompare === leg) {
                 return index;
             }
             index++;
         }
         return -1;
     }
     /**
      * Gets the flight plan segment to which a leg belongs.
      * @param leg A flight plan leg definition.
      * @returns The segment to which the leg belongs, or null if the leg is not in this flight plan.
      */
     getSegmentFromLeg(leg) {
         for (const segment of this.segments()) {
             if (segment.legs.includes(leg)) {
                 return segment;
             }
         }
         return null;
     }
     /**
      * Gets the segment index for a given global leg index.
      * @param legIndex The global leg index to get the segment index for.
      * @returns The segment index for the given leg index.
      */
     getSegmentIndex(legIndex) {
         for (const segment of this.segments()) {
             if (segment.offset <= legIndex && legIndex < segment.offset + segment.legs.length) {
                 return segment.segmentIndex;
             }
         }
         return -1;
     }
     /**
      * Gets the leg immediately previous to a position in this flight plan specified by segment index and leg index.
      * @param segmentIndex A segment index.
      * @param legIndex A leg index.
      * @returns the leg immediately previous to the specified position, or null if there is no such leg.
      */
     getPrevLeg(segmentIndex, legIndex) {
         var _a, _b;
         if (segmentIndex < 0) {
             return null;
         }
         segmentIndex = Math.min(segmentIndex, this.planSegments.length);
         legIndex = Math.min(legIndex, (_b = (_a = this.planSegments[segmentIndex]) === null || _a === void 0 ? void 0 : _a.legs.length) !== null && _b !== void 0 ? _b : 0);
         let segment = this.planSegments[segmentIndex];
         let leg = segment === null || segment === void 0 ? void 0 : segment.legs[legIndex - 1];
         while (!leg && --segmentIndex >= 0) {
             segment = this.planSegments[segmentIndex];
             if (segment) {
                 leg = segment.legs[segment.legs.length - 1];
             }
         }
         return leg !== null && leg !== void 0 ? leg : null;
     }
     /**
      * Gets the leg immediately after a position in this flight plan specified by segment index and leg index.
      * @param segmentIndex A segment index.
      * @param legIndex A leg index.
      * @returns the leg immediately after the specified position, or null if there is no such leg.
      */
     getNextLeg(segmentIndex, legIndex) {
         if (segmentIndex >= this.planSegments.length) {
             return null;
         }
         segmentIndex = Math.max(segmentIndex, -1);
         legIndex = Math.max(legIndex, -1);
         let segment = this.planSegments[segmentIndex];
         let leg = segment === null || segment === void 0 ? void 0 : segment.legs[legIndex + 1];
         while (!leg && ++segmentIndex < this.planSegments.length) {
             segment = this.planSegments[segmentIndex];
             if (segment) {
                 leg = segment.legs[0];
             }
         }
         return leg !== null && leg !== void 0 ? leg : null;
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     setDirectToData(arg1, arg2, arg3) {
         if (typeof arg1 !== 'number') {
             // arg1 is a LegDefinition or null.
             arg1 = arg1 ? this.getLegIndexFromLeg(arg1) : -1;
         }
         let segmentIndex = -1;
         let segmentLegIndex = -1;
         let notify = true;
         if (typeof arg2 !== 'number') {
             // arg1 is global leg index
             if (arg1 >= 0) {
                 segmentIndex = this.getSegmentIndex(arg1);
                 if (segmentIndex >= 0) {
                     segmentLegIndex = arg1 - this.getSegment(segmentIndex).offset;
                 }
             }
             notify = arg2 !== null && arg2 !== void 0 ? arg2 : true;
         }
         else {
             segmentIndex = arg1;
             segmentLegIndex = arg2;
             notify = arg3 !== null && arg3 !== void 0 ? arg3 : true;
         }
         this.directToData.segmentIndex = segmentIndex;
         this.directToData.segmentLegIndex = segmentLegIndex;
         notify && this.events.onDirectDataChanged && this.events.onDirectDataChanged(this.directToData);
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     setLegVerticalData(arg1, arg2, arg3, arg4) {
         let notify = true;
         let segmentIndex = -1;
         let segmentLegIndex = -1;
         let verticalData;
         if (typeof arg2 !== 'number') {
             segmentIndex = this.getSegmentIndex(arg1);
             const segment = this.getSegment(segmentIndex);
             segmentLegIndex = arg1 - segment.offset;
             verticalData = arg2;
             notify = arg3 !== undefined ? arg3 : notify;
         }
         else {
             segmentIndex = arg1;
             segmentLegIndex = arg2;
             verticalData = arg3;
             notify = arg4 !== undefined ? arg4 : notify;
         }
         const leg = this.tryGetLeg(segmentIndex, segmentLegIndex);
         if (leg) {
             Object.assign(leg.verticalData, verticalData);
             notify && this.events.onLegChanged && this.events.onLegChanged(segmentIndex, segmentLegIndex, PlanChangeType.Changed, leg);
         }
         else {
             console.warn(`Failed to set Leg Vertical Data for Segment ${segmentIndex} Leg ${segmentLegIndex}.`);
         }
     }
     /**
      * Sets the airway for a segment.
      * @param segmentIndex A segment index.
      * @param airway The airway name.
      * @param notify is whether to send an event for this change
      */
     setAirway(segmentIndex, airway, notify = true) {
         const segment = this.getSegment(segmentIndex);
         if (!airway) {
             segment.airway = undefined;
         }
         else {
             segment.airway = airway;
         }
         this.events.onSegmentChanged && notify && this.events.onSegmentChanged(segmentIndex, PlanChangeType.Changed, segment);
     }
     /**
      * Sets user data in the flight plan.
      * @param key The key of the user data.
      * @param data The data to set.
      * @param notify Whether or not to notify subscribers.
      */
     setUserData(key, data, notify = true) {
         this.userData[key] = data;
         this.events.onUserDataSet && notify && this.events.onUserDataSet(key, data);
     }
     /**
      * Sets user data in the flight plan.
      * @param key The key of the user data.
      * @param notify Whether or not to notify subscribers.
      */
     deleteUserData(key, notify = true) {
         if (this.userData[key] !== undefined) {
             delete this.userData[key];
         }
         this.events.onUserDataDelete && notify && this.events.onUserDataDelete(key);
     }
     /**
      * Gets user data from the flight plan.
      * @param key The key of the user data.
      * @returns The user data, if found.
      */
     getUserData(key) {
         return this.userData[key];
     }
     /**
      * Reflows all flight plan segment offsets after a plan change.
      */
     reflowSegmentOffsets() {
         let nextOffset = undefined;
         for (let i = 0; i < this.planSegments.length; i++) {
             const segment = this.planSegments[i];
             if (segment) {
                 if (nextOffset === undefined) {
                     segment.offset = 0;
                 }
                 else {
                     segment.offset = nextOffset;
                 }
                 nextOffset = segment.legs.length + segment.offset;
             }
         }
     }
     /**
      * Builds leg names using default nomenclature.
      * @param leg The leg to build a name for.
      * @returns The name of the leg.
      */
     buildDefaultLegName(leg) {
         let legDistanceNM;
         switch (leg.type) {
             case LegType.CA:
             case LegType.FA:
             case LegType.VA:
                 return `${UnitType.METER.convertTo(leg.altitude1, UnitType.FOOT).toFixed(0)}FT`;
             case LegType.FM:
             case LegType.VM:
                 return 'MANSEQ';
             case LegType.FC:
                 legDistanceNM = Math.round(UnitType.METER.convertTo(leg.distance, UnitType.NMILE));
                 return `D${leg.course.toFixed(0).padStart(3, '0')}${String.fromCharCode(64 + Utils.Clamp(legDistanceNM, 1, 26))}`;
             case LegType.CD:
             case LegType.FD:
             case LegType.VD:
                 legDistanceNM = UnitType.METER.convertTo(leg.distance, UnitType.NMILE);
                 return `${ICAO.getIdent(leg.originIcao)}${legDistanceNM.toFixed(1)}`;
             case LegType.CR:
             case LegType.VR:
                 return `${ICAO.getIdent(leg.originIcao)}${leg.theta.toFixed(0)}`;
             case LegType.CI:
             case LegType.VI:
                 return 'INTRCPT';
             case LegType.PI:
                 return 'PROC. TURN';
             case LegType.HA:
             case LegType.HM:
             case LegType.HF:
                 return 'HOLD';
             default:
                 return ICAO.getIdent(leg.fixIcao);
         }
     }
     /**
      * Sets the origin runway in procedure details.
      * @param runway The oneway runway to set as the origin, or undefined
      * @param notify Whether or not to notify subscribers.
      */
     setOriginRunway(runway = undefined, notify = true) {
         this.procedureDetails.originRunway = runway;
         const details = new ProcedureDetails;
         Object.assign(details, this.procedureDetails);
         this.events.onProcedureDetailsChanged && notify && this.events.onProcedureDetailsChanged(details);
     }
     /**
      * Sets the destination runway in procedure details.
      * @param runway The oneway runway to set as the destination, or undefined
      * @param notify Whether or not to notify subscribers.
      */
     setDestinationRunway(runway = undefined, notify = true) {
         this.procedureDetails.destinationRunway = runway;
         const details = new ProcedureDetails;
         Object.assign(details, this.procedureDetails);
         this.events.onProcedureDetailsChanged && notify && this.events.onProcedureDetailsChanged(details);
     }
     /**
      * Sets the departure procedure details.
      * @param facilityIcao The facility ICAO of the facility containing the procedure
      * @param departureIndex The index of the departure in the origin airport information
      * @param departureTransitionIndex The index of the departure transition in the origin airport departure information
      * @param departureRunwayIndex The index of the selected runway in the original airport departure information
      * @param notify Whether or not to notify subscribers.
      */
     setDeparture(facilityIcao = undefined, departureIndex = -1, departureTransitionIndex = -1, departureRunwayIndex = -1, notify = true) {
         this.procedureDetails.departureIndex = departureIndex;
         this.procedureDetails.departureFacilityIcao = facilityIcao;
         this.procedureDetails.departureTransitionIndex = departureTransitionIndex;
         this.procedureDetails.departureRunwayIndex = departureRunwayIndex;
         const details = new ProcedureDetails;
         Object.assign(details, this.procedureDetails);
         this.events.onProcedureDetailsChanged && notify && this.events.onProcedureDetailsChanged(details);
     }
     /**
      * Sets the arrival procedure details.
      * @param facilityIcao The facility ICAO of the facility containing the procedure
      * @param arrivalIndex The index of the arrival in the destination airport information
      * @param arrivalTransitionIndex index of the arrival transition in the destination airport arrival information
      * @param arrivalRunwayTransitionIndex The index of the selected runway transition at the destination airport arrival information
      * @param notify Whether or not to notify subscribers
      */
     setArrival(facilityIcao = undefined, arrivalIndex = -1, arrivalTransitionIndex = -1, arrivalRunwayTransitionIndex = -1, notify = true) {
         this.procedureDetails.arrivalIndex = arrivalIndex;
         this.procedureDetails.arrivalFacilityIcao = facilityIcao;
         this.procedureDetails.arrivalTransitionIndex = arrivalTransitionIndex;
         this.procedureDetails.arrivalRunwayTransitionIndex = arrivalRunwayTransitionIndex;
         const details = new ProcedureDetails;
         Object.assign(details, this.procedureDetails);
         this.events.onProcedureDetailsChanged && notify && this.events.onProcedureDetailsChanged(details);
     }
     /**
      * Sets the approach procedure details.
      * @param facilityIcao The facility ICAO of the facility containing the procedure
      * @param approachIndex The index of the apporach in the destination airport information
      * @param approachTransitionIndex The index of the approach transition in the destination airport approach information
      * @param notify Whether or not to notify subscribers
      */
     setApproach(facilityIcao = undefined, approachIndex = -1, approachTransitionIndex = -1, notify = true) {
         this.procedureDetails.approachIndex = approachIndex;
         this.procedureDetails.approachFacilityIcao = facilityIcao;
         this.procedureDetails.approachIndex = approachIndex;
         this.procedureDetails.approachTransitionIndex = approachTransitionIndex;
         const details = new ProcedureDetails;
         Object.assign(details, this.procedureDetails);
         this.events.onProcedureDetailsChanged && notify && this.events.onProcedureDetailsChanged(details);
     }
     /**
      * Copies the flight plan.
      * @param planIndex The flight plan index to assign to this plan, or the same plan
      * index if not provided.
      * @returns The copied flight plan.
      */
     copy(planIndex) {
         if (planIndex === undefined) {
             planIndex = this.planIndex;
         }
         const newPlan = new FlightPlan(planIndex, this.calculator);
         newPlan.setProcedureDetails(this.procedureDetails, false);
         newPlan.setDirectToData(this.directToData.segmentIndex, this.directToData.segmentLegIndex);
         for (const segment of this.segments()) {
             newPlan.addSegment(segment.segmentIndex, segment.segmentType, segment.airway, false);
             for (const leg of segment.legs) {
                 newPlan.addLeg(segment.segmentIndex, leg.leg, undefined, leg.flags, false);
             }
         }
         if (this.originAirport !== undefined) {
             newPlan.setOriginAirport(this.originAirport, false);
         }
         if (this.destinationAirport !== undefined) {
             newPlan.setDestinationAirport(this.destinationAirport, false);
         }
         newPlan.setLateralLeg(this.activeLateralLeg);
         newPlan.setVerticalLeg(this.activeVerticalLeg);
         newPlan.setCalculatingLeg(this.activeCalculatingLeg);
         if (this.userData.size > 0) {
             for (const kv in this.userData) {
                 newPlan.setUserData(kv[0], kv[1], false);
             }
         }
         return newPlan;
     }
 }
 /**
  * Creates a default instance of a flight plan leg.
  * @param partial A portion of leg options to apply.
  * @returns A default instance of a flight plan leg.
  */
 FlightPlan.createLeg = (partial) => Object.assign({
     type: 0,
     fixIcao: ICAO.emptyIcao,
     arcCenterFixIcao: ICAO.emptyIcao,
     originIcao: ICAO.emptyIcao,
     flyOver: 0,
     turnDirection: 0,
     trueDegrees: 0,
     theta: 0,
     rho: 0,
     distance: 0,
     distanceMinutes: 0,
     speedRestriction: 0,
     altDesc: 0,
     altitude1: 0,
     altitude2: 0,
     course: 0,
     fixTypeFlags: 0
 }, partial);
 
 /**
  * Manages the active flightplans of the navigational systems.
  */
 class FlightPlanner {
     /**
      * Creates an instance of the FlightPlanner.
      * @param bus The event bus instance to notify changes on.
      * @param calculator The flight path calculator to use with this planner.
      */
     constructor(bus, calculator) {
         this.bus = bus;
         this.calculator = calculator;
         /** The flight plans managed by this flight planner. */
         this.flightPlans = [];
         this.ignoreSync = false;
         /** The active flight plan index. */
         this._activePlanIndex = 0;
         this.flightPlanSynced = new SubEvent();
         this.publisher = bus.getPublisher();
         const subscriber = bus.getSubscriber();
         subscriber.on('fplsync_fplRequest').handle(() => !this.ignoreSync && this.onFlightPlanRequest());
         subscriber.on('fplsync_fplResponse').handle(data => !this.ignoreSync && this.onFlightPlanResponse(data));
         subscriber.on('fplsync_fplCreated').handle(data => !this.ignoreSync && this.onPlanCreated(data));
         subscriber.on('fplsync_fplDeleted').handle(data => !this.ignoreSync && this.onPlanDeleted(data));
         subscriber.on('fplsync_fplActiveLegChange').handle(data => !this.ignoreSync && this.onActiveLegChanged(data));
         subscriber.on('fplsync_fplLegChange').handle(data => !this.ignoreSync && this.onLegChanged(data));
         subscriber.on('fplsync_fplSegmentChange').handle(data => !this.ignoreSync && this.onSegmentChanged(data));
         subscriber.on('fplsync_fplCalculated').handle(data => !this.ignoreSync && this.onCalculated(data));
         subscriber.on('fplsync_fplOriginDestChanged').handle(data => !this.ignoreSync && this.onOriginDestChanged(data));
         subscriber.on('fplsync_fplProcDetailsChanged').handle(data => !this.ignoreSync && this.onProcedureDetailsChanged(data));
         subscriber.on('fplsync_fplIndexChanged').handle(data => !this.ignoreSync && this.onPlanIndexChanged(data));
         subscriber.on('fplsync_fplCopied').handle(data => !this.ignoreSync && this.onPlanCopied(data));
         subscriber.on('fplsync_fplUserDataSet').handle(data => !this.ignoreSync && this.onUserDataSet(data));
         subscriber.on('fplsync_fplUserDataDelete').handle(data => !this.ignoreSync && this.onUserDataDelete(data));
         subscriber.on('fplsync_fplDirectToDataChanged').handle(data => !this.ignoreSync && this.onDirectToDataChanged(data));
     }
     /**
      * Set a new active plan index.
      * @param planIndex The new active plan index.
      */
     set activePlanIndex(planIndex) {
         this._activePlanIndex = planIndex;
     }
     /**
      * Get the active plan index.
      * @returns The active plan index number.
      */
     get activePlanIndex() {
         return this._activePlanIndex;
     }
     /**
      * Requests synchronization from other FlightPlanner instances.
      */
     requestSync() {
         this.sendFlightPlanRequest();
     }
     /**
      * An event generated when a set of flight plans is requested.
      */
     onFlightPlanRequest() {
         this.ignoreSync = true;
         this.publisher.pub('fplsync_fplResponse', {
             flightPlans: this.flightPlans.map(plan => {
                 const newPlan = Object.assign({}, plan);
                 newPlan.calculator = undefined;
                 return newPlan;
             }), planIndex: this.activePlanIndex
         }, true, false);
         this.ignoreSync = false;
     }
     /**
      * Sends a flight plan request event.
      */
     sendFlightPlanRequest() {
         this.ignoreSync = true;
         this.publisher.pub('fplsync_fplRequest', {}, true, false);
         this.ignoreSync = false;
     }
     /**
      * A callback which is called in response to flight plan request response sync events.
      * @param data The event data.
      */
     onFlightPlanResponse(data) {
         for (let i = 0; i < data.flightPlans.length; i++) {
             const newPlan = Object.assign(new FlightPlan(i, this.calculator), data.flightPlans[i]);
             newPlan.events = this.buildPlanEventHandlers(i);
             this.flightPlans[i] = newPlan;
             this.sendEvent('fplLoaded', { planIndex: i }, false);
             // Make sure the newly loaded plans are calculated at least once from the beginning
             newPlan.calculate(0);
         }
         this.setActivePlanIndex(data.planIndex);
         this.flightPlanSynced.notify(this, true);
     }
     /**
      * Checks whether a flight plan exists at a specified index.
      * @param planIndex The index to check.
      * @returns Whether a a flight plan exists at `planIndex`.
      */
     hasFlightPlan(planIndex) {
         return !!this.flightPlans[planIndex];
     }
     /**
      * Gets a flight plan from the flight planner.
      * @param planIndex The index of the flight plan.
      * @returns The requested flight plan.
      * @throws Error if a flight plan does not exist at `planIndex`.
      */
     getFlightPlan(planIndex) {
         const plan = this.flightPlans[planIndex];
         if (!plan) {
             throw new Error(`FlightPlanner: Flight plan does not exist at index ${planIndex}`);
         }
         return plan;
     }
     /**
      * Creates a new flight plan at a specified index if one does not already exist.
      * @param planIndex The index at which to create the new flight plan.
      * @param notify Whether to send an event notification. True by default.
      * @returns The new flight plan, or the existing flight plan at `planIndex`.
      */
     createFlightPlan(planIndex, notify = true) {
         if (this.flightPlans[planIndex]) {
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             return this.flightPlans[planIndex];
         }
         const flightPlan = new FlightPlan(planIndex, this.calculator);
         flightPlan.events = this.buildPlanEventHandlers(planIndex);
         this.flightPlans[planIndex] = flightPlan;
         notify && this.sendPlanCreated(planIndex);
         return flightPlan;
     }
     /**
      * A callback which is called in response to flight plan request response sync events.
      * @param data The event data.
      */
     onPlanCreated(data) {
         this.createFlightPlan(data.planIndex, false);
         this.sendEvent('fplCreated', data, false);
     }
     /**
      * Sends a flight plan created event.
      * @param planIndex The index of the flight plan that was created.
      */
     sendPlanCreated(planIndex) {
         const data = { planIndex };
         this.sendEvent('fplCreated', data, true);
     }
     /**
      * Deletes a flight plan from the flight planner.
      * @param planIndex The index of the flight plan to delete.
      * @param notify Whether to send an event notification. True by default.
      */
     deleteFlightPlan(planIndex, notify = true) {
         const flightPlan = this.flightPlans[planIndex];
         if (flightPlan) {
             flightPlan.events = {};
             this.flightPlans[planIndex] = undefined;
             notify && this.sendPlanDeleted(planIndex);
         }
         if (planIndex === this.flightPlans.length - 1) {
             this.flightPlans.length--;
         }
     }
     /**
      * A callback which is called in response to flight plan deleted sync events.
      * @param data The event data.
      */
     onPlanDeleted(data) {
         this.deleteFlightPlan(data.planIndex, false);
         this.sendEvent('fplDeleted', data, false);
     }
     /**
      * Sends a flight plan deleted event.
      * @param planIndex The index of the flight plan that was created.
      */
     sendPlanDeleted(planIndex) {
         const data = { planIndex };
         this.sendEvent('fplDeleted', data, true);
     }
     /**
      * Builds the plan event handlers for the flight plan.
      * @param planIndex The index of the flight plan.
      * @returns The plan event handlers.
      */
     buildPlanEventHandlers(planIndex) {
         return {
             onLegChanged: (segmentIndex, index, type, leg) => this.sendLegChanged(planIndex, segmentIndex, index, type, leg),
             onSegmentChanged: (segmentIndex, type, segment) => this.sendSegmentChanged(planIndex, segmentIndex, type, segment),
             onActiveLegChanged: (index, segmentIndex, legIndex, previousSegmentIndex, previousLegIndex, type) => this.sendActiveLegChange(planIndex, index, segmentIndex, legIndex, previousSegmentIndex, previousLegIndex, type),
             onCalculated: (index) => this.sendCalculated(planIndex, index),
             onOriginDestChanged: (type, airport) => this.sendOriginDestChanged(planIndex, type, airport),
             onProcedureDetailsChanged: (details) => this.sendProcedureDetailsChanged(planIndex, details),
             onUserDataSet: (key, data) => this.sendUserDataSet(planIndex, key, data),
             onUserDataDelete: (key) => this.sendUserDataDelete(planIndex, key),
             onDirectDataChanged: (directToData) => this.sendDirectToData(planIndex, directToData)
         };
     }
     /**
      * Checks whether an active flight plan exists.
      * @returns Whether an active flight plan exists.
      */
     hasActiveFlightPlan() {
         return this.hasFlightPlan(this.activePlanIndex);
     }
     /**
      * Gets the currently active flight plan from the flight planner.
      * @returns The currently active flight plan.
      * @throws Error if no active flight plan exists.
      */
     getActiveFlightPlan() {
         return this.getFlightPlan(this.activePlanIndex);
     }
     /**
      * Copies a flight plan to another flight plan slot.
      * @param sourcePlanIndex The source flight plan index.
      * @param targetPlanIndex The target flight plan index.
      * @param notify Whether or not to notify subscribers that the plan has been copied.
      */
     copyFlightPlan(sourcePlanIndex, targetPlanIndex, notify = true) {
         const sourcePlan = this.flightPlans[sourcePlanIndex];
         if (!sourcePlan) {
             return;
         }
         const newPlan = sourcePlan.copy(targetPlanIndex);
         newPlan.events = this.buildPlanEventHandlers(targetPlanIndex);
         this.flightPlans[targetPlanIndex] = newPlan;
         if (notify) {
             this.sendPlanCopied(sourcePlanIndex, targetPlanIndex);
         }
     }
     /**
      * A callback which is called in response to flight plan copied sync events.
      * @param data The event data.
      */
     onPlanCopied(data) {
         this.copyFlightPlan(data.planIndex, data.targetPlanIndex, false);
         this.sendEvent('fplCopied', data, false);
     }
     /**
      * Sends a leg change event.
      * @param planIndex The index of the flight plan that was the source of the copy.
      * @param targetPlanIndex The index of the copy.
      */
     sendPlanCopied(planIndex, targetPlanIndex) {
         const data = { planIndex, targetPlanIndex };
         this.sendEvent('fplCopied', data, true);
     }
     /**
      * A callback which is called in response to leg changed sync events.
      * @param data The event data.
      */
     onLegChanged(data) {
         const plan = this.getFlightPlan(data.planIndex);
         switch (data.type) {
             case PlanChangeType.Added:
                 data.leg && plan.addLeg(data.segmentIndex, data.leg.leg, data.legIndex, data.leg.flags, false);
                 break;
             case PlanChangeType.Removed:
                 plan.removeLeg(data.segmentIndex, data.legIndex, false);
                 break;
             case PlanChangeType.Changed:
                 data.leg && data.leg.verticalData && plan.setLegVerticalData(data.segmentIndex, data.legIndex, data.leg.verticalData, false);
                 break;
         }
         this.sendEvent('fplLegChange', data, false);
     }
     /**
      * Sends a leg change event.
      * @param planIndex The index of the flight plan.
      * @param segmentIndex The index of the segment.
      * @param index The index of the leg.
      * @param type The type of change.
      * @param leg The leg that was changed.
      */
     sendLegChanged(planIndex, segmentIndex, index, type, leg) {
         const data = {
             planIndex, segmentIndex, legIndex: index, type, leg
         };
         this.sendEvent('fplLegChange', data, true);
     }
     /**
      * A callback which is called in response to segment changed sync events.
      * @param data The event data.
      */
     onSegmentChanged(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         switch (data.type) {
             case PlanChangeType.Added:
                 data.segment && plan.addSegment(data.segmentIndex, data.segment.segmentType, data.segment.airway, false);
                 break;
             case PlanChangeType.Inserted:
                 data.segment && plan.insertSegment(data.segmentIndex, data.segment.segmentType, data.segment.airway, false);
                 break;
             case PlanChangeType.Removed:
                 plan.removeSegment(data.segmentIndex, false);
                 break;
             case PlanChangeType.Changed:
                 data.segment && plan.setAirway(data.segmentIndex, data.segment.airway, false);
                 break;
         }
         this.sendEvent('fplSegmentChange', data, false);
     }
     /**
      * Sends a segment change event.
      * @param planIndex The index of the flight plan.
      * @param index The index of the segment.
      * @param type The type of change.
      * @param segment The segment that was changed.
      */
     sendSegmentChanged(planIndex, index, type, segment) {
         const data = {
             planIndex, segmentIndex: index, type, segment
         };
         this.sendEvent('fplSegmentChange', data, true);
     }
     /**
      * A callback which is called in response to active leg changed sync events.
      * @param data The event data.
      */
     onActiveLegChanged(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         switch (data.type) {
             case ActiveLegType.Lateral:
                 plan.setLateralLeg(data.index, false);
                 break;
             case ActiveLegType.Vertical:
                 plan.setVerticalLeg(data.index, false);
                 break;
             case ActiveLegType.Calculating:
                 plan.setCalculatingLeg(data.index, false);
                 break;
         }
         this.sendEvent('fplActiveLegChange', data, false);
     }
     /**
      * Sends an active leg change event.
      * @param planIndex The index of the flight plan.
      * @param index The index of the leg.
      * @param segmentIndex The index of the plan segment.
      * @param legIndex The index of the leg within the segment.
      * @param previousSegmentIndex The index of the segment in which the previously active leg is.
      * @param previousLegIndex The index of the previously active leg within the previously active segment.
      * @param type The type of leg that was changed.
      */
     sendActiveLegChange(planIndex, index, segmentIndex, legIndex, previousSegmentIndex, previousLegIndex, type) {
         const data = {
             segmentIndex, legIndex, planIndex,
             index, previousSegmentIndex, previousLegIndex, type
         };
         this.sendEvent('fplActiveLegChange', data, true);
     }
     /**
      * A callback which is called in response to calculation sync events.
      * @param data The event data.
      */
     async onCalculated(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         await plan.calculate(data.index, false);
         this.sendEvent('fplCalculated', data, false);
     }
     /**
      * Sends a calculated event.
      * @param planIndex The index of the flight plan.
      * @param index The index that the path was generated from.
      */
     sendCalculated(planIndex, index) {
         const data = { planIndex, index };
         this.sendEvent('fplCalculated', data, true);
     }
     /**
      * A callback which is called in response to origin/destination changed sync events.
      * @param data The event data.
      */
     onOriginDestChanged(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         switch (data.type) {
             case OriginDestChangeType.OriginAdded:
                 data.airport && plan.setOriginAirport(data.airport, false);
                 break;
             case OriginDestChangeType.OriginRemoved:
                 plan.removeOriginAirport(false);
                 break;
             case OriginDestChangeType.DestinationAdded:
                 data.airport && plan.setDestinationAirport(data.airport, false);
                 break;
             case OriginDestChangeType.DestinationRemoved:
                 plan.removeDestinationAirport(false);
                 break;
         }
         this.sendEvent('fplOriginDestChanged', data, false);
     }
     /**
      * Sends a origin/dest change event.
      * @param planIndex The index of the flight plan.
      * @param type The origin/destination change type.
      * @param airport The airport that was changed.
      */
     sendOriginDestChanged(planIndex, type, airport) {
         const data = { planIndex, type, airport };
         this.sendEvent('fplOriginDestChanged', data, true);
     }
     /**
      * A callback which is called in response to procedure changed sync events.
      * @param data The event data.
      */
     onProcedureDetailsChanged(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         plan.setProcedureDetails(data.details, false);
         this.sendEvent('fplProcDetailsChanged', data, false);
     }
     /**
      * Sends a procedure details change event.
      * @param planIndex The index of the flight plan.
      * @param details The details that were changed.
      */
     sendProcedureDetailsChanged(planIndex, details) {
         const data = { planIndex, details };
         this.sendEvent('fplProcDetailsChanged', data, true);
     }
     /**
      * A callback which is called in response to flight plan index changed sync events.
      * @param data The event data.
      */
     onPlanIndexChanged(data) {
         this.activePlanIndex = data.planIndex;
         this.sendEvent('fplIndexChanged', data, false);
     }
     /**
      * Sends an active plan index change event.
      * @param planIndex The index of the flight plan.
      */
     sendPlanIndexChanged(planIndex) {
         const data = { planIndex };
         this.sendEvent('fplIndexChanged', data, true);
     }
     /**
      * A callback which is called in response to user data set sync events.
      * @param data The event data.
      */
     onUserDataSet(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         plan.setUserData(data.key, data.data, false);
         this.sendEvent('fplUserDataSet', data, false);
     }
     /**
      * A callback which is called in response to user data delete sync events.
      * @param data The event data.
      */
     onUserDataDelete(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         plan.deleteUserData(data.key, false);
         this.sendEvent('fplUserDataDelete', data, false);
     }
     /**
      * Sends a user data set event.
      * @param planIndex The index of the flight plan.
      * @param key The key of the user data.
      * @param userData The data that was set.
      */
     sendUserDataSet(planIndex, key, userData) {
         const data = { planIndex, key, data: userData };
         this.sendEvent('fplUserDataSet', data, true);
     }
     /**
      * Sends a user data delete event.
      * @param planIndex The index of the flight plan.
      * @param key The key of the user data.
      */
     sendUserDataDelete(planIndex, key) {
         const data = { planIndex, key, data: undefined };
         this.sendEvent('fplUserDataDelete', data, true);
     }
     /**
      * A callback which is called in response to direct to data changed sync events.
      * @param data The event data.
      */
     onDirectToDataChanged(data) {
         const plan = this.flightPlans[data.planIndex];
         if (!plan) {
             return;
         }
         plan.setDirectToData(data.directToData.segmentIndex, data.directToData.segmentLegIndex, false);
         this.sendEvent('fplDirectToDataChanged', data, false);
     }
     /**
      * Sends a direct to data changed event.
      * @param planIndex The index of the flight plan.
      * @param directToData The direct to data.
      */
     sendDirectToData(planIndex, directToData) {
         const data = { planIndex, directToData: directToData };
         this.sendEvent('fplDirectToDataChanged', data, true);
     }
     /**
      * Method to set an active flight plan index.
      * @param planIndex The index of the flight plan to make active.
      */
     setActivePlanIndex(planIndex) {
         if (this.hasFlightPlan(planIndex)) {
             this.activePlanIndex = planIndex;
             this.sendPlanIndexChanged(planIndex);
         }
     }
     /**
      * Sends a local event and its sync counterpart.
      * @param topic The topic of the local event.
      * @param data The event data.
      * @param sync Whether to send the sync event.
      */
     sendEvent(topic, data, sync) {
         if (sync) {
             this.ignoreSync = true;
             this.publisher.pub(`fplsync_${topic}`, data, true, false);
             this.ignoreSync = false;
         }
         this.publisher.pub(topic, data, false, false);
     }
     /**
      * Gets an instance of FlightPlanner.
      * @param bus The event bus.
      * @param calculator A flight path calculator.
      * @returns An instance of FlightPlanner.
      */
     static getPlanner(bus, calculator) {
         var _a;
         return (_a = FlightPlanner.INSTANCE) !== null && _a !== void 0 ? _a : (FlightPlanner.INSTANCE = new FlightPlanner(bus, calculator));
     }
 }
 
 /**
  * A manager for user settings. Provides settings using their names as keys, publishes value change events on the
  * event bus, and keeps setting values up to date when receiving change events across the bus.
  */
 class DefaultUserSettingManager {
     /**
      * Constructor.
      * @param bus The bus used by this manager to publish setting change events.
      * @param settingDefs The setting definitions used to initialize this manager's settings.
      */
     constructor(bus, settingDefs) {
         this.bus = bus;
         this.publisher = bus.getPublisher();
         this.subscriber = bus.getSubscriber();
         this.settings = new Map(settingDefs.map(def => {
             const syncTopic = `${DefaultUserSettingManager.SYNC_TOPIC_PREFIX}${def.name}`;
             const entry = {
                 syncTopic,
                 syncTime: 0
             };
             entry.setting = new SyncableUserSetting(def, this.onSettingValueChanged.bind(this, entry));
             this.subscriber.on(syncTopic).handle(this.onSettingValueSynced.bind(this, entry));
             this.onSettingValueChanged(entry, entry.setting.value);
             return [def.name, entry];
         }));
     }
     /**
      * Gets a setting from this manager.
      * @param name The name of the setting to get.
      * @returns a setting.
      * @throws Error if no setting with the specified name exists.
      */
     getSetting(name) {
         const entry = this.settings.get(name);
         if (!entry) {
             throw new Error(`Could not find setting with name ${name}`);
         }
         return entry.setting;
     }
     /**
      * Gets an array of all settings of this manager.
      * @returns an array of all settings of this manager.
      */
     getAllSettings() {
         return Array.from(this.settings.values(), entry => entry.setting);
     }
     /**
      * Gets a consumer which notifies handlers when the value of a setting changes.
      * @param name The name of a setting.
      * @returns a consumer which notifies handlers when the value of the setting changes.
      * @throws Error if no setting with the specified name exists.
      */
     whenSettingChanged(name) {
         const setting = this.settings.get(name);
         if (!setting) {
             throw new Error(`Could not find setting with name ${name}`);
         }
         return this.subscriber.on(name).whenChanged();
     }
     /**
      * Maps a user setting manager to abstracted settings keys.
      * @param map The map of key abstractions to apply.
      * @returns A new mapped user setting manager.
      */
     mapTo(map) {
         return new MappedUserSettingManager(this, map);
     }
     /**
      * A callback which is called when one of this manager's settings has its value changed locally.
      * @param entry The entry for the setting that was changed.
      * @param value The new value of the setting.
      */
     onSettingValueChanged(entry, value) {
         entry.syncTime = Date.now();
         this.publisher.pub(entry.syncTopic, { value, syncTime: entry.syncTime }, true, true);
     }
     /**
      * A callback which is called when a setting changed event is received over the event bus.
      * @param entry The entry for the setting that was changed.
      * @param data The sync data.
      */
     onSettingValueSynced(entry, data) {
         // protect against race conditions by not responding to sync events older than the last time this manager synced
         // the setting
         if (data.syncTime < entry.syncTime) {
             return;
         }
         entry.syncTime = data.syncTime;
         entry.setting.syncValue(data.value);
         // publish the public setting change event. Do NOT sync across the bus because doing so can result in older events
         // being received after newer events.
         this.publisher.pub(entry.setting.definition.name, data.value, false, true);
     }
 }
 DefaultUserSettingManager.SYNC_TOPIC_PREFIX = 'usersetting.';
 /**
  * A manager for user settings. Provides settings using their names as keys, publishes value change events on the
  * event bus, and keeps setting values up to date when receiving change events across the bus, using a mapping from
  * abstracted settings keys to true underlying settings keys.
  */
 class MappedUserSettingManager {
     /**
      * Creates an instance of a MappedUserSettingManager.
      * @param parent The parent setting manager.
      * @param map The map of abstracted keys to true underlying keys.
      */
     constructor(parent, map) {
         this.parent = parent;
         this.map = map;
     }
     /** @inheritdoc */
     getSetting(name) {
         var _a;
         const mappedName = ((_a = this.map[name]) !== null && _a !== void 0 ? _a : name);
         return this.parent.getSetting(mappedName);
     }
     /** @inheritdoc */
     whenSettingChanged(name) {
         var _a;
         const mappedName = ((_a = this.map[name]) !== null && _a !== void 0 ? _a : name);
         return this.parent.whenSettingChanged(mappedName);
     }
 }
 /**
  * An implementation of a user setting which can be synced across multiple instances.
  */
 class SyncableUserSetting {
     /**
      * Constructor.
      * @param definition This setting's definition.
      * @param valueChangedCallback A function to be called whenever the value of this setting changes.
      */
     constructor(definition, valueChangedCallback) {
         this.definition = definition;
         this.valueChangedCallback = valueChangedCallback;
         this.isSyncing = false;
         this.valueSub = Subject.create(definition.defaultValue);
         this.valueSub.sub(v => {
             !this.isSyncing && valueChangedCallback(v);
         });
     }
     // eslint-disable-next-line jsdoc/require-returns
     /** This setting's current value. */
     get value() {
         return this.valueSub.get();
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     set value(v) {
         this.valueSub.set(v);
     }
     /**
      * Syncs this setting to a value. This will not trigger a call to valueChangedCallback.
      * @param value The value to which to sync.
      */
     syncValue(value) {
         this.isSyncing = true;
         this.valueSub.set(value);
         this.isSyncing = false;
     }
 }
 
 /**
  * TCAS operating modes.
  */
 var TCASOperatingMode;
 (function (TCASOperatingMode) {
     TCASOperatingMode[TCASOperatingMode["Standby"] = 0] = "Standby";
     TCASOperatingMode[TCASOperatingMode["TAOnly"] = 1] = "TAOnly";
     TCASOperatingMode[TCASOperatingMode["TA_RA"] = 2] = "TA_RA";
 })(TCASOperatingMode || (TCASOperatingMode = {}));
 /**
  * TCAS alert level.
  */
 var TCASAlertLevel;
 (function (TCASAlertLevel) {
     TCASAlertLevel[TCASAlertLevel["None"] = 0] = "None";
     TCASAlertLevel[TCASAlertLevel["ProximityAdvisory"] = 1] = "ProximityAdvisory";
     TCASAlertLevel[TCASAlertLevel["TrafficAdvisory"] = 2] = "TrafficAdvisory";
     TCASAlertLevel[TCASAlertLevel["ResolutionAdvisory"] = 3] = "ResolutionAdvisory";
 })(TCASAlertLevel || (TCASAlertLevel = {}));
 UnitType.KNOT.createNumber(30);
 
 var APVerticalModes;
 (function (APVerticalModes) {
     APVerticalModes[APVerticalModes["NONE"] = 0] = "NONE";
     APVerticalModes[APVerticalModes["PITCH"] = 1] = "PITCH";
     APVerticalModes[APVerticalModes["VS"] = 2] = "VS";
     APVerticalModes[APVerticalModes["FLC"] = 3] = "FLC";
     APVerticalModes[APVerticalModes["ALT"] = 4] = "ALT";
     APVerticalModes[APVerticalModes["VNAV"] = 5] = "VNAV";
     APVerticalModes[APVerticalModes["GP"] = 6] = "GP";
     APVerticalModes[APVerticalModes["GS"] = 7] = "GS";
     APVerticalModes[APVerticalModes["CAP"] = 8] = "CAP";
 })(APVerticalModes || (APVerticalModes = {}));
 var APLateralModes;
 (function (APLateralModes) {
     APLateralModes[APLateralModes["NONE"] = 0] = "NONE";
     APLateralModes[APLateralModes["ROLL"] = 1] = "ROLL";
     APLateralModes[APLateralModes["LEVEL"] = 2] = "LEVEL";
     APLateralModes[APLateralModes["GPSS"] = 3] = "GPSS";
     APLateralModes[APLateralModes["HEADING"] = 4] = "HEADING";
     APLateralModes[APLateralModes["VOR"] = 5] = "VOR";
     APLateralModes[APLateralModes["LOC"] = 6] = "LOC";
     APLateralModes[APLateralModes["BC"] = 7] = "BC";
     APLateralModes[APLateralModes["NAV"] = 8] = "NAV";
 })(APLateralModes || (APLateralModes = {}));
 var APAltitudeModes;
 (function (APAltitudeModes) {
     APAltitudeModes[APAltitudeModes["NONE"] = 0] = "NONE";
     APAltitudeModes[APAltitudeModes["ALTS"] = 1] = "ALTS";
     APAltitudeModes[APAltitudeModes["ALTV"] = 2] = "ALTV";
 })(APAltitudeModes || (APAltitudeModes = {}));
 
 /** AP Mode Types */
 var APModeType;
 (function (APModeType) {
     APModeType[APModeType["LATERAL"] = 0] = "LATERAL";
     APModeType[APModeType["VERTICAL"] = 1] = "VERTICAL";
     APModeType[APModeType["APPROACH"] = 2] = "APPROACH";
 })(APModeType || (APModeType = {}));
 /**
  * A class that manages the autopilot modes and autopilot mode states.
  */
 class APStateManager {
     /**
      * Creates an instance of the APStateManager.
      * @param bus An instance of the event bus.
      */
     constructor(bus) {
         this.bus = bus;
         this.apListenerRegistered = false;
         this.managedModeSet = false;
         this.stateManagerInitialized = Subject.create(false);
         this.lateralPressed = new SubEvent();
         this.verticalPressed = new SubEvent();
         this.approachPressed = new SubEvent();
         this.vnavPressed = new SubEvent();
         this.apMasterOn = Subject.create(false);
         this.fdMasterOn = Subject.create(false);
         KeyInterceptManager.getManager(bus).then(manager => {
             this.keyInterceptManager = manager;
             this.setupKeyIntercepts(manager);
             this.bus.getSubscriber().on('key_intercept').handle(this.handleKeyIntercepted.bind(this));
         });
         this.apListener = RegisterViewListener('JS_LISTENER_AUTOPILOT', () => {
             this.onAPListenerRegistered();
             this.apListenerRegistered = true;
         });
     }
     /**
      * A callback which is called when the autopilot listener has been registered.
      */
     onAPListenerRegistered() {
         const ap = this.bus.getSubscriber();
         ap.on('ap_lock_set').handle(lock => {
             if (lock === APLockType.VNav) {
                 this.vnavPressed.notify(this, true);
             }
         });
         ap.on('ap_lock_release').handle(lock => {
             if (lock === APLockType.VNav) {
                 this.vnavPressed.notify(this, false);
             }
         });
         ap.on('ap_master_disengage').handle(() => {
             this.apMasterOn.set(false);
         });
         ap.on('ap_master_engage').handle(() => {
             this.apMasterOn.set(true);
         });
         ap.on('flight_director_state').whenChanged().handle((fd) => {
             this.fdMasterOn.set(fd);
         });
     }
     /**
      * Checks whether the AP State Manager has completed listerner steps,
      * and if so, finishes initializing and then notifies Autopilot of the same.
      * @param force forces the initialize
      */
     initialize(force = false) {
         this.onBeforeInitialize();
         if (force || (this.keyInterceptManager && this.apListenerRegistered)) {
             this.setManagedMode(true).then(() => {
                 SimVar.SetSimVarValue('AUTOPILOT ALTITUDE LOCK VAR', SimVarValueType.Feet, 0);
                 if (this.fdMasterOn.get()) {
                     this.setFlightDirector(false);
                 }
                 this.stateManagerInitialized.set(true);
             });
         }
     }
     /**
      * Sets the Flight Director State
      * @param on is wheter to set the FD On.
      */
     setFlightDirector(on) {
         if (on !== this.fdMasterOn.get()) {
             SimVar.SetSimVarValue('K:TOGGLE_FLIGHT_DIRECTOR', 'number', 0);
         }
     }
     /**
      * Sets Managed Mode.
      * @param set is wheter to set or unset managed mode.
      */
     async setManagedMode(set) {
         return new Promise(resolve => {
             setTimeout(() => {
                 if (set) {
                     Coherent.call('apSetAutopilotMode', MSFSAPStates.AvionicsManaged, 1).then(() => resolve());
                 }
                 else {
                     Coherent.call('apSetAutopilotMode', MSFSAPStates.AvionicsManaged, 0).then(() => resolve());
                 }
                 this.managedModeSet = set;
             }, 1000);
         });
     }
     /**
      * Toggles VNAV L Var value.
      */
     toggleVnav() {
         const vnavXmlVarValue = SimVar.GetSimVarValue('L:XMLVAR_VNAVButtonValue', 'Bool');
         SimVar.SetSimVarValue('L:XMLVAR_VNAVButtonValue', 'Bool', vnavXmlVarValue ? 0 : 1);
     }
     /**
      * Sends AP Mode Events from the Intercept to the Autopilot.
      * @param type is the AP Mode Type for this event
      * @param mode is the mode to set/unset.
      * @param set is whether to actively set or unset this mode.
      */
     sendApModeEvent(type, mode, set) {
         switch (type) {
             case APModeType.LATERAL:
                 if (mode !== undefined) {
                     this.lateralPressed.notify(this, { mode: mode, set: set });
                 }
                 break;
             case APModeType.VERTICAL:
                 if (mode !== undefined) {
                     this.verticalPressed.notify(this, { mode: mode, set: set });
                 }
                 break;
             case APModeType.APPROACH:
                 this.approachPressed.notify(this, set);
                 break;
         }
     }
     /**
      * Method to override with steps to run before initialze method is run.
      */
     onBeforeInitialize() {
         //noop
     }
 }
 
 var APStates;
 (function (APStates) {
     APStates[APStates["None"] = 0] = "None";
     APStates[APStates["APActive"] = 1] = "APActive";
     APStates[APStates["YawDamper"] = 2] = "YawDamper";
     APStates[APStates["Heading"] = 4] = "Heading";
     APStates[APStates["Nav"] = 8] = "Nav";
     APStates[APStates["NavArmed"] = 16] = "NavArmed";
     APStates[APStates["Approach"] = 32] = "Approach";
     APStates[APStates["ApproachArmed"] = 64] = "ApproachArmed";
     APStates[APStates["Backcourse"] = 128] = "Backcourse";
     APStates[APStates["BackcourseArmed"] = 256] = "BackcourseArmed";
     APStates[APStates["Alt"] = 512] = "Alt";
     APStates[APStates["AltS"] = 1024] = "AltS";
     APStates[APStates["AltV"] = 2048] = "AltV";
     APStates[APStates["VS"] = 4096] = "VS";
     APStates[APStates["FLC"] = 8192] = "FLC";
     APStates[APStates["GP"] = 16384] = "GP";
     APStates[APStates["GPArmed"] = 32768] = "GPArmed";
     APStates[APStates["GS"] = 65536] = "GS";
     APStates[APStates["GSArmed"] = 131072] = "GSArmed";
     APStates[APStates["Path"] = 262144] = "Path";
     APStates[APStates["PathArmed"] = 524288] = "PathArmed";
     APStates[APStates["PathInvalid"] = 1048576] = "PathInvalid";
     APStates[APStates["Pitch"] = 2097152] = "Pitch";
     APStates[APStates["Roll"] = 4194304] = "Roll";
     APStates[APStates["VNAV"] = 8388608] = "VNAV";
     APStates[APStates["ATSpeed"] = 16777216] = "ATSpeed";
     APStates[APStates["ATMach"] = 33554432] = "ATMach";
     APStates[APStates["ATArmed"] = 67108864] = "ATArmed";
     APStates[APStates["FD"] = 134217728] = "FD";
 })(APStates || (APStates = {}));
 
 /**
  * A bank angle controller that maintains a constant radius turn.
  */
 class ArcTurnController {
     constructor() {
         this.bankController = new PidController(1.5, 0, 0, 15, -15);
         this.precessionController = new PidController(0.025, 0, 0, 300, -300);
         this.previousTime = new Date().appTime();
         this.filter = new ExpSmoother(500);
     }
     /**
      * Gets the bank angle output for a given radius error.
      * @param radiusError The radius error.
      * @returns The bank angle output.
      */
     getOutput(radiusError) {
         var _a;
         const currentTime = new Date().appTime();
         const dTime = currentTime - this.previousTime;
         let bankAngle = 0;
         if (this.previousRadiusError !== undefined) {
             const input = ((radiusError - this.previousRadiusError) / dTime) * 1000;
             const precessionRate = isNaN((_a = this.filter.last()) !== null && _a !== void 0 ? _a : NaN)
                 ? this.filter.reset(input)
                 : this.filter.next(input, dTime);
             const targetPrecessionRate = -this.precessionController.getOutput(dTime, radiusError);
             const precessionError = targetPrecessionRate - precessionRate;
             bankAngle = this.bankController.getOutput(dTime, precessionError);
             SimVar.SetSimVarValue('L:AP_RADUIS_ERROR', 'number', radiusError);
             SimVar.SetSimVarValue('L:AP_PRECESSION_RATE', 'number', precessionRate);
             SimVar.SetSimVarValue('L:AP_TARGET_PRECESSION_RATE', 'number', targetPrecessionRate);
             SimVar.SetSimVarValue('L:AP_BANK_ADJUSTMENT', 'number', bankAngle);
         }
         this.previousTime = currentTime;
         this.previousRadiusError = radiusError;
         return -bankAngle;
     }
     /**
      * Resets the controller.
      */
     reset() {
         this.previousTime = Date.now();
         this.previousRadiusError = undefined;
         this.precessionController.reset();
         this.bankController.reset();
     }
 }
 
 /**
  * The state of a given plane director.
  */
 var DirectorState;
 (function (DirectorState) {
     /** The plane director is not currently armed or active. */
     DirectorState["Inactive"] = "Inactive";
     /** The plane director is currently armed. */
     DirectorState["Armed"] = "Armed";
     /** The plane director is currently active. */
     DirectorState["Active"] = "Active";
 })(DirectorState || (DirectorState = {}));
 
 /**
  * The current vertical navigation state.
  */
 var VNavMode;
 (function (VNavMode) {
     /** VNAV Disabled. */
     VNavMode[VNavMode["Disabled"] = 0] = "Disabled";
     /** VNAV Enabled. */
     VNavMode[VNavMode["Enabled"] = 1] = "Enabled";
 })(VNavMode || (VNavMode = {}));
 /**
  * The current VNAV path mode.
  */
 var VNavPathMode;
 (function (VNavPathMode) {
     /** VNAV path is not active. */
     VNavPathMode[VNavPathMode["None"] = 0] = "None";
     /** VNAV path is armed for capture. */
     VNavPathMode[VNavPathMode["PathArmed"] = 1] = "PathArmed";
     /** VNAV path is actively navigating. */
     VNavPathMode[VNavPathMode["PathActive"] = 2] = "PathActive";
     /** The current VNAV path is not valid. */
     VNavPathMode[VNavPathMode["PathInvalid"] = 3] = "PathInvalid";
 })(VNavPathMode || (VNavPathMode = {}));
 /**
  * The current VNAV approach guidance mode.
  */
 var VNavApproachGuidanceMode;
 (function (VNavApproachGuidanceMode) {
     /** VNAV is not currently following approach guidance. */
     VNavApproachGuidanceMode[VNavApproachGuidanceMode["None"] = 0] = "None";
     /** VNAV has armed ILS glideslope guidance for capture. */
     VNavApproachGuidanceMode[VNavApproachGuidanceMode["GSArmed"] = 1] = "GSArmed";
     /** VNAV is actively following ILS glideslope guidance. */
     VNavApproachGuidanceMode[VNavApproachGuidanceMode["GSActive"] = 2] = "GSActive";
     /** VNAV RNAV glidepath guidance is armed for capture. */
     VNavApproachGuidanceMode[VNavApproachGuidanceMode["GPArmed"] = 3] = "GPArmed";
     /** VNAV is actively follow RNAV glidepath guidance. */
     VNavApproachGuidanceMode[VNavApproachGuidanceMode["GPActive"] = 4] = "GPActive";
 })(VNavApproachGuidanceMode || (VNavApproachGuidanceMode = {}));
 /**
  * The current VNAV altitude capture type.
  */
 var VNavAltCaptureType;
 (function (VNavAltCaptureType) {
     /** Altitude capture is not armed. */
     VNavAltCaptureType[VNavAltCaptureType["None"] = 0] = "None";
     /** Altitude will capture the selected altitude. */
     VNavAltCaptureType[VNavAltCaptureType["Selected"] = 1] = "Selected";
     /** Altitude will capture the VANV target altitude. */
     VNavAltCaptureType[VNavAltCaptureType["VNAV"] = 2] = "VNAV";
 })(VNavAltCaptureType || (VNavAltCaptureType = {}));
 
 /**
  * An Autopilot.
  */
 class Autopilot {
     /**
      * Creates an instance of the Autopilot.
      * @param bus The event bus.
      * @param flightPlanner This autopilot's associated flight planner.
      * @param config This autopilot's configuration.
      * @param stateManager This autopilot's state manager.
      */
     constructor(bus, flightPlanner, config, stateManager) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.config = config;
         this.stateManager = stateManager;
         this.cdiSource = { type: NavSourceType.Nav, index: 0 };
         this.lateralModes = new Map();
         this.lateralActive = APLateralModes.NONE;
         this.lateralArmed = APLateralModes.NONE;
         this.verticalModes = new Map();
         this.verticalActive = APVerticalModes.NONE;
         this.verticalArmed = APVerticalModes.NONE;
         this.verticalAltitudeArmed = APAltitudeModes.NONE;
         this.verticalApproachArmed = APVerticalModes.NONE;
         this.altCapArmed = false;
         this.lateralModeFailed = false;
         this.inClimb = false;
         this.currentAltitude = 0;
         this.vnavCaptureType = VNavAltCaptureType.None;
         this.apValues = {
             selectedAltitude: Subject.create(0),
             selectedVerticalSpeed: Subject.create(0),
             selectedIas: Subject.create(0),
             selectedPitch: Subject.create(0),
 
 //PM Modif
 
             maxBankAngle: Subject.create(25),
             selectedHeading: Subject.create(0),
             capturedAltitude: Subject.create(0),
             approachIsActive: Subject.create(false),
             approachHasGP: Subject.create(false),
             nav1HasGs: Subject.create(false),
             nav2HasGs: Subject.create(false),
             lateralActive: Subject.create(APLateralModes.NONE),
             verticalActive: Subject.create(APVerticalModes.NONE),
             navToNavLocArm: false
         };
         this.autopilotInitialized = false;
         this.directors = this.createDirectors(config);
         this.navToNavManager = config.createNavToNavManager(this.apValues);
         this.stateManager.stateManagerInitialized.sub((v) => {
             if (v) {
                 this.autopilotInitialized = true;
             }
             else {
                 this.autopilotInitialized = false;
             }
             this.onInitialized();
         });
         this.flightPlanner.flightPlanSynced.on((sender, v) => {
         });
         this.initLateralModes();
         this.initVerticalModes();
         this.initNavToNavManager();
         this.monitorEvents();
     }
     /**
      * Creates this autopilot's directors.
      * @param config This autopilot's configuration.
      * @returns This autopilot's directors.
      */
     createDirectors(config) {
         return {
             headingDirector: config.createHeadingDirector(this.apValues),
             rollDirector: config.createRollDirector(this.apValues),
             wingLevelerDirector: config.createWingLevelerDirector(this.apValues),
             gpssDirector: config.createGpssDirector(this.apValues),
             vorDirector: config.createVorDirector(this.apValues),
             locDirector: config.createLocDirector(this.apValues),
             bcDirector: config.createBcDirector(this.apValues),
             pitchDirector: config.createPitchDirector(this.apValues),
             vsDirector: config.createVsDirector(this.apValues),
             flcDirector: config.createFlcDirector(this.apValues),
             altHoldDirector: config.createAltHoldDirector(this.apValues),
             altCapDirector: config.createAltCapDirector(this.apValues),
             vnavDirector: config.createVNavDirector(this.apValues),
             gpDirector: config.createGpDirector(this.apValues),
             gsDirector: config.createGsDirector(this.apValues)
         };
     }
     /**
      * Update method for the Autopilot.
      */
     update() {
         if (this.autopilotInitialized) {
             this.onBeforeUpdate();
             this.checkModes();
             this.manageAltitudeCapture();
             this.updateModes();
             this.onAfterUpdate();
         }
     }
     /**
      * This method runs each update cycle before the update occurs.
      */
     onBeforeUpdate() {
         // noop
     }
     /**
      * This method runs each update cycle after the update occurs.
      */
     onAfterUpdate() {
         // noop
     }
     /**
      * This method runs whenever the initialized state of the Autopilot changes.
      */
     onInitialized() {
         // noop
     }
     /**
      * Handles input from the State Manager when a lateral mode button is pressed.
      * @param data is the AP Lateral Mode Event Data
      */
     lateralPressed(data) {
         var _a, _b, _c, _d;
         const mode = data.mode;
         if (mode !== APLateralModes.NAV && !this.lateralModes.has(mode)) {
             return;
         }
         const set = data.set;
         if (set === undefined || set === false) {
             if (this.isLateralModeActivatedOrArmed(mode)) {
                 return;
             }
         }
         if (set === undefined || set === true) {
             if (!this.stateManager.fdMasterOn.get()) {
                 this.stateManager.setFlightDirector(true);
             }
             switch (mode) {
                 case APLateralModes.NONE:
                     break;
                 case APLateralModes.LEVEL:
                 case APLateralModes.ROLL:
                 case APLateralModes.HEADING:
                 case APLateralModes.LOC:
                 case APLateralModes.BC:
                     (_a = this.lateralModes.get(mode)) === null || _a === void 0 ? void 0 : _a.arm();
                     break;
                 case APLateralModes.NAV:
                     if (this.cdiSource.type === NavSourceType.Gps) {
                         (_b = this.lateralModes.get(APLateralModes.GPSS)) === null || _b === void 0 ? void 0 : _b.arm();
                     }
                     else {
                         (_c = this.lateralModes.get(APLateralModes.VOR)) === null || _c === void 0 ? void 0 : _c.arm();
                         (_d = this.lateralModes.get(APLateralModes.LOC)) === null || _d === void 0 ? void 0 : _d.arm();
                     }
                     break;
             }
         }
     }
     /**
      * Handles input from the State Manager when a vertical mode button is pressed.
      * @param data is the AP Vertical Mode Event Data
      */
     verticalPressed(data) {
         var _a;
         const mode = data.mode;
         if (!this.verticalModes.has(mode)) {
             return;
         }
         const set = data.set;
         if (set === undefined || set === false) {
             if (this.isVerticalModeActivatedOrArmed(mode)) {
                 return;
             }
         }
         if (set === undefined || set === true) {
             if (!this.stateManager.fdMasterOn.get()) {
                 this.stateManager.setFlightDirector(true);
             }
             switch (mode) {
                 case APVerticalModes.NONE:
                 case APVerticalModes.VNAV:
                     break;
                 case APVerticalModes.ALT:
                     this.setAltHold();
                     break;
                 case APVerticalModes.PITCH:
                 case APVerticalModes.VS:
                 case APVerticalModes.FLC:
                 case APVerticalModes.GP:
                 case APVerticalModes.GS:
                     (_a = this.verticalModes.get(mode)) === null || _a === void 0 ? void 0 : _a.arm();
             }
         }
     }
     /**
      * Checks if a mode is active or armed and optionally deactivates it.
      * @param mode is the AP Mode to check.
      * @returns whether this mode was active or armed and subsequently disabled.
      */
     isLateralModeActivatedOrArmed(mode) {
         var _a, _b, _c, _d, _e, _f;
         switch (mode) {
             case this.lateralActive:
                 (_a = this.lateralModes.get(mode)) === null || _a === void 0 ? void 0 : _a.deactivate();
                 (_b = this.lateralModes.get(this.config.defaultLateralMode)) === null || _b === void 0 ? void 0 : _b.arm();
                 return true;
             case this.lateralArmed:
                 (_c = this.lateralModes.get(mode)) === null || _c === void 0 ? void 0 : _c.deactivate();
                 this.lateralArmed = APLateralModes.NONE;
                 return true;
             case APLateralModes.NAV: {
                 const activeNavMode = this.lateralActive === APLateralModes.LOC ? APLateralModes.LOC
                     : this.lateralActive === APLateralModes.VOR ? APLateralModes.VOR
                         : this.lateralActive === APLateralModes.GPSS ? APLateralModes.GPSS
                             : APLateralModes.NONE;
                 if (activeNavMode !== APLateralModes.NONE) {
                     (_d = this.lateralModes.get(activeNavMode)) === null || _d === void 0 ? void 0 : _d.deactivate();
                     (_e = this.lateralModes.get(this.config.defaultLateralMode)) === null || _e === void 0 ? void 0 : _e.arm();
                     this.lateralActive = this.config.defaultLateralMode;
                 }
                 const armedNavMode = this.lateralArmed === APLateralModes.LOC ? APLateralModes.LOC
                     : this.lateralArmed === APLateralModes.VOR ? APLateralModes.VOR
                         : this.lateralArmed === APLateralModes.GPSS ? APLateralModes.GPSS
                             : APLateralModes.NONE;
                 if (armedNavMode !== APLateralModes.NONE) {
                     (_f = this.lateralModes.get(armedNavMode)) === null || _f === void 0 ? void 0 : _f.deactivate();
                     this.lateralArmed = APLateralModes.NONE;
                 }
                 if (armedNavMode !== APLateralModes.NONE || activeNavMode !== APLateralModes.NONE) {
                     return true;
                 }
             }
         }
         return false;
     }
     /**
      * Checks if a mode is active or armed and deactivates it.
      * @param mode is the AP Mode to check.
      * @returns whether this mode was active or armed and subsequently disabled.
      */
     isVerticalModeActivatedOrArmed(mode) {
         var _a, _b, _c, _d;
         switch (mode) {
             case this.verticalActive:
                 (_a = this.verticalModes.get(mode)) === null || _a === void 0 ? void 0 : _a.deactivate();
                 this.verticalActive = this.config.defaultVerticalMode;
                 (_b = this.verticalModes.get(this.verticalActive)) === null || _b === void 0 ? void 0 : _b.arm();
                 return true;
             case this.verticalArmed:
                 if (mode !== APVerticalModes.ALT) {
                     (_c = this.verticalModes.get(mode)) === null || _c === void 0 ? void 0 : _c.deactivate();
                     this.verticalArmed = APVerticalModes.NONE;
                     return true;
                 }
                 break;
             case this.verticalApproachArmed:
                 (_d = this.verticalModes.get(mode)) === null || _d === void 0 ? void 0 : _d.deactivate();
                 this.verticalApproachArmed = APVerticalModes.NONE;
                 return true;
         }
         return false;
     }
     /**
      * Handles input from the State Manager when the APPR button is pressed.
      * @param set is whether this event commands a specific set
      */
     approachPressed(set) {
         var _a, _b, _c, _d, _e, _f, _g, _h;
         if ((set === undefined || set === false) && this.isVerticalModeActivatedOrArmed(APVerticalModes.GP)) {
             (_a = this.lateralModes.get(APLateralModes.GPSS)) === null || _a === void 0 ? void 0 : _a.deactivate();
             this.apValues.navToNavLocArm = false;
             return;
         }
         if ((set === undefined || set === false) && this.isVerticalModeActivatedOrArmed(APVerticalModes.GS)) {
             (_b = this.lateralModes.get(APLateralModes.LOC)) === null || _b === void 0 ? void 0 : _b.deactivate();
             this.apValues.navToNavLocArm = false;
             return;
         }
         if (set === undefined || set === true) {
             switch (this.canApproachArm()) {
                 case APLateralModes.LOC:
                     if (((_c = this.lateralModes.get(APLateralModes.LOC)) === null || _c === void 0 ? void 0 : _c.state) === DirectorState.Inactive) {
                         (_d = this.lateralModes.get(APLateralModes.LOC)) === null || _d === void 0 ? void 0 : _d.arm();
                     }
                     (_e = this.verticalModes.get(APVerticalModes.GS)) === null || _e === void 0 ? void 0 : _e.arm();
                     break;
                 case APLateralModes.GPSS:
                     if (((_f = this.lateralModes.get(APLateralModes.GPSS)) === null || _f === void 0 ? void 0 : _f.state) === DirectorState.Inactive) {
                         (_g = this.lateralModes.get(APLateralModes.GPSS)) === null || _g === void 0 ? void 0 : _g.arm();
                     }
                     (_h = this.verticalModes.get(APVerticalModes.GP)) === null || _h === void 0 ? void 0 : _h.arm();
                     break;
             }
         }
     }
     /**
      * A method to check whether an approach can arm.
      * @returns The AP Lateral Mode that can be armed.
      */
     canApproachArm() {
         switch (this.cdiSource.type) {
             case NavSourceType.Nav:
                 if (this.cdiSource.index === 1 && this.apValues.nav1HasGs.get()) {
                     return APLateralModes.LOC;
                 }
                 else if (this.cdiSource.index === 2 && this.apValues.nav2HasGs.get()) {
                     return APLateralModes.LOC;
                 }
                 break;
             case NavSourceType.Gps:
                 if (this.apValues.approachIsActive.get() && this.apValues.approachHasGP.get()) {
                     return APLateralModes.GPSS;
                 }
                 else if (this.navToNavManager) {
                     if (this.navToNavManager.canLocArm() && this.apValues.approachIsActive.get()) {
                         this.apValues.navToNavLocArm = true;
                         return APLateralModes.LOC;
                     }
                 }
         }
         return APLateralModes.NONE;
     }
     /**
      * Callback to set the lateral active mode.
      * @param mode is the mode being set.
      */
     setLateralActive(mode) {
         this.checkRollModeActive();
         if (mode !== this.lateralActive) {
             const currentMode = this.lateralModes.get(this.lateralActive);
             currentMode === null || currentMode === void 0 ? void 0 : currentMode.deactivate();
             this.lateralActive = mode;
         }
         if (this.lateralArmed === mode) {
             this.lateralArmed = APLateralModes.NONE;
         }
         this.apValues.lateralActive.set(this.lateralActive);
     }
     /**
      * Callback to set the lateral armed mode.
      * @param mode is the mode being set.
      */
     setLateralArmed(mode) {
         const currentMode = this.lateralModes.get(this.lateralArmed);
         currentMode === null || currentMode === void 0 ? void 0 : currentMode.deactivate();
         this.lateralArmed = mode;
     }
     /**
      * Callback to set the vertical active mode.
      * @param mode is the mode being set.
      */
     setVerticalActive(mode) {
         this.checkPitchModeActive();
         if (mode !== this.verticalActive) {
             const currentMode = this.verticalModes.get(this.verticalActive);
             if ((currentMode === null || currentMode === void 0 ? void 0 : currentMode.state) !== DirectorState.Inactive) {
                 currentMode === null || currentMode === void 0 ? void 0 : currentMode.deactivate();
             }
             this.verticalActive = mode;
         }
         if (this.verticalArmed === mode) {
             this.verticalArmed = APVerticalModes.NONE;
         }
         else if (this.verticalApproachArmed === mode) {
             this.verticalApproachArmed = APVerticalModes.NONE;
         }
         this.apValues.verticalActive.set(this.verticalActive);
     }
     /**
      * Callback to set the vertical armed mode.
      * @param mode is the mode being set.
      */
     setVerticalArmed(mode) {
         if (mode !== this.verticalArmed) {
             const currentMode = this.verticalModes.get(this.verticalArmed);
             if ((currentMode === null || currentMode === void 0 ? void 0 : currentMode.state) !== DirectorState.Inactive) {
                 currentMode === null || currentMode === void 0 ? void 0 : currentMode.deactivate();
             }
         }
         this.verticalArmed = mode;
     }
     /**
      * Callback to set the vertical approach armed mode.
      * @param mode is the mode being set.
      */
     setVerticalApproachArmed(mode) {
         const currentMode = this.verticalModes.get(this.verticalApproachArmed);
         currentMode === null || currentMode === void 0 ? void 0 : currentMode.deactivate();
         this.verticalApproachArmed = mode;
     }
     /**
      * Method called when the ALT button is pressed.
      */
     setAltHold() {
         var _a;
         if (this.verticalModes.has(APVerticalModes.ALT)) {
             const currentAlt = 10 * (this.inClimb ? Math.ceil(this.currentAltitude / 10) : Math.floor(this.currentAltitude / 10));
             this.apValues.capturedAltitude.set(currentAlt);
             (_a = this.verticalModes.get(APVerticalModes.ALT)) === null || _a === void 0 ? void 0 : _a.activate();
         }
     }
     /**
      * Initializes the Autopilot with the available lateral modes from the config.
      */
     initLateralModes() {
         if (this.directors.rollDirector) {
             this.lateralModes.set(APLateralModes.ROLL, this.directors.rollDirector);
             this.directors.rollDirector.onActivate = () => {
                 this.setLateralActive(APLateralModes.ROLL);
             };
         }
         if (this.directors.wingLevelerDirector) {
             this.lateralModes.set(APLateralModes.LEVEL, this.directors.wingLevelerDirector);
             this.directors.wingLevelerDirector.onActivate = () => {
                 this.setLateralActive(APLateralModes.LEVEL);
             };
         }
         if (this.directors.headingDirector) {
             this.lateralModes.set(APLateralModes.HEADING, this.directors.headingDirector);
             this.directors.headingDirector.onActivate = () => {
                 this.setLateralActive(APLateralModes.HEADING);
             };
         }
         if (this.directors.gpssDirector) {
             this.lateralModes.set(APLateralModes.GPSS, this.directors.gpssDirector);
             this.directors.gpssDirector.onArm = () => {
                 this.setLateralArmed(APLateralModes.GPSS);
             };
             this.directors.gpssDirector.onActivate = () => {
                 this.setLateralActive(APLateralModes.GPSS);
             };
         }
         if (this.directors.vorDirector) {
             this.lateralModes.set(APLateralModes.VOR, this.directors.vorDirector);
             this.directors.vorDirector.onArm = () => {
                 this.setLateralArmed(APLateralModes.VOR);
             };
             this.directors.vorDirector.onActivate = () => {
                 this.setLateralActive(APLateralModes.VOR);
             };
         }
         if (this.directors.locDirector) {
             this.lateralModes.set(APLateralModes.LOC, this.directors.locDirector);
             this.directors.locDirector.onArm = () => {
                 this.setLateralArmed(APLateralModes.LOC);
             };
             this.directors.locDirector.onActivate = () => {
                 this.setLateralActive(APLateralModes.LOC);
             };
         }
     }
     /**
      * Initializes the Autopilot with the available Nav To Nav Manager.
      */
     initNavToNavManager() {
         if (this.navToNavManager) {
             this.navToNavManager.onTransferred = () => {
                 var _a;
                 if (this.lateralActive === APLateralModes.GPSS) {
                     (_a = this.lateralModes.get(APLateralModes.LOC)) === null || _a === void 0 ? void 0 : _a.activate();
                 }
                 this.apValues.navToNavLocArm = false;
             };
         }
     }
     /**
      * Initializes the Autopilot with the available vertical modes from the config.
      */
     initVerticalModes() {
         if (this.directors.pitchDirector) {
             this.verticalModes.set(APVerticalModes.PITCH, this.directors.pitchDirector);
             this.directors.pitchDirector.onActivate = () => {
                 this.setVerticalActive(APVerticalModes.PITCH);
             };
         }
         if (this.directors.vsDirector) {
             this.verticalModes.set(APVerticalModes.VS, this.directors.vsDirector);
             this.directors.vsDirector.onActivate = () => {
                 this.setVerticalActive(APVerticalModes.VS);
             };
         }
         if (this.directors.flcDirector) {
             this.verticalModes.set(APVerticalModes.FLC, this.directors.flcDirector);
             this.directors.flcDirector.onActivate = () => {
                 this.setVerticalActive(APVerticalModes.FLC);
             };
         }
         if (this.directors.altHoldDirector) {
             this.verticalModes.set(APVerticalModes.ALT, this.directors.altHoldDirector);
             this.directors.altHoldDirector.onArm = () => {
                 this.setVerticalArmed(APVerticalModes.ALT);
             };
             this.directors.altHoldDirector.onActivate = () => {
                 this.altCapArmed = false;
                 this.setVerticalActive(APVerticalModes.ALT);
             };
         }
         if (this.directors.altCapDirector) {
             this.verticalModes.set(APVerticalModes.CAP, this.directors.altCapDirector);
             this.directors.altCapDirector.onArm = () => {
                 var _a;
                 this.altCapArmed = true;
                 if (this.verticalArmed === APVerticalModes.ALT) {
                     (_a = this.verticalModes.get(this.verticalArmed)) === null || _a === void 0 ? void 0 : _a.deactivate();
                 }
             };
             this.directors.altCapDirector.onActivate = () => {
                 var _a;
                 this.altCapArmed = false;
                 this.setVerticalActive(APVerticalModes.CAP);
                 (_a = this.verticalModes.get(APVerticalModes.ALT)) === null || _a === void 0 ? void 0 : _a.arm();
             };
         }
         if (this.directors.vnavDirector) {
             this.verticalModes.set(APVerticalModes.VNAV, this.directors.vnavDirector);
             this.directors.vnavDirector.onArm = () => {
                 this.setVerticalArmed(APVerticalModes.VNAV);
             };
             this.directors.vnavDirector.onActivate = () => {
                 this.setVerticalActive(APVerticalModes.VNAV);
             };
             this.directors.vnavDirector.onDeactivate = () => {
                 var _a;
                 (_a = this.verticalModes.get(APVerticalModes.CAP)) === null || _a === void 0 ? void 0 : _a.activate();
             };
         }
         if (this.directors.gpDirector) {
             this.verticalModes.set(APVerticalModes.GP, this.directors.gpDirector);
             this.directors.gpDirector.onArm = () => {
                 this.setVerticalApproachArmed(APVerticalModes.GP);
             };
             this.directors.gpDirector.onActivate = () => {
                 var _a;
                 this.setVerticalActive(APVerticalModes.GP);
                 (_a = this.verticalModes.get(APVerticalModes.VNAV)) === null || _a === void 0 ? void 0 : _a.deactivate();
                 this.setVerticalArmed(APVerticalModes.NONE);
                 this.setVerticalApproachArmed(APVerticalModes.NONE);
             };
         }
         if (this.directors.gsDirector) {
             this.verticalModes.set(APVerticalModes.GS, this.directors.gsDirector);
             this.directors.gsDirector.onArm = () => {
                 this.setVerticalApproachArmed(APVerticalModes.GS);
             };
             this.directors.gsDirector.onActivate = () => {
                 var _a;
                 this.setVerticalActive(APVerticalModes.GS);
                 (_a = this.verticalModes.get(APVerticalModes.VNAV)) === null || _a === void 0 ? void 0 : _a.deactivate();
                 this.setVerticalArmed(APVerticalModes.NONE);
                 this.setVerticalApproachArmed(APVerticalModes.NONE);
             };
         }
     }
     /**
      * Checks if all the active and armed modes are still in their proper state
      * and takes corrective action if not.
      */
     checkModes() {
         var _a, _b, _c, _d, _e, _f, _g;
         if (this.lateralModeFailed) {
             this.lateralModeFailed = false;
         }
         if (!this.stateManager.apMasterOn.get() && !this.stateManager.fdMasterOn.get()) {
             return;
         }
         if (!this.lateralModes.has(this.lateralActive) || ((_a = this.lateralModes.get(this.lateralActive)) === null || _a === void 0 ? void 0 : _a.state) !== DirectorState.Active) {
             if (this.lateralActive !== APLateralModes.NONE) {
                 this.lateralModeFailed = true;
             }
             (_b = this.lateralModes.get(this.config.defaultLateralMode)) === null || _b === void 0 ? void 0 : _b.arm();
         }
         if (this.lateralArmed !== APLateralModes.NONE && (!this.lateralModes.has(this.lateralArmed) || ((_c = this.lateralModes.get(this.lateralArmed)) === null || _c === void 0 ? void 0 : _c.state) !== DirectorState.Armed)) {
             this.setLateralArmed(APLateralModes.NONE);
         }
         if (!this.verticalModes.has(this.verticalActive) || ((_d = this.verticalModes.get(this.verticalActive)) === null || _d === void 0 ? void 0 : _d.state) !== DirectorState.Active) {
             (_e = this.verticalModes.get(APVerticalModes.PITCH)) === null || _e === void 0 ? void 0 : _e.arm();
         }
         if (this.verticalArmed !== APVerticalModes.NONE && (!this.verticalModes.has(this.verticalArmed) || ((_f = this.verticalModes.get(this.verticalArmed)) === null || _f === void 0 ? void 0 : _f.state) !== DirectorState.Armed)) {
             this.setVerticalArmed(APVerticalModes.NONE);
         }
         if (this.verticalApproachArmed !== APVerticalModes.NONE &&
             (!this.verticalModes.has(this.verticalApproachArmed) || ((_g = this.verticalModes.get(this.verticalApproachArmed)) === null || _g === void 0 ? void 0 : _g.state) !== DirectorState.Armed)) {
             this.setVerticalApproachArmed(APVerticalModes.NONE);
         }
     }
     /**
      * Runs update on each of the active and armed modes.
      */
     updateModes() {
         var _a, _b, _c, _d, _e, _f, _g, _h;
         if (this.lateralActive !== APLateralModes.NONE && this.lateralModes.has(this.lateralActive)) {
             (_a = this.lateralModes.get(this.lateralActive)) === null || _a === void 0 ? void 0 : _a.update();
         }
         if (this.lateralArmed !== APLateralModes.NONE && this.lateralModes.has(this.lateralArmed)) {
             (_b = this.lateralModes.get(this.lateralArmed)) === null || _b === void 0 ? void 0 : _b.update();
         }
         if (this.verticalActive !== APVerticalModes.NONE && this.verticalActive !== APVerticalModes.VNAV && this.verticalModes.has(this.verticalActive)) {
             (_c = this.verticalModes.get(this.verticalActive)) === null || _c === void 0 ? void 0 : _c.update();
         }
         if (this.verticalArmed !== APVerticalModes.NONE && this.verticalArmed !== APVerticalModes.VNAV && this.verticalModes.has(this.verticalArmed)) {
             (_d = this.verticalModes.get(this.verticalArmed)) === null || _d === void 0 ? void 0 : _d.update();
         }
         if (this.verticalApproachArmed !== APVerticalModes.NONE && this.verticalModes.has(this.verticalApproachArmed)) {
             (_e = this.verticalModes.get(this.verticalApproachArmed)) === null || _e === void 0 ? void 0 : _e.update();
         }
         if (this.altCapArmed) {
             (_f = this.verticalModes.get(APVerticalModes.CAP)) === null || _f === void 0 ? void 0 : _f.update();
         }
         //while vnav and vnav director are one in the same we always want to
         //run the vnav update cycle no matter the director state
         (_g = this.verticalModes.get(APVerticalModes.VNAV)) === null || _g === void 0 ? void 0 : _g.update();
         //while lnav and lnav director are one in the same we always want to
         //run the lnav update cycle no matter the director state
         (_h = this.lateralModes.get(APLateralModes.GPSS)) === null || _h === void 0 ? void 0 : _h.update();
     }
     /**
      * Checks and sets the proper armed altitude mode.
      */
     manageAltitudeCapture() {
         var _a, _b, _c;
         let altCapType = APAltitudeModes.NONE;
         let armAltCap = false;
         switch (this.verticalActive) {
             case APVerticalModes.VS:
             case APVerticalModes.FLC:
             case APVerticalModes.PITCH:
                 if (this.inClimb && this.apValues.selectedAltitude.get() > this.currentAltitude) {
                     altCapType = APAltitudeModes.ALTS;
                     armAltCap = true;
                 }
                 else if (!this.inClimb && this.apValues.selectedAltitude.get() < this.currentAltitude) {
                     altCapType = APAltitudeModes.ALTS;
                     armAltCap = true;
                 }
                 break;
             case APVerticalModes.VNAV: {
                 if (!this.inClimb) {
                     altCapType = this.vnavCaptureType === VNavAltCaptureType.VNAV ? APAltitudeModes.ALTV : APAltitudeModes.ALTS;
                 }
                 break;
             }
             case APVerticalModes.CAP:
                 altCapType = this.verticalAltitudeArmed;
                 break;
         }
         if (this.verticalAltitudeArmed !== altCapType) {
             this.verticalAltitudeArmed = altCapType;
         }
         if (armAltCap && (!this.altCapArmed || ((_a = this.verticalModes.get(APVerticalModes.CAP)) === null || _a === void 0 ? void 0 : _a.state) === DirectorState.Inactive)) {
             (_b = this.verticalModes.get(APVerticalModes.CAP)) === null || _b === void 0 ? void 0 : _b.arm();
         }
         else if (!armAltCap && this.altCapArmed) {
             (_c = this.verticalModes.get(APVerticalModes.CAP)) === null || _c === void 0 ? void 0 : _c.deactivate();
             this.altCapArmed = false;
         }
     }
     /**
      * Monitors subevents and bus events.
      */
     monitorEvents() {
         this.stateManager.lateralPressed.on((sender, data) => {
             if (this.autopilotInitialized && data !== undefined) {
                 this.lateralPressed(data);
             }
         });
         this.stateManager.verticalPressed.on((sender, data) => {
             if (this.autopilotInitialized && data !== undefined) {
                 this.verticalPressed(data);
             }
         });
         this.stateManager.approachPressed.on((sender, data) => {
             if (this.autopilotInitialized) {
                 this.approachPressed(data);
             }
         });
         this.stateManager.vnavPressed.on((sender, data) => {
             var _a, _b;
             if (this.autopilotInitialized) {
                 if (data === true) {
                     (_a = this.verticalModes.get(APVerticalModes.VNAV)) === null || _a === void 0 ? void 0 : _a.arm();
                 }
                 else {
                     (_b = this.verticalModes.get(APVerticalModes.VNAV)) === null || _b === void 0 ? void 0 : _b.deactivate();
                 }
             }
         });
         const ap = this.bus.getSubscriber();
         ap.on('alt_select').withPrecision(0).handle((alt) => {
             this.apValues.selectedAltitude.set(alt);
         });
         ap.on('heading_select').withPrecision(0).handle((hdg) => {
             this.apValues.selectedHeading.set(hdg);
         });
         ap.on('flc_hold_knots').withPrecision(0).handle((ias) => {
             this.apValues.selectedIas.set(ias);
         });
         ap.on('pitch_ref').withPrecision(1).handle((pitch) => {
             this.apValues.selectedPitch.set(pitch);
         });
         ap.on('vs_hold_fpm').withPrecision(0).handle((ias) => {
             this.apValues.selectedVerticalSpeed.set(ias);
         });
         const nav = this.bus.getSubscriber();
         nav.on('cdi_select').handle((src) => {
             this.cdiSource = src;
         });
         nav.on('glideslope').handle((gs) => {
             if (gs.source.type === NavSourceType.Nav) {
                 switch (gs.source.index) {
                     case 1:
                         this.apValues.nav1HasGs.set(gs.isValid);
                         break;
                     case 2:
                         this.apValues.nav2HasGs.set(gs.isValid);
                         break;
                 }
             }
         });
         const adc = this.bus.getSubscriber();
         adc.on('vs').withPrecision(0).handle((vs) => {
             this.inClimb = vs < 1 ? false : true;
         });
         adc.on('alt').withPrecision(0).handle(alt => {
             this.currentAltitude = alt;
         });
         const vnav = this.bus.getSubscriber();
         vnav.on('vnavAltCaptureType').whenChanged().handle((v) => {
             this.vnavCaptureType = v;
         });
         this.stateManager.apMasterOn.sub(() => {
             if (this.autopilotInitialized) {
                 this.handleApFdStateChange();
             }
         });
         this.stateManager.fdMasterOn.sub(() => {
             if (this.autopilotInitialized) {
                 this.handleApFdStateChange();
             }
         });
     }
     /**
      * Additional events to be monitored (to be overridden).
      */
     monitorAdditionalEvents() {
         //noop
     }
     /**
      * Manages the FD state and the modes when AP/FD are off.
      */
     handleApFdStateChange() {
         const ap = this.stateManager.apMasterOn.get();
         const fd = this.stateManager.fdMasterOn.get();
         if (ap && !fd) {
             SimVar.SetSimVarValue('K:TOGGLE_FLIGHT_DIRECTOR', 'number', 0);
         }
         else if (!ap && !fd) {
             this.lateralModes.forEach((mode) => {
                 if (mode.state !== DirectorState.Inactive) {
                     mode.deactivate();
                 }
             });
             this.verticalModes.forEach((mode) => {
                 if (mode.state !== DirectorState.Inactive) {
                     mode.deactivate();
                 }
             });
             this.lateralActive = APLateralModes.NONE;
             this.lateralArmed = APLateralModes.NONE;
             this.verticalActive = APVerticalModes.NONE;
             this.verticalArmed = APVerticalModes.NONE;
             this.verticalApproachArmed = APVerticalModes.NONE;
             this.verticalAltitudeArmed = APAltitudeModes.NONE;
             this.altCapArmed = false;
         }
     }
     /**
      * Sets a sim AP mode.
      * @param mode The mode to set.
      * @param enabled Whether or not the mode is enabled or disabled.
      */
     setSimAP(mode, enabled) {
         Coherent.call('apSetAutopilotMode', mode, enabled ? 1 : 0);
     }
     /**
      * Checks if the sim AP is in roll mode and sets it if not.
      */
     checkRollModeActive() {
         if (!APController.apGetAutopilotModeActive(MSFSAPStates.Bank)) {
             // console.log('checkRollModeActive had to set Bank mode');
             this.setSimAP(MSFSAPStates.Bank, true);
         }
     }
     /**
      * Checks if the sim AP is in pitch mode and sets it if not.
      */
     checkPitchModeActive() {
         if (!APController.apGetAutopilotModeActive(MSFSAPStates.Pitch)) {
             // console.log('checkPitchModeActive had to set Pitch mode');
             this.setSimAP(MSFSAPStates.Pitch, true);
         }
     }
 }
 
 /**
  * A configuration for the G1000 NavProcessor, including a custrom simvar
  * publisher configured with your LNav simvars for GPS data publishing.
  */
 class NPConfig extends NavProcessorConfig {
     /**
      * Create an NPConfig.
      * @param bus The event bus
      * @param planner A flight planner for LNav data
      */
     constructor(bus, planner) {
         super();
         this.numGps = 0;
         this.numAdf = 1;
         this.courseIncEvents.add('AS1000_PFD_CRS_INC');
         this.courseIncEvents.add('AS1000_MFD_CRS_INC');
         this.courseDecEvents.add('AS1000_PFD_CRS_DEC');
         this.courseDecEvents.add('AS1000_MFD_CRS_DEC');
         this.additionalSources.push(new LNavNavSource(bus, planner));
     }
 }
 /** A custom nav data source that provides info from our flight plan manager. */
 class LNavNavSource {
     /**
      * Create an LNavNavSource.
      * @param bus An event bus.
      * @param planner The flight planner.
      */
     constructor(bus, planner) {
         this.srcId = { type: NavSourceType.Gps, index: 1 };
         this.hasCdi = true;
         this.hasDme = true;
         this.hasGlideslope = false;
         this.hasLocalizer = false;
         this.signal = null;
         this.activeCdi = false;
         this.isLocalizerFrequency = false;
         // eslint-disable-next-line @typescript-eslint/no-unused-vars
         this.validHander = (valid, source) => { };
         // eslint-disable-next-line @typescript-eslint/no-unused-vars
         this.identHandler = (ident, source) => { };
         // eslint-disable-next-line @typescript-eslint/no-unused-vars
         this.brgHandler = (brg, source) => { };
         // eslint-disable-next-line @typescript-eslint/no-unused-vars
         this.distHandler = (dist, source) => { };
         this._activeForCount = 0;
         this._dist = null;
         this._brg = null;
         this._ident = null;
         this._valid = false;
         this.planner = planner;
         const fpl = bus.getSubscriber();
         const lnav = bus.getSubscriber();
         fpl.on('fplActiveLegChange').handle((change) => {
             this.onLegChange(change);
         });
         lnav.on('lnavDis').withPrecision(1).handle((dist) => {
             this.distance = dist;
         });
         lnav.on('lnavBrgMag').whenChangedBy(1).handle((brg) => {
             this.bearing = brg;
         });
     }
     /**
      * Get the validity of the source.
      * @returns Whether the source is valid.
      */
     get valid() {
         return this._valid;
     }
     /**
      * Set the validity of the source.
      * @param valid Whether the source is valid.
      */
     set valid(valid) {
         this._valid = valid;
         this.validHander(this.valid, this.srcId);
     }
     /**
      * Get the distance to the active waypoint.
      * @returns The distance in nm or null.
      */
     get distance() {
         return this._dist;
     }
     /**
      * Set the tistance to the active waypoint.
      * @param dist The distance in nm or null.
      */
     set distance(dist) {
         this._dist = dist;
         this.activeBrg && this.distHandler(this.distance, this.srcId);
     }
     /**
      * Get the bearing to the current waypoint.
      * @returns The bearing in degrees or null.
      */
     get bearing() {
         return this._brg;
     }
     /**
      * Set the bearing to the current waypoint.
      * @param brg The bearing in degrees.
      */
     set bearing(brg) {
         this._brg = brg;
         this.activeBrg && this.brgHandler(this.bearing, this.srcId);
     }
     /**
      * Get the ident of the current waypoint.
      * @returns The ident as a string or null.
      */
     get ident() {
         return this._ident;
     }
     /**
      * Set the ident of the current waypoint.
      * @param ident The ident as a string or null.
      */
     set ident(ident) {
         this._ident = ident;
         if (ident === null) {
             this.valid = false;
         }
         else {
             this.valid = true;
         }
         this.activeBrg && this.identHandler(this.ident, this.srcId);
     }
     /**
      * Get active bearing status.
      * @returns Whether we are active for bearing data.
      */
     get activeBrg() {
         return this._activeForCount > 0;
     }
     /**
      * Set active bearing status.
      */
     set activeBrg(active) {
         if (active) {
             this._activeForCount++;
         }
         else if (this._activeForCount > 0) {
             this._activeForCount--;
         }
         if (!this.activeBrg) {
             this.brgHandler(null, this.srcId);
             this.distHandler(null, this.srcId);
             this.identHandler(null, this.srcId);
         }
         else {
             this.brgHandler(this.bearing, this.srcId);
             this.distHandler(this.distance, this.srcId);
             // See if we need to update our ident info before displaying.
             const plan = this.planner.hasActiveFlightPlan() ? this.planner.getActiveFlightPlan() : undefined;
             if (plan && plan.length > 0 && plan.activeLateralLeg < plan.length) {
                 const ident = plan.getLeg(plan.activeLateralLeg).name;
                 if (ident) {
                     this.ident = ident;
                 }
             }
             this.identHandler(this.ident, this.srcId);
         }
     }
     /**
      * Handle a change in the active leg.
      * @param change The change event.
      */
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     onLegChange(change) {
         const plan = this.planner.getActiveFlightPlan();
         if (plan.length > 0 && plan.activeLateralLeg < plan.length) {
             const leg = plan.getLeg(plan.activeLateralLeg);
             this.ident = leg.name ? leg.name : null;
         }
     }
 }
 
 /**
  * Utility Methods for the FMS.
  */
 class FmsUtils {
     /**
      * Utility method to return a one-way runway leg
      * @param airport The runway's parent airport.
      * @param oneWayRunway is the one wway runway object
      * @param isOriginRunway is a bool whether this is the origin or destination (origin = true, dest = false)
      * @returns a leg object for the runway
      */
     static buildRunwayLeg(airport, oneWayRunway, isOriginRunway) {
         const leg = FlightPlan.createLeg({
             lat: oneWayRunway.latitude,
             lon: oneWayRunway.longitude,
             type: isOriginRunway ? LegType.IF : LegType.TF,
             fixIcao: RunwayUtils.getRunwayFacilityIcao(airport, oneWayRunway),
             altitude1: oneWayRunway.elevation
         });
         return leg;
     }
     /**
      * Utility method to return a one-way runway leg from an approach runway leg definition
      * @param airport is the facility associated with the arrival
      * @param runwayIcao is the icao string for the runway waypoint in the final legs
      * @returns a leg object for the runway
      */
     static buildRunwayLegForApproach(airport, runwayIcao) {
         for (let i = 0; i < airport.runways.length; i++) {
             const match = RunwayUtils.getOneWayRunways(airport.runways[i], i).find((r) => {
                 return (r.designation == ICAO.getIdent(runwayIcao));
             });
             if (match) {
                 const leg = FlightPlan.createLeg({
                     lat: match.latitude,
                     lon: match.longitude,
                     type: LegType.TF,
                     fixIcao: runwayIcao
                 });
                 return leg;
             }
         }
         return undefined;
     }
     /**
      * Utility method to return a visual approach for a runway.
      * @param airport is the airport facility for the visual approach.
      * @param runway is the runway to build the visual approach for.
      * @param finalLegDistance is the distance from the runway to place the faf leg in NM.
      * @param initialLegDistance is the distance from the final leg to place the iaf leg in NM.
      * @param name is the optional name for the approach.
      * @param finalLegIdent is the optional name for the faf leg.
      * @param initialLegIdent is the optional name for the iaf leg.
      * @returns an approach procedure.
      */
     static buildVisualApproach(airport, runway, finalLegDistance, initialLegDistance, name, finalLegIdent, initialLegIdent) {
         const runwayVec = GeoPoint.sphericalToCartesian(runway.latitude, runway.longitude, FmsUtils.vec3Cache[0]);
         const approachPath = FmsUtils.geoCircleCache[0].setAsGreatCircle(runwayVec, runway.course);
         const iafLatLon = approachPath.offsetDistanceAlong(runwayVec, UnitType.NMILE.convertTo(-(initialLegDistance + finalLegDistance), UnitType.GA_RADIAN), FmsUtils.geoPointCache[0]);
         const runwayCode = RunwayUtils.getRunwayCode(runway.direction);
         const runwayLetter = RunwayUtils.getDesignatorLetter(runway.runwayDesignator).padStart(1, ' ');
         initialLegIdent !== null && initialLegIdent !== void 0 ? initialLegIdent : (initialLegIdent = 'STRGHT');
         const iafLeg = FlightPlan.createLeg({
             type: LegType.IF,
             fixIcao: `S${ICAO.getIdent(airport.icao).padStart(4, ' ')}${runwayCode}${runwayLetter}${initialLegIdent}`,
             lat: iafLatLon.lat,
             lon: iafLatLon.lon,
         });
         const fafLatLon = approachPath.offsetDistanceAlong(runwayVec, UnitType.NMILE.convertTo(-finalLegDistance, UnitType.GA_RADIAN), FmsUtils.geoPointCache[0]);
         finalLegIdent !== null && finalLegIdent !== void 0 ? finalLegIdent : (finalLegIdent = ' FINAL');
         const fafLeg = FlightPlan.createLeg({
             type: LegType.CF,
             fixIcao: `S${ICAO.getIdent(airport.icao).padStart(4, ' ')}${runwayCode}${runwayLetter}${finalLegIdent}`,
             course: MagVar.trueToMagnetic(approachPath.bearingAt(fafLatLon), fafLatLon),
             fixTypeFlags: FixTypeFlags.FAF,
             lat: fafLatLon.lat,
             lon: fafLatLon.lon,
             altDesc: AltitudeRestrictionType.AtOrAbove,
             altitude1: runway.elevation + 110,
         });
         const runwayLeg = FmsUtils.buildRunwayLeg(airport, runway, false);
         runwayLeg.fixTypeFlags = FixTypeFlags.MAP;
         const finalLegs = [];
         finalLegs.push(iafLeg);
         finalLegs.push(fafLeg);
         finalLegs.push(runwayLeg);
         const missedLegLatLon = approachPath.offsetDistanceAlong(runwayVec, UnitType.NMILE.convertTo(5, UnitType.GA_RADIAN), FmsUtils.geoPointCache[0]);
         const missedLeg = FlightPlan.createLeg({
             type: LegType.TF,
             fixIcao: `S${ICAO.getIdent(airport.icao).padStart(4, ' ')}${runwayCode}${runwayLetter}MANSEQ`,
             lat: missedLegLatLon.lat,
             lon: missedLegLatLon.lon,
         });
         const missedLegs = [];
         missedLegs.push(missedLeg);
         const proc = {
             name: name !== null && name !== void 0 ? name : `Visual RW${runway.designation}`,
             runway: runway.designation,
             icaos: [],
             transitions: [{ name: 'STRAIGHT', legs: [] }],
             finalLegs: finalLegs,
             missedLegs: missedLegs,
             approachType: AdditionalApproachType.APPROACH_TYPE_VISUAL,
             approachSuffix: '',
             runwayDesignator: runway.runwayDesignator,
             runwayNumber: runway.direction,
             rnavTypeFlags: RnavTypeFlags.None
         };
         return proc;
     }
     /**
      * Utility method to return a single RnavTypeFlag from multiple possible flags.
      * @param rnavTypeFlags The input RnavTypeFlags.
      * @returns A single RnavTypeFlag
      */
     static getBestRnavType(rnavTypeFlags) {
         if (rnavTypeFlags & RnavTypeFlags.LPV) {
             return RnavTypeFlags.LPV;
         }
         if (rnavTypeFlags & RnavTypeFlags.LNAVVNAV) {
             return RnavTypeFlags.LNAVVNAV;
         }
         if (rnavTypeFlags & RnavTypeFlags.LP) {
             return RnavTypeFlags.LP;
         }
         if (rnavTypeFlags & RnavTypeFlags.LNAV) {
             return RnavTypeFlags.LNAV;
         }
         return RnavTypeFlags.None;
     }
     /**
      * Utility method to check whether an approach is authorized for GPS guidance.
      * @param approach The approach procedure
      * @returns True if GPS guidance is authorized, false otherwise.
      */
     static isGpsApproach(approach) {
         switch (approach.approachType) {
             case ApproachType.APPROACH_TYPE_GPS:
             case ApproachType.APPROACH_TYPE_RNAV:
                 return true;
         }
         return false;
     }
     /**
      * Utility method to check for an approach with a a tunable localizer.
      * @param approach The approach procedure
      * @returns True if a localizer needs to be tuned, otherwise false.
      */
     static isLocalizerApproach(approach) {
         switch (approach.approachType) {
             case ApproachType.APPROACH_TYPE_ILS:
             case ApproachType.APPROACH_TYPE_LDA:
             case ApproachType.APPROACH_TYPE_LOCALIZER:
             case ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE:
             case ApproachType.APPROACH_TYPE_SDF:
                 return true;
         }
         return false;
     }
     /**
      * Gets an approach procedure from a flight plan.
      * @param plan A flight plan.
      * @param destination The detsination airport of the flight plan.
      * @returns The approach procedure from the flight plan, or undefined if the plan has no approach.
      */
     static getApproachFromPlan(plan, destination) {
         let approach = destination.approaches[plan.procedureDetails.approachIndex];
         if (!approach) {
             const visualRwyDesignation = plan.getUserData('visual_approach');
             if (visualRwyDesignation && plan.destinationAirport) {
                 const runway = RunwayUtils.matchOneWayRunwayFromDesignation(destination, visualRwyDesignation);
                 if (runway) {
                     approach = {
                         name: `VISUAL ${visualRwyDesignation}`,
                         runway: runway.designation,
                         icaos: [],
                         transitions: [],
                         finalLegs: [],
                         missedLegs: [],
                         approachType: AdditionalApproachType.APPROACH_TYPE_VISUAL,
                         approachSuffix: '',
                         runwayDesignator: runway.runwayDesignator,
                         runwayNumber: runway.direction,
                         rnavTypeFlags: RnavTypeFlags.None
                     };
                 }
             }
         }
         return approach;
     }
     /**
      * Checks whether a flight plan has an approach loaded.
      * @param plan A flight plan.
      * @returns Whether the flight plan has an approach loaded.
      */
     static isApproachLoaded(plan) {
         return plan.procedureDetails.approachIndex >= 0 || (plan.getUserData('visual_approach') !== undefined && plan.destinationAirport !== undefined);
     }
     /**
      * Checks whether a plan has a vectors-to-final approach loaded.
      * @param plan A flight plan.
      * @returns Whether the flight plan has a vectors-to-final approach loaded.
      */
     static isVtfApproachLoaded(plan) {
         return !!FmsUtils.getApproachVtfLeg(plan);
     }
     /**
      * Gets the vectors-to-final leg of a flight plan.
      * @param plan A flight plan.
      * @returns The vectors-to-final leg of the flight plan, or undefined if one could not be found.
      */
     static getApproachVtfLeg(plan) {
         if (!FmsUtils.isApproachLoaded(plan) || plan.procedureDetails.approachTransitionIndex >= 0) {
             return undefined;
         }
         // There should only be one approach segment
         for (const approachSegment of plan.segmentsOfType(FlightPlanSegmentType.Approach)) {
             return approachSegment.legs.find(leg => BitFlags.isAll(leg.flags, LegDefinitionFlags.VectorsToFinal) && BitFlags.isAll(leg.leg.fixTypeFlags, FixTypeFlags.FAF));
         }
         return undefined;
     }
     /**
      * Gets the name of a departure procedure as a string.
      * @param airport The airport to which the departure belongs.
      * @param departure A departure procedure definition.
      * @param transitionIndex The index of the departure enroute transition.
      * @param runway The runway of the departure, if any.
      * @returns The name of the departure procedure.
      */
     static getDepartureNameAsString(airport, departure, transitionIndex, runway) {
         let name = `${ICAO.getIdent(airport.icao)}–`;
         if (runway) {
             name += `RW${runway.designation}.`;
         }
         const transition = departure.enRouteTransitions[transitionIndex];
         if (transition !== undefined && transitionIndex > -1 && transition.legs.length > 0) {
             name += `${departure.name}.${ICAO.getIdent(transition.legs[transition.legs.length - 1].fixIcao)}`;
         }
         else if (departure.commonLegs.length > 0) {
             name += `${departure.name}.${ICAO.getIdent(departure.commonLegs[departure.commonLegs.length - 1].fixIcao)}`;
         }
         else {
             name += `${departure.name}`;
         }
         return name;
     }
     /**
      * Gets the name of a arrival procedure as a string.
      * @param airport The airport to which the departure belongs.
      * @param arrival An arrival procedure definition.
      * @param transitionIndex The index of the arrival enroute transition.
      * @param runway The runway of the arrival, if any.
      * @returns The name of the arrival procedure.
      */
     static getArrivalNameAsString(airport, arrival, transitionIndex, runway) {
         let name = `${ICAO.getIdent(airport.icao)}–`;
         const transition = arrival.enRouteTransitions[transitionIndex];
         if (transition !== undefined && transitionIndex > -1 && transition.legs.length > 0) {
             name += `${ICAO.getIdent(transition.legs[0].fixIcao)}.${arrival === null || arrival === void 0 ? void 0 : arrival.name}`;
         }
         else if (arrival.commonLegs.length > 0) {
             name += `${ICAO.getIdent(arrival.commonLegs[0].fixIcao)}.${arrival === null || arrival === void 0 ? void 0 : arrival.name}`;
         }
         else {
             name += `${arrival === null || arrival === void 0 ? void 0 : arrival.name}`;
         }
         if (runway) {
             name += `.RW${runway.designation}`;
         }
         return name;
     }
     /**
      * Utility method to analyze an approach for its name components and
      * pack them into a custom type.
      * @param proc The approach procedure.
      * @returns The name as an ApproachNameParts
      */
     static getApproachNameAsParts(proc) {
         let type;
         let subtype;
         let rnavType;
         switch (proc.approachType) {
             case ApproachType.APPROACH_TYPE_GPS:
                 type = 'GPS';
                 break;
             case ApproachType.APPROACH_TYPE_VOR:
                 type = 'VOR';
                 break;
             case ApproachType.APPROACH_TYPE_NDB:
                 type = 'NDB';
                 break;
             case ApproachType.APPROACH_TYPE_ILS:
                 type = 'ILS';
                 break;
             case ApproachType.APPROACH_TYPE_LOCALIZER:
                 type = 'LOC';
                 break;
             case ApproachType.APPROACH_TYPE_SDF:
                 type = 'SDF';
                 break;
             case ApproachType.APPROACH_TYPE_LDA:
                 type = 'LDA';
                 break;
             case ApproachType.APPROACH_TYPE_VORDME:
                 type = 'VOR/DME';
                 break;
             case ApproachType.APPROACH_TYPE_NDBDME:
                 type = 'NDB/DME';
                 break;
             case ApproachType.APPROACH_TYPE_RNAV:
                 type = 'RNAV';
                 subtype = 'GPS';
                 break;
             case ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE:
                 type = 'LOC BC';
                 break;
             case AdditionalApproachType.APPROACH_TYPE_VISUAL:
                 type = 'VISUAL';
                 break;
             default:
                 type = '???';
                 break;
         }
         const approachIsCircling = !proc.runway ? true : false;
         if (proc.approachType === ApproachType.APPROACH_TYPE_RNAV) {
             switch (FmsUtils.getBestRnavType(proc.rnavTypeFlags)) {
                 case RnavTypeFlags.LNAV:
                     rnavType = approachIsCircling ? 'LNAV' : 'LNAV+V';
                     break;
                 case RnavTypeFlags.LP:
                     rnavType = approachIsCircling ? 'LP' : 'LP+V';
                     break;
                 case RnavTypeFlags.LNAVVNAV:
                     rnavType = 'LNAV/VNAV';
                     break;
                 case RnavTypeFlags.LPV:
                     rnavType = 'LPV';
                     break;
             }
         }
         return {
             type: type,
             subtype: subtype,
             suffix: proc.approachSuffix ? proc.approachSuffix : undefined,
             runway: proc.runwayNumber === 0 ? undefined : RunwayUtils.getRunwayNameString(proc.runwayNumber, proc.runwayDesignator, true),
             flags: rnavType
         };
     }
     /**
      * Utility method that takes an approach and returns its name as a flat
      * string suitable for use in embedded text content.
      * @param approach The approach as an ApproaceProcedure
      * @returns The formatted name as a string.
      */
     static getApproachNameAsString(approach) {
         const parts = FmsUtils.getApproachNameAsParts(approach);
         let name = parts.type;
         parts.subtype && (name += `${parts.subtype}`);
         parts.suffix && (name += `${parts.runway ? ' ' : '–'}${parts.suffix}`);
         parts.runway && (name += ` ${parts.runway}`);
         parts.flags && (name += ` ${parts.flags}`);
         return name;
     }
     /**
      * Gets an approach frequency from the facility record.
      * @param facility The airport facility.
      * @param approachIndex The approach Index.
      * @returns The FacilityFrequency or undefined
      */
     static getApproachFrequency(facility, approachIndex) {
         const approach = facility === null || facility === void 0 ? void 0 : facility.approaches[approachIndex !== null && approachIndex !== void 0 ? approachIndex : -1];
         if (approach && (approach.approachType === ApproachType.APPROACH_TYPE_ILS
             || approach.approachType === ApproachType.APPROACH_TYPE_LOCALIZER
             || approach.approachType === ApproachType.APPROACH_TYPE_LDA
             || approach.approachType === ApproachType.APPROACH_TYPE_SDF)) {
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             const freq = RunwayUtils.getLocFrequency(facility, approach.runwayNumber, approach.runwayDesignator);
             return freq;
         }
         else {
             return undefined;
         }
     }
     /**
      * Gets the nominal leg from which a specified flight plan leg originates. The nominal from leg excludes any legs
      * which are part of a direct to or vectors-to-final sequence.
      * @param plan A flight plan.
      * @param segmentIndex The index of the segment containing the leg for which to get the from leg.
      * @param segmentLegIndex The index of the leg for which to get the from leg in its segment.
      * @returns The nominal leg from which the specified flight plan leg originates.
      */
     static getNominalFromLeg(plan, segmentIndex, segmentLegIndex) {
         let leg = plan.getPrevLeg(segmentIndex, segmentLegIndex);
         if (!leg) {
             return undefined;
         }
         for (leg of plan.legs(true, plan.getLegIndexFromLeg(leg))) {
             if (!BitFlags.isAny(leg.flags, LegDefinitionFlags.DirectTo | LegDefinitionFlags.VectorsToFinal)) {
                 return leg;
             }
         }
         return undefined;
     }
     /**
      * Gets the global leg index of the nominal leg from which a specified flight plan leg originates. The nominal from
      * leg excludes any legs which are part of a direct to or vectors-to-final sequence.
      * @param plan A flight plan.
      * @param segmentIndex The index of the segment containing the leg for which to get the from leg.
      * @param segmentLegIndex The index of the leg for which to get the from leg in its segment.
      * @returns The nominal leg from which the specified flight plan leg originates.
      */
     static getNominalFromLegIndex(plan, segmentIndex, segmentLegIndex) {
         let leg = plan.getPrevLeg(segmentIndex, segmentLegIndex);
         if (!leg) {
             return -1;
         }
         let index = plan.getLegIndexFromLeg(leg);
         for (leg of plan.legs(true, index)) {
             if (!BitFlags.isAny(leg.flags, LegDefinitionFlags.DirectTo | LegDefinitionFlags.VectorsToFinal)) {
                 return index;
             }
             index--;
         }
         return -1;
     }
 }
 FmsUtils.vec3Cache = [new Float64Array(3)];
 FmsUtils.geoPointCache = [new GeoPoint(0, 0)];
 FmsUtils.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
 FmsUtils.DTO_LEG_OFFSET = 3;
 
 var DirectToState;
 (function (DirectToState) {
     DirectToState[DirectToState["NONE"] = 0] = "NONE";
     DirectToState[DirectToState["TOEXISTING"] = 1] = "TOEXISTING";
     DirectToState[DirectToState["TORANDOM"] = 2] = "TORANDOM";
 })(DirectToState || (DirectToState = {}));
 var ProcedureType;
 (function (ProcedureType) {
     ProcedureType[ProcedureType["DEPARTURE"] = 0] = "DEPARTURE";
     ProcedureType[ProcedureType["ARRIVAL"] = 1] = "ARRIVAL";
     ProcedureType[ProcedureType["APPROACH"] = 2] = "APPROACH";
     ProcedureType[ProcedureType["VISUALAPPROACH"] = 3] = "VISUALAPPROACH";
 })(ProcedureType || (ProcedureType = {}));
 var AirwayLegType;
 (function (AirwayLegType) {
     AirwayLegType[AirwayLegType["NONE"] = 0] = "NONE";
     AirwayLegType[AirwayLegType["ENTRY"] = 1] = "ENTRY";
     AirwayLegType[AirwayLegType["EXIT"] = 2] = "EXIT";
     AirwayLegType[AirwayLegType["ONROUTE"] = 3] = "ONROUTE";
     AirwayLegType[AirwayLegType["EXIT_ENTRY"] = 4] = "EXIT_ENTRY";
 })(AirwayLegType || (AirwayLegType = {}));
 /**
  * A fms menu system tracker.
  */
 class Fms {
     /**
      * Initialize an instance of the FMS.
      * @param bus is the event bus
      * @param flightPlanner is the flight planner
      * @param viewService the popout menu service
      * @param g1000EvtPub is the G1000 Control Publisher
      * @param autopilot is the optional autopilot.
      */
     constructor(bus, flightPlanner, viewService, g1000EvtPub, autopilot) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.autopilot = autopilot;
         this.ppos = new GeoPoint(0, 0);
         this.facRepo = FacilityRespository.getRepository(this.bus);
         this.facLoader = new FacilityLoader(this.facRepo);
         this.calculator = new FlightPathCalculator(this.facLoader, { defaultClimbRate: 300, defaultSpeed: 85, bankAngle: 15 });
         this.approachDetails = {
             approachLoaded: false,
             approachType: ApproachType.APPROACH_TYPE_UNKNOWN,
             approachRnavType: RnavTypeFlags.None,
             approachIsActive: false,
             approachIsCircling: false
         };
         this.approachFrequency = Subject.create(undefined);
         this._lastApproachFrequencyEventValue = undefined;
         this.cdiSource = { type: NavSourceType.Gps, index: 1 };
         this.missedApproachActive = false;
         this.insertApproachOpId = 0;
         /**
          * Sets the approach details when an approach_details_set event is received from the bus.
          * @param approachDetails The approachDetails received from the bus.
          */
         this.onApproachDetailsSet = (approachDetails) => {
             if (approachDetails !== this.approachDetails) {
                 this.approachDetails = approachDetails;
             }
         };
         if (viewService) {
             Fms.viewService = viewService;
         }
         if (g1000EvtPub) {
             Fms.g1000EvtPub = g1000EvtPub;
         }
         this.bus.getSubscriber().on('gps-position').atFrequency(1).handle(pos => this.ppos.set(pos.lat, pos.long));
         this.bus.getSubscriber().on('cdi_select').handle(source => this.cdiSource = source);
         const planEvents = this.bus.getSubscriber();
         planEvents.on('fplActiveLegChange').handle(data => this.onActiveLegChanged(data.type, data.planIndex));
         planEvents.on('fplLoaded').handle(() => this.checkApproachState());
         this.approachFrequency.sub((v) => {
             if (v !== this._lastApproachFrequencyEventValue) {
                 g1000EvtPub.publishEvent('approach_freq_set', v);
             }
         });
         const g1000Events = this.bus.getSubscriber();
         g1000Events.on('approach_freq_set').handle((v) => {
             this._lastApproachFrequencyEventValue = v;
             this.approachFrequency.set(v);
         });
         g1000Events.on('activate_missed_approach').handle(v => {
             this.missedApproachActive = v;
             if (this.missedApproachActive) {
                 Fms.g1000EvtPub.publishEvent('suspend_sequencing', false);
                 this.setApproachDetails(undefined, undefined, undefined, false);
             }
         });
         g1000Events.on('approach_details_set').handle(this.onApproachDetailsSet);
     }
     /**
      * Initializes the primary flight plan. Does nothing if the primary flight plan already exists.
      */
     async initPrimaryFlightPlan() {
         if (this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX)) {
             return;
         }
         this.flightPlanner.createFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         await this.emptyPrimaryFlightPlan();
     }
     /**
      * Checks whether an indexed flight plan exists.
      * @param index A flight plan index.
      * @returns Whether a flight plan at the specified index exists.
      */
     hasFlightPlan(index) {
         return this.flightPlanner.hasFlightPlan(index);
     }
     /**
      * Gets a specified flightplan, or by default the primary flight plan.
      * @param index The index of the flight plan.
      * @returns the requested flight plan
      * @throws Error if no flight plan exists at the specified index.
      */
     getFlightPlan(index = Fms.PRIMARY_PLAN_INDEX) {
         return this.flightPlanner.getFlightPlan(index);
     }
     /**
      * Checks whether the primary flight plan exists.
      * @returns Whether the primary flight plan exists.
      */
     hasPrimaryFlightPlan() {
         return this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX);
     }
     /**
      * Gets the primary flight plan.
      * @returns The primary flight plan.
      * @throws Error if the primary flight plan does not exist.
      */
     getPrimaryFlightPlan() {
         return this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
     }
     /**
      * Checks whether the Direct To Random flight plan exists.
      * @returns Whether the Direct To Random flight plan exists.
      */
     hasDirectToFlightPlan() {
         return this.flightPlanner.hasFlightPlan(Fms.DTO_RANDOM_PLAN_INDEX);
     }
     /**
      * Gets the Direct To Random flight plan.
      * @returns The Direct To Random flight plan.
      * @throws Error if the Direct To Random flight plan does not exist.
      */
     getDirectToFlightPlan() {
         return this.flightPlanner.getFlightPlan(Fms.DTO_RANDOM_PLAN_INDEX);
     }
     /**
      * Handles when a flight plan active leg changes.
      * @param legType The type of flight plan active leg change.
      * @param planIndex The index of the plan whose active leg changed.
      */
     onActiveLegChanged(legType, planIndex) {
         if (legType === ActiveLegType.Lateral && planIndex === 0) {
             const activePlan = this.flightPlanner.getActiveFlightPlan();
             if (activePlan.length > 0 && !this.missedApproachActive) {
                 const activeSegment = activePlan.getSegment(activePlan.getSegmentIndex(Math.max(0, activePlan.activeLateralLeg)));
                 if (activeSegment.segmentType === FlightPlanSegmentType.Approach && activePlan.activeLateralLeg - activeSegment.offset > 0) {
                     this.setApproachDetails(undefined, undefined, undefined, true);
                 }
                 else {
                     this.setApproachDetails(undefined, undefined, undefined, false);
                 }
             }
             else {
                 this.setApproachDetails(undefined, undefined, undefined, false);
             }
             if (!this.missedApproachActive
                 && activePlan.activeLateralLeg < activePlan.length - 1
                 && BitFlags.isAll(activePlan.getLeg(activePlan.activeLateralLeg).flags, LegDefinitionFlags.MissedApproach)) {
                 Fms.g1000EvtPub.publishEvent('activate_missed_approach', true);
             }
         }
     }
     /**
      * A method to check the current approach state.
      */
     async checkApproachState() {
         const plan = this.getFlightPlan();
         let approachLoaded = false;
         let approachIsActive = false;
         let approachType;
         let approachRnavType;
         let approachIsCircling = false;
         if (plan.destinationAirport && (plan.procedureDetails.approachIndex > -1 || plan.getUserData('visual_approach') !== undefined)) {
             approachLoaded = true;
             if (plan.length > 0 && plan.activeLateralLeg < plan.length && plan.activeLateralLeg > 0) {
                 const segment = plan.getSegment(plan.getSegmentIndex(plan.activeLateralLeg));
                 approachIsActive = segment.segmentType === FlightPlanSegmentType.Approach;
             }
             if (plan.procedureDetails.approachIndex > -1) {
                 const facility = await this.facLoader.getFacility(FacilityType.Airport, plan.destinationAirport);
                 const approach = facility.approaches[plan.procedureDetails.approachIndex];
                 if (approach) {
                     approachType = approach.approachType;
                     approachRnavType = FmsUtils.getBestRnavType(approach.rnavTypeFlags);
                     approachIsCircling = !approach.runway;
                 }
             }
             else {
                 approachType = AdditionalApproachType.APPROACH_TYPE_VISUAL;
                 approachRnavType = RnavTypeFlags.None;
             }
         }
         this.setApproachDetails(approachLoaded, approachType, approachRnavType, approachIsActive, approachIsCircling);
     }
     /**
      * Removes the direct to existing legs from the primary flight plan. If a direct to existing is currently active,
      * this will effectively cancel it.
      * @param lateralLegIndex The index of the leg to set as the active lateral leg after the removal operation. Defaults
      * to the index of the current active primary flight plan leg.
      */
     removeDirectToExisting(lateralLegIndex) {
         const plan = this.getFlightPlan();
         const directToData = plan.directToData;
         if (directToData && directToData.segmentIndex > -1) {
             plan.removeLeg(directToData.segmentIndex, directToData.segmentLegIndex + 1, true);
             plan.removeLeg(directToData.segmentIndex, directToData.segmentLegIndex + 1, true);
             plan.removeLeg(directToData.segmentIndex, directToData.segmentLegIndex + 1, true);
             const activateIndex = lateralLegIndex !== null && lateralLegIndex !== void 0 ? lateralLegIndex : plan.activeLateralLeg;
             const adjustedActivateIndex = activateIndex - Utils.Clamp(activateIndex - (plan.getSegment(directToData.segmentIndex).offset + directToData.segmentLegIndex), 0, 3);
             plan.setDirectToData(-1, true);
             plan.setLateralLeg(adjustedActivateIndex);
             plan.setCalculatingLeg(adjustedActivateIndex);
             plan.calculate(0);
         }
     }
     /**
      * Checks whether a leg in the primary flight plan can be manually activated.
      * @param segmentIndex The index of the segment in which the leg resides.
      * @param segmentLegIndex The index of the leg in its segment.
      * @returns Whether the leg can be manually activated.
      */
     canActivateLeg(segmentIndex, segmentLegIndex) {
         const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
         if (!plan) {
             return false;
         }
         const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
         if (!leg || BitFlags.isAll(leg.flags, LegDefinitionFlags.DirectTo) || leg === plan.getLeg(0)) {
             return false;
         }
         switch (leg.leg.type) {
             case LegType.CF:
             case LegType.FC:
             case LegType.FD:
                 return true;
             case LegType.CI:
             case LegType.VI:
             case LegType.FA:
             case LegType.CA:
             case LegType.VA:
             case LegType.VM:
                 return false;
         }
         const prevLeg = plan.getPrevLeg(segmentIndex, segmentLegIndex);
         switch (prevLeg.leg.type) {
             case LegType.VA:
             case LegType.CA:
             case LegType.VM:
             case LegType.Discontinuity:
                 return false;
         }
         return true;
     }
     /**
      * Checks whether a leg in the primary flight plan is a valid direct to target.
      * @param segmentIndex The index of the segment in which the leg resides.
      * @param segmentLegIndex The index of the leg in its segment.
      * @returns Whether the leg is a valid direct to target.
      * @throws Error if a leg could not be found at the specified location.
      */
     canDirectTo(segmentIndex, segmentLegIndex) {
         const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
         if (!plan) {
             return false;
         }
         const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
         if (!leg || leg.leg.fixIcao === '' || leg.leg.fixIcao === ICAO.emptyIcao) {
             return false;
         }
         switch (leg.leg.type) {
             case LegType.IF:
             case LegType.TF:
             case LegType.DF:
             case LegType.CF:
             case LegType.AF:
             case LegType.RF:
                 return true;
         }
         return false;
     }
     /**
      * Gets the current Direct To State.
      * @returns the DirectToState.
      */
     getDirectToState() {
         if (this.flightPlanner.activePlanIndex == 1 && this.getFlightPlan(1).getSegment(0).segmentType === FlightPlanSegmentType.RandomDirectTo) {
             return DirectToState.TORANDOM;
         }
         else {
             const plan = this.getFlightPlan();
             const directDataExists = plan.directToData.segmentIndex > -1 && plan.directToData.segmentLegIndex > -1;
             if (directDataExists && plan.segmentCount >= plan.directToData.segmentIndex
                 && plan.getLegIndexFromLeg(plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex]) === plan.activeLateralLeg - 3) {
                 return DirectToState.TOEXISTING;
             }
         }
         return DirectToState.NONE;
     }
     /**
      * Gets the ICAO string of the current Direct To target.
      * @returns The ICAO string of the current Direct To target, or undefined if Direct To is not active.
      */
     getDirectToTargetIcao() {
         var _a;
         return (_a = this.getDirectToLeg()) === null || _a === void 0 ? void 0 : _a.fixIcao;
     }
     /**
      * Gets the current DTO Target Flight Plan Leg.
      * @returns the FlightPlanLeg.
      */
     getDirectToLeg() {
         switch (this.getDirectToState()) {
             case DirectToState.TORANDOM: {
                 const plan = this.getDirectToFlightPlan();
                 return plan.getSegment(0).legs[2].leg;
             }
             case DirectToState.TOEXISTING: {
                 const plan = this.getFlightPlan();
                 return plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET].leg;
             }
         }
         return undefined;
     }
     /**
      * Checks if a segment is the first enroute segment that is not an airway.
      * @param segmentIndex is the segment index of the segment to check
      * @returns whether or not the segment is the first enroute segment that is not an airway.
      */
     isFirstEnrouteSegment(segmentIndex) {
         const plan = this.getFlightPlan();
         for (let i = 0; i < plan.segmentCount; i++) {
             const segment = plan.getSegment(i);
             if (segment.segmentType === FlightPlanSegmentType.Enroute && !segment.airway) {
                 return i === segmentIndex;
             }
         }
         return false;
     }
     /**
      * Adds a user facility.
      * @param userFacility the facility to add.
      */
     addUserFacility(userFacility) {
         this.facRepo.add(userFacility);
     }
     /**
      * Removes a user facility.
      * @param userFacility the facility to remove.
      */
     removeUserFacility(userFacility) {
         this.facRepo.remove(userFacility);
     }
     /**
      * Adds a visual or runway facility from the FlightPlanLeg.
      * @param leg the leg to build the facility from.
      * @param visualRunwayDesignation is the visual runway this facility belongs to.
      */
     addVisualFacilityFromLeg(leg, visualRunwayDesignation) {
         const fac = {
             icao: leg.fixIcao,
             lat: leg.lat !== undefined ? leg.lat : 0,
             lon: leg.lon !== undefined ? leg.lon : 0,
             approach: `VISUAL ${visualRunwayDesignation}`,
             city: '',
             name: `${visualRunwayDesignation} - ${ICAO.getIdent(leg.fixIcao)}`,
             region: '',
             magvar: 0
         };
         this.facRepo.add(fac);
     }
     /**
      * Method to insert a waypoint to the flightplan.
      * @param segmentIndex is index of the segment to add the waypoint to
      * @param facility is the new facility to add a leg to.
      * @param legIndex is the index to insert the waypoint (if none, append)
      * @returns whether the waypoint was successfully inserted.
      */
     insertWaypoint(segmentIndex, facility, legIndex) {
         var _a;
         const leg = FlightPlan.createLeg({
             type: LegType.TF,
             fixIcao: facility.icao
         });
         const plan = this.getFlightPlan();
         const segment = plan.getSegment(segmentIndex);
         const prevLeg = plan.getPrevLeg(segmentIndex, legIndex !== null && legIndex !== void 0 ? legIndex : Infinity);
         const nextLeg = plan.getNextLeg(segmentIndex, legIndex === undefined ? Infinity : legIndex - 1);
         // Make sure we are not inserting a duplicate leg
         if ((prevLeg && this.isDuplicateLeg(prevLeg.leg, leg)) || (nextLeg && this.isDuplicateLeg(leg, nextLeg.leg))) {
             return false;
         }
         // Deal with whether this insert is in an airway segment
         if (segment.airway) {
             //check to see if this insert will leave more than 1 airway leg
             if (!legIndex || segment.legs.length - legIndex < 3) {
                 // we don't need another airway segment,
                 // we just need to add the inserted segment, the remaining airway segments into the next enroute segment
                 const nextSegment = plan.getSegment(segmentIndex + 1);
                 if (nextSegment.airway || nextSegment.segmentType !== FlightPlanSegmentType.Enroute) {
                     //the next segment is an airway, arrival, approach or destination, so we need to add an enroute segment
                     this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex + 1);
                 }
                 //now we can add the new leg into the next enroute segment
                 this.planAddLeg(segmentIndex + 1, leg);
                 //get the legs after the insert index from the first airway segment, if any, and move them to the second airway segment
                 legIndex = legIndex ? legIndex : segment.legs.length - 1;
                 const legsToMove = [];
                 const legsLength = segment.legs.length;
                 for (let i = legIndex; i < legsLength; i++) {
                     legsToMove.push(segment.legs[i].leg);
                 }
                 for (let j = legsLength - 1; j >= legIndex; j--) {
                     this.planRemoveLeg(segmentIndex, j, true, true);
                 }
                 for (let k = 0; k < legsToMove.length; k++) {
                     this.planAddLeg(segmentIndex + 1, legsToMove[k]);
                 }
             }
             else {
                 //we need to create a new airway segment
                 //split the segment into three
                 this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex + 1);
                 this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex + 1);
                 const newAirwaySegment = plan.getSegment(segmentIndex + 2);
                 //add the leg to the new enroute segment (between the old and new airway segments)
                 this.planAddLeg(segmentIndex + 1, leg);
                 //get the legs after the insert index from the first airway segment, if any, and move them to the second airway segment
                 legIndex = legIndex ? legIndex : segment.legs.length - 1;
                 const legsToMove = [];
                 const legsLength = segment.legs.length;
                 for (let i = legIndex; i < legsLength; i++) {
                     legsToMove.push(segment.legs[i].leg);
                 }
                 for (let j = legsLength - 1; j >= legIndex; j--) {
                     this.planRemoveLeg(segmentIndex, j, true, true);
                 }
                 this.planAddLeg(segmentIndex + 1, legsToMove[0]);
                 for (let k = 1; k < legsToMove.length; k++) {
                     this.planAddLeg(segmentIndex + 2, legsToMove[k]);
                 }
                 const airway = (_a = segment.airway) === null || _a === void 0 ? void 0 : _a.split('.');
                 segment.airway = airway && airway[0] ? airway[0] + '.' + segment.legs[legIndex - 1].name : segment.airway;
                 plan.setAirway(segmentIndex, segment.airway);
                 newAirwaySegment.airway = airway && airway[0] ? airway[0] + '.' + newAirwaySegment.legs[newAirwaySegment.legs.length - 1].name : segment.airway;
                 plan.setAirway(segmentIndex + 2, newAirwaySegment.airway);
             }
             return true;
         }
         this.planAddLeg(segmentIndex, leg, legIndex);
         return true;
     }
     /**
      * Removes a leg to a waypoint from the primary flight plan.
      * @param segmentIndex The index of the segment containing the leg to remove.
      * @param segmentLegIndex The index of the leg to remove in its segment.
      * @returns Whether the waypoint was successfully removed.
      */
     removeWaypoint(segmentIndex, segmentLegIndex) {
         const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
         if (!plan) {
             return false;
         }
         const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
         if (!leg || BitFlags.isAny(leg.leg.fixTypeFlags, FixTypeFlags.FAF | FixTypeFlags.MAP)) {
             return false;
         }
         const legDeleted = this.planRemoveLeg(segmentIndex, segmentLegIndex);
         const nextLeg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
         if (legDeleted && nextLeg && (nextLeg.leg.type === LegType.HA || nextLeg.leg.type === LegType.HM || nextLeg.leg.type === LegType.HF)) {
             if (plan.tryGetLeg(segmentIndex, segmentLegIndex)) {
                 this.planRemoveLeg(segmentIndex, segmentLegIndex, true, true, true);
             }
         }
         return legDeleted;
     }
     /**
      * Gets the airway leg type of a flight plan leg.
      * @param plan The flight plan containing the query leg.
      * @param segmentIndex The index of the flight plan segment containing the query leg.
      * @param segmentLegIndex The index of the query leg in its segment.
      * @returns The airway leg type of the query leg.
      */
     getAirwayLegType(plan, segmentIndex, segmentLegIndex) {
         const segment = plan.getSegment(segmentIndex);
         const segmentIsAirway = segment.airway !== undefined;
         const nextSegmentIsAirway = segmentIndex + 1 < plan.segmentCount && plan.getSegment(segmentIndex + 1).airway !== undefined;
         const legIsLast = segmentLegIndex == segment.legs.length - 1;
         if ((segmentIsAirway && legIsLast && nextSegmentIsAirway)) {
             return AirwayLegType.EXIT_ENTRY;
         }
         if ((legIsLast && nextSegmentIsAirway)) {
             return AirwayLegType.ENTRY;
         }
         if (segmentIsAirway) {
             if (legIsLast) {
                 return AirwayLegType.EXIT;
             }
             return AirwayLegType.ONROUTE;
         }
         return AirwayLegType.NONE;
     }
     /**
      * Method to get the distance of an airway segment.
      * @param segmentIndex is the index of the segment of the airway.
      * @returns the cumulative distance for the airway segment.
      */
     getAirwayDistance(segmentIndex) {
         var _a, _b, _c, _d;
         const plan = this.getFlightPlan();
         const segment = plan.getSegment(segmentIndex);
         const entrySegment = plan.getSegment(segmentIndex - 1);
         const entryCumulativeDistance = (_b = (_a = entrySegment.legs[entrySegment.legs.length - 1]) === null || _a === void 0 ? void 0 : _a.calculated) === null || _b === void 0 ? void 0 : _b.cumulativeDistance;
         const exitCumulativeDistance = (_d = (_c = segment.legs[segment.legs.length - 1]) === null || _c === void 0 ? void 0 : _c.calculated) === null || _d === void 0 ? void 0 : _d.cumulativeDistance;
         return exitCumulativeDistance && entryCumulativeDistance ? exitCumulativeDistance - entryCumulativeDistance : -1;
     }
     /**
      * Method to add a new origin airport and runway to the flight plan.
      * @param airport is the facility of the origin airport.
      * @param runway is the onewayrunway
      */
     setOrigin(airport, runway) {
         const plan = this.getFlightPlan();
         const segmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Departure);
         if (airport) {
             plan.setOriginAirport(airport.icao);
             plan.setOriginRunway(runway);
             this.planClearSegment(segmentIndex, FlightPlanSegmentType.Departure);
             this.planAddOriginDestinationLeg(true, segmentIndex, airport, runway);
             const prevLeg = plan.getPrevLeg(segmentIndex, 1);
             const nextLeg = plan.getNextLeg(segmentIndex, 0);
             if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
                 this.planRemoveDuplicateLeg(prevLeg, nextLeg);
             }
         }
         else {
             plan.removeOriginAirport();
             this.setApproachDetails(false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, false);
             this.planClearSegment(segmentIndex, FlightPlanSegmentType.Departure);
         }
         plan.calculate(0);
     }
     /**
      * Method to add a new destination airport and runway to the flight plan.
      * @param airport is the facility of the destination airport.
      * @param runway is the selected runway at the destination facility.
      */
     setDestination(airport, runway) {
         const plan = this.getFlightPlan();
         const destSegmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Destination);
         if (airport) {
             plan.setDestinationAirport(airport.icao);
             plan.setDestinationRunway(runway);
             this.planClearSegment(destSegmentIndex, FlightPlanSegmentType.Destination);
             const hasArrival = plan.procedureDetails.arrivalIndex > -1;
             const hasApproach = plan.procedureDetails.approachIndex > -1;
             if (!hasArrival && !hasApproach) {
                 this.planAddOriginDestinationLeg(false, destSegmentIndex, airport, runway);
             }
         }
         else {
             plan.removeDestinationAirport();
             this.planClearSegment(destSegmentIndex, FlightPlanSegmentType.Destination);
         }
         plan.calculate(0);
     }
     /**
      * Method to remove runway or airport legs from segments where they shouldn't exist.
      */
     removeDestLegFromSegments() {
         const plan = this.getFlightPlan();
         const destination = plan.destinationAirport;
         const hasArrival = plan.procedureDetails.arrivalIndex > -1;
         const hasApproach = plan.procedureDetails.approachIndex > -1 || plan.getUserData('visual_approach');
         const destinationSegmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Destination);
         const destinationSegment = plan.getSegment(destinationSegmentIndex);
         if (hasApproach && destination) {
             if (hasArrival) {
                 const arrivalSegmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Arrival);
                 const arrival = plan.getSegment(arrivalSegmentIndex);
                 const lastArrivalLegIcao = arrival.legs[arrival.legs.length - 1].leg.fixIcao;
                 if (lastArrivalLegIcao === destination || lastArrivalLegIcao.search('R') === 0) {
                     this.planRemoveLeg(arrivalSegmentIndex, arrival.legs.length - 1);
                 }
             }
             if (destinationSegment.legs.length > 0) {
                 this.planClearSegment(destinationSegmentIndex, FlightPlanSegmentType.Destination);
             }
         }
         else if (hasArrival && destination) {
             if (destinationSegment.legs.length > 0) {
                 this.planClearSegment(destinationSegmentIndex, FlightPlanSegmentType.Destination);
             }
         }
     }
     /**
      * Method to ensure only one segment of a specific type exists in the flight plan and optionally insert it if needed.
      * @param segmentType is the segment type we want to evaluate.
      * @param insert is whether to insert the segment if missing
      * @returns segmentIndex of the only segment of this type in the flight plan.
      */
     ensureOnlyOneSegmentOfType(segmentType, insert = true) {
         const plan = this.getFlightPlan();
         let segmentIndex;
         const selectedSegments = plan.segmentsOfType(segmentType);
         const segmentIndexArray = [];
         for (const element of selectedSegments) {
             segmentIndexArray.push(element.segmentIndex);
         }
         if (segmentIndexArray.length === 0) {
             if (insert) {
                 segmentIndex = this.planInsertSegmentOfType(segmentType);
             }
             else {
                 segmentIndex = -1;
             }
         }
         else if (segmentIndexArray.length > 1) {
             for (let i = 0; i < segmentIndexArray.length; i++) {
                 this.planRemoveSegment(segmentIndexArray[i]);
             }
             segmentIndex = this.planInsertSegmentOfType(segmentType);
         }
         else {
             segmentIndex = segmentIndexArray[0];
         }
         return segmentIndex;
     }
     /**
      * Method to invert the flightplan.
      */
     invertFlightplan() {
         var _a;
         const plan = this.getFlightPlan();
         const activeLegIcao = plan.getLeg(plan.activeLateralLeg).leg.fixIcao;
         if (plan.directToData.segmentIndex >= 0 && plan.directToData.segmentLegIndex >= 0) {
             this.removeDirectToExisting();
         }
         if (!Simplane.getIsGrounded() && activeLegIcao) {
             this.createDirectToRandom(activeLegIcao);
         }
         const newOriginIcao = plan.destinationAirport;
         const newDestinationIcao = plan.originAirport;
         const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
         if (lastEnrouteSegmentIndex === 1 && plan.getSegment(1).legs.length > 0) {
             //case for when there is only 1 enroute segment and it has at least 1 waypoint, a simple reversal is all that's required.
             const segment = Object.assign({}, plan.getSegment(1));
             this.emptyPrimaryFlightPlan();
             for (let l = segment.legs.length - 1; l >= 0; l--) {
                 plan.addLeg(1, segment.legs[l].leg);
             }
         }
         else if (lastEnrouteSegmentIndex > 1) {
             //case for when there is more than 1 enroute segment we know we have to deal with airways
             const legs = [];
             for (let i = 1; i <= lastEnrouteSegmentIndex; i++) {
                 //create a temporary list of legs that looks like what a flight plan import looks like with ICAO and the airway
                 //we fly FROM the leg on.
                 const oldSegment = plan.getSegment(i);
                 const airway = oldSegment.airway ? (_a = oldSegment.airway) === null || _a === void 0 ? void 0 : _a.split('.')[0] : undefined;
                 for (const leg of oldSegment.legs) {
                     const legListItem = { icao: leg.leg.fixIcao, airway: airway };
                     legs.push(legListItem);
                 }
             }
             //after the array of legs is complete, we just reverse it
             legs.reverse();
             this.emptyPrimaryFlightPlan();
             let currentSegment = 1;
             let lastLegWasAirway = false;
             //last we go through each leg and use the same logic we use for the flight plan import to go through each leg and create airway
             //segments as appropriate for these legs.
             for (let i = 0; i < legs.length; i++) {
                 const wpt = legs[i];
                 const segment = plan.getSegment(currentSegment);
                 if (wpt.airway) {
                     const leg = FlightPlan.createLeg({
                         type: LegType.TF,
                         fixIcao: wpt.icao
                     });
                     plan.addLeg(currentSegment, leg);
                     if (!lastLegWasAirway) {
                         plan.insertSegment(currentSegment + 1, FlightPlanSegmentType.Enroute, wpt.airway);
                         currentSegment += 1;
                     }
                     for (let j = i + 1; j < legs.length; j++) {
                         i++;
                         const airwayLeg = FlightPlan.createLeg({
                             type: LegType.TF,
                             fixIcao: legs[j].icao
                         });
                         plan.addLeg(currentSegment, airwayLeg);
                         if (legs[j].airway !== wpt.airway) {
                             lastLegWasAirway = legs[j].airway ? true : false;
                             break;
                         }
                     }
                     plan.setAirway(currentSegment, wpt.airway + '.' + ICAO.getIdent(legs[i].icao));
                     currentSegment += 1;
                     plan.insertSegment(currentSegment, FlightPlanSegmentType.Enroute, lastLegWasAirway ? legs[i].airway : undefined);
                 }
                 else {
                     let leg = undefined;
                     leg = FlightPlan.createLeg({
                         type: LegType.TF,
                         fixIcao: wpt.icao
                     });
                     if (leg) {
                         plan.addLeg(currentSegment, leg);
                         if (lastLegWasAirway) {
                             plan.setAirway(currentSegment, segment.airway + '.' + ICAO.getIdent(wpt.icao));
                             currentSegment += 1;
                             plan.insertSegment(currentSegment, FlightPlanSegmentType.Enroute);
                         }
                         lastLegWasAirway = false;
                     }
                 }
             }
             if (plan.getSegment(currentSegment).airway) {
                 currentSegment += 1;
                 plan.insertSegment(currentSegment, FlightPlanSegmentType.Enroute);
             }
         }
         else {
             this.emptyPrimaryFlightPlan();
         }
         if (newOriginIcao) {
             this.facLoader.getFacility(FacilityType.Airport, newOriginIcao).then((facility) => {
                 this.setOrigin(facility);
             });
         }
         if (newDestinationIcao) {
             this.facLoader.getFacility(FacilityType.Airport, newDestinationIcao).then((facility) => {
                 this.setDestination(facility);
             });
         }
         this.setApproachDetails(false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, false);
         plan.calculate(0);
     }
     /**
      * Method to add or replace a departure procedure in the flight plan.
      * @param facility is the facility that contains the procedure to add.
      * @param departureIndex is the index of the departure
      * @param departureRunwayIndex is the index of the runway transition
      * @param enrouteTransitionIndex is the index of the enroute transition
      * @param oneWayRunway is the one way runway to set as the origin leg.
      */
     insertDeparture(facility, departureIndex, departureRunwayIndex, enrouteTransitionIndex, oneWayRunway) {
         const plan = this.getFlightPlan();
         plan.setDeparture(facility.icao, departureIndex, enrouteTransitionIndex, departureRunwayIndex);
         const segmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Departure);
         this.planClearSegment(segmentIndex, FlightPlanSegmentType.Departure);
         const insertProcedureObject = this.buildDepartureLegs(facility, departureIndex, enrouteTransitionIndex, departureRunwayIndex, oneWayRunway);
         if (oneWayRunway) {
             plan.setOriginAirport(facility.icao);
             plan.setOriginRunway(oneWayRunway);
         }
         else if (plan.originAirport == facility.icao && plan.procedureDetails.originRunway) {
             const originLeg = FmsUtils.buildRunwayLeg(facility, plan.procedureDetails.originRunway, true);
             insertProcedureObject.procedureLegs.splice(0, 1, originLeg);
         }
         else {
             plan.setOriginAirport(facility.icao);
         }
         insertProcedureObject.procedureLegs.forEach(l => this.planAddLeg(segmentIndex, l));
         const nextLeg = plan.getNextLeg(segmentIndex, Infinity);
         const depSegment = plan.getSegment(segmentIndex);
         const lastDepLeg = depSegment.legs[depSegment.legs.length - 1];
         if (nextLeg && lastDepLeg && this.isDuplicateLeg(lastDepLeg.leg, nextLeg.leg)) {
             this.planRemoveDuplicateLeg(lastDepLeg, nextLeg);
         }
         this.setVerticalData(plan, segmentIndex);
         plan.calculate(0);
     }
     /**
      * Method to insert the arrival legs.
      * @param facility is the facility to build legs from.
      * @param procedureIndex is the procedure index to build legs from.
      * @param enrouteTransitionIndex is the enroute transition index to build legs from.
      * @param runwayTransitionIndex is the runway transition index to build legs from.
      * @param oneWayRunway is the one way runway, if one is specified in the procedure.
      * @returns InsertProcedureObject to insert into the flight plan.
      */
     buildDepartureLegs(facility, procedureIndex, enrouteTransitionIndex, runwayTransitionIndex, oneWayRunway) {
         const departure = facility.departures[procedureIndex];
         const enRouteTransition = departure.enRouteTransitions[enrouteTransitionIndex];
         const runwayTransition = departure.runwayTransitions[runwayTransitionIndex];
         const insertProcedureObject = { procedureLegs: [] };
         let originLeg;
         if (oneWayRunway) {
             originLeg = FmsUtils.buildRunwayLeg(facility, oneWayRunway, true);
         }
         else {
             originLeg = FlightPlan.createLeg({
                 lat: facility.lat,
                 lon: facility.lon,
                 type: LegType.IF,
                 fixIcao: facility.icao
             });
         }
         insertProcedureObject.procedureLegs.push(originLeg);
         if (runwayTransition !== undefined && runwayTransition.legs.length > 0) {
             runwayTransition.legs.forEach((leg) => {
                 insertProcedureObject.procedureLegs.push(FlightPlan.createLeg(leg));
             });
         }
         for (let i = 0; i < departure.commonLegs.length; i++) {
             const leg = FlightPlan.createLeg(departure.commonLegs[i]);
             if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                 this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                 insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                     this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                 continue;
             }
             insertProcedureObject.procedureLegs.push(leg);
         }
         if (enRouteTransition) {
             for (let i = 0; i < enRouteTransition.legs.length; i++) {
                 const leg = FlightPlan.createLeg(enRouteTransition.legs[i]);
                 if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                     this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                     insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                         this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                     continue;
                 }
                 insertProcedureObject.procedureLegs.push(enRouteTransition.legs[i]);
             }
         }
         return insertProcedureObject;
     }
     /**
      * Method to add or replace an arrival procedure in the flight plan.
      * @param facility is the facility that contains the procedure to add.
      * @param arrivalIndex is the index of the arrival procedure.
      * @param arrivalRunwayTransitionIndex is the index of the arrival runway transition.
      * @param enrouteTransitionIndex is the index of the enroute transition.
      * @param oneWayRunway is the one way runway to set as the destination leg.
      */
     insertArrival(facility, arrivalIndex, arrivalRunwayTransitionIndex, enrouteTransitionIndex, oneWayRunway) {
         const plan = this.getFlightPlan();
         plan.setArrival(facility.icao, arrivalIndex, enrouteTransitionIndex, arrivalRunwayTransitionIndex);
         if (plan.length > 0 && plan.procedureDetails.approachIndex < 0 && plan.destinationAirport) {
             if (!this.moveDirectToDestinationLeg(plan, FlightPlanSegmentType.Enroute)) {
                 if (plan.getLeg(plan.activeLateralLeg).leg.fixIcao === plan.destinationAirport && plan.destinationAirport !== facility.icao && plan.activeLateralLeg === plan.length - 1) {
                     const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
                     const newDestinationLeg = FlightPlan.createLeg({ fixIcao: plan.destinationAirport, type: LegType.TF });
                     this.planAddLeg(lastEnrouteSegmentIndex, newDestinationLeg);
                 }
             }
         }
         if (plan.procedureDetails.approachIndex < 0) {
             plan.setDestinationAirport(facility.icao);
             plan.setDestinationRunway(oneWayRunway);
         }
         const segmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Arrival);
         if (plan.getSegment(segmentIndex).legs.length > 0) {
             this.planClearSegment(segmentIndex, FlightPlanSegmentType.Arrival);
         }
         const insertProcedureObject = this.buildArrivalLegs(facility, arrivalIndex, enrouteTransitionIndex, arrivalRunwayTransitionIndex, oneWayRunway);
         let directTargetLeg;
         let handleDirectToDestination = false;
         const directToState = this.getDirectToState();
         if (plan.procedureDetails.approachIndex > -1) {
             insertProcedureObject.procedureLegs.pop();
         }
         else if (directToState === DirectToState.TOEXISTING) {
             directTargetLeg = this.getDirectToLeg();
             if ((directTargetLeg === null || directTargetLeg === void 0 ? void 0 : directTargetLeg.fixIcao) === plan.destinationAirport &&
                 (directTargetLeg === null || directTargetLeg === void 0 ? void 0 : directTargetLeg.fixIcao) === insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1].fixIcao) {
                 insertProcedureObject.procedureLegs.pop();
                 handleDirectToDestination = true;
             }
         }
         insertProcedureObject.procedureLegs.forEach(l => this.planAddLeg(segmentIndex, l));
         const arrSegment = plan.getSegment(segmentIndex);
         const prevLeg = plan.getPrevLeg(segmentIndex, 0);
         const firstArrLeg = arrSegment.legs[0];
         if (prevLeg && firstArrLeg && this.isDuplicateLeg(prevLeg.leg, firstArrLeg.leg)) {
             this.planRemoveDuplicateLeg(prevLeg, firstArrLeg);
         }
         this.removeDestLegFromSegments();
         const nextLeg = plan.getNextLeg(segmentIndex, Infinity);
         const lastArrLeg = arrSegment.legs[arrSegment.legs.length - 1];
         if (nextLeg && lastArrLeg && this.isDuplicateLeg(lastArrLeg.leg, nextLeg.leg)) {
             this.planRemoveDuplicateLeg(lastArrLeg, nextLeg);
         }
         if (handleDirectToDestination) {
             this.moveDirectToDestinationLeg(plan, FlightPlanSegmentType.Arrival, segmentIndex);
             this.activateLeg(segmentIndex, arrSegment.legs.length - 1);
         }
         else if (directToState === DirectToState.TOEXISTING && directTargetLeg && directTargetLeg.fixIcao === plan.destinationAirport) {
             this.removeDirectToExisting();
             this.createDirectToRandom(plan.destinationAirport);
         }
         this.setVerticalData(plan, segmentIndex);
         plan.calculate(0);
     }
     /**
      * Method to insert the arrival legs.
      * @param facility is the facility to build legs from.
      * @param procedureIndex is the procedure index to build legs from.
      * @param enrouteTransitionIndex is the enroute transition index to build legs from.
      * @param runwayTransitionIndex is the runway transition index to build legs from.
      * @param oneWayRunway is the one way runway, if one is specified in the procedure.
      * @returns InsertProcedureObject to insert into the flight plan.
      */
     buildArrivalLegs(facility, procedureIndex, enrouteTransitionIndex, runwayTransitionIndex, oneWayRunway) {
         const arrival = facility.arrivals[procedureIndex];
         const enRouteTransition = arrival.enRouteTransitions[enrouteTransitionIndex];
         const runwayTransition = arrival.runwayTransitions[runwayTransitionIndex];
         const insertProcedureObject = { procedureLegs: [] };
         if (enRouteTransition !== undefined && enRouteTransition.legs.length > 0) {
             enRouteTransition.legs.forEach((leg) => {
                 insertProcedureObject.procedureLegs.push(FlightPlan.createLeg(leg));
             });
         }
         for (let i = 0; i < arrival.commonLegs.length; i++) {
             const leg = FlightPlan.createLeg(arrival.commonLegs[i]);
             if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                 this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                 insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                     this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                 continue;
             }
             insertProcedureObject.procedureLegs.push(leg);
         }
         if (runwayTransition) {
             for (let i = 0; i < runwayTransition.legs.length; i++) {
                 const leg = FlightPlan.createLeg(runwayTransition.legs[i]);
                 if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                     this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                     insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                         this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                     continue;
                 }
                 insertProcedureObject.procedureLegs.push(leg);
             }
         }
         const destinationLeg = oneWayRunway
             ? FmsUtils.buildRunwayLeg(facility, oneWayRunway, false)
             : FlightPlan.createLeg({
                 lat: facility.lat,
                 lon: facility.lon,
                 type: LegType.TF,
                 fixIcao: facility.icao
             });
         insertProcedureObject.procedureLegs.push(destinationLeg);
         this.tryInsertIFLeg(insertProcedureObject);
         return insertProcedureObject;
     }
     /**
      * Method to move a direct to destination to a specified target segment.
      * @param plan is the primary flight plan.
      * @param targetSegmentType is the target segment type.
      * @param arrivalSegmentIndex is the arrival segment index
      * @returns whether a direct to destination was moved.
      */
     moveDirectToDestinationLeg(plan, targetSegmentType, arrivalSegmentIndex) {
         if (this.getDirectToState() === DirectToState.TOEXISTING) {
             const directTargetSegmentIndex = targetSegmentType === FlightPlanSegmentType.Arrival ? arrivalSegmentIndex : this.findLastEnrouteSegmentIndex(plan);
             if (directTargetSegmentIndex !== undefined && directTargetSegmentIndex > 0 && plan.getLeg(plan.activeLateralLeg).leg.fixIcao === plan.destinationAirport) {
                 const destinationLeg = Object.assign({}, plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex].leg);
                 const directTargetLeg = Object.assign({}, plan.getLeg(plan.activeLateralLeg).leg);
                 const directOriginLeg = Object.assign({}, plan.getLeg(plan.activeLateralLeg - 1).leg);
                 const discoLeg = Object.assign({}, plan.getLeg(plan.activeLateralLeg - 2).leg);
                 const newDirectLegIndex = plan.getSegment(directTargetSegmentIndex).legs.length;
                 plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                 plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                 plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                 plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                 plan.setDirectToData(directTargetSegmentIndex, newDirectLegIndex);
                 plan.addLeg(directTargetSegmentIndex, destinationLeg);
                 plan.addLeg(directTargetSegmentIndex, discoLeg, undefined, LegDefinitionFlags.DirectTo);
                 plan.addLeg(directTargetSegmentIndex, directOriginLeg, undefined, LegDefinitionFlags.DirectTo);
                 const newActiveLeg = plan.addLeg(directTargetSegmentIndex, directTargetLeg, undefined, LegDefinitionFlags.DirectTo);
                 plan.setLateralLeg(plan.getLegIndexFromLeg(newActiveLeg));
                 plan.setCalculatingLeg(plan.activeLateralLeg);
                 plan.calculate(plan.activeLateralLeg);
                 return true;
             }
         }
         return false;
     }
     /**
      * Method to find the last enroute segment of the supplied flight plan.
      * @param plan is the flight plan to find the last enroute segment in.
      * @returns a segment index.
      */
     findLastEnrouteSegmentIndex(plan) {
         let enrouteSegmentFound = 0;
         for (let i = 1; i < plan.segmentCount; i++) {
             const segment = plan.getSegment(i);
             if (segment.segmentType === FlightPlanSegmentType.Enroute) {
                 enrouteSegmentFound = i;
             }
         }
         return enrouteSegmentFound;
     }
     /**
      * Method manage the destination leg in the last enroute segment.
      * @param plan is the flight plan.
      * @param currentDestination is the currently set destination airport icao.
      */
     manageAirportLeg(plan, currentDestination) {
         if (plan.procedureDetails.arrivalIndex > -1 || !currentDestination || Simplane.getIsGrounded()) {
             //if we don't have a destination set, or an arrival is selected, don't add the airport to enroute
             return;
         }
         const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
         const segment = plan.getSegment(lastEnrouteSegmentIndex);
         const lastLegIndex = segment.legs.length - 1;
         if (currentDestination && (lastLegIndex < 0 || segment.legs[lastLegIndex].leg.fixIcao !== currentDestination)) {
             //if a destination is set, AND either (a) the last enroute segment is empty OR (b) the last enroute segment isn't empty and
             //the last leg of the last enroute segment is not already the current destination
             this.planAddLeg(lastEnrouteSegmentIndex, FlightPlan.createLeg({ fixIcao: currentDestination, type: LegType.TF }));
         }
     }
     /**
      * Method to check whether an approach can load, or only activate.
      * @returns true if the approach can be loaded and not activated, otherwise the approach can only be immediatly activated.
      */
     canApproachLoad() {
         const plan = this.getFlightPlan();
         if (plan.length > 0) {
             const activeSegment = plan.getSegment(plan.getSegmentIndex(plan.activeLateralLeg));
             if (activeSegment.segmentType !== FlightPlanSegmentType.Approach && plan.length > 1) {
                 return true;
             }
         }
         return false;
     }
     /**
      * Method to add or replace an approach procedure in the flight plan.
      * @param facility is the facility that contains the procedure to add.
      * @param approachIndex is the index of the approach procedure.
      * @param approachTransitionIndex is the index of the approach transition.
      * @param visualRunwayNumber is the visual runway number, if any.
      * @param visualRunwayDesignator is the visual runway designator, if any.
      * @param transStartIndex is the starting leg index for the transition, if any.
      * @param skipCourseReversal Whether to skip the course reversal.
      * @returns A Promise which is fulfilled with whether the approach was inserted.
      */
     async insertApproach(facility, approachIndex, approachTransitionIndex, visualRunwayNumber, visualRunwayDesignator, transStartIndex, skipCourseReversal) {
         var _a, _b;
         const plan = this.getFlightPlan();
         let visualRunway;
         if (visualRunwayNumber !== undefined && visualRunwayDesignator !== undefined) {
             visualRunway = RunwayUtils.matchOneWayRunway(facility, visualRunwayNumber, visualRunwayDesignator);
             if (!visualRunway) {
                 return false;
             }
         }
         const opId = ++this.insertApproachOpId;
         const insertProcedureObject = await this.buildApproachLegs(facility, approachIndex, approachTransitionIndex, visualRunway, transStartIndex, skipCourseReversal);
         if (visualRunway) {
             plan.setUserData('visual_approach', `${visualRunway.designation}`);
         }
         else if (plan.getUserData('visual_approach')) {
             plan.deleteUserData('visual_approach');
         }
         plan.setApproach(facility.icao, approachIndex, approachTransitionIndex);
         if (plan.procedureDetails.arrivalIndex < 0) {
             if (!this.moveDirectToDestinationLeg(plan, FlightPlanSegmentType.Enroute)) {
                 this.manageAirportLeg(plan, plan.destinationAirport);
             }
         }
         plan.setDestinationAirport(facility.icao);
         const segmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Approach);
         if (plan.getSegment(segmentIndex).legs.length > 0) {
             this.planClearSegment(segmentIndex, FlightPlanSegmentType.Approach);
         }
         if (opId !== this.insertApproachOpId) {
             return false;
         }
         if (insertProcedureObject.runway) {
             plan.setDestinationRunway(insertProcedureObject.runway);
         }
         let haveAddedMap = false;
         insertProcedureObject.procedureLegs.forEach((l) => {
             var _a;
             let isMissedLeg = false;
             if (visualRunway !== undefined) {
                 this.addVisualFacilityFromLeg(l, visualRunway.designation);
                 if (haveAddedMap) {
                     isMissedLeg = true;
                 }
                 if (l.fixTypeFlags & FixTypeFlags.MAP) {
                     haveAddedMap = true;
                 }
             }
             let flags = (_a = l.legDefinitionFlags) !== null && _a !== void 0 ? _a : LegDefinitionFlags.None;
             if (isMissedLeg) {
                 flags |= LegDefinitionFlags.MissedApproach;
             }
             this.planAddLeg(segmentIndex, l, undefined, flags);
         });
         let skipDestinationLegCheck = false;
         if (this.getDirectToState() === DirectToState.TOEXISTING) {
             if (((_a = this.getDirectToLeg()) === null || _a === void 0 ? void 0 : _a.fixIcao) === plan.destinationAirport) {
                 skipDestinationLegCheck = true;
             }
         }
         if (!skipDestinationLegCheck) {
             this.removeDestLegFromSegments();
         }
         const prevLeg = plan.getPrevLeg(segmentIndex, 0);
         const firstAppLeg = plan.getSegment(segmentIndex).legs[0];
         if (prevLeg && firstAppLeg && this.isDuplicateLeg(prevLeg.leg, firstAppLeg.leg)) {
             this.planRemoveDuplicateLeg(prevLeg, firstAppLeg);
         }
         // Adds missed approach legs
         if (!visualRunway && insertProcedureObject.procedureLegs.length > 0) {
             const missedLegs = (_b = facility.approaches[approachIndex].missedLegs) !== null && _b !== void 0 ? _b : [];
             if (missedLegs.length > 0) {
                 let maphIndex = -1;
                 for (let m = missedLegs.length - 1; m >= 0; m--) {
                     switch (missedLegs[m].type) {
                         case LegType.HA:
                         case LegType.HF:
                         case LegType.HM:
                             maphIndex = m - 1;
                             break;
                     }
                 }
                 for (let n = 0; n < missedLegs.length; n++) {
                     const newLeg = FlightPlan.createLeg(missedLegs[n]);
                     if (maphIndex >= 0 && n === maphIndex) {
                         newLeg.fixTypeFlags |= FixTypeFlags.MAHP;
                         this.planAddLeg(segmentIndex, newLeg, undefined, LegDefinitionFlags.MissedApproach);
                     }
                     else {
                         this.planAddLeg(segmentIndex, newLeg, undefined, LegDefinitionFlags.MissedApproach);
                     }
                 }
             }
         }
         const approachType = visualRunway ? AdditionalApproachType.APPROACH_TYPE_VISUAL : facility.approaches[approachIndex].approachType;
         const rnavTypeFlag = visualRunway ? RnavTypeFlags.None : FmsUtils.getBestRnavType(facility.approaches[approachIndex].rnavTypeFlags);
         const approachIsCircling = !visualRunway && !facility.approaches[approachIndex].runway ? true : false;
         this.setApproachDetails(true, approachType, rnavTypeFlag, false, approachIsCircling);
         this.setVerticalData(plan, segmentIndex);
         plan.calculate();
         this.loadApproachFrequency(facility, approachIndex);
         this.setLocFrequency(1);
         this.setLocFrequency(2);
         return true;
     }
     /**
      * Method to insert the approach legs.
      * @param facility The facility to build legs from.
      * @param approachIndex The approach procedure index to build legs from.
      * @param approachTransitionIndex The transition index to build legs from.
      * @param visualRunway If this is a visual approach, the visual approach one way runway object.
      * @param transStartIndex The starting leg index for the transition, if any.
      * @param skipCourseReversal Whether to skip the course reversal.
      * @returns A Promise which is fulfilled with an `InsertProcedureObject` containing the flight plan legs to insert
      * into the flight plan.
      */
     async buildApproachLegs(facility, approachIndex, approachTransitionIndex, visualRunway, transStartIndex, skipCourseReversal) {
         const isVisual = !!visualRunway;
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const approach = isVisual ? FmsUtils.buildVisualApproach(facility, visualRunway, 1, 2.5) : facility.approaches[approachIndex];
         const transition = approach.transitions[approachTransitionIndex];
         const isVtf = approachTransitionIndex < 0;
         const insertProcedureObject = { procedureLegs: [] };
         if (transition !== undefined && transition.legs.length > 0) {
             const startIndex = transStartIndex !== undefined ? transStartIndex : 0;
             for (let t = startIndex; t < transition.legs.length; t++) {
                 insertProcedureObject.procedureLegs.push(FlightPlan.createLeg(transition.legs[t]));
             }
         }
         const lastTransitionLeg = insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1];
         if (isVtf) {
             insertProcedureObject.procedureLegs.push(FlightPlan.createLeg({ type: LegType.Discontinuity }));
         }
         const finalLegs = approach.finalLegs;
         for (let i = 0; i < finalLegs.length; i++) {
             const leg = FlightPlan.createLeg(finalLegs[i]);
             if (i === 0 && lastTransitionLeg && this.isDuplicateIFLeg(lastTransitionLeg, leg)) {
                 insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] = this.mergeDuplicateLegData(lastTransitionLeg, leg);
                 continue;
             }
             if (!isVisual && leg.fixIcao[0] === 'R') {
                 const approachRunway = RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator);
                 if (approachRunway) {
                     insertProcedureObject.runway = approachRunway;
                     const runwayLeg = FmsUtils.buildRunwayLeg(facility, approachRunway, false);
                     insertProcedureObject.procedureLegs.push(runwayLeg);
                 }
             }
             else if (isVisual && i === finalLegs.length - 1) {
                 insertProcedureObject.runway = visualRunway;
                 insertProcedureObject.procedureLegs.push(leg);
                 if (approach.missedLegs.length > 0) {
                     insertProcedureObject.procedureLegs.push(approach.missedLegs[0]);
                 }
             }
             else {
                 insertProcedureObject.procedureLegs.push(leg);
                 if (isVtf && BitFlags.isAll(leg.fixTypeFlags, FixTypeFlags.FAF)) {
                     await this.insertVtfLegs(insertProcedureObject, leg, finalLegs[i - 1], finalLegs[i + 1]);
                 }
             }
         }
         if (!isVisual) {
             this.tryInsertIFLeg(insertProcedureObject);
             this.tryReconcileIAFLeg(insertProcedureObject);
             this.manageFafAltitudeRestriction(insertProcedureObject);
             this.tryCleanupHold(insertProcedureObject);
             if (skipCourseReversal) {
                 this.tryRemoveCourseReversal(insertProcedureObject);
             }
             this.tryInsertMap(insertProcedureObject);
             if (!insertProcedureObject.runway && approach.runway) {
                 insertProcedureObject.runway = RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator);
             }
             return insertProcedureObject;
         }
         return insertProcedureObject;
     }
     /**
      * Inserts vectors-to-final legs into an insert procedure object. Vectors to final legs consist of a discontinuity
      * leg followed by a CF leg to the final approach fix. The course of the CF leg (the vectors-to-final course) is
      * defined as follows:
      * * If the leg to the faf is a CF leg, the VTF course is equal to the CF leg course.
      * * If the leg to the faf is not an IF leg, the VTF course is defined by the great-circle path from the fix
      * immediately prior to the faf to the faf.
      * * If the leg to the faf is an IF leg, the VTF course is defined by the great-circle path from the faf to the fix
      * immediately following it.
      *
      * If a VTF course cannot be defined, then no vectors-to-final legs are inserted.
      * @param proc The insert procedure object to which to insert the vectors-to-final legs.
      * @param fafLeg The leg to the final approach fix in the procedure.
      * @param prevLeg The leg immediately prior to the faf leg.
      * @param nextLeg The leg immediately after the faf leg.
      */
     async insertVtfLegs(proc, fafLeg, prevLeg, nextLeg) {
         if (fafLeg.type === LegType.CF) {
             // faf leg is a CF -> copy the leg into the VTF sequence.
             const discoLeg = FlightPlan.createLeg({ type: LegType.Discontinuity });
             discoLeg.legDefinitionFlags = LegDefinitionFlags.VectorsToFinal;
             proc.procedureLegs.push(discoLeg);
             proc.procedureLegs.push(Object.assign({ legDefinitionFlags: LegDefinitionFlags.VectorsToFinal }, fafLeg));
         }
         else {
             try {
                 const fafFacility = await this.facLoader.getFacility(ICAO.getFacilityType(fafLeg.fixIcao), fafLeg.fixIcao);
                 const fafPoint = Fms.geoPointCache[0].set(fafFacility);
                 let course;
                 if (fafLeg.type === LegType.IF) {
                     // faf leg is an IF, meaning it is the first leg in the approach -> get the course from the next leg.
                     let nextLegFixIcao = '';
                     switch (nextLeg === null || nextLeg === void 0 ? void 0 : nextLeg.type) {
                         case LegType.IF:
                         case LegType.TF:
                         case LegType.DF:
                         case LegType.CF:
                         case LegType.AF:
                         case LegType.RF:
                         case LegType.HF:
                         case LegType.HM:
                         case LegType.HA:
                             nextLegFixIcao = nextLeg.fixIcao;
                     }
                     const nextLegFacility = await this.facLoader.getFacility(ICAO.getFacilityType(nextLegFixIcao), nextLegFixIcao);
                     course = MagVar.trueToMagnetic(fafPoint.bearingTo(nextLegFacility), fafPoint);
                 }
                 else {
                     // faf leg is not the first leg in the approach -> get the course from the previous leg.
                     let prevLegFixIcao = '';
                     switch (prevLeg === null || prevLeg === void 0 ? void 0 : prevLeg.type) {
                         case LegType.IF:
                         case LegType.TF:
                         case LegType.DF:
                         case LegType.CF:
                         case LegType.AF:
                         case LegType.RF:
                         case LegType.HF:
                         case LegType.HM:
                         case LegType.HA:
                             prevLegFixIcao = prevLeg.fixIcao;
                     }
                     const prevLegFacility = await this.facLoader.getFacility(ICAO.getFacilityType(prevLegFixIcao), prevLegFixIcao);
                     course = MagVar.trueToMagnetic(fafPoint.bearingFrom(prevLegFacility), fafPoint);
                 }
                 const discoLeg = FlightPlan.createLeg({ type: LegType.Discontinuity });
                 discoLeg.legDefinitionFlags = LegDefinitionFlags.VectorsToFinal;
                 const vtfLeg = FlightPlan.createLeg({
                     type: LegType.CF,
                     fixIcao: fafLeg.fixIcao,
                     course,
                     fixTypeFlags: fafLeg.fixTypeFlags,
                     altDesc: fafLeg.altDesc,
                     altitude1: fafLeg.altitude1,
                     altitude2: fafLeg.altitude2
                 });
                 vtfLeg.legDefinitionFlags = LegDefinitionFlags.VectorsToFinal;
                 proc.procedureLegs.push(discoLeg);
                 proc.procedureLegs.push(vtfLeg);
             }
             catch (_a) {
                 // noop
             }
         }
     }
     /**
      * Manages the altitude constraints when adding a procedure by creating a VerticalData object for each leg.
      * @param plan The Flight Plan.
      * @param segmentIndex The segment index for the inserted procedure.
      */
     setVerticalData(plan, segmentIndex) {
         const segment = plan.getSegment(segmentIndex);
         for (let l = 0; l < segment.legs.length; l++) {
             const leg = segment.legs[l];
             const altitude1 = leg.leg.altitude1;
             const altitude2 = leg.leg.altitude2;
             const altDesc = leg.leg.altDesc;
             const verticalData = {
                 altDesc: altDesc,
                 altitude1: altitude1,
                 altitude2: altitude2
             };
             plan.setLegVerticalData(segmentIndex, l, verticalData);
         }
     }
     /**
      * Method to set a user altitude constraint.
      * @param segmentIndex The segment index to insert the constraint at.
      * @param segmentLegIndex The leg index to insert the constraint at.
      * @param altitude The altitude to set the constraint at; if undefined, delete user constraint.
      * @param revert Whether to revert the constraint data to the nav data constraint.
      */
     setUserConstraint(segmentIndex, segmentLegIndex, altitude, revert = false) {
         if (this.hasPrimaryFlightPlan()) {
             const plan = this.getPrimaryFlightPlan();
             const verticalData = {
                 altDesc: altitude !== undefined ? AltitudeRestrictionType.At : AltitudeRestrictionType.Unused,
                 altitude1: altitude !== undefined ? UnitType.FOOT.convertTo(altitude, UnitType.METER) : 0,
                 altitude2: 0
             };
             if (revert) {
                 const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
                 if (leg !== null) {
                     const altitude1 = leg.leg.altitude1;
                     const altitude2 = leg.leg.altitude2;
                     const altDesc = leg.leg.altDesc;
                     verticalData.altDesc = altDesc;
                     verticalData.altitude1 = altitude1;
                     verticalData.altitude2 = altitude2;
                 }
             }
             plan.setLegVerticalData(segmentIndex, segmentLegIndex, verticalData);
             const directToData = plan.directToData;
             if (this.getDirectToState() === DirectToState.TOEXISTING && segmentIndex === directToData.segmentIndex
                 && segmentLegIndex === directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET) {
                 plan.setLegVerticalData(segmentIndex, directToData.segmentLegIndex, verticalData);
             }
             plan.calculate();
         }
     }
     /**
      * Method to check if a leg has a user specified constraint.
      * @param segmentIndex The segment index to insert the constraint at.
      * @param segmentLegIndex The leg index to insert the constraint at.
      * @returns Whether the leg has a user constraint.
      */
     isConstraintUser(segmentIndex, segmentLegIndex) {
         if (this.hasPrimaryFlightPlan()) {
             const plan = this.getPrimaryFlightPlan();
             const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
             if ((leg === null || leg === void 0 ? void 0 : leg.verticalData.altDesc) !== (leg === null || leg === void 0 ? void 0 : leg.leg.altDesc) || (leg === null || leg === void 0 ? void 0 : leg.verticalData.altitude1) !== (leg === null || leg === void 0 ? void 0 : leg.leg.altitude1) || (leg === null || leg === void 0 ? void 0 : leg.verticalData.altitude2) !== (leg === null || leg === void 0 ? void 0 : leg.leg.altitude2)) {
                 return true;
             }
         }
         return false;
     }
     /**
      * Method to check if a leg constraint can be reverted to the nav data constraint.
      * @param segmentIndex The segment index to insert the constraint at.
      * @param segmentLegIndex The leg index to insert the constraint at.
      * @returns Whether the leg has a nav data constraint to be reverted to.
      */
     hasConstraint(segmentIndex, segmentLegIndex) {
         if (this.hasPrimaryFlightPlan()) {
             const plan = this.getPrimaryFlightPlan();
             const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
             if (leg !== null && leg.leg.altDesc !== AltitudeRestrictionType.Unused) {
                 switch (leg.leg.altDesc) {
                     case AltitudeRestrictionType.At:
                     case AltitudeRestrictionType.AtOrAbove:
                     case AltitudeRestrictionType.AtOrBelow:
                         return UnitType.METER.convertTo(leg.leg.altitude1, UnitType.FOOT);
                     case AltitudeRestrictionType.Between:
                         return UnitType.METER.convertTo(leg.leg.altitude2, UnitType.FOOT);
                 }
             }
         }
         return undefined;
     }
     /**
      * Manages the altitude constraints for FAF legs where vertical angle info is also provided.
      * @param proc A procedure object.
      * @returns the procedure object, after it has been changed.
      */
     manageFafAltitudeRestriction(proc) {
         proc.procedureLegs.forEach(leg => {
             if (leg.fixTypeFlags === FixTypeFlags.FAF && leg.altitude2 > 0) {
                 const alt = leg.altitude1 <= leg.altitude2 ? leg.altitude1 : leg.altitude2;
                 leg.altDesc = AltitudeRestrictionType.AtOrAbove;
                 leg.altitude1 = alt;
                 leg.altitude2 = 0;
             }
         });
         return proc;
     }
     /**
      * Inserts an IF leg at the beginning of a procedure if it begins with a leg type which defines a fixed origin.
      * @param proc A procedure object.
      * @returns the procedure object, after it has been changed.
      */
     tryInsertIFLeg(proc) {
         const firstLeg = proc.procedureLegs[0];
         let icao;
         switch (firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) {
             case LegType.HA:
             case LegType.HF:
             case LegType.HM:
             case LegType.PI:
             case LegType.FD:
             case LegType.FC:
                 icao = firstLeg.fixIcao;
                 break;
             case LegType.FM:
             case LegType.VM:
                 icao = firstLeg.originIcao;
                 break;
         }
         if (icao && icao !== ICAO.emptyIcao) {
             proc.procedureLegs.unshift(FlightPlan.createLeg({
                 type: LegType.IF,
                 fixIcao: icao,
                 fixTypeFlags: firstLeg.fixTypeFlags & (FixTypeFlags.IF | FixTypeFlags.IAF)
             }));
             if ((firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) === LegType.HF || (firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) === LegType.PI) {
                 proc.procedureLegs[0].altDesc = firstLeg.altDesc;
                 proc.procedureLegs[0].altitude1 = firstLeg.altitude1;
                 proc.procedureLegs[0].altitude2 = firstLeg.altitude2;
             }
             // need to remove IF/IAF flags from the original first leg (now the second leg)
             const replacementLeg = FlightPlan.createLeg(proc.procedureLegs[1]);
             replacementLeg.fixTypeFlags = replacementLeg.fixTypeFlags & ~(FixTypeFlags.IF | FixTypeFlags.IAF);
             if ((firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) !== LegType.PI) {
                 replacementLeg.altDesc = AltitudeRestrictionType.Unused;
                 replacementLeg.altitude1 = 0;
                 replacementLeg.altitude2 = 0;
             }
             proc.procedureLegs[1] = replacementLeg;
         }
         return proc;
     }
     /**
      * Checks the approach legs for an IAF fix type flag, and if one exists, amend the approach to ensure that
      * the IAF is not on a hold/pt leg and that we do not add legs prior to the IAF except in cases where we needed to add
      * an IF leg type.
      * @param proc A procedure object.
      * @returns the procedure object, after it has been changed.
      */
     tryReconcileIAFLeg(proc) {
         let iafIndex = -1;
         for (let i = 0; i < proc.procedureLegs.length; i++) {
             const leg = proc.procedureLegs[i];
             if (leg.fixTypeFlags === FixTypeFlags.IAF) {
                 iafIndex = i;
                 switch (leg.type) {
                     case LegType.HA:
                     case LegType.HF:
                     case LegType.HM:
                     case LegType.PI:
                     case LegType.FD:
                     case LegType.FC:
                         if (iafIndex > 0) {
                             leg.fixTypeFlags &= ~FixTypeFlags.IAF;
                             proc.procedureLegs[iafIndex - 1].fixTypeFlags |= FixTypeFlags.IAF;
                             iafIndex--;
                         }
                 }
                 break;
             }
         }
         return proc;
     }
     /**
      * Inserts a MAP fix type flag if none exists on the approach.
      * @param proc A procedure object.
      * @returns the procedure object, after it has been changed.
      */
     tryInsertMap(proc) {
         let addMap = true;
         let runwayIndex = -1;
         for (let i = 0; i < proc.procedureLegs.length; i++) {
             const leg = proc.procedureLegs[i];
             if (leg.fixTypeFlags === FixTypeFlags.MAP) {
                 addMap = false;
                 break;
             }
             if (leg.fixIcao.search('R') === 0) {
                 runwayIndex = i;
                 break;
             }
         }
         if (addMap && runwayIndex > -1) {
             proc.procedureLegs[runwayIndex].fixTypeFlags = FixTypeFlags.MAP;
         }
         return proc;
     }
     /**
      * Method to remove the duplicate leg after the hold leg.
      * @param proc A procedure object.
      * @returns the procedure object, after it has been changed.
      */
     tryCleanupHold(proc) {
         for (let i = 0; i < proc.procedureLegs.length; i++) {
             const leg = proc.procedureLegs[i];
             if (leg.type === LegType.HF) {
                 const next = proc.procedureLegs[i + 1];
                 if (leg.fixIcao === next.fixIcao && next.type === LegType.IF) {
                     proc.procedureLegs.splice(i + 1, 1);
                 }
             }
         }
         return proc;
     }
     /**
      * Method to remove a course reversal in an approach procedure.
      * @param proc A procedure object.
      * @returns the procedure object, after it has been changed.
      */
     tryRemoveCourseReversal(proc) {
         let canRemove = false;
         if (proc.procedureLegs.length > 2) {
             const leg = proc.procedureLegs[1];
             switch (leg.type) {
                 case LegType.HA:
                 case LegType.HF:
                 case LegType.HM:
                 case LegType.PI:
                     canRemove = true;
             }
         }
         if (canRemove) {
             proc.procedureLegs.splice(1, 1);
         }
         return proc;
     }
     /**
      * Method to remove the departure from the flight plan.
      */
     async removeDeparture() {
         const plan = this.getFlightPlan();
         const segmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Departure);
         plan.setDeparture();
         this.planClearSegment(segmentIndex, FlightPlanSegmentType.Departure);
         if (plan.originAirport) {
             const airport = await this.facLoader.getFacility(FacilityType.Airport, plan.originAirport);
             const updatedSegmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Departure);
             this.planAddOriginDestinationLeg(true, updatedSegmentIndex, airport, plan.procedureDetails.originRunway);
             const prevLeg = plan.getPrevLeg(updatedSegmentIndex, 1);
             const nextLeg = plan.getNextLeg(updatedSegmentIndex, 0);
             if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
                 this.planRemoveDuplicateLeg(prevLeg, nextLeg);
             }
         }
         plan.calculate(0);
     }
     /**
      * Method to remove the arrival from the flight plan.
      */
     async removeArrival() {
         const plan = this.getFlightPlan();
         const segmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Arrival);
         plan.setArrival();
         this.planRemoveSegment(segmentIndex);
         if (plan.procedureDetails.approachIndex < 0 && plan.destinationAirport) {
             const airport = await this.facLoader.getFacility(FacilityType.Airport, plan.destinationAirport);
             const destSegmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Destination);
             this.planAddOriginDestinationLeg(false, destSegmentIndex, airport, plan.procedureDetails.destinationRunway);
         }
         const prevLeg = plan.getPrevLeg(segmentIndex, 0);
         const nextLeg = plan.getNextLeg(segmentIndex, -1);
         if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
             this.planRemoveDuplicateLeg(prevLeg, nextLeg);
         }
         plan.calculate(0);
     }
     /**
      * Method to remove the approach from the flight plan.
      */
     async removeApproach() {
         this.loadApproachFrequency();
         this.setApproachDetails(false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, false);
         const plan = this.getFlightPlan();
         const hasArrival = plan.procedureDetails.arrivalIndex >= 0;
         const segmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Approach);
         if (hasArrival) {
             const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
             const segment = plan.getSegment(lastEnrouteSegmentIndex);
             const lastLegIndex = segment && segment.legs.length > 0 ? segment.legs.length - 1 : 0;
             if (plan.destinationAirport && segment.legs[lastLegIndex] && segment.legs[lastLegIndex].leg.fixIcao === plan.destinationAirport) {
                 this.planRemoveLeg(lastEnrouteSegmentIndex, lastLegIndex);
             }
             plan.setDestinationRunway();
             if (plan.procedureDetails.arrivalFacilityIcao && plan.procedureDetails.arrivalFacilityIcao !== plan.destinationAirport) {
                 const arrivalFacility = await this.facLoader.getFacility(FacilityType.Airport, plan.procedureDetails.arrivalFacilityIcao);
                 this.setDestination(arrivalFacility);
             }
         }
         plan.setApproach();
         this.planRemoveSegment(segmentIndex);
         if (plan.destinationAirport) {
             const airport = await this.facLoader.getFacility(FacilityType.Airport, plan.destinationAirport);
             const destLegSegmentIndex = hasArrival
                 ? this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Arrival)
                 : this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Destination);
             this.planAddOriginDestinationLeg(false, destLegSegmentIndex, airport, plan.procedureDetails.destinationRunway);
         }
         const prevLeg = plan.getPrevLeg(segmentIndex, 0);
         const nextLeg = plan.getNextLeg(segmentIndex, -1);
         if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
             this.planRemoveDuplicateLeg(prevLeg, nextLeg);
         }
         if (plan.getUserData('visual_approach')) {
             plan.deleteUserData('visual_approach');
         }
         plan.calculate(0);
     }
     /**
      * Method to activate a leg in the flight plan.
      * @param segmentIndex is the index of the segment containing the leg to activate.
      * @param legIndex is the index of the leg in the selected segment activate.
      * @param fplnIndex is the index of the flight plan in which to activate the leg.
      * @param inhibitImmediateSequence Whether to inhibit immediate automatic sequencing past the activated leg.
      */
     activateLeg(segmentIndex, legIndex, fplnIndex = 0, inhibitImmediateSequence = false) {
         const plan = this.getFlightPlan(fplnIndex);
         const indexInFlightplan = plan.getSegment(segmentIndex).offset + legIndex;
         if (fplnIndex === 0 && this.flightPlanner.activePlanIndex > 0) {
             this.flightPlanner.setActivePlanIndex(0);
         }
         if (this.missedApproachActive) {
             const segment = plan.getSegment(segmentIndex);
             if (segment.legs[legIndex] && !BitFlags.isAll(segment.legs[legIndex].flags, LegDefinitionFlags.MissedApproach)) {
                 Fms.g1000EvtPub.publishEvent('activate_missed_approach', false);
             }
         }
         if ((fplnIndex !== 0 && this.getDirectToState() === DirectToState.TOEXISTING)
             || (fplnIndex === 0 && (segmentIndex < plan.directToData.segmentIndex || (segmentIndex === plan.directToData.segmentIndex && legIndex <= plan.directToData.segmentLegIndex)))) {
             this.removeDirectToExisting(indexInFlightplan);
             if (fplnIndex !== 0) {
                 plan.setLateralLeg(indexInFlightplan);
                 plan.setCalculatingLeg(indexInFlightplan);
                 plan.calculate(Math.max(0, indexInFlightplan - 1));
             }
         }
         else {
             plan.setLateralLeg(indexInFlightplan);
             plan.setCalculatingLeg(indexInFlightplan);
             plan.calculate(Math.max(0, indexInFlightplan - 1));
         }
         Fms.g1000EvtPub.publishEvent('suspend_sequencing', false);
         if (inhibitImmediateSequence) {
             Fms.g1000EvtPub.publishEvent('lnav_inhibit_next_sequence', true);
         }
     }
     /**
      * Checks whether an approach can be activated. An approach can be activated if and only if the primary flight plan
      * has a non-vectors-to-final approach loaded.
      * @returns Whether an approach can be activated.
      */
     canActivateApproach() {
         const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
         if (!plan) {
             return false;
         }
         return FmsUtils.isApproachLoaded(plan) && !FmsUtils.isVtfApproachLoaded(plan);
     }
     /**
      * Activates an approach. Activating an approach activates a Direct To to the first approach waypoint of the primary
      * flight plan, and attempts to load the primary approach frequency (if one exists) to the nav radios. If the primary
      * flight plan does not have an approach loaded, this method has no effect.
      */
     activateApproach() {
         if (!this.canActivateApproach()) {
             return;
         }
         const approachSegmentIndex = this.ensureOnlyOneSegmentOfType(FlightPlanSegmentType.Approach, false);
         this.createDirectToExisting(approachSegmentIndex, 0);
         this.setLocFrequency(1, true);
         this.setLocFrequency(2, true);
     }
     /**
      * Checks whether vectors-to-final can be activated. VTF can be activated if and only if the primary flight plan has
      * an approach loaded.
      * @returns Whether vectors-to-final can be activated.
      */
     canActivateVtf() {
         const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
         if (!plan) {
             return false;
         }
         return FmsUtils.isApproachLoaded(plan);
     }
     /**
      * Activates vectors-to-final. Activating vectors-to-final activates the primary flight plan's vectors-to-final leg,
      * and attempts to load the primary approach frequency (if one exists) to the nav radios. If the primary flight plan
      * has a non-VTF approach loaded, it will be replaced by its VTF counterpart. If the primary flight plan has no
      * approach loaded, this method has no effect.
      */
     async activateVtf() {
         if (!this.canActivateVtf()) {
             return;
         }
         const plan = this.getPrimaryFlightPlan();
         let vtfLeg = FmsUtils.getApproachVtfLeg(plan);
         let approachType = ApproachType.APPROACH_TYPE_UNKNOWN;
         if (!vtfLeg) {
             // if a VTF approach is not loaded; replace the current approach with its VTF counterpart.
             try {
                 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                 const airport = await this.facLoader.getFacility(FacilityType.Airport, plan.procedureDetails.approachFacilityIcao);
                 const visApproachRwyDesignation = plan.getUserData('visual_approach');
                 if (plan.procedureDetails.approachIndex >= 0) {
                     await this.insertApproach(airport, plan.procedureDetails.approachIndex, -1);
                     approachType = airport.approaches[plan.procedureDetails.approachIndex].approachType;
                 }
                 else {
                     let runway;
                     if (visApproachRwyDesignation) {
                         runway = RunwayUtils.matchOneWayRunwayFromDesignation(airport, visApproachRwyDesignation);
                     }
                     if (!runway) {
                         return;
                     }
                     approachType = AdditionalApproachType.APPROACH_TYPE_VISUAL;
                     await this.insertApproach(airport, -1, -1, runway.direction, runway.runwayDesignator);
                 }
                 vtfLeg = FmsUtils.getApproachVtfLeg(plan);
             }
             catch (_a) {
                 return;
             }
         }
         else {
             approachType = this.approachDetails.approachType;
         }
         if (!vtfLeg) {
             return;
         }
         const segment = plan.getSegmentFromLeg(vtfLeg);
         if (!segment) {
             return;
         }
         this.activateLeg(segment.segmentIndex, segment.legs.indexOf(vtfLeg), Fms.PRIMARY_PLAN_INDEX, true);
         this.setLocFrequency(1, true);
         this.setLocFrequency(2, true);
         switch (approachType) {
             case ApproachType.APPROACH_TYPE_ILS:
             case ApproachType.APPROACH_TYPE_LDA:
             case ApproachType.APPROACH_TYPE_LOCALIZER:
             case ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE:
             case ApproachType.APPROACH_TYPE_SDF:
             case ApproachType.APPROACH_TYPE_VOR:
             case ApproachType.APPROACH_TYPE_VORDME:
                 this.bus.getPublisher().pub('cdi_src_set', { type: NavSourceType.Nav, index: 1 }, true);
                 break;
         }
     }
     /**
      * Method to check if the approach is VTF.
      * @returns whether the approach is VTF.
      */
     isApproachVtf() {
         if (!this.hasPrimaryFlightPlan()) {
             return false;
         }
         const plan = this.getPrimaryFlightPlan();
         return FmsUtils.isVtfApproachLoaded(plan);
     }
     /**
      * Method to check if there is a currently loaded missed approach to be activated.
      * @returns whether the approach can activate
      */
     canMissedApproachActivate() {
         const plan = this.getFlightPlan();
         if (this.cdiSource.type === NavSourceType.Gps && plan && plan.activeLateralLeg < plan.length - 1 && plan.segmentCount > 0) {
             const segmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
             if (segmentIndex > 0) {
                 const segment = plan.getSegment(segmentIndex);
                 if (segment.segmentType === FlightPlanSegmentType.Approach
                     && BitFlags.isAll(segment.legs[segment.legs.length - 1].flags, LegDefinitionFlags.MissedApproach)) {
                     for (let i = 0; i < segment.legs.length; i++) {
                         const leg = segment.legs[i];
                         if (leg.leg.fixTypeFlags === FixTypeFlags.FAF) {
                             if (plan.activeLateralLeg - segment.offset >= i) {
                                 return true;
                             }
                         }
                     }
                 }
             }
         }
         return false;
     }
     /**
      * Method to activate the missed approach.
      */
     activateMissedApproach() {
         if (this.canMissedApproachActivate()) {
             Fms.g1000EvtPub.publishEvent('activate_missed_approach', true);
         }
     }
     // eslint-disable-next-line jsdoc/require-jsdoc
     createDirectToRandom(target, course) {
         const icao = typeof target === 'string' ? target : target.icao;
         const plan = this.flightPlanner.createFlightPlan(1);
         plan.setLateralLeg(0);
         plan.setCalculatingLeg(0);
         for (let i = plan.segmentCount - 1; i >= 0; i--) {
             if (plan.getSegment(i) !== undefined) {
                 plan.removeSegment(i);
             }
         }
         plan.insertSegment(0, FlightPlanSegmentType.RandomDirectTo, undefined, true);
         const segment = plan.getSegment(0);
         if (segment) {
             let originPos = this.ppos;
             if (course) {
                 originPos = this.ppos.offset(NavMath.normalizeHeading(course + 180), UnitType.NMILE.convertTo(500, UnitType.GA_RADIAN));
             }
             const discoLeg = FlightPlan.createLeg({ type: LegType.Discontinuity });
             const dtoOriginLeg = this.createDTOOriginLeg(originPos);
             const dtoTargetLeg = this.createDTODirectLeg(icao);
             plan.addLeg(0, discoLeg, 0, LegDefinitionFlags.DirectTo);
             plan.addLeg(0, dtoOriginLeg, 1, LegDefinitionFlags.DirectTo);
             plan.addLeg(0, dtoTargetLeg, 2, LegDefinitionFlags.DirectTo);
             plan.calculate(0);
             if (this.flightPlanner.activePlanIndex !== 1) {
                 this.flightPlanner.setActivePlanIndex(1);
             }
             Fms.g1000EvtPub.publishEvent('suspend_sequencing', false);
         }
     }
     /**
      * Method to create a direct to an existing waypoint in the plan. This method will also then call activateLeg.
      * @param segmentIndex is the index of the segment containing the leg to activate as direct to.
      * @param legIndex is the index of the leg in the specified segment to activate as direct to.
      * @param course is the course for this direct to, if specified.
      */
     createDirectToExisting(segmentIndex, legIndex, course) {
         const plan = this.getFlightPlan();
         const segment = plan.getSegment(segmentIndex);
         const leg = segment.legs[legIndex];
         let legIndexDelta = 0;
         if (plan.directToData.segmentIndex > -1 && plan.directToData.segmentLegIndex > -1) {
             legIndexDelta -= plan.directToData.segmentIndex === segmentIndex && legIndex > plan.directToData.segmentLegIndex ? 3 : 0;
             if (this.getDirectToState() === DirectToState.TOEXISTING) {
                 this.removeDirectToExisting();
             }
             else {
                 plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                 plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                 plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
             }
         }
         plan.setDirectToData(segmentIndex, legIndex + legIndexDelta);
         if (segment && leg) {
             let originPos = this.ppos;
             if (course) {
                 originPos = this.ppos.offset(NavMath.normalizeHeading(course + 180), UnitType.NMILE.convertTo(500, UnitType.GA_RADIAN));
             }
             const discoLeg = FlightPlan.createLeg({ type: LegType.Discontinuity });
             const dtoOriginLeg = this.createDTOOriginLeg(originPos);
             const dtoTargetLeg = this.createDTODirectLeg(leg.leg.fixIcao, leg.leg);
             plan.addLeg(segmentIndex, discoLeg, legIndex + legIndexDelta + 1, LegDefinitionFlags.DirectTo);
             plan.addLeg(segmentIndex, dtoOriginLeg, legIndex + legIndexDelta + 2, LegDefinitionFlags.DirectTo);
             plan.addLeg(segmentIndex, dtoTargetLeg, legIndex + legIndexDelta + FmsUtils.DTO_LEG_OFFSET, (leg.flags & LegDefinitionFlags.MissedApproach) | LegDefinitionFlags.DirectTo);
             plan.setLegVerticalData(segmentIndex, legIndex + legIndexDelta + FmsUtils.DTO_LEG_OFFSET, leg.verticalData);
             this.activateLeg(segmentIndex, legIndex + legIndexDelta + FmsUtils.DTO_LEG_OFFSET);
         }
     }
     /**
      * Creates a Direct-To origin IF leg.
      * @param ppos The current plane position.
      * @returns a Direct-To origin IF leg.
      */
     createDTOOriginLeg(ppos) {
         return FlightPlan.createLeg({
             type: LegType.IF,
             lat: ppos.lat,
             lon: ppos.lon
         });
     }
     /**
      * Creates a Direct-To DF leg.
      * @param icao is the icao.
      * @param leg The FlightPlanLeg.
      * @returns a Direct-To DF leg.
      */
     createDTODirectLeg(icao, leg) {
         const planeHeading = SimVar.GetSimVarValue('PLANE HEADING DEGREES TRUE', 'degrees');
         if (leg) {
             const directLeg = Object.assign({}, leg);
             directLeg.type = LegType.DF;
             directLeg.course = planeHeading === 0 ? 360 : planeHeading;
             return directLeg;
         }
         else {
             return FlightPlan.createLeg({
                 type: LegType.DF,
                 fixIcao: icao,
                 course: planeHeading === 0 ? 360 : planeHeading
             });
         }
     }
     /**
      * Empties the primary flight plan.
      */
     async emptyPrimaryFlightPlan() {
         if (!this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX)) {
             return;
         }
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         const directToState = this.getDirectToState();
         if (directToState === DirectToState.TOEXISTING || (directToState !== DirectToState.TORANDOM && !Simplane.getIsGrounded() && plan.activeLateralLeg > 0)) {
             const directToIcao = plan.getLeg(plan.activeLateralLeg).leg.fixIcao;
             if (directToIcao) {
                 const facType = ICAO.getFacilityType(directToIcao);
                 const fac = await this.facLoader.getFacility(facType, directToIcao);
                 this.createDirectToRandom(fac);
             }
         }
         for (let i = plan.segmentCount - 1; i >= 0; i--) {
             this.planRemoveSegment(i);
         }
         plan.addSegment(0, FlightPlanSegmentType.Departure);
         plan.addSegment(1, FlightPlanSegmentType.Enroute);
         plan.addSegment(2, FlightPlanSegmentType.Destination);
         this.setOrigin(undefined, undefined);
         this.setDestination(undefined, undefined);
         plan.setDirectToData(-1);
         this.setApproachDetails(false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, false);
         plan.setLateralLeg(0);
         plan.setVerticalLeg(0);
         plan.setCalculatingLeg(0);
         plan.calculate(0);
     }
     /**
      * Builds a flight plan to preview a procedure.
      * @param calculator The flight path calculator to assign to the preview plan.
      * @param facility The airport facility to load the approach from
      * @param procType The type of procedure to preview.
      * @param procIndex The procedure index selected.
      * @param transIndex The transition index selected.
      * @param oneWayRunway The one way runway to build the preview with, if any.
      * @param rwyTransIndex The runway transition index selected, if any.
      * @param visualRunwayNumber is the visual runway number, if any.
      * @param visualRunwayDesignator is the visual runway designator, if any.
      * @param transStartIndex The transition start offset, if any.
      * @returns A Promise which is fulfilled with whether the preview plan was successfully built.
      */
     async buildProcedurePreviewPlan(calculator, facility, procType, procIndex, transIndex, oneWayRunway, rwyTransIndex, visualRunwayNumber, visualRunwayDesignator, transStartIndex) {
         var _a;
         const plan = new FlightPlan(0, calculator);
         let procedureLegObject;
         switch (procType) {
             case ProcedureType.APPROACH:
                 procedureLegObject = await this.buildApproachLegs(facility, procIndex, transIndex, undefined, transStartIndex !== undefined ? transStartIndex : 0);
                 plan.addSegment(0, FlightPlanSegmentType.Approach, undefined, false);
                 break;
             case ProcedureType.ARRIVAL: {
                 const runwayIndex = rwyTransIndex !== null && rwyTransIndex !== void 0 ? rwyTransIndex : -1;
                 procedureLegObject = this.buildArrivalLegs(facility, procIndex, transIndex, runwayIndex, oneWayRunway);
                 plan.addSegment(0, FlightPlanSegmentType.Arrival, undefined, false);
                 break;
             }
             case ProcedureType.DEPARTURE: {
                 const runwayIndex = rwyTransIndex !== null && rwyTransIndex !== void 0 ? rwyTransIndex : -1;
                 procedureLegObject = this.buildDepartureLegs(facility, procIndex, transIndex, runwayIndex, oneWayRunway);
                 plan.addSegment(0, FlightPlanSegmentType.Departure, undefined, false);
                 break;
             }
             case ProcedureType.VISUALAPPROACH:
                 if (visualRunwayNumber !== undefined && visualRunwayDesignator !== undefined) {
                     const visualRunway = RunwayUtils.matchOneWayRunway(facility, visualRunwayNumber, visualRunwayDesignator);
                     procedureLegObject = await this.buildApproachLegs(facility, -1, -1, visualRunway);
                     plan.addSegment(0, FlightPlanSegmentType.Approach, undefined, false);
                 }
                 break;
         }
         if (procedureLegObject && procedureLegObject.procedureLegs.length > 0) {
             if (procedureLegObject.procedureLegs[0].type !== LegType.IF) {
                 const replacementLeg = FlightPlan.createLeg({
                     type: LegType.IF,
                     fixIcao: procedureLegObject.procedureLegs[0].fixIcao,
                     fixTypeFlags: procedureLegObject.procedureLegs[0].fixTypeFlags,
                 });
                 procedureLegObject.procedureLegs.splice(0, 1, replacementLeg);
             }
             procedureLegObject.procedureLegs.forEach((l) => {
                 var _a;
                 plan.addLeg(0, l, undefined, (_a = l.legDefinitionFlags) !== null && _a !== void 0 ? _a : LegDefinitionFlags.None, false);
             });
             if (procType === ProcedureType.APPROACH) {
                 // Adds missed approach legs
                 if (!visualRunwayNumber && !visualRunwayDesignator && procedureLegObject.procedureLegs.length > 0) {
                     const missedLegs = (_a = facility.approaches[procIndex].missedLegs) !== null && _a !== void 0 ? _a : [];
                     if (missedLegs && missedLegs.length > 0) {
                         let maphIndex = -1;
                         for (let m = missedLegs.length - 1; m >= 0; m--) {
                             switch (missedLegs[m].type) {
                                 case LegType.HA:
                                 case LegType.HF:
                                 case LegType.HM:
                                     maphIndex = m - 1;
                                     break;
                             }
                         }
                         for (let n = 0; n < missedLegs.length; n++) {
                             const newLeg = FlightPlan.createLeg(missedLegs[n]);
                             if (maphIndex > 0 && n === maphIndex) {
                                 newLeg.fixTypeFlags |= FixTypeFlags.MAHP;
                                 plan.addLeg(0, newLeg, undefined, LegDefinitionFlags.MissedApproach, false);
                             }
                             else {
                                 plan.addLeg(0, newLeg, undefined, LegDefinitionFlags.MissedApproach, false);
                             }
                         }
                     }
                 }
             }
             await plan.calculate(0);
             return plan;
         }
         else {
             return plan;
         }
     }
     /**
      * Builds a flight plan to preview procedure transitions.
      * @param calculator The flight path calculator to assign to the preview plan.
      * @param facility The airport facility to which the procedure to preview belongs.
      * @param procType The type of procedure to preview.
      * @param procIndex The index of the procedure to preview.
      * @param rwyTransIndex The index of the procedure's runway transition.
      * @returns The index of the procedure transition preview plan.
      */
     async buildProcedureTransitionPreviewPlan(calculator, facility, procType, procIndex, rwyTransIndex) {
         const plan = new FlightPlan(0, calculator);
         let legs;
         switch (procType) {
             case ProcedureType.DEPARTURE:
                 if (facility.departures[procIndex] && rwyTransIndex !== undefined) {
                     legs = this.buildDepartureTransitionPreviewLegs(facility.departures[procIndex], rwyTransIndex);
                 }
                 break;
             case ProcedureType.ARRIVAL:
                 if (facility.arrivals[procIndex] && rwyTransIndex !== undefined) {
                     legs = this.buildArrivalTransitionPreviewLegs(facility.arrivals[procIndex], rwyTransIndex);
                 }
                 break;
             case ProcedureType.APPROACH:
                 if (facility.approaches[procIndex]) {
                     legs = this.buildApproachTransitionPreviewLegs(facility.approaches[procIndex]);
                 }
                 break;
         }
         if (legs && legs.length > 0) {
             plan.addSegment(0, FlightPlanSegmentType.Enroute, undefined, false);
             legs.forEach((l) => {
                 plan.addLeg(0, l, undefined, 0, false);
             });
             await plan.calculate(0);
         }
         return plan;
     }
     /**
      * Builds a sequence of legs for a departure transition preview. The sequence consists of the legs of each departure
      * transition in order. Discontinuity legs separate legs of different transitions.
      * @param departure A departure.
      * @param rwyTransIndex The runway transition index of the departure.
      * @returns A sequence of legs for a departure transition preview.
      */
     buildDepartureTransitionPreviewLegs(departure, rwyTransIndex) {
         var _a;
         const runwayTransition = departure.runwayTransitions[rwyTransIndex];
         if (!runwayTransition && departure.runwayTransitions.length > 0) {
             return [];
         }
         const insertProcObject = { procedureLegs: [] };
         const legs = [];
         const preTransitionLegs = [];
         const lastCommonLeg = departure.commonLegs[departure.commonLegs.length - 1];
         const lastPreTransitionLeg = lastCommonLeg !== null && lastCommonLeg !== void 0 ? lastCommonLeg : runwayTransition.legs[runwayTransition.legs.length - 1];
         const secondLastPreTransitionLeg = lastPreTransitionLeg
             ? lastCommonLeg
                 ? (_a = departure.commonLegs[departure.commonLegs.length - 2]) !== null && _a !== void 0 ? _a : runwayTransition.legs[runwayTransition.legs.length - 1]
                 : runwayTransition.legs[runwayTransition.legs.length - 2]
             : undefined;
         secondLastPreTransitionLeg && preTransitionLegs.push(secondLastPreTransitionLeg);
         lastPreTransitionLeg && preTransitionLegs.push(lastPreTransitionLeg);
         const transitions = departure.enRouteTransitions;
         for (let i = 0; i < transitions.length; i++) {
             const transition = transitions[i];
             if (transition.legs.length > 0) {
                 insertProcObject.procedureLegs.push(...preTransitionLegs);
                 for (let j = 0; j < transition.legs.length; j++) {
                     const leg = transition.legs[j];
                     if (j === 0 && lastPreTransitionLeg && this.isDuplicateIFLeg(lastPreTransitionLeg, leg)) {
                         continue;
                     }
                     insertProcObject.procedureLegs.push(leg);
                 }
                 this.tryCleanupHold(insertProcObject);
                 legs.push(...insertProcObject.procedureLegs, FlightPlan.createLeg({ type: LegType.Discontinuity }));
                 insertProcObject.procedureLegs.length = 0;
             }
         }
         return legs;
     }
     /**
      * Builds a sequence of legs for an arrival transition preview. The sequence consists of the legs of each arrival
      * transition in order. Discontinuity legs separate legs of different transitions.
      * @param arrival An arrival.
      * @param rwyTransIndex The runway transition index of the arrival.
      * @returns A sequence of legs for an arrival transition preview.
      */
     buildArrivalTransitionPreviewLegs(arrival, rwyTransIndex) {
         var _a;
         const runwayTransition = arrival.runwayTransitions[rwyTransIndex];
         if (!runwayTransition && arrival.runwayTransitions.length > 0) {
             return [];
         }
         const insertProcObject = { procedureLegs: [] };
         const legs = [];
         const firstCommonLeg = arrival.commonLegs[0];
         const firstPostTransitionLeg = firstCommonLeg !== null && firstCommonLeg !== void 0 ? firstCommonLeg : runwayTransition.legs[0];
         const secondPostTransitionLeg = firstPostTransitionLeg
             ? firstCommonLeg
                 ? (_a = arrival.commonLegs[1]) !== null && _a !== void 0 ? _a : runwayTransition.legs[0]
                 : runwayTransition.legs[1]
             : undefined;
         const transitions = arrival.enRouteTransitions;
         for (let i = 0; i < transitions.length; i++) {
             const transition = transitions[i];
             if (transition.legs.length > 0) {
                 for (let j = 0; j < transition.legs.length; j++) {
                     insertProcObject.procedureLegs.push(transition.legs[j]);
                 }
                 const lastTransitionLeg = insertProcObject.procedureLegs[insertProcObject.procedureLegs.length - 1];
                 if (firstPostTransitionLeg && !this.isDuplicateIFLeg(lastTransitionLeg, firstPostTransitionLeg)) {
                     insertProcObject.procedureLegs.push(firstPostTransitionLeg);
                     // need to add the second post-transition leg if the last transition leg is a PI leg and first post-
                     // transition leg is an IF so that the calculator can get an inbound course for the PI leg.
                     if (lastTransitionLeg.type === LegType.PI && firstPostTransitionLeg.type === LegType.IF && secondPostTransitionLeg) {
                         insertProcObject.procedureLegs.push(secondPostTransitionLeg);
                     }
                 }
                 this.tryInsertIFLeg(insertProcObject);
                 this.tryCleanupHold(insertProcObject);
                 legs.push(...insertProcObject.procedureLegs, FlightPlan.createLeg({ type: LegType.Discontinuity }));
                 insertProcObject.procedureLegs.length = 0;
             }
         }
         return legs;
     }
     /**
      * Builds a sequence of legs for an approach transition preview. The sequence consists of the legs of each approach
      * transition in order, followed by the first leg of the final approach. Discontinuity legs separate legs of
      * different transitions.
      * @param approach An approach.
      * @returns A sequence of legs for an approach transition preview.
      */
     buildApproachTransitionPreviewLegs(approach) {
         const insertProcObject = { procedureLegs: [] };
         const legs = [];
         const firstFinalLeg = approach.finalLegs[0];
         const secondFinalLeg = approach.finalLegs[1];
         const transitions = approach.transitions;
         for (let i = 0; i < transitions.length; i++) {
             const transition = transitions[i];
             if (transition.legs.length > 0) {
                 for (let j = 0; j < transition.legs.length; j++) {
                     insertProcObject.procedureLegs.push(transition.legs[j]);
                 }
                 const lastTransitionLeg = insertProcObject.procedureLegs[insertProcObject.procedureLegs.length - 1];
                 if (firstFinalLeg && !this.isDuplicateIFLeg(lastTransitionLeg, firstFinalLeg)) {
                     insertProcObject.procedureLegs.push(firstFinalLeg);
                     // need to add the second final approach leg if the last transition leg is a PI leg and first final leg is
                     // an IF so that the calculator can get an inbound course for the PI leg.
                     if (lastTransitionLeg.type === LegType.PI && firstFinalLeg.type === LegType.IF && secondFinalLeg) {
                         insertProcObject.procedureLegs.push(secondFinalLeg);
                     }
                 }
                 this.tryInsertIFLeg(insertProcObject);
                 this.tryCleanupHold(insertProcObject);
                 legs.push(...insertProcObject.procedureLegs, FlightPlan.createLeg({ type: LegType.Discontinuity }));
                 insertProcObject.procedureLegs.length = 0;
             }
         }
         return legs;
     }
     /**
      * Builds a temporary flight plan to preview an airway entry.
      * @param airway The airway object.
      * @param entry The entry intersection facility.
      * @param exit The exit intersection facility.
      * @returns the index of the temporary flight plan.
      */
     buildAirwayPreviewSegment(airway, entry, exit) {
         this.flightPlanner.deleteFlightPlan(Fms.PROC_PREVIEW_PLAN_INDEX);
         const plan = this.flightPlanner.createFlightPlan(Fms.PROC_PREVIEW_PLAN_INDEX);
         const airwayLegObject = this.buildAirwayLegs(airway, entry, exit);
         plan.insertSegment(0, FlightPlanSegmentType.Enroute, airway.name, false);
         if (airwayLegObject.procedureLegs.length > 0) {
             airwayLegObject.procedureLegs.forEach((l) => {
                 plan.addLeg(0, l, undefined, LegDefinitionFlags.None, false);
             });
             plan.calculate(0, true);
         }
         return Fms.PROC_PREVIEW_PLAN_INDEX;
     }
     /**
      * Adds an airway and airway segment to the flight plan.
      * @param airway The airway object.
      * @param entry The entry intersection facility.
      * @param exit The exit intersection facility.
      * @param segmentIndex Is the segment index for the entry leg.
      * @param legIndex Is the leg index of the entry leg in the segment of the
      */
     insertAirwaySegment(airway, entry, exit, segmentIndex, legIndex) {
         const plan = this.getFlightPlan();
         const airwaySegmentIndex = this.prepareAirwaySegment(`${airway.name}.${ICAO.getIdent(exit.icao)}`, segmentIndex, legIndex);
         const airwayLegObject = this.buildAirwayLegs(airway, entry, exit);
         const airwayLegs = airwayLegObject.procedureLegs;
         for (let i = 1; i < airwayLegs.length; i++) {
             this.planAddLeg(airwaySegmentIndex, airwayLegs[i]);
         }
         // handle duplicates
         const airwaySegment = plan.getSegment(airwaySegmentIndex);
         const lastLeg = airwaySegment.legs[airwaySegment.legs.length - 1];
         const nextLeg = plan.getNextLeg(airwaySegmentIndex + 1, -1);
         if (lastLeg && nextLeg && this.isDuplicateLeg(lastLeg.leg, nextLeg.leg)) {
             const nextLegIndex = plan.getLegIndexFromLeg(nextLeg);
             const nextLegSegmentIndex = plan.getSegmentIndex(nextLegIndex);
             const nextLegSegment = plan.getSegment(nextLegSegmentIndex);
             if (this.getAirwayLegType(plan, nextLegSegmentIndex, nextLegIndex - nextLegSegment.offset) === AirwayLegType.ENTRY) {
                 // the duplicated leg is an airway entry -> remove the segment containing it (the segment is guaranteed to
                 // contain just the one leg)
                 this.planRemoveSegment(nextLegSegmentIndex);
             }
             else {
                 this.planRemoveDuplicateLeg(lastLeg, nextLeg);
             }
         }
         plan.calculate(0, true);
     }
     /**
      * Prepares a new, empty airway segment in the primary flight plan which is ready to accept airway legs. Also
      * modifies the segment containing the entry leg, if necessary, either splitting it following the entry leg if it is
      * a non-airway enroute segment, or removing all legs following the entry leg if it is an airway segment. If the
      * entry leg is the last leg in its segment, a new non-airway enroute segment will be inserted after the entry leg
      * segment if the entry leg segment is the last segment in the flight plan or if the following segment is not an
      * enroute segment. If the entry leg is the entry for an existing airway segment, the existing airway segment will be
      * removed.
      * @param airwayName The name of the airway.
      * @param entrySegmentIndex The index of the segment containing the airway entry leg.
      * @param entrySegmentLegIndex The index of the airway entry leg in its segment.
      * @returns The index of the new airway segment.
      */
     prepareAirwaySegment(airwayName, entrySegmentIndex, entrySegmentLegIndex) {
         const plan = this.getPrimaryFlightPlan();
         const entrySegment = plan.getSegment(entrySegmentIndex);
         const nextSegment = entrySegmentIndex + 1 < plan.segmentCount ? plan.getSegment(entrySegmentIndex + 1) : undefined;
         let airwaySegmentIndex = entrySegmentIndex + 1;
         let removeLegsSegmentIndex = -1;
         let removeLegsFromIndex = -1;
         if (entrySegment.airway !== undefined) {
             // the entry leg is within an existing airway segment -> remove all legs in the same segment after the entry leg
             removeLegsSegmentIndex = entrySegmentIndex;
             removeLegsFromIndex = entrySegmentLegIndex + 1;
         }
         else if (entrySegmentLegIndex === entrySegment.legs.length - 1 && (nextSegment === null || nextSegment === void 0 ? void 0 : nextSegment.airway) !== undefined) {
             // the entry leg is the entry leg for an existing airway segment -> remove all legs from the existing airway segment
             removeLegsSegmentIndex = entrySegmentIndex + 1;
             removeLegsFromIndex = 0;
         }
         // remove legs as required
         if (removeLegsSegmentIndex >= 0) {
             const removeLegsSegment = plan.getSegment(removeLegsSegmentIndex);
             if (plan.directToData.segmentIndex === removeLegsSegmentIndex && plan.directToData.segmentLegIndex >= removeLegsFromIndex) {
                 this.removeDirectToExisting();
             }
             if (this.getAirwayLegType(plan, removeLegsSegmentIndex, removeLegsSegment.legs.length - 1) === AirwayLegType.EXIT_ENTRY) {
                 // preserve the airway entry leg
                 const lastLeg = removeLegsSegment.legs[removeLegsSegment.legs.length - 1];
                 this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, removeLegsSegmentIndex + 1);
                 this.planAddLeg(removeLegsSegmentIndex + 1, lastLeg.leg, 0);
             }
             if (removeLegsFromIndex > 0) {
                 while (removeLegsSegment.legs.length > removeLegsFromIndex) {
                     this.planRemoveLeg(removeLegsSegmentIndex, entrySegment.legs.length - 1, true, true);
                 }
             }
             else {
                 this.planRemoveSegment(removeLegsSegmentIndex);
             }
         }
         if (entrySegment.legs.length - 1 > entrySegmentLegIndex) {
             // entry leg is not the last leg in its segment -> split the segment after the entry leg
             airwaySegmentIndex = this.splitSegment(plan, entrySegmentIndex, entrySegmentLegIndex);
         }
         else if (plan.getSegment(entrySegmentIndex).segmentType === FlightPlanSegmentType.Enroute
             && ((nextSegment === null || nextSegment === void 0 ? void 0 : nextSegment.segmentType) !== FlightPlanSegmentType.Enroute)) {
             // entry leg is the last leg in its segment and the following segment doesn't exist or is not an enroute segment
             plan.insertSegment(airwaySegmentIndex, FlightPlanSegmentType.Enroute);
         }
         plan.insertSegment(airwaySegmentIndex, FlightPlanSegmentType.Enroute, airwayName);
         return airwaySegmentIndex;
     }
     /**
      * Splits a segment into two segments if type is enroute; if departure, remove legs after the legIndex, else do nothing.
      * @param plan is the flight plan to edit.
      * @param segmentIndex Is the segment index for the entry leg.
      * @param legIndex Is the leg index of the entry leg in the segment of the
      * @returns the segment number of the new airway segment if one was created, else the current segment or if no action was taken.
      */
     splitSegment(plan, segmentIndex, legIndex) {
         const segment = plan.getSegment(segmentIndex);
         if (segment.segmentType === FlightPlanSegmentType.Enroute) {
             const nextSegmentIndex = this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex + 1);
             for (let i = legIndex + 1; i < segment.legs.length; i++) {
                 const leg = segment.legs[i].leg;
                 this.planRemoveLeg(segmentIndex, i);
                 this.planAddLeg(nextSegmentIndex, leg);
             }
             return nextSegmentIndex;
         }
         else if (segment.segmentType === FlightPlanSegmentType.Departure) {
             for (let i = legIndex + 1; i < segment.legs.length; i++) {
                 this.planRemoveLeg(segmentIndex, i);
             }
         }
         return segmentIndex;
     }
     /**
      * Builds a legs for an airway.
      * @param airway The airway object.
      * @param entry The entry intersection facility.
      * @param exit The exit intersection facility.
      * @returns the InsertProcedureObject.
      */
     buildAirwayLegs(airway, entry, exit) {
         const insertAirwayObject = { procedureLegs: [] };
         const waypoints = airway.waypoints;
         const entryIndex = waypoints.findIndex((w) => w.icao === entry.icao);
         const exitIndex = waypoints.findIndex((w) => w.icao === exit.icao);
         const ascending = exitIndex > entryIndex;
         if (ascending) {
             for (let i = entryIndex; i <= exitIndex; i++) {
                 const leg = FlightPlan.createLeg({
                     fixIcao: waypoints[i].icao,
                     type: i === entryIndex ? LegType.IF : LegType.TF
                 });
                 insertAirwayObject.procedureLegs.push(leg);
             }
         }
         else {
             for (let i = entryIndex; i >= exitIndex; i--) {
                 const leg = FlightPlan.createLeg({
                     fixIcao: waypoints[i].icao,
                     type: i === entryIndex ? LegType.IF : LegType.TF
                 });
                 insertAirwayObject.procedureLegs.push(leg);
             }
         }
         return insertAirwayObject;
     }
     /**
      * Method to remove an airway from the flight plan.
      * @param segmentIndex is the segment index of the airway to remove.
      */
     removeAirway(segmentIndex) {
         const plan = this.getFlightPlan();
         let combineSegments = false;
         const nextSegmentIsAirway = plan.getSegment(segmentIndex + 1).airway;
         if (segmentIndex > 0) {
             const priorSegmentEnrouteNonAirway = plan.getSegment(segmentIndex - 1).segmentType === FlightPlanSegmentType.Enroute
                 && plan.getSegment(segmentIndex - 1).airway === undefined;
             const nextSegmentEnrouteNonAirway = plan.getSegment(segmentIndex + 1).segmentType === FlightPlanSegmentType.Enroute
                 && plan.getSegment(segmentIndex + 1).airway === undefined;
             if (priorSegmentEnrouteNonAirway && nextSegmentEnrouteNonAirway) {
                 combineSegments = true;
             }
             let entryLeg = undefined;
             if (nextSegmentIsAirway) {
                 const segment = plan.getSegment(segmentIndex);
                 entryLeg = segment.legs[segment.legs.length - 1].leg;
             }
             this.planRemoveSegment(segmentIndex);
             if (combineSegments) {
                 this.mergeSegments(plan, segmentIndex - 1);
             }
             if (priorSegmentEnrouteNonAirway && entryLeg !== undefined) {
                 this.planAddLeg(segmentIndex - 1, entryLeg);
             }
             else if (entryLeg !== undefined) {
                 const newSegmentIndex = this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex);
                 this.planAddLeg(newSegmentIndex, entryLeg);
             }
         }
         plan.calculate(0, true);
     }
     /**
      * Merges the legs of two consecutive segments into a single segment. All legs in the second segment are moved to the
      * first, and the second segment is removed from the flight plan.
      * @param plan The flight plan to modify.
      * @param segmentIndex The index of the first segment to merge.
      */
     mergeSegments(plan, segmentIndex) {
         const segmentToGrow = plan.getSegment(segmentIndex);
         const segmentToRemove = plan.getSegment(segmentIndex + 1);
         const segmentToGrowOrigLength = segmentToGrow.legs.length;
         segmentToRemove.legs.forEach((l) => {
             plan.addLeg(segmentIndex, l.leg, undefined, l.flags);
         });
         if (plan.directToData.segmentIndex === segmentIndex + 1) {
             plan.setDirectToData(segmentIndex, segmentToGrowOrigLength + plan.directToData.segmentLegIndex);
         }
         this.planRemoveSegment(segmentIndex + 1);
     }
     /**
      * Inserts a hold-at-waypoint leg to the primary flight plan. The hold leg will be inserted immediately after the
      * specified parent leg. The hold leg must have the same fix as the parent leg.
      * @param planIndex The plan index to add the hold to.
      * @param segmentIndex The index of the segment that contains the hold's parent leg.
      * @param legIndex The index of the hold's parent leg in its segment.
      * @param holdLeg The hold leg to add.
      * @returns Whether the hold-at-waypoint leg was successfully inserted.
      */
     insertHold(planIndex, segmentIndex, legIndex, holdLeg) {
         const plan = this.hasFlightPlan(planIndex) && this.getFlightPlan(planIndex);
         if (!plan) {
             return false;
         }
         const prevLeg = plan.getPrevLeg(segmentIndex, legIndex + 1);
         if ((prevLeg === null || prevLeg === void 0 ? void 0 : prevLeg.leg.fixIcao) !== holdLeg.fixIcao) {
             return false;
         }
         if (planIndex === Fms.PRIMARY_PLAN_INDEX) {
             this.planAddLeg(segmentIndex, holdLeg, legIndex + 1);
             return true;
         }
         else {
             plan.addLeg(segmentIndex, holdLeg);
             Fms.g1000EvtPub.publishEvent('suspend_sequencing', false);
             return true;
         }
     }
     /**
      * Returns the index of the last element in the array where predicate is true, and -1
      * otherwise.
      * @param array The source array to search in
      * @param predicate find calls predicate once for each element of the array, in descending
      * order, until it finds one where predicate returns true. If such an element is found,
      * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
      * @param defaultReturn is the default value
      * @returns either the index or the default if the predicate criteria is not met
      */
     findLastSegmentIndex(array, predicate, defaultReturn = -1) {
         let l = array.length;
         while (l--) {
             if (predicate(array[l], l, array)) {
                 return array[l].segmentIndex;
             }
         }
         return defaultReturn;
     }
     /**
      * Adds a leg to the flight plan.
      * @param segmentIndex The segment to add the leg to.
      * @param leg The leg to add to the plan.
      * @param index The index of the leg in the segment to insert. Will add to the end of the segment if ommitted.
      * @param flags Leg definition flags to apply to the new leg. Defaults to `None` (0).
      * @param notify Whether or not to send notifications after the operation.
      */
     planAddLeg(segmentIndex, leg, index, flags = 0, notify = true) {
         const plan = this.getFlightPlan();
         const dtoLegIndex = plan.directToData.segmentLegIndex;
         const dtoSegmentIndex = plan.directToData.segmentIndex;
         if (dtoSegmentIndex >= 0
             && (segmentIndex < dtoSegmentIndex
                 || (segmentIndex === dtoSegmentIndex && index !== undefined && index <= dtoLegIndex))) {
             this.removeDirectToExisting();
         }
         const segment = plan.getSegment(segmentIndex);
         const addIndex = index ? index : Math.max(segment.legs.length - 1, 0);
         if (segment.segmentType === FlightPlanSegmentType.Approach
             && addIndex > 0
             && BitFlags.isAll(segment.legs[addIndex - 1].flags, LegDefinitionFlags.MissedApproach)) {
             flags |= LegDefinitionFlags.MissedApproach;
         }
         plan.addLeg(segmentIndex, leg, index, flags, notify);
         plan.calculate(plan.activeLateralLeg - 1);
         const activeSegmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
         if (activeSegmentIndex !== -1) {
             const activeLegIndex = plan.activeLateralLeg - plan.getSegment(activeSegmentIndex).offset;
             if (segmentIndex < activeSegmentIndex || (index && segmentIndex == activeSegmentIndex && index < activeLegIndex)) {
                 plan.setLateralLeg(plan.activeLateralLeg + 1);
                 plan.setCalculatingLeg(plan.activeLateralLeg);
             }
         }
         else {
             console.error('planAddLeg: activeSegmentIndex was -1');
         }
     }
     /**
      * Removes a leg from the flight plan.
      * @param segmentIndex The segment to add the leg to.
      * @param segmentLegIndex The index of the leg in the segment to remove.
      * @param notify Whether or not to send notifications after the operation. True by default.
      * @param skipDupCheck Whether to skip checking for duplicates. False by default.
      * @param skipCancelDirectTo Whether to skip canceling a direct to existing if the removed leg is equal to or is
      * located before the direct to target. False by default.
      * @returns whether a leg was removed.
      */
     planRemoveLeg(segmentIndex, segmentLegIndex, notify = true, skipDupCheck = false, skipCancelDirectTo = false) {
         const plan = this.getFlightPlan();
         if (segmentIndex < 0 || segmentIndex >= plan.segmentCount) {
             return false;
         }
         const toRemoveLeg = plan.getSegment(segmentIndex).legs[segmentLegIndex];
         if (!toRemoveLeg) {
             return false;
         }
         const removeLegGlobalIndex = plan.getSegment(segmentIndex).offset + segmentLegIndex;
         const prevLeg = plan.getPrevLeg(segmentIndex, segmentLegIndex);
         const nextLeg = plan.getNextLeg(segmentIndex, segmentLegIndex);
         const isDirectToExistingActive = this.getDirectToState() === DirectToState.TOEXISTING;
         let removed = false;
         const airwayLegType = this.getAirwayLegType(plan, segmentIndex, segmentLegIndex);
         if (airwayLegType !== AirwayLegType.NONE) {
             removed = this.removeLegAirwayHandler(plan, airwayLegType, segmentIndex, segmentLegIndex);
         }
         else {
             removed = plan.removeLeg(segmentIndex, segmentLegIndex, notify) !== null ? true : false;
         }
         if (!removed) {
             return false;
         }
         const dtoLegIndex = plan.directToData.segmentLegIndex;
         const dtoSegmentIndex = plan.directToData.segmentIndex;
         if (!skipCancelDirectTo
             && dtoSegmentIndex >= 0
             && (segmentIndex < dtoSegmentIndex
                 || (segmentIndex === dtoSegmentIndex && segmentLegIndex <= dtoLegIndex))) {
             // Need to adjust direct to data to compensate for removed leg.
             if (segmentIndex === dtoSegmentIndex) {
                 plan.directToData.segmentLegIndex--;
             }
             if (isDirectToExistingActive && segmentIndex === dtoSegmentIndex && segmentLegIndex === dtoLegIndex) {
                 const directIcao = plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET].leg.fixIcao;
                 this.createDirectToRandom(directIcao);
             }
             this.removeDirectToExisting(plan.activeLateralLeg - 1);
         }
         else if (removeLegGlobalIndex < plan.activeLateralLeg || plan.activeLateralLeg >= plan.length) {
             plan.setLateralLeg(plan.activeLateralLeg - 1);
             plan.setCalculatingLeg(plan.activeLateralLeg);
         }
         // Detect if we have created consecutive duplicate legs. If we have, we need to delete one of them.
         if (!skipDupCheck && prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
             this.planRemoveDuplicateLeg(prevLeg, nextLeg);
         }
         if (!skipDupCheck && this.checkIfRemoveLeftEmptySegmentToDelete(plan, segmentIndex)) {
             this.planRemoveSegment(segmentIndex);
         }
         plan.calculate(plan.activeLateralLeg - 1);
         return true;
     }
     /**
      * Method to handle a remove leg request t.
      * @param plan is the flight plan.
      * @param airwayLegType is the airwayLegType returned from the checkIfAirwayLeg method.
      * @param segmentIndex The segment we are removing from.
      * @param index is the leg index in the segment we are removing.
      * @returns whether this handler processed the remove request.
      */
     removeLegAirwayHandler(plan, airwayLegType, segmentIndex, index) {
         var _a, _b;
         switch (airwayLegType) {
             case AirwayLegType.ONROUTE:
                 if (plan.getSegment(segmentIndex + 1).airway) {
                     const segment = plan.getSegment(segmentIndex);
                     const leg = segment.legs[segment.legs.length - 1].leg;
                     this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex + 1);
                     this.planAddLeg(segmentIndex + 1, leg);
                 }
                 this.removeAirway(segmentIndex);
                 return true;
             case AirwayLegType.ENTRY:
                 if (plan.getSegment(segmentIndex).segmentType === FlightPlanSegmentType.Enroute) {
                     const segment = plan.getSegment(segmentIndex + 1);
                     const leg = segment.legs[0].leg;
                     plan.removeLeg(segmentIndex + 1, 0);
                     this.planAddLeg(segmentIndex, leg);
                 }
                 else if (plan.getSegment(segmentIndex).segmentType === FlightPlanSegmentType.Departure) {
                     this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex + 1);
                     const segment = plan.getSegment(segmentIndex + 2);
                     const leg = segment.legs[0].leg;
                     plan.removeLeg(segmentIndex + 2, 0);
                     this.planAddLeg(segmentIndex + 1, leg);
                 }
                 return plan.removeLeg(segmentIndex, index) !== null ? true : false;
             case AirwayLegType.EXIT: {
                 if (index < 1) {
                     this.removeAirway(segmentIndex);
                     return true;
                 }
                 else {
                     const segment = plan.getSegment(segmentIndex);
                     const airway = (_a = segment.airway) === null || _a === void 0 ? void 0 : _a.split('.');
                     segment.airway = airway && airway[0] ? airway[0] + '.' + segment.legs[index - 1].name : segment.airway;
                     plan.setAirway(segmentIndex, segment.airway);
                     return plan.removeLeg(segmentIndex, index) !== null ? true : false;
                 }
             }
             case AirwayLegType.EXIT_ENTRY: {
                 const segment = plan.getSegment(segmentIndex + 1);
                 const leg = segment.legs[0].leg;
                 this.planRemoveLeg(segmentIndex + 1, 0);
                 if (index < 1) {
                     this.removeAirway(segmentIndex);
                 }
                 else {
                     plan.removeLeg(segmentIndex, index);
                     this.planInsertSegmentOfType(FlightPlanSegmentType.Enroute, segmentIndex + 1);
                     const firstAirwaySegment = plan.getSegment(segmentIndex);
                     const airway = (_b = firstAirwaySegment.airway) === null || _b === void 0 ? void 0 : _b.split('.');
                     firstAirwaySegment.airway = airway && airway[0] ? airway[0] + '.' + firstAirwaySegment.legs[index - 1].name : firstAirwaySegment.airway;
                     plan.setAirway(segmentIndex, firstAirwaySegment.airway);
                 }
                 this.planAddLeg(segmentIndex + 1, leg);
                 return true;
             }
         }
         return false;
     }
     /**
      * Checks if a remove left an empty segment that also needs to be removed.
      * @param plan is the flight plan
      * @param segmentIndex The segment to add the leg to.
      * @returns whether to remove the segment.
      */
     checkIfRemoveLeftEmptySegmentToDelete(plan, segmentIndex) {
         const segment = plan.getSegment(segmentIndex);
         let nextSegment;
         if (segmentIndex < plan.segmentCount - 1) {
             nextSegment = plan.getSegment(segmentIndex + 1);
         }
         if (segment.legs.length < 1) {
             switch (segment.segmentType) {
                 case FlightPlanSegmentType.Enroute:
                     if (nextSegment && nextSegment.segmentType === FlightPlanSegmentType.Enroute) {
                         return true;
                     }
                     break;
                 //TODO: Add more cases as appropriate
             }
         }
         return false;
     }
     /**
      * Adds an appropriate origin or destination leg (either an airport or runway fix) to the primary flight plan. Origin
      * legs are added to the beginning of the specified segment. Destination legs are added to the end of the specified
      * segment.
      * @param isOrigin Whether to add an origin leg.
      * @param segmentIndex The index of the segment to which to add the leg.
      * @param airport The origin airport.
      * @param runway The origin runway.
      */
     planAddOriginDestinationLeg(isOrigin, segmentIndex, airport, runway) {
         let leg;
         if (runway) {
             leg = FmsUtils.buildRunwayLeg(airport, runway, isOrigin);
         }
         else {
             leg = FlightPlan.createLeg({
                 lat: airport.lat,
                 lon: airport.lon,
                 type: isOrigin ? LegType.IF : LegType.TF,
                 fixIcao: airport.icao,
                 altitude1: airport.runways[0].elevation + UnitType.FOOT.convertTo(50, UnitType.METER)
             });
         }
         if (leg) {
             this.planAddLeg(segmentIndex, leg, isOrigin ? 0 : undefined);
             if (!isOrigin) {
                 const plan = this.getFlightPlan();
                 const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
                 const lastEnrouteSegment = plan.getSegment(lastEnrouteSegmentIndex);
                 for (let i = lastEnrouteSegment.legs.length - 1; i >= 0; i--) {
                     if (lastEnrouteSegment.legs[i].leg.fixIcao === airport.icao) {
                         this.planRemoveLeg(lastEnrouteSegmentIndex, i, true, true);
                     }
                 }
             }
         }
     }
     /**
      * Method to add a segment to the flightplan.
      * @param segmentType is the FlightPlanSegmentType.
      * @param index is the optional segment index to insert the segment.
      * @returns the segment index of the inserted segment.
      */
     planInsertSegmentOfType(segmentType, index) {
         const plan = this.getFlightPlan();
         let segmentIndex = -1;
         if (index) {
             segmentIndex = index - 1;
         }
         else {
             const segments = [];
             for (const segment of plan.segments()) {
                 segments.push(segment);
             }
             switch (segmentType) {
                 case FlightPlanSegmentType.Origin:
                     break;
                 case FlightPlanSegmentType.Departure:
                     segmentIndex = 0;
                     break;
                 case FlightPlanSegmentType.Arrival:
                     segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                         return v.segmentType === FlightPlanSegmentType.Enroute;
                     }, 2);
                     break;
                 case FlightPlanSegmentType.Approach:
                     segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                         return v.segmentType === FlightPlanSegmentType.Enroute || v.segmentType === FlightPlanSegmentType.Arrival;
                     }, 2);
                     break;
                 case FlightPlanSegmentType.MissedApproach:
                     segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                         return v.segmentType === FlightPlanSegmentType.Approach;
                     }, 2);
                     break;
                 case FlightPlanSegmentType.Destination:
                     segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                         return v.segmentType === FlightPlanSegmentType.Enroute || v.segmentType === FlightPlanSegmentType.Arrival
                             || v.segmentType === FlightPlanSegmentType.Approach;
                     }, 5);
                     break;
                 default:
                     segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                         return v.segmentType === FlightPlanSegmentType.Enroute || v.segmentType === FlightPlanSegmentType.Arrival
                             || v.segmentType === FlightPlanSegmentType.Approach || v.segmentType === FlightPlanSegmentType.Destination;
                     }, 1);
                     segmentIndex--;
                     break;
             }
         }
         return this.planInsertSegment(segmentIndex + 1, segmentType).segmentIndex;
     }
     /**
      * Method to remove all legs from a segment.
      * @param segmentIndex is the index of the segment to delete all legs from.
      * @param segmentType is the type if segment to delete all legs from, if known.
      */
     planClearSegment(segmentIndex, segmentType) {
         this.planRemoveSegment(segmentIndex);
         this.planInsertSegment(segmentIndex, segmentType);
     }
     /**
      * Inserts a segment into the flight plan at the specified index and
      * reflows the subsequent segments.
      * @param segmentIndex The index to insert the flight plan segment.
      * @param segmentType The type of segment this will be.
      * @param airway The airway this segment is made up of, if any
      * @param notify Whether or not to send notifications after the operation.
      * @returns The new flight plan segment.
      */
     planInsertSegment(segmentIndex, segmentType = FlightPlanSegmentType.Enroute, airway, notify = true) {
         const plan = this.getFlightPlan();
         const segment = plan.insertSegment(segmentIndex, segmentType, airway, notify);
         plan.calculate(plan.activeLateralLeg - 1);
         if (plan.directToData.segmentIndex >= 0 && segmentIndex <= plan.directToData.segmentIndex) {
             plan.setDirectToData(plan.directToData.segmentIndex + 1, plan.directToData.segmentLegIndex);
         }
         return segment;
     }
     /**
      * Removes a segment from the flight plan and reflows the segments following
      * the removed segment, not leaving an empty segment at the specified index.
      * @param segmentIndex The index of the segment to remove.
      * @param notify Whether or not to send notifications after the operation.
      */
     planRemoveSegment(segmentIndex, notify = true) {
         const plan = this.getFlightPlan();
         const segment = plan.getSegment(segmentIndex);
         const activeSegmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
         if (plan.directToData.segmentIndex >= 0) {
             if (segmentIndex < plan.directToData.segmentIndex) {
                 plan.setDirectToData(plan.directToData.segmentIndex - 1, plan.directToData.segmentLegIndex);
             }
             else if (segmentIndex === plan.directToData.segmentIndex) {
                 plan.setDirectToData(-1);
             }
         }
         if (activeSegmentIndex === segmentIndex && !Simplane.getIsGrounded() && plan.length > 1) {
             const directIcao = plan.getLeg(plan.activeLateralLeg).leg.fixIcao;
             this.removeDirectToExisting();
             if (this.getDirectToState() !== DirectToState.TORANDOM) {
                 this.createDirectToRandom(directIcao);
             }
         }
         plan.setLateralLeg(plan.activeLateralLeg - Utils.Clamp(plan.activeLateralLeg - segment.offset, 0, segment.legs.length));
         plan.setCalculatingLeg(plan.activeLateralLeg);
         plan.removeSegment(segmentIndex, notify);
         plan.calculate(plan.activeLateralLeg - 1);
     }
     /**
      * Checks whether of two consecutive flight plan legs, the second is a duplicate of the first. The second leg is
      * considered a duplicate if and only if it is an IF, TF, or DF leg with the same terminator fix as the first leg,
      * which is also an IF, TF, or DF leg.
      * @param leg1 The first leg.
      * @param leg2 The second leg.
      * @returns whether the second leg is a duplicate of the first.
      */
     isDuplicateLeg(leg1, leg2) {
         if (leg2.type !== LegType.IF
             && leg2.type !== LegType.DF
             && leg2.type !== LegType.TF
             && leg2.type !== LegType.CF) {
             return false;
         }
         return (leg1.type === LegType.IF
             || leg1.type === LegType.TF
             || leg1.type === LegType.DF
             || leg1.type === LegType.CF)
             && leg1.fixIcao === leg2.fixIcao;
     }
     /**
      * Checks whether of two consecutive flight plan legs, the second is an IF leg and is a duplicate of the first. The
      * IF leg is considered a duplicate if and only if its fix is the same as the fix at which the first leg terminates.
      * @param leg1 The first leg.
      * @param leg2 The second leg.
      * @returns whether the second leg is an duplicate IF leg of the first.
      */
     isDuplicateIFLeg(leg1, leg2) {
         if (leg2.type !== LegType.IF) {
             return false;
         }
         if (leg1.type !== LegType.TF
             && leg1.type !== LegType.DF
             && leg1.type !== LegType.RF
             && leg1.type !== LegType.CF
             && leg1.type !== LegType.AF
             && leg1.type !== LegType.IF) {
             return false;
         }
         return leg1.fixIcao === leg2.fixIcao;
     }
     /**
      * Merges two duplicate legs such that the new merged leg contains the fix type and altitude data from the source leg
      * and all other data is derived from the target leg.
      * @param target The target leg.
      * @param source The source leg.
      * @returns the merged leg.
      */
     mergeDuplicateLegData(target, source) {
         const merged = FlightPlan.createLeg(target);
         merged.fixTypeFlags |= source.fixTypeFlags;
         merged.altDesc = source.altDesc;
         merged.altitude1 = source.altitude1;
         merged.altitude2 = source.altitude2;
         return merged;
     }
     /**
      * Deletes one of two consecutive duplicate legs. If one leg is in a procedure and the other is not, the leg that is
      * not in a procedure will be deleted. If the legs are in different procedures, the earlier leg will be deleted.
      * Otherwise, the later leg will be deleted. If the deleted leg is the target leg of a direct to, the legs in the
      * direct to sequence will be copied and moved to immediately follow the duplicate leg that was not deleted.
      * @param leg1 The first duplicate leg.
      * @param leg2 The second duplicate leg.
      * @returns the leg that was deleted, or null if neither leg was deleted.
      * @throws Error if direct to legs could not be updated.
      */
     planRemoveDuplicateLeg(leg1, leg2) {
         const plan = this.getFlightPlan();
         const leg1Segment = plan.getSegmentFromLeg(leg1);
         const leg1Index = plan.getLegIndexFromLeg(leg1);
         const leg2Segment = plan.getSegmentFromLeg(leg2);
         const leg2Index = plan.getLegIndexFromLeg(leg2);
         if (!leg1Segment || !leg2Segment) {
             return null;
         }
         const isLeg1DirectToLeg = BitFlags.isAll(leg1.flags, LegDefinitionFlags.DirectTo);
         const isLeg2DirectToLeg = BitFlags.isAll(leg2.flags, LegDefinitionFlags.DirectTo);
         const dupDirectToLeg = isLeg1DirectToLeg ? leg1
             : isLeg2DirectToLeg ? leg2
                 : null;
         if (dupDirectToLeg) {
             if (dupDirectToLeg.leg.type === LegType.IF) {
                 // Technically this should never happen.
                 return null;
             }
             else {
                 // If one of the duplicates is the second leg in a direct to sequence, then the true duplicated leg is the
                 // target leg of the DTO. In this case, we call this method with the DTO target leg replacing the DTO leg.
                 const dtoTargetLeg = plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex];
                 return isLeg1DirectToLeg ? this.planRemoveDuplicateLeg(dtoTargetLeg, leg2) : this.planRemoveDuplicateLeg(leg1, dtoTargetLeg);
             }
         }
         const isLeg1InProc = leg1Segment.segmentType !== FlightPlanSegmentType.Enroute;
         const isLeg2InProc = leg2Segment.segmentType !== FlightPlanSegmentType.Enroute;
         let toDeleteSegment;
         let toDeleteIndex;
         let toDeleteLeg;
         if ((!isLeg1InProc && isLeg2InProc)
             || (isLeg1InProc && isLeg2InProc && leg1Segment !== leg2Segment)
             || BitFlags.isAny(leg2.leg.fixTypeFlags, FixTypeFlags.FAF | FixTypeFlags.MAP)) {
             toDeleteSegment = leg1Segment;
             toDeleteIndex = leg1Index - leg1Segment.offset;
             toDeleteLeg = leg1;
         }
         else {
             toDeleteSegment = leg2Segment;
             toDeleteIndex = leg2Index - leg2Segment.offset;
             leg1.leg = this.mergeDuplicateLegData(leg1.leg, leg2.leg);
             toDeleteLeg = leg2;
         }
         if (toDeleteIndex >= 0) {
             const dtoTargetLeg = plan.directToData.segmentIndex < 0 ? null : plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex];
             const needMoveDtoLegs = toDeleteLeg === dtoTargetLeg;
             if (needMoveDtoLegs) {
                 const isDtoExistingActive = this.getDirectToState() === DirectToState.TOEXISTING;
                 // If the removed leg was the target leg of a DTO existing, we need to shift the DTO legs to target the leg
                 // that was not removed.
                 const oldDiscoLeg = plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                 const oldDtoLeg1 = plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                 const oldDtoLeg2 = plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                 if (!oldDtoLeg1 || !oldDtoLeg2 || !oldDiscoLeg) {
                     throw new Error(`Fms: Could not remove direct to legs starting at segment index ${plan.directToData.segmentIndex}, leg index ${plan.directToData.segmentLegIndex} during duplicate leg resolution.`);
                 }
                 const preservedLeg = toDeleteLeg === leg1 ? leg2 : leg1;
                 const preservedLegIndex = plan.getLegIndexFromLeg(preservedLeg);
                 const newTargetSegmentIndex = plan.getSegmentIndex(preservedLegIndex);
                 const newTargetSegmentLegIndex = preservedLegIndex - plan.getSegment(newTargetSegmentIndex).offset;
                 plan.setDirectToData(newTargetSegmentIndex, newTargetSegmentLegIndex);
                 plan.addLeg(newTargetSegmentIndex, FlightPlan.createLeg(oldDiscoLeg.leg), newTargetSegmentLegIndex + 1, LegDefinitionFlags.DirectTo);
                 plan.addLeg(newTargetSegmentIndex, FlightPlan.createLeg(oldDtoLeg1.leg), newTargetSegmentLegIndex + 2, LegDefinitionFlags.DirectTo);
                 plan.addLeg(newTargetSegmentIndex, FlightPlan.createLeg(oldDtoLeg2.leg), newTargetSegmentLegIndex + 3, LegDefinitionFlags.DirectTo);
                 if (isDtoExistingActive) {
                     plan.setLateralLeg(preservedLegIndex + FmsUtils.DTO_LEG_OFFSET);
                 }
             }
             const success = this.planRemoveLeg(toDeleteSegment.segmentIndex, toDeleteIndex, true, false, needMoveDtoLegs);
             if (success) {
                 return toDeleteLeg;
             }
         }
         return null;
     }
     /**
      * Loads an approach frequency into the fms.
      * @param facility The airport facility.
      * @param approachIndex The approach Index.
      */
     loadApproachFrequency(facility, approachIndex) {
         this.approachFrequency.set(FmsUtils.getApproachFrequency(facility, approachIndex));
     }
     /**
      * Loads an approach frequency into the fms.
      * @param radioIndex The radio index to set (1 or 2).
      * @param forceNotify resets the subject to force a cross-instrument notification.
      */
     setLocFrequency(radioIndex, forceNotify = false) {
         const setActive = this.cdiSource.type === NavSourceType.Gps || this.cdiSource.index !== radioIndex;
         const approachFrequency = this.approachFrequency.get();
         if (forceNotify) {
             this.approachFrequency.set(undefined);
             this.approachFrequency.set(approachFrequency);
         }
         if (approachFrequency !== undefined && radioIndex > 0 && radioIndex < 3) {
             SimVar.SetSimVarValue(`K:NAV${radioIndex}_STBY_SET_HZ`, 'Hz', approachFrequency.freqMHz * 1000000);
             if (setActive) {
                 SimVar.SetSimVarValue(`K:NAV${radioIndex}_RADIO_SWAP`, 'Bool', 1);
             }
         }
     }
     /**
      * Sets the approach details for the loaded approach and sends an event across the bus.
      * @param approachLoaded Whether an approach is loaded.
      * @param approachType The approach type.
      * @param approachRnavType The approach RNAV type.
      * @param approachIsActive Whether the approach is active.
      * @param approachIsCircling Whether the approach is a circling approach.
      */
     setApproachDetails(approachLoaded, approachType, approachRnavType, approachIsActive, approachIsCircling) {
         const approachDetails = {
             approachLoaded: approachLoaded !== undefined ? approachLoaded : this.approachDetails.approachLoaded,
             approachType: approachType !== undefined ? approachType : this.approachDetails.approachType,
             approachRnavType: approachRnavType !== undefined ? approachRnavType : this.approachDetails.approachRnavType,
             approachIsActive: approachIsActive !== undefined ? approachIsActive : this.approachDetails.approachIsActive,
             approachIsCircling: approachIsCircling !== undefined ? approachIsCircling : this.approachDetails.approachIsCircling
         };
         if (approachDetails.approachIsActive && !approachDetails.approachLoaded) {
             this.checkApproachState();
             return;
         }
         if (approachDetails !== this.approachDetails) {
             this.approachDetails = approachDetails;
             Fms.g1000EvtPub.publishEvent('approach_details_set', this.approachDetails);
         }
     }
 }
 Fms.PRIMARY_PLAN_INDEX = 0;
 Fms.DTO_RANDOM_PLAN_INDEX = 1;
 Fms.PROC_PREVIEW_PLAN_INDEX = 2;
 Fms.geoPointCache = [new GeoPoint(0, 0)];
 
 var LNavVars;
 (function (LNavVars) {
     /** The name of the LVar used to publish the current dtk (magnetic). */
     LNavVars["DTK"] = "L:WTAP_LNav_DTK_Mag";
     /** The name of the LVar used to publish the current xtk. */
     LNavVars["XTK"] = "L:WTAP_LNav_XTK";
     /** The name of the LVar used to publish the next dtk (magnetic). */
     LNavVars["NextDTK"] = "L:WTAP_LNav_Next_DTK_Mag";
     /** The name of the LVar used to publish the next xtk. */
     LNavVars["NextXTK"] = "L:WTAP_LNav_Next_XTK";
     /** The name of the LVar used to publish the bearing (magnetic) to the current waypoint. */
     LNavVars["Bearing"] = "L:WTAP_LNav_BRG_Mag";
     /** The name of the LVar used to publish the distance to the current waypoint. */
     LNavVars["Distance"] = "L:WTAP_LNav_DIS";
     /** The name of the LVar used to publish the distance to the current waypoint. */
     LNavVars["DistanceToTurn"] = "L:WTAP_LNav_DIS_Turn";
     /** Whether or not the LNAV is in a leg-to-leg turn. */
     LNavVars["IsTurning"] = "L:WTAP_LNav_IsTurning";
     /** The current max CDI deflection, in NM. */
     LNavVars["CDIScale"] = "L:WTAP_LNav_CDI_Scale";
     /** The current value of the CDI scale label enum. */
     LNavVars["CDIScaleLabel"] = "L:WTGarmin_LNavData_CDI_Scale_Label";
     /** The current calculated Distance to the Destination. */
     LNavVars["DistanceToDestination"] = "L:WTAP_LNav_Destination_Dis";
     /** Whether LNAV is tracking to a waypoint. */
     LNavVars["IsTrackingWaypoint"] = "L:WTAP_LNav_Is_Tracking";
     /** Whether LNAV is tracking to a waypoint. */
     LNavVars["EffectiveLegIndex"] = "L:WTAP_LNav_Effective_Leg_Index";
 })(LNavVars || (LNavVars = {}));
 /**
  * Valid CDI scale labels for the LVar scale enum.
  */
 var CDIScaleLabel;
 (function (CDIScaleLabel) {
     CDIScaleLabel[CDIScaleLabel["Departure"] = 0] = "Departure";
     CDIScaleLabel[CDIScaleLabel["Terminal"] = 1] = "Terminal";
     CDIScaleLabel[CDIScaleLabel["TerminalDeparture"] = 2] = "TerminalDeparture";
     CDIScaleLabel[CDIScaleLabel["TerminalArrival"] = 3] = "TerminalArrival";
     CDIScaleLabel[CDIScaleLabel["Enroute"] = 4] = "Enroute";
     CDIScaleLabel[CDIScaleLabel["Oceanic"] = 5] = "Oceanic";
     CDIScaleLabel[CDIScaleLabel["LNav"] = 6] = "LNav";
     CDIScaleLabel[CDIScaleLabel["LNavPlusV"] = 7] = "LNavPlusV";
     CDIScaleLabel[CDIScaleLabel["Visual"] = 8] = "Visual";
     CDIScaleLabel[CDIScaleLabel["LNavVNav"] = 9] = "LNavVNav";
     CDIScaleLabel[CDIScaleLabel["LP"] = 10] = "LP";
     CDIScaleLabel[CDIScaleLabel["LPPlusV"] = 11] = "LPPlusV";
     CDIScaleLabel[CDIScaleLabel["LPV"] = 12] = "LPV";
     CDIScaleLabel[CDIScaleLabel["Approach"] = 13] = "Approach";
     CDIScaleLabel[CDIScaleLabel["MissedApproach"] = 14] = "MissedApproach";
 })(CDIScaleLabel || (CDIScaleLabel = {}));
 /** A publisher to poll and publish nav/com simvars. */
 class LNavSimVarPublisher extends SimVarPublisher {
     /**
      * Create a NavComSimVarPublisher
      * @param bus The EventBus to publish to
      */
     constructor(bus) {
         super(LNavSimVarPublisher.simvars, bus);
     }
 }
 LNavSimVarPublisher.simvars = new Map([
     ['lnavBrgMag', { name: LNavVars.Bearing, type: SimVarValueType.Degree }],
     ['lnavDis', { name: LNavVars.Distance, type: SimVarValueType.NM }],
     ['lnavDisTurn', { name: LNavVars.DistanceToTurn, type: SimVarValueType.NM }],
     ['lnavDtkMag', { name: LNavVars.DTK, type: SimVarValueType.Degree }],
     ['lnavXtk', { name: LNavVars.XTK, type: SimVarValueType.NM }],
     ['lnavNextDtkMag', { name: LNavVars.NextDTK, type: SimVarValueType.Degree }],
     ['lnavNextXtkMag', { name: LNavVars.NextXTK, type: SimVarValueType.NM }],
     ['lnavCdiScaling', { name: LNavVars.CDIScale, type: SimVarValueType.NM }],
     ['lnavCdiScalingLabel', { name: LNavVars.CDIScaleLabel, type: SimVarValueType.Number }],
     ['lnavDistanceToDestination', { name: LNavVars.DistanceToDestination, type: SimVarValueType.NM }],
     ['lnavIsTracking', { name: LNavVars.IsTrackingWaypoint, type: SimVarValueType.Bool }],
 ]);
 
 /**
  * Events published by the VNAV system on the bus.
  */
 var VNavSimVars;
 (function (VNavSimVars) {
     /** The vertical deviation. */
     VNavSimVars["VerticalDeviation"] = "L:WTAP_VNav_Vertical_Deviation";
     /** The VNAV target altitude. */
     VNavSimVars["TargetAltitude"] = "L:WTAP_VNav_Target_Altitude";
     /** The VNAV path mode. */
     VNavSimVars["PathMode"] = "L:WTAP_VNav_Path_Mode";
     /** The VNAV mode. */
     VNavSimVars["VNAVMode"] = "L:WTAP_VNav_Mode";
     /** The VNAV approach guidance mode. */
     VNavSimVars["ApproachMode"] = "L:WTAP_VNav_Approach_Mode";
     /** The distance to the next TOD, or -1 if one does not exist. */
     VNavSimVars["TODDistance"] = "L:WTAP_VNav_Distance_To_TOD";
     /** The index of the leg for the next TOD. */
     VNavSimVars["TODLegIndex"] = "L:WTAP_VNav_TOD_Leg_Index";
     /** The distance from the end of the TOD leg that the TOD is. */
     VNavSimVars["TODDistanceInLeg"] = "L:WTAP_VNav_TOD_Distance_In_Leg";
     /** The index of the leg for the next BOD. */
     VNavSimVars["BODLegIndex"] = "L:WTAP_VNav_BOD_Leg_Index";
     /** The index of the leg for the next constraint. */
     VNavSimVars["CurrentConstraintLegIndex"] = "L:WTAP_VNav_Constraint_Leg_Index";
     /** The current constraint altitude. */
     VNavSimVars["CurrentConstraintAltitude"] = "L:WTAP_VNav_Constraint_Altitude";
     /** The distance to the next BOD, or -1 if one does not exist. */
     VNavSimVars["BODDistance"] = "L:WTAP_VNav_Distance_To_BOD";
     /** The VNAV current altitude capture type. */
     VNavSimVars["CaptureType"] = "L:WTAP_VNav_Alt_Capture_Type";
     /** The current required flight path angle. */
     VNavSimVars["FPA"] = "L:WTAP_VNav_FPA";
     /** The current LPV vertical deviation. */
     VNavSimVars["LPVVerticalDeviation"] = "L:WTAP_GP_Vertical_Deviation";
     /** The current remaining LPV distance. */
     VNavSimVars["LPVDistance"] = "L:WTAP_GP_Distance";
     /** The required VS to the current constraint. */
     VNavSimVars["RequiredVS"] = "L:WTAP_VNAV_Required_VS";
     /** Whether a VNAV Path Exists for the current leg. */
     VNavSimVars["PathAvailable"] = "L:WTAP_VNAV_Path_Available";
 })(VNavSimVars || (VNavSimVars = {}));
 /** A publisher to poll and publish nav/com simvars. */
 class VNavSimVarPublisher extends SimVarPublisher {
     /**
      * Create a NavComSimVarPublisher
      * @param bus The EventBus to publish to
      */
     constructor(bus) {
         super(VNavSimVarPublisher.simvars, bus);
     }
 }
 VNavSimVarPublisher.simvars = new Map([
     ['vnavVDev', { name: VNavSimVars.VerticalDeviation, type: SimVarValueType.Feet }],
     ['vnavTargetAlt', { name: VNavSimVars.TargetAltitude, type: SimVarValueType.Feet }],
     ['vnavPathMode', { name: VNavSimVars.PathMode, type: SimVarValueType.Number }],
     ['vnavMode', { name: VNavSimVars.VNAVMode, type: SimVarValueType.Number }],
     ['vnavApproachMode', { name: VNavSimVars.ApproachMode, type: SimVarValueType.Number }],
     ['vnavTodDistance', { name: VNavSimVars.TODDistance, type: SimVarValueType.Number }],
     ['vnavTodLegDistance', { name: VNavSimVars.TODDistanceInLeg, type: SimVarValueType.Number }],
     ['vnavTodLegIndex', { name: VNavSimVars.TODLegIndex, type: SimVarValueType.Number }],
     ['vnavBodLegIndex', { name: VNavSimVars.BODLegIndex, type: SimVarValueType.Number }],
     ['vnavConstraintLegIndex', { name: VNavSimVars.CurrentConstraintLegIndex, type: SimVarValueType.Number }],
     ['vnavConstraintAltitude', { name: VNavSimVars.CurrentConstraintAltitude, type: SimVarValueType.Feet }],
     ['vnavBodDistance', { name: VNavSimVars.BODDistance, type: SimVarValueType.Number }],
     ['vnavAltCaptureType', { name: VNavSimVars.CaptureType, type: SimVarValueType.Number }],
     ['vnavFpa', { name: VNavSimVars.FPA, type: SimVarValueType.FPM }],
     ['vnavLpvVDev', { name: VNavSimVars.LPVVerticalDeviation, type: SimVarValueType.Feet }],
     ['vnavLpvDistance', { name: VNavSimVars.LPVDistance, type: SimVarValueType.Number }],
     ['vnavRequiredVs', { name: VNavSimVars.RequiredVS, type: SimVarValueType.Number }]
 ]);
 
 /**
  * The wind overlay options.
  */
 var WindOverlaySettingMode;
 (function (WindOverlaySettingMode) {
     WindOverlaySettingMode[WindOverlaySettingMode["Off"] = 0] = "Off";
     WindOverlaySettingMode[WindOverlaySettingMode["Opt1"] = 1] = "Opt1";
     WindOverlaySettingMode[WindOverlaySettingMode["Opt2"] = 2] = "Opt2";
     WindOverlaySettingMode[WindOverlaySettingMode["Opt3"] = 3] = "Opt3";
 })(WindOverlaySettingMode || (WindOverlaySettingMode = {}));
 /**
  * Setting modes for the pfd map layout option.
  */
 var PfdMapLayoutSettingMode;
 (function (PfdMapLayoutSettingMode) {
     PfdMapLayoutSettingMode[PfdMapLayoutSettingMode["Off"] = 0] = "Off";
     PfdMapLayoutSettingMode[PfdMapLayoutSettingMode["Inset"] = 1] = "Inset";
     PfdMapLayoutSettingMode[PfdMapLayoutSettingMode["HSI"] = 2] = "HSI";
     PfdMapLayoutSettingMode[PfdMapLayoutSettingMode["TFC"] = 3] = "TFC";
 })(PfdMapLayoutSettingMode || (PfdMapLayoutSettingMode = {}));
 /**
  * Utility class for retrieving PFD user setting managers.
  */
 class PFDUserSettings {
     /**
      * Retrieves a manager for map user settings.
      * @param bus The event bus.
      * @returns a manager for map user settings.
      */
     static getManager(bus) {
         var _a;
         return (_a = PFDUserSettings.INSTANCE) !== null && _a !== void 0 ? _a : (PFDUserSettings.INSTANCE = new DefaultUserSettingManager(bus, [
             {
                 name: 'windOption',
                 defaultValue: WindOverlaySettingMode.Off
             },
             {
                 name: 'mapLayout',
                 defaultValue: PfdMapLayoutSettingMode.Off
             },
             {
                 name: 'svtToggle',
                 defaultValue: true
             },
             {
                 name: 'baroHpa',
                 defaultValue: false
             },
             {
                 name: 'altMetric',
                 defaultValue: false
             },
             {
                 name: 'svtHdgLabelToggle',
                 defaultValue: true
             }
         ]));
     }
 }
 
 /**
  * Controls the value of the autopilot selected altitude setting in response to key events.
  */
 class AltitudeSelectManager {
     /**
      * Constructor.
      * @param bus The event bus.
      * @param minValue The minimum value of the selected altitude setting.
      * @param maxValue The maximum value of the selected altitude setting.
      */
     constructor(bus, minValue, maxValue) {
         this.bus = bus;
         this.pfdSettingsManager = PFDUserSettings.getManager(this.bus);
         this.altimeterMetricSetting = this.pfdSettingsManager.getSetting('altMetric');
         this.isEnabled = true;
         this.isInitialized = false;
         this.isLocked = false;
         this.lockDebounceTimer = new DebounceTimer();
         this.selectedAltitudeChangedHandler = () => {
             // wait one frame before unlocking due to delay between when a key event is created and when it is intercepted on
             // the JS side
             setTimeout(() => {
                 this.isLocked = false;
                 this.lockDebounceTimer.clear();
             });
         };
         this.minValueFeet = Math.round(minValue.asUnit(UnitType.FOOT));
         this.maxValueFeet = Math.round(maxValue.asUnit(UnitType.FOOT));
         this.minValueMeters = Math.round(minValue.asUnit(UnitType.METER));
         this.maxValueMeters = Math.round(maxValue.asUnit(UnitType.METER));
         KeyInterceptManager.getManager(bus).then(manager => {
             this.keyInterceptManager = manager;
             manager.interceptKey('AP_ALT_VAR_SET_ENGLISH', false);
             manager.interceptKey('AP_ALT_VAR_SET_METRIC', false);
             manager.interceptKey('AP_ALT_VAR_INC', false);
             manager.interceptKey('AP_ALT_VAR_DEC', false);
             const sub = this.bus.getSubscriber();
             sub.on('alt_select').whenChanged().handle(this.selectedAltitudeChangedHandler);
             sub.on('key_intercept').handle(this.onKeyIntercepted.bind(this));
             this.bus.pub('ap_alt_sel_set', !this.isEnabled || this.isInitialized, true);
         });
     }
     /**
      * Sets whether this manager is enabled. When this manager is disabled, all key events to change the selected
      * altitude setting are processed "as-is".
      * @param isEnabled Whether this manager is enabled.
      */
     setEnabled(isEnabled) {
         this.isEnabled = isEnabled;
         this.bus.pub('ap_alt_sel_set', !isEnabled || this.isInitialized, true);
     }
     /**
      * Responds to key intercepted events.
      * @param data The event data.
      * @param data.key The key that was intercepted.
      * @param data.index The index of the intercepted key event.
      * @param data.value The value of the intercepted key event.
      */
     onKeyIntercepted({ key, index, value }) {
         index !== null && index !== void 0 ? index : (index = 1); // key events without an explicit index automatically get mapped to index 1
         // Even though values are uint32, we will do what the sim does and pretend they're actually sint32
         if (value !== undefined && value >= 2147483648) {
             value -= 4294967296;
         }
         if (!this.isEnabled || index > 1) {
             this.passThroughKeyEvent(key, index, value);
             return;
         }
         // In order to avoid race conditions, only handle a key event if we are not already busy setting the selected
         // altitude simvar
         if (!this.isLocked) {
             this.handleKeyEvent(key, value);
         }
     }
     //PM Modif added config data for increment size
     /**
      * Handles a key event.
      * @param key The key.
      * @param value The value of the key event.
      */
     handleKeyEvent(key, value) {
         const currentValue = SimVar.GetSimVarValue('AUTOPILOT ALTITUDE LOCK VAR:1', SimVarValueType.Feet);
         let startValue = currentValue;
         if (!this.isInitialized) {
             startValue = SimVar.GetSimVarValue('INDICATED ALTITUDE', SimVarValueType.Feet);
             this.bus.pub('ap_alt_sel_set', true, true);
             this.isInitialized = true;
         }
         let limitLargeIncrement = PMS50_AUTOPILOT_CONFIG.altitude_large_increment ? PMS50_AUTOPILOT_CONFIG.altitude_large_increment : 1000;
         switch (key) {
             case 'AP_ALT_VAR_INC':
                 this.changeSelectedAltitude(startValue, 1, value !== undefined && value > limitLargeIncrement - 1);
                 break;
             case 'AP_ALT_VAR_DEC':
                 this.changeSelectedAltitude(startValue, -1, value !== undefined && value > limitLargeIncrement - 1);
                 break;
             case 'AP_ALT_VAR_SET_ENGLISH':
             case 'AP_ALT_VAR_SET_METRIC': {
                 if (value !== undefined && value !== currentValue) {
                     const delta = value - currentValue;
                     this.changeSelectedAltitude(startValue, delta < 0 ? -1 : 1, Math.abs(delta) > limitLargeIncrement - 1);
                 }
                 break;
             }
         }
     }
     //PM Modif added config data for increment size
     /**
      * Increments or decrements the selected altitude setting. The amount the setting is changed depends on whether the
      * PFD altimeter metric mode is enabled. The value of the setting after the change is guaranteed to be a round number
      * in the appropriate units (nearest 100 feet or 50 meters).
      * @param startValue The value from which to change, in feet.
      * @param direction The direction of the change: `1` for increment, `-1` for decrement.
      * @param useLargeIncrement Whether to change the altitude by the large increment (1000 feet/500 meters) instead of
      * the small increment (100 feet/50 meters). False by default.
      */
     changeSelectedAltitude(startValue, direction, useLargeIncrement = false) {
         const roundFunc = direction === 1 ? Math.floor : Math.ceil;
         const isMetric = this.altimeterMetricSetting.value;
         let min, max, increment, units;
         let limitLargeIncrement = PMS50_AUTOPILOT_CONFIG.altitude_large_increment ? PMS50_AUTOPILOT_CONFIG.altitude_large_increment : 1000;
         if (isMetric) {
             min = this.minValueMeters;
             max = this.maxValueMeters;
             increment = limitLargeIncrement ? limitLargeIncrement / 20 : 50;
             units = UnitType.METER;
         }
         else {
             min = this.minValueFeet;
             max = this.maxValueFeet;
             increment = limitLargeIncrement ? limitLargeIncrement / 10 : 100;
             units = UnitType.FOOT;
         }
         startValue = Math.round(UnitType.FOOT.convertTo(startValue, units));
         useLargeIncrement && (useLargeIncrement = startValue % increment === 0);
         const valueToSet = UnitType.FOOT.convertFrom(Utils.Clamp((roundFunc(startValue / increment) + direction * (useLargeIncrement ? 10 : 1)) * increment, min, max), units);
         if (valueToSet !== SimVar.GetSimVarValue('AUTOPILOT ALTITUDE LOCK VAR:1', SimVarValueType.Feet)) {
             SimVar.SetSimVarValue('AUTOPILOT ALTITUDE LOCK VAR:1', SimVarValueType.Feet, valueToSet);
             this.isLocked = true;
             // Sometimes the alt select change event will not fire if the change is too small, so we set a timeout to unlock
             // just in case
             this.lockDebounceTimer.schedule(() => { this.isLocked = false; }, 250);
         }
     }
     /**
      * Processes a key event "as-is".
      * @param key The key that was pressed.
      * @param index The index of the key event.
      * @param value The value of the key event.
      */
     passThroughKeyEvent(key, index, value) {
         index = Math.max(1, index);
         const currentValue = SimVar.GetSimVarValue(`AUTOPILOT ALTITUDE LOCK VAR:${index}`, SimVarValueType.Feet);
         let valueToSet = currentValue;
         switch (key) {
             case 'AP_ALT_VAR_SET_ENGLISH':
             case 'AP_ALT_VAR_SET_METRIC':
                 if (value !== undefined) {
                     valueToSet = value;
                 }
                 break;
             case 'AP_ALT_VAR_INC':
                 valueToSet += value !== null && value !== void 0 ? value : 100;
                 break;
             case 'AP_ALT_VAR_DEC':
                 valueToSet -= value !== null && value !== void 0 ? value : 100;
                 break;
         }
         SimVar.SetSimVarValue(`AUTOPILOT ALTITUDE LOCK VAR:${index}`, SimVarValueType.Feet, valueToSet);
     }
 }
 
 /**
  * A G1000 NXi autopilot.
  */
 class G1000Autopilot extends Autopilot {
     constructor() {
         super(...arguments);
         this.externalAutopilotInstalled = Subject.create(false);
         this.lateralArmedModeSubject = Subject.create(APLateralModes.NONE);
         this.altArmedSubject = Subject.create(false);
         this.altSelectManager = new AltitudeSelectManager(this.bus, G1000Autopilot.MIN_ALT_SELECT, G1000Autopilot.MAX_ALT_SELECT);
     }
     /** @inheritdoc */
     onAfterUpdate() {
         if (!this.externalAutopilotInstalled.get()) {
             this.updateFma();
         }
         else {
             this.lateralArmedModeSubject.set(this.lateralArmed);
             this.altArmedSubject.set(this.altCapArmed);
         }
         //this.updateFma();
         //PM Modif set a variable for captured altitude
         SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_CAPTURED_ALTITUDE", "feet", this.apValues.capturedAltitude.get());
     }
     /** @inheritdoc */
     onInitialized() {
         this.bus.pub('vnav_enabled', true);
         this.monitorAdditionalEvents();
     }
     /** @inheritdoc */
     monitorAdditionalEvents() {
         //check for KAP140 installed
         //PM Modif not used in our autopilot module. This is mode only for both G100Nxi and KAP140 in the cockpit
         // this.bus.getSubscriber<APEvents>().on('kap_140_installed').handle(v => {
         //   this.externalAutopilotInstalled.set(v);
         //   if (v) {
         //     this.config.defaultVerticalMode = APVerticalModes.VS;
         //     this.config.defaultLateralMode = APLateralModes.LEVEL;
         //     this.altSelectManager.setEnabled(false);
         //     this.handleApFdStateChange();
         //     this.updateFma(true);
         //     Fms.g1000EvtPub.publishEvent('fd_not_installed', true);
         //   }
         // });
     }
     /**
      * Publishes data for the FMA.
      * @param clear Is to clear the FMA
      */
     updateFma(clear = false) {
         const publisher = this.bus.getPublisher();
         const data = {
             verticalActive: clear ? APVerticalModes.NONE : this.verticalActive,
             verticalArmed: clear ? APVerticalModes.NONE : this.verticalArmed,
             verticalApproachArmed: clear ? APVerticalModes.NONE : this.verticalApproachArmed,
             verticalAltitudeArmed: clear ? APAltitudeModes.NONE : this.verticalAltitudeArmed,
             altitideCaptureArmed: clear ? false : this.altCapArmed,
             altitideCaptureValue: clear ? -1 : this.apValues.capturedAltitude.get(),
             lateralActive: clear ? APLateralModes.NONE : this.lateralActive,
             lateralArmed: clear ? APLateralModes.NONE : this.lateralArmed,
             lateralModeFailed: clear ? false : this.lateralModeFailed
         };
         publisher.pub('fma_modes', data, true);
     }
     //PM Modif added a way to block auto arming
     manageAltitudeCapture() {
         var _a, _b, _c;
         let altCapType = APAltitudeModes.NONE;
         let armAltCap = false;
         switch (this.verticalActive) {
             case APVerticalModes.VS:
             case APVerticalModes.FLC:
             case APVerticalModes.PITCH:
                 if (this.inClimb && this.apValues.selectedAltitude.get() > this.currentAltitude && this.enableAltArming) {
                     altCapType = APAltitudeModes.ALTS;
                     armAltCap = true;
                 }
                 else if (!this.inClimb && this.apValues.selectedAltitude.get() < this.currentAltitude && this.enableAltArming) {
                     altCapType = APAltitudeModes.ALTS;
                     armAltCap = true;
                 }
                 break;
             case APVerticalModes.VNAV: {
                 if (!this.inClimb) {
                     altCapType = this.vnavCaptureType === VNavAltCaptureType.VNAV ? APAltitudeModes.ALTV : APAltitudeModes.ALTS;
                 }
                 break;
             }
             case APVerticalModes.CAP:
                 altCapType = this.verticalAltitudeArmed;
                 break;
         }
         if (this.verticalAltitudeArmed !== altCapType) {
             this.verticalAltitudeArmed = altCapType;
         }
         //PM Modif
         // Block auto arming
         if (!this.enableAltArming)
             armAltCap = false;
         if (armAltCap && (!this.altCapArmed || ((_a = this.verticalModes.get(APVerticalModes.CAP)) === null || _a === void 0 ? void 0 : _a.state) === DirectorState.Inactive)) {
             (_b = this.verticalModes.get(APVerticalModes.CAP)) === null || _b === void 0 ? void 0 : _b.arm();
         }
         else if (!armAltCap && this.altCapArmed) {
             (_c = this.verticalModes.get(APVerticalModes.CAP)) === null || _c === void 0 ? void 0 : _c.deactivate();
             this.altCapArmed = false;
         }
     }
 }
 G1000Autopilot.MIN_ALT_SELECT = UnitType.FOOT.createNumber(-1000);
 G1000Autopilot.MAX_ALT_SELECT = UnitType.FOOT.createNumber(50000);
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A director that handles OBS Lateral Navigation.
  */
 class GpsObsDirector {
     /**
      * Creates an instance of the GPS OBS Director.
      * @param bus The event bus to use with this instance.
      * @param aircraftState Is the current aircraft state.
      */
     constructor(bus, aircraftState) {
         this.bus = bus;
         this.aircraftState = aircraftState;
         this.obsSetting = 0;
         this.obsActive = false;
         this.xtk = undefined;
         this.magvar = 0;
         this.obsLeg = Subject.create(undefined);
         this.geoPointCache = [new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.currentBankRef = 0;
         this.bankServo = new LinearServo(10);
         const hEvent = bus.getSubscriber();
         hEvent.on('hEvent').handle((e) => {
             if (e === 'AS1000_PFD_CRS_INC' || e === 'AS1000_MFD_CRS_INC') {
                 this.incrementObs(true);
             }
             else if (e === 'AS1000_PFD_CRS_DEC' || e === 'AS1000_MFD_CRS_DEC') {
                 this.incrementObs(false);
             }
         });
         const nav = this.bus.getSubscriber();
         nav.on('gps_obs_active').whenChanged().handle((state) => {
             var _a, _b;
             this.obsActive = state;
             if (this.obsActive) {
                 const dtk = (_b = (_a = this.obsLeg.get()) === null || _a === void 0 ? void 0 : _a.calculated) === null || _b === void 0 ? void 0 : _b.initialDtk;
                 if (dtk !== undefined) {
                     this.obsSetting = dtk;
                 }
                 else if (this.obsSetting < 0 || this.obsSetting > 360) {
                     this.obsSetting = 0;
                 }
                 SimVar.SetSimVarValue('K:GPS_OBS_SET', SimVarValueType.Degree, this.obsSetting);
             }
             if (!this.obsActive) {
                 this.deactivate();
             }
         });
         nav.on('gps_obs_value').whenChanged().handle((value) => {
             this.obsSetting = value;
         });
         const gnss = this.bus.getSubscriber();
         gnss.on('magvar').whenChanged().handle((v) => {
             this.magvar = v;
         });
         this.state = DirectorState.Inactive;
     }
     /**
      * Activates the LNAV director.
      */
     activate() {
         this.state = DirectorState.Active;
     }
     /**
      * Arms the LNAV director.
      */
     arm() {
         this.state = DirectorState.Armed;
     }
     /**
      * Deactivates the LNAV director.
      */
     deactivate() {
         if (this.onDeactivate !== undefined) {
             this.onDeactivate();
         }
         this.state = DirectorState.Inactive;
     }
     /**
      * Increments or Decrements the OBS Setting for GPS if in GPS OBS MODE.
      * @param increment is whether to increment (or decrement) the value.
      */
     incrementObs(increment) {
         if (this.obsActive) {
             if (increment) {
                 SimVar.SetSimVarValue('K:GPS_OBS_INC', SimVarValueType.Number, 0);
             }
             else {
                 SimVar.SetSimVarValue('K:GPS_OBS_DEC', SimVarValueType.Number, 0);
             }
         }
     }
     /**
      * Updates the lateral director.
      */
     update() {
         if (this.obsActive) {
             if (this.obsSetting >= 0 && this.obsSetting <= 360) {
                 SimVar.SetSimVarValue(LNavVars.DTK, 'degrees', Math.floor(this.obsSetting));
             }
             this.getObsXtk();
             if (this.xtk !== undefined) {
                 SimVar.SetSimVarValue(LNavVars.XTK, 'nautical miles', this.xtk);
             }
         }
         if (this.state === DirectorState.Active) {
             this.navigateFlightPath();
         }
     }
     /**
      * Gets the current obs xtk.
      */
     getObsXtk() {
         var _a, _b;
         const leg = this.obsLeg.get();
         if (((_a = leg === null || leg === void 0 ? void 0 : leg.calculated) === null || _a === void 0 ? void 0 : _a.endLat) !== undefined && ((_b = leg === null || leg === void 0 ? void 0 : leg.calculated) === null || _b === void 0 ? void 0 : _b.endLon) !== undefined) {
             const start = this.geoPointCache[0].set(leg.calculated.endLat, leg.calculated.endLon);
             const obsTrue = NavMath.normalizeHeading(this.obsSetting + this.magvar);
             const path = this.geoCircleCache[0].setAsGreatCircle(start, obsTrue);
             this.xtk = UnitType.GA_RADIAN.convertTo(path.distance(this.aircraftState.planePos), UnitType.NMILE);
         }
         else {
             this.xtk = undefined;
         }
     }
     /**
      * Navigates the provided leg flight path.
      */
     navigateFlightPath() {
         if (this.xtk === undefined) {
             this.deactivate();
             return;
         }
         const absInterceptAngle = Math.min(Math.pow(Math.abs(this.xtk) * 20, 1.35) + (Math.abs(this.xtk) * 50), 45);
         const interceptAngle = this.xtk < 0 ? absInterceptAngle : -1 * absInterceptAngle;
         const obsTrue = NavMath.normalizeHeading(this.obsSetting + this.magvar);
         const bankAngle = this.desiredBank(NavMath.normalizeHeading(obsTrue + interceptAngle), this.xtk);
         if (this.state === DirectorState.Active) {
             this.setBank(bankAngle);
         }
     }
     /**
      * Tries to activate when armed.
      * @returns whether OBS can activate
      */
     canActivate() {
         if (this.xtk !== undefined && Math.abs(this.xtk) < 1) {
             return true;
         }
         return false;
     }
     /**
      * Gets a desired bank from a desired track.
      * @param desiredTrack The desired track.
      * @param xtk The cross track.
      * @returns The desired bank angle.
      */
     desiredBank(desiredTrack, xtk) {
         const turnDirection = NavMath.getTurnDirection(this.aircraftState.track, desiredTrack);
         const headingDiff = Math.abs(NavMath.diffAngle(this.aircraftState.track, desiredTrack));
         let baseBank = Math.min(1.25 * headingDiff, 25);
         if (baseBank <= 2.5) {
             baseBank = NavMath.clamp(xtk * 100, -2.5, 2.5);
         }
         else {
             baseBank *= (turnDirection === 'left' ? 1 : -1);
         }
         return baseBank;
     }
     /**
      * Sets the desired AP bank angle.
      * @param bankAngle The desired AP bank angle.
      */
     setBank(bankAngle) {
         if (isFinite(bankAngle)) {
             this.currentBankRef = this.bankServo.drive(this.currentBankRef, bankAngle);
             SimVar.SetSimVarValue('AUTOPILOT BANK HOLD REF', 'degrees', this.currentBankRef);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 var TransitionMode;
 (function (TransitionMode) {
     TransitionMode[TransitionMode["None"] = 0] = "None";
     TransitionMode[TransitionMode["Ingress"] = 1] = "Ingress";
     TransitionMode[TransitionMode["Egress"] = 2] = "Egress";
 })(TransitionMode || (TransitionMode = {}));
 /**
  * A class that handles lateral navigation.
  */
 class LNavDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param flightPlanner The flight planner to use with this instance.
      */
     //PM Modif
     constructor(bus, apValues, flightPlanner, lateralInterceptCurve, hasVectorAnticipation) {
         this.bus = bus;
         this.apValues = apValues;
         this.flightPlanner = flightPlanner;
         this.lateralInterceptCurve = lateralInterceptCurve;
         this.hasVectorAnticipation = hasVectorAnticipation;
         this.vec3Cache = [new Float64Array(3), new Float64Array(3)];
         this.geoPointCache = [new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.previousLegIndex = 0;
         /** The current active leg index. */
         this.currentLegIndex = 0;
         /** The current flight path vector index. */
         this.currentVectorIndex = 0;
         this.currentLeg = undefined;
         this.dtk = 0;
         this.xtk = 0;
         //PM Modif
         this.alongVectorDistance = 0;
         this.vectorDistanceRemaining = 0;
         this.vectorAnticipationDistance = 0;
         this.transitionMode = TransitionMode.None;
         this.isSuspended = false;
         this.suspendedLegIndex = 0;
         this.resetVectorsOnSuspendEnd = false;
         this.inhibitNextSequence = false;
         this.missedApproachActive = false;
         this.currentBankRef = 0;
         this.arcController = new ArcTurnController();
         this.bankServo = new LinearServo(10);
         this.lnavData = {
             currentLegIndex: -1,
             nextLegIndex: -1,
             vectorIndex: 0,
             transitionMode: TransitionMode.None,
             courseToSteer: 0,
             isSuspended: false
         };
         this.bearingToEnd = 0;
         this.canArm = false;
         this.trackAtActivation = 0;
         this.isInterceptingFromArmedState = false;
         this.aircraftState = {
             tas: 0,
             track: 0,
             magvar: 0,
             windSpeed: 0,
             windDirection: 0,
             planePos: new GeoPoint(0, 0),
             hdgTrue: 0
         };
         this.obsDirector = new GpsObsDirector(this.bus, this.aircraftState);
         this.obsDirector.onDeactivate = () => {
             this.isSuspended = false;
             SimVar.SetSimVarValue(LNavVars.DTK, 'degrees', this.dtk);
         };
         const adc = bus.getSubscriber();
         const controls = bus.getSubscriber();
         const plan = bus.getSubscriber();
         this.publisher = bus.getPublisher();
         adc.on('ambient_wind_velocity').handle(w => this.aircraftState.windSpeed = w);
         adc.on('ambient_wind_direction').handle(wd => this.aircraftState.windDirection = wd);
         adc.on('tas').handle(tas => this.aircraftState.tas = tas);
         adc.on('hdg_deg_true').handle(hdg => this.aircraftState.hdgTrue = hdg);
         const nav = this.bus.getSubscriber();
         nav.on('cdi_select').handle((src) => {
             if (this.state !== DirectorState.Inactive && src.type !== NavSourceType.Gps) {
                 this.deactivate();
             }
         });
         controls.on('suspend_sequencing').handle(() => {
             this.trySetSuspended(false);
         });
         controls.on('activate_missed_approach').handle((v) => {
             this.missedApproachActive = v;
         });
         controls.on('lnav_inhibit_next_sequence').handle(inhibit => {
             this.inhibitNextSequence = inhibit;
             if (inhibit) {
                 this.suspendedLegIndex = 0;
             }
         });
         plan.on('fplActiveLegChange').handle(e => {
             if (e.type === ActiveLegType.Lateral) {
                 this.currentVectorIndex = 0;
                 this.transitionMode = TransitionMode.Ingress;
                 this.inhibitNextSequence = false;
             }
         });
         plan.on('fplIndexChanged').handle(() => {
             this.currentVectorIndex = 0;
             this.transitionMode = TransitionMode.Ingress;
             this.inhibitNextSequence = false;
         });
         plan.on('fplSegmentChange').handle((e) => {
             if (e.type === PlanChangeType.Removed && (!this.flightPlanner.hasActiveFlightPlan() || this.flightPlanner.getActiveFlightPlan().length < 1)) {
                 this.lnavData.vectorIndex = 0;
                 this.lnavData.transitionMode = TransitionMode.None;
                 this.lnavData.currentLegIndex = -1;
                 this.lnavData.nextLegIndex = -1;
             }
         });
         const gps = bus.getSubscriber();
         gps.on('gps-position').handle(lla => {
             this.aircraftState.planePos.set(lla.lat, lla.long);
         });
         gps.on('track_deg_true').handle(t => this.aircraftState.track = t);
         gps.on('magvar').handle(m => this.aircraftState.magvar = m);
         this.state = DirectorState.Inactive;
     }
     /**
      * Activates the LNAV director.
      */
     activate() {
         this.isInterceptingFromArmedState = true;
         this.trackAtActivation = this.aircraftState.track;
         this.state = DirectorState.Active;
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', true);
     }
     /**
      * Arms the LNAV director.
      */
     arm() {
         if (this.state === DirectorState.Inactive) {
             this.isInterceptingFromArmedState = false;
             if (this.canArm) {
                 this.state = DirectorState.Armed;
                 if (this.onArm !== undefined) {
                     this.onArm();
                 }
                 SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', true);
             }
         }
     }
     /**
      * Deactivates the LNAV director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         if (this.obsDirector.state !== DirectorState.Inactive) {
             this.obsDirector.deactivate();
         }
         this.isInterceptingFromArmedState = false;
         SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', false);
     }
     /**
      * Updates the lateral director.
      */
     update() {
         var _a, _b;
         let clearInhibitNextSequence = true;
         const flightPlan = this.flightPlanner.hasActiveFlightPlan() ? this.flightPlanner.getActiveFlightPlan() : undefined;
         this.currentLegIndex = flightPlan ? flightPlan.activeLateralLeg : 0;
         if (flightPlan && this.currentLegIndex <= flightPlan.length - 1) {
             this.currentLeg = flightPlan.getLeg(this.currentLegIndex);
             let nextLeg = undefined;
             try {
                 nextLeg = flightPlan.getLeg(this.currentLegIndex + 1);
             }
             catch ( /* Continue */_c) { /* Continue */ }
             // We don't want to clear the inhibit next sequence flag until the active leg has been calculated
             // since we never sequence through non-calculated legs.
             clearInhibitNextSequence = !!this.currentLeg.calculated;
             this.calculateTracking();
             if (this.currentLegIndex > flightPlan.length - 1) {
                 return;
             }
             try {
                 nextLeg = flightPlan.getLeg(this.currentLegIndex + 1);
             }
             catch ( /* Continue */_d) { /* Continue */ }
             const calcs = this.currentLeg.calculated;
             this.calculateNextTracking(nextLeg === null || nextLeg === void 0 ? void 0 : nextLeg.calculated);
             this.lnavData.currentLegIndex = this.currentLegIndex;
             this.lnavData.nextLegIndex = nextLeg ? this.currentLegIndex + 1 : -1;
             this.lnavData.vectorIndex = this.currentVectorIndex;
             this.lnavData.transitionMode = this.transitionMode;
             this.lnavData.isSuspended = this.isSuspended;
             this.obsDirector.obsLeg.set(this.currentLeg);
             if (this.obsDirector.obsActive) {
                 this.isSuspended = true;
                 this.suspendedLegIndex = this.currentLegIndex;
                 if (this.state === DirectorState.Active && this.obsDirector.state !== DirectorState.Active) {
                     this.obsDirector.activate();
                     SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', true);
                 }
                 if (this.state === DirectorState.Armed && this.obsDirector.canActivate()) {
                     this.obsDirector.activate();
                     this.state = DirectorState.Active;
                     if (this.onActivate !== undefined) {
                         this.onActivate();
                     }
                     SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', true);
                 }
                 if (this.currentLeg && ((_a = this.currentLeg.calculated) === null || _a === void 0 ? void 0 : _a.endLat) && ((_b = this.currentLeg.calculated) === null || _b === void 0 ? void 0 : _b.endLon)) {
                     const planePos = this.aircraftState.planePos;
                     this.bearingToEnd = MagVar.trueToMagnetic(planePos.bearingTo(this.currentLeg.calculated.endLat, this.currentLeg.calculated.endLon), planePos);
                     SimVar.SetSimVarValue(LNavVars.Bearing, 'degrees', this.bearingToEnd);
                     const distance = planePos.distance(this.currentLeg.calculated.endLat, this.currentLeg.calculated.endLon);
                     SimVar.SetSimVarValue(LNavVars.Distance, 'meters', UnitType.GA_RADIAN.convertTo(distance, UnitType.METER));
                 }
                 this.obsDirector.update();
                 return;
             }
             if (calcs !== undefined) {
                 this.canArm = true;
             }
             else {
                 this.canArm = false;
             }
             if (this.state !== DirectorState.Inactive && calcs !== undefined) {
                 this.navigateFlightPath(calcs);
             }
         }
         else {
             this.currentLeg = undefined;
             this.canArm = false;
         }
         if (this.state === DirectorState.Armed) {
             this.tryActivate();
         }
         this.publisher.pub('dataChanged', this.lnavData, true);
         this.inhibitNextSequence && (this.inhibitNextSequence = !clearInhibitNextSequence);
     }
     /**
      * Calculates the tracking information for the next leg.
      * @param calcs The leg calculations for the next leg.
      */
     calculateNextTracking(calcs) {
         var _a;
         if (calcs !== undefined) {
             const vector = calcs.flightPath[0];
             if (vector !== undefined) {
                 SimVar.SetSimVarValue(LNavVars.NextDTK, 'degrees', (_a = calcs.initialDtk) !== null && _a !== void 0 ? _a : 0);
                 const circle = FlightPathUtils.setGeoCircleFromVector(vector, this.geoCircleCache[0]);
                 SimVar.SetSimVarValue(LNavVars.NextXTK, 'nautical miles', UnitType.GA_RADIAN.convertTo(circle.distance(this.aircraftState.planePos), UnitType.NMILE));
             }
         }
     }
     /**
      * Navigates the provided leg flight path.
      * @param calcs The legs calculations that has the provided flight path.
      */
     navigateFlightPath(calcs) {
         let absInterceptAngle;
         //PM Modif
         let naturalAbsInterceptAngle = 0;
         if (this.lateralInterceptCurve !== undefined) {
             naturalAbsInterceptAngle = this.lateralInterceptCurve(this.dtk, this.xtk, this.aircraftState.tas);
         }
         else {
             naturalAbsInterceptAngle = Math.min(Math.pow(Math.abs(this.xtk) * 20, 1.35) + (Math.abs(this.xtk) * 50), 45);
             if (naturalAbsInterceptAngle <= 2.5) {
                 naturalAbsInterceptAngle = NavMath.clamp(Math.abs(this.xtk * 150), 0, 2.5);
             }
         }
         if (this.isInterceptingFromArmedState) {
             absInterceptAngle = Math.abs(NavMath.diffAngle(this.trackAtActivation, this.dtk));
             if (absInterceptAngle > naturalAbsInterceptAngle || absInterceptAngle < 5 || absInterceptAngle < Math.abs(NavMath.diffAngle(this.dtk, this.bearingToEnd))) {
                 absInterceptAngle = naturalAbsInterceptAngle;
                 this.isInterceptingFromArmedState = false;
             }
         }
         else {
             absInterceptAngle = naturalAbsInterceptAngle;
         }
         const interceptAngle = this.xtk < 0 ? absInterceptAngle : -1 * absInterceptAngle;
         this.lnavData.courseToSteer = NavMath.normalizeHeading(this.dtk + interceptAngle);
         let bankAngle = this.desiredBank(this.lnavData.courseToSteer);
         const vector = LNavDirector.getVectorsForTransitionMode(calcs, this.transitionMode, this.isSuspended)[this.currentVectorIndex];
         if (vector !== undefined && !FlightPathUtils.isVectorGreatCircle(vector)) {
             bankAngle = this.adjustBankAngleForArc(vector, bankAngle);
         }
         if (this.state === DirectorState.Active) {
             this.setBank(bankAngle);
         }
     }
     /**
      * Adjusts the desired bank angle for arc vectors.
      * @param vector The arc vector to adjust for.
      * @param bankAngle The current starting input desired bank angle.
      * @returns The adjusted bank angle.
      */
     adjustBankAngleForArc(vector, bankAngle) {
         const circle = FlightPathUtils.setGeoCircleFromVector(vector, this.geoCircleCache[0]);
         const turnDirection = FlightPathUtils.getTurnDirectionFromCircle(circle);
         const radius = UnitType.GA_RADIAN.convertTo(FlightPathUtils.getTurnRadiusFromCircle(circle), UnitType.METER);
         const relativeWindHeading = NavMath.normalizeHeading(this.aircraftState.windDirection - this.aircraftState.hdgTrue);
         const headwind = this.aircraftState.windSpeed * Math.cos(relativeWindHeading * Avionics.Utils.DEG2RAD);
         const distance = UnitType.GA_RADIAN.convertTo(circle.distance(this.aircraftState.planePos), UnitType.METER);
         const bankAdjustment = this.arcController.getOutput(distance);
         const turnBankAngle = NavMath.bankAngle(this.aircraftState.tas - headwind, radius) * (turnDirection === 'left' ? 1 : -1);
         const turnRadius = NavMath.turnRadius(this.aircraftState.tas - headwind, 25);
         const bankBlendFactor = Math.max(1 - (Math.abs(UnitType.NMILE.convertTo(this.xtk, UnitType.METER)) / turnRadius), 0);
         bankAngle = (bankAngle * (1 - bankBlendFactor)) + (turnBankAngle * bankBlendFactor) + bankAdjustment;
         //PM Modif
         bankAngle = Math.min(Math.max(bankAngle, -25), 25);
         return bankAngle;
     }
     /**
      * Sets the desired AP bank angle.
      * @param bankAngle The desired AP bank angle.
      */
     setBank(bankAngle) {
         if (isFinite(bankAngle)) {
             this.currentBankRef = this.bankServo.drive(this.currentBankRef, bankAngle);
             SimVar.SetSimVarValue('AUTOPILOT BANK HOLD REF', 'degrees', this.currentBankRef);
         }
     }
     /**
      * Gets a desired bank from a desired track.
      * @param desiredTrack The desired track.
      * @returns The desired bank angle.
      */
     desiredBank(desiredTrack) {
         const turnDirection = NavMath.getTurnDirection(this.aircraftState.track, desiredTrack);
         const headingDiff = Math.abs(NavMath.diffAngle(this.aircraftState.track, desiredTrack));
         //PM Modif
         let baseBank = Math.min(1.25 * headingDiff, 25);
         baseBank *= (turnDirection === 'left' ? 1 : -1);
         return baseBank;
     }
     /**
      * Calculates the tracking from the current leg.
      */
     //PM Modif
     calculateTracking() {
         const plan = this.flightPlanner.getActiveFlightPlan();
         let didAdvance;
         do {
             didAdvance = false;
             if (!this.currentLeg) {
                 break;
             }
             //Don't really need to fly the intial leg?
             if (this.currentLeg.leg.type === LegType.IF && this.currentLegIndex === 0 && plan.length > 1) {
                 this.currentLeg = plan.getLeg(++this.currentLegIndex);
                 plan.setLateralLeg(this.currentLegIndex);
                 plan.setCalculatingLeg(this.currentLegIndex);
                 //        didAdvance = true;
                 continue;
             }
             this.transitionMode;
             this.currentLegIndex;
             this.currentVectorIndex;
             this.isSuspended;
             const calcs = this.currentLeg.calculated;
             if (calcs) {
                 const vectors = LNavDirector.getVectorsForTransitionMode(calcs, this.transitionMode, this.isSuspended);
                 const vector = vectors[this.currentVectorIndex];
                 if (vector && vector.radius > 0) {
                     const planePos = this.aircraftState.planePos;
                     const circle = FlightPathUtils.setGeoCircleFromVector(vector, this.geoCircleCache[0]);
                     const start = GeoPoint.sphericalToCartesian(vector.startLat, vector.startLon, this.vec3Cache[0]);
                     const end = GeoPoint.sphericalToCartesian(vector.endLat, vector.endLon, this.vec3Cache[1]);
                     this.xtk = UnitType.GA_RADIAN.convertTo(circle.distance(planePos), UnitType.NMILE);
                     this.dtk = circle.bearingAt(planePos, Math.PI);
                     this.bearingToEnd = MagVar.trueToMagnetic(planePos.bearingTo(vector.endLat, vector.endLon), planePos);
                     SimVar.SetSimVarValue(LNavVars.Bearing, 'degrees', this.bearingToEnd);
                     const normDist = FlightPathUtils.getAlongArcNormalizedDistance(circle, start, end, planePos);
                     const vectorDistanceNM = UnitType.METER.convertTo(vector.distance, UnitType.NMILE);
                     this.alongVectorDistance = normDist * vectorDistanceNM;
                     this.vectorDistanceRemaining = (1 - normDist) * vectorDistanceNM;
                     if (this.hasVectorAnticipation) {
                         const rollTimeSeconds = (this.apValues.maxBankAngle.get() + Math.abs(this.currentBankRef)) / 5;
                         this.vectorAnticipationDistance = UnitType.SECOND.convertTo(rollTimeSeconds, UnitType.HOUR) * this.aircraftState.gs;
                     }
                     if (normDist > 1 || (this.hasVectorAnticipation && this.vectorDistanceRemaining < this.vectorAnticipationDistance)) {
                         didAdvance = this.advanceVector(this.currentLeg);
                         //            this.advanceVector(this.currentLeg);
                     }
                 }
                 else {
                     this.alongVectorDistance = 0;
                     this.vectorDistanceRemaining = 0;
                     this.vectorAnticipationDistance = 0;
                     didAdvance = this.advanceVector(this.currentLeg);
                     //          this.advanceVector(this.currentLeg);
                 }
                 // didAdvance = transitionMode !== this.transitionMode
                 //   || legIndex !== this.currentLegIndex
                 //   || vectorIndex !== this.currentVectorIndex
                 //   || isSuspended !== this.isSuspended;
             }
         } while (didAdvance && this.currentLegIndex <= plan.length - 1);
         this.lnavData.courseToSteer = this.dtk;
     }
     /**
      * Applies suspends that apply at the end of a leg.
      */
     applyEndOfLegSuspends() {
         const plan = this.flightPlanner.getActiveFlightPlan();
         const leg = plan.getLeg(plan.activeLateralLeg);
         if (leg.leg.type === LegType.FM || leg.leg.type === LegType.VM || this.inhibitNextSequence) {
             this.trySetSuspended(true, this.inhibitNextSequence);
         }
         else if (plan.activeLateralLeg < plan.length - 1) {
             const nextLeg = plan.getLeg(plan.activeLateralLeg + 1);
             if (!this.missedApproachActive
                 && (leg.leg.fixTypeFlags === FixTypeFlags.MAP
                     || (!BitFlags.isAll(leg.flags, LegDefinitionFlags.MissedApproach) && BitFlags.isAll(nextLeg.flags, LegDefinitionFlags.MissedApproach)))) {
                 this.trySetSuspended(true);
             }
         }
     }
     /**
      * Applies suspends that apply at the beginning of a leg.
      */
     applyStartOfLegSuspends() {
         const plan = this.flightPlanner.getActiveFlightPlan();
         const leg = plan.getLeg(plan.activeLateralLeg);
         if (leg.leg.type === LegType.HM || plan.activeLateralLeg === plan.length - 1) {
             this.trySetSuspended(true);
         }
     }
     /**
      * Advances the current flight path vector along the flight path.
      * @param leg The definition of the leg being flown.
      * @returns Whether the current flight path vector was advanced.
      */
     advanceVector(leg) {
         var _a;
         let didAdvance = false;
         const plan = this.flightPlanner.getActiveFlightPlan();
         this.currentVectorIndex++;
         const calcs = leg.calculated;
         let vectors = calcs ? LNavDirector.getVectorsForTransitionMode(calcs, this.transitionMode, this.isSuspended) : undefined;
         while (!vectors || this.currentVectorIndex >= vectors.length) {
             switch (this.transitionMode) {
                 case TransitionMode.Ingress:
                     this.arcController.reset();
                     this.transitionMode = TransitionMode.None;
                     vectors = calcs ? LNavDirector.getVectorsForTransitionMode(calcs, this.transitionMode, this.isSuspended) : undefined;
                     this.currentVectorIndex = Math.max(0, this.isSuspended ? (_a = calcs === null || calcs === void 0 ? void 0 : calcs.ingressJoinIndex) !== null && _a !== void 0 ? _a : 0 : 0);
                     didAdvance = true;
                     break;
                 case TransitionMode.None:
                     if (!this.isSuspended) {
                         plan.setCalculatingLeg(this.currentLegIndex + 1);
                         this.transitionMode = TransitionMode.Egress;
                         vectors = calcs ? LNavDirector.getVectorsForTransitionMode(calcs, this.transitionMode, this.isSuspended) : undefined;
                         this.currentVectorIndex = 0;
                         didAdvance = true;
                     }
                     else if (leg.leg.type === LegType.HM) {
                         vectors = calcs === null || calcs === void 0 ? void 0 : calcs.flightPath;
                         this.currentVectorIndex = 0;
                         didAdvance = true;
                     }
                     else {
                         return didAdvance;
                     }
                     break;
                 case TransitionMode.Egress:
                     return this.advanceEgressToIngress();
             }
         }
         return didAdvance;
     }
     /**
      * Advances the current flight plan leg to the next leg.
      * @returns Whether the leg was advanced.
      */
     advanceEgressToIngress() {
         const plan = this.flightPlanner.getActiveFlightPlan();
         this.applyEndOfLegSuspends();
         if (!this.isSuspended) {
             if (this.currentLegIndex + 1 >= plan.length) {
                 this.transitionMode = TransitionMode.None;
                 return false;
             }
             this.currentLeg = plan.getLeg(++this.currentLegIndex);
             this.transitionMode = TransitionMode.Ingress;
             this.currentVectorIndex = 0;
             this.suspendedLegIndex = 0;
             plan.setLateralLeg(this.currentLegIndex);
             plan.setCalculatingLeg(this.currentLegIndex);
             this.applyStartOfLegSuspends();
             return true;
         }
         else {
             return false;
         }
     }
     /**
      * Sets flight plan advance in or out of SUSP.
      * @param isSuspended Whether or not advance is suspended.
      * @param resetVectorsOnSuspendEnd Whether to reset the tracked vector to the beginning of the leg when the applied
      * suspend ends. Ignored if `isSuspended` is false. Defaults to false.
      */
     trySetSuspended(isSuspended, resetVectorsOnSuspendEnd) {
         var _a;
         if (isSuspended && this.currentLegIndex === this.suspendedLegIndex) {
             return;
         }
         if (isSuspended) {
             this.suspendedLegIndex = this.currentLegIndex;
             this.resetVectorsOnSuspendEnd = resetVectorsOnSuspendEnd !== null && resetVectorsOnSuspendEnd !== void 0 ? resetVectorsOnSuspendEnd : false;
         }
         if (this.isSuspended !== isSuspended) {
             this.publisher.pub('suspChanged', isSuspended, true, true);
             this.isSuspended = isSuspended;
             if (!isSuspended && this.resetVectorsOnSuspendEnd) {
                 this.transitionMode = TransitionMode.Ingress;
                 this.currentVectorIndex = 0;
                 this.resetVectorsOnSuspendEnd = false;
             }
             else {
                 const legCalc = (_a = this.currentLeg) === null || _a === void 0 ? void 0 : _a.calculated;
                 const ingressJoinVector = legCalc === null || legCalc === void 0 ? void 0 : legCalc.flightPath[legCalc.ingressJoinIndex];
                 if (legCalc && this.transitionMode === TransitionMode.None && legCalc.ingressJoinIndex >= 0 && ingressJoinVector && legCalc.ingress.length > 0) {
                     // reconcile vector indexes
                     const vectors = isSuspended ? legCalc.flightPath : legCalc.ingressToEgress;
                     const lastIngressVector = legCalc.ingress[legCalc.ingress.length - 1];
                     const offset = (this.geoPointCache[0].set(lastIngressVector.endLat, lastIngressVector.endLon).equals(ingressJoinVector.endLat, ingressJoinVector.endLon)
                         ? 2
                         : 1) * (isSuspended ? 1 : -1);
                     // Not using Utils.Clamp() because I need it to clamp to >=0 last.
                     this.currentVectorIndex = Math.max(0, Math.min(this.currentVectorIndex + offset, vectors.length - 1));
                 }
             }
         }
     }
     /**
      * Tries to activate when armed.
      */
     tryActivate() {
         const headingDiff = NavMath.diffAngle(this.aircraftState.track, this.dtk);
         if (Math.abs(this.xtk) < 0.6 && Math.abs(headingDiff) < 110) {
             this.activate();
         }
     }
     /**
      * Gets the flight path vectors to navigate for a leg and a given transition mode.
      * @param calc The calculations for a flight plan leg.
      * @param mode A transition mode.
      * @param isSuspended Whether sequencing is suspended.
      * @returns The flight path vectors to navigate for the given leg and transition mode.
      */
     static getVectorsForTransitionMode(calc, mode, isSuspended) {
         switch (mode) {
             case TransitionMode.None:
                 return isSuspended ? calc.flightPath : calc.ingressToEgress;
             case TransitionMode.Ingress:
                 return calc.ingress;
             case TransitionMode.Egress:
                 return calc.egress;
         }
     }
 }
 
 /**
  * A VNAV path autopilot director.
  */
 class VNavDirector {
     /**
      * Creates an instance of the VNAV director.
      * @param bus The event bus to use with this instance.
      * @param flightPlanner The flight planner to use with this instance.
      * @param calculator The VNAV path calculator to use with this instance.
      * @param apValues are the autopilot ap values.
      */
     constructor(bus, flightPlanner, calculator, apValues) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.calculator = calculator;
         this.apValues = apValues;
         this._mode = VNavMode.Disabled;
         this._pathMode = VNavPathMode.None;
         this.state = DirectorState.Inactive;
         this.approachDetails = {
             approachLoaded: false,
             approachType: ApproachType.APPROACH_TYPE_UNKNOWN,
             approachRnavType: RnavTypeFlags.None,
             approachIsActive: false,
             approachIsCircling: false
         };
         this.vec3Cache = [new Float64Array(3), new Float64Array(3), new Float64Array(3), new Float64Array(3)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.planePos = new GeoPoint(0, 0);
         this.currentAltitude = 0;
         this.currentGpsAltitude = 0;
         this.preselectedAltitude = 0;
         this.currentGroundSpeed = 0;
         this.isVNavUnavailable = false;
         this.capturedAltitude = 0;
         this.isAltCaptured = false;
         this.awaitingAltCap = -1;
         this.awaitingRearm = -1;
         /** The leg distance from the current leg to the constraint leg, not including the distance from ppos to the current leg target. */
         this.constraintDistance = -1;
         this.lpvDeviation = Subject.create(0);
         this.bus.getSubscriber().on('dataChanged').handle(data => this.lnavData = data);
         this.bus.getSubscriber().on('alt_select').handle(selected => this.preselectedAltitude = selected);
         this.bus.getSubscriber().on('approach_details_set').handle(d => {
             this.approachDetails = d;
             this.apValues.approachIsActive.set(d.approachLoaded && d.approachIsActive);
         });
         this.bus.getSubscriber().on('alt').handle(alt => this.currentAltitude = alt);
         const gnss = this.bus.getSubscriber();
         gnss.on('gps-position').handle(lla => {
             this.planePos.set(lla.lat, lla.long);
             this.currentGpsAltitude = UnitType.METER.convertTo(lla.alt, UnitType.FOOT);
         });
         gnss.on('ground_speed').handle(gs => this.currentGroundSpeed = gs);
         this.bus.getSubscriber().on('vnav_enabled').handle(d => {
             if (d) {
                 this.enable();
             }
             else {
                 this.disable();
             }
         });
         this.apValues.verticalActive.sub(mode => {
             if (mode === APVerticalModes.ALT && this.awaitingAltCap !== -1) {
                 this.awaitingRearm = this.awaitingAltCap;
                 this.awaitingAltCap = -1;
             }
             if (this.awaitingRearm > -1 && mode !== APVerticalModes.ALT && mode !== APVerticalModes.CAP) {
                 this.awaitingRearm = -1;
                 this.awaitingAltCap = -1;
             }
         });
         this.apValues.lateralActive.sub(mode => {
             if (mode === APLateralModes.LOC && this.pathMode === VNavPathMode.PathArmed) {
                 this.awaitingAltCap = -1;
                 this.awaitingRearm = -1;
                 this.deactivate();
             }
         });
         SimVar.SetSimVarValue(VNavSimVars.TODDistance, SimVarValueType.Number, Number.MAX_SAFE_INTEGER);
         SimVar.SetSimVarValue(VNavSimVars.LPVVerticalDeviation, SimVarValueType.Number, Number.MAX_SAFE_INTEGER);
         SimVar.SetSimVarValue(VNavSimVars.PathMode, SimVarValueType.Number, VNavPathMode.None);
     }
     /**
      * Setter for pathMode that also sets the simvar
      * @param pathMode is the VNavPathMode to set.
      */
     set pathMode(pathMode) {
         if (pathMode !== this._pathMode) {
             this._pathMode = pathMode;
             SimVar.SetSimVarValue(VNavSimVars.PathMode, SimVarValueType.Number, this._pathMode);
         }
         if (this._pathMode === VNavPathMode.PathArmed) {
             this.state = DirectorState.Armed;
             this.isAltCaptured = false;
             this.onArm && this.onArm();
         }
         if (this._pathMode === VNavPathMode.PathActive) {
             this.state = DirectorState.Active;
             this.onActivate && this.onActivate();
         }
     }
     /**
      * Getter for pathMode that also sets the simvar
      * @returns the current VNavPathMode.
      */
     get pathMode() {
         return this._pathMode;
     }
     /**
      * Setter for mode that also sets the simvar
      * @param mode is the mode to set.
      */
     set mode(mode) {
         if (this._mode !== mode) {
             this._mode = mode;
             SimVar.SetSimVarValue(VNavSimVars.VNAVMode, SimVarValueType.Number, this._mode);
         }
     }
     /**
      * Getter for Vnav Mode
      * @returns the current Vnav Mode.
      */
     get mode() {
         return this._mode;
     }
     /**
      * Activates the VNAV director's calculations.
      * Does NOT enable any path following.
      */
     enable() {
         this.mode = VNavMode.Enabled;
         this.pathMode = VNavPathMode.None;
         this.state = DirectorState.Inactive;
     }
     /**
      * Deactivates the VNAV director's calculations.
      */
     disable() {
         this.mode = VNavMode.Disabled;
         this.deactivate();
     }
     /**
      * Activates the director.
      * We do not use this method in vnav director.
      * @throws an error if someone calls this method.
      */
     activate() {
         throw new Error('Activate Method in VNAV Director not supported.');
     }
     /**
      * Arms the VNAV Director.
      * This is called by the Autopilot when the VNAV button is pressed to an on state.
      */
     arm() {
         this.awaitingAltCap = -1;
         this.awaitingRearm = -1;
         if (this.mode === VNavMode.Enabled) {
             this.state = DirectorState.Armed;
             this.pathMode = VNavPathMode.None;
         }
     }
     /**
      * Deactivates the VNAV director.
      * This is called by the Autopilot when the VNAV button is pressed to an off state or when GP/GS activates.
      * This is called by the VNAV Director at the end of the path or if there is an invalid path or other error.
      */
     deactivate() {
         if (this.state !== DirectorState.Inactive) {
             this.pathMode = VNavPathMode.None;
             this.state = DirectorState.Inactive;
             this.isAltCaptured = false;
             if (this.awaitingAltCap === -1 && this.awaitingRearm === -1) {
                 SimVar.SetSimVarValue('L:XMLVAR_VNAVButtonValue', 'Bool', 0);
             }
         }
     }
     /**
      * Method to call when VNAV Encounters a failed state.
      */
     failed() {
         if (this.state === DirectorState.Active) {
             this.state = DirectorState.Inactive;
             if (!this.isAltCaptured) {
                 this.apValues.capturedAltitude.set(this.currentAltitude);
             }
             this.onDeactivate && this.onDeactivate();
         }
         else {
             this.deactivate();
         }
     }
     /**
      * Method called to delegate altitude capture to the Alt Cap Director.
      */
     onDelegateAltCap() {
         this.onDeactivate && this.onDeactivate();
     }
     /**
      * Updates the VNAV director.
      */
     update() {
         var _a;
         let alongLegDistance = -1;
         if (this.lnavData !== undefined && this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX)) {
             const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
             let desiredAltFeet = Number.POSITIVE_INFINITY;
             let targetAltitudeFeet;
             let verticalDeviation = Number.MAX_SAFE_INTEGER;
             let requiredVs = 0;
             alongLegDistance = this.getAlongLegDistance(this.lnavData);
             const nextLeg = this.lnavData.nextLegIndex >= 0 && this.lnavData.nextLegIndex < plan.length ? plan.getLeg(this.lnavData.nextLegIndex) : undefined;
             if (nextLeg && plan.activeLateralLeg < plan.length) {
                 const todBodDetails = this.calculator.todBodDetails(plan.activeLateralLeg, alongLegDistance);
                 SimVar.SetSimVarValue(VNavSimVars.TODDistance, SimVarValueType.Number, todBodDetails.distanceFromTod);
                 SimVar.SetSimVarValue(VNavSimVars.TODLegIndex, SimVarValueType.Number, todBodDetails.todLegIndex);
                 SimVar.SetSimVarValue(VNavSimVars.TODDistanceInLeg, SimVarValueType.Number, todBodDetails.distanceFromLegEnd);
                 SimVar.SetSimVarValue(VNavSimVars.BODLegIndex, SimVarValueType.Number, todBodDetails.bodLegIndex);
                 SimVar.SetSimVarValue(VNavSimVars.BODDistance, SimVarValueType.Number, todBodDetails.distanceFromBod);
                 SimVar.SetSimVarValue(VNavSimVars.CurrentConstraintLegIndex, SimVarValueType.Number, todBodDetails.currentConstraintIndex);
                 const nextConstraintAltitude = this.calculator.getNextConstraintAltitude(plan.activeLateralLeg);
                 if (nextConstraintAltitude === undefined) {
                     this.bus.getPublisher().pub('vnavNextConstraintAltitude', -1);
                 }
                 else {
                     this.bus.getPublisher().pub('vnavNextConstraintAltitude', UnitType.METER.convertTo(nextConstraintAltitude, UnitType.FOOT));
                 }
                 const constraintAltitude = this.calculator.getCurrentConstraintAltitude(plan.activeLateralLeg);
                 if (nextConstraintAltitude !== constraintAltitude) {
                     this.setVNavUnavailable(true);
                 }
                 else {
                     this.setVNavUnavailable(false);
                 }
                 const simvarAltitudeSet = constraintAltitude !== undefined ? UnitType.METER.convertTo(constraintAltitude, UnitType.FOOT) : -1;
                 SimVar.SetSimVarValue(VNavSimVars.CurrentConstraintAltitude, SimVarValueType.Feet, simvarAltitudeSet);
                 const desiredAltitude = this.calculator.getDesiredAltitude(plan.activeLateralLeg, alongLegDistance);
                 desiredAltFeet = UnitType.METER.convertTo(desiredAltitude, UnitType.FOOT);
                 this.setConstraintDistance(plan, todBodDetails.currentConstraintIndex);
                 const currentLeg = this.lnavData.currentLegIndex >= 0 && this.lnavData.currentLegIndex < plan.length ? plan.getLeg(this.lnavData.currentLegIndex) : undefined;
                 if (((_a = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _a === void 0 ? void 0 : _a.distanceWithTransitions) && constraintAltitude !== undefined) {
                     const distance = this.constraintDistance +
                         UnitType.METER.convertTo(currentLeg.calculated.distanceWithTransitions - alongLegDistance, UnitType.NMILE);
                     requiredVs = this.getRequiredVs(distance, constraintAltitude);
                 }
             }
             else {
                 this.failed();
             }
             if (plan.length > 0) {
                 const finalLeg = plan.getLeg(plan.length - 1);
                 const lpvDistance = this.manageGP(finalLeg, plan, alongLegDistance);
                 verticalDeviation = desiredAltFeet - this.currentAltitude;
                 SimVar.SetSimVarValue(VNavSimVars.VerticalDeviation, SimVarValueType.Feet, verticalDeviation);
                 const targetAltitude = this.calculator.getTargetAltitude(plan.activeLateralLeg);
                 if (targetAltitude !== undefined) {
                     targetAltitudeFeet = UnitType.METER.convertTo(targetAltitude, UnitType.FOOT);
                     SimVar.SetSimVarValue(VNavSimVars.TargetAltitude, SimVarValueType.Feet, targetAltitudeFeet);
                 }
                 else {
                     SimVar.SetSimVarValue(VNavSimVars.TargetAltitude, SimVarValueType.Feet, -1);
                 }
                 if (this.apValues.verticalActive.get() === APVerticalModes.GP ||
                     (this.apValues.approachHasGP.get() && this.state === DirectorState.Inactive && plan.activeLateralLeg >= this.calculator.getFafLegIndex())) {
                     requiredVs = this.getRequiredVs(UnitType.METER.convertTo(lpvDistance, UnitType.NMILE), this.calculator.getLpvRunwayAltitude(), this.currentGpsAltitude);
                 }
                 SimVar.SetSimVarValue(VNavSimVars.RequiredVS, SimVarValueType.Number, requiredVs);
                 if (plan.activeLateralLeg === this.awaitingRearm) {
                     this.arm();
                 }
                 if (this.state !== DirectorState.Inactive && this.awaitingAltCap === -1 && this.awaitingRearm === -1) {
                     this.manageAltHold(targetAltitudeFeet);
                     this.trackVerticalPath(targetAltitudeFeet, verticalDeviation, plan);
                 }
                 else if (plan.activeLateralLeg < plan.length) {
                     const fpa = this.calculator.getLeg(plan.activeLateralLeg).fpa;
                     SimVar.SetSimVarValue(VNavSimVars.FPA, SimVarValueType.FPM, fpa);
                 }
                 else {
                     SimVar.SetSimVarValue(VNavSimVars.FPA, SimVarValueType.FPM, 0);
                 }
             }
             else {
                 // TODO: remove this once we have a better way to get LPV state - does an LPV exist or not
                 SimVar.SetSimVarValue(VNavSimVars.LPVDistance, SimVarValueType.Number, Number.MAX_SAFE_INTEGER);
                 this.lpvDeviation.set(-1001);
             }
         }
         else {
             this.failed();
         }
         this.calculator.currentAlongLegDistance = alongLegDistance;
     }
     /**
      * Tracks the vertical path.
      * @param targetAltitude The current VNAV target altitude, if any.
      * @param verticalDeviation The current vertical deviation.
      * @param plan The active flight plan.
      */
     trackVerticalPath(targetAltitude, verticalDeviation, plan) {
         if (targetAltitude === undefined) {
             targetAltitude = Number.NEGATIVE_INFINITY;
         }
         if (plan.activeLateralLeg >= plan.length) {
             this.isAltCaptured = false;
             this.failed();
             return;
         }
         const targetIsSelectedAltitude = this.preselectedAltitude > targetAltitude;
         targetAltitude = Math.max(targetAltitude, this.preselectedAltitude);
         const deviationFromTarget = targetAltitude - this.currentAltitude;
         const fpaPercentage = Math.max(verticalDeviation / -100, -1) + 1;
         const fpa = this.calculator.getLeg(plan.activeLateralLeg).fpa;
         const desiredPitch = (fpa * fpaPercentage) * -1;
         SimVar.SetSimVarValue(VNavSimVars.FPA, SimVarValueType.FPM, fpa);
         if (this.pathMode === VNavPathMode.None) {
             if (this.preselectedAltitude + 75 < this.currentAltitude) {
                 this.pathMode = VNavPathMode.PathArmed;
             }
             else {
                 this.deactivate();
             }
         }
         if (!this.isAltCaptured && this.pathMode === VNavPathMode.PathActive && fpa === 0) {
             this.apValues.capturedAltitude.set(100 * Math.round(targetAltitude / 100));
             this.pathMode = VNavPathMode.PathArmed;
             this.onDelegateAltCap();
             if (UnitType.METER.convertTo(this.calculator.getFafAltitude(), UnitType.FOOT) === targetAltitude) {
                 this.deactivate();
             }
         }
         if (this.pathMode === VNavPathMode.PathArmed || this.pathMode == VNavPathMode.PathActive) {
             if (verticalDeviation <= 100 && verticalDeviation >= -15 && this.pathMode === VNavPathMode.PathArmed) {
                 if (Math.abs(deviationFromTarget) > 75 && (!this.isAltCaptured && fpa !== 0)) {
                     SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD', 'Bool', 0);
                     this.pathMode = VNavPathMode.PathActive;
                 }
             }
             if (!this.isAltCaptured && Math.abs(deviationFromTarget) <= 250 && this.pathMode == VNavPathMode.PathActive) {
                 this.capturedAltitude = targetAltitude;
                 this.apValues.capturedAltitude.set(Math.round(this.capturedAltitude));
                 this.isAltCaptured = true;
             }
             if (this.isAltCaptured && this.pathMode === VNavPathMode.PathActive) {
                 const altCapDeviation = Math.abs(this.capturedAltitude - this.currentAltitude);
                 const captureActivationValue = Math.tan(UnitType.DEGREE.convertTo(fpa, UnitType.RADIAN)) * UnitType.NMILE.convertTo(this.currentGroundSpeed / 360, UnitType.FOOT);
                 if (altCapDeviation < Math.abs(captureActivationValue)) {
                     if (!targetIsSelectedAltitude && !this.calculator.getIsPathEnd(plan.activeLateralLeg)) {
                         this.awaitingAltCap = plan.activeLateralLeg + 1;
                     }
                     this.onDelegateAltCap();
                     return;
                 }
             }
             if (this.pathMode === VNavPathMode.PathActive) {
                 //We need the instant AOA here so we're avoiding the bus
                 const aoa = SimVar.GetSimVarValue('INCIDENCE ALPHA', SimVarValueType.Degree);
                 const maximumPitch = this.isAltCaptured ? 6 : 0;
                 const targetPitch = aoa + NavMath.clamp(desiredPitch, -6, maximumPitch);
                 SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, -targetPitch);
             }
         }
     }
     /**
      * Manages the sim ALT hold.
      * @param targetAltitude The current VNAV target altitude, if any.
      */
     manageAltHold(targetAltitude) {
         if (targetAltitude !== undefined) {
             const targetAltFeet = targetAltitude;
             if (this.preselectedAltitude >= targetAltFeet) {
                 SimVar.SetSimVarValue(VNavSimVars.CaptureType, SimVarValueType.Number, VNavAltCaptureType.Selected);
             }
             else {
                 SimVar.SetSimVarValue(VNavSimVars.CaptureType, SimVarValueType.Number, VNavAltCaptureType.VNAV);
             }
         }
         else {
             SimVar.SetSimVarValue(VNavSimVars.TargetAltitude, SimVarValueType.Feet, -1);
             SimVar.SetSimVarValue(VNavSimVars.CaptureType, SimVarValueType.Number, VNavAltCaptureType.None);
         }
     }
     /**
      * Gets the distance along the current leg.
      * @param lnavData The current LNAV data.
      * @returns The distance along the current leg, in meters.
      */
     getAlongLegDistance(lnavData) {
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         const leg = lnavData.currentLegIndex >= 0 && lnavData.currentLegIndex < plan.length ? plan.getLeg(lnavData.currentLegIndex) : undefined;
         let distance = 0;
         if (leg === null || leg === void 0 ? void 0 : leg.calculated) {
             const calcs = leg.calculated;
             const ingress = calcs.ingress;
             for (let i = 0; i < ingress.length; i++) {
                 const vector = ingress[i];
                 if (lnavData.transitionMode === TransitionMode.Ingress && lnavData.vectorIndex === i) {
                     return distance + this.getAlongVectorDistance(vector, this.planePos);
                 }
                 distance += vector.distance;
             }
             let vectors, startIndex, endIndex;
             if (lnavData.isSuspended && lnavData.transitionMode === TransitionMode.None) {
                 vectors = calcs.flightPath;
                 startIndex = Math.max(0, calcs.ingressJoinIndex);
                 endIndex = vectors.length;
             }
             else {
                 vectors = calcs.ingressToEgress;
                 startIndex = 0;
                 endIndex = vectors.length;
             }
             for (let i = startIndex; i < endIndex; i++) {
                 const vector = vectors[i];
                 if (lnavData.transitionMode === TransitionMode.None && lnavData.vectorIndex === i) {
                     return distance + this.getAlongVectorDistance(vector, this.planePos);
                 }
                 distance += vector.distance;
             }
             const egress = calcs.egress;
             for (let i = 0; i < egress.length; i++) {
                 const vector = egress[i];
                 if (lnavData.transitionMode === TransitionMode.Egress && lnavData.vectorIndex === i) {
                     return distance + this.getAlongVectorDistance(vector, this.planePos);
                 }
                 distance += vector.distance;
             }
         }
         return distance;
     }
     /**
      * Gets the distance along a flight path vector from the start of the vector to a specified point.
      * @param vector A flight path vector.
      * @param point A point.
      * @returns The distance along the flight path vector, in meters, from the start of the vector to the specified
      * point.
      */
     getAlongVectorDistance(vector, point) {
         const circle = FlightPathUtils.setGeoCircleFromVector(vector, this.geoCircleCache[0]);
         return UnitType.GA_RADIAN.convertTo(FlightPathUtils.getAlongArcSignedDistance(circle, GeoPoint.sphericalToCartesian(vector.startLat, vector.startLon, this.vec3Cache[0]), GeoPoint.sphericalToCartesian(vector.endLat, vector.endLon, this.vec3Cache[1]), circle.closest(point, this.vec3Cache[2])), UnitType.METER);
     }
     /**
      * Manages the GP State and sets required data for GP guidance.
      * @param finalLeg The LegDefinition for the last flight plan leg.
      * @param plan The FlightPlan.
      * @param alongLegDistance The Along Leg Distance
      * @returns The LPV Distance
      */
     manageGP(finalLeg, plan, alongLegDistance) {
         let lpvDeviation = Number.POSITIVE_INFINITY;
         let lpvDistance = -1;
         let gpExists = false;
         let gpCalculated = false;
         if (this.approachDetails.approachLoaded && this.approachDetails.approachIsActive && !this.approachDetails.approachIsCircling) {
             switch (this.approachDetails.approachType) {
                 case ApproachType.APPROACH_TYPE_GPS:
                 case ApproachType.APPROACH_TYPE_RNAV:
                 case AdditionalApproachType.APPROACH_TYPE_VISUAL:
                     gpExists = true;
             }
         }
         if (gpExists && (finalLeg === null || finalLeg === void 0 ? void 0 : finalLeg.calculated) !== undefined) {
             lpvDistance = this.calculator.getLpvDistance(plan.activeLateralLeg, alongLegDistance, this.planePos);
             const desiredLPVAltitude = this.calculator.getDesiredLpvAltitude(lpvDistance);
             const desiredLPVAltitudeFeet = UnitType.METER.convertTo(desiredLPVAltitude, UnitType.FOOT);
             lpvDeviation = desiredLPVAltitudeFeet - this.currentGpsAltitude;
             this.lpvDeviation.set(lpvDeviation);
             gpCalculated = true;
         }
         else {
             this.lpvDeviation.set(-1001);
         }
         SimVar.SetSimVarValue(VNavSimVars.LPVVerticalDeviation, SimVarValueType.Feet, lpvDeviation);
         SimVar.SetSimVarValue(VNavSimVars.LPVDistance, SimVarValueType.Number, lpvDistance);
         this.apValues.approachHasGP.set(gpCalculated);
         return lpvDistance;
     }
     /**
      * Gets the current required vertical speed.
      * @param distance is the distance to the constraint.
      * @param targetAltitude is the target altitude for the constraint.
      * @param currentAltitude is the current altitude (defaults to baro alt)
      * @returns the required vs in fpm.
      */
     getRequiredVs(distance, targetAltitude, currentAltitude = this.currentAltitude) {
         if (targetAltitude > 0) {
             const deviation = currentAltitude - UnitType.METER.convertTo(targetAltitude, UnitType.FOOT);
             if (deviation > 0 && distance > 0) {
                 const fpaRequired = UnitType.RADIAN.convertTo(Math.atan((deviation / UnitType.NMILE.convertTo(distance, UnitType.FOOT))), UnitType.DEGREE);
                 return UnitType.NMILE.convertTo(this.currentGroundSpeed / 60, UnitType.FOOT) * Math.tan(UnitType.DEGREE.convertTo(-fpaRequired, UnitType.RADIAN));
             }
         }
         return 0;
     }
     /**
      * Sets the leg distance from the current leg to the constraint leg, not include the distance to the current active leg.
      * @param plan is the flight plan.
      * @param constraintLegIndex is the leg index of the current constraint.
      */
     setConstraintDistance(plan, constraintLegIndex) {
         var _a, _b;
         if (constraintLegIndex !== undefined && constraintLegIndex > -1) {
             const currentLeg = plan.getLeg(plan.activeLateralLeg);
             const constraintLeg = plan.getLeg(constraintLegIndex);
             if (((_a = constraintLeg.calculated) === null || _a === void 0 ? void 0 : _a.cumulativeDistanceWithTransitions) && ((_b = currentLeg.calculated) === null || _b === void 0 ? void 0 : _b.cumulativeDistanceWithTransitions)) {
                 const currentLegCumulativeNM = UnitType.METER.convertTo(currentLeg.calculated.cumulativeDistanceWithTransitions, UnitType.NMILE);
                 const bodCumulativeNM = UnitType.METER.convertTo(constraintLeg.calculated.cumulativeDistanceWithTransitions, UnitType.NMILE);
                 this.constraintDistance = (bodCumulativeNM - currentLegCumulativeNM);
                 return;
             }
         }
         this.constraintDistance = -1;
     }
     /**
      * Gets the constraint index for a vertical direct based on an input global leg index.
      * @param legIndex The input global leg index selected.
      * @returns The constraint index for the vertical direct
      */
     getVerticalDirectConstraint(legIndex) {
         if (this.flightPlanner.hasFlightPlan(0)) {
             const plan = this.flightPlanner.getFlightPlan(0);
             return this.calculator.getVerticalDirectConstraintFromIndex(legIndex, plan.activeLateralLeg);
         }
         return undefined;
     }
     /**
      * Activates a vertical direct to a selected constraint.
      * @param constraint The constraint to go direct to.
      * @returns Whether the vertical direct was activated or not.
      */
     activateVerticalDirect(constraint) {
         if (this.flightPlanner.hasFlightPlan(0)) {
             const plan = this.flightPlanner.getFlightPlan(0);
             const verticalData = {
                 altDesc: AltitudeRestrictionType.Unused,
                 altitude1: 0,
                 altitude2: 0
             };
             if (plan.length > constraint.index) {
                 for (let i = 0; i < constraint.index; i++) {
                     plan.setLegVerticalData(i, verticalData);
                 }
                 this.calculator.activateVerticalDirect(constraint.index);
                 return true;
             }
         }
         return false;
     }
     /**
      * Sets whether VNAV is unavailable due to invalid legs between the plane and the next target.
      * @param isUnavailable Whether or not VNAV is unavailable.
      */
     setVNavUnavailable(isUnavailable) {
         if (this.isVNavUnavailable !== isUnavailable) {
             if (isUnavailable) {
                 this.bus.getPublisher().pub('alerts_push', {
                     key: 'vnv-unavailable-legs',
                     title: 'VNV UNAVAILABLE',
                     message: 'Unsupported leg type in flight plan.'
                 }, true, false);
             }
             else {
                 this.bus.getPublisher().pub('alerts_remove', 'vnv-unavailable-legs', true, false);
             }
             this.isVNavUnavailable = isUnavailable;
         }
     }
 }
 
 /**
  * Handles the calculation of the VNAV flight path.
  */
 class VNavPathCalculator {
     /**
      * Creates an instance of the VNavPathCalculator.
      * @param bus The EventBus to use with this instance.
      * @param flightPlanner The flight planner to use with this instance.
      */
     constructor(bus, flightPlanner) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.flightPathAngle = 3;
         this.maxFlightPathAngle = 6;
         this.segments = [];
         this.constraints = [];
         this.destLegIndex = 0;
         this.fafLegIndex = 0;
         this.missedApproachStartIndex = -1;
         this.isSuspended = false;
         this.verticalDirectIndex = -1;
         this.planChanged = false;
         // private lastUpdateTime = 0;
         this.cursor = {
             segment: new FlightPlanSegment(-1, -1, []),
             legIndex: -1,
             legDefinition: {
                 leg: FlightPlan.createLeg({}),
                 flags: LegDefinitionFlags.None,
                 verticalData: { altDesc: AltitudeRestrictionType.Unused, altitude1: 0, altitude2: 0 }
             },
             index: 0
         };
         this.currentAltitude = 0;
         this.lpvFpa = 0;
         /**
          * Sets an FPA on the current constraint when an event is received from the VNAV Profile Window via the bus.
          * @param fpa The FPA to set the constraint to manually.
          */
         this.setFpaHandler = (fpa) => {
             const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
             const constraint = this.getConstraintFromLegIndex(plan.activeLateralLeg);
             const leg = plan.tryGetLeg(plan.activeLateralLeg);
             if (leg && constraint) {
                 leg.verticalData.fpa = fpa;
                 constraint.fpa = fpa;
                 constraint.type = 'manual';
                 this.computeVnavPath();
             }
         };
         const fpl = bus.getSubscriber();
         fpl.on('fplCopied').handle(e => e.planIndex === 0 && this.onPlanChanged());
         fpl.on('fplCreated').handle(e => e.planIndex === 0 && this.onPlanChanged());
         fpl.on('fplLegChange').handle(e => {
             if (e.planIndex === 0) {
                 this.onPlanChanged(e);
             }
         });
         fpl.on('fplLoaded').handle(e => e.planIndex === 0 && this.onPlanChanged());
         fpl.on('fplSegmentChange').handle(e => {
             if (e.planIndex === 0) {
                 this.onPlanChanged(undefined, e);
             }
         });
         fpl.on('fplIndexChanged').handle(() => this.onPlanChanged());
         fpl.on('fplCalculated').handle(e => e.planIndex === 0 && this.onPlanCalculated());
         bus.getSubscriber().on('alt').handle(alt => this.currentAltitude = UnitType.FOOT.convertTo(alt, UnitType.METER));
         bus.getSubscriber().on('suspChanged').handle(v => this.isSuspended = v);
         bus.getSubscriber().on('vnav_fpa_set').handle(this.setFpaHandler);
     }
     /**
      * Gets a VNAV leg from the plan.
      * @param index The index of the leg to get.
      * @returns The requested VNAV leg.
      * @throws Not found if the index is not valid.
      */
     getLeg(index) {
         for (let i = 0; i < this.segments.length; i++) {
             const segment = this.segments[i];
             if (index >= segment.offset && index < segment.offset + segment.legs.length) {
                 return segment.legs[index - segment.offset];
             }
         }
         throw new Error(`Leg with index ${index} not found`);
     }
     /**
      * Gets a VNAV leg from the plan from a specified segment.
      * @param segmentIndex The segment index of the leg to get.
      * @param legIndex The index of the leg to get within the specified segment.
      * @returns The requested VNAV leg.
      * @throws Not found if the index is not valid.
      */
     getLegFromSegment(segmentIndex, legIndex) {
         const segment = this.segments[segmentIndex];
         const leg = segment.legs[legIndex];
         if (segment && leg) {
             return leg;
         }
         else {
             throw new Error(`Leg from segment ${segmentIndex} index ${legIndex} not found`);
         }
     }
     /**
      * Gets a VNAV leg from the plan from a specified segment.
      * @returns The vnav segments.
      * @throws Not found if the index is not valid.
      */
     getSegments() {
         return this.segments;
     }
     /**
      * Gets the VNAV desired altitude.
      * @param index The leg index to get the target for.
      * @param distanceAlongLeg The distance along the leg the aircraft is presently.
      * @returns The current VNAV desired altitude.
      */
     getDesiredAltitude(index, distanceAlongLeg) {
         const priorConstraint = this.getPriorConstraintFromLegIndex(index);
         if (priorConstraint && priorConstraint.nextVnavEligibleLegIndex && index < priorConstraint.nextVnavEligibleLegIndex) {
             return priorConstraint.altitude;
         }
         const leg = this.getLeg(index);
         return leg.altitude + this.altitudeForDistance(leg.fpa, leg.distance - distanceAlongLeg);
     }
     /**
      * Gets the current LPV distance.
      * @param index The current leg index.
      * @param distanceAlongLeg The distance along the leg the aircraft is presently.
      * @param ppos The current position from LNAV Data State
      * @returns The current LPV distance.
      */
     getLpvDistance(index, distanceAlongLeg, ppos) {
         let globalLegIndex = 0;
         let distance = 0;
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         const destLeg = plan.getLeg(this.destLegIndex);
         if (index <= this.destLegIndex) {
             for (let segmentIndex = 0; segmentIndex < this.segments.length; segmentIndex++) {
                 const segment = this.segments[segmentIndex];
                 for (let legIndex = 0; legIndex < segment.legs.length; legIndex++) {
                     const leg = segment.legs[legIndex];
                     if (globalLegIndex <= this.destLegIndex) {
                         if (index === globalLegIndex) {
                             distance += leg.distance - distanceAlongLeg;
                         }
                         else if (globalLegIndex > index) {
                             distance += segment.legs[legIndex].distance;
                         }
                     }
                     globalLegIndex++;
                 }
             }
             if (ICAO.isFacility(destLeg.leg.fixIcao)
                 && ICAO.getFacilityType(destLeg.leg.fixIcao) !== FacilityType.RWY
                 && plan.procedureDetails.destinationRunway !== undefined
                 && destLeg.calculated && destLeg.calculated.endLat !== undefined && destLeg.calculated.endLon !== undefined) {
                 const runway = plan.procedureDetails.destinationRunway;
                 const runwayGeoPoint = new GeoPoint(runway.latitude, runway.longitude);
                 if (index === this.destLegIndex && this.isSuspended) {
                     distance = UnitType.GA_RADIAN.convertTo(runwayGeoPoint.distance(ppos), UnitType.METER);
                 }
                 else {
                     distance += UnitType.GA_RADIAN.convertTo(runwayGeoPoint.distance(destLeg.calculated.endLat, destLeg.calculated.endLon), UnitType.METER);
                 }
             }
         }
         return distance;
     }
     /**
      * Gets the LPV desired altitude.
      * @param distance The current LPV distance.
      * @returns The current LPV desired altitude.
      */
     getDesiredLpvAltitude(distance) {
         return this.getLpvRunwayAltitude() + this.altitudeForDistance(this.lpvFpa, distance + 100);
     }
     /**
      * Gets the LPV runway altitude.
      * @returns The LPV runway altitude.
      */
     getLpvRunwayAltitude() {
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         const destLeg = plan.getLeg(this.destLegIndex);
         let destAltitude = destLeg.leg.altitude1;
         if (ICAO.isFacility(destLeg.leg.fixIcao)
             && ICAO.getFacilityType(destLeg.leg.fixIcao) !== FacilityType.RWY
             && plan.procedureDetails.destinationRunway !== undefined) {
             destAltitude = plan.procedureDetails.destinationRunway.elevation;
         }
         return destAltitude;
     }
     /**
      * Gets the VNAV target altitude for the given leg index.
      * @param index The index of the leg.
      * @returns The next VNAV target altitude, or undefined if none exists.
      */
     getTargetAltitude(index) {
         const priorConstraint = this.getPriorConstraintFromLegIndex(index);
         if (priorConstraint && priorConstraint.nextVnavEligibleLegIndex && index < priorConstraint.nextVnavEligibleLegIndex) {
             return priorConstraint.altitude;
         }
         let i = this.constraints.length - 1;
         while (i >= 0) {
             const constraint = this.constraints[i];
             if (index <= constraint.index && constraint.isTarget && !constraint.isBeyondFaf) {
                 return constraint.altitude;
             }
             i--;
         }
     }
     /**
      * Gets the VNAV TOD/BOD details.
      * @param index The current leg index.
      * @param distanceAlongLeg The distance the plane is along the current leg.
      * @returns The distance the plane is from the next TOD.
      */
     todBodDetails(index, distanceAlongLeg) {
         const details = {
             todLegIndex: -1,
             bodLegIndex: -1,
             distanceFromLegEnd: 0,
             distanceFromTod: 0,
             distanceFromBod: 0,
             currentConstraintIndex: -1
         };
         let globalIndex = 0;
         const constraint = this.getConstraintFromLegIndex(index);
         details.currentConstraintIndex = constraint && constraint.index ? constraint.index : -1;
         const priorConstraint = this.getPriorConstraintFromLegIndex(index);
         if ((priorConstraint === null || priorConstraint === void 0 ? void 0 : priorConstraint.nextVnavEligibleLegIndex) !== undefined && priorConstraint.nextVnavEligibleLegIndex > index) {
             return details;
         }
         for (let i = 0; i < this.segments.length && details.bodLegIndex === -1; i++) {
             const segment = this.segments[i];
             for (let l = 0; l < segment.legs.length; l++) {
                 const leg = segment.legs[l];
                 if (globalIndex >= index) {
                     if (details.todLegIndex === -1) {
                         if (index === globalIndex) {
                             details.distanceFromTod = leg.distance - distanceAlongLeg;
                         }
                         else {
                             details.distanceFromTod += leg.distance;
                         }
                         if (leg.todDistance !== undefined && leg.altitude <= this.currentAltitude) {
                             details.todLegIndex = globalIndex;
                             details.distanceFromTod -= leg.todDistance;
                             details.distanceFromLegEnd = leg.todDistance;
                         }
                     }
                     if (leg.isBod) {
                         details.bodLegIndex = globalIndex;
                         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
                         const currentLeg = plan.getLeg(plan.activeLateralLeg);
                         let bodDistanceInMeters = 0;
                         if (plan.activeLateralLeg === globalIndex && currentLeg.calculated) {
                             bodDistanceInMeters = currentLeg.calculated.distanceWithTransitions - distanceAlongLeg;
                         }
                         else if (plan.activeLateralLeg < globalIndex) {
                             const bodLeg = plan.getLeg(globalIndex);
                             if (bodLeg.calculated && currentLeg.calculated && bodLeg.calculated.cumulativeDistanceWithTransitions && currentLeg.calculated.cumulativeDistanceWithTransitions) {
                                 bodDistanceInMeters = (bodLeg.calculated.cumulativeDistanceWithTransitions - currentLeg.calculated.cumulativeDistanceWithTransitions)
                                     + (currentLeg.calculated.distanceWithTransitions - distanceAlongLeg);
                             }
                         }
                         details.distanceFromBod = bodDistanceInMeters;
                         if (details.todLegIndex < index) {
                             details.distanceFromTod = 0;
                         }
                         return details;
                     }
                 }
                 globalIndex++;
             }
         }
         if (details.todLegIndex < index) {
             details.distanceFromTod = 0;
         }
         return details;
     }
     /**
      * Gets and returns the FAF altitude.
      * @returns the FAF constraint altitude.
      */
     getFafAltitude() {
         return this.getLeg(this.fafLegIndex).altitude;
     }
     /**
      * Gets and returns the FAF Leg Index.
      * @returns the FAF Leg Index.
      */
     getFafLegIndex() {
         return this.fafLegIndex;
     }
     /**
      * Gets and returns whether the input leg index is a path end.
      * @param legIndex is the global leg index to check.
      * @returns whether the input leg index is a path end.
      */
     getIsPathEnd(legIndex) {
         const constraintIndex = this.constraints.findIndex(c => c.index === legIndex);
         if (constraintIndex > -1 && this.constraints[constraintIndex].isPathEnd) {
             return true;
         }
         return false;
     }
     /**
      * Gets and returns the current constraint altitude.
      * @param index is the global leg index to check.
      * @returns the altitude or undefined.
      */
     getCurrentConstraintAltitude(index) {
         const priorConstraint = this.getPriorConstraintFromLegIndex(index);
         const currentConstraint = this.getConstraintFromLegIndex(index);
         if (priorConstraint && priorConstraint.nextVnavEligibleLegIndex && index < priorConstraint.nextVnavEligibleLegIndex) {
             return priorConstraint.altitude;
         }
         else {
             return currentConstraint && currentConstraint.altitude ? currentConstraint.altitude : undefined;
         }
     }
     /**
      * Gets and returns the next constraint altitude.
      * @param index is the global leg index to check.
      * @returns the altitude or undefined.
      */
     getNextConstraintAltitude(index) {
         const currentConstraint = this.getConstraintFromLegIndex(index);
         return currentConstraint && currentConstraint.altitude ? currentConstraint.altitude : undefined;
     }
     /**
      * Gets and returns the vertical direct constraint based on an input index.
      * @param selectedIndex The global leg index selected for vertical direct.
      * @param activeIndex The active leg index.
      * @returns The Vnav Constraint for the vertical direct or undefined.
      */
     getVerticalDirectConstraintFromIndex(selectedIndex, activeIndex) {
         if (this.constraints.length > 0) {
             if (selectedIndex < activeIndex) {
                 return this.getConstraintFromLegIndex(activeIndex);
             }
             for (let c = this.constraints.length - 1; c >= 0; c--) {
                 const constraint = this.constraints[c];
                 if (constraint.index === selectedIndex || (c === this.constraints.length - 1 && selectedIndex < constraint.index)) {
                     return constraint;
                 }
                 else if (c < this.constraints.length - 1 && constraint.index > selectedIndex) {
                     return this.constraints[c + 1];
                 }
             }
         }
         return undefined;
     }
     /**
      * Activates a vertical direct to a constraint index.
      * @param constraintIndex The global leg index of the constraint to go direct to.
      */
     activateVerticalDirect(constraintIndex) {
         this.verticalDirectIndex = constraintIndex;
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         this.buildVerticalPath(plan, this.verticalDirectIndex);
     }
     /**
      * Sets planChanged to true to flag that a plan change has been received over the bus.
      * @param legChangeEvent The FlightPlanLegEvent, if any.
      * @param segmentChangeEvent The FlightPlanSegmentEvent, if any.
      */
     onPlanChanged(legChangeEvent, segmentChangeEvent) {
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         if (this.verticalDirectIndex > -1) {
             if (legChangeEvent !== undefined) {
                 const globalIndex = plan.getSegment(legChangeEvent.segmentIndex).offset + legChangeEvent.legIndex;
                 if (globalIndex <= this.verticalDirectIndex) {
                     this.verticalDirectIndex = -1;
                 }
             }
             else if (segmentChangeEvent !== undefined) {
                 const verticalDirectSegmentIndex = plan.getSegmentIndex(this.verticalDirectIndex);
                 if (segmentChangeEvent.segmentIndex <= verticalDirectSegmentIndex) {
                     this.verticalDirectIndex = -1;
                 }
             }
         }
         this.planChanged = true;
         this.currentAlongLegDistance = undefined;
     }
     /**
      * Method fired on a flight plan change event to rebuild the vertical path.
      */
     onPlanCalculated() {
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         if (this.planChanged) {
             this.buildVerticalPath(plan, this.verticalDirectIndex > -1 ? this.verticalDirectIndex : undefined);
         }
         else {
             this.computeVnavPath();
         }
     }
     /**
      * Resets the VNAV plan segments, legs, and constraints based on the new plan.
      * @param plan The Flight Plan.
      * @param verticalDirectIndex The vertical direct index, if any
      */
     buildVerticalPath(plan, verticalDirectIndex) {
         var _a, _b, _c, _d;
         this.fafLegIndex = this.getFafIndex(plan);
         this.constraints.length = 0;
         let currentConstraintAlt = 0;
         let priorConstraintAlt = Number.POSITIVE_INFINITY;
         let pathIsDirect = false;
         let constraintContainsManualLeg = false;
         let currentConstraint = this.createConstraint(0, 0, '$DEFAULT', 'normal');
         this.segments.length = 0;
         this.destLegIndex = Math.max(0, plan.length - 1);
         this.missedApproachStartIndex = this.destLegIndex;
         const directToData = plan.directToData;
         const directToGlobalLegIndex = directToData.segmentIndex > 0 && directToData.segmentLegIndex > -1 ?
             plan.getSegment(directToData.segmentIndex).offset + directToData.segmentLegIndex : -1;
         // Iterate forward through the plan to build the constraints
         for (const segment of plan.segments()) {
             // Add the plan segments to the VNav Path Calculator Segments
             this.segments[segment.segmentIndex] = {
                 offset: segment.offset,
                 legs: []
             };
             let missedApproachFound = false;
             for (let legIndex = 0; legIndex < segment.legs.length; legIndex++) {
                 const planLeg = segment.legs[legIndex];
                 const leg = this.createLeg(segment.segmentIndex, legIndex, (_a = planLeg.name) !== null && _a !== void 0 ? _a : '', (_c = (_b = planLeg.calculated) === null || _b === void 0 ? void 0 : _b.distanceWithTransitions) !== null && _c !== void 0 ? _c : undefined);
                 const globalLegIndex = segment.offset + legIndex;
                 switch (planLeg.leg.type) {
                     case LegType.CI:
                     case LegType.VI:
                     case LegType.FM:
                     case LegType.VM:
                         constraintContainsManualLeg = true;
                 }
                 //Check if the leg is part of the missed approach, and set the missed approach start and dest leg indexes.
                 if (segment.segmentType === FlightPlanSegmentType.Approach && !missedApproachFound && BitFlags.isAll(planLeg.flags, LegDefinitionFlags.MissedApproach)) {
                     this.missedApproachStartIndex = globalLegIndex;
                     this.destLegIndex = Math.max(0, globalLegIndex - 1);
                     missedApproachFound = true;
                 }
                 //Check if we are in a vertical direct
                 if (verticalDirectIndex !== undefined && verticalDirectIndex === globalLegIndex) {
                     currentConstraint.type = 'direct';
                     pathIsDirect = true;
                 }
                 //Check if we in a direct to (or approach is activated)
                 if (directToData.segmentIndex === segment.segmentIndex && legIndex === directToData.segmentLegIndex + 3
                     && BitFlags.isAll(planLeg.flags, LegDefinitionFlags.DirectTo)) {
                     currentConstraint.type = 'direct';
                     pathIsDirect = true;
                     currentConstraint.legs.length = 0;
                     if (this.constraints.length > 0) {
                         this.constraints.length = 0;
                     }
                 }
                 // Check if this leg has a constraint
                 let legIsConstraint = false;
                 if (segment.segmentType !== FlightPlanSegmentType.Origin && segment.segmentType !== FlightPlanSegmentType.Departure &&
                     planLeg.verticalData && planLeg.verticalData.altDesc !== AltitudeRestrictionType.Unused && globalLegIndex <= this.fafLegIndex && !missedApproachFound) {
                     currentConstraintAlt = this.getConstraintAltitude(planLeg);
                     // If the current constraint altitude is higher than the prior constraint altitude,
                     // then mark it as invalid and don't process the constraint.
                     const tempConstraintDistance = this.getConstraintDistance(currentConstraint);
                     const fpaTempValue = this.getFpa(leg.distance + tempConstraintDistance, Math.abs(currentConstraintAlt - priorConstraintAlt));
                     const currentWithPrecision = Math.round(currentConstraintAlt * 10) / 10;
                     const priorWithPrecision = Math.round(priorConstraintAlt * 10) / 10;
                     if ((verticalDirectIndex !== undefined && verticalDirectIndex > globalLegIndex) ||
                         (globalLegIndex <= directToGlobalLegIndex)) {
                         legIsConstraint = false;
                     }
                     else if (currentWithPrecision > priorWithPrecision || (!constraintContainsManualLeg &&
                         (priorConstraintAlt < Number.POSITIVE_INFINITY && fpaTempValue > 6))) {
                         leg.invalidConstraintAltitude = currentConstraintAlt;
                     }
                     else {
                         legIsConstraint = true;
                     }
                 }
                 // Add the leg to the current constraint.
                 currentConstraint.legs.unshift(leg);
                 this.segments[segment.segmentIndex].legs.push(leg);
                 switch (planLeg.leg.type) {
                     case LegType.HA:
                     case LegType.HM:
                     case LegType.HF:
                     case LegType.VM:
                     case LegType.FM:
                         if (this.constraints.length > 0) {
                             const priorConstraint = this.constraints[0];
                             priorConstraint.isPathEnd = true;
                             priorConstraint.isTarget = true;
                             priorConstraint.nextVnavEligibleLegIndex = globalLegIndex + 1;
                         }
                 }
                 const isLastLeg = globalLegIndex === plan.length - 1;
                 // If the current leg has a valid constraint, set the constraint details on the current constraint,
                 // then add a new empty constraint
                 if (legIsConstraint || isLastLeg) {
                     currentConstraint.index = globalLegIndex;
                     currentConstraint.name = isLastLeg ? '$DEST' : (_d = planLeg.name) !== null && _d !== void 0 ? _d : '';
                     currentConstraint.type = isLastLeg ? 'dest' : pathIsDirect ? 'direct' : 'normal';
                     //If we happen to be in the destination segment (i.e. the end of the plan)
                     //set the alt to the next constraint alt so that the segment is flat
                     currentConstraint.altitude = globalLegIndex > this.fafLegIndex ? priorConstraintAlt : currentConstraintAlt;
                     // TODO: Is this still needed?
                     if (pathIsDirect) {
                         currentConstraint.isTarget = true;
                     }
                     // If this is the FAF, set target and path end
                     if (globalLegIndex === this.fafLegIndex) {
                         currentConstraint.isTarget = true;
                         currentConstraint.isPathEnd = true;
                     }
                     if (planLeg.verticalData.fpa && plan.activeLateralLeg <= globalLegIndex && plan.activeLateralLeg >= currentConstraint.legs[currentConstraint.legs.length - 1].legIndex) {
                         currentConstraint.fpa = planLeg.verticalData.fpa;
                         currentConstraint.type = 'manual';
                     }
                     else if (planLeg.verticalData.fpa) {
                         planLeg.verticalData.fpa = undefined;
                     }
                     // Add the current constraint to the array of constraints in reverse order
                     if (!isLastLeg) {
                         this.constraints.unshift(currentConstraint);
                         constraintContainsManualLeg = false;
                         pathIsDirect = false;
                     }
                     // Set the prior constraint altitude from the current constraint before creating a new 
                     priorConstraintAlt = currentConstraint.altitude;
                     // Create a new empty constraint
                     if (!isLastLeg) {
                         currentConstraint = this.createConstraint(plan.length - 1, 0, '$DEFAULT', 'normal');
                     }
                 }
             }
         }
         this.calcLpvFpa(plan, this.fafLegIndex, this.destLegIndex);
         this.planChanged = false;
         this.computeVnavPath();
     }
     /**
      * Computes the VNAV descent path.
      */
     computeVnavPath() {
         this.fillLegAndConstraintDistances();
         if (!this.computeFlightPathAngles()) {
             const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
             this.buildVerticalPath(plan, this.verticalDirectIndex > -1 ? this.verticalDirectIndex : undefined);
             return;
         }
         for (let constraintIndex = 0; constraintIndex < this.constraints.length; constraintIndex++) {
             const constraint = this.constraints[constraintIndex];
             let todDistance = constraint.todDistance;
             let altitude = constraint.altitude;
             //If the next constraint altitude is going to bust our current altitude, adjust the
             //constraint segment TOD distance to match our current altitude
             const nextAlt = constraint.altitude + this.altitudeForDistance(constraint.fpa, constraint.distance);
             if (nextAlt > this.currentAltitude) {
                 todDistance = this.distanceForAltitude(constraint.fpa, this.currentAltitude - altitude);
             }
             for (let legIndex = 0; legIndex < constraint.legs.length; legIndex++) {
                 const leg = constraint.legs[legIndex];
                 leg.fpa = constraint.index <= this.fafLegIndex ? constraint.fpa : 0;
                 leg.altitude = altitude;
                 altitude += this.altitudeForDistance(leg.fpa, leg.distance);
                 if (legIndex === 0) {
                     leg.isAdvisory = false;
                 }
                 else {
                     leg.isAdvisory = true;
                 }
                 if (legIndex === 0 && constraint.isTarget) {
                     leg.isBod = true;
                 }
                 else {
                     leg.isBod = false;
                 }
                 if (leg.distance >= todDistance && !constraint.isBeyondFaf) {
                     leg.todDistance = todDistance;
                 }
                 else {
                     leg.todDistance = undefined;
                     todDistance -= leg.distance;
                 }
             }
         }
         this.calcLpvFpa(this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX), this.fafLegIndex, this.destLegIndex);
         this.notify();
     }
     /**
      * Fills the VNAV plan leg and constraint segment distances.
      */
     fillLegAndConstraintDistances() {
         this.iterateReverse(cursor => { var _a, _b; return this.segments[cursor.segment.segmentIndex].legs[cursor.legIndex].distance = (_b = (_a = cursor.legDefinition.calculated) === null || _a === void 0 ? void 0 : _a.distanceWithTransitions) !== null && _b !== void 0 ? _b : 0; });
         for (let constraintIndex = 0; constraintIndex < this.constraints.length; constraintIndex++) {
             const constraint = this.constraints[constraintIndex];
             constraint.distance = this.getConstraintDistance(constraint);
         }
     }
     /**
      * Gets a constraint segment distance from the constraint legs.
      * @param constraint The constraint to calculate a distance for.
      * @returns The constraint distance.
      */
     getConstraintDistance(constraint) {
         let distance = 0;
         for (let legIndex = 0; legIndex < constraint.legs.length; legIndex++) {
             distance += constraint.legs[legIndex].distance;
         }
         return distance;
     }
     /**
      * Computes the flight path angles for each constraint segment.
      * @returns Whether the flight path angles were computed.
      */
     computeFlightPathAngles() {
         let isCurrentlyDirect = false;
         for (let i = 0; i < this.constraints.length; i++) {
             const currentConstraint = this.constraints[i];
             const nextConstraint = this.constraints[i + 1];
             currentConstraint.legs.forEach((leg) => {
                 if (leg.invalidConstraintAltitude) {
                     return false;
                 }
             });
             if (currentConstraint.type === 'manual') {
                 // If we have manually set an FPA on this constraint, do not calculate the FPA.
                 continue;
             }
             if (currentConstraint.type !== 'direct') {
                 currentConstraint.fpa = this.flightPathAngle;
             }
             currentConstraint.isTarget = isCurrentlyDirect ? false : true;
             if (currentConstraint.index === this.fafLegIndex) {
                 currentConstraint.isTarget = true;
             }
             if (currentConstraint.index > this.fafLegIndex) {
                 currentConstraint.isBeyondFaf = true;
             }
             if (nextConstraint !== undefined && nextConstraint.type !== 'dep' && !nextConstraint.isPathEnd) {
                 const directFpa = this.getFpa(currentConstraint.distance, nextConstraint.altitude - currentConstraint.altitude);
                 const endAltitude = currentConstraint.altitude + this.altitudeForDistance(this.flightPathAngle, currentConstraint.distance);
                 const todDistance = this.distanceForAltitude(this.flightPathAngle, nextConstraint.altitude - currentConstraint.altitude);
                 currentConstraint.todDistance = todDistance;
                 //If going direct is within a half a degree of the default FPA, or if we were unable to meet
                 //the next constraint, go direct
                 if (Math.abs(directFpa - this.flightPathAngle) <= 0.5 || endAltitude < nextConstraint.altitude) {
                     // Check if the FPA will exceed the max flight path angle and if so, invalidate the constraint.
                     if (directFpa > this.maxFlightPathAngle && i !== 0) {
                         return false;
                     }
                     else {
                         currentConstraint.fpa = directFpa;
                         isCurrentlyDirect = true;
                         currentConstraint.todDistance = currentConstraint.distance;
                     }
                 }
                 else if (currentConstraint.altitude === nextConstraint.altitude || currentConstraint.isBeyondFaf) {
                     currentConstraint.fpa = 0;
                     isCurrentlyDirect = false;
                 }
                 else {
                     isCurrentlyDirect = false;
                 }
             }
             else {
                 isCurrentlyDirect = false;
             }
             //If the constraint is a direct, check if an FPA > 3 is required and, if so, attempt to set the max FPA
             if (currentConstraint.type === 'direct' && currentConstraint.fpa === 0 && this.currentAlongLegDistance !== undefined) {
                 const plan = this.flightPlanner.getActiveFlightPlan();
                 const legsToConstraint = currentConstraint.index - plan.activeLateralLeg;
                 let distance = 0;
                 for (let l = 0; l <= legsToConstraint; l++) {
                     const leg = currentConstraint.legs[l];
                     distance += leg.distance;
                 }
                 distance -= this.currentAlongLegDistance;
                 const fpaRequired = this.getFpa(distance, 50 + this.currentAltitude - currentConstraint.altitude);
                 //If the constraint is a vertical direct, don't clamp at 3 degrees
                 const minFpaClamp = this.verticalDirectIndex === currentConstraint.index ? 0 : 3;
                 currentConstraint.fpa = Utils.Clamp(fpaRequired, minFpaClamp, this.maxFlightPathAngle);
                 currentConstraint.todDistance = this.distanceForAltitude(currentConstraint.fpa, this.currentAltitude - currentConstraint.altitude);
             }
         }
         return true;
     }
     /**
      * Calculates the LPV flight path angle using the destination elevation
      * and FAF altitude restriction.
      * @param plan The plan to calculate from.
      * @param fafIndex The leg index of the FAF.
      * @param destIndex The leg index of the destination.
      */
     calcLpvFpa(plan, fafIndex, destIndex) {
         if (plan.length < 2 || fafIndex > plan.length || destIndex > plan.length) {
             return;
         }
         const fafLeg = plan.getLeg(fafIndex);
         const destLeg = plan.getLeg(destIndex);
         let fafToDestDistance = 0;
         for (let i = fafIndex + 1; i <= destIndex; i++) {
             const leg = plan.getLeg(i);
             if (leg.calculated !== undefined) {
                 fafToDestDistance += leg.calculated.distance;
             }
         }
         let destAltitude = destLeg.leg.altitude1;
         if (ICAO.isFacility(destLeg.leg.fixIcao)
             && ICAO.getFacilityType(destLeg.leg.fixIcao) !== FacilityType.RWY
             && plan.procedureDetails.destinationRunway !== undefined
             && destLeg.calculated && destLeg.calculated.endLat !== undefined && destLeg.calculated.endLon !== undefined) {
             const runway = plan.procedureDetails.destinationRunway;
             const runwayGeoPoint = new GeoPoint(runway.latitude, runway.longitude);
             destAltitude = runway.elevation;
             fafToDestDistance += UnitType.GA_RADIAN.convertTo(runwayGeoPoint.distance(destLeg.calculated.endLat, destLeg.calculated.endLon), UnitType.METER);
         }
         this.lpvFpa = this.getFpa(fafToDestDistance + 225, fafLeg.leg.altitude1 - destAltitude + 15);
     }
     /**
      * Gets the FAF index in the plan.
      * @param plan The flight plan.
      * @returns The FAF index in the plan.
      */
     getFafIndex(plan) {
         let fafIndex = -1;
         this.iterateReverse(cursor => {
             if (fafIndex === -1 && cursor.legDefinition.leg.fixTypeFlags & FixTypeFlags.FAF) {
                 fafIndex = cursor.legIndex + cursor.segment.offset;
             }
         });
         fafIndex = fafIndex > -1 ? fafIndex : fafIndex = plan.length - 2;
         return fafIndex;
     }
     /**
      * Gets an increase in altitude for a given flight path angle and
      * lateral distance.
      * @param fpa The flight path angle to use, in degrees.
      * @param distance The lateral distance.
      * @returns The increase in altitude.
      */
     altitudeForDistance(fpa, distance) {
         return Math.tan(UnitType.DEGREE.convertTo(fpa, UnitType.RADIAN)) * distance;
     }
     /**
      * Gets a lateral distance for a given altitude increase and flight
      * path angle.
      * @param fpa The flight path angle to use, in degrees.
      * @param altitude The increase in altitude.
      * @returns The lateral distance.
      */
     distanceForAltitude(fpa, altitude) {
         return altitude / Math.tan(UnitType.DEGREE.convertTo(fpa, UnitType.RADIAN));
     }
     /**
      * Gets the flight path angle for a given distance and altitude.
      * @param distance The distance to get the angle for.
      * @param altitude The altitude to get the angle for.
      * @returns The required flight path angle, in degrees.
      */
     getFpa(distance, altitude) {
         return UnitType.RADIAN.convertTo(Math.atan(altitude / distance), UnitType.DEGREE);
     }
     /**
      * Gets the leg index for the current constraint.
      * @param index The current leg index.
      * @returns Index if the current constraint, or -1 if none is found.
      */
     getCurrentConstraintIndex(index) {
         for (let c = this.constraints.length - 1; c >= 0; c--) {
             const constraintIndex = this.constraints[c].index;
             if (constraintIndex >= index) {
                 return constraintIndex;
             }
         }
         return -1;
     }
     /**
      * Gets the VNAV Constraint that contains the supplied leg index.
      * @param legIndex The flight plan leg index to find the constraint for.
      * @returns The VNAV Constraint that contains the input leg index.
      */
     getConstraintFromLegIndex(legIndex) {
         if (this.constraints.length > 0) {
             const constraintIndex = this.constraints.findIndex(c => c.index === this.getCurrentConstraintIndex(legIndex));
             return this.constraints[constraintIndex];
         }
         return undefined;
     }
     /**
      * Gets the first VNAV Constraint Altitude.
      * @returns The first VNAV constraint altitude in the plan.
      */
     getFirstDescentConstraintAltitude() {
         if (this.constraints.length > 0) {
             for (let i = this.constraints.length - 1; i >= 0; i--) {
                 const constraint = this.constraints[i];
                 if (constraint.type !== 'dep') {
                     return constraint.altitude;
                 }
             }
         }
         return undefined;
     }
     /**
      * Gets the VNAV Constraint immediately prior to the constraint that contains a flight plan leg.
      * @param index The global leg index of a flight plan leg.
      * @returns The VNAV Constraint immediately prior to the constraint that contains the flight plan leg with the
      * specified global leg index.
      */
     getPriorConstraintFromLegIndex(index) {
         for (let c = 0; c < this.constraints.length; c++) {
             if (this.constraints[c].index < index) {
                 return this.constraints[c];
             }
         }
         return undefined;
     }
     /**
      * Gets the constraint for a leg altitude restriction.
      * @param leg The leg to get the constraint for.
      * @returns The altitude constraint.
      */
     getConstraintAltitude(leg) {
         switch (leg.verticalData.altDesc) {
             case AltitudeRestrictionType.At:
             case AltitudeRestrictionType.AtOrAbove:
             case AltitudeRestrictionType.AtOrBelow:
                 return leg.verticalData.altitude1;
             case AltitudeRestrictionType.Between:
                 return leg.verticalData.altitude2;
         }
         return Number.POSITIVE_INFINITY;
     }
     /**
      * Iterates through the active flight plan in reverse order.
      * @param each The function to call for each flight plan leg.
      */
     iterateReverse(each) {
         const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
         let segmentIndex = plan.segmentCount - 1;
         let index = 0;
         while (segmentIndex >= 0) {
             const segment = plan.getSegment(segmentIndex);
             let legIndex = segment.legs.length - 1;
             while (legIndex >= 0) {
                 this.cursor.legDefinition = segment.legs[legIndex];
                 this.cursor.legIndex = legIndex;
                 this.cursor.segment = segment;
                 this.cursor.index = index;
                 each(this.cursor);
                 legIndex--;
                 index++;
             }
             segmentIndex--;
         }
     }
     /**
      * Creates a new empty constraint.
      * @param index The leg index of the constraint.
      * @param altitude The altitude of the constraint.
      * @param name The name of the leg for the constraint.
      * @param type The type of constraint.
      * @returns A new empty constraint.
      */
     createConstraint(index, altitude, name, type = 'normal') {
         return {
             index,
             altitude,
             name,
             isTarget: false,
             isPathEnd: false,
             todDistance: 0,
             distance: 0,
             fpa: 0,
             legs: [],
             type,
             isBeyondFaf: false
         };
     }
     /**
      * Creates a new VNAV plan leg.
      * @param segmentIndex The segment index for the leg.
      * @param legIndex The index of the leg within the segment.
      * @param name The name of the leg.
      * @param distance The leg distance.
      * @returns A new VNAV plan leg.
      */
     createLeg(segmentIndex, legIndex, name, distance = 0) {
         return {
             segmentIndex,
             legIndex,
             fpa: 0,
             altitude: 0,
             isUserDefined: false,
             distance: distance,
             isBod: false,
             isAdvisory: true,
             name
         };
     }
     /**
      * Sends an event on the fpl bus that the vertical plan has been updated.
      */
     notify() {
         this.bus.pub('vnavUpdated', true, false);
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * An altitude capture autopilot director.
  */
 class APAltCapDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues are the ap selected values for the autopilot.
      */
     constructor(bus, apValues) {
         this.bus = bus;
         this.apValues = apValues;
         this.groundSpeed = 0;
         this.capturedAltitude = 0;
         this.indicatedAltitude = 0;
         this.verticalSpeed = 0;
         this.initialFpa = 0;
         this.selectedAltitude = 0;
         this.state = DirectorState.Inactive;
         this.bus.getSubscriber().on('ground_speed').withPrecision(0).handle((g) => {
             this.groundSpeed = g;
         });
         const adc = this.bus.getSubscriber();
         adc.on('alt').withPrecision(0).handle((alt) => {
             this.indicatedAltitude = alt;
         });
         adc.on('vs').withPrecision(0).handle((vs) => {
             this.verticalSpeed = vs;
         });
         this.apValues.capturedAltitude.sub((cap) => {
             this.capturedAltitude = Math.round(cap);
         });
         this.apValues.selectedAltitude.sub((alt) => {
             this.selectedAltitude = alt;
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         this.setCaptureFpa(this.verticalSpeed);
         SimVar.SetSimVarValue('AUTOPILOT ALTITUDE LOCK', 'Bool', true);
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         this.state = DirectorState.Armed;
         if (this.onArm !== undefined) {
             this.onArm();
         }
     }
     /**
      * Deactivates this director.
      * @param captured is whether the altitude was captured.
      */
     deactivate(captured = false) {
         this.state = DirectorState.Inactive;
         if (!captured) {
             SimVar.SetSimVarValue('AUTOPILOT ALTITUDE LOCK', 'Bool', false);
         }
         //this.capturedAltitude = 0;
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             this.captureAltitude(this.capturedAltitude);
         }
         if (this.state === DirectorState.Armed) {
             this.tryActivate();
         }
     }
     /**
      * Attempts to activate altitude capture.
      */
     tryActivate() {
         const deviationFromTarget = Math.abs(this.selectedAltitude - this.indicatedAltitude);
         if (deviationFromTarget <= Math.abs(this.verticalSpeed / 6)) {
             this.apValues.capturedAltitude.set(Math.round(this.selectedAltitude));
             this.activate();
         }
     }
     /**
      * Holds a captured altitude.
      * @param targetAltitude is the captured targed altitude
      */
     captureAltitude(targetAltitude) {
         const altCapDeviation = this.indicatedAltitude - targetAltitude;
         const altCapPitchPercentage = Math.min(Math.abs(altCapDeviation) / 100, 1);
         const desiredPitch = (this.initialFpa * altCapPitchPercentage);
         const aoa = SimVar.GetSimVarValue('INCIDENCE ALPHA', SimVarValueType.Degree);
         const targetPitch = aoa + MathUtils.clamp(desiredPitch, -6, 6);
         this.setPitch(targetPitch);
     }
     /**
      * Sets the initial capture FPA from the current vs value when capture is initiated.
      * @param vs target vertical speed.
      */
     setCaptureFpa(vs) {
         if (Math.abs(vs) < 400) {
             const altCapDeviation = this.indicatedAltitude - this.selectedAltitude;
             vs = altCapDeviation > 0 ? -400 : 400;
         }
         this.initialFpa = this.getFpa(UnitType.NMILE.convertTo(this.groundSpeed / 60, UnitType.FOOT), vs);
     }
     /**
      * Gets a desired fpa.
      * @param distance is the distance traveled per minute.
      * @param altitude is the vertical speed per minute.
      * @returns The desired pitch angle.
      */
     getFpa(distance, altitude) {
         return UnitType.RADIAN.convertTo(Math.atan(altitude / distance), UnitType.DEGREE);
     }
     /**
      * Sets the desired AP pitch angle.
      * @param targetPitch The desired AP pitch angle.
      */
     setPitch(targetPitch) {
         if (isFinite(targetPitch)) {
             SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, -targetPitch);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * An altitude hold autopilot director.
  */
 class APAltDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues are the ap selected values for the autopilot.
      */
     constructor(bus, apValues) {
         this.bus = bus;
         this.groundSpeed = 0;
         this.capturedAltitude = 0;
         this.indicatedAltitude = 0;
         this.state = DirectorState.Inactive;
         this.bus.getSubscriber().on('ground_speed').withPrecision(0).handle((g) => {
             this.groundSpeed = g;
         });
         this.bus.getSubscriber().on('alt').withPrecision(0).handle((alt) => {
             this.indicatedAltitude = alt;
         });
         apValues.capturedAltitude.sub((cap) => {
             this.capturedAltitude = Math.round(cap);
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT ALTITUDE LOCK', 'Bool', true);
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         this.state = DirectorState.Armed;
         if (this.onArm !== undefined) {
             this.onArm();
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         SimVar.SetSimVarValue('AUTOPILOT ALTITUDE LOCK', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             this.holdAltitude(this.capturedAltitude);
         }
         if (this.state === DirectorState.Armed) {
             this.tryActivate();
         }
     }
     /**
      * Attempts to activate altitude capture.
      */
     tryActivate() {
         const deviationFromTarget = Math.abs(this.capturedAltitude - this.indicatedAltitude);
         if (deviationFromTarget <= 20) {
             this.activate();
         }
     }
     /**
      * Holds a captured altitude.
      * @param targetAltitude is the captured targed altitude
      */
     holdAltitude(targetAltitude) {
         const deltaAlt = this.indicatedAltitude - targetAltitude;
         let setVerticalSpeed = 0;
         const correction = MathUtils.clamp(10 * Math.abs(deltaAlt), 100, 500);
         if (deltaAlt > 10) {
             setVerticalSpeed = 0 - correction;
         }
         else if (deltaAlt < -10) {
             setVerticalSpeed = correction;
         }
         this.setPitch(this.getDesiredPitch(setVerticalSpeed));
     }
     /**
      * Gets a desired pitch from the selected vs value.
      * @param vs target vertical speed.
      * @returns The desired pitch angle.
      */
     getDesiredPitch(vs) {
         //We need the instant AOA and VS here so we're avoiding the bus
         const aoa = SimVar.GetSimVarValue('INCIDENCE ALPHA', SimVarValueType.Degree);
         const desiredPitch = this.getFpa(UnitType.NMILE.convertTo(this.groundSpeed / 60, UnitType.FOOT), vs);
         return NavMath.clamp(aoa + desiredPitch, -10, 10);
     }
     /**
      * Gets a desired fpa.
      * @param distance is the distance traveled per minute.
      * @param altitude is the vertical speed per minute.
      * @returns The desired pitch angle.
      */
     getFpa(distance, altitude) {
         return UnitType.RADIAN.convertTo(Math.atan(altitude / distance), UnitType.DEGREE);
     }
     /**
      * Sets the desired AP pitch angle.
      * @param targetPitch The desired AP pitch angle.
      */
     setPitch(targetPitch) {
         if (isFinite(targetPitch)) {
             SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, -targetPitch);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A Flight Level Change autopilot director.
  */
 class APFLCDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues is the AP selected values subject.
      */
     constructor(bus, apValues) {
         this.bus = bus;
         this._lastTime = 0;
         this.currentIas = 0;
         this.selectedIas = 0;
         this.selectedAltitude = 0;
         this.currentAltitude = 0;
         this.currentPitch = 0;
         this.accelerationController = new PidController(.3, 0, 0.5, 10, -10);
         this.pitchController = new PidController(1.5, 0, 0, 15, -15);
         this.state = DirectorState.Inactive;
         const adc = this.bus.getSubscriber();
         adc.on('alt').withPrecision(0).handle((alt) => {
             this.currentAltitude = alt;
         });
         adc.on('ias').withPrecision(2).handle((ias) => {
             this.currentIas = ias;
         });
         adc.on('pitch_deg').withPrecision(1).handle((pitch) => {
             this.currentPitch = -pitch;
         });
         apValues.selectedIas.sub((ias) => {
             this.selectedIas = ias;
         });
         apValues.selectedAltitude.sub((alt) => {
             this.selectedAltitude = alt;
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         this.initialize();
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT FLIGHT LEVEL CHANGE', 'Bool', true);
         // Make sure we sync the selected IAS when FLC activates.
         SimVar.SetSimVarValue('K:AP_SPD_VAR_SET', 'number', this.currentIas);
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         if (this.state == DirectorState.Inactive) {
             this.activate();
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         SimVar.SetSimVarValue('AUTOPILOT FLIGHT LEVEL CHANGE', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             this.setPitch(this.getDesiredPitch());
         }
     }
     /**
      * Initializes this director on activation.
      */
     initialize() {
         this._lastTime = 0;
         this.accelerationController.reset();
         this.pitchController.reset();
     }
     /**
      * Gets a desired pitch from the selected vs value.
      * @returns The desired pitch angle.
      */
     getDesiredPitch() {
         const time = performance.now() / 1000;
         let dt = time - this._lastTime;
         if (this._lastTime === 0) {
             dt = 0;
         }
         //step 1 - we want to find the IAS error from target and set a target acceleration
         const iasError = this.currentIas - this.selectedIas;
         const targetAcceleration = this.accelerationController.getOutput(dt, -iasError);
         //step 2 - we want to find the current acceleration, feed that to the pid to manage to the target acceleration
         const acceleration = UnitType.FOOT.convertTo(SimVar.GetSimVarValue('ACCELERATION BODY Z', 'feet per second squared'), UnitType.NMILE) * 3600;
         const accelerationError = acceleration - targetAcceleration;
         const pitchCorrection = this.pitchController.getOutput(dt, accelerationError);
         const aoa = SimVar.GetSimVarValue('INCIDENCE ALPHA', SimVarValueType.Degree);
         this._lastTime = time;
         const targetPitch = isNaN(pitchCorrection) ? this.currentPitch - aoa : (this.currentPitch - aoa) + pitchCorrection;
         if (this.selectedAltitude > this.currentAltitude) {
             return MathUtils.clamp(targetPitch + aoa, aoa, 15);
         }
         else {
             return MathUtils.clamp(targetPitch + aoa, -15, aoa);
         }
     }
     /**
      * Sets the desired AP pitch angle.
      * @param targetPitch The desired AP pitch angle.
      */
     setPitch(targetPitch) {
         if (isFinite(targetPitch)) {
             SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, -targetPitch);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A heading autopilot director.
  */
 class APHdgDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues The AP Values from the Autopilot.
      */
     constructor(bus, apValues) {
         this.bus = bus;
         this.currentBankRef = 0;
         this.currentHeading = 0;
         this.selectedHeading = 0;
         this.bankServo = new LinearServo(10);
         this.state = DirectorState.Inactive;
         apValues.selectedHeading.sub((h) => {
             this.selectedHeading = h;
         });
         const adc = this.bus.getSubscriber();
         adc.on('hdg_deg').withPrecision(0).handle((h) => {
             this.currentHeading = h;
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT HEADING LOCK', 'Bool', true);
         this.state = DirectorState.Active;
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         if (this.state == DirectorState.Inactive) {
             this.activate();
         }
     }
     /**
      * Deactivates this director.
      */
     async deactivate() {
         await SimVar.SetSimVarValue('AUTOPILOT HEADING LOCK', 'Bool', false);
         this.state = DirectorState.Inactive;
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             // let bankAngle = this.desiredBank(NavMath.normalizeHeading(this.dtk + interceptAngle), this.xtk);
             this.setBank(this.desiredBank(this.selectedHeading));
         }
     }
     /**
      * Gets a desired bank from a Target Selected Heading.
      * @param targetHeading The target heading.
      * @returns The desired bank angle.
      */
     desiredBank(targetHeading) {
         const turnDirection = NavMath.getTurnDirection(this.currentHeading, targetHeading);
         const headingDiff = Math.abs(NavMath.diffAngle(this.currentHeading, targetHeading));
         //PM Modif
         let baseBank = Math.min(1.25 * headingDiff, 25);
         baseBank *= (turnDirection === 'left' ? 1 : -1);
         return baseBank;
     }
     /**
      * Sets the desired AP bank angle.
      * @param bankAngle The desired AP bank angle.
      */
     setBank(bankAngle) {
         if (isFinite(bankAngle)) {
             this.currentBankRef = this.bankServo.drive(this.currentBankRef, bankAngle);
             SimVar.SetSimVarValue('AUTOPILOT BANK HOLD REF', 'degrees', this.currentBankRef);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * An autopilot pitch director.
  */
 class APPitchDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues are the AP Values subjects.
      */
     constructor(bus, apValues) {
         this.bus = bus;
         this.apValues = apValues;
         this.selectedPitch = 0;
         this.currentPitch = 0;
         this.state = DirectorState.Inactive;
         this.apValues.selectedPitch.sub((p) => {
             this.selectedPitch = p;
         });
         const adc = this.bus.getSubscriber();
         adc.on('pitch_deg').withPrecision(1).handle((p) => {
             this.currentPitch = p;
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         this.apValues.selectedPitch.set(this.currentPitch);
         SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD', 'Bool', true);
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         if (this.state == DirectorState.Inactive) {
             this.activate();
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             this.setPitch(this.selectedPitch);
         }
     }
     /**
      * Sets the desired AP pitch angle.
      * @param targetPitch The desired AP pitch angle.
      */
     setPitch(targetPitch) {
         if (isFinite(targetPitch)) {
             SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, targetPitch);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * An autopilot roll director.
  */
 class APRollDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      */
     constructor(bus) {
         this.bus = bus;
         this.currentBankRef = 0;
         this.desiredBank = 0;
         this.actualBank = 0;
         this.bankServo = new LinearServo(10);
         this.state = DirectorState.Inactive;
         const adc = this.bus.getSubscriber();
         adc.on('roll_deg').withPrecision(1).handle((roll) => {
             this.actualBank = roll;
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         this.desiredBank = this.actualBank;
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT BANK HOLD', 'Bool', true);
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         if (this.state == DirectorState.Inactive) {
             this.activate();
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         this.desiredBank = 0;
         SimVar.SetSimVarValue('AUTOPILOT BANK HOLD', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             this.setBank(this.desiredBank);
         }
     }
     /**
      * Sets the desired AP bank angle.
      * @param bankAngle The desired AP bank angle.
      */
     setBank(bankAngle) {
         if (isFinite(bankAngle)) {
             this.currentBankRef = this.bankServo.drive(this.currentBankRef, bankAngle);
             SimVar.SetSimVarValue('AUTOPILOT BANK HOLD REF', 'degrees', this.currentBankRef);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A vertical speed autopilot director.
  */
 class APVSDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues are the ap selected values for the autopilot.
      */
     constructor(bus, apValues) {
         this.bus = bus;
         this.groundSpeed = 0;
         this.selectedVS = 0;
         this.state = DirectorState.Inactive;
         this.bus.getSubscriber().on('ground_speed').withPrecision(0).handle((g) => {
             this.groundSpeed = g;
         });
         apValues.selectedVerticalSpeed.sub((vs) => {
             this.selectedVS = vs;
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         Coherent.call('AP_VS_VAR_SET_ENGLISH', 1, Simplane.getVerticalSpeed());
         SimVar.SetSimVarValue('AUTOPILOT VERTICAL HOLD', 'Bool', true);
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         if (this.state == DirectorState.Inactive) {
             this.activate();
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         SimVar.SetSimVarValue('AUTOPILOT VERTICAL HOLD', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             this.setPitch(this.getDesiredPitch());
         }
     }
     /**
      * Gets a desired pitch from the selected vs value.
      * @returns The desired pitch angle.
      */
     getDesiredPitch() {
         //We need the instant AOA and VS here so we're avoiding the bus
         const aoa = SimVar.GetSimVarValue('INCIDENCE ALPHA', SimVarValueType.Degree);
         // const vs = SimVar.GetSimVarValue('VERTICAL SPEED', SimVarValueType.FPM);
         // const currentfpa = this.getFpa(this.groundSpeed / 60, vs);
         const desiredPitch = this.getFpa(UnitType.NMILE.convertTo(this.groundSpeed / 60, UnitType.FOOT), this.selectedVS);
         return MathUtils.clamp(aoa + (isNaN(desiredPitch) ? 10 : desiredPitch), -10, 10);
     }
     /**
      * Gets a desired fpa.
      * @param distance is the distance traveled per minute.
      * @param altitude is the vertical speed per minute.
      * @returns The desired pitch angle.
      */
     getFpa(distance, altitude) {
         return UnitType.RADIAN.convertTo(Math.atan(altitude / distance), UnitType.DEGREE);
     }
     /**
      * Sets the desired AP pitch angle.
      * @param targetPitch The desired AP pitch angle.
      */
     setPitch(targetPitch) {
         if (isFinite(targetPitch)) {
             SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, -targetPitch);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
 * A Nav/Loc autopilot director.
 */
 class APNavDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues Is the apValues object.
      * @param mode is the APLateralMode for this instance of the director.
      */
     //PM Modif
     constructor(bus, apValues, mode, lateralInterceptCurve) {
         this.bus = bus;
         this.apValues = apValues;
         this.mode = mode;
         this.lateralInterceptCurve = lateralInterceptCurve;
         this.bankServo = new LinearServo(10);
         this.currentBankRef = 0;
         this.currentHeading = 0;
         this.currentTrack = 0;
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.ppos = new GeoPoint(0, 0);
         this.navLocation = new GeoPoint(NaN, NaN);
         this.isApproachMode = Subject.create(false);
         this.state = DirectorState.Inactive;
         this.monitorEvents();
     }
     /**
      * Activates this director.
      */
     activate() {
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', true);
         this.state = DirectorState.Active;
     }
     /**
      * Arms this director.
      */
     arm() {
         if (this.state === DirectorState.Inactive && this.canArm()) {
             this.state = DirectorState.Armed;
             if (this.onArm !== undefined) {
                 this.onArm();
             }
             SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', true);
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         SimVar.SetSimVarValue('AUTOPILOT NAV1 LOCK', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (!this.canArm()) {
             this.deactivate();
         }
         if (this.state === DirectorState.Armed) {
             if (this.canActivate()) {
                 this.activate();
             }
         }
         if (this.state === DirectorState.Active) {
             this.setBank(this.desiredBank());
         }
     }
     /**
      * Method to check whether the director can arm.
      * @returns Whether or not this director can arm.
      */
     canArm() {
         var _a, _b, _c, _d, _e, _f, _g, _h;
         const typeIsCorrect = ((_a = this.navSource) === null || _a === void 0 ? void 0 : _a.type) === NavSourceType.Nav;
         const index = (_b = this.navSource) === null || _b === void 0 ? void 0 : _b.index;
         if (this.mode === APLateralModes.LOC && typeIsCorrect) {
             const indexIsCorrect = index == ((_c = this.cdi) === null || _c === void 0 ? void 0 : _c.source.index) && ((_d = this.loc) === null || _d === void 0 ? void 0 : _d.isValid) && index == ((_e = this.loc) === null || _e === void 0 ? void 0 : _e.source.index);
             if (indexIsCorrect) {
                 this.isApproachMode.set(true);
                 return true;
             }
         }
         if (this.mode === APLateralModes.VOR && typeIsCorrect) {
             const indexIsCorrect = index == ((_f = this.cdi) === null || _f === void 0 ? void 0 : _f.source.index) && !((_g = this.loc) === null || _g === void 0 ? void 0 : _g.isValid) && index == ((_h = this.obs) === null || _h === void 0 ? void 0 : _h.source.index);
             if (indexIsCorrect) {
                 this.isApproachMode.set(false);
                 return true;
             }
         }
         if (this.mode === APLateralModes.LOC && this.apValues.navToNavLocArm) {
             this.isApproachMode.set(true);
             return true;
         }
         this.isApproachMode.set(false);
         return false;
     }
     /**
      * Method to check whether the director can activate.
      * @returns Whether or not this director can activate.
      */
     canActivate() {
         var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
         const typeIsCorrect = ((_a = this.navSource) === null || _a === void 0 ? void 0 : _a.type) === NavSourceType.Nav;
         const index = (_b = this.navSource) === null || _b === void 0 ? void 0 : _b.index;
         const indexIsCorrect = index == ((_c = this.cdi) === null || _c === void 0 ? void 0 : _c.source.index)
             && ((((_d = this.loc) === null || _d === void 0 ? void 0 : _d.isValid) && index == ((_e = this.loc) === null || _e === void 0 ? void 0 : _e.source.index)) || (!((_f = this.loc) === null || _f === void 0 ? void 0 : _f.isValid) && index == ((_g = this.obs) === null || _g === void 0 ? void 0 : _g.source.index)));
         if (typeIsCorrect && indexIsCorrect && this.cdi !== undefined && this.cdi.deviation !== null && Math.abs(this.cdi.deviation) < 127 && (((_h = this.obs) === null || _h === void 0 ? void 0 : _h.heading) || ((_j = this.loc) === null || _j === void 0 ? void 0 : _j.course))) {
             const dtk = this.loc && this.loc.isValid && this.loc.course ? Units.Radians.toDegrees(this.loc.course) : (_k = this.obs) === null || _k === void 0 ? void 0 : _k.heading;
             if (dtk === null || dtk === undefined) {
                 return false;
             }
             const headingDiff = NavMath.diffAngle(this.currentHeading, dtk);
             const isLoc = (_m = (_l = this.loc) === null || _l === void 0 ? void 0 : _l.isValid) !== null && _m !== void 0 ? _m : false;
             const sensitivity = isLoc ? 1 : .6;
             if (Math.abs(this.cdi.deviation * sensitivity) < 127 && Math.abs(headingDiff) < 110) {
                 return true;
             }
         }
         return false;
     }
     /**
      * Gets a desired bank from the nav input data.
      * @returns The desired bank angle.
      */
     desiredBank() {
         var _a, _b, _c, _d, _e, _f;
         const isLoc = (_b = (_a = this.loc) === null || _a === void 0 ? void 0 : _a.isValid) !== null && _b !== void 0 ? _b : false;
         const hasValidDeviation = this.cdi !== undefined && this.cdi.deviation !== null && Math.abs(this.cdi.deviation) < 127;
         const hasValidObs = this.obs !== undefined && this.obs.heading !== null;
         let zoneOfConfusion = false;
         if (isLoc && !hasValidDeviation) {
             this.deactivate();
             return NaN;
         }
         if (!isLoc && (!hasValidDeviation || !hasValidObs)) {
             if (!this.checkForZoneOfConfusion()) {
                 this.deactivate();
                 return NaN;
             }
             else {
                 zoneOfConfusion = true;
             }
         }
         if (zoneOfConfusion || (this.cdi && this.cdi.deviation !== null)) {
             const xtk = zoneOfConfusion ? 0 : (this.cdi && this.cdi.deviation !== null) ? this.getXtk(this.cdi.deviation, isLoc) : 0;
             const dtk = isLoc && ((_c = this.loc) === null || _c === void 0 ? void 0 : _c.course) !== undefined ? Units.Radians.toDegrees(this.loc.course) : (_d = this.obs) === null || _d === void 0 ? void 0 : _d.heading;
             if (dtk === null || dtk === undefined) {
                 this.deactivate();
                 return NaN;
             }
             //PM Modif: We took a more recent version of the code to improve stability in VOR or LOC mode (pb reproted by the C414 author).
             //PM Modif
             let absInterceptAngle = 0;
             if (this.lateralInterceptCurve !== undefined) {
                 absInterceptAngle = this.lateralInterceptCurve(this.getNavDistance(), ((_f = (_e = this.cdi) === null || _e === void 0 ? void 0 : _e.deviation) !== null && _f !== void 0 ? _f : 0) / 127, this.tas, isLoc);
             }
             else {
                 absInterceptAngle = Math.min(Math.pow(Math.abs(xtk) * 20, 1.35) + (Math.abs(xtk) * 50), 45);
                 if (absInterceptAngle <= 2.5) {
                     absInterceptAngle = NavMath.clamp(Math.abs(xtk * 150), 0, 2.5);
                 }
             }
             const interceptAngle = xtk > 0 ? absInterceptAngle : -1 * absInterceptAngle;
             const desiredTrack = NavMath.normalizeHeading(dtk + interceptAngle);
             const turnDirection = NavMath.getTurnDirection(this.currentTrack, desiredTrack);
             const trackDiff = Math.abs(NavMath.diffAngle(this.currentTrack, desiredTrack));
             let baseBank = Math.min(1.25 * trackDiff, 25);
             baseBank *= (turnDirection === 'left' ? 1 : -1);
             return baseBank;
             // const absInterceptAngle = Math.min(Math.pow(Math.abs(xtk) * 20, 1.35) + (Math.abs(xtk) * 50), isLoc ? 20 : 40);
             // const interceptAngle = xtk > 0 ? absInterceptAngle : -1 * absInterceptAngle;
             // const desiredTrack = NavMath.normalizeHeading(dtk + interceptAngle);
             // const turnDirection = NavMath.getTurnDirection(this.currentTrack, desiredTrack);
             // const headingDiff = Math.abs(NavMath.diffAngle(this.currentTrack, desiredTrack));
             // let baseBank = Math.min(1.25 * headingDiff, 25);
             // if (baseBank <= 2.5) {
             //   baseBank = NavMath.clamp(xtk * -100, -2.5, 2.5);
             // } else {
             //   baseBank *= (turnDirection === 'left' ? 1 : -1);
             // }
             // return baseBank;
         }
         this.deactivate();
         return NaN;
     }
     /**
      * Gets a xtk value from the nav input data.
      * @param deviation is the input deviation value
      * @param isLoc is whether this is a LOC signal.
      * @returns The xtk value.
      */
     getXtk(deviation, isLoc) {
         const scale = isLoc ? 1 : 2;
         const factor = isLoc ? .35 : 1;
         return MathUtils.clamp(this.getNavDistance() * Math.sin(UnitType.DEGREE.convertTo(12, UnitType.RADIAN) * ((factor * deviation) / 127)), -scale, scale);
     }
     /**
      * Gets the lateral distance from PPOS to the nav signal.
      * @returns The distance value in nautical miles.
      */
     getNavDistance() {
         let navDistance = 5;
         if (!isNaN(this.navLocation.lat)) {
             const navPosition = this.geoPointCache[0];
             navPosition.set(this.navLocation);
             const planePosGP = this.geoPointCache[1];
             planePosGP.set(this.ppos);
             navDistance = UnitType.GA_RADIAN.convertTo(planePosGP.distance(navPosition), UnitType.NMILE);
         }
         return Math.abs(navDistance);
     }
     /**
      * Sets the desired AP bank angle.
      * @param bankAngle The desired AP bank angle.
      */
     setBank(bankAngle) {
         if (isFinite(bankAngle)) {
             this.currentBankRef = this.bankServo.drive(this.currentBankRef, bankAngle);
             SimVar.SetSimVarValue('AUTOPILOT BANK HOLD REF', 'degrees', this.currentBankRef);
         }
     }
     /**
      * Checks if we might be getting a wild deviation because of the zone of confusion and allows APNavDirector some time to resolve.
      * @returns Whether we might be in the zone of confusion.
      */
     checkForZoneOfConfusion() {
         if (this.getNavDistance() < 2 && this.cdi !== undefined && this.cdi.deviation !== null) {
             return true;
         }
         return false;
     }
     /**
      * Method to monitor nav events to keep track of NAV related data needed for guidance.
      */
     monitorEvents() {
         const nav = this.bus.getSubscriber();
         nav.on('cdi_deviation').handle(cdi => this.cdi = cdi);
         nav.on('obs_setting').handle(obs => this.obs = obs);
         nav.on('localizer').handle(loc => this.loc = loc);
         nav.on('cdi_select').handle((source) => {
             this.navSource = source;
             if (this.state === DirectorState.Active) {
                 this.deactivate();
             }
         });
         nav.on('navLocation').handle((loc) => {
             this.navLocation.set(loc.lat, loc.long);
         });
         //PM Modif
         const adc = this.bus.getSubscriber();
         adc.on('hdg_deg')
             .withPrecision(0)
             .handle((h) => {
             this.currentHeading = h;
         });
         //PM Modif
         adc.on('tas').handle(s => this.tas = s);
         const gnss = this.bus.getSubscriber();
         gnss.on('gps-position').atFrequency(1).handle((lla) => {
             this.ppos.set(lla.lat, lla.long);
         });
         gnss.on('track_deg_magnetic').withPrecision(0).handle((t) => {
             this.currentTrack = t;
         });
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * An RNAV LPV glidepath autopilot director.
  */
 class APGPDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues are the ap selected values for the autopilot.
      * @param vnavDirector is the vnav director (required to fly LPV approaches)
      */
     constructor(bus, apValues, vnavDirector) {
         this.bus = bus;
         this.apValues = apValues;
         this.vnavDirector = vnavDirector;
         this.lpvDeviation = 0;
         this.state = DirectorState.Inactive;
         vnavDirector.lpvDeviation.sub((dev) => {
             this.lpvDeviation = dev;
         });
         apValues.approachHasGP.sub(v => {
             if (this.state !== DirectorState.Inactive && !v) {
                 this.deactivate();
             }
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         SimVar.SetSimVarValue(VNavSimVars.ApproachMode, SimVarValueType.Number, VNavApproachGuidanceMode.GPActive);
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ACTIVE', 'Bool', true);
         SimVar.SetSimVarValue('AUTOPILOT APPROACH ACTIVE', 'Bool', true);
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ARM', 'Bool', false);
     }
     /**
      * Arms this director.
      */
     arm() {
         if (this.state === DirectorState.Inactive) {
             this.state = DirectorState.Armed;
             SimVar.SetSimVarValue(VNavSimVars.ApproachMode, SimVarValueType.Number, VNavApproachGuidanceMode.GPArmed);
             if (this.onArm !== undefined) {
                 this.onArm();
             }
             SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ARM', 'Bool', true);
             SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ACTIVE', 'Bool', false);
             SimVar.SetSimVarValue('AUTOPILOT APPROACH ACTIVE', 'Bool', true);
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         SimVar.SetSimVarValue(VNavSimVars.ApproachMode, SimVarValueType.Number, VNavApproachGuidanceMode.None);
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ARM', 'Bool', false);
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ACTIVE', 'Bool', false);
         SimVar.SetSimVarValue('AUTOPILOT APPROACH ACTIVE', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Armed) {
             if (this.apValues.lateralActive.get() === APLateralModes.GPSS && this.lpvDeviation <= 100 && this.lpvDeviation >= -15 && this.vnavDirector.calculator.lpvFpa !== 0) {
                 this.activate();
             }
         }
         if (this.state === DirectorState.Active) {
             if (this.apValues.lateralActive.get() !== APLateralModes.GPSS) {
                 this.deactivate();
             }
             this.setPitch(this.getDesiredPitch());
         }
     }
     /**
      * Gets a desired pitch from the selected vs value.
      * @returns The desired pitch angle.
      */
     getDesiredPitch() {
         const fpaPercentage = Math.max(this.lpvDeviation / -100, -1) + 1;
         const desiredPitch = (this.vnavDirector.calculator.lpvFpa * fpaPercentage) * -1;
         //We need the instant AOA here so we're avoiding the bus
         const aoa = SimVar.GetSimVarValue('INCIDENCE ALPHA', SimVarValueType.Degree);
         return aoa + MathUtils.clamp(desiredPitch, -6, 2);
     }
     /**
      * Sets the desired AP pitch angle.
      * @param targetPitch The desired AP pitch angle.
      */
     setPitch(targetPitch) {
         if (isFinite(targetPitch)) {
             SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, -targetPitch);
         }
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * A glideslope autopilot director.
  */
 class APGSDirector {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param apValues is the APValues object from the Autopilot.
      */
     constructor(bus, apValues) {
         this.bus = bus;
         this.apValues = apValues;
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.ppos = new GeoPoint(0, 0);
         this.gsLocation = new GeoPoint(NaN, NaN);
         this.state = DirectorState.Inactive;
         const nav = this.bus.getSubscriber();
         nav.on('glideslope').handle(gs => this.glideslope = gs);
         nav.on('gsLocation').handle((loc) => {
             this.gsLocation.set(loc.lat, loc.long);
         });
         const gnss = this.bus.getSubscriber();
         gnss.on('gps-position').atFrequency(1).handle((lla) => {
             this.ppos.set(lla.lat, lla.long);
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         SimVar.SetSimVarValue(VNavSimVars.ApproachMode, SimVarValueType.Number, VNavApproachGuidanceMode.GSActive);
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ACTIVE', 'Bool', true);
         SimVar.SetSimVarValue('AUTOPILOT APPROACH ACTIVE', 'Bool', true);
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ARM', 'Bool', false);
     }
     /**
      * Arms this director.
      */
     arm() {
         if (this.canArm() && this.state === DirectorState.Inactive) {
             this.state = DirectorState.Armed;
             SimVar.SetSimVarValue(VNavSimVars.ApproachMode, SimVarValueType.Number, VNavApproachGuidanceMode.GSArmed);
             if (this.onArm !== undefined) {
                 this.onArm();
             }
             SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ARM', 'Bool', true);
             SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ACTIVE', 'Bool', false);
             SimVar.SetSimVarValue('AUTOPILOT APPROACH ACTIVE', 'Bool', true);
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         SimVar.SetSimVarValue(VNavSimVars.ApproachMode, SimVarValueType.Number, VNavApproachGuidanceMode.None);
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ARM', 'Bool', false);
         SimVar.SetSimVarValue('AUTOPILOT GLIDESLOPE ACTIVE', 'Bool', false);
         SimVar.SetSimVarValue('AUTOPILOT APPROACH ACTIVE', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Armed) {
             if (this.apValues.lateralActive.get() === APLateralModes.LOC && this.glideslope !== undefined &&
                 this.glideslope.isValid && this.glideslope.deviation <= 0.1 && this.glideslope.deviation >= -0.1) {
                 this.activate();
             }
             if (!this.canArm()) {
                 this.deactivate();
             }
         }
         if (this.state === DirectorState.Active) {
             if (this.apValues.lateralActive.get() !== APLateralModes.LOC) {
                 this.deactivate();
             }
             this.trackGlideslope();
         }
     }
     /**
      * Method to check whether the director can arm.
      * @returns Whether or not this director can arm.
      */
     canArm() {
         if (this.apValues.navToNavLocArm || (this.glideslope !== undefined && this.glideslope.isValid)) {
             return true;
         }
         return false;
     }
     /**
      * Tracks the Glideslope.
      */
     trackGlideslope() {
         if (this.glideslope !== undefined && this.glideslope.isValid) {
             let gsDistance = UnitType.NMILE.convertTo(5, UnitType.METER);
             if (!isNaN(this.gsLocation.lat)) {
                 const gsPosition = this.geoPointCache[0];
                 gsPosition.set(this.gsLocation);
                 const planePosGP = this.geoPointCache[1];
                 planePosGP.set(this.ppos);
                 gsDistance = UnitType.GA_RADIAN.convertTo(planePosGP.distance(gsPosition), UnitType.METER);
             }
             const gainDenominator = MathUtils.clamp((2200 - (0.4 * gsDistance)) / 3000, 0.1, 1);
             const fpaPercentage = Math.max(this.glideslope.deviation / gainDenominator, -1) + 1;
             const desiredPitch = (this.glideslope.gsAngle * fpaPercentage) * -1;
             //We need the instant AOA here so we're avoiding the bus
             const aoa = SimVar.GetSimVarValue('INCIDENCE ALPHA', SimVarValueType.Degree);
             const targetPitch = aoa + MathUtils.clamp(desiredPitch, -6, 6);
             SimVar.SetSimVarValue('AUTOPILOT PITCH HOLD REF', SimVarValueType.Degree, -targetPitch);
         }
         else {
             this.deactivate();
         }
     }
 }
 
 /**
  * A G1000 NXi nav-to-nav manager.
  */
 class G1000NavToNavManager {
     /**
      * Creates an instance of the LateralDirector.
      * @param bus The event bus to use with this instance.
      * @param flightPlanner The flight planner.
      * @param apValues are the ap state values.
      */
     constructor(bus, flightPlanner, apValues) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.apValues = apValues;
         this.onTransferred = () => { };
         this.currentHeading = 0;
         this.approachFrequency = undefined;
         this.nav1Frequency = 0;
         this.nav2Frequency = 0;
         this.isSourceChanging = false;
         /** Index of nav radio that has a localizer and frequency matches loaded approach. */
         this.canArmIndex = 0;
         this.navToNavCompleted = false;
         this.planePos = new GeoPoint(0, 0);
         /**
          * Callback to handle the nav source changed event when received.
          * @param e is the NavSourceId event
          */
         this.handleNavToNavSourceChanged = (e) => {
             if (e.type === NavSourceType.Nav && e.index === this.canArmIndex) {
                 this.onTransferred();
                 this.isSourceChanging = false;
                 if (this.navToNavCdiConsumer !== undefined) {
                     this.navToNavCdiConsumer.off(this.handleNavToNavSourceChanged);
                     this.navToNavCdiConsumer = undefined;
                     this.navToNavCompleted = true;
                     return;
                 }
             }
         };
         this.monitorEvents();
     }
     /** @inheritdoc */
     canLocArm() {
         return this.canArmIndex > 0;
     }
     /** @inheritdoc */
     canLocActivate() {
         if (this.canArmIndex < 1) {
             return false;
         }
         const cdi = this.canArmIndex === 1 ? this.nav1Cdi : this.nav2Cdi;
         const loc = this.canArmIndex === 1 ? this.nav1Localizer : this.nav2Localizer;
         if (cdi && cdi.deviation !== null && Math.abs(cdi.deviation) < 127 && (loc === null || loc === void 0 ? void 0 : loc.course)) {
             const dtk = loc && loc.isValid && loc.course ? Units.Radians.toDegrees(loc.course) : undefined;
             if (dtk === null || dtk === undefined) {
                 return false;
             }
             const headingDiff = NavMath.diffAngle(this.currentHeading, dtk);
             if (cdi.deviation > 0 && cdi.deviation < 65 && headingDiff < 0 && headingDiff > -90) {
                 return true;
             }
             else if (cdi.deviation < 0 && cdi.deviation > -65 && headingDiff > 0 && headingDiff < 90) {
                 return true;
             }
             else if (Math.abs(cdi.deviation) < 35 && Math.abs(headingDiff) < 20) {
                 return true;
             }
         }
         return false;
     }
     /**
      * Updates the canArmIndex after inputs from the event bus or changes in the approach frequency.
      */
     updateState() {
         if (this.approachFrequency !== undefined) {
             //PM Modif the nav1 ad nav2 frequency were never set
             this.nav1Frequency = Math.round(SimVar.GetSimVarValue("NAV ACTIVE FREQUENCY:1", "MHz") * 100) / 100;
             this.nav2Frequency = Math.round(SimVar.GetSimVarValue("NAV ACTIVE FREQUENCY:2", "MHz") * 100) / 100;
             const apprFreq = Math.round(this.approachFrequency.freqMHz * 100) / 100;
             if (apprFreq > 107) {
                 if (apprFreq == this.nav1Frequency && this.nav1Localizer && this.nav1Localizer.isValid) {
                     this.canArmIndex = 1;
                 }
                 else if (apprFreq == this.nav2Frequency && this.nav2Localizer && this.nav2Localizer.isValid) {
                     this.canArmIndex = 2;
                 }
                 else {
                     this.canArmIndex = 0;
                 }
             }
             else {
                 this.canArmIndex = 0;
             }
         }
         else {
             this.canArmIndex = 0;
         }
     }
     /**
      * Updates the nav 1 and nav 2 frequency from the bus.
      * @param radioState A radiostate event.
      */
     updateRadioState(radioState) {
         if (radioState.radioType === RadioType.Nav) {
             switch (radioState.index) {
                 case 1:
                     this.nav1Frequency = Math.round(radioState.activeFrequency * 100) / 100;
                     break;
                 case 2:
                     this.nav2Frequency = Math.round(radioState.activeFrequency * 100) / 100;
                     break;
             }
             this.updateState();
         }
     }
     /**
      * Tries to auto switch the source if criteria are met.
      * @param d is the lnav data from the event bus
      */
     tryAutoSwitchSource(d) {
         var _a, _b, _c;
         const plan = this.flightPlanner.hasActiveFlightPlan() && this.flightPlanner.getActiveFlightPlan();
         const currentLeg = plan && d.currentLegIndex >= 0 && d.currentLegIndex < plan.length ? plan.getLeg(d.currentLegIndex) : undefined;
         if (!this.navToNavCompleted
             && !this.isSourceChanging
             && ((_a = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _a === void 0 ? void 0 : _a.endLat) !== undefined
             && ((_b = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _b === void 0 ? void 0 : _b.endLon) !== undefined) {
             const fafIsActive = (currentLeg.leg.fixTypeFlags & FixTypeFlags.FAF) !== 0;
             const fafDistance = UnitType.GA_RADIAN.convertTo(this.planePos.distance(currentLeg.calculated.endLat, currentLeg.calculated.endLon), UnitType.NMILE);
             if (fafIsActive && fafDistance < 15 && this.canArmIndex > 0 && this.canLocActivate() && ((_c = this.activeSource) === null || _c === void 0 ? void 0 : _c.type) === NavSourceType.Gps) {
                 this.changeSource();
                 this.navToNavCdiConsumer = this.bus.getSubscriber().on('cdi_select');
                 this.navToNavCdiConsumer.handle(this.handleNavToNavSourceChanged);
             }
         }
     }
     /**
      * Method to set the HSI/NAV Source to the Can Arm Index.
      */
     changeSource() {
         const controlPublisher = this.bus.getPublisher();
         const navSource = {
             type: NavSourceType.Nav,
             index: this.canArmIndex
         };
         this.isSourceChanging = true;
         controlPublisher.pub('cdi_src_set', navSource, true);
     }
     /**
      * Method to monitor nav events to keep track of NAV related data needed for guidance.
      */
     monitorEvents() {
         const sub = this.bus.getSubscriber();
         sub.on('setRadioState').handle((state) => {
             this.updateRadioState(state);
         });
         sub.on('setFrequency').handle((frequency) => {
             if (frequency.bank == FrequencyBank.Active && frequency.radio.radioType === RadioType.Nav) {
                 switch (frequency.radio.index) {
                     case 1:
                         this.nav1Frequency = Math.round(frequency.frequency * 100) / 100;
                         break;
                     case 2:
                         this.nav2Frequency = Math.round(frequency.frequency * 100) / 100;
                 }
                 this.updateState();
             }
         });
         sub.on('nav_1_localizer').handle((loc) => {
             this.nav1Localizer = loc;
             this.updateState();
         });
         sub.on('nav_2_localizer').handle((loc) => {
             this.nav2Localizer = loc;
             this.updateState();
         });
         sub.on('nav_1_cdi').handle((cdi) => {
             this.nav1Cdi = cdi;
         });
         sub.on('nav_2_cdi').handle((cdi) => {
             this.nav2Cdi = cdi;
         });
         sub.on('cdi_select').handle((source) => this.activeSource = source);
         sub.on('hdg_deg').withPrecision(0).handle((h) => {
             this.currentHeading = h;
         });
         sub.on('gps-position').handle(lla => { this.planePos.set(lla.lat, lla.long); });
         sub.on('approach_freq_set').handle((v) => {
             this.approachFrequency = v;
             this.navToNavCompleted = false;
             this.updateState();
         });
         sub.on('dataChanged').handle((d) => {
             this.tryAutoSwitchSource(d);
         });
         // const fpl = this.bus.getSubscriber<FlightPlannerEvents>();
         // fpl.on('fplProcDetailsChanged').handle((e: FlightPlanProcedureDetailsEvent) => {
         //   if (e.details.approachIndex !== this.approachProcedureDetails.approachIndex ||
         //     e.details.approachTransitionIndex !== this.approachProcedureDetails.approachTransitionIndex) {
         //     this.approachProcedureDetails.approachIndex = e.details.approachIndex;
         //     this.approachProcedureDetails.approachTransitionIndex = e.details.approachTransitionIndex;
         //     this.navToNavCompleted = false;
         //   }
         // });
         this.apValues.approachIsActive.sub((v) => {
             if (v) {
                 this.navToNavCompleted = false;
             }
         });
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /**
  * An autopilot wing leveler director.
  */
 class APLvlDirector {
     /**
      * Creates an instance of the wing leveler.
      * @param bus The event bus to use with this instance.
      */
     constructor(bus) {
         this.bus = bus;
         this.currentBankRef = 0;
         this.desiredBank = 0;
         this.actualBank = 0;
         this.bankServo = new LinearServo(10);
         this.state = DirectorState.Inactive;
         const adc = this.bus.getSubscriber();
         adc.on('roll_deg').withPrecision(1).handle((roll) => {
             this.actualBank = roll;
         });
     }
     /**
      * Activates this director.
      */
     activate() {
         this.state = DirectorState.Active;
         this.desiredBank = 0;
         if (this.onActivate !== undefined) {
             this.onActivate();
         }
         SimVar.SetSimVarValue('AUTOPILOT WING LEVELER', 'Bool', true);
     }
     /**
      * Arms this director.
      * This director has no armed mode, so it activates immediately.
      */
     arm() {
         if (this.state == DirectorState.Inactive) {
             this.activate();
         }
     }
     /**
      * Deactivates this director.
      */
     deactivate() {
         this.state = DirectorState.Inactive;
         this.desiredBank = 0;
         SimVar.SetSimVarValue('AUTOPILOT WING LEVELER', 'Bool', false);
     }
     /**
      * Updates this director.
      */
     update() {
         if (this.state === DirectorState.Active) {
             this.setBank(this.desiredBank);
         }
     }
     /**
      * Sets the desired AP bank angle.
      * @param bankAngle The desired AP bank angle.
      */
     setBank(bankAngle) {
         if (isFinite(bankAngle)) {
             this.currentBankRef = this.bankServo.drive(this.currentBankRef, bankAngle);
             SimVar.SetSimVarValue('AUTOPILOT BANK HOLD REF', 'degrees', this.currentBankRef);
         }
     }
 }
 
 /**
  * A G1000 NXi autopilot configuration.
  */
 class G1000APConfig {
     /**
      * Instantiates the AP Config for the Autopilot.
      * @param bus is an instance of the Event Bus.
      * @param flightPlanner is an instance of the flight planner.
      */
     constructor(bus, flightPlanner) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.defaultLateralMode = APLateralModes.ROLL;
         this.defaultVerticalMode = APVerticalModes.PITCH;
     }
     /** @inheritdoc */
     createHeadingDirector(apValues) {
         return new APHdgDirector(this.bus, apValues);
     }
     /** @inheritdoc */
     createRollDirector() {
         return new APRollDirector(this.bus);
     }
     /** @inheritdoc */
     createWingLevelerDirector() {
         return new APLvlDirector(this.bus);
     }
     /** @inheritdoc */
     //PM Modif
     createGpssDirector(apValues) {
         return new LNavDirector(this.bus, apValues, this.flightPlanner, this.lnavInterceptCurve.bind(this), true);
     }
     /** @inheritdoc */
     //PM Modif
     createVorDirector(apValues) {
         return new APNavDirector(this.bus, apValues, APLateralModes.VOR, this.navInterceptCurve.bind(this));
     }
     /** @inheritdoc */
     //PM Modif
     createLocDirector(apValues) {
         return new APNavDirector(this.bus, apValues, APLateralModes.LOC, this.navInterceptCurve.bind(this));
     }
     /** @inheritdoc */
     createBcDirector() {
         return undefined;
     }
     /** @inheritdoc */
     createPitchDirector(apValues) {
         return new APPitchDirector(this.bus, apValues);
     }
     /** @inheritdoc */
     createVsDirector(apValues) {
         return new APVSDirector(this.bus, apValues);
     }
     /** @inheritdoc */
     createFlcDirector(apValues) {
         return new APFLCDirector(this.bus, apValues);
     }
     /** @inheritdoc */
     createAltHoldDirector(apValues) {
         return new APAltDirector(this.bus, apValues);
     }
     /** @inheritdoc */
     createAltCapDirector(apValues) {
         return new APAltCapDirector(this.bus, apValues);
     }
     /** @inheritdoc */
     createVNavDirector(apValues) {
         var _a;
         return (_a = this.vnavDirector) !== null && _a !== void 0 ? _a : (this.vnavDirector = new VNavDirector(this.bus, this.flightPlanner, new VNavPathCalculator(this.bus, this.flightPlanner), apValues));
     }
     /** @inheritdoc */
     createGpDirector(apValues) {
         return new APGPDirector(this.bus, apValues, this.createVNavDirector(apValues));
     }
     /** @inheritdoc */
     createGsDirector(apValues) {
         return new APGSDirector(this.bus, apValues);
     }
     /** @inheritdoc */
     createNavToNavManager(apValues) {
         return new G1000NavToNavManager(this.bus, this.flightPlanner, apValues);
     }
     //PM Modif
     /**
      * Calculates intercept angles for radio nav.
      * @param distanceToSource The distance from the plane to the source of the navigation signal, in nautical miles.
      * @param deflection The lateral deflection of the desired track relative to the plane, normalized from `-1` to `1`.
      * Negative values indicate that the desired track is to the left of the plane.
      * @param tas The true airspeed of the plane, in knots.
      * @param isLoc Whether the source of the navigation signal is a localizer. Defaults to `false`.
      * @returns The intercept angle, in degrees, to capture the desired track from the navigation signal.
      */
     navInterceptCurve(distanceToSource, deflection, tas, isLoc) {
         if (isLoc) {
             return this.localizerInterceptCurve(distanceToSource, deflection, tas);
         }
         else {
             // max deflection is 2.5 degrees or 0.0436332 radians
             const fullScaleDeflectionInRadians = 0.0436332;
             return this.defaultInterceptCurve(Math.sin(fullScaleDeflectionInRadians * -deflection) * distanceToSource, tas);
         }
     }
     /**
      * Calculates intercept angles for LNAV.
      * @param dtk The desired track, in degrees true.
      * @param xtk The cross-track error, in nautical miles. Negative values indicate that the plane is to the left of the
      * desired track.
      * @param tas The true airspeed of the plane, in knots.
      * @returns The intercept angle, in degrees, to capture the desired track from the navigation signal.
      */
     lnavInterceptCurve(dtk, xtk, tas) {
         return this.defaultInterceptCurve(xtk, tas);
     }
     /**
      * Calculates intercept angles for localizers.
      * @param distanceToSource The distance from the plane to the localizer, in nautical miles.
      * @param deflection The lateral deflection of the desired track relative to the plane, normalized from `-1` to `1`.
      * Negative values indicate that the desired track is to the left of the plane.
      * @param tas The true airspeed of the plane, in knots.
      * @returns The intercept angle, in degrees, to capture the localizer course.
      */
     localizerInterceptCurve(distanceToSource, deflection, tas) {
         // max deflection is 2.5 degrees or 0.0436332 radians
         const fullScaleDeflectionInRadians = 0.0436332;
         const xtkNM = Math.sin(fullScaleDeflectionInRadians * -deflection) * distanceToSource;
         const xtkMeters = UnitType.NMILE.convertTo(xtkNM, UnitType.METER);
         const xtkMetersAbs = Math.abs(xtkMeters);
         if (xtkMetersAbs < 4) {
             return 0;
         }
         else if (xtkMetersAbs < 250) {
             return NavMath.clamp(Math.abs(xtkNM * 75), 1, 5);
         }
         const turnRadiusMeters = NavMath.turnRadius(tas, 22.5);
         const interceptAngle = this.calculateTurnBasedInterceptAngle(turnRadiusMeters, xtkMeters);
         return NavMath.clamp(interceptAngle, 0, 20);
     }
     /**
      * Calculates non-localizer intercept angles.
      * @param xtk The cross-track error, in nautical miles. Negative values indicate that the plane is to the left of the
      * desired track.
      * @param tas The true airspeed of the plane, in knots.
      * @returns The intercept angle, in degrees, to capture the desired track.
      */
     defaultInterceptCurve(xtk, tas) {
         const xtkMeters = UnitType.NMILE.convertTo(xtk, UnitType.METER);
         const xtkMetersAbs = Math.abs(xtkMeters);
         if (xtkMetersAbs < 250) {
             return NavMath.clamp(Math.abs(xtk * 75), 0, 5);
         }
         const turnRadiusMeters = NavMath.turnRadius(tas, 22.5);
         const interceptAngle = this.calculateTurnBasedInterceptAngle(turnRadiusMeters, xtkMeters);
         return NavMath.clamp(interceptAngle, 0, 45);
     }
     /**
      * Calculates an intercept angle to a track such that the intercept course, projected forward from the plane's
      * position, intercepts the desired track at the same point as a constant-radius turn overlapping the plane's
      * position configured to be tangent to the desired track. This has the effect of producing an intercept angle which
      * guarantees a no-overshoot intercept for all initial ground tracks for which a no-overshoot intercept is possible
      * given the specified turn radius and cross-track error.
      *
      * If the magnitude of the cross-track error is greater than twice the turn radius, no constant-radius turn
      * overlapping the plane's position will be tangent to the desired track; in this case the maximum possible intercept
      * angle of 90 degrees is returned.
      * @param turnRadius The turn radius, in the same units as `xtk`.
      * @param xtk The cross-track error, in the same units as `turnRadius`.
      * @returns The calculated intercept angle, in degrees.
      */
     calculateTurnBasedInterceptAngle(turnRadius, xtk) {
         return UnitType.RADIAN.convertTo(Math.acos(NavMath.clamp((turnRadius - Math.abs(xtk)) / turnRadius, -1, 1)), UnitType.DEGREE) / 2;
     }
 }
 
 /**
  * A G1000 NXi autopilot state manager.
  */
 class G1000APStateManager extends APStateManager {
     constructor() {
         super(...arguments);
         this.vsLastPressed = 0;
     }
     /** @inheritdoc */
     onAPListenerRegistered() {
         super.onAPListenerRegistered();
         const hEvent = this.bus.getSubscriber();
         hEvent.on('hEvent').handle((e) => {
             if (e === 'AS1000_VNAV_TOGGLE') {
                 this.toggleVnav();
             }
         });
     }
     /** @inheritdoc */
     setupKeyIntercepts(manager) {
         //alt modes
         manager.interceptKey('AP_ALT_HOLD', false);
         manager.interceptKey('AP_ALT_HOLD', false);
         manager.interceptKey('AP_ALT_HOLD_ON', false);
         manager.interceptKey('AP_ALT_HOLD_OFF', false);
         manager.interceptKey('AP_PANEL_ALTITUDE_HOLD', false);
         manager.interceptKey('AP_PANEL_ALTITUDE_ON', false);
         manager.interceptKey('AP_PANEL_ALTITUDE_OFF', false);
         manager.interceptKey('AP_PANEL_ALTITUDE_SET', false);
         //vs modes
         manager.interceptKey('AP_PANEL_VS_HOLD', false);
         manager.interceptKey('AP_PANEL_VS_ON', false);
         manager.interceptKey('AP_PANEL_VS_OFF', false);
         manager.interceptKey('AP_PANEL_VS_SET', false);
         manager.interceptKey('AP_VS_HOLD', false);
         manager.interceptKey('AP_VS_ON', false);
         manager.interceptKey('AP_VS_OFF', false);
         manager.interceptKey('AP_VS_SET', false);
         //pitch modes
         manager.interceptKey('AP_ATT_HOLD', false);
         manager.interceptKey('AP_ATT_HOLD_ON', false);
         manager.interceptKey('AP_ATT_HOLD_OFF', false);
         manager.interceptKey('AP_PITCH_LEVELER', false);
         manager.interceptKey('AP_PITCH_LEVELER_ON', false);
         manager.interceptKey('AP_PITCH_LEVELER_OFF', false);
         //roll modes
         manager.interceptKey('AP_BANK_HOLD', false);
         manager.interceptKey('AP_BANK_HOLD_ON', false);
         manager.interceptKey('AP_BANK_HOLD_OFF', false);
         manager.interceptKey('AP_WING_LEVELER', false);
         manager.interceptKey('AP_WING_LEVELER_ON', false);
         manager.interceptKey('AP_WING_LEVELER_OFF', false);
         //flc modes
         manager.interceptKey('FLIGHT_LEVEL_CHANGE', false);
         manager.interceptKey('FLIGHT_LEVEL_CHANGE_ON', false);
         manager.interceptKey('FLIGHT_LEVEL_CHANGE_OFF', false);
         //nav modes
         manager.interceptKey('AP_NAV1_HOLD', false);
         manager.interceptKey('AP_NAV1_HOLD_ON', false);
         manager.interceptKey('AP_NAV1_HOLD_OFF', false);
         manager.interceptKey('AP_NAV_SELECT_SET', false);
         manager.interceptKey('TOGGLE_GPS_DRIVES_NAV1', false);
         //hdg modes
         manager.interceptKey('AP_HDG_HOLD', false);
         manager.interceptKey('AP_HDG_HOLD_ON', false);
         manager.interceptKey('AP_HDG_HOLD_OFF', false);
         manager.interceptKey('AP_PANEL_HEADING_HOLD', false);
         manager.interceptKey('AP_PANEL_HEADING_ON', false);
         manager.interceptKey('AP_PANEL_HEADING_OFF', false);
         manager.interceptKey('AP_PANEL_HEADING_SET', false);
         //bank modes
         manager.interceptKey('AP_BANK_HOLD', false);
         manager.interceptKey('AP_BANK_HOLD_ON', false);
         manager.interceptKey('AP_BANK_HOLD_OFF', false);
         //appr modes
         manager.interceptKey('AP_LOC_HOLD', false);
         manager.interceptKey('AP_LOC_HOLD_ON', false);
         manager.interceptKey('AP_LOC_HOLD_OFF', false);
         manager.interceptKey('AP_APR_HOLD', false);
         manager.interceptKey('AP_APR_HOLD_ON', false);
         manager.interceptKey('AP_APR_HOLD_OFF', false);
         manager.interceptKey('AP_BC_HOLD', false);
         manager.interceptKey('AP_BC_HOLD_ON', false);
         manager.interceptKey('AP_BC_HOLD_OFF', false);
         //baro set intercept
         manager.interceptKey('BAROMETRIC', true);
     }
     /** @inheritdoc */
     handleKeyIntercepted({ key, value }) {
         const controlEventPub = this.bus.getPublisher();
         switch (key) {
             case 'AP_NAV1_HOLD':
                 // PM MODIF
                 //        this.sendApModeEvent(APModeType.LATERAL, APLateralModes.NAV);
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.NAV, !SimVar.GetSimVarValue('AUTOPILOT NAV1 LOCK', SimVarValueType.Bool));
                 break;
             case 'AP_NAV1_HOLD_ON':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.NAV, true);
                 break;
             case 'AP_NAV1_HOLD_OFF':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.NAV, false);
                 break;
             case 'AP_LOC_HOLD':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.LOC);
                 break;
             case 'AP_LOC_HOLD_ON':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.LOC, true);
                 break;
             case 'AP_LOC_HOLD_OFF':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.LOC, false);
                 break;
             case 'AP_APR_HOLD':
                 // PM MODIF
                 //        this.sendApModeEvent(APModeType.APPROACH);
                 this.sendApModeEvent(APModeType.APPROACH, undefined, !SimVar.GetSimVarValue('AUTOPILOT APPROACH HOLD', SimVarValueType.Bool));
                 break;
             case 'AP_APR_HOLD_ON':
                 this.sendApModeEvent(APModeType.APPROACH, undefined, true);
                 break;
             case 'AP_APR_HOLD_OFF':
                 this.sendApModeEvent(APModeType.APPROACH, undefined, false);
                 break;
             case 'AP_BC_HOLD':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.BC);
                 break;
             case 'AP_BC_HOLD_ON':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.BC, true);
                 break;
             case 'AP_BC_HOLD_OFF':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.BC, false);
                 break;
             case 'AP_HDG_HOLD':
             case 'AP_PANEL_HEADING_HOLD':
                 // PM MODIF
                 //        this.sendApModeEvent(APModeType.LATERAL, APLateralModes.HEADING);
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.HEADING, !SimVar.GetSimVarValue('AUTOPILOT HEADING LOCK', SimVarValueType.Bool));
                 break;
             case 'AP_PANEL_HEADING_ON':
             case 'AP_HDG_HOLD_ON':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.HEADING, true);
                 break;
             case 'AP_PANEL_HEADING_OFF':
             case 'AP_HDG_HOLD_OFF':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.HEADING, false);
                 break;
             case 'AP_PANEL_HEADING_SET':
                 if (value !== undefined) {
                     this.sendApModeEvent(APModeType.LATERAL, APLateralModes.HEADING, value === 1 ? true : false);
                 }
                 break;
             case 'AP_BANK_HOLD':
             case 'AP_BANK_HOLD_ON':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.ROLL, true);
                 break;
             case 'AP_WING_LEVELER':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.LEVEL);
                 break;
             case 'AP_WING_LEVELER_ON':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.LEVEL, true);
                 break;
             case 'AP_WING_LEVELER_OFF':
                 this.sendApModeEvent(APModeType.LATERAL, APLateralModes.LEVEL, false);
                 break;
             case 'AP_PANEL_VS_HOLD':
             case 'AP_VS_HOLD':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.VS);
                 break;
             case 'AP_PANEL_VS_ON':
             case 'AP_VS_ON':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.VS, true);
                 break;
             case 'AP_PANEL_VS_OFF':
             case 'AP_VS_OFF':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.VS, false);
                 break;
             case 'AP_PANEL_VS_SET':
             case 'AP_VS_SET':
                 // TODO Remove this when the Bravo default mapping is fixed.
                 if (value !== undefined && this.vsLastPressed < Date.now() - 100) {
                     this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.VS, value === 1 ? true : false);
                 }
                 this.vsLastPressed = Date.now();
                 break;
             case 'AP_ALT_HOLD':
             case 'AP_PANEL_ALTITUDE_HOLD':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.ALT);
                 break;
             case 'AP_ALT_HOLD_ON':
             case 'AP_PANEL_ALTITUDE_ON':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.ALT, true);
                 break;
             case 'AP_ALT_HOLD_OFF':
             case 'AP_PANEL_ALTITUDE_OFF':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.ALT, false);
                 break;
             case 'AP_PANEL_ALTITUDE_SET':
                 if (value !== undefined) {
                     this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.ALT, value === 1 ? true : false);
                 }
                 break;
             case 'FLIGHT_LEVEL_CHANGE':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.FLC);
                 break;
             case 'FLIGHT_LEVEL_CHANGE_ON':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.FLC, true);
                 break;
             case 'FLIGHT_LEVEL_CHANGE_OFF':
                 this.sendApModeEvent(APModeType.VERTICAL, APVerticalModes.FLC, false);
                 break;
             case 'AP_NAV_SELECT_SET':
                 if (value !== undefined && value >= 1 && value <= 2) {
                     controlEventPub.pub('cdi_src_set', { type: NavSourceType.Nav, index: value }, true);
                 }
                 break;
             case 'TOGGLE_GPS_DRIVES_NAV1':
                 controlEventPub.pub('cdi_src_gps_toggle', true, true);
                 break;
             case 'BAROMETRIC':
                 controlEventPub.pub('baro_set', true, true);
                 break;
         }
     }
     /** @inheritdoc */
     onBeforeInitialize() {
         SimVar.SetSimVarValue('L:WT1000_AP_G1000_INSTALLED', SimVarValueType.Bool, true);
     }
 }
 
 /**
  * A class that calculates displayed lateral nav data information from
  * supplied lnav values.
  */
 class NavdataComputer {
     /**
      * Creates a new instance of the NavdataComputer.
      * @param bus The event bus to use with this instance.
      * @param flightPlanner The flight planner to use with this instance.
      * @param facilityLoader The facility loader to use with this instance.
      */
     constructor(bus, flightPlanner, facilityLoader) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.facilityLoader = facilityLoader;
         this.vec3Cache = [new Float64Array(3)];
         this.geoPointCache = [new GeoPoint(0, 0), new GeoPoint(0, 0), new GeoPoint(0, 0)];
         this.geoCircleCache = [new GeoCircle(new Float64Array(3), 0)];
         this.planePos = new GeoPoint(0, 0);
         this.obsAvailable = false;
         this.approachDetails = {
             approachLoaded: false,
             approachType: ApproachType.APPROACH_TYPE_UNKNOWN,
             approachRnavType: RnavTypeFlags.None,
             approachIsActive: false,
             approachIsCircling: false
         };
         const sub = this.bus.getSubscriber();
         //PM Modif OBS simvar values
         this.isObsActive = ConsumerSubject.create(sub.on('gps_obs_active'), false);
         sub.on('gps-position').handle(lla => { this.planePos.set(lla.lat, lla.long); });
         sub.on('dataChanged').handle(this.onDataChange.bind(this));
         sub.on('fplOriginDestChanged').handle(this.flightPlanOriginDestChanged.bind(this));
         sub.on('approach_details_set').handle(d => { this.approachDetails = d; });
     }
     /**
      * A callback fired when the LNAV data changes.
      * @param data The new LNAV data.
      */
     onDataChange(data) {
         const magVar = MagVar.get(this.planePos);
         this.computeTrackingVars(data, magVar);
         this.computeCDIScaling(data);
         //SimVar.SetSimVarValue(LNavVars.CurrentVector, 'number', data.vectorIndex);
         if (this.flightPlanner.hasActiveFlightPlan()) {
             const plan = this.flightPlanner.getActiveFlightPlan();
             const currentLeg = data.currentLegIndex >= 0 && data.currentLegIndex < plan.length ? plan.getLeg(data.currentLegIndex) : undefined;
             if (currentLeg) {
                 this.setObsAvailable(currentLeg.leg.type);
             }
         }
     }
     /**
      * A callback fired when the origin or destination changes in the flight plan.
      * @param e The event that was captured.
      */
     flightPlanOriginDestChanged(e) {
         if (e.airport !== undefined) {
             this.facilityLoader.getFacility(FacilityType.Airport, e.airport).then(fac => {
                 switch (e.type) {
                     case OriginDestChangeType.OriginAdded:
                         this.originFacility = fac;
                         break;
                     case OriginDestChangeType.DestinationAdded:
                         this.destinationFacility = fac;
                         break;
                 }
             });
         }
         if (e.type === OriginDestChangeType.OriginRemoved) {
             this.originFacility = undefined;
         }
         if (e.type === OriginDestChangeType.DestinationRemoved) {
             this.destinationFacility = undefined;
         }
     }
     /**
      * Computes the nav tracking data, such as XTK, DTK, and distance to turn.
      * @param data The LNAV data to compute with.
      * @param magVar The computed current location magvar.
      */
     computeTrackingVars(data, magVar) {
         var _a, _b, _c;
         let xtk = 0;
         let dtk = 0;
         let distance = 0;
         let distanceToTurn = 0;
         let totalDistance = 0;
         let isTracking = false;
         const plan = this.flightPlanner.hasActiveFlightPlan() && this.flightPlanner.getActiveFlightPlan();
         const currentLeg = plan && data.currentLegIndex >= 0 && data.currentLegIndex < plan.length ? plan.getLeg(data.currentLegIndex) : undefined;
         const nextLeg = plan && data.nextLegIndex >= 0 && data.nextLegIndex < plan.length ? plan.getLeg(data.nextLegIndex) : undefined;
         if (data.transitionMode === TransitionMode.Egress && ((_a = nextLeg === null || nextLeg === void 0 ? void 0 : nextLeg.calculated) === null || _a === void 0 ? void 0 : _a.flightPath.length)) {
             const circle = FlightPathUtils.setGeoCircleFromVector(nextLeg.calculated.flightPath[0], this.geoCircleCache[0]);
             xtk = UnitType.GA_RADIAN.convertTo(circle.distance(this.planePos), UnitType.NMILE);
             dtk = (_b = nextLeg.calculated.initialDtk) !== null && _b !== void 0 ? _b : 0;
             distance = this.getActiveDistance(nextLeg, this.planePos);
             distanceToTurn = this.getDistanceToTurn(nextLeg, this.planePos);
             isTracking = true;
         }
         else if ((_c = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _c === void 0 ? void 0 : _c.flightPath.length) {
             const legCalc = currentLeg.calculated;
             let vector = legCalc.flightPath[data.transitionMode === TransitionMode.None
                 ? data.vectorIndex
                 : data.transitionMode === TransitionMode.Ingress
                     ? 0
                     : legCalc.flightPath.length - 1];
             switch (currentLeg.leg.type) {
                 case LegType.DF:
                     vector = legCalc.flightPath[legCalc.flightPath.length - 1];
                     break;
                 case LegType.HM:
                 case LegType.HF:
                 case LegType.HA: {
                     const vectors = data.transitionMode === TransitionMode.None
                         ? LNavDirector.getVectorsForTransitionMode(legCalc, data.transitionMode, data.isSuspended)
                         : legCalc.flightPath;
                     const searchStartIndex = data.transitionMode === TransitionMode.None
                         ? data.vectorIndex
                         : data.transitionMode === TransitionMode.Ingress
                             ? 0
                             : 3;
                     for (let i = searchStartIndex; i < vectors.length; i++) {
                         const holdVector = vectors[i];
                         if (BitFlags.isAll(holdVector.flags, FlightPathVectorFlags.HoldLeg)) {
                             vector = holdVector;
                             break;
                         }
                     }
                 }
             }
             if (vector !== undefined) {
                 const circle = FlightPathUtils.setGeoCircleFromVector(vector, this.geoCircleCache[0]);
                 const alongTrackPos = circle.closest(this.planePos, this.vec3Cache[0]);
                 xtk = UnitType.GA_RADIAN.convertTo(circle.distance(this.planePos), UnitType.NMILE);
                 dtk = MagVar.trueToMagnetic(circle.bearingAt(alongTrackPos), magVar);
                 distance = this.getActiveDistance(currentLeg, this.planePos);
                 distanceToTurn = this.getDistanceToTurn(currentLeg, this.planePos);
                 totalDistance = this.getTotalDistance(distance);
                 isTracking = true;
             }
         }
         //PM Modif OBS simvar values
         // Don't publish these values if OBS is active, since the OBS director will do that for us.
         if (!this.isObsActive.get()) {
             SimVar.SetSimVarValue(LNavVars.DTK, SimVarValueType.Degree, dtk);
             SimVar.SetSimVarValue(LNavVars.XTK, SimVarValueType.NM, xtk);
         }
         SimVar.SetSimVarValue(LNavVars.Distance, SimVarValueType.Meters, distance);
         SimVar.SetSimVarValue(LNavVars.DistanceToTurn, SimVarValueType.Meters, distanceToTurn);
         SimVar.SetSimVarValue(LNavVars.DistanceToDestination, SimVarValueType.Meters, totalDistance);
         SimVar.SetSimVarValue(LNavVars.IsTrackingWaypoint, SimVarValueType.Bool, isTracking);
     }
     /**
      * Computes the CDI scaling for the given LNAV data.
      * @param data The LNAV data to compute with.
      */
     computeCDIScaling(data) {
         var _a, _b, _c, _d, _e, _f;
         let scaling = 2.0;
         let scalingLabel = CDIScaleLabel.Enroute;
         const flightPlan = this.flightPlanner.hasActiveFlightPlan() ? this.flightPlanner.getActiveFlightPlan() : undefined;
         if (flightPlan && flightPlan.length > 0 && flightPlan.activeLateralLeg < flightPlan.length) {
             const activeSegment = flightPlan.getSegment(flightPlan.getSegmentIndex(flightPlan.activeLateralLeg));
             let previousLeg;
             try {
                 previousLeg = flightPlan.getLeg(flightPlan.activeLateralLeg - 1);
             }
             catch ( /*Do nothing*/_g) { /*Do nothing*/ }
             //We are currently in the departure segment
             if (activeSegment.segmentType === FlightPlanSegmentType.Departure) {
                 scaling = 0.3;
                 scalingLabel = CDIScaleLabel.Departure;
                 const prevLegType = previousLeg === null || previousLeg === void 0 ? void 0 : previousLeg.leg.type;
                 if (prevLegType && prevLegType !== LegType.IF && prevLegType !== LegType.CA && prevLegType !== LegType.FA) {
                     scaling = 1.0;
                     scalingLabel = CDIScaleLabel.Terminal;
                 }
             }
             //We are not in the departure segment any longer
             if (this.originFacility !== undefined && activeSegment.segmentType !== FlightPlanSegmentType.Departure) {
                 const distance = UnitType.GA_RADIAN.convertTo(this.planePos.distance(this.originFacility), UnitType.NMILE);
                 scaling = 2.0 - NavMath.clamp(31 - distance, 0, 1);
                 if (distance <= 30) {
                     scalingLabel = CDIScaleLabel.Terminal;
                 }
             }
             //Check for distance to destination
             if (this.destinationFacility !== undefined && activeSegment.segmentType !== FlightPlanSegmentType.Departure) {
                 const distance = UnitType.GA_RADIAN.convertTo(this.planePos.distance(this.destinationFacility), UnitType.NMILE);
                 scaling = 2.0 - NavMath.clamp(31 - distance, 0, 1);
                 if (distance <= 30) {
                     scalingLabel = CDIScaleLabel.Terminal;
                 }
             }
             //Check for distance from arrival start
             if (activeSegment.segmentType === FlightPlanSegmentType.Arrival && activeSegment.legs.length > 1) {
                 const firstArrivalLeg = activeSegment.legs[1];
                 //If we're going from the start of the arrival (i.e. the second leg)
                 if (flightPlan.activeLateralLeg === activeSegment.offset + 1
                     && ((_a = firstArrivalLeg.calculated) === null || _a === void 0 ? void 0 : _a.startLat) !== undefined
                     && ((_b = firstArrivalLeg.calculated) === null || _b === void 0 ? void 0 : _b.startLon) !== undefined
                     && ((_c = firstArrivalLeg.calculated) === null || _c === void 0 ? void 0 : _c.endLat) !== undefined
                     && ((_d = firstArrivalLeg.calculated) === null || _d === void 0 ? void 0 : _d.endLon) !== undefined) {
                     const start = this.geoPointCache[1].set(firstArrivalLeg.calculated.startLat, firstArrivalLeg.calculated.startLon);
                     const end = this.geoPointCache[2].set(firstArrivalLeg.calculated.endLat, firstArrivalLeg.calculated.endLon);
                     const distance = NavMath.alongTrack(start, end, this.planePos);
                     scaling = 2.0 - NavMath.clamp(distance, 0, 1);
                     if (distance >= 1) {
                         scalingLabel = CDIScaleLabel.Terminal;
                     }
                 }
                 else if (flightPlan.activeLateralLeg > activeSegment.offset + 1) {
                     scaling = 1.0;
                     scalingLabel = CDIScaleLabel.Terminal;
                 }
             }
             //We are in the approach
             if (activeSegment.segmentType === FlightPlanSegmentType.Approach) {
                 scaling = 1.0;
                 scalingLabel = CDIScaleLabel.Terminal;
                 const fafIndex = this.getFafIndex(activeSegment);
                 const currentLeg = data.currentLegIndex >= 0 && data.currentLegIndex < flightPlan.length ? flightPlan.getLeg(data.currentLegIndex) : undefined;
                 if (fafIndex !== undefined && flightPlan.activeLateralLeg === fafIndex) {
                     const fafCalc = flightPlan.getLeg(fafIndex).calculated;
                     if ((fafCalc === null || fafCalc === void 0 ? void 0 : fafCalc.endLat) !== undefined && (fafCalc === null || fafCalc === void 0 ? void 0 : fafCalc.endLon) !== undefined) {
                         const distance = UnitType.GA_RADIAN.convertTo(this.planePos.distance(fafCalc.endLat, fafCalc.endLon), UnitType.NMILE);
                         scaling = 1.0 - (0.7 * (NavMath.clamp(2 - distance, 0, 2) / 2));
                         if (distance <= 2) {
                             scalingLabel = this.getApproachCdiScale();
                         }
                     }
                 }
                 else if (((_e = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _e === void 0 ? void 0 : _e.endLat) && ((_f = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _f === void 0 ? void 0 : _f.endLon) && fafIndex !== undefined && flightPlan.activeLateralLeg > fafIndex) {
                     if (currentLeg && BitFlags.isAll(currentLeg.flags, LegDefinitionFlags.MissedApproach)) {
                         scaling = 1.0;
                         scalingLabel = CDIScaleLabel.MissedApproach;
                     }
                     else {
                         const legLength = currentLeg.calculated.distance;
                         const distance = UnitType.GA_RADIAN.convertTo(this.planePos.distance(currentLeg.calculated.endLat, currentLeg.calculated.endLon), UnitType.NMILE);
                         scaling = 0.3 - (0.112 * (NavMath.clamp(legLength - distance, 0, legLength) / legLength));
                         scalingLabel = this.getApproachCdiScale();
                     }
                 }
             }
         }
         SimVar.SetSimVarValue(LNavVars.CDIScale, SimVarValueType.NM, scaling);
         SimVar.SetSimVarValue(LNavVars.CDIScaleLabel, SimVarValueType.Number, scalingLabel);
     }
     /**
      * Gets the index of the FAF in a segment.
      * @param segment The segment to search.
      * @returns The index of the FAF if found.
      */
     getFafIndex(segment) {
         let fafLeg = segment.legs[segment.legs.length - 2];
         let fafIndex = segment.legs.length - 2;
         for (let i = 0; i < segment.legs.length; i++) {
             const leg = segment.legs[i];
             if (leg.leg.fixTypeFlags & FixTypeFlags.FAF) {
                 fafLeg = leg;
                 fafIndex = i;
                 break;
             }
         }
         if (fafLeg !== undefined) {
             return segment.offset + fafIndex;
         }
     }
     /**
      * Gets the active distance from the plane position to the leg end.
      * @param leg The leg to get the distance for.
      * @param pos The current plane position.
      * @returns The distance, in meters.
      */
     getActiveDistance(leg, pos) {
         var _a;
         const finalVector = (_a = leg.calculated) === null || _a === void 0 ? void 0 : _a.flightPath[leg.calculated.flightPath.length - 1];
         if (finalVector !== undefined) {
             return UnitType.GA_RADIAN.convertTo(pos.distance(finalVector.endLat, finalVector.endLon), UnitType.METER);
         }
         return 0;
     }
     /**
      * Gets the total distance from the plane position to the destination leg.
      * @param activeDistance The distance from the present position to the current leg end.
      * @returns The distance, in meters.
      */
     getTotalDistance(activeDistance) {
         var _a, _b;
         const plan = this.flightPlanner.getActiveFlightPlan();
         const activeLegCumulativeDistance = plan.activeLateralLeg < plan.length
             ? (_a = plan.getLeg(plan.activeLateralLeg).calculated) === null || _a === void 0 ? void 0 : _a.cumulativeDistanceWithTransitions
             : undefined;
         let lastLegIndex = Math.max(0, plan.length - 1);
         if (plan.length > 1) {
             const finalSegment = plan.getSegment(plan.getSegmentIndex(plan.length - 1));
             for (let i = finalSegment.legs.length - 1; i >= 0; i--) {
                 const leg = finalSegment.legs[i];
                 if (!BitFlags.isAll(leg.flags, LegDefinitionFlags.MissedApproach)) {
                     lastLegIndex = i + finalSegment.offset;
                     break;
                 }
             }
         }
         const destinationLegCumulativeDistance = (_b = plan.getLeg(lastLegIndex).calculated) === null || _b === void 0 ? void 0 : _b.cumulativeDistanceWithTransitions;
         if (destinationLegCumulativeDistance !== undefined && activeLegCumulativeDistance !== undefined && activeDistance >= 0) {
             return destinationLegCumulativeDistance - activeLegCumulativeDistance + activeDistance;
         }
         return 0;
     }
     /**
      * Gets the active distance from the plane position to the next leg turn.
      * @param leg The leg to get the distance for.
      * @param pos The current plane position.
      * @returns The distance, in meters.
      */
     getDistanceToTurn(leg, pos) {
         if (leg.calculated !== undefined) {
             const firstEgressVector = leg.calculated.egress[0];
             if (firstEgressVector) {
                 return UnitType.GA_RADIAN.convertTo(pos.distance(firstEgressVector.startLat, firstEgressVector.startLon), UnitType.METER);
             }
             else {
                 return this.getActiveDistance(leg, pos);
             }
         }
         return 0;
     }
     /**
      * Checks and sets whether obs is available on the current leg and sends an event over the bus to update the softkeymenu.
      * @param currentLegType The current leg type.
      */
     setObsAvailable(currentLegType) {
         let newObsAvailable = false;
         switch (currentLegType) {
             case LegType.AF:
             case LegType.CD:
             case LegType.CF:
             case LegType.CR:
             case LegType.DF:
             case LegType.IF:
             case LegType.RF:
             case LegType.TF:
                 newObsAvailable = true;
                 break;
         }
         if (newObsAvailable !== this.obsAvailable) {
             this.obsAvailable = newObsAvailable;
             this.bus.getPublisher().pub('obs_available', this.obsAvailable, true, true);
         }
     }
     /**
      * Checks and returns the CDI Scale when in an approach.
      * @returns The CDIScaleLabel appropriate for the approach.
      */
     getApproachCdiScale() {
         switch (this.approachDetails.approachType) {
             case ApproachType.APPROACH_TYPE_GPS:
             case ApproachType.APPROACH_TYPE_RNAV:
                 switch (this.approachDetails.approachRnavType) {
                     case RnavTypeFlags.LPV:
                         return CDIScaleLabel.LPV;
                     case RnavTypeFlags.LP:
                         return this.approachDetails.approachIsCircling ? CDIScaleLabel.LP : CDIScaleLabel.LPPlusV;
                     case RnavTypeFlags.LNAVVNAV:
                         return CDIScaleLabel.LNavVNav;
                 }
                 return this.approachDetails.approachIsCircling ? CDIScaleLabel.LNav : CDIScaleLabel.LNavPlusV;
             case AdditionalApproachType.APPROACH_TYPE_VISUAL:
                 return CDIScaleLabel.Visual;
             default:
                 return CDIScaleLabel.Terminal;
         }
     }
 }
 
 /** A control publisher that handles G1000 events too. */
 class G1000ControlPublisher extends BasePublisher {
     /**
      * Create a ControlPublisher.
      * @param bus The EventBus to publish to.
      */
     constructor(bus) {
         super(bus);
     }
     /**
      * Publish a control event.
      * @param event The event from ControlEvents.
      * @param value The value of the event.
      */
     publishEvent(event, value) {
         this.publish(event, value, true);
     }
 }
 
 /// <reference types="msfstypes/JS/common" />
 /**
  * An instrument that gathers localizer and glideslope information for use by
  * the AP systems.
  */
 class APRadioNavInstrument {
     /**
      * Creates an instance of the APRadioNavInstrument.
      * @param bus The event bus to use with this instance.
      */
     constructor(bus) {
         this.bus = bus;
         this.navRadioData = {
             1: {
                 glideslope: this.createEmptyGlideslope({ index: 1, type: NavSourceType.Nav }),
                 localizer: this.createEmptyLocalizer({ index: 1, type: NavSourceType.Nav }),
                 cdi: this.createEmptyCdi({ index: 1, type: NavSourceType.Nav }),
                 obs: this.createEmptyObs({ index: 1, type: NavSourceType.Nav })
             },
             2: {
                 glideslope: this.createEmptyGlideslope({ index: 2, type: NavSourceType.Nav }),
                 localizer: this.createEmptyLocalizer({ index: 2, type: NavSourceType.Nav }),
                 cdi: this.createEmptyCdi({ index: 2, type: NavSourceType.Nav }),
                 obs: this.createEmptyObs({ index: 2, type: NavSourceType.Nav })
             }
         };
         this.currentCdiIndex = 1;
         this.navProc = new NavProcSimVarPublisher(bus);
         this.publisher = bus.getPublisher();
     }
     /** @inheritdoc */
     init() {
         this.navProc.subscribe('nav1_glideslope');
         this.navProc.subscribe('nav1_gs_lla');
         this.navProc.subscribe('nav1_gs_error');
         this.navProc.subscribe('nav1_raw_gs');
         this.navProc.subscribe('nav1_localizer');
         this.navProc.subscribe('nav1_localizer_crs');
         this.navProc.subscribe('nav1_cdi');
         this.navProc.subscribe('nav1_obs');
         this.navProc.subscribe('nav2_glideslope');
         this.navProc.subscribe('nav2_gs_lla');
         this.navProc.subscribe('nav2_gs_error');
         this.navProc.subscribe('nav2_raw_gs');
         this.navProc.subscribe('nav2_localizer');
         this.navProc.subscribe('nav2_localizer_crs');
         this.navProc.subscribe('nav2_cdi');
         this.navProc.subscribe('nav2_obs');
         this.navProc.subscribe('nav1_lla');
         this.navProc.subscribe('nav2_lla');
         this.navProc.startPublish();
         const navProcSubscriber = this.bus.getSubscriber();
         navProcSubscriber.on('nav1_glideslope').handle(hasGs => this.setGlideslopeValue(1, 'isValid', hasGs));
         navProcSubscriber.on('nav1_gs_lla').handle(lla => this.sendGlideslopePosition(1, lla));
         navProcSubscriber.on('nav1_gs_error').handle(gsError => this.setGlideslopeValue(1, 'deviation', gsError));
         navProcSubscriber.on('nav1_raw_gs').handle(rawGs => this.setGlideslopeValue(1, 'gsAngle', rawGs));
         navProcSubscriber.on('nav1_localizer').handle(hasLoc => this.setLocalizerValue(1, 'isValid', hasLoc));
         navProcSubscriber.on('nav1_localizer_crs').handle(locCourse => this.setLocalizerValue(1, 'course', locCourse));
         navProcSubscriber.on('nav1_cdi').handle(deviation => this.setCDIValue(1, 'deviation', deviation));
         navProcSubscriber.on('nav1_obs').handle(obs => this.setOBSValue(1, 'heading', obs));
         navProcSubscriber.on('nav2_glideslope').handle(hasGs => this.setGlideslopeValue(2, 'isValid', hasGs));
         navProcSubscriber.on('nav2_gs_lla').handle(lla => this.sendGlideslopePosition(2, lla));
         navProcSubscriber.on('nav2_gs_error').handle(gsError => this.setGlideslopeValue(2, 'deviation', gsError));
         navProcSubscriber.on('nav2_raw_gs').handle(rawGs => this.setGlideslopeValue(2, 'gsAngle', rawGs));
         navProcSubscriber.on('nav2_localizer').handle(hasLoc => this.setLocalizerValue(2, 'isValid', hasLoc));
         navProcSubscriber.on('nav2_localizer_crs').handle(locCourse => this.setLocalizerValue(2, 'course', locCourse));
         navProcSubscriber.on('nav2_cdi').handle(deviation => this.setCDIValue(2, 'deviation', deviation));
         navProcSubscriber.on('nav2_obs').handle(obs => this.setOBSValue(2, 'heading', obs));
         navProcSubscriber.on('nav1_lla').handle(lla => this.sendNavPosition(1, lla));
         navProcSubscriber.on('nav2_lla').handle(lla => this.sendNavPosition(2, lla));
         const navEvents = this.bus.getSubscriber();
         navEvents.on('cdi_select').handle(source => this.currentCdiIndex = source.type === NavSourceType.Nav ? source.index : 0);
     }
     /** @inheritdoc */
     onUpdate() {
         this.navProc.onUpdate();
     }
     /**
      * Sets a value in a nav radio glideslope.
      * @param index The index of the nav radio.
      * @param field The field to set.
      * @param value The value to set the field to.
      */
     setGlideslopeValue(index, field, value) {
         this.navRadioData[index].glideslope[field] = value;
         if (this.currentCdiIndex === index) {
             this.publisher.pub('glideslope', this.navRadioData[index].glideslope);
         }
     }
     /**
      * Sends the current glideslope's LLA position.
      * @param index The index of the nav radio.
      * @param lla The LLA to send.
      */
     sendGlideslopePosition(index, lla) {
         if (this.currentCdiIndex === index) {
             this.publisher.pub('gsLocation', lla);
         }
     }
     /**
      * Sends the current nav's LLA position.
      * @param index The index of the nav radio.
      * @param lla The LLA to send.
      */
     sendNavPosition(index, lla) {
         if (this.currentCdiIndex === index) {
             this.publisher.pub('navLocation', lla);
         }
     }
     /**
      * Sets a value in a nav radio localizer.
      * @param index The index of the nav radio.
      * @param field The field to set.
      * @param value The value to set the field to.
      */
     setLocalizerValue(index, field, value) {
         this.navRadioData[index].localizer[field] = value;
         if (this.currentCdiIndex === index) {
             this.publisher.pub('localizer', this.navRadioData[index].localizer);
         }
         switch (index) {
             case 1:
                 this.publisher.pub('nav_1_localizer', this.navRadioData[index].localizer);
                 break;
             case 2:
                 this.publisher.pub('nav_2_localizer', this.navRadioData[index].localizer);
                 break;
         }
     }
     /**
      * Sets a value in a nav radio localizer.
      * @param index The index of the nav radio.
      * @param field The field to set.
      * @param value The value to set the field to.
      */
     setCDIValue(index, field, value) {
         this.navRadioData[index].cdi[field] = value;
         if (this.currentCdiIndex === index) {
             this.publisher.pub('cdi_deviation', this.navRadioData[index].cdi);
         }
         switch (index) {
             case 1:
                 this.publisher.pub('nav_1_cdi', this.navRadioData[index].cdi);
                 break;
             case 2:
                 this.publisher.pub('nav_2_cdi', this.navRadioData[index].cdi);
                 break;
         }
     }
     /**
      * Sets a value in a nav radio localizer.
      * @param index The index of the nav radio.
      * @param field The field to set.
      * @param value The value to set the field to.
      */
     setOBSValue(index, field, value) {
         this.navRadioData[index].obs[field] = value;
         if (this.currentCdiIndex === index) {
             this.publisher.pub('obs_setting', this.navRadioData[index].obs);
         }
     }
     /**
      * Creates an empty localizer data.
      * @param id The nav source ID.
      * @returns New empty localizer data.
      */
     createEmptyLocalizer(id) {
         return {
             isValid: false,
             course: 0,
             source: id
         };
     }
     /**
      * Creates an empty glideslope data.
      * @param id The nav source ID.
      * @returns New empty glideslope data.
      */
     createEmptyGlideslope(id) {
         return {
             isValid: false,
             gsAngle: 0,
             deviation: 0,
             source: id
         };
     }
     /**
      * Creates an empty CDI data.
      * @param id The nav source ID.
      * @returns New empty CDI data.
      */
     createEmptyCdi(id) {
         return {
             deviation: 0,
             source: id
         };
     }
     /**
      * Creates an empty OBS data.
      * @param id The nav source ID.
      * @returns New empty OBS data.
      */
     createEmptyObs(id) {
         return {
             heading: 0,
             source: id
         };
     }
 }
 
 /// <reference types="msfstypes/JS/simvar" />
 /** Simvars to publish. */
 var FuelComputerSimVars;
 (function (FuelComputerSimVars) {
     FuelComputerSimVars["Remaining"] = "L:WT1000_Fuel_GalRemaining";
     FuelComputerSimVars["Burned"] = "L:WT1000_Fuel_GalBurned";
     FuelComputerSimVars["Endurance"] = "L:WT1000_Fuel_Endurance";
     FuelComputerSimVars["Range"] = "L:WT1000_Fuel_Range";
 })(FuelComputerSimVars || (FuelComputerSimVars = {}));
 /** A publisher to poll fuel-related simvars. */
 class FuelSimVarPublisher extends SimVarPublisher {
     /**
      * Create a FuelSimVarPublisher
      * @param bus The EventBus to publish to
      */
     constructor(bus) {
         super(FuelSimVarPublisher.simvars, bus);
     }
 }
 FuelSimVarPublisher.simvars = new Map([
     ['fuelQty', { name: 'FUEL TOTAL QUANTITY', type: SimVarValueType.GAL }],
     ['fuelFlow1', { name: 'ENG FUEL FLOW GPH:1', type: SimVarValueType.GPH }],
     ['fuelFlow2', { name: 'ENG FUEL FLOW GPH:2', type: SimVarValueType.GPH }],
 ]);
 /** A simple fuel totalizer and related logic. */
 class Totalizer {
     constructor() {
         this._fuelCapacity = 0;
         this._fuelRemaining = 0;
         this._fuelBurned = 0;
         this._rawQty = 0;
         this._priorRawQty = 0;
     }
     /**
      * Set the total fuel capacity, but only once.
      * @param capacity The total capacity.
      */
     setCapacity(capacity) {
         if (this._fuelCapacity == 0) {
             this._fuelCapacity = capacity;
         }
     }
     /**
      * Set the amount of fuel remaining.
      * @param value The amount of fuel remaining.
      */
     set fuelRemaining(value) {
         this._fuelRemaining = value;
         SimVar.SetSimVarValue(FuelComputerSimVars.Remaining, 'gallons', value);
     }
     /**
      * Get the amount of fuel remaining.
      * @returns The amount of fuel remaining.
      */
     get fuelRemaining() {
         return this._fuelRemaining;
     }
     /**
      * Set the amount of fuel burned.
      * @param value The amount of fuel burned.
      */
     set fuelBurned(value) {
         this._fuelBurned = value;
         SimVar.SetSimVarValue(FuelComputerSimVars.Burned, 'gallons', value);
     }
     /**
      * Get the amount of fuel burned.
      * @returns The amount of fuel burned.
      */
     get fuelBurned() {
         return this._fuelBurned;
     }
     /**
      * Set the raw quantity present.
      * @param value The quantity present.
      */
     set rawQty(value) {
         if (value < this._priorRawQty) {
             // We would normally expect the raw quantity to decrease.   If it does,
             // assume we're seeing standard fuel burn and update our totals.  If it
             // goes _up_, we'll treat it as a refuel and not add any burn total and
             // it's up to the user to increase the fuel remaining, just like in
             // real life!
             const burned = this._priorRawQty - value;
             this.fuelBurned += burned;
             this.fuelRemaining -= burned;
         }
         this._priorRawQty = value;
     }
     /**
      * Get the current raw quantity.
      * @returns The current raw quantity.
      */
     get rawQty() {
         return this._rawQty;
     }
     /**
      * Handle an adjustment to the fuel remaining value.
      * @param adjustment The adjustment to make.
      */
     adjust(adjustment) {
         switch (adjustment.direction) {
             case 'add':
                 this.fuelRemaining += adjustment.amount;
                 break;
             case 'remove':
                 this.fuelRemaining -= adjustment.amount;
                 break;
             case 'set':
                 this.fuelRemaining = adjustment.amount;
                 break;
         }
         // Always reset the burn total when there's an adjustment.
         this.fuelBurned = 0;
     }
     /**
      * Reset the fuel totalizer.
      * @param flag If true, reset.   (We need to have real noop events.)
      */
     reset(flag) {
         if (flag) {
             this.fuelRemaining = this._fuelCapacity;
             this.fuelBurned = 0;
         }
     }
 }
 /**
  * An instrument that tracks fuel state for use by the G1000 systems page.
  */
 class FuelComputer {
     /**
      * Create a fuel computer.
      * @param bus An event bus
      */
     constructor(bus) {
         this.simVarPublisher = new FuelSimVarPublisher(bus);
         this.simVarPublisher.subscribe('fuelQty');
         this.simVarPublisher.subscribe('fuelFlow1');
         this.simVarPublisher.subscribe('fuelFlow2');
         this.simVarSubscriber = bus.getSubscriber();
         this.controlSubscriber = bus.getSubscriber();
         this.totalizer = new Totalizer();
     }
     /** Intialize the instrument. */
     init() {
         this.simVarPublisher.startPublish();
         this.totalizer.setCapacity(SimVar.GetSimVarValue('FUEL TOTAL CAPACITY', 'gallons') - SimVar.GetSimVarValue('UNUSABLE FUEL TOTAL QUANTITY', 'gallons'));
         this.totalizer.fuelRemaining = SimVar.GetSimVarValue('FUEL TOTAL QUANTITY', 'gallons');
         this.simVarSubscriber.on('fuelQty').whenChangedBy(0.1).handle((qty) => { this.totalizer.rawQty = qty; });
         this.controlSubscriber.on('fuel_adjustment').handle((adjustment) => { this.totalizer.adjust(adjustment); });
         this.controlSubscriber.on('fuel_comp_reset').handle((flag) => { this.totalizer.reset(flag); });
     }
     /**
      * Perform events for the update loop.
      */
     onUpdate() {
         this.simVarPublisher.onUpdate();
     }
 }
 
 /**
  * A class that synchronizes the local NXi state to the sim GPS system.
  */
 class GpsSynchronizer {
     /**
      * Creates an instance of GpsSynchronizer.
      * @param bus The bus to source events from.
      * @param flightPlanner An instance of the flight planner.
      * @param facLoader An instance of the facility loader.
      */
     constructor(bus, flightPlanner, facLoader) {
         this.bus = bus;
         this.flightPlanner = flightPlanner;
         this.facLoader = facLoader;
         this.magvar = 0;
         this.distanceToCurrentLeg = -1;
         this.groundSpeed = 0;
         this.trueTrack = 0;
         this.zuluTime = 0;
         this.numPlanLegs = Subject.create(0);
         this.hasReachedDestination = Subject.create(false);
         this.isDestinationLegActive = Subject.create(false);
         this.isDirectToActive = Subject.create(false);
         const lnavVars = bus.getSubscriber();
         lnavVars.on('lnavDtkMag').handle(this.onDtkChanged.bind(this));
         lnavVars.on('lnavXtk').handle(this.onXtkChanged.bind(this));
         lnavVars.on('lnavDis').handle(this.onLnavDistanceChanged.bind(this));
         lnavVars.on('lnavBrgMag').handle(this.onLnavBearingChanged.bind(this));
         lnavVars.on('lnavDistanceToDestination').handle(this.onLnavDistanceToDestinationChanged.bind(this));
         const lnav = bus.getSubscriber();
         lnav.on('dataChanged').handle(this.onLNavDataChanged.bind(this));
         const adc = bus.getSubscriber();
         adc.on('hdg_deg_true').handle(this.onTrueHeadingChanged.bind(this));
         const vnav = bus.getSubscriber();
         vnav.on('vnavRequiredVs').handle(this.requiredVsChanged.bind(this));
         const gnss = bus.getSubscriber();
         gnss.on('gps-position').handle(this.onPositionChanged.bind(this));
         gnss.on('zulu_time').handle(t => this.zuluTime = t);
         gnss.on('track_deg_true').handle(this.onTrackTrueChanged.bind(this));
         gnss.on('ground_speed').handle(this.onGroundSpeedChanged.bind(this));
         gnss.on('magvar').handle(this.onMagvarChanged.bind(this));
         const plan = bus.getSubscriber();
         plan.on('fplActiveLegChange').handle(() => {
             this.hasReachedDestination.set(false);
             if (this.flightPlanner.hasActiveFlightPlan()) {
                 const activeFlightplan = this.flightPlanner.getActiveFlightPlan();
                 this.checkDestinationLegActive(activeFlightplan);
                 this.checkDirectToState(activeFlightplan);
                 this.onIsPrevLegChanged(activeFlightplan);
                 this.onWaypointIndexChanged(activeFlightplan);
                 //PM modif Call that at each leg change otherwise the GPS APPROACH APPROACH TYPE varibal is reset (by the sim) when activating an approach
                 this.checkApproachTypeAndTimezone(activeFlightplan, activeFlightplan.procedureDetails.approachIndex);
             }
         });
         plan.on('fplSegmentChange').handle(this.onPlanSegmentChanged.bind(this));
         plan.on('fplIndexChanged').handle(this.onPlanSegmentChanged.bind(this));
         this.numPlanLegs.sub(this.onNumLegsChanged.bind(this));
     }
     /**
      * Updates the GpsSynchronizer.
      */
     update() {
         const isGpsOverridden = SimVar.GetSimVarValue('GPS OVERRIDDEN', SimVarValueType.Bool);
         if (!isGpsOverridden) {
             SimVar.SetSimVarValue('GPS OVERRIDDEN', SimVarValueType.Bool, true);
         }
         let numPlanLegs = 0;
         if (this.flightPlanner.hasActiveFlightPlan()) {
             const plan = this.flightPlanner.getActiveFlightPlan();
             numPlanLegs = plan.length;
         }
         this.numPlanLegs.set(numPlanLegs);
         //PM Modif fix a bug with GPS IS ACTIVE FLIGHT PLAN that seems to be reset by Asobo
         // when we are changing the active leg and the Asobo flight plan has only an origin
         if (!SimVar.GetSimVarValue('GPS IS ACTIVE FLIGHT PLAN', SimVarValueType.Bool)) {
             const numLegs = this.numPlanLegs.get();
             SimVar.SetSimVarValue('GPS IS ACTIVE FLIGHT PLAN', SimVarValueType.Bool, numLegs > 0);
         }
     }
     /**
      * Handles when the active plan segments are changed.
      */
     onPlanSegmentChanged() {
         const plan = this.flightPlanner.getActiveFlightPlan();
         const approachSegments = [...plan.segmentsOfType(FlightPlanSegmentType.Approach)];
         if (approachSegments && approachSegments.length > 0) {
             SimVar.SetSimVarValue('GPS IS APPROACH LOADED', SimVarValueType.Bool, true);
             //SimVar.SetSimVarValue('GPS APPROACH WP COUNT', SimVarValueType.Number, approachSegments[0].legs.length);
         }
         else {
             SimVar.SetSimVarValue('GPS IS APPROACH LOADED', SimVarValueType.Bool, false);
             //SimVar.SetSimVarValue('GPS APPROACH WP COUNT', SimVarValueType.Number, 0);
         }
         //SimVar.SetSimVarValue('GPS APPROACH APPROACH INDEX', SimVarValueType.Number, plan.procedureDetails.approachIndex);
         //SimVar.SetSimVarValue('GPS APPROACH TRANSITION INDEX', SimVarValueType.Number, plan.procedureDetails.approachTransitionIndex);
         this.checkApproachTypeAndTimezone(plan, plan.procedureDetails.approachIndex);
     }
     /**
      * Handles when LNAV data is changed.
      * @param lnavData The LNAV data to process.
      */
     onLNavDataChanged(lnavData) {
         SimVar.SetSimVarValue('GPS COURSE TO STEER', SimVarValueType.Degree, lnavData.courseToSteer);
         const plan = this.flightPlanner.hasActiveFlightPlan() && this.flightPlanner.getActiveFlightPlan();
         const currentLeg = plan && lnavData.currentLegIndex >= 0 && lnavData.currentLegIndex < plan.length ? plan.getLeg(lnavData.currentLegIndex) : undefined;
         if (currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) {
             SimVar.SetSimVarValue('GPS WP NEXT LAT', SimVarValueType.Degree, currentLeg.calculated.endLat);
             SimVar.SetSimVarValue('GPS WP NEXT LON', SimVarValueType.Degree, currentLeg.calculated.endLon);
         }
     }
     /**
      * Checks to see if we are in a direct to state.
      * @param plan The Active Flight Plan.
      */
     checkDirectToState(plan) {
         let isDirectToActive = false;
         if (plan.activeLateralLeg >= 0 && plan.activeLateralLeg < plan.length) {
             const activeLeg = plan.getLeg(plan.activeLateralLeg);
             isDirectToActive = BitFlags.isAll(activeLeg.flags, LegDefinitionFlags.DirectTo);
         }
         this.isDirectToActive.set(isDirectToActive);
     }
     /**
      * Checks to see if we have reached the plan destination.
      * @param plan The Active Flight Plan
      */
     checkDestinationLegActive(plan) {
         if (plan.length > 1) {
             const finalSegment = plan.getSegment(plan.getSegmentIndex(plan.length - 1));
             const isApproachActive = plan.activeLateralLeg >= finalSegment.offset && finalSegment.segmentType === FlightPlanSegmentType.Approach;
             SimVar.SetSimVarValue('GPS IS APPROACH ACTIVE', SimVarValueType.Bool, isApproachActive);
             let destinationLegIndex = plan.length - 1;
             let fafIndex = -1;
             if (isApproachActive) {
                 for (let i = finalSegment.legs.length - 1; i >= 0; i--) {
                     const leg = finalSegment.legs[i];
                     if (!BitFlags.isAll(leg.flags, LegDefinitionFlags.MissedApproach)) {
                         destinationLegIndex = i + finalSegment.offset;
                     }
                     if (leg.leg.fixTypeFlags === FixTypeFlags.FAF) {
                         fafIndex = i + finalSegment.offset;
                         break;
                     }
                 }
             }
             this.checkApproachMode(plan, isApproachActive, fafIndex);
             if (!this.hasReachedDestination.get() && destinationLegIndex === plan.activeLateralLeg) {
                 this.isDestinationLegActive.set(true);
                 return;
             }
         }
         else {
             this.checkApproachMode(plan, false, -1);
         }
         this.isDestinationLegActive.set(false);
     }
     /**
      * Checks the approach mode on leg change.
      * @param plan The Active Flight Plan.
      * @param isApproachActive Whether the approach is active.
      * @param fafIndex The destination leg index.
      */
     checkApproachMode(plan, isApproachActive, fafIndex) {
         let approachMode = 0;
         let currentLeg;
         if (isApproachActive && plan.activeLateralLeg >= 0 && plan.activeLateralLeg < plan.length) {
             currentLeg = plan.getLeg(plan.activeLateralLeg);
             if (BitFlags.isAll(currentLeg.flags, LegDefinitionFlags.MissedApproach)) {
                 approachMode = 3;
             }
             else if (fafIndex > -1 && plan.activeLateralLeg >= fafIndex) {
                 approachMode = 2;
             }
             else {
                 approachMode = 1;
             }
         }
         this.checkApproachWaypointType(currentLeg);
         SimVar.SetSimVarValue('GPS APPROACH MODE', SimVarValueType.Number, approachMode);
         SimVar.SetSimVarValue('GPS APPROACH IS FINAL', SimVarValueType.Bool, approachMode === 2);
     }
     /**
      * Handles when the active leg index changes.
      * @param plan The Active Flight Plan.
      */
     onWaypointIndexChanged(plan) {
         if (plan.activeLateralLeg >= 0 && plan.activeLateralLeg < plan.length) {
             const leg = plan.getLeg(plan.activeLateralLeg);
             //PM Modif add a space at the end of the value (Asobo bug?)
             SimVar.SetSimVarValue('GPS WP NEXT ID', SimVarValueType.String, leg.name ? leg.name + " " : '');
             //SimVar.SetSimVarValue('GPS WP NEXT ID', SimVarValueType.String, leg.name ?? '');
             //SimVar.SetSimVarValue('GPS FLIGHT PLAN WP INDEX', SimVarValueType.Number, plan.activeLateralLeg);
         }
     }
     /**
      * Handles when the number of active plan legs changes.
      * @param numLegs The number of active plan legs.
      */
     onNumLegsChanged(numLegs) {
         SimVar.SetSimVarValue('GPS IS ACTIVE FLIGHT PLAN', SimVarValueType.Bool, numLegs > 0);
         SimVar.SetSimVarValue('GPS IS ACTIVE WAY POINT', SimVarValueType.Bool, numLegs > 1);
         //SimVar.SetSimVarValue('GPS FLIGHT PLAN WP COUNT', SimVarValueType.Number, numLegs);
         if (this.flightPlanner.hasActiveFlightPlan()) {
             const plan = this.flightPlanner.getActiveFlightPlan();
             this.onIsPrevLegChanged(plan);
         }
     }
     /**
      * Handles when the previous leg changes.
      * @param plan The Active Flight Plan
      */
     onIsPrevLegChanged(plan) {
         const numLegs = this.numPlanLegs.get();
         if (numLegs > 1 && plan.activeLateralLeg > 0 && plan.activeLateralLeg < plan.length) {
             SimVar.SetSimVarValue('GPS WP PREV VALID', SimVarValueType.Bool, true);
             const prevLeg = plan.getLeg(plan.activeLateralLeg - 1);
             if (prevLeg.calculated) {
                 SimVar.SetSimVarValue('GPS WP PREV LAT', SimVarValueType.Degree, prevLeg.calculated.endLat);
                 SimVar.SetSimVarValue('GPS WP PREV LON', SimVarValueType.Degree, prevLeg.calculated.endLon);
             }
         }
     }
     /**
      * Handles when the LNAV Distance to Destination Changes.
      * @param dis The new distance to destination.
      */
     onLnavDistanceToDestinationChanged(dis) {
         const eteSeconds = 3600 * dis / this.groundSpeed;
         SimVar.SetSimVarValue('GPS ETE', SimVarValueType.Seconds, eteSeconds);
         SimVar.SetSimVarValue('GPS ETA', SimVarValueType.Seconds, eteSeconds + this.zuluTime);
     }
     /**
      * Handles when the LNAV DTK changes.
      * @param dtk The new DTK.
      */
     onDtkChanged(dtk) {
         SimVar.SetSimVarValue('GPS WP DESIRED TRACK', SimVarValueType.Radians, UnitType.DEGREE.convertTo(dtk, UnitType.RADIAN));
     }
     /**
      * Handles when the LNAV XTK changes.
      * @param xtk The new XTK.
      */
     onXtkChanged(xtk) {
         SimVar.SetSimVarValue('GPS WP CROSS TRK', SimVarValueType.Meters, UnitType.NMILE.convertTo(xtk, UnitType.METER) * -1);
     }
     /**
      * Handles when the LNAV DIS to WP changes.
      * @param dis The distance.
      */
     onLnavDistanceChanged(dis) {
         if (this.isDestinationLegActive.get() && dis < 1000) {
             this.hasReachedDestination.set(true);
         }
         const distanceMeters = UnitType.NMILE.convertTo(dis, UnitType.METER);
         SimVar.SetSimVarValue('GPS WP DISTANCE', SimVarValueType.Meters, distanceMeters);
         const eteSeconds = 3600 * dis / this.groundSpeed;
         SimVar.SetSimVarValue('GPS WP ETE', SimVarValueType.Seconds, eteSeconds);
         SimVar.SetSimVarValue('GPS WP ETA', SimVarValueType.Seconds, eteSeconds + this.zuluTime);
     }
     /**
      * Handles when the LNAV Bearing to WP changes.
      * @param brg The bearing.
      */
     onLnavBearingChanged(brg) {
         SimVar.SetSimVarValue('GPS WP BEARING', SimVarValueType.Radians, UnitType.DEGREE.convertTo(brg, UnitType.RADIAN));
     }
     /**
      * Handles when the True Ground Track Changes.
      * @param trk The true track.
      */
     onTrackTrueChanged(trk) {
         SimVar.SetSimVarValue('GPS GROUND TRUE TRACK', SimVarValueType.Radians, UnitType.DEGREE.convertTo(trk, UnitType.RADIAN));
     }
     /**
      * Handles when the Ground Speed changes.
      * @param gs The current ground speed.
      */
     onGroundSpeedChanged(gs) {
         this.groundSpeed = gs;
         SimVar.SetSimVarValue('GPS GROUND SPEED', SimVarValueType.MetersPerSecond, UnitType.KNOT.convertTo(gs, UnitType.MPS));
     }
     /**
      * Handles when the true heading changes.
      * @param hdg The true heading.
      */
     onTrueHeadingChanged(hdg) {
         SimVar.SetSimVarValue('GPS GROUND TRUE HEADING', SimVarValueType.Radians, UnitType.DEGREE.convertTo(hdg, UnitType.RADIAN));
     }
     /**
      * Handles when the magvar changes.
      * @param magvar The new magvar.
      */
     onMagvarChanged(magvar) {
         this.magvar = magvar;
         SimVar.SetSimVarValue('GPS MAGVAR', SimVarValueType.Radians, UnitType.DEGREE.convertTo(magvar, UnitType.RADIAN));
     }
     /**
      * Handles when the VNAV required VS changes.
      * @param vs The required vertical speed.
      */
     requiredVsChanged(vs) {
         SimVar.SetSimVarValue('GPS WP VERTICAL SPEED', SimVarValueType.FPM, vs);
     }
     /**
      * Handles when the plane position changes.
      * @param pos The new plane position.
      */
     onPositionChanged(pos) {
         SimVar.SetSimVarValue('GPS POSITION LAT', SimVarValueType.Degree, pos.lat);
         SimVar.SetSimVarValue('GPS POSITION LON', SimVarValueType.Degree, pos.long);
         SimVar.SetSimVarValue('GPS POSITION ALT', SimVarValueType.Meters, pos.alt);
     }
     /**
      * Handles checking the approach type and timezone.
      * @param plan The active flight plan.
      * @param approachIndex The approach index in the active plan.
      */
     async checkApproachTypeAndTimezone(plan, approachIndex) {
         let approachType = 0;
         if (approachIndex > -1 && plan.destinationAirport) {
             const facility = await this.facLoader.getFacility(FacilityType.Airport, plan.destinationAirport);
             const approachName = facility.approaches[approachIndex].name.split(' ');
             switch (approachName[0]) {
                 case 'RNAV':
                     approachType = 10;
                     break;
                 case 'LOC':
                     approachType = 5;
                     break;
                 case 'ILS':
                     approachType = 4;
                     break;
                 case 'LDA':
                     approachType = 7;
                     break;
                 case 'VOR':
                     approachType = 2;
                     break;
                 case 'VDM':
                     approachType = 8;
                     break;
                 case 'NDB':
                     approachType = 3;
                     break;
                 case 'NDM':
                     approachType = 9;
                     break;
                 case 'BC':
                     approachType = 11;
                     break;
             }
             // TODO: Find a way to get the timezone from the facility or by lat/lon?
         }
         SimVar.SetSimVarValue('GPS APPROACH APPROACH TYPE', SimVarValueType.Number, approachType);
     }
     /**
      * Handles checking the approach waypoint type.
      * @param leg The active lateral leg.
      */
     checkApproachWaypointType(leg) {
         let legType = 0;
         let segmentType = 0;
         if (leg) {
             switch (leg.leg.type) {
                 case LegType.AF:
                     legType = leg.leg.turnDirection === LegTurnDirection.Left ? 4 : 5;
                     segmentType = leg.leg.turnDirection === LegTurnDirection.Left ? 2 : 1;
                     break;
                 case LegType.RF:
                     legType = 1;
                     segmentType = leg.leg.turnDirection === LegTurnDirection.Left ? 2 : 1;
                     break;
                 case LegType.CA:
                 case LegType.FA:
                 case LegType.VA:
                     legType = 9;
                     break;
                 case LegType.FM:
                 case LegType.VM:
                     legType = 10;
                     break;
                 case LegType.CD:
                 case LegType.FD:
                 case LegType.VD:
                     legType = 8;
                     break;
                 case LegType.PI:
                     legType = leg.leg.turnDirection === LegTurnDirection.Left ? 2 : 3;
                     break;
                 case LegType.HA:
                 case LegType.HM:
                 case LegType.HF:
                     legType = leg.leg.turnDirection === LegTurnDirection.Left ? 6 : 7;
                     break;
                 default:
                     legType = 1;
             }
         }
         SimVar.SetSimVarValue('GPS APPROACH WP TYPE', SimVarValueType.Number, legType);
         SimVar.SetSimVarValue('GPS APPROACH SEGMENT TYPE', SimVarValueType.Number, segmentType);
         SimVar.SetSimVarValue('GPS APPROACH IS WP RUNWAY', SimVarValueType.Bool, (leg === null || leg === void 0 ? void 0 : leg.leg.fixIcao[0]) === 'R');
     }
 }
 
 /** A class for syncing a flight plan with the game */
 class FlightPlanAsoboSync {
     /**
      * Inits flight plan asobo sync
      */
     static init() {
         if (!FlightPlanAsoboSync.fpListenerInitialized) {
             RegisterViewListener('JS_LISTENER_FLIGHTPLAN');
             FlightPlanAsoboSync.fpListenerInitialized = true;
         }
     }
     /**
      * Loads the flight plan from the sim.
      * @param fms The fms.
      * @returns a Promise which is fulfilled when the flight plan has been loaded.
      */
     static async loadFromGame(fms) {
         FlightPlanAsoboSync.init();
         await Wait.awaitDelay(200);
         Coherent.call('LOAD_CURRENT_ATC_FLIGHTPLAN');
         // Coherent.call('LOAD_CURRENT_GAME_FLIGHT');
         await Wait.awaitDelay(5000);
         //PM MODIF the bushtrip call never returns, don't know why
         //    const isBushtrip = await Coherent.call('GET_IS_BUSHTRIP');
         //    if (!isBushtrip) {
         {
             const data = await Coherent.call('GET_FLIGHTPLAN');
             const isDirectTo = data.isDirectTo;
             let lastEnrouteSegment = 1;
             // TODO: dirto
             if (!isDirectTo) {
                 if (data.waypoints.length === 0) {
                     //PM MODIF create an empty primary flight plan in this case
                     await fms.emptyPrimaryFlightPlan();
                     return;
                 }
                 // init a flightplan
                 await fms.emptyPrimaryFlightPlan();
                 const plan = fms.getPrimaryFlightPlan();
                 // set origin
                 let originFacilityType = undefined;
                 if (ICAO.isFacility(data.waypoints[0].icao)) {
                     originFacilityType = ICAO.getFacilityType(data.waypoints[0].icao);
                 }
                 if (originFacilityType === FacilityType.Airport) {
                     const originFac = await fms.facLoader.getFacility(ICAO.getFacilityType(data.waypoints[0].icao), data.waypoints[0].icao);
                     if (originFac !== undefined) {
                         FlightPlanAsoboSync.setDeparture(originFac, data, fms);
                     }
                 }
                 else if (originFacilityType !== undefined) {
                     FlightPlanAsoboSync.buildNonAirportOriginLeg(data, plan, fms);
                 }
                 // set dest
                 const destIndex = data.waypoints.length - 1;
                 let destFacilityType = undefined;
                 if (ICAO.isFacility(data.waypoints[destIndex].icao)) {
                     destFacilityType = ICAO.getFacilityType(data.waypoints[destIndex].icao);
                 }
                 if (destFacilityType === FacilityType.Airport) {
                     const destFac = await fms.facLoader.getFacility(ICAO.getFacilityType(data.waypoints[destIndex].icao), data.waypoints[destIndex].icao);
                     if (destFac !== undefined) {
                         await FlightPlanAsoboSync.setDestination(destFac, data, fms);
                     }
                 }
                 // set enroute waypoints
                 lastEnrouteSegment = FlightPlanAsoboSync.setEnroute(data, plan, fms);
                 // set non-airport destination leg as the last enroute leg
                 if (destFacilityType !== FacilityType.Airport && destFacilityType !== undefined) {
                     FlightPlanAsoboSync.buildNonAirportDestLeg(data, plan, fms, lastEnrouteSegment);
                 }
                 // if (destinationSet && !originSet) {
                 //   if (plan.length >= 1) {
                 //     plan.getSegmentIndex(0)
                 //     fms.createDirectToExisting()
                 //   }
                 // }
                 plan.calculate(0).then(() => {
                     plan.setLateralLeg(0);
                 });
             }
         }
     }
     // public static async SaveToGame(fpln: FlightPlanManager): Promise<void> {
     //   // eslint-disable-next-line no-async-promise-executor
     //   return new Promise(async (resolve, reject) => {
     //     FlightPlanAsoboSync.init();
     //     const plan = fpln.getCurrentFlightPlan();
     //     if (WTDataStore.get('WT_CJ4_FPSYNC', 0) !== 0 && (plan.checksum !== this.fpChecksum)) {
     //       // await Coherent.call("CREATE_NEW_FLIGHTPLAN");
     //       await Coherent.call("SET_CURRENT_FLIGHTPLAN_INDEX", 0).catch(console.log);
     //       await Coherent.call("CLEAR_CURRENT_FLIGHT_PLAN").catch(console.log);
     //       if (plan.hasOrigin && plan.hasDestination) {
     //         if (plan.hasOrigin) {
     //           await Coherent.call("SET_ORIGIN", plan.originAirfield.icao, false);
     //         }
     //         if (plan.hasDestination) {
     //           await Coherent.call("SET_DESTINATION", plan.destinationAirfield.icao, false);
     //         }
     //         let coIndex = 1;
     //         for (let i = 0; i < plan.enroute.waypoints.length; i++) {
     //           const wpt = plan.enroute.waypoints[i];
     //           if (wpt.icao.trim() !== "") {
     //             await Coherent.call("ADD_WAYPOINT", wpt.icao, coIndex, false);
     //             coIndex++;
     //           }
     //         }
     //         await Coherent.call("SET_ORIGIN_RUNWAY_INDEX", plan.procedureDetails.originRunwayIndex).catch(console.log);
     //         await Coherent.call("SET_DEPARTURE_RUNWAY_INDEX", plan.procedureDetails.departureRunwayIndex);
     //         await Coherent.call("SET_DEPARTURE_PROC_INDEX", plan.procedureDetails.departureIndex);
     //         await Coherent.call("SET_DEPARTURE_ENROUTE_TRANSITION_INDEX", plan.procedureDetails.departureTransitionIndex);
     //         await Coherent.call("SET_ARRIVAL_RUNWAY_INDEX", plan.procedureDetails.arrivalRunwayIndex);
     //         await Coherent.call("SET_ARRIVAL_PROC_INDEX", plan.procedureDetails.arrivalIndex);
     //         await Coherent.call("SET_ARRIVAL_ENROUTE_TRANSITION_INDEX", plan.procedureDetails.arrivalTransitionIndex);
     //         await Coherent.call("SET_APPROACH_INDEX", plan.procedureDetails.approachIndex).then(() => {
     //           Coherent.call("SET_APPROACH_TRANSITION_INDEX", plan.procedureDetails.approachTransitionIndex);
     //         });
     //       }
     //       this.fpChecksum = plan.checksum;
     //     }
     //     Coherent.call("RECOMPUTE_ACTIVE_WAYPOINT_INDEX");
     //   });
     // }
     /**
      * Sets the departure procedure or facility if specified
      * @param facility is the origin airport facility record
      * @param data is the flight plan sync data object from the world map
      * @param fms an instance of the fms
      * @returns whether a departure was set.
      */
     static setDeparture(facility, data, fms) {
         let originOneWayRunway = undefined;
         if (data.originRunwayIndex > -1) {
             const oneWayRunways = [];
             let index = 0;
             facility.runways.forEach((runway) => {
                 for (const rw of RunwayUtils.getOneWayRunways(runway, index)) {
                     oneWayRunways.push(rw);
                 }
                 index++;
             });
             oneWayRunways.sort(RunwayUtils.sortRunways);
             originOneWayRunway = oneWayRunways[data.originRunwayIndex];
         }
         if (data.departureProcIndex !== -1) {
             if (data.departureRunwayIndex > -1) {
                 const runwayTransition = facility.departures[data.departureProcIndex].runwayTransitions[data.departureRunwayIndex];
                 const runwayString = RunwayUtils.getRunwayNameString(runwayTransition.runwayNumber, runwayTransition.runwayDesignation);
                 originOneWayRunway = RunwayUtils.matchOneWayRunwayFromDesignation(facility, runwayString);
             }
             const enrouteTransitionIndex = data.departureEnRouteTransitionIndex === 0 &&
                 facility.departures[data.departureProcIndex].enRouteTransitions.length < 1 ? -1 : data.departureEnRouteTransitionIndex;
             fms.insertDeparture(facility, data.departureProcIndex, data.departureRunwayIndex, enrouteTransitionIndex, originOneWayRunway);
             return true;
         }
         else if (facility !== undefined) {
             fms.setOrigin(facility, originOneWayRunway);
             return true;
         }
         return false;
     }
     /**
      * Sets the destination airport
      * @param facility is the destination airport facility record
      * @param data is the flight plan sync data object from the world map
      * @param fms an instance of the fms
      * @returns A Promise which is fulfilled with whether a destination was set.
      */
     static async setDestination(facility, data, fms) {
         let destOneWayRunway = undefined;
         let setDestination = false;
         if (data.arrivalProcIndex !== -1) {
             if (data.arrivalRunwayIndex > -1) {
                 const runwayTransition = facility.arrivals[data.arrivalProcIndex].runwayTransitions[data.arrivalRunwayIndex];
                 if (runwayTransition !== undefined) {
                     const runwayString = RunwayUtils.getRunwayNameString(runwayTransition.runwayNumber, runwayTransition.runwayDesignation);
                     destOneWayRunway = RunwayUtils.matchOneWayRunwayFromDesignation(facility, runwayString);
                 }
             }
             const enrouteTransitionIndex = data.arrivalEnRouteTransitionIndex === 0 &&
                 facility.arrivals[data.arrivalProcIndex].enRouteTransitions.length < 1 ? -1 : data.arrivalEnRouteTransitionIndex;
             fms.insertArrival(facility, data.arrivalProcIndex, data.arrivalRunwayIndex, enrouteTransitionIndex, destOneWayRunway);
             setDestination = true;
         }
         if (data.approachIndex !== -1) {
             const approachTransitionIndex = data.approachTransitionIndex === 0 &&
                 facility.approaches[data.approachIndex].transitions.length < 1 ? -1 : data.approachTransitionIndex;
             await fms.insertApproach(facility, data.approachIndex, approachTransitionIndex);
             setDestination = true;
         }
         if (facility !== undefined && data.arrivalProcIndex === -1 && data.approachIndex === -1) {
             fms.setDestination(facility);
             setDestination = true;
         }
         return setDestination;
     }
     /**
      * Sets the enroute portion of the flight plan
      * @param data is the flight plan sync data object from the world map
      * @param plan an instance of the flight plan
      * @param fms an instance of the fms
      * @returns the last enroute segment
      */
     static setEnroute(data, plan, fms) {
         const enrouteStart = (data.departureWaypointsSize == -1) ? 1 : data.departureWaypointsSize;
         //PM MODIF bug Asobo arrivalWaypointsSize sometimes is 0
         // Test case from world map ENSD/ENSO low airways
         if (data.arrivalProcIndex >= 0 && data.arrivalWaypointsSize == 0) {
             let ArrivalSegment = null;
             for (const segment of plan.segments()) {
                 if (segment.segmentType == FlightPlanSegmentType.Arrival) {
                     ArrivalSegment = segment;
                     break;
                 }
             }
             if (ArrivalSegment)
                 data.arrivalWaypointsSize = ArrivalSegment.legs.length - 1;
         }
        //PM MODIF new code from WT
//         const enroute = data.waypoints.slice(enrouteStart, -(data.arrivalWaypointsSize + 1));
         const enroute = data.waypoints.slice(enrouteStart, -(Math.max(0, data.arrivalWaypointsSize) + 1));
         let custIdx = 1;
         let currentSegment = 1;
         let lastDepartureLegIcao = undefined;
         let lastLegWasAirway = false;
         if (data.departureProcIndex > -1) {
             const depSegment = plan.getSegment(0);
             if (depSegment.legs.length > 1) {
                 lastDepartureLegIcao = depSegment.legs[depSegment.legs.length - 1].leg.fixIcao;
             }
         }
         for (let i = 0; i < enroute.length; i++) {
             const wpt = enroute[i];
             const segment = plan.getSegment(currentSegment);
             if (wpt.airwayIdent) {
                 if (currentSegment == 1 && lastDepartureLegIcao == wpt.icao) ;
                 else {
                     const leg = FlightPlan.createLeg({
                         type: LegType.TF,
                         fixIcao: wpt.icao
                     });
                     plan.addLeg(currentSegment, leg);
                     if (!lastLegWasAirway) {
                         plan.insertSegment(currentSegment + 1, FlightPlanSegmentType.Enroute, wpt.airwayIdent);
                         currentSegment += 1;
                         // plan.setAirway(currentSegment, segment.airway + '.' + wpt.ident);
                     }
                 }
                 for (let j = i + 1; j < enroute.length; j++) {
                     i++;
                     const airwayLeg = FlightPlan.createLeg({
                         type: LegType.TF,
                         fixIcao: enroute[j].icao
                     });
                     plan.addLeg(currentSegment, airwayLeg);
                     if (enroute[j].airwayIdent !== wpt.airwayIdent) {
                         lastLegWasAirway = enroute[j].airwayIdent ? true : false;
                         break;
                     }
                 }
                 plan.setAirway(currentSegment, wpt.airwayIdent + '.' + enroute[i].ident);
                 currentSegment += 1;
                 plan.insertSegment(currentSegment, FlightPlanSegmentType.Enroute, lastLegWasAirway ? enroute[i].airwayIdent : undefined);
             }
             else {
                 let skip = false;
                 if (currentSegment == 1 && lastDepartureLegIcao == wpt.icao) {
                     skip = true;
                 }
                 let leg = undefined;
                 if (!skip && wpt.icao.trim() == '') {
                     const re = /(?:[D][\d])|(?:DLast)|(?:TIMEVERT)|(?:TIMECLIMB)|(?:TIMECRUIS)|(?:TIMEDSCNT)|(?:TIMEAPPROACH)/;
                     skip = wpt.ident.match(re) !== null;
                 }
                 if (!skip && (wpt.ident === 'Custom' || wpt.icao.trim() == '')) {
                     const userFacility = UserFacilityUtils.createFromLatLon(`U      USR${custIdx.toString().padStart(2, '0')}`, wpt.lla.lat, wpt.lla.long, true, wpt.icao.trim() === '' ? wpt.ident : `Custom ${custIdx.toString().padStart(2, '0')}`);
                     fms.addUserFacility(userFacility);
                     leg = FlightPlan.createLeg({
                         type: LegType.TF,
                         fixIcao: userFacility.icao,
                         lat: wpt.lla.lat,
                         lon: wpt.lla.long
                     });
                     custIdx++;
                 }
                 else if (!skip && wpt.icao.trim() !== '') {
                     leg = FlightPlan.createLeg({
                         type: LegType.TF,
                         fixIcao: wpt.icao
                     });
                 }
                 if (leg) {
                     plan.addLeg(currentSegment, leg);
                     if (lastLegWasAirway) {
                         plan.setAirway(currentSegment, segment.airway + '.' + wpt.ident);
                         currentSegment += 1;
                         plan.insertSegment(currentSegment, FlightPlanSegmentType.Enroute);
                     }
                     lastLegWasAirway = false;
                 }
             }
         }
         if (plan.getSegment(currentSegment).airway) {
             currentSegment += 1;
             plan.insertSegment(currentSegment, FlightPlanSegmentType.Enroute);
         }
         return currentSegment;
     }
     /**
      * Sets the first leg of the enroute plan as the first leg in the world map plan, but as an IF leg
      * @param data is the flight plan sync data object from the world map
      * @param plan an instance of the flight plan
      * @param fms an instance of FMS
      */
     static buildNonAirportOriginLeg(data, plan, fms) {
         const wpt = data.waypoints[0];
         if (wpt.ident === 'CUSTD' || wpt.ident === 'CUSTA') {
             const userFacility = UserFacilityUtils.createFromLatLon('U      CUSTD', wpt.lla.lat, wpt.lla.long, true, 'Custom Origin');
             fms.addUserFacility(userFacility);
             const leg = FlightPlan.createLeg({
                 type: LegType.IF,
                 fixIcao: 'U      CUSTD',
                 lat: wpt.lla.lat,
                 lon: wpt.lla.long
             });
             plan.addLeg(1, leg);
         }
         else if (wpt.icao.trim() !== '') {
             const leg = FlightPlan.createLeg({
                 type: LegType.IF,
                 fixIcao: wpt.icao
             });
             plan.addLeg(1, leg);
         }
     }
     /**
      * Sets the last leg of the enroute plan as the last leg in the world map plan, but as an TF leg
      * @param data is the flight plan sync data object from the world map
      * @param plan an instance of the flight plan
      * @param fms an instance of FMS
      * @param lastEnrouteSegment is the last enroute segment
      */
     static buildNonAirportDestLeg(data, plan, fms, lastEnrouteSegment) {
         const wpt = data.waypoints[data.waypoints.length - 1];
         if (wpt.ident === 'CUSTD' || wpt.ident === 'CUSTA') {
             const userFacility = UserFacilityUtils.createFromLatLon('U      CUSTA', wpt.lla.lat, wpt.lla.long, true, 'Custom Destination');
             fms.addUserFacility(userFacility);
             const leg = FlightPlan.createLeg({
                 type: LegType.TF,
                 fixIcao: userFacility.icao,
                 lat: wpt.lla.lat,
                 lon: wpt.lla.long
             });
             plan.addLeg(lastEnrouteSegment, leg);
         }
         else if (wpt.icao.trim() !== '') {
             const leg = FlightPlan.createLeg({
                 type: LegType.TF,
                 fixIcao: wpt.icao
             });
             plan.addLeg(lastEnrouteSegment, leg);
         }
     }
 }
 FlightPlanAsoboSync.fpChecksum = 0;
 FlightPlanAsoboSync.fpListenerInitialized = false;
 
 PMS50_AUTOPILOT_CONFIG = {};
 /**
  * The base PMS50_AUTOPILOT MFD instrument class.
  */
 class PMS50_AUTOPILOT extends BaseInstrument {
     /**
      * Creates an instance of the PMS50_AUTOPILOT.
      */
     constructor() {
         super();
         // Set a variable telling we are here
         SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_INSTALLED", "bool", true);
         SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_VERSION", "number", 1);
         // And another one to tell that we can disable altitude arming
         SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_MANAGE_MANUAL_ARMING", "bool", true);
         this.lastCalculate = 0;
         this.bus = new EventBus();
         this.gnss = new GNSSPublisher(this.bus);
         this.adc = new ADCPublisher(this.bus);
         this.controlPublisher = new ControlPublisher(this.bus);
         this.eis = new EISPublisher(this.bus);
         this.electricalPublisher = new ElectricalPublisher(this.bus);
         this.lNavPublisher = new LNavSimVarPublisher(this.bus);
         this.vNavPublisher = new VNavSimVarPublisher(this.bus);
         this.apRadioNav = new APRadioNavInstrument(this.bus);
         this.hEventPublisher = new HEventPublisher(this.bus);
         this.g1000ControlPublisher = new G1000ControlPublisher(this.bus);
         this.controlPublisher = new ControlPublisher(this.bus);
         this.apInstrument = new AutopilotInstrument(this.bus);
         this.clock = new Clock(this.bus);
         this.fuelComputer = new FuelComputer(this.bus);
         this.loader = new FacilityLoader(FacilityRespository.getRepository(this.bus));
         this.calculator = new FlightPathCalculator(this.loader, { defaultClimbRate: 300, defaultSpeed: 85, bankAngle: 17.5 });
         this.planner = FlightPlanner.getPlanner(this.bus, this.calculator);
         this.navProcessor = new NavProcessor(this.bus, new NPConfig(this.bus, this.planner));
         this.gpsSynchronizer = new GpsSynchronizer(this.bus, this.planner, this.loader);
         this.backplane = new InstrumentBackplane();
         this.backplane.addPublisher('adc', this.adc);
         this.backplane.addPublisher('hEvents', this.hEventPublisher);
         this.backplane.addPublisher('gnss', this.gnss);
         this.backplane.addPublisher('eis', this.eis);
         this.backplane.addPublisher('control', this.controlPublisher);
         this.backplane.addPublisher('g1000', this.g1000ControlPublisher);
         this.backplane.addPublisher('lnav', this.lNavPublisher);
         this.backplane.addPublisher('vnav', this.vNavPublisher);
         this.backplane.addPublisher('electrical', this.electricalPublisher);
         this.backplane.addInstrument('ap', this.apInstrument);
         this.backplane.addInstrument('nav', this.navProcessor);
         this.backplane.addInstrument('apRadioNav', this.apRadioNav);
         this.backplane.addInstrument('fuelComputer', this.fuelComputer);
         window['planner'] = this.planner;
         window['calculator'] = this.calculator;
         this.heventSub = this.bus.getSubscriber();
         this.xmlLogicHost = new CompositeLogicXMLHost();
         this.navdataComputer = new NavdataComputer(this.bus, this.planner, this.loader);
         this.initDuration = 3000;
         this.needValidationAfterInit = true;
         this.isNavigating = true;
         window.addEventListener('OnDestroy', this.onDestroy.bind(this));
    }
     onDestroy() {
        // This function is called when the object is destroyed
        SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_INSTALLED", "bool", false);
        SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_VERSION", "number", 0);
        SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_MANAGE_MANUAL_ARMING", "bool", false);
    }
     /**
      * The instrument template ID.
      * @returns The instrument template ID.
      */
     get templateID() {
         return 'PMS50_AUTOPILOT';
     }
     /**
      * Whether or not the instrument is interactive (a touchscreen instrument).
      * @returns True
      */
     get isInteractive() {
         return true;
     }
     /**
      * A callback called when the element has connection to the DOM.
      */
     connectedCallback() {
         super.connectedCallback();
//PM Modif new switching
        setTimeout(() => {
            BaseInstrument.allInstrumentsLoaded = true;
        }, 5000);
//End PM Modif new switching
        this.autopilot = new G1000Autopilot(this.bus, this.planner, new G1000APConfig(this.bus, this.planner), new G1000APStateManager(this.bus));
         this.fms = new Fms(this.bus, this.planner, undefined, this.g1000ControlPublisher, this.autopilot);
         FlightPlanAsoboSync.init();
         this.backplane.init();
         this.controlPublisher.publishEvent('publish_radio_states', true);
         // Set the current nav source to be sync with what is currently otherwise WT init to GPS internaly
         this.navProcessor.switchCdiSrc(SimVar.GetSimVarValue('GPS DRIVES NAV1', SimVarValueType.Bool) ? 3 : undefined);
         // force enable animations
         document.documentElement.classList.add('animationsEnabled');
         this.controlPublisher.publishEvent('init_cdi', true);
         let parsedUrl = new URL(this.getAttribute("Url").toLowerCase());
         this.urlConfig.configPath = parsedUrl.searchParams.get("configpath");
         if (this.urlConfig.configPath && this.urlConfig.configPath.length) {
             if (this.urlConfig.configPath[0] != "/")
                 this.urlConfig.configPath = "/" + this.urlConfig.configPath;
             this.loadFile(this.urlConfig.configPath, (text) => {
                 PMS50_AUTOPILOT_CONFIG = JSON.parse(text);
             });
         }
     }
     /**
      * A callback called when the instrument is initialized.
      */
     Init() {
         super.Init();
         this.initPrimaryFlightPlan();
         this.clock.init();
         this.initializeAvElectrical();
         if (this.getGameState() === GameState.briefing || this.getGameState() === GameState.ingame) {
             this.startPublishers();
         }
     }
     /**
      * Initialized the avionics electrical bus XML logic.
      */
     initializeAvElectrical() {
         var _a;
         let mfdId = 'PMS50_AUTOPILOT';
         const logicElement = this.xmlConfig.getElementsByTagName('Logic');
         if (logicElement.length > 0) {
             mfdId = (_a = logicElement[0].getElementsByTagName('MFD')[0].textContent) !== null && _a !== void 0 ? _a : 'PMS50_AUTOPILOT';
         }
         const mfdBusLogic = this.getElectricalLogicForName(mfdId);
         if (mfdBusLogic !== undefined) {
             this.electricalPublisher.setAv2Bus(mfdBusLogic);
         }
     }
     /**
      * Gets the electrical bus XML logic for a given panel name.
      * @param name The name of the panel.
      * @returns The XML logic element, or undefined if none was found.
      */
     getElectricalLogicForName(name) {
         const instrumentConfigs = this.xmlConfig.getElementsByTagName('Instrument');
         for (let i = 0; i < instrumentConfigs.length; i++) {
             const el = instrumentConfigs.item(i);
             if (el !== null) {
                 const nameEl = el.getElementsByTagName('Name');
                 if (nameEl.length > 0 && nameEl[0].textContent === name) {
                     const electrics = el.getElementsByTagName('Electric');
                     if (electrics.length > 0) {
                         return new CompositeLogicXMLElement(this, electrics[0]);
                     }
                 }
             }
         }
     }
     /**
      * Initializes the primary flight plan.
      */
     async initPrimaryFlightPlan() {
         // Request a sync from the MFD in case of an instrument reload
         //    this.fms.flightPlanner.requestSync();
         //    await Wait.awaitDelay(500);
         // Initialize the primary plan in case one was not synced.
         this.fms.initPrimaryFlightPlan();
     }
     /** @inheritdoc */
     onGameStateChanged(oldState, newState) {
         super.onGameStateChanged(oldState, newState);
         if (oldState !== newState) {
             this.bus.pub('vc_game_state', newState);
         }
         if (newState === GameState.briefing || newState === GameState.ingame) {
             this.startPublishers();
         }
     }
     /** @inheritdoc */
     onPowerOn() {
         super.onPowerOn();
         this.bus.pub('vc_powered', true);
     }
     /** @inheritdoc */
     onShutDown() {
         super.onShutDown();
         this.bus.pub('vc_powered', false);
     }
     /**
      * A callback called when the instrument gets a frame update.
      */
     Update() {
         var _a;
         super.Update();
        // Do the stuff for the new WT GPS switching solution
        // We disable WTT mode if GTN750 is disabled or if another instrument manages WTT
        if(this.isNavigating) {
            if (SimVar.GetSimVarValue("L:XMLVAR_GPS_DISABLED_GTN750", "bool") || SimVar.GetSimVarValue("L:XMLVAR_NEXTGEN_FLIGHTPLAN_ENABLED", "bool")) {
                this.isNavigating = false;
                this.onDestroy();
                window.location.assign(`coui://html_ui/Pages/VCockpit/Core/VCockpit.html?id=${encodeURIComponent("L:XMLVAR_GPS_DISABLED_GTN750")}`);
            }
        }
        const now = Date.now();
         if (now - this.lastCalculate > 3000) {
             this.lastCalculate = now;
         }
         this.clock.onUpdate();
         this.backplane.onUpdate();
         (_a = this.autopilot) === null || _a === void 0 ? void 0 : _a.update();
         this.xmlLogicHost.update(this.deltaTime);
         this.gpsSynchronizer.update();
         if (this.previousScreenState !== this.screenState) {
             this.onScreenStateChanged();
         }
         if (!this.intervalRecomputeId) {
             // We recompute the active leg for ATC every 5 seconds
             this.intervalRecomputeId = setInterval(() => {
                 Coherent.call("RECOMPUTE_ACTIVE_WAYPOINT_INDEX", 0);
             }, 5000);
         }
         if (!this.intervalSetNextId) {
             this.intervalSetNextId = setInterval(() => {
                 // Recompute the GPS WP NEXT ID beacause WT doesn't do it too often
                 let fp = this.fms.getFlightPlan();
                 if (fp && fp.activeLateralLeg > 0) {
                     let activeLeg;
                     let activeWaypointIdent = "";
                     // Small bug in WT that doesn't reset the active leg when removing destination in FP with just origin and destination
                     if (fp.activeLateralLeg == 1 && fp.length == 1 && fp.planSegments[2].legs.length == 0) {
                         activeWaypointIdent = "";
                     }
                     else {
                         activeLeg = fp.getLeg(fp.activeLateralLeg);
                         activeWaypointIdent = activeLeg ? activeLeg.name : 0;
                     }
                     SimVar.SetSimVarValue("GPS WP NEXT ID", "string", activeWaypointIdent + " ");
                 }
             }, 100);
         }
     }
     /**
      * Handles when the instrument screen state has changed.
      */
     onScreenStateChanged() {
         this.bus.pub('vc_screen_state', { previous: this.previousScreenState, current: this.screenState });
         this.previousScreenState = this.screenState;
         if (this.screenState === ScreenState.ON) {
             this.bus.pub('mfd_power_on', true, true, true);
         }
         else {
             this.bus.pub('mfd_power_on', false, true, true);
         }
     }
     /**
      * Starts data publishers to the bus.
      */
     startPublishers() {
         this.adc.subscribe('hdg_deg');
         this.adc.subscribe('hdg_deg_true');
         this.adc.subscribe('tas');
         this.adc.subscribe('ambient_wind_direction');
         this.adc.subscribe('ambient_wind_velocity');
         this.adc.subscribe('alt');
         this.adc.subscribe('on_ground');
         this.adc.subscribe('vs');
         this.adc.subscribe('pitch_deg');
         this.adc.subscribe('ias');
         this.adc.subscribe('roll_deg');
         this.adc.subscribe('delta_heading_rate');
         this.eis.subscribe('rpm_1');
         this.eis.subscribe('recip_ff_1');
         this.eis.subscribe('oil_press_1');
         this.eis.subscribe('oil_temp_1');
         this.eis.subscribe('egt_1');
         this.eis.subscribe('vac');
         this.eis.subscribe('fuel_left');
         this.eis.subscribe('fuel_right');
         this.eis.subscribe('fuel_total');
         this.eis.subscribe('fuel_flow_total');
         this.eis.subscribe('eng_hours_1');
         this.eis.subscribe('elec_bus_main_v');
         this.eis.subscribe('elec_bus_main_a');
         this.eis.subscribe('elec_bus_avionics_v');
         this.eis.subscribe('elec_bus_avionics_a');
         this.eis.subscribe('elec_bus_genalt_1_v');
         this.eis.subscribe('elec_bus_genalt_1_a');
         this.eis.subscribe('elec_bat_v');
         this.eis.subscribe('elec_bat_a');
         this.electricalPublisher.subscribe('elec_av1_bus');
         this.electricalPublisher.subscribe('elec_av2_bus');
         this.electricalPublisher.subscribe('elec_circuit_navcom1_on');
         this.electricalPublisher.subscribe('elec_circuit_navcom2_on');
         this.lNavPublisher.subscribe('lnavBrgMag');
         this.lNavPublisher.subscribe('lnavDis');
         this.lNavPublisher.subscribe('lnavDtkMag');
         this.lNavPublisher.subscribe('lnavXtk');
         this.lNavPublisher.subscribe('lnavCurrentVector');
         this.lNavPublisher.subscribe('lnavIsTracking');
         this.lNavPublisher.subscribe('lnavDistanceToDestination');
         this.vNavPublisher.subscribe('vnavTodLegIndex');
         this.vNavPublisher.subscribe('vnavTodLegDistance');
         this.vNavPublisher.subscribe('vnavBodLegIndex');
         this.vNavPublisher.subscribe('vnavMode');
         this.vNavPublisher.subscribe('vnavPathMode');
         this.vNavPublisher.subscribe('vnavFpa');
         this.vNavPublisher.subscribe('vnavTodDistance');
         this.vNavPublisher.subscribe('vnavTargetAlt');
         this.vNavPublisher.subscribe('vnavVDev');
         this.vNavPublisher.subscribe('vnavAltCaptureType');
         this.vNavPublisher.subscribe('vnavBodDistance');
         this.vNavPublisher.subscribe('vnavConstraintAltitude');
         this.vNavPublisher.subscribe('vnavConstraintLegIndex');
         this.vNavPublisher.subscribe('vnavRequiredVs');
         this.vNavPublisher.subscribe('vnavNextConstraintAltitude');
     }
     /**
      * Callback called when the flight starts.
      */
     onFlightStart() {
         super.onFlightStart();
         this.autopilot.stateManager.initialize();
         //PM Modif enable auto arming by default
         this.enableAltArming(true);
         RegisterViewListener('JS_LISTENER_MAPS');
         Wait.awaitCondition(() => this.planner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX), 1000)
             .then(() => FlightPlanAsoboSync.loadFromGame(this.fms));
     }
     /**
      * A callback called when the instrument received a H event.
      * @param args The H event and associated arguments, if any.
      */
     onInteractionEvent(args) {
         this.hEventPublisher.dispatchHEvent(args[0]);
         //PM Modif Event used to sync the captured altitude with the current sim value
         // Used in APGA to manage the UP/DN increment while in alt mode
         //Event used to disable auto arming
         if (args[0] == "PMS50_AUTOPILOT_SYNC_CAPTURED_ALTITUDE")
             this.autopilot.apValues.capturedAltitude.set(SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_CAPTURED_ALTITUDE_SET", "feet"));
         else if (args[0] == "PMS50_AUTOPILOT_ALT_ARMING_ON") {
             this.enableAltArming(true);
         }
         else if (args[0] == "PMS50_AUTOPILOT_ALT_ARMING_OFF") {
             this.enableAltArming(false);
         }
     }
     enableAltArming(enable = true) {
         this.autopilot.enableAltArming = enable;
         SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_ALT_ARMING", "bool", enable);
     }
     loadFile(file, callbackSuccess) {
         let httpRequest = new XMLHttpRequest();
         httpRequest.onreadystatechange = function (data) {
             if (this.readyState === XMLHttpRequest.DONE) {
                 let loaded = this.status === 200 || this.status === 0;
                 if (loaded) {
                     if (callbackSuccess)
                         callbackSuccess(this.responseText);
                 }
             }
         };
         httpRequest.open("GET", file);
         httpRequest.send();
     }
 }

    //PM Modif
    function setup() {
        const searchString = window.location.search.substring(1);
        if (searchString.length > 0) {
            // Parse URL parameters from search string.
            const checkVar = () => {
                try {
                    const urlParams = new URLSearchParams(window.location.search);
                    const disabledVar = decodeURIComponent(urlParams.get('id'));
                    const isDisabled = SimVar.GetSimVarValue(disabledVar, 'number');

                    if (isDisabled !== null && isDisabled !== 1 && !SimVar.GetSimVarValue("L:XMLVAR_NEXTGEN_FLIGHTPLAN_ENABLED", "bool")) {
                        window.location.assign('coui://html_ui/Pages/VCockpit/Core/VCockpit.html');
                    }
                    else
                        requestAnimationFrame(checkVar);
                }
                catch (err) {
                    requestAnimationFrame(checkVar);
                }
            };
            requestAnimationFrame(checkVar);
        }
        else {
            registerInstrument('pms50-autopilot', PMS50_AUTOPILOT);
        }     
    }
    setup();
    //End PM Modif


 
 