Querying Navdata
What Are Facilities?
Facilities are the heart of MSFS navigational data, and encompass all of the different types of navigational data that are available to query. The Javascript facility loading system can access the following types of facilities.
Airports
An airport facility describes an airport. Each airport facility contains data that include (but are not limited to):
- Metadata about the airport, such as its name and location.
- Radio frequencies associated with the airport.
- A list of runways at the airport.
- Departure, arrival, and approach instrument procedures for the airport.
The structure of airport facility objects is defined by the AirportFacility interface.
VORs
A VOR facility describes a VOR (VHF omni-directional range) or ILS/localizer station. If the station has an associated DME or glideslope, then the facility will include data on these as well.
Note that VOR facilities do not include airway information for VORs that are part of an airway. If a VOR is part of an airway, then it will have an associated VOR intersection facility that includes airway information.
The structure of VOR facility objects is defined by the VorFacility interface.
NDBs
An NDB facility describes an NDB (non-directional beacon) station. Similar to VOR facilities, NDB facilities do not include airway information for NDBs that are part of an airway; this information is instead found in the associated NDB intersection facility.
The structure of NDB facility objects is defined by the NdbFacility interface.
Intersections
An intersection facility describes a generic waypoint or position fix.
Intersections are the only facilities that are considered part of airways. As such, they are the only facilities that contain information about airways to which they are connected. If a VOR or NDB station is part of an airway, then there will exist both a VOR or NDB facility describing the station and an associated intersection facility which contains the airway information for the station.
The structure of intersection facility objects is defined by the IntersectionFacility interface.
Runways
A runway facility describes a runway at an airport. Runways described by runway facilities are directional. If a single physical runway can be used in both directions, then there will be two runway facilities associated with it (e.g. runway 03-21 will have two runway facilities — one for runway 03 and one for runway 21).
The structure of runway facility objects is defined by the RunwayFacility interface.
User Facilities
A user facility describes a position fix that is not part of any published navigational database and is instead defined on an ad-hoc basis (either directly using latitude/longitude coordinates or in relation to other facilities). Unlike most other facilities, user facilities are mutable. That is, their informational content can change in the span of a single flight or sim session.
The structure of user facility objects is defined by the UserFacility interface.
Visual Approach Facilities
A visual approach facility describes a position fix that is not part of any published navigational database and is used as part of procedurally generated approach procedures. Unlike most other facilities, visual approach facilities are mutable. That is, their informational content can change in the span of a single flight or sim session.
The structure of visual approach facility objects is defined by the VisualFacility interface.
Boundaries
A boundary facility describes an airspace boundary. Boundary facilities are unique among facilities in that they are not uniquely identified by an ICAO. As such, they cannot be individually retrieved using an ICAO lookup and must be retrieved using nearest searches.
The structure of boundary facility objects is defined by the BoundaryFacility interface.
MSFS Facility ICAO Format
Most navigational facilities in MSFS are uniquely identified using a structure called an ICAO. When operating in the Javascript environment, the ICAO format is defined by the IcaoValue interface.
In previous iterations of MSFS, ICAOs were represented by bare strings. This representation is still supported in MSFS 2024 for backward compatibility reasons but is considered deprecated. It is highly recommended to only use IcaoValue when working with ICAOs.
IcaoValue has five properties. The first one, __Type, is used for interoperability between the Javascript environment and the sim's native C++ environment; this property is always the string 'JS_ICAO'. The other four properties are used to differentiate different facilities and are explained below.
There is a special ICAO called the empty ICAO, in which all properties (except __Type) have a value equal to the empty string. The empty ICAO does not identify a facility but is instead used in various contexts to represent the concept of no facility.
Type
The type property is a single-character string that represents the type of the facility identified by the ICAO. The mapping from ICAO type to facility type is summarized by the following table:
| ICAO Type | Facility Type |
|---|---|
A | Airport |
V | VOR |
N | NDB |
W | Intersection |
R | Runway |
U | User |
S | Visual Approach |
The IcaoType enum can be used to access all the different ICAO types.
Region
The region property is a string (maximum of 2 characters) typically used to store the ICAO Airport Prefix Code associated with the facility identified by the ICAO. Some single-character prefix codes have been subdivided into multiple regions. For example, the K prefix for the continental United States is divided into regions K1 through K7, and the Y prefix for Australia is divided into regions YB and YM.
Airport ICAOs may either include the region code of the airport or omit it (i.e. set the region to the empty string). Both formulations are considered equivalent. For example, both of the following ICAOs can be used to identify Seattle-Tacoma International Airport (KSEA) and are functionally equivalent:
{
__Type: 'JS_ICAO',
type: 'A',
region: '',
airport: '',
ident: 'KSEA',
}
{
__Type: 'JS_ICAO',
type: 'A',
region: 'K1',
airport: '',
ident: 'KSEA',
}
Airport
The airport property is used by ICAOs of terminal facilities (facilities associated with an airport) to identify the associated airport. In these cases, the airport property is equal to the ident of the associated airport. Note that this property is always the empty string for airport ICAOs.
Ident
The ident property stores the string identifier for the ICAO's facility. The ident property is never the empty string for any ICAO that identifies a facility.
Working with ICAOs
The framework provides the ICAO utility class for working with ICAOs. This class contains a collection of static methods that implement commonly used operations on ICAOs. The following sections give a brief overview of some of the more basic operations.
Creating ICAOs
Use ICAO.value() to create a new ICAO struct:
import { ICAO } from '@microsoft/msfs-sdk';
const icao = ICAO.value(
'A', // type
'', // region
'', // airport
'KSEA' // ident
);
If you need the empty ICAO, then use ICAO.emptyValue() instead of manually creating an empty ICAO with ICAO.value(). Not only is ICAO.emptyValue() easier to use, it returns a cached object and thus avoids having to allocate memory for a new object every time it is called.
All ICAO structs are immutable. Never attempt to modify an ICAO struct after it has been created.
Comparing ICAOs
In Javascript, the strict equality operator compares objects by reference and so cannot be used to safely compare ICAOs. Instead, use ICAO.valueEquals() to compare two ICAOs:
import { ICAO } from '@microsoft/msfs-sdk';
const icao1 = ICAO.value('A', '', '', 'KSEA');
const icao1Copy = ICAO.value('A', '', '', 'KSEA');
const icao2 = ICAO.value('A', '', '', 'KORD');
const icao3 = ICAO.value('A', 'K1', '', 'KSEA');
ICAO.valueEquals(icao1, icao2); // false
ICAO.valueEquals(icao1, icao1Copy); // true
ICAO.valueEquals(icao1, icao3); // true (see section on ICAO region for an explanation)
Using ICAOs as Unique Keys
For the same reason that the strict equality operator cannot be used to safely compare ICAOs, it is also usually not safe to use ICAOs directly as unique keys (for example, in a Set or Map). Instead, you can use ICAO.getUid() to get a unique string identifier for an ICAO (not to be confused with the ICAO's ident!) and use the identifier as a key in place of the ICAO:
import { ICAO, IcaoValue } from '@microsoft/msfs-sdk';
const icao1 = ICAO.value('A', '', '', 'KSEA');
const icao1Copy = ICAO.value('A', '', '', 'KSEA');
const icao2 = ICAO.value('A', '', '', 'KORD');
const uniqueIcaos = new Map<string, IcaoValue>();
uniqueIcaos.set(ICAO.getUid(icao1), icao1);
uniqueIcaos.has(ICAO.getUid(icao1)); // true
uniqueIcaos.has(ICAO.getUid(icao2)); // false
uniqueIcaos.has(ICAO.getUid(icao1Copy)); // true
Loading Individual Facilities
The avionics framework includes a class with which you can query facility data, called FacilityLoader. FacilityLoader can be used to look up facilities using ICAOs:
import { EventBus, FacilityLoader, FacilityRepository, FacilityType, ICAO } from '@microsoft/msfs-sdk';
const eventBus = new EventBus();
const facilityLoader = new FacilityLoader(FacilityRepository.getRepository(eventBus));
const ord = await facilityLoader.getFacility(FacilityType.Airport, ICAO.value('A', '', '', 'KORD'));
console.log(`This airport is called ${ord.name}`);
Airport Facility Data Flags
Care must be taken when retrieving large numbers of airport facilities, since these objects can be quite large. If you only need a subset of the data included in an airport facility, you can optimize the retrieval process by excluding the data you don't need by specifying airport data flags:
import { AirportFacilityDataFlags, FacilityType, ICAO } from '@microsoft/msfs-sdk';
await facilityLoader.getFacility(
FacilityType.Airport,
ICAO.value('A', '', '', 'KORD'),
AirportFacilityDataFlags.Runways | AirportFacilityDataFlags.Frequencies
);
In the above example, runway and frequency data will be included in the airport facility returned by the facility loader, but other types of data such as instrument procedures, gates, and holding patterns will not necessarily be included.
Data types included in the data flags passed to the facility loader are guaranteed to be included in the returned facility. For optimization reasons, excluding a type of data from the airport data flags does not guarantee that the data will be excluded from the returned facility. Data that are requested to be excluded may be excluded from the returned facility, but if the facility loader decides that it is faster to return a facility object with the data included than with the data excluded, then the data will be included despite the exclusion request.
If airport data flags are not explicitly passed to the facility loader, then by default all data will be included in the returned facility.
You can use the loadedDataFlags property on AirportFacility to check which data types have been included in a specific airport facility object.
Loading VOR and NDB Intersection Facilities
Each VOR and NDB station that is part of an airway has two different associated facilities. One of the facilities is a VOR or NDB facility, and the other one is an intersection facility that contains data related to the airways that the station is part of.
The VOR/NDB facility for the station can be retrieved using an ICAO that you would typically expect to be assigned to the facility, with type V for VORs and type N for NDBs. The intersection facility ICAO, on the other hand, does not use the intersection ICAO type (W), but is instead identical to the VOR/NDB facility ICAO. This is the only situation in which there are multiple facilities mapped to the same ICAO. When retrieving these facilities from FacilityLoader, you must specify the appropriate facility type to disambiguate between the two facilities that use the same ICAO:
import { FacilityType, ICAO } from '@microsoft/msfs-sdk';
const icao = ICAO.value('V', 'K1', '', 'SEA');
// Retrieves the VOR facility for the Seattle VORTAC.
await facilityLoader.getFacility(FacilityType.VOR, icao);
// Retrieves the intersection facility for the Seattle VORTAC.
await facilityLoader.getFacility(FacilityType.Intersection, icao);
Loading Runway Facilities
When loading runway facilities from FacilityLoader, it is crucial that the region property of the runway ICAO used to look up the facility is empty:
// Good
await facilityLoader.getFacility(FacilityType.Runway, ICAO.value('R', '', 'KSEA', 'RW16L'));
// Bad
await facilityLoader.getFacility(FacilityType.Runway, ICAO.value('R', 'K1', 'KSEA', 'RW16L'));
Runway ICAOs, especially when retrieved from published instrument procedure data, will sometimes have a non-empty region property. These must be converted to ICAOs with an empty region property before they can be used to retrieve runway facilities from FacilityLoader.
Runway ICAOs returned by RunwayUtils.getRunwayFacilityIcaoValue() always have an empty region property and are safe to use with FacilityLoader.
Finding An ICAO
Sometimes, you may not have the full ICAO available for a facility (such as when attempting to retrieve a facility from only its ident). In these cases, you can get a collection of ICAOs whose ident starts with the given string:
import { FacilitySearchType } from '@microsoft/msfs-sdk';
// Find ICAOs with idents that start with 'JOT'.
const matches = await facilityLoader.searchByIdentWithIcaoStructs(FacilitySearchType.All, 'JOT');
By default, searchByIdentWithIcaoStructs() limits the maximum number of results to 40. If you wish, you can specify a custom maximum result count:
// Limit to 10 results or fewer.
const matches = await facilityLoader.searchByIdentWithIcaoStructs(FacilitySearchType.All, 'JOT', 10);
You can also limit your search to a specific facility type:
// Limit results to NDB facilities.
const matches = await facilityLoader.searchByIdentWithIcaoStructs(FacilitySearchType.NDB, 'AB', 10);
Searching Nearest Facilities
Sometimes, it is helpful to get a collection of facilities that are within a certain geographic distance. For this, the framework provides nearest facilities search sessions.
By starting a search session, you can get a list of the facilities that are within a specfied distance of a point defined by latitude/longitude coordinates. Once started, you can change the center point and radius of the search session and it will keep track of the ICAOs of facilities that have "entered" or "exited" the search area since the last time you updated the search session.
This means that for the first search performed in a given session, all facilities within range will return in the data as added facilities. If no search parameters change (the center point or radius of the search) and no facilities in the database or repository were added, removed, or changed, then the next update of the search will return no facilities added or removed. In other words, the facilities that were returned last time are still the set of facilities in the search area. If you change the search area parameters, such as by moving the center point or changing the radius, then the data returned will include only the differences (the diff) since the last search update: everything added to the set of facilities in range, and everything removed from the set of facilities in range.
This type of search is especially useful for things like displaying facilities on a moving map or displaying a list that shows the nearest facilities to the airplane. Since the nearest search session returns a diff with each update, downstream code can focus on only those facilities that were added or removed from the search.
Performing A Nearest Facility Search
You can perform a nearest facility search by starting a new search session using the facility loader, and then searching using the returned session:
import { AirportFacility, FacilitySearchType, FacilityType, ICAO } from '@microsoft/msfs-sdk';
const session = await facilityLoader.startNearestSearchSessionWithIcaoStructs(FacilitySearchType.Airport);
const nearestAirports = new Map<string, AirportFacility>();
setInterval(async () => {
const lat = SimVar.GetSimVarValue('PLANE LATITUDE', 'degrees');
const lon = SimVar.GetSimVarValue('PLANE LONGITUDE', 'degrees');
const distanceMeters = UnitType.NMILE.convertTo(100, UnitType.METER);
const diff = await session.searchNearest(lat, lon, distanceMeters, 25);
for (let i = 0; i < diff.removed.length; i++) {
nearestAirports.delete(ICAO.getUid(diff.removed[i]));
}
await Promise.all(diff.added.map(async (icao) => {
const airport = await facilityLoader.getFacility(FacilityType.Airport, icao);
nearestAirports.set(ICAO.getUid(icao), airport);
}));
}, 1000);
This code starts a new session, then searches for the closest 25 airports within 100NM of the current plane position every second, adding and removing from the collection of currently found airports as necessary.
Each search session started during a flight uses memory to track your session state. This memory only freed after the end of the flight. It is highly recommended to limit the number of search sessions started and reuse sessions where possible.