@fboes/aerofly-data 1.0.0
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 +22 -0
- package/.prettierignore +4 -0
- package/LICENSE.txt +9 -0
- package/README.md +51 -0
- package/data/aircraft-liveries.json +2488 -0
- package/data/aircraft-select.html +41 -0
- package/data/aircraft.json +786 -0
- package/data/aircraft.md +43 -0
- package/data/airport-list.json +6677 -0
- package/data/airports.geojson +140601 -0
- package/dist/index.html +53 -0
- package/get-aircraft.js +266 -0
- package/get-airports.js +212 -0
- package/icons/af-airfield.svg +3 -0
- package/icons/af-closed.svg +3 -0
- package/icons/af-heliport.svg +3 -0
- package/icons/af-large_airbase.svg +3 -0
- package/icons/af-large_airport.svg +4 -0
- package/icons/af-medium_airbase.svg +3 -0
- package/icons/af-medium_airport.svg +3 -0
- package/icons/af-small_airbase.svg +3 -0
- package/icons/af-small_airport.svg +3 -0
- package/icons/navaid-fix.svg +3 -0
- package/icons/navaid-ndb.svg +3 -0
- package/icons/navaid-vor.svg +3 -0
- package/icons/navaid-waypoint.svg +3 -0
- package/package.json +27 -0
- package/tmp/.gitkeep +0 -0
package/dist/index.html
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Aerofly FS Airport Code Validator</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
margin: 20px;
|
|
11
|
+
text-align: center;
|
|
12
|
+
}
|
|
13
|
+
#status {
|
|
14
|
+
font-size: 1.5em;
|
|
15
|
+
margin-left: 10px;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<h1>Aerofly FS Airport Code Validator</h1>
|
|
21
|
+
<p>Enter an ICAO airport code:</p>
|
|
22
|
+
<input type="text" id="icaoInput" placeholder="Enter ICAO code" pattern="A-Za-z0-9" autocapitalize="on" />
|
|
23
|
+
<span id="status">❓</span>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
// Load the airport list from the JSON file
|
|
27
|
+
let airportList = [];
|
|
28
|
+
fetch('./data/airport-list.json')
|
|
29
|
+
.then(response => response.json())
|
|
30
|
+
.then(data => {
|
|
31
|
+
airportList = data;
|
|
32
|
+
})
|
|
33
|
+
.catch(error => {
|
|
34
|
+
console.error('Error loading airport list:', error);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Validate the input
|
|
38
|
+
const inputField = document.getElementById('icaoInput');
|
|
39
|
+
const status = document.getElementById('status');
|
|
40
|
+
|
|
41
|
+
inputField.addEventListener('input', () => {
|
|
42
|
+
const inputValue = inputField.value.toUpperCase();
|
|
43
|
+
if (airportList.includes(inputValue)) {
|
|
44
|
+
status.textContent = '✅'; // Green checkmark
|
|
45
|
+
} else if (inputValue.trim() === '') {
|
|
46
|
+
status.textContent = '❓'; // Question mark for empty input
|
|
47
|
+
} else {
|
|
48
|
+
status.textContent = '🚫'; // Red forbidden sign
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
package/get-aircraft.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
//@ts-check
|
|
4
|
+
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef AeroflyAircraftParsed
|
|
10
|
+
* @type {{
|
|
11
|
+
* name: string,
|
|
12
|
+
* nameFull: string,
|
|
13
|
+
* icaoCode: string,
|
|
14
|
+
* tags: string[],
|
|
15
|
+
* approachAirspeedKts: number,
|
|
16
|
+
* cruiseAltitudeFt: number,
|
|
17
|
+
* cruiseSpeedKts: number,
|
|
18
|
+
* maximumRangeNm: number,
|
|
19
|
+
* }}
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef AeroflyAircraft
|
|
24
|
+
* @type {AeroflyAircraftParsed & {
|
|
25
|
+
* aeroflyCode: string,
|
|
26
|
+
* liveries: {
|
|
27
|
+
* aeroflyCode: string,
|
|
28
|
+
* name: string,
|
|
29
|
+
* }[],
|
|
30
|
+
* }}
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
*
|
|
35
|
+
* @param {string} directory
|
|
36
|
+
* @returns {AeroflyAircraft[]}
|
|
37
|
+
*/
|
|
38
|
+
const getAeroflyAircraft = (directory) => {
|
|
39
|
+
return fs
|
|
40
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
41
|
+
.filter((dirent) => dirent.isDirectory())
|
|
42
|
+
.sort()
|
|
43
|
+
.map((dirent) => {
|
|
44
|
+
const tmdFileContent = fs.readFileSync(
|
|
45
|
+
path.join(dirent.parentPath, dirent.name, dirent.name + ".tmc"),
|
|
46
|
+
"utf8"
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const tmdOptionFileContent = fs.readFileSync(
|
|
50
|
+
path.join(dirent.parentPath, dirent.name, "option.tmc"),
|
|
51
|
+
"utf8"
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const liveries = [
|
|
55
|
+
{
|
|
56
|
+
aeroflyCode: "default",
|
|
57
|
+
name: parseTmdLine(tmdOptionFileContent, "Description"),
|
|
58
|
+
},
|
|
59
|
+
...fs
|
|
60
|
+
.readdirSync(path.join(dirent.parentPath, dirent.name), {
|
|
61
|
+
withFileTypes: true,
|
|
62
|
+
})
|
|
63
|
+
.filter((dirent) => dirent.isDirectory())
|
|
64
|
+
.filter((dirent) =>
|
|
65
|
+
fs.existsSync(
|
|
66
|
+
path.join(dirent.parentPath, dirent.name, "preview.ttx")
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
.sort()
|
|
70
|
+
.map((dirent) => {
|
|
71
|
+
const tmdFileContent = fs.readFileSync(
|
|
72
|
+
path.join(dirent.parentPath, dirent.name, "option.tmc"),
|
|
73
|
+
"utf8"
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
aeroflyCode: dirent.name,
|
|
78
|
+
name: parseTmdLine(tmdFileContent, "Description"),
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...parseAircraft(tmdFileContent),
|
|
85
|
+
aeroflyCode: dirent.name,
|
|
86
|
+
liveries,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} tmdFileContent
|
|
93
|
+
* @returns {AeroflyAircraftParsed}
|
|
94
|
+
*/
|
|
95
|
+
const parseAircraft = (tmdFileContent) => {
|
|
96
|
+
const tags = parseTmdLine(tmdFileContent, "Tags").trim().split(" ");
|
|
97
|
+
|
|
98
|
+
// type: ;
|
|
99
|
+
return {
|
|
100
|
+
name: parseTmdLine(tmdFileContent, "DisplayName"),
|
|
101
|
+
nameFull: parseTmdLine(tmdFileContent, "DisplayNameFull"),
|
|
102
|
+
icaoCode: parseTmdLine(tmdFileContent, "ICAO"),
|
|
103
|
+
tags,
|
|
104
|
+
/*MinimumAirspeed: convertSpeed(
|
|
105
|
+
parseTmdLine(tmdFileContent, "MinimumAirspeed")
|
|
106
|
+
),*/
|
|
107
|
+
approachAirspeedKts: convertSpeed(
|
|
108
|
+
parseTmdLine(tmdFileContent, "ApproachAirspeed")
|
|
109
|
+
),
|
|
110
|
+
/*CruiseAirspeed: convertSpeed(
|
|
111
|
+
parseTmdLine(tmdFileContent, "CruiseAirspeed")
|
|
112
|
+
),*/
|
|
113
|
+
cruiseAltitudeFt: convertAltitude(
|
|
114
|
+
parseTmdLine(tmdFileContent, "CruiseAltitude")
|
|
115
|
+
),
|
|
116
|
+
cruiseSpeedKts: convertSpeed(parseTmdLine(tmdFileContent, "CruiseSpeed")),
|
|
117
|
+
/*MaximumAirspeed: convertSpeed(
|
|
118
|
+
parseTmdLine(tmdFileContent, "MaximumAirspeed")
|
|
119
|
+
),
|
|
120
|
+
MaximumAltitude: convertAltitude(
|
|
121
|
+
parseTmdLine(tmdFileContent, "MaximumAltitude")
|
|
122
|
+
),
|
|
123
|
+
MaximumSpeed: convertSpeed(parseTmdLine(tmdFileContent, "MaximumSpeed")),*/
|
|
124
|
+
maximumRangeNm: convertDistance(
|
|
125
|
+
parseTmdLine(tmdFileContent, "MaximumRange")
|
|
126
|
+
),
|
|
127
|
+
/*FlapAirspeedRange: parseTmdLine(tmdFileContent, "FlapAirspeedRange")
|
|
128
|
+
.trim()
|
|
129
|
+
.split(" ")
|
|
130
|
+
.map((v) => convertSpeed(v)),
|
|
131
|
+
NormalAirspeedRange: parseTmdLine(tmdFileContent, "NormalAirspeedRange")
|
|
132
|
+
.trim()
|
|
133
|
+
.split(" ")
|
|
134
|
+
.map((v) => convertSpeed(v)),
|
|
135
|
+
CautionAirspeedRange: parseTmdLine(tmdFileContent, "CautionAirspeedRange")
|
|
136
|
+
.trim()
|
|
137
|
+
.split(" ")
|
|
138
|
+
.map((v) => convertSpeed(v)),*/
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
*
|
|
144
|
+
* @param {string} tmdFileContent
|
|
145
|
+
* @param {string} key
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
const parseTmdLine = (tmdFileContent, key) => {
|
|
149
|
+
const r = new RegExp("\\[" + key + "\\]\\s*\\[(.+?)\\]");
|
|
150
|
+
const match = tmdFileContent.match(r);
|
|
151
|
+
return match && match[1] ? match[1] : "";
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
*
|
|
156
|
+
* @param {string} speed in m/s
|
|
157
|
+
* @returns {number} in kts
|
|
158
|
+
*/
|
|
159
|
+
const convertSpeed = (speed) => {
|
|
160
|
+
return Math.round(Number(speed) * 1.94384449);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
*
|
|
165
|
+
* @param {string} altitude in m
|
|
166
|
+
* @returns {number} in ft
|
|
167
|
+
*/
|
|
168
|
+
const convertAltitude = (altitude) => {
|
|
169
|
+
return Math.round(Number(altitude) * 3.28084);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
*
|
|
174
|
+
* @param {string} range in m
|
|
175
|
+
* @returns {number} in kts
|
|
176
|
+
*/
|
|
177
|
+
const convertDistance = (range) => {
|
|
178
|
+
return Math.round(Number(range) / 1852);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// -----------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
const inputDirectory = process.argv[2] ?? ".";
|
|
184
|
+
const aeroflyAircraft = getAeroflyAircraft(inputDirectory);
|
|
185
|
+
|
|
186
|
+
process.stderr
|
|
187
|
+
.write(`Found \x1b[92m${aeroflyAircraft.length}\x1b[0m Aerofly FS Aircraft
|
|
188
|
+
`);
|
|
189
|
+
|
|
190
|
+
// Ensure the output directory exists
|
|
191
|
+
const outputDirectory = path.join("data");
|
|
192
|
+
await fs.promises.mkdir(outputDirectory, { recursive: true });
|
|
193
|
+
|
|
194
|
+
// Write the full output (with liveries) to aircraft-liveries.json
|
|
195
|
+
const outputFilePathWithLiveries = path.join(
|
|
196
|
+
outputDirectory,
|
|
197
|
+
"aircraft-liveries.json"
|
|
198
|
+
);
|
|
199
|
+
await fs.promises.writeFile(
|
|
200
|
+
outputFilePathWithLiveries,
|
|
201
|
+
JSON.stringify(aeroflyAircraft, null, 2),
|
|
202
|
+
"utf-8"
|
|
203
|
+
);
|
|
204
|
+
process.stderr.write(
|
|
205
|
+
`Full aircraft data (with liveries) written to \x1b[92m${outputFilePathWithLiveries}\x1b[0m\n`
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Write the abbreviated output (without liveries) to aircraft.json
|
|
209
|
+
const outputFilePathWithoutLiveries = path.join(
|
|
210
|
+
outputDirectory,
|
|
211
|
+
"aircraft.json"
|
|
212
|
+
);
|
|
213
|
+
await fs.promises.writeFile(
|
|
214
|
+
outputFilePathWithoutLiveries,
|
|
215
|
+
JSON.stringify(
|
|
216
|
+
aeroflyAircraft,
|
|
217
|
+
(key, value) => (key === "liveries" ? undefined : value),
|
|
218
|
+
2
|
|
219
|
+
),
|
|
220
|
+
"utf-8"
|
|
221
|
+
);
|
|
222
|
+
process.stderr.write(
|
|
223
|
+
`Abbreviated aircraft data (without liveries) written to \x1b[92m${outputFilePathWithoutLiveries}\x1b[0m\n`
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Write summary to aircraft.md
|
|
227
|
+
const summaryFilePath = path.join(outputDirectory, "aircraft.md");
|
|
228
|
+
let summaryContent = `\
|
|
229
|
+
# Aerofly FS Aircraft Summary
|
|
230
|
+
|
|
231
|
+
| Aircraft Name | ICAO Code | Aerofly FS Code | Approach Speed (kts) | Cruise Altitude (ft) | Cruise Speed (kts) | Maximum Range (nm) |
|
|
232
|
+
| ------------------------------------ | --------- | --------------- | -------------------: | -------------------: | -----------------: | -----------------: |
|
|
233
|
+
`;
|
|
234
|
+
summaryContent += aeroflyAircraft
|
|
235
|
+
.map((aircraft) => {
|
|
236
|
+
return `| ${aircraft.nameFull} | \`${aircraft.icaoCode}\` | \`${aircraft.aeroflyCode}\` | ${aircraft.approachAirspeedKts} | ${aircraft.cruiseAltitudeFt} | ${aircraft.cruiseSpeedKts} | ${aircraft.maximumRangeNm} |`;
|
|
237
|
+
})
|
|
238
|
+
.join("\n");
|
|
239
|
+
|
|
240
|
+
await fs.promises.writeFile(summaryFilePath, summaryContent, "utf-8");
|
|
241
|
+
process.stderr.write(`Summary written to \x1b[92m${summaryFilePath}\x1b[0m\n`);
|
|
242
|
+
|
|
243
|
+
// Write HTML <select> options to aircraft-select.html
|
|
244
|
+
const selectFilePath = path.join(outputDirectory, "aircraft-select.html");
|
|
245
|
+
let selectContent = `\
|
|
246
|
+
<select id="aircraft-select">
|
|
247
|
+
`;
|
|
248
|
+
|
|
249
|
+
// Sort aeroflyAircraft by nameFull
|
|
250
|
+
const sortedAircraft = aeroflyAircraft.sort((a, b) =>
|
|
251
|
+
a.nameFull.localeCompare(b.nameFull)
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
selectContent += sortedAircraft
|
|
255
|
+
.map((aircraft) => {
|
|
256
|
+
return ` <option value="${aircraft.aeroflyCode}">${aircraft.nameFull}</option>`;
|
|
257
|
+
})
|
|
258
|
+
.join("\n");
|
|
259
|
+
selectContent += `
|
|
260
|
+
</select>
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
await fs.promises.writeFile(selectFilePath, selectContent, "utf-8");
|
|
264
|
+
process.stderr.write(
|
|
265
|
+
`HTML <select> options written to \x1b[92m${selectFilePath}\x1b[0m\n`
|
|
266
|
+
);
|
package/get-airports.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
//@ts-check
|
|
4
|
+
|
|
5
|
+
import GeoJSON from "@fboes/geojson";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { parse } from "csv-parse/sync";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {string} type
|
|
13
|
+
* @param {boolean} isMilitary
|
|
14
|
+
* @param {number|undefined} lenght in Bytes
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
const geoJsonType = (type, isMilitary, lenght) => {
|
|
18
|
+
if (type === "heliport") {
|
|
19
|
+
return type;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (lenght !== undefined) {
|
|
23
|
+
let size = "closed";
|
|
24
|
+
if (lenght > 1200) {
|
|
25
|
+
size = "large";
|
|
26
|
+
} else if (lenght > 1050) {
|
|
27
|
+
size = "medium";
|
|
28
|
+
} else if (lenght > 920) {
|
|
29
|
+
size = "small";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (size === "closed") {
|
|
33
|
+
return size;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return size + "_" + (isMilitary ? "airbase" : "airport");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return type;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
*
|
|
44
|
+
* @param {string} directory
|
|
45
|
+
* @param {RegExp?} icaoFilter
|
|
46
|
+
* @returns {Map<string,number>}
|
|
47
|
+
*/
|
|
48
|
+
const getAeroflyAirports = (directory, icaoFilter) => {
|
|
49
|
+
const aeroflyAirports = new Map();
|
|
50
|
+
let maxLength = 0;
|
|
51
|
+
let minLength = 10_000;
|
|
52
|
+
|
|
53
|
+
const files = fs
|
|
54
|
+
.readdirSync(directory)
|
|
55
|
+
.filter((fn) => fn.endsWith(".wad"))
|
|
56
|
+
.sort();
|
|
57
|
+
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
const icaoCode = file.replace(/\.wad$/, "").toUpperCase();
|
|
60
|
+
if (!icaoFilter || icaoCode.match(icaoFilter)) {
|
|
61
|
+
const stats = fs.statSync(path.join(directory, file));
|
|
62
|
+
maxLength = Math.max(maxLength, stats.size);
|
|
63
|
+
minLength = Math.min(minLength, stats.size);
|
|
64
|
+
aeroflyAirports.set(icaoCode, stats.size);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return aeroflyAirports;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// -----------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
const inputDirectory = process.argv[2] ?? ".";
|
|
74
|
+
const icaoFilterArg = process.argv[3]?.replace(/[^A-Z]/, "").toUpperCase();
|
|
75
|
+
const icaoFilter = icaoFilterArg
|
|
76
|
+
? new RegExp("^[" + icaoFilterArg + "]")
|
|
77
|
+
: null;
|
|
78
|
+
|
|
79
|
+
const aeroflyGeoJson = new GeoJSON.FeatureCollection();
|
|
80
|
+
const aeroflyAirports = getAeroflyAirports(inputDirectory, icaoFilter);
|
|
81
|
+
const aeroflyAirportsLength = aeroflyAirports.size;
|
|
82
|
+
process.stderr
|
|
83
|
+
.write(`Found \x1b[92m${aeroflyAirports.size}\x1b[0m Aerofly FS Airports
|
|
84
|
+
`);
|
|
85
|
+
|
|
86
|
+
const airportsSource = fs.readFileSync(`tmp/airports.csv`);
|
|
87
|
+
/** @type {string[][]} with a single CSV line from airports.csv */
|
|
88
|
+
const airportsRecords = parse(airportsSource, { bom: true });
|
|
89
|
+
|
|
90
|
+
let airportsRecordsProcessed = 0;
|
|
91
|
+
|
|
92
|
+
// Collect all ICAO codes
|
|
93
|
+
const icaoCodes = [];
|
|
94
|
+
|
|
95
|
+
for (const airportsRecord of airportsRecords) {
|
|
96
|
+
// 'id', 'ident',
|
|
97
|
+
// 'type', 'name',
|
|
98
|
+
// 'latitude_deg', 'longitude_deg',
|
|
99
|
+
// 'elevation_ft', 'continent',
|
|
100
|
+
|
|
101
|
+
// 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/
|
|
102
|
+
|
|
103
|
+
const icaoCode = airportsRecord[1];
|
|
104
|
+
const icaoCodeAlternate = airportsRecord[12];
|
|
105
|
+
|
|
106
|
+
// EL = Europe
|
|
107
|
+
// K = US
|
|
108
|
+
if (
|
|
109
|
+
icaoFilter &&
|
|
110
|
+
!icaoCode.match(icaoFilter) &&
|
|
111
|
+
!icaoCodeAlternate.match(icaoFilter)
|
|
112
|
+
) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
airportsRecordsProcessed++;
|
|
117
|
+
|
|
118
|
+
const length =
|
|
119
|
+
aeroflyAirports.get(icaoCode) ?? aeroflyAirports.get(icaoCodeAlternate);
|
|
120
|
+
|
|
121
|
+
if (length !== undefined) {
|
|
122
|
+
// Add the ICAO code to the list
|
|
123
|
+
icaoCodes.push(icaoCode);
|
|
124
|
+
|
|
125
|
+
// Remove airport from list of Aerofly FS4 Airports
|
|
126
|
+
aeroflyAirports.delete(icaoCode) ||
|
|
127
|
+
aeroflyAirports.delete(icaoCodeAlternate);
|
|
128
|
+
|
|
129
|
+
const isMilitary =
|
|
130
|
+
airportsRecord[3].match(
|
|
131
|
+
/\b(base|rnas|raf|naval|air\s?force|coast\s?guard|army|afs|mod)\b/i
|
|
132
|
+
) !== null;
|
|
133
|
+
let type = airportsRecord[2];
|
|
134
|
+
if (isMilitary) {
|
|
135
|
+
type = type.replace(/port/, "base");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const feature = new GeoJSON.Feature(
|
|
139
|
+
new GeoJSON.Point(
|
|
140
|
+
Number(airportsRecord[5]),
|
|
141
|
+
Number(airportsRecord[4]),
|
|
142
|
+
Number(airportsRecord[6]) * 0.3048
|
|
143
|
+
),
|
|
144
|
+
{
|
|
145
|
+
title: icaoCode,
|
|
146
|
+
type: geoJsonType(type, isMilitary, length),
|
|
147
|
+
description: airportsRecord[3],
|
|
148
|
+
elevation: Number(airportsRecord[6]),
|
|
149
|
+
municipality: airportsRecord[10],
|
|
150
|
+
fileSize: Math.ceil(length),
|
|
151
|
+
"marker-symbol": airportsRecord[2].match(/heliport/)
|
|
152
|
+
? "heliport"
|
|
153
|
+
: airportsRecord[2].match(/small/)
|
|
154
|
+
? "airfield"
|
|
155
|
+
: "airport",
|
|
156
|
+
"marker-color": airportsRecord[2].match(/large/)
|
|
157
|
+
? "#5e6eba"
|
|
158
|
+
: airportsRecord[2].match(/small/)
|
|
159
|
+
? "#777777"
|
|
160
|
+
: "#555555",
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
if (isMilitary) {
|
|
164
|
+
feature.setProperty("isMilitary", true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
aeroflyGeoJson.addFeature(feature);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (airportsRecordsProcessed % 5000 === 0) {
|
|
171
|
+
const index = aeroflyAirportsLength - aeroflyAirports.size;
|
|
172
|
+
process.stderr
|
|
173
|
+
.write(` Processed \x1b[92m${String(airportsRecordsProcessed).padStart(5)}\x1b[0m airport records, found \x1b[92m${String(index).padStart(5)}\x1b[0m Aerofly FS Airports
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Ensure the output directory exists
|
|
179
|
+
const outputDirectory = path.join("data");
|
|
180
|
+
if (!fs.existsSync(outputDirectory)) {
|
|
181
|
+
fs.mkdirSync(outputDirectory, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Write the GeoJSON data to a file
|
|
185
|
+
const outputFilePath = path.join(outputDirectory, "airports.geojson");
|
|
186
|
+
fs.writeFileSync(outputFilePath, JSON.stringify(aeroflyGeoJson, null, 2), "utf-8");
|
|
187
|
+
|
|
188
|
+
// Write the ICAO codes to airport-list.json
|
|
189
|
+
const icaoListFilePath = path.join(outputDirectory, "airport-list.json");
|
|
190
|
+
fs.writeFileSync(icaoListFilePath, JSON.stringify(icaoCodes, null, 2), "utf-8");
|
|
191
|
+
|
|
192
|
+
// Log a message to STDERR to confirm the files were written
|
|
193
|
+
process.stderr.write(`GeoJSON data written to \x1b[92m${outputFilePath}\x1b[0m\n`);
|
|
194
|
+
process.stderr.write(`ICAO code list written to \x1b[92m${icaoListFilePath}\x1b[0m\n`);
|
|
195
|
+
|
|
196
|
+
if (aeroflyAirports.size > 0) {
|
|
197
|
+
process.stderr.write(
|
|
198
|
+
`Missing airport matches for \x1b[92m${
|
|
199
|
+
aeroflyAirports.size
|
|
200
|
+
}\x1b[0m Aerofly FS4 Airports, \x1b[92m${(
|
|
201
|
+
(aeroflyAirports.size / aeroflyAirportsLength) *
|
|
202
|
+
100
|
|
203
|
+
).toFixed(1)}%\x1b[0m
|
|
204
|
+
Missing matches for Aerofly FS4 Airport codes:
|
|
205
|
+
\x1b[90m> ${[...aeroflyAirports]
|
|
206
|
+
.map((a) => {
|
|
207
|
+
return a[0];
|
|
208
|
+
})
|
|
209
|
+
.join(", ")}\x1b[0m
|
|
210
|
+
`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="-9.4369e-16" y="3.9209e-8" width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#777" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.8521 0.66145h-0.39688c-0.13229 0-0.13229-0.13229 0-0.13229h1.0583c0.13229 0 0.13229 0.13229 0 0.13229h-0.39686s0.15875 0.11465 0.15875 0.37923v0.2646h1.1642v0.38806l-1.1642 0.38806-0.097014 0.97014 0.48507 0.25577v0.13229h-1.3582v-0.13229l0.48507-0.25577-0.097014-0.97014-1.1642-0.38806v-0.38806h1.1642v-0.26458c0-0.2646 0.15875-0.37925 0.15875-0.37925z" fill="#f9f9f9" stroke-width=".19403"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#bebebe" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="-9.4369e-16" y="3.9209e-8" width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#555" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.3053 1.0142c-0.19403 0-0.19403 0.19403 0 0.19403h0.77611v0.19403c-0.053746 0-0.097014 0.043268-0.097014 0.097014v0.097014h-0.69237c-0.044141-0.17073-0.19779-0.29083-0.37478-0.29104-0.21432 0-0.38806 0.17373-0.38806 0.38806s0.17373 0.38806 0.38806 0.38806c0.072217-1.94e-4 0.14296-0.020528 0.20425-0.058732l0.47484 0.64082c0.19546 0.28144 0.54681 0.29104 0.6791 0.29104h0.97014s0.19403 0 0.19403-0.19403v-0.19288c0-0.14213-0.024448-0.21962-0.097014-0.29219l-0.58208-0.58208s-0.11479-0.097014-0.24708-0.097014h-0.14098v-0.097014c0-0.053746-0.043268-0.097014-0.097014-0.097014v-0.19403h0.77611c0.19403 0 0.19403-0.19403 0-0.19403zm-0.38806 0.48507c0.10716 0 0.19403 0.086866 0.19403 0.19403 0 0.10716-0.086866 0.19403-0.19403 0.19403-0.10716 0-0.19403-0.086866-0.19403-0.19403 0-0.10716 0.086866-0.19403 0.19403-0.19403zm1.5522 0.29104c0.097014 0 0.1532 0.06269 0.19403 0.097014l0.48507 0.48507h-0.6791s-0.19403 0-0.19403-0.19403v-0.19403s0-0.19403 0.19403-0.19403z" fill="#f9f9f9" stroke-width=".19403"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#8dd35f" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.9844 0.49911-0.13234 0.42694 0.0093833 0.39188-0.12296 0.019041v0.32372l-0.83616 1.1235-0.073779 0.22851 0.98372 0.038084 0.049187 0.41893h0.24593l0.049186-0.41893 0.98372-0.038084-0.073779-0.22851-0.83616-1.1235v-0.32372l-0.12297-0.019041 0.0092684-0.39188-0.13223-0.42694z" fill="#fff" stroke-linecap="round" stroke-width=".008179"/></svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#5f5fd3" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
|
|
4
|
+
p
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#555" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.9844 0.49911-0.13234 0.42694 0.0093833 0.39188-0.12296 0.019041v0.32372l-0.83616 1.1235-0.073779 0.22851 0.98372 0.038084 0.049187 0.41893h0.24593l0.049186-0.41893 0.98372-0.038084-0.073779-0.22851-0.83616-1.1235v-0.32372l-0.12297-0.019041 0.0092684-0.39188-0.13223-0.42694z" fill="#fff" stroke-linecap="round" stroke-width=".008179"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#555" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#777" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.9844 0.49911-0.13234 0.42694 0.0093833 0.39188-0.12296 0.019041v0.32372l-0.83616 1.1235-0.073779 0.22851 0.98372 0.038084 0.049187 0.41893h0.24593l0.049186-0.41893 0.98372-0.038084-0.073779-0.22851-0.83616-1.1235v-0.32372l-0.12297-0.019041 0.0092684-0.39188-0.13223-0.42694z" fill="#fff" stroke-linecap="round" stroke-width=".008179"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#777" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.64925 0 0 .64925 94.84 -35.012)" fill-rule="evenodd"><path d="m-143.02 54.168-0.17579 0.30664-2.9121 5.043h6.1777zm0 0.81445 2e-3 2e-3 -0.17578 0.30664-2.207 3.8184h4.7637l2e-3 2e-3h-4.7676l2e-3 -2e-3zm2e-3 0.81641 1.6758 2.9043h-3.3535z" color="#000000" fill="#fff" style="-inkscape-stroke:none;paint-order:stroke markers fill"/><path d="m-143.02 54.576-2.7344 4.7383h5.4707zm2e-3 0.81641 2.0273 3.5137h-4.0566z" color="#000000" fill="#1a1a1a" style="-inkscape-stroke:none;paint-order:stroke markers fill"/></g></svg>
|