@fboes/aerofly-data 1.8.1 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +1 -10
- package/CHANGELOG.md +6 -0
- package/data/aircraft-liveries.json +178 -23
- package/data/aircraft-liveries.json.d.ts +3 -0
- package/data/aircraft.json +132 -0
- package/data/aircraft.json.d.ts +3 -0
- package/data/airport-coordinates-object.json +1915 -1915
- package/data/airport-coordinates.json +331 -331
- package/data/airport-list.json +2 -2
- package/data/airports.geojson +83295 -83294
- package/dist/main.js +1 -1
- package/get-airports.js +126 -78
- package/package.json +1 -1
- package/src/aircraft-functions.js +33 -9
- package/src/airport-functions.js +5 -8
package/dist/main.js
CHANGED
package/get-airports.js
CHANGED
|
@@ -14,8 +14,6 @@ import {
|
|
|
14
14
|
} from "./src/airport-functions.js";
|
|
15
15
|
|
|
16
16
|
const inputDirectory = process.argv[2] ?? ".";
|
|
17
|
-
const icaoFilterArg = process.argv[3]?.replace(/[^A-Z]/, "").toUpperCase();
|
|
18
|
-
const icaoFilter = icaoFilterArg ? new RegExp("^[" + icaoFilterArg + "]") : null;
|
|
19
17
|
|
|
20
18
|
// Ensure the output directory exists
|
|
21
19
|
const outputDirectory = path.join("data");
|
|
@@ -26,108 +24,144 @@ if (!fs.existsSync(outputDirectory)) {
|
|
|
26
24
|
const aeroflyGeoJson = new GeoJSON.FeatureCollection();
|
|
27
25
|
const aeroflyAirports = new Map([
|
|
28
26
|
...addCustomAeroflyAirportsToMap(path.join(outputDirectory, "airports-custom.md")),
|
|
29
|
-
...getAeroflyAirports(inputDirectory
|
|
27
|
+
...getAeroflyAirports(inputDirectory),
|
|
30
28
|
]);
|
|
31
29
|
const aeroflyAirportsLength = aeroflyAirports.size;
|
|
32
30
|
process.stdout.write(`Found \x1b[92m${aeroflyAirports.size}\x1b[0m Aerofly FS Airports
|
|
33
31
|
`);
|
|
34
32
|
|
|
35
33
|
const airportsSource = fs.readFileSync(`tmp/airports.csv`);
|
|
36
|
-
/** @type {string[][]} with a single CSV line from airports.csv */
|
|
37
|
-
const airportsRecords = parse(airportsSource, { bom: true });
|
|
38
34
|
|
|
35
|
+
/** @type {string[][]} with a single CSV line from airports.csv */
|
|
36
|
+
let airportsRecords = parse(airportsSource, { bom: true });
|
|
39
37
|
let airportsRecordsProcessed = 0;
|
|
40
38
|
|
|
41
39
|
// Collect all ICAO codes
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @type {string[]}
|
|
43
|
+
*/
|
|
42
44
|
const icaoCodes = [];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @type {{
|
|
48
|
+
* code: String,
|
|
49
|
+
* name: String,
|
|
50
|
+
* lat: Number,
|
|
51
|
+
* lon: Number
|
|
52
|
+
* }[]}
|
|
53
|
+
*/
|
|
43
54
|
const icaoCoordinatesObject = [];
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param {string[]} searchWords
|
|
59
|
+
* @returns {[number, string]|[undefined, undefined]} Returns the length of the airport and the code if found, otherwise undefined.
|
|
60
|
+
*/
|
|
61
|
+
const getAeroflyAirport = (searchWords) => {
|
|
62
|
+
for (const word of searchWords) {
|
|
63
|
+
if (aeroflyAirports.has(word)) {
|
|
64
|
+
return [aeroflyAirports.get(word) || 0, word];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return [undefined, undefined];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} aeroflyCode
|
|
72
|
+
* @param {string} bestCode
|
|
73
|
+
* @param {string[]} airportsRecord
|
|
74
|
+
* @param {number} length
|
|
75
|
+
*/
|
|
76
|
+
const addRecord = (aeroflyCode, bestCode, airportsRecord, length) => {
|
|
77
|
+
// Remove airport from list of Aerofly FS4 Airports
|
|
78
|
+
aeroflyAirports.delete(aeroflyCode);
|
|
79
|
+
|
|
80
|
+
const isMilitary =
|
|
81
|
+
airportsRecord[3].match(/\b(base|rnas|raf|naval|air\s?force|coast\s?guard|army|afs|mod|cgas)\b/i) !== null;
|
|
82
|
+
let type = airportsRecord[2];
|
|
83
|
+
if (isMilitary) {
|
|
84
|
+
type = type.replace(/port/, "base");
|
|
85
|
+
}
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
// Filter community airports and add regular airports to secondary lists
|
|
88
|
+
if (length > 0) {
|
|
89
|
+
icaoCodes.push(bestCode);
|
|
90
|
+
icaoCoordinatesObject.push({
|
|
91
|
+
code: bestCode,
|
|
92
|
+
name: airportsRecord[3],
|
|
93
|
+
lat: Number(airportsRecord[4]),
|
|
94
|
+
lon: Number(airportsRecord[5]),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
52
97
|
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
const feature = new GeoJSON.Feature(
|
|
99
|
+
new GeoJSON.Point(Number(airportsRecord[5]), Number(airportsRecord[4]), Number(airportsRecord[6]) * 0.3048),
|
|
100
|
+
{
|
|
101
|
+
title: bestCode,
|
|
102
|
+
type: geoJsonType(type, isMilitary, length),
|
|
103
|
+
description: airportsRecord[3],
|
|
104
|
+
elevation: Number(airportsRecord[6]),
|
|
105
|
+
municipality: airportsRecord[10],
|
|
106
|
+
fileSize: Math.ceil(length),
|
|
107
|
+
"marker-symbol": airportsRecord[2].match(/heliport/)
|
|
108
|
+
? "heliport"
|
|
109
|
+
: airportsRecord[2].match(/small/)
|
|
110
|
+
? "airfield"
|
|
111
|
+
: "airport",
|
|
112
|
+
"marker-color": airportsRecord[2].match(/large/)
|
|
113
|
+
? "#5e6eba"
|
|
114
|
+
: airportsRecord[2].match(/small/)
|
|
115
|
+
? "#777777"
|
|
116
|
+
: "#555555",
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
if (isMilitary) {
|
|
120
|
+
feature.setProperty("isMilitary", true);
|
|
55
121
|
}
|
|
56
122
|
|
|
57
|
-
|
|
123
|
+
aeroflyGeoJson.addFeature(feature);
|
|
124
|
+
};
|
|
58
125
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* @param {string[]} searchWords
|
|
62
|
-
* @returns {[number, string]|[undefined, undefined]} Returns the length of the airport and the code if found, otherwise undefined.
|
|
63
|
-
*/
|
|
64
|
-
const getAeroflyAirport = (searchWords) => {
|
|
65
|
-
for (const word of searchWords) {
|
|
66
|
-
if (aeroflyAirports.has(word)) {
|
|
67
|
-
return [aeroflyAirports.get(word) || 0, word];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return [undefined, undefined];
|
|
71
|
-
};
|
|
126
|
+
// -----------------------------------------------------------------------------
|
|
127
|
+
// PARSING
|
|
72
128
|
|
|
73
|
-
|
|
129
|
+
// 3685,"KMIA","large_airport","Miami International Airport",25.79319953918457,-80.29060363769531,8,"NA","US","US-FL","Miami","yes","KMIA","MIA","MIA","http://www.miami-airport.com/","https://en.wikipedia.org/wiki/
|
|
130
|
+
// 19927,"KGBN","small_airport","Gila Bend Air Force Auxiliary Airport",32.887501,-112.720001,883,"NA","US","US-AZ","Gila Bend","no",,,"KGXF","GXF",,"https://en.wikipedia.org/wiki/Gila_Bend_Air_Force_Auxiliary_Field",
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
|
|
132
|
+
process.stdout.write("ROUND 1: ICAO codes\n");
|
|
133
|
+
for (const airportsRecord of airportsRecords) {
|
|
134
|
+
airportsRecordsProcessed++;
|
|
135
|
+
const icaoCode = airportsRecord[12];
|
|
136
|
+
const [length, aeroflyCode] = getAeroflyAirport([icaoCode]);
|
|
77
137
|
|
|
78
|
-
|
|
79
|
-
|
|
138
|
+
if (length !== undefined && aeroflyCode !== undefined) {
|
|
139
|
+
addRecord(aeroflyCode, icaoCode, airportsRecord, length);
|
|
140
|
+
}
|
|
80
141
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
142
|
+
if (airportsRecordsProcessed % 5000 === 0) {
|
|
143
|
+
const index = aeroflyAirportsLength - aeroflyAirports.size;
|
|
144
|
+
process.stdout
|
|
145
|
+
.write(` Processed \x1b[92m${String(airportsRecordsProcessed).padStart(5)}\x1b[0m airport records, found \x1b[92m${String(index).padStart(5)}\x1b[0m Aerofly FS Airports
|
|
84
146
|
`);
|
|
85
|
-
|
|
147
|
+
}
|
|
148
|
+
}
|
|
86
149
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let type = airportsRecord[2];
|
|
90
|
-
if (isMilitary) {
|
|
91
|
-
type = type.replace(/port/, "base");
|
|
92
|
-
}
|
|
150
|
+
airportsRecords = parse(airportsSource, { bom: true });
|
|
151
|
+
airportsRecordsProcessed = 0;
|
|
93
152
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
lat: Number(airportsRecord[4]),
|
|
101
|
-
lon: Number(airportsRecord[5]),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
153
|
+
process.stdout.write("ROUND 2: Other codes\n");
|
|
154
|
+
for (const airportsRecord of airportsRecords) {
|
|
155
|
+
airportsRecordsProcessed++;
|
|
156
|
+
const ident = airportsRecord[1];
|
|
157
|
+
const icaoCode = airportsRecord[12];
|
|
158
|
+
const searchWords = getAirportSearchWords(ident, icaoCode, airportsRecord);
|
|
104
159
|
|
|
105
|
-
|
|
106
|
-
new GeoJSON.Point(Number(airportsRecord[5]), Number(airportsRecord[4]), Number(airportsRecord[6]) * 0.3048),
|
|
107
|
-
{
|
|
108
|
-
title: bestCode,
|
|
109
|
-
type: geoJsonType(type, isMilitary, length),
|
|
110
|
-
description: airportsRecord[3],
|
|
111
|
-
elevation: Number(airportsRecord[6]),
|
|
112
|
-
municipality: airportsRecord[10],
|
|
113
|
-
fileSize: Math.ceil(length),
|
|
114
|
-
"marker-symbol": airportsRecord[2].match(/heliport/)
|
|
115
|
-
? "heliport"
|
|
116
|
-
: airportsRecord[2].match(/small/)
|
|
117
|
-
? "airfield"
|
|
118
|
-
: "airport",
|
|
119
|
-
"marker-color": airportsRecord[2].match(/large/)
|
|
120
|
-
? "#5e6eba"
|
|
121
|
-
: airportsRecord[2].match(/small/)
|
|
122
|
-
? "#777777"
|
|
123
|
-
: "#555555",
|
|
124
|
-
},
|
|
125
|
-
);
|
|
126
|
-
if (isMilitary) {
|
|
127
|
-
feature.setProperty("isMilitary", true);
|
|
128
|
-
}
|
|
160
|
+
const [length, aeroflyCode] = getAeroflyAirport(searchWords);
|
|
129
161
|
|
|
130
|
-
|
|
162
|
+
if (length !== undefined && aeroflyCode !== undefined) {
|
|
163
|
+
const bestCode = icaoCode || aeroflyCode || ident;
|
|
164
|
+
addRecord(aeroflyCode, bestCode, airportsRecord, length);
|
|
131
165
|
}
|
|
132
166
|
|
|
133
167
|
if (airportsRecordsProcessed % 5000 === 0) {
|
|
@@ -138,6 +172,19 @@ for (const airportsRecord of airportsRecords) {
|
|
|
138
172
|
}
|
|
139
173
|
}
|
|
140
174
|
|
|
175
|
+
icaoCoordinatesObject.sort((a, b) => {
|
|
176
|
+
if (a.code < b.code) {
|
|
177
|
+
return -1;
|
|
178
|
+
}
|
|
179
|
+
if (a.code > b.code) {
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
return 0;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// -----------------------------------------------------------------------------
|
|
186
|
+
// OUTPUT
|
|
187
|
+
|
|
141
188
|
// Write the GeoJSON data to a file
|
|
142
189
|
const outputFilePath = path.join(outputDirectory, "airports.geojson");
|
|
143
190
|
fs.writeFileSync(outputFilePath, JSON.stringify(aeroflyGeoJson, null, 2), "utf-8");
|
|
@@ -184,7 +231,8 @@ process.stdout.write(`Unmatched airports file written to \x1b[92m${unmatchedFile
|
|
|
184
231
|
|
|
185
232
|
if (aeroflyAirports.size > 0) {
|
|
186
233
|
process.stdout.write(
|
|
187
|
-
|
|
234
|
+
`\
|
|
235
|
+
Missing airport matches for \x1b[92m${aeroflyAirports.size}\x1b[0m Aerofly FS4 Airports, \x1b[92m${(
|
|
188
236
|
(aeroflyAirports.size / aeroflyAirportsLength) *
|
|
189
237
|
100
|
|
190
238
|
).toFixed(1)}%\x1b[0m
|
package/package.json
CHANGED
|
@@ -14,6 +14,9 @@ import * as path from "path";
|
|
|
14
14
|
* cruiseAltitudeFt: number,
|
|
15
15
|
* cruiseSpeedKts: number,
|
|
16
16
|
* maximumRangeNm: number,
|
|
17
|
+
* maximumLoadRangeNm: number,
|
|
18
|
+
* maximumFuelRangeNm: number,
|
|
19
|
+
* maximumFerryRangeNm: number,
|
|
17
20
|
* maximumFuelMassKg?: number,
|
|
18
21
|
* maximumPayloadKg?: number,
|
|
19
22
|
* maximumTakeoffMassKg?: number,
|
|
@@ -88,14 +91,16 @@ export const getAeroflyAircraft = (directory) => {
|
|
|
88
91
|
requirements: parseRequirements(tmdFileContent),
|
|
89
92
|
};
|
|
90
93
|
|
|
91
|
-
const liveryIcaoCode = getLiveryIcaoCode(livery.name);
|
|
92
|
-
if (liveryIcaoCode !== "") {
|
|
93
|
-
livery.icaoCode = liveryIcaoCode;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
94
|
return livery;
|
|
97
95
|
}),
|
|
98
|
-
]
|
|
96
|
+
].map((livery) => {
|
|
97
|
+
const liveryIcaoCode = getLiveryIcaoCode(livery.name);
|
|
98
|
+
if (liveryIcaoCode !== "") {
|
|
99
|
+
livery.icaoCode = liveryIcaoCode;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return livery;
|
|
103
|
+
});
|
|
99
104
|
|
|
100
105
|
return {
|
|
101
106
|
aeroflyCode: dirent.name,
|
|
@@ -113,13 +118,15 @@ export const getAeroflyAircraft = (directory) => {
|
|
|
113
118
|
const getLiveryIcaoCode = (airlineName) => {
|
|
114
119
|
const name = airlineName.toLowerCase();
|
|
115
120
|
switch (name) {
|
|
121
|
+
case "all nippon airlines":
|
|
122
|
+
return "ANA";
|
|
116
123
|
case "american airlines":
|
|
117
124
|
case "american":
|
|
118
125
|
return "AAL";
|
|
119
126
|
case "all nippon airways":
|
|
120
127
|
return "ANA";
|
|
121
|
-
case "
|
|
122
|
-
return "
|
|
128
|
+
case "china eastern":
|
|
129
|
+
return "CES";
|
|
123
130
|
case "lufthansa":
|
|
124
131
|
return "DLH";
|
|
125
132
|
case "united airlines":
|
|
@@ -128,6 +135,12 @@ const getLiveryIcaoCode = (airlineName) => {
|
|
|
128
135
|
case "delta air lines":
|
|
129
136
|
case "delta":
|
|
130
137
|
return "DAL";
|
|
138
|
+
case "emirates":
|
|
139
|
+
return "UAE";
|
|
140
|
+
case "singapore airlines":
|
|
141
|
+
return "SIA";
|
|
142
|
+
case "ethiopian":
|
|
143
|
+
return "ETH";
|
|
131
144
|
case "air france":
|
|
132
145
|
return "AFR";
|
|
133
146
|
case "british airways":
|
|
@@ -224,6 +237,9 @@ export const parseAircraft = (tmdFileContent) => {
|
|
|
224
237
|
),
|
|
225
238
|
MaximumSpeed: convertSpeed(parseTmdLine(tmdFileContent, "MaximumSpeed")),*/
|
|
226
239
|
maximumRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumRange")),
|
|
240
|
+
maximumLoadRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumLoadRange")),
|
|
241
|
+
maximumFuelRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumFuelRange")),
|
|
242
|
+
maximumFerryRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumFerryRange")),
|
|
227
243
|
/*FlapAirspeedRange: parseTmdLine(tmdFileContent, "FlapAirspeedRange")
|
|
228
244
|
.trim()
|
|
229
245
|
.split(" ")
|
|
@@ -293,7 +309,7 @@ export const convertAltitude = (altitude) => {
|
|
|
293
309
|
/**
|
|
294
310
|
*
|
|
295
311
|
* @param {string} range in m
|
|
296
|
-
* @returns {number} in
|
|
312
|
+
* @returns {number} in NM
|
|
297
313
|
*/
|
|
298
314
|
export const convertDistance = (range) => {
|
|
299
315
|
return Math.round(Number(range) / 1852);
|
|
@@ -437,3 +453,11 @@ ${html}\
|
|
|
437
453
|
</select>
|
|
438
454
|
`;
|
|
439
455
|
};
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* @param {AeroflyAircraft[]} sortedAircraft
|
|
459
|
+
* @returns {string[]}
|
|
460
|
+
*/
|
|
461
|
+
export const getTags = (sortedAircraft) => {
|
|
462
|
+
return [...new Set(sortedAircraft.flatMap((aircraft) => aircraft.tags))].sort();
|
|
463
|
+
};
|
package/src/airport-functions.js
CHANGED
|
@@ -40,10 +40,9 @@ export const geoJsonType = (type, isMilitary, length) => {
|
|
|
40
40
|
/**
|
|
41
41
|
*
|
|
42
42
|
* @param {string} directory
|
|
43
|
-
* @param {RegExp?} icaoFilter
|
|
44
43
|
* @returns {Map<string,number>}
|
|
45
44
|
*/
|
|
46
|
-
export const getAeroflyAirports = (directory
|
|
45
|
+
export const getAeroflyAirports = (directory) => {
|
|
47
46
|
const aeroflyAirports = new Map();
|
|
48
47
|
let maxLength = 0;
|
|
49
48
|
let minLength = 10000;
|
|
@@ -55,12 +54,10 @@ export const getAeroflyAirports = (directory, icaoFilter) => {
|
|
|
55
54
|
|
|
56
55
|
for (const file of files) {
|
|
57
56
|
const icaoCode = file.replace(/\.wad$/, "").toUpperCase();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
aeroflyAirports.set(icaoCode, stats.size);
|
|
63
|
-
}
|
|
57
|
+
const stats = fs.statSync(path.join(directory, file));
|
|
58
|
+
maxLength = Math.max(maxLength, stats.size);
|
|
59
|
+
minLength = Math.min(minLength, stats.size);
|
|
60
|
+
aeroflyAirports.set(icaoCode, stats.size);
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
return aeroflyAirports;
|