msfs-simconnect-api-wrapper
Advanced tools
Comparing version 3.0.0 to 3.1.0
@@ -0,1 +1,7 @@ | ||
v3.1.0 | ||
- Airport code has been restored, in a radically different way internally, but with the same API as before. | ||
The airport data is now stored in a bundled database file. If the bundled database and the in-game airport database different in the number of airports, you'll get a console warning that you'll probably want to update your version of `msfs-simconnect-api-wrapper`. | ||
v3.0.0 | ||
@@ -64,2 +70,1 @@ | ||
There was no changelog prior to v1.4.1 | ||
@@ -12,19 +12,11 @@ import { | ||
import { SystemEvents as SysEvents } from "./system-events/index.js"; | ||
import { | ||
SystemEvents as AirportEvents, | ||
airportGetHandler, | ||
} from "./special/airports.js"; | ||
// Special import for working with airport data | ||
const { AIRPORTS_IN_RANGE, AIRPORTS_OUT_OF_RANGE } = SysEvents; | ||
import { AirportEvents, getAirportHandler } from "./special/airports.js"; | ||
import { SIMCONNECT_EXCEPTION } from "./exceptions.js"; | ||
export const SystemEvents = Object.assign({}, SysEvents, AirportEvents); | ||
export const MSFS_NOT_CONNECTED = `Not connected to MSFS`; | ||
const SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT = 0; | ||
const SIMCONNECT_FACILITY_AIRPORT = 1000; | ||
const codeSafe = (string) => string.replaceAll(` `, `_`); | ||
// direct export for downstream users: | ||
export const SystemEvents = Object.assign({}, SysEvents, AirportEvents); | ||
/** | ||
@@ -39,5 +31,4 @@ * API: | ||
export class MSFS_API { | ||
constructor(appName = "MSFS API", AirportDB) { | ||
constructor(appName = "MSFS API") { | ||
this.appName = appName; | ||
this.AirportDB = AirportDB; | ||
@@ -47,5 +38,2 @@ // set up a listener list for simconnect event handling: | ||
// set up a list for special (non-simconnect) get handlers | ||
this.specialGetHandlers = [airportGetHandler]; | ||
// set up an event/data/request id counter: | ||
@@ -62,7 +50,8 @@ this.id = 1; | ||
opts.onRetry ??= () => {}; | ||
const { host, port } = opts; | ||
try { | ||
const { host = `0.0.0.0`, port = 500 } = opts; | ||
const { handle } = await open(this.appName, Protocol.KittyHawk, { | ||
remote: { host, port }, | ||
}); | ||
const remote = (this.remote = host | ||
? { host, port: port ?? 500 } | ||
: undefined); | ||
const { handle } = await open(this.appName, Protocol.KittyHawk, remote); | ||
if (!handle) throw new Error(`No connection handle to MSFS`); | ||
@@ -76,3 +65,5 @@ this.handle = handle; | ||
); | ||
this.addAirportHandling(handle); | ||
// special non-simconnect handling | ||
this.specialGetHandlers = [await getAirportHandler(this, handle)]; | ||
// Signal that we're done | ||
opts.onConnect(handle); | ||
@@ -150,30 +141,2 @@ } catch (err) { | ||
addAirportHandling(handle) { | ||
// TODO: - [x] get(`NEARBY_AIRPORTS`) to get the current list of nearby airports. | ||
// - [ ] get(`ALL_AIRPORTS`) to get the list of all airports in the sim. | ||
// - [x] on(`AIRPORTS_IN_RANGE`) to subscribe to "new airports in range" events. | ||
// - [x] on(`AIRPORTS_OUT_OF_RANGE`) to subscribe to "airports dropping out of range" events. | ||
// - [x] document these four special cases | ||
const TYPE = SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT; | ||
const IN_RANGE = this.nextId(); | ||
const OUT_OF_RANGE = this.nextId(); | ||
handle.subscribeToFacilitiesEx1(TYPE, IN_RANGE, OUT_OF_RANGE); | ||
handle.on("airportList", (data) => { | ||
const { requestID: id } = data; | ||
// work around the node-simconnect typo, if it still exists | ||
if (data.aiports) { | ||
data.airports ??= data.aiports; | ||
delete data.aiports; | ||
} | ||
// Are there in/out of range airports? | ||
this.eventListeners[id]?.handlers.forEach((handle) => | ||
handle(data.airports) | ||
); | ||
}); | ||
} | ||
/** | ||
@@ -301,2 +264,3 @@ * Add an event listener. This returns a function that acts | ||
if (!this.connected) throw new Error(MSFS_NOT_CONNECTED); | ||
const DATA_ID = this.nextId(); | ||
@@ -310,3 +274,3 @@ const REQUEST_ID = DATA_ID; | ||
if (get.supports(propName)) { | ||
return get(this, propName); | ||
return get(propName); | ||
} | ||
@@ -313,0 +277,0 @@ } |
{ | ||
"name": "msfs-simconnect-api-wrapper", | ||
"version": "3.0.0", | ||
"version": "3.1.0", | ||
"description": "A convenient SimConnect API for playing with Microsoft Flight Simulator 2020", | ||
@@ -5,0 +5,0 @@ "main": "msfs-api.js", |
@@ -90,4 +90,2 @@ # msfs-simconnect-api-wrapper | ||
<!-- | ||
##### Special Events | ||
@@ -100,4 +98,2 @@ | ||
---> | ||
#### `off(evtDefinition, handler)` | ||
@@ -111,30 +107,17 @@ | ||
<!-- | ||
##### special (non-simconnect) variables | ||
There are a number of special variables that can only be retrieved using a get call with a single variable name, yielding data that is not services by SimConnect's own variables (or data that requires a considerable amount of low-level event handling). | ||
There are a number of special variables that can only be retrieved using a get call with a single variable name, yielding data that is not serviced by SimConnect's own variables (or data that requires a considerable amount of low-level event handling). | ||
There are currently three variables: | ||
- `ALL_AIRPORTS`, which yields the list of all airports known to MSFS. | ||
- `NEARBY_AIRPORTS`, which yields the list of airports that are currently in range of our airplane (or rather, in range of "the current Sim bubble", which is all world tiles currently loaded and active in the sim). | ||
- `ALL_AIRPORTS`, which yields the list of all airports known to MSFS. | ||
- `NEARBY_AIRPORTS:NM`, which yields the list of airports within a radius of `NM` nautical miles around the airplane's location. | ||
- `AIRPORT:index`, which yields an airport's information (including runway information), with `index` being the airport's ICAO code. | ||
Both calls return objects of the following type: | ||
The first three return arrays of Airport object, the last one returns a single Airport object | ||
``` | ||
FacilityAirport { | ||
icao: four character ICAO code | ||
latitude: number in degrees | ||
longitude: number in degrees | ||
altitude: number in meters | ||
} | ||
``` | ||
Airport objects have the following shape: | ||
Pay special attention to the altitude, which is *not* in feet, it is in meters. | ||
- `AIRPORT:index`, which yields an airport's information (including runway information), with `index` being the airport's ICAO code. | ||
This call returns objects of the following type: | ||
``` | ||
@@ -144,3 +127,3 @@ { | ||
longitude: number in degrees | ||
altitude: number in meters | ||
altitude: number in feet | ||
declination: number in degree | ||
@@ -156,6 +139,4 @@ name: airport name as a string with at most 32 characters | ||
Again, pay special attention to the altitude, which is *not* in feet, it is in meters. | ||
Runway objects have the following shape: | ||
Runway objects are of the following type: | ||
``` | ||
@@ -165,3 +146,3 @@ { | ||
longitude: number in degrees, marking the center of the runway | ||
altitude: number in meters | ||
altitude: number in feet | ||
heading: number in degrees | ||
@@ -178,3 +159,3 @@ length: number in meters | ||
Approaches are of the following type: | ||
Approaches gave the following shape: | ||
@@ -192,3 +173,2 @@ ``` | ||
``` | ||
--> | ||
@@ -195,0 +175,0 @@ #### `schedule(handler, interval, ...propNames)` |
/** | ||
* This extension adds the following special (non-simconnect) variables: | ||
* | ||
* - ALL_AIRPORTS, for a list of all airports in the game. | ||
* - NEARBY_AIRPORTS, for a list of all airports in the "local reality bubble". | ||
* - ALL_AIRPORTS, for a list of all airports in the game. | ||
* - AIRPORT:index, for getting a specific airport's information, where `index` is the four character ICAO code. | ||
* - NEARBY_AIRPORTS:NM, for a list of all airports within <NM> nautical miles of the aircraft. | ||
* - AIRPORT:ICAO, for getting a specific airport's information, where `ICAO` is the four character ICAO code. | ||
* | ||
@@ -13,12 +14,26 @@ * This extension adds the following special (non-simconnect) events: | ||
*/ | ||
import fs from "node:fs"; | ||
import zlib from "node:zlib"; | ||
import { Protocol, open } from "node-simconnect"; | ||
import { getDistanceBetweenPoints as dist } from "./utils.js"; | ||
import { | ||
RUNWAY_SURFACES, | ||
RUNWAY_NUMBER, | ||
RUNWAY_DESIGNATOR, | ||
ILS_TYPES, | ||
SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, | ||
} from "./constants.js"; | ||
const SPECIAL_VARS = [`NEARBY AIRPORTS`, `ALL AIRPORTS`, `AIRPORT`]; | ||
import path from "node:path"; | ||
import url from "node:url"; | ||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); | ||
const AIRPORT_DB_LOCATION = path.join(__dirname, `..`, `airport.db.gz`); | ||
export const SystemEvents = { | ||
export const AirportEvents = { | ||
AIRPORTS_IN_RANGE: { | ||
name: `Airports in range`, | ||
name: `AirportsInRage`, | ||
desc: `Use this event with the on() function to get notified about airports coming into range of our airplane (by being loaded into the sim's "reality bubble").`, | ||
}, | ||
AIRPORTS_OUT_OF_RANGE: { | ||
name: `Airports out of range`, | ||
name: `AirportsOutOfRange`, | ||
desc: `Use this event with the on() function to get notified about airports dropping out of range of our airplane (by being loaded into the sim's "reality bubble").`, | ||
@@ -28,186 +43,89 @@ }, | ||
const RUNWAY_SURFACES = [ | ||
`concrete`, | ||
`grass`, | ||
`water fsx`, | ||
`grass bumpy`, | ||
`asphalt`, | ||
`short grass`, | ||
`long grass`, | ||
`hard turf`, | ||
`snow`, | ||
`ice`, | ||
`urban`, | ||
`forest`, | ||
`dirt`, | ||
`coral`, | ||
`gravel`, | ||
`oil treated`, | ||
`steel mats`, | ||
`bituminus`, | ||
`brick`, | ||
`macadam`, | ||
`planks`, | ||
`sand`, | ||
`shale`, | ||
`tarmac`, | ||
`wright flyer track`, | ||
`ocean`, | ||
`water`, | ||
`pond`, | ||
`lake`, | ||
`river`, | ||
`waste water`, | ||
`paint`, | ||
]; | ||
const KM_PER_NM = 1.852; | ||
const FEET_PER_METERS = 3.28084; | ||
const degrees = (rad) => (rad / Math.PI) * 180; | ||
const radians = (deg) => (deg / 180) * Math.PI; | ||
RUNWAY_SURFACES[254] = `unknown`; | ||
const NEARBY_AIRPORTS = `NEARBY AIRPORTS`; | ||
const ALL_AIRPORTS = `ALL AIRPORTS`; | ||
const AIRPORT = `AIRPORT:`; | ||
const SPECIAL_VARS = [ALL_AIRPORTS, NEARBY_AIRPORTS, AIRPORT]; | ||
const RUNWAY_NUMBER = [ | ||
`none`, | ||
`1`, | ||
`2`, | ||
`3`, | ||
`4`, | ||
`5`, | ||
`6`, | ||
`7`, | ||
`8`, | ||
`9`, | ||
`10`, | ||
`11`, | ||
`12`, | ||
`13`, | ||
`14`, | ||
`15`, | ||
`16`, | ||
`17`, | ||
`18`, | ||
`19`, | ||
`20`, | ||
`21`, | ||
`22`, | ||
`23`, | ||
`24`, | ||
`25`, | ||
`26`, | ||
`27`, | ||
`28`, | ||
`29`, | ||
`30`, | ||
`31`, | ||
`32`, | ||
`33`, | ||
`34`, | ||
`35`, | ||
`36`, | ||
`north`, | ||
`northeast`, | ||
`east`, | ||
`southeast`, | ||
`south`, | ||
`southwest`, | ||
`west`, | ||
`northwest`, | ||
`last`, | ||
]; | ||
const RUNWAY_DESIGNATOR = [ | ||
`none`, | ||
`left`, | ||
`right`, | ||
`center`, | ||
`water`, | ||
`a`, | ||
`b`, | ||
`last`, | ||
]; | ||
const ILS_TYPES = { | ||
0: `none`, | ||
65: `airport`, | ||
86: `VOR`, | ||
78: `NDB`, | ||
87: `waypoint`, | ||
}; | ||
const SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT = 0; | ||
const SIMCONNECT_FACILITY_AIRPORT = 1000; | ||
const AIRPORT_TYPE = SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT; | ||
let facilityDefinitionRegistered = false; | ||
const nextId = (function () { | ||
const startId = 100; | ||
let id = startId; | ||
return () => { | ||
if (id > 900) { | ||
id = startId; | ||
} | ||
return id++; | ||
}; | ||
})(); | ||
function registerFacilityDefinition(handle) { | ||
if (facilityDefinitionRegistered) return; | ||
facilityDefinitionRegistered = true; | ||
/** | ||
* ...docs go here... | ||
*/ | ||
export async function getAirportHandler(api, handle) { | ||
const TYPE = SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT; | ||
const IN_RANGE = api.nextId(); | ||
const OUT_OF_RANGE = api.nextId(); | ||
handle.on(`airportList`, ({ requestID: id, airports }) => { | ||
if (id === IN_RANGE || id === OUT_OF_RANGE) { | ||
const eventName = | ||
AirportEvents[ | ||
id === IN_RANGE ? `AIRPORTS_IN_RANGE` : `AIRPORTS_OUT_OF_RANGE` | ||
].name; | ||
api.eventListeners[eventName]?.handlers.forEach((handle) => | ||
handle(airports) | ||
); | ||
} | ||
}); | ||
handle.subscribeToFacilitiesEx1(TYPE, IN_RANGE, OUT_OF_RANGE); | ||
const FA = SIMCONNECT_FACILITY_AIRPORT; | ||
return new Promise(async (resolve) => { | ||
const finished = () => resolve(handler); | ||
const airports = new AirportHandler(api, finished); | ||
// Airport | ||
handle.addToFacilityDefinition(FA, "OPEN AIRPORT"); // Open | ||
async function handler(varName) { | ||
return airports.get(varName); | ||
} | ||
[ | ||
`LATITUDE`, | ||
`LONGITUDE`, | ||
`ALTITUDE`, | ||
`MAGVAR`, | ||
`NAME`, | ||
`NAME64`, | ||
`REGION`, | ||
`N_RUNWAYS`, | ||
].forEach((thing) => handle.addToFacilityDefinition(FA, thing)); | ||
// Runway | ||
handle.addToFacilityDefinition(FA, "OPEN RUNWAY"); // "start of data" marker | ||
[ | ||
`LATITUDE`, | ||
`LONGITUDE`, | ||
`ALTITUDE`, | ||
`HEADING`, | ||
`LENGTH`, | ||
`WIDTH`, | ||
`PATTERN_ALTITUDE`, | ||
`SLOPE`, | ||
`TRUE_SLOPE`, | ||
`SURFACE`, | ||
].forEach((thing) => handle.addToFacilityDefinition(FA, thing)); | ||
[ | ||
`PRIMARY_NUMBER`, | ||
`PRIMARY_DESIGNATOR`, | ||
`PRIMARY_ILS_TYPE`, | ||
`PRIMARY_ILS_ICAO`, | ||
`PRIMARY_ILS_REGION`, | ||
].forEach((thing) => handle.addToFacilityDefinition(FA, thing)); | ||
[ | ||
`SECONDARY_NUMBER`, | ||
`SECONDARY_DESIGNATOR`, | ||
`SECONDARY_ILS_TYPE`, | ||
`SECONDARY_ILS_ICAO`, | ||
`SECONDARY_ILS_REGION`, | ||
].forEach((thing) => handle.addToFacilityDefinition(FA, thing)); | ||
handle.addToFacilityDefinition(FA, "CLOSE RUNWAY"); // "end of data" marker | ||
handler.supports = function (name) { | ||
if (SPECIAL_VARS.includes(name)) return true; | ||
if (name.startsWith(NEARBY_AIRPORTS)) return true; | ||
if (name.startsWith(AIRPORT)) return true; | ||
return false; | ||
}; | ||
}); | ||
} | ||
// Add support for three new airport-related variables | ||
export function airportGetHandler(api, propName) { | ||
const { handle } = api; | ||
/** | ||
* ...docs go here... | ||
*/ | ||
export class AirportHandler { | ||
constructor(api, finished) { | ||
this.api = api; | ||
this.init(api.remote, finished); | ||
} | ||
propName = propName.replaceAll(` `, `_`); | ||
async init(remote, finished) { | ||
// For some reason if we use our existing API handle, we only get 2 of the | ||
// 33 pages for "all airports", so in order to get the full list of airports, | ||
// the airport handler uses its own, dedicated simconnection. | ||
const { handle } = await open(`airport`, Protocol.KittyHawk, remote); | ||
this.handle = handle; | ||
console.log(`registering facility data`); | ||
if (propName === `NEARBY_AIRPORTS`) { | ||
return new Promise((resolve) => { | ||
const airports = []; | ||
const getID = api.nextId(); | ||
const list = []; | ||
const requestId = nextId(); | ||
await new Promise((resolve) => { | ||
const handler = (data) => { | ||
if (data.requestID === getID) { | ||
airports.push(...(data.airports ?? data.aiports)); | ||
// was this the last entry in the set? | ||
if (data.entryNumber === data.outOf - 1) { | ||
if (data.requestID === requestId) { | ||
const { airports, entryNumber, outOf } = data; | ||
list.push(...airports); | ||
if (entryNumber >= outOf - 1) { | ||
handle.off("airportList", handler); | ||
api.releaseId(getID); | ||
resolve({ [propName]: airports }); | ||
resolve(); | ||
} | ||
@@ -217,152 +135,294 @@ } | ||
handle.on("airportList", handler); | ||
// Use the Ex1 version for "only in local bubble" | ||
handle.requestFacilitiesListEx1(AIRPORT_TYPE, getID); | ||
handle.requestFacilitiesList(AIRPORT_TYPE, requestId); | ||
}); | ||
} | ||
const airportCount = list.length; | ||
// console.log(`MSFS reported ${airportCount} airports`) | ||
if (propName === `ALL_AIRPORTS`) { | ||
return new Promise((resolve) => { | ||
const airports = []; | ||
const getID = api.nextId(); | ||
const handler = (data) => { | ||
if (data.requestID === getID) { | ||
airports.push(...(data.airports ?? data.aiports)); | ||
// was this the last entry in the set? | ||
if (data.entryNumber === data.outOf - 1) { | ||
handle.off("airportList", handler); | ||
api.releaseId(getID); | ||
resolve({ [propName]: airports }); | ||
} | ||
if (fs.existsSync(AIRPORT_DB_LOCATION)) { | ||
let zipped, json; | ||
try { | ||
zipped = fs.readFileSync(AIRPORT_DB_LOCATION); | ||
} catch (e) { | ||
console.error(`file read error:`, e); | ||
} | ||
try { | ||
json = zlib.gunzipSync(zipped); | ||
} catch (e) { | ||
console.error(`zlib error:`, e); | ||
} | ||
try { | ||
this.airports = JSON.parse(json); | ||
handle.close(); | ||
// console.log(`Finished loading airport database (${this.airports.length} airports).`); | ||
if (this.airports.length !== airportCount) { | ||
console.warn( | ||
`MSFS has ${airportCount} airports, db has ${this.airports.length} airports.` | ||
); | ||
console.warn( | ||
`You may need to update your version of msfs-simconnet-api-wrapper...` | ||
); | ||
} | ||
}; | ||
handle.on("airportList", handler); | ||
// Use the regular (non-Ex1) version to get "all" | ||
handle.requestFacilitiesList(AIRPORT_TYPE, getID); | ||
}); | ||
} | ||
return finished(); | ||
} catch (e) { | ||
console.log(`JSON parse error:`, e); | ||
} | ||
} | ||
if (propName.startsWith(`AIRPORT:`)) { | ||
return new Promise((resolve) => { | ||
const AIRPORT_ICAO = propName.substring(propName.indexOf(`:`) + 1); | ||
const getID = api.nextId(); | ||
console.log(`No airport database found: building a new one.`); | ||
registerFacilityDefinition(handle); | ||
let N = 0; | ||
const AHC = 1000; | ||
setDetailFields(handle, AHC); | ||
const airportData = { icao: AIRPORT_ICAO, runways: [] }; | ||
function getAirportDetails({ icao }) { | ||
return new Promise((resolve) => { | ||
const airportData = { icao, runways: [] }; | ||
const requestID = nextId(); | ||
const handler = ({ data, type, userRequestId }) => { | ||
if (userRequestId !== getID) return; | ||
const processData = ({ userRequestId: id, type, data }) => { | ||
if (type === 0) addAirportDetails(data, airportData); | ||
if (type === 1) addAirportRunway(data, airportData); | ||
}; | ||
// airport | ||
if (type === 0) { | ||
const latitude = data.readFloat64(); | ||
const longitude = data.readFloat64(); | ||
const altitude = data.readFloat64(); | ||
const declination = data.readFloat32(); | ||
const name = data.readString32(); | ||
const name64 = data.readString64(); | ||
const region = data.readString8(); | ||
const runwayCount = data.readInt32(); | ||
const processDataEnd = ({ userRequestId: id, ...rest }) => { | ||
handle.off("facilityData", processData); | ||
handle.off("facilityDataEnd", processDataEnd); | ||
resolve(airportData); | ||
}; | ||
Object.assign(airportData, { | ||
latitude, | ||
longitude, | ||
altitude, | ||
declination, | ||
name, | ||
name64, | ||
region, | ||
runwayCount, | ||
}); | ||
} | ||
handle.on("facilityData", processData); | ||
handle.on("facilityDataEnd", processDataEnd); | ||
handle.requestFacilityData(AHC, requestID, icao); | ||
}); | ||
} | ||
// runway | ||
if (type === 1) { | ||
const latitude = data.readFloat64(); | ||
const longitude = data.readFloat64(); | ||
const altitude = data.readFloat64(); | ||
const heading = data.readFloat32(); | ||
const length = data.readFloat32(); | ||
const width = data.readFloat32(); | ||
const patternAltitude = data.readFloat32(); | ||
const slope = data.readFloat32(); | ||
const slopeTrue = data.readFloat32(); | ||
const surface = RUNWAY_SURFACES[data.readInt32()]; | ||
this.airports = []; | ||
const primary_number = RUNWAY_NUMBER[data.readInt32()]; | ||
const primary_designator = RUNWAY_DESIGNATOR[data.readInt32()]; | ||
const primary_ils_type = ILS_TYPES[data.readInt32()]; | ||
const primary_ils_icao = data.readString8(); | ||
const primary_ils_region = data.readString8(); | ||
const processAirports = async (list, resolve) => { | ||
if (list.length === 0) return resolve(); | ||
const percentage = (1 - list.length / N) * 100; | ||
console.log(`${percentage.toFixed(2)}%`); | ||
const entry = list.shift(); | ||
const details = await getAirportDetails(entry); | ||
this.airports.push(details); | ||
processAirports(list, resolve); | ||
}; | ||
const secondary_number = RUNWAY_NUMBER[data.readInt32()]; | ||
const secondary_designator = RUNWAY_DESIGNATOR[data.readInt32()]; | ||
const secondary_ils_type = ILS_TYPES[data.readInt32()]; | ||
const secondary_ils_icao = data.readString8(); | ||
const secondary_ils_region = data.readString8(); | ||
const LIST_LIMIT = Infinity; | ||
await new Promise((resolve) => { | ||
const toProcess = list.slice(0, LIST_LIMIT); | ||
N = toProcess.length; | ||
processAirports(toProcess, resolve); | ||
}); | ||
airportData.runways.push({ | ||
latitude, | ||
longitude, | ||
altitude, | ||
heading, | ||
length, | ||
width, | ||
patternAltitude, | ||
slope, | ||
slopeTrue, | ||
surface, | ||
approach: [ | ||
{ | ||
designation: primary_designator, | ||
marking: primary_number, | ||
ILS: { | ||
type: primary_ils_type, | ||
icao: primary_ils_icao, | ||
region: primary_ils_region, | ||
}, | ||
}, | ||
{ | ||
designation: secondary_designator, | ||
marking: secondary_number, | ||
ILS: { | ||
type: secondary_ils_type, | ||
icao: secondary_ils_icao, | ||
region: secondary_ils_region, | ||
}, | ||
}, | ||
], | ||
}); | ||
} | ||
}; | ||
handle.close(); | ||
handle.on("facilityData", handler); | ||
const json = JSON.stringify(this.airports, null, 2); | ||
const zipped = zlib.gzipSync(Buffer.from(json)); | ||
fs.writeFileSync(AIRPORT_DB_LOCATION, zipped); | ||
const checkDone = ({ userRequestId: id }) => { | ||
if (id === getID) { | ||
api.releaseId(getID); | ||
handle.off("facilityDataEnd", checkDone); | ||
handle.off("facilityData", handler); | ||
resolve({ | ||
[propName]: airportData, | ||
}); | ||
} | ||
}; | ||
finished(); | ||
} | ||
handle.on("facilityDataEnd", checkDone); | ||
/** | ||
* | ||
* @param {*} varName | ||
* @returns | ||
*/ | ||
async get(varName) { | ||
const propName = varName.replaceAll(` `, `_`); | ||
if (varName === ALL_AIRPORTS) { | ||
return { [propName]: this.airports }; | ||
} | ||
if (varName.startsWith(NEARBY_AIRPORTS)) { | ||
return { [propName]: await this.getAllNearbyAirports(varName) }; | ||
} | ||
if (varName.startsWith(AIRPORT)) { | ||
return { [propName]: await this.getAirport(varName) }; | ||
} | ||
return {}; | ||
} | ||
handle.requestFacilityData( | ||
SIMCONNECT_FACILITY_AIRPORT, | ||
getID, | ||
AIRPORT_ICAO | ||
); | ||
/** | ||
* | ||
* @param {*} varName | ||
* @returns | ||
*/ | ||
async getAllNearbyAirports(varName) { | ||
const { PLANE_LATITUDE: lat, PLANE_LONGITUDE: long } = await this.api.get( | ||
`PLANE_LATITUDE`, | ||
`PLANE_LONGITUDE` | ||
); | ||
const distanceNM = getVarArg(varName) ?? 200; | ||
const KM = distanceNM * KM_PER_NM; | ||
const found = []; | ||
this.airports.forEach((airport) => { | ||
const alat = radians(airport.latitude); | ||
const along = radians(airport.longitude); | ||
const d = dist(lat, long, alat, along); | ||
if (d <= KM) found.push({ ...airport, distance: d / KM_PER_NM }); | ||
}); | ||
return found.sort((a, b) => a.distance - b.distance); | ||
} | ||
/** | ||
* | ||
* @param {*} varName | ||
* @returns | ||
*/ | ||
async getAirport(varName) { | ||
const icao = getVarArg(varName); | ||
return { | ||
[varName]: this.airports.find((airport) => airport.icao === icao), | ||
}; | ||
} | ||
} | ||
// Used in getSpecial to check whether to forward a variable | ||
airportGetHandler.supports = (name) => { | ||
const pos = name.indexOf(`:`); | ||
if (pos !== -1) name = name.substring(0, pos); | ||
return SPECIAL_VARS.includes(name); | ||
}; | ||
/** | ||
* | ||
* @param {*} varName | ||
* @returns | ||
*/ | ||
function getVarArg(varName) { | ||
if (!varName.includes(`:`)) return; | ||
return varName.substring(varName.indexOf(`:`) + 1); | ||
} | ||
/** | ||
* | ||
* @param {*} handle | ||
* @param {*} AHC | ||
*/ | ||
function setDetailFields(handle, AHC) { | ||
const add = (name) => handle.addToFacilityDefinition(AHC, name); | ||
// airport | ||
add(`OPEN AIRPORT`); | ||
add(`LATITUDE`); | ||
add(`LONGITUDE`); | ||
add(`ALTITUDE`); | ||
add(`MAGVAR`); | ||
add(`NAME`); | ||
add(`NAME64`); | ||
add(`REGION`); | ||
add(`N_RUNWAYS`); | ||
{ | ||
// runways | ||
add(`OPEN RUNWAY`); | ||
add(`LATITUDE`); | ||
add(`LONGITUDE`); | ||
add(`ALTITUDE`); | ||
add(`HEADING`); | ||
add(`LENGTH`); | ||
add(`WIDTH`); | ||
add(`PATTERN_ALTITUDE`); | ||
add(`SLOPE`); | ||
add(`TRUE_SLOPE`); | ||
add(`SURFACE`); | ||
// ILS details | ||
add(`PRIMARY_NUMBER`); | ||
add(`PRIMARY_DESIGNATOR`); | ||
add(`PRIMARY_ILS_TYPE`); | ||
add(`PRIMARY_ILS_ICAO`); | ||
add(`PRIMARY_ILS_REGION`); | ||
add(`SECONDARY_NUMBER`); | ||
add(`SECONDARY_DESIGNATOR`); | ||
add(`SECONDARY_ILS_TYPE`); | ||
add(`SECONDARY_ILS_ICAO`); | ||
add(`SECONDARY_ILS_REGION`); | ||
add(`CLOSE RUNWAY`); | ||
} | ||
add("CLOSE AIRPORT"); // Close | ||
} | ||
/** | ||
* | ||
* @param {*} data | ||
* @param {*} airportData | ||
*/ | ||
function addAirportDetails(data, airportData) { | ||
const latitude = data.readFloat64(); | ||
const longitude = data.readFloat64(); | ||
const altitude = data.readFloat64() * FEET_PER_METERS; | ||
const declination = data.readFloat32(); | ||
const name = data.readString32(); | ||
const name64 = data.readString64(); | ||
const region = data.readString8(); | ||
const runwayCount = data.readInt32(); | ||
Object.assign(airportData, { | ||
latitude, | ||
longitude, | ||
altitude, | ||
declination, | ||
name, | ||
name64, | ||
region, | ||
runwayCount, | ||
}); | ||
} | ||
/** | ||
* | ||
* @param {*} data | ||
* @param {*} airportData | ||
*/ | ||
function addAirportRunway(data, airportData) { | ||
const latitude = data.readFloat64(); | ||
const longitude = data.readFloat64(); | ||
const altitude = data.readFloat64() * FEET_PER_METERS; | ||
const heading = data.readFloat32(); | ||
const length = data.readFloat32(); | ||
const width = data.readFloat32(); | ||
const patternAltitude = data.readFloat32(); | ||
const slope = degrees(data.readFloat32()); | ||
const slopeTrue = degrees(data.readFloat32()); | ||
const surface = RUNWAY_SURFACES[data.readInt32()]; | ||
const primary_number = RUNWAY_NUMBER[data.readInt32()]; | ||
const primary_designator = RUNWAY_DESIGNATOR[data.readInt32()]; | ||
const primary_ils_type = ILS_TYPES[data.readInt32()]; | ||
const primary_ils_icao = data.readString8(); | ||
const primary_ils_region = data.readString8(); | ||
const secondary_number = RUNWAY_NUMBER[data.readInt32()]; | ||
const secondary_designator = RUNWAY_DESIGNATOR[data.readInt32()]; | ||
const secondary_ils_type = ILS_TYPES[data.readInt32()]; | ||
const secondary_ils_icao = data.readString8(); | ||
const secondary_ils_region = data.readString8(); | ||
const approach = [ | ||
{ | ||
designation: primary_designator, | ||
marking: primary_number, | ||
ILS: { | ||
type: primary_ils_type, | ||
icao: primary_ils_icao, | ||
region: primary_ils_region, | ||
}, | ||
}, | ||
{ | ||
designation: secondary_designator, | ||
marking: secondary_number, | ||
ILS: { | ||
type: secondary_ils_type, | ||
icao: secondary_ils_icao, | ||
region: secondary_ils_region, | ||
}, | ||
}, | ||
]; | ||
airportData.runways.push({ | ||
latitude, | ||
longitude, | ||
altitude, | ||
heading, | ||
length, | ||
width, | ||
patternAltitude, | ||
slope, | ||
slopeTrue, | ||
surface, | ||
approach, | ||
}); | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6366546
30
7274
215