|
| 1 | +# Developer-Oriented In-Sim Navigation Data API Specification |
| 2 | + |
| 3 | +To be Reviewed By: Markus Hamburger, Malte Hallström, Jack Lavigne |
| 4 | + |
| 5 | +Authors: Alex Cutforth |
| 6 | + |
| 7 | +Status: Outdated |
| 8 | + |
| 9 | +Date: Feb 1, 2024 |
| 10 | + |
| 11 | +## Problem |
| 12 | + |
| 13 | +Accessing Navigraph's navigation data in Microsoft Flight Simulator has always been challenging due to the limitations of the JavaScript and Webassembly APIs. However, with the recent addition of the Communications API, allowing different WASM modules and Coherent pages to interact with each other, this has become possible. |
| 14 | + |
| 15 | +Navigraph has decided to use an architecture consisting of a wasm module whose job it is to download navigation data databases from the internet, and to query them. This means an API must be designed and implemented to best cater to the needs of aircraft developers. |
| 16 | + |
| 17 | +This RFC will outline the following: |
| 18 | + |
| 19 | +- Naming, typing, and other standards to use throughout the API |
| 20 | +- Example data structures for the returned data which are comprehensive and ergonomic to use |
| 21 | +- Example functions the API will implement to provide the necessary data. |
| 22 | + |
| 23 | +## Anti-Goals |
| 24 | + |
| 25 | +This interface will not be designed for use in parallel with in sim data, it is meant to provide the best experience possible with Navigraph's navigation data capabilities. This interface will only be providing data from Navigraph's databases. |
| 26 | + |
| 27 | +This interface is also not designed for use outside of Microsoft Flight Simulator and is not designed (as of now) with anything other than the Flight Management System in mind. |
| 28 | + |
| 29 | +# Solution |
| 30 | + |
| 31 | +## Standards |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +- All functions and fields and variables involved in this interface should use `snake_case`, and all names of objects and types should use `PascalCase`. This is the code style which rust uses, and it should be used JS side as well for consistency and so objects dont need to be re assigned. |
| 36 | +- Data fields which act as an identifier should always use the shorthand term: `ident` and should not name the type they are part of. |
| 37 | + - Example: Runways may contain the fields: |
| 38 | + - `ident: "RW23L"` |
| 39 | + - `airport_ident: "NZAA"` |
| 40 | + - Example: Airports may contain the field: |
| 41 | + - `ident: "NZAA"` |
| 42 | +- Using the term `icao` to refer to an identifier is an anti-pattern as it may be confused with the `icao_code` which contains a two-letter code such as `ES` which represents the area of the world where this piece of data lies. |
| 43 | + - Example: Airports may contain the fields: |
| 44 | + - `icao_code: "NZ"` |
| 45 | + - `ident: "NZAA"` |
| 46 | +- Fields should represent the unit that the data will be using as a type alias, not by a suffix or prefix to the name of the field. |
| 47 | + - Do: `runway_length: Feet` |
| 48 | + - Don't: `runway_length_ft: number` |
| 49 | +- Radio navigation aid data type names should consist of the acronym of the type, followed by the suffix `Navaid` |
| 50 | + - Examples: `VhfNavaid`, `IlsNavaid`, `NdbNavaid`, |
| 51 | +- Acronyms should **_not_** be all capitalised |
| 52 | + - Do: `Vhf` |
| 53 | + - Dont: `NDB` |
| 54 | +- Latitudes should be encoded to as `lat` and Longitudes should be encoded to as `long`, and should wherever they are used in conjunction with each other, be part of a `Coordinates` data structure |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +- Items which are linked to other data should **_not_** have the linked data queried automatically and added to the returned data. |
| 59 | + - This is because increasing the size of data this much will result in slower queries due to the performance issues with Coherent's JSON parser. |
| 60 | + - While it is useful to have linked data such as Navaid information on airway or procedures, this would not be viable when using the API in a language other than JS or Rust |
| 61 | +- For items such as Procedures and Airways, which are represented in the database as a large number of rows with the same `ident` and possibly `icao_code`. They should be grouped into an object, containing list(s) which stores the individual elements. Fields which are known to be the same throughout the grouping should be on the group object, not the individual elements. |
| 62 | + - If the individual elements are split into sections such as transitions in a procedure, these should be encoded as subgroups. |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +- Data should be provided in the same units that the Database provides for consistency with aviation standards |
| 67 | +- Enums should be used where possible and should have values which match the database encoding. |
| 68 | + - Except for when an enum is to be used as a filter, where its values should support bitwise flags. |
| 69 | +- The data provided by this API should be able to be encoded purely in JSON, meaning: |
| 70 | + |
| 71 | + - No functions |
| 72 | + - No classes |
| 73 | + - This is to support JSON serialization as a form of copying or sending between instruments |
| 74 | + |
| 75 | +- Data fields should never be empty strings, they should be undefined |
| 76 | +- Data fields which are linked to each other will either both be undefined or both be defined |
| 77 | +- Data being encoded as possibly undefined should be based on database sweeps to find whether there are any null fields, not the database schema. |
| 78 | + |
| 79 | +- Datastructures which are expected to have certain fields be defined based on the value of another field should have the type-system enforce this where possible, for example: |
| 80 | + |
| 81 | + - Procedure legs will have their fields filtered based upon the `path_termination` field. Specs for this can be found [here](https://developers.navigraph.com/docs/navigation-data/dfd-data-format#procedure-leg-data-fields-minimum-requirements) or in `ARINC SPECIFICATION 424 Attachment 5 Data fields table 3` |
| 82 | + - Altitude constraints |
| 83 | + |
| 84 | +- Values which are not defined should be undefined as opposed to null. This means in the rust implementation, all structs used for serialization which have Option fields should have `#[serde_with::skip_serializing_none]` as an attribute about the struct |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +- Functions which fetch items by the identifier should be named as `get_{type}s` |
| 89 | +- Functions which fetch a list of items by a grouping should be named `get_{type}_at_{group}` and should take in the identifier of the item used as the grouping |
| 90 | +- Functions which fetch a list of items by location and range should use the naming: `get_{type}s_in_range`, and should take in: |
| 91 | + - The center coordinates of the query |
| 92 | + - The range in nautical miles to search around the center |
| 93 | + - Any filters necessary for the type being fetched |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +- In the rust implementation, Mapping one row to one output struct should use a From<> implementation, but any other mapping should be done with a descriptively named function |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +## Example Datastructures |
| 102 | + |
| 103 | +These datastructures are for use in JS/TS, however, they should be encoded as close to this as they can be on the WASM side, so minimal computation needs to happen in Coherent. |
| 104 | + |
| 105 | +```ts |
| 106 | +export interface Coordinates { |
| 107 | + lat: Degrees |
| 108 | + long: Degrees |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +For data which links to a Fix such as Departures, Arrivals, Airways etc should use the Fix interface as the type for those fields. The data in Fix should be obtainable purely from id fields and location field. |
| 113 | + |
| 114 | +The Fix data will be enough information for computing and rendering procedures and airways on most aircraft. After reading the fixType field, full data for the Fix can be fetched using the respective queries. |
| 115 | + |
| 116 | +```ts |
| 117 | +export enum FixType { |
| 118 | + Airport, |
| 119 | + NdbNavaid, |
| 120 | + RunwayThreshold, |
| 121 | + GlsNavaid, |
| 122 | + IlsNavaid, |
| 123 | + VhfNavaid, |
| 124 | + Waypoint, |
| 125 | +} |
| 126 | + |
| 127 | +interface Fix { |
| 128 | + fix_type: FixType |
| 129 | + ident: string |
| 130 | + icao_code: string |
| 131 | + location: Coordinates |
| 132 | + airport_ident?: string |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +```ts |
| 137 | +interface Transition { |
| 138 | + ident: string |
| 139 | + legs: ProcedureLeg[] |
| 140 | +} |
| 141 | + |
| 142 | +interface Departure { |
| 143 | + ident: string |
| 144 | + runway_transitions: Transition[] |
| 145 | + common_legs: ProcedureLeg[] |
| 146 | + enroute_transitions: Transition[] |
| 147 | + engine_out_legs: ProcedureLeg[] |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +```ts |
| 152 | +export interface Airport { |
| 153 | + area_code: string |
| 154 | + ident_3_letter?: string |
| 155 | + name: string |
| 156 | + ifr_capability: IfrCapability |
| 157 | + elevation: Feet |
| 158 | + transition_altitude?: Feet |
| 159 | + transition_level?: Feet |
| 160 | + speed_limit?: Knots |
| 161 | + speed_limit_altitude?: Feet |
| 162 | + iata_ident?: string |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +```ts |
| 167 | +export interface Airway { |
| 168 | + ident: string |
| 169 | + fixes: Fix[] |
| 170 | + route_type: RouteType |
| 171 | + level: AirwayLevel |
| 172 | + direction?: AirwayDirection |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +## Full description of functions to be implemented |
| 177 | + |
| 178 | +```ts |
| 179 | +get_database_info(ident: string): Promise<Airport> |
| 180 | + |
| 181 | +get_airport(ident: string): Promise<Airport> |
| 182 | +get_waypoints(ident: string): Promise<Waypoint[]> |
| 183 | +get_vhf_navaids(ident: string): Promise<VhfNavaid[]> |
| 184 | +get_ndb_navaids(ident: string): Promise<NdbNavaid[]> |
| 185 | +get_airways(ident: string): Promise<Airway[]> |
| 186 | + |
| 187 | +get_airways_at_fix(fix_ident: string, fix_icao_code: string): Promise<Airway[]> |
| 188 | +get_holds_at_fix(fix_ident: string, fix_icao_code: string): Promise<Hold[]> |
| 189 | + |
| 190 | +get_waypoints_in_range(center: Coordinates, range: NauticalMiles): Promise<Waypoint[]> |
| 191 | +get_vhf_navaids_in_range(center: Coordinates, range: NauticalMiles): Promise<VhfNavaid[]> |
| 192 | +get_ndb_navaids_in_range(center: Coordinates, range: NauticalMiles): Promise<NdbNavaid[]> |
| 193 | +get_airways_in_range(center: Coordinates, range: NauticalMiles): Promise<Airway[]> |
| 194 | +get_controlled_airspaces_in_range(center: Coordinates, range: NauticalMiles): Promise<ControlledAirspace[]> |
| 195 | +get_restrictive_airspaces_in_range(center: Coordinates, range: NauticalMiles): Promise<RestrictiveAirspace[]> |
| 196 | +get_communications_in_range(center: Coordinates, range: NauticalMiles): Promise<Communication[]> |
| 197 | + |
| 198 | +get_runways_at_airport(airport_ident: string): Promise<Runway[]> |
| 199 | +get_departures_at_airport(airport_ident: string): Promise<Departure[]> |
| 200 | +get_arrivals_at_airport(airport_ident: string): Promise<Arrival[]> |
| 201 | +get_approaches_at_airport(airport_ident: string): Promise<Approach[]> |
| 202 | +get_ndb_navaids_at_airport(airport_ident: string): Promise<NdbNavaid[]> |
| 203 | +get_ils_navaids_at_airport(airport_ident: string): Promise<IlsNavaid[]> |
| 204 | +get_gls_navaids_at_airport(airport_ident: string): Promise<GlsNavaid[]> |
| 205 | +get_path_points_at_airport(airport_ident: string): Promise<GlsNavaid[]> |
| 206 | +get_communications_at_airport(airport_ident: string): Promise<Communication[]> |
| 207 | +get_gates_at_airport(airport_ident: string): Promise<Gate[]> |
| 208 | +``` |
0 commit comments