@fboes/aerofly-data 1.8.0 → 1.8.2
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 +4 -0
- package/data/aircraft-liveries.json +132 -0
- package/data/aircraft-liveries.json.d.ts +4 -0
- package/data/aircraft.json +132 -0
- package/data/aircraft.json.d.ts +4 -0
- package/data/airport-coordinates-object.json +1905 -1905
- package/data/airport-coordinates.json +328 -328
- package/data/airport-list.json +1 -1
- package/data/airports.geojson +83351 -83350
- package/dist/main.js +1 -1
- package/get-airports.js +126 -78
- package/package.json +1 -1
- package/src/aircraft-functions.js +15 -3
- 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,
|
|
@@ -118,8 +121,6 @@ const getLiveryIcaoCode = (airlineName) => {
|
|
|
118
121
|
return "AAL";
|
|
119
122
|
case "all nippon airways":
|
|
120
123
|
return "ANA";
|
|
121
|
-
case "american airlines":
|
|
122
|
-
return "AAL";
|
|
123
124
|
case "lufthansa":
|
|
124
125
|
return "DLH";
|
|
125
126
|
case "united airlines":
|
|
@@ -224,6 +225,9 @@ export const parseAircraft = (tmdFileContent) => {
|
|
|
224
225
|
),
|
|
225
226
|
MaximumSpeed: convertSpeed(parseTmdLine(tmdFileContent, "MaximumSpeed")),*/
|
|
226
227
|
maximumRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumRange")),
|
|
228
|
+
maximumLoadRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumLoadRange")),
|
|
229
|
+
maximumFuelRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumFuelRange")),
|
|
230
|
+
maximumFerryRangeNm: convertDistance(parseTmdLine(tmdFileContent, "MaximumFerryRange")),
|
|
227
231
|
/*FlapAirspeedRange: parseTmdLine(tmdFileContent, "FlapAirspeedRange")
|
|
228
232
|
.trim()
|
|
229
233
|
.split(" ")
|
|
@@ -293,7 +297,7 @@ export const convertAltitude = (altitude) => {
|
|
|
293
297
|
/**
|
|
294
298
|
*
|
|
295
299
|
* @param {string} range in m
|
|
296
|
-
* @returns {number} in
|
|
300
|
+
* @returns {number} in NM
|
|
297
301
|
*/
|
|
298
302
|
export const convertDistance = (range) => {
|
|
299
303
|
return Math.round(Number(range) / 1852);
|
|
@@ -437,3 +441,11 @@ ${html}\
|
|
|
437
441
|
</select>
|
|
438
442
|
`;
|
|
439
443
|
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @param {AeroflyAircraft[]} sortedAircraft
|
|
447
|
+
* @returns {string[]}
|
|
448
|
+
*/
|
|
449
|
+
export const getTags = (sortedAircraft) => {
|
|
450
|
+
return [...new Set(sortedAircraft.flatMap((aircraft) => aircraft.tags))].sort();
|
|
451
|
+
};
|
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;
|