diff --git a/client/app.js b/client/app.js index a10aaf2f..85d5c31d 100644 --- a/client/app.js +++ b/client/app.js @@ -5,10 +5,11 @@ var logger = require('morgan'); var fs = require('fs'); var basicAuth = require('express-basic-auth') -var atcRouter = require('./routes/api/atc'); -var indexRouter = require('./routes/index'); -var uikitRouter = require('./routes/uikit'); -var usersRouter = require('./routes/users'); +var atcRouter = require('./routes/api/atc'); +var airbasesRouter = require('./routes/api/airbases'); +var indexRouter = require('./routes/index'); +var uikitRouter = require('./routes/uikit'); +var usersRouter = require('./routes/users'); var resourcesRouter = require('./routes/resources'); var app = express(); @@ -21,6 +22,7 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/api/atc', atcRouter); +app.use('/api/airbases', airbasesRouter); app.use('/users', usersRouter); app.use('/uikit', uikitRouter); app.use('/resources', resourcesRouter); diff --git a/client/package-lock.json b/client/package-lock.json index 05df666f..e6e672a7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@types/geojson": "^7946.0.10", "@types/leaflet": "^1.9.0", + "@types/svg-injector": "^0.0.29", "cookie-parser": "~1.4.4", "debug": "~2.6.9", "ejs": "^3.1.8", @@ -1865,6 +1866,11 @@ "integrity": "sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw==", "dev": true }, + "node_modules/@types/svg-injector": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/svg-injector/-/svg-injector-0.0.29.tgz", + "integrity": "sha512-tNvoN0Xk2si6IfxQI/PqInipOuCyXkDZhCh9Vc4aFv/l1DhIBfTFbXRXYUHBAGXaUNMOCHEtg9475O9J+4NXOg==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -6989,6 +6995,11 @@ "integrity": "sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw==", "dev": true }, + "@types/svg-injector": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/svg-injector/-/svg-injector-0.0.29.tgz", + "integrity": "sha512-tNvoN0Xk2si6IfxQI/PqInipOuCyXkDZhCh9Vc4aFv/l1DhIBfTFbXRXYUHBAGXaUNMOCHEtg9475O9J+4NXOg==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", diff --git a/client/package.json b/client/package.json index 8fa54c31..f0db50cb 100644 --- a/client/package.json +++ b/client/package.json @@ -12,6 +12,7 @@ "dependencies": { "@types/geojson": "^7946.0.10", "@types/leaflet": "^1.9.0", + "@types/svg-injector": "^0.0.29", "cookie-parser": "~1.4.4", "debug": "~2.6.9", "ejs": "^3.1.8", diff --git a/client/public/stylesheets/atc/atc.css b/client/public/stylesheets/atc/atc.css index c19e19fd..e9fa7441 100644 --- a/client/public/stylesheets/atc/atc.css +++ b/client/public/stylesheets/atc/atc.css @@ -1,4 +1,8 @@ - +.ol-strip-board .ol-dialog-header { + align-items: center; + display:flex; + justify-content: space-between; +} .ol-strip-board-strips { display:flex; @@ -178,6 +182,7 @@ .ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] { display:flex; + row-gap: 4px; } .ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] a { @@ -192,4 +197,9 @@ [data-board-type="tower"] { right:10px; top:10px; +} + +[data-board-type="tower"] .ol-auto-suggest { + top:30px; + translate:0; } \ No newline at end of file diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 1af78c87..b0fbae9e 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -589,7 +589,7 @@ nav.ol-panel> :last-child { } .ol-sortable .handle { - background-image: url("/images/icons/grip-lines-solid.svg"); + background-image: url("/resources/theme/images/icons/grip-lines-solid.svg"); cursor: ns-resize; filter: invert(); height: 12px; diff --git a/client/public/themes/olympus/images/icons/crosshairs-solid.svg b/client/public/themes/olympus/images/icons/crosshairs-solid.svg new file mode 100644 index 00000000..c2a60f84 --- /dev/null +++ b/client/public/themes/olympus/images/icons/crosshairs-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/routes/api/airbases.js b/client/routes/api/airbases.js new file mode 100644 index 00000000..a080c794 --- /dev/null +++ b/client/routes/api/airbases.js @@ -0,0 +1,93 @@ +var express = require('express'); +var app = express(); + +var fs = require('fs'); + +const bodyParser = require('body-parser'); +app.use(bodyParser.urlencoded({ extended: false})); +app.use(bodyParser.json()); + +const allowedTheatres = [ + "caucasus", + "marianas", + "nevada", + "persiangulf", + "syria" +]; + + +function getAirbasesData( theatreName ) { + + if ( !isValidTheatre( theatreName ) ) { + return false; + } + + return JSON.parse( fs.readFileSync( `src/airfields/${theatreName}.json` ) ).airfields + +} + + +function isValidTheatre( theatre ) { + + return ( allowedTheatres.indexOf( theatre ) > -1 ) + +} + + +function sendInvalidTheatre( res ) { + + res.status( 400 ).send( "Missing/invalid theatre name; must be one of:\n\t" + allowedTheatres.join( "\n\t" ) ); + +} + + +/**************************************************************************************************************/ +// Endpoints +/**************************************************************************************************************/ + + + +app.get( "/", ( req, res ) => { + + sendInvalidTheatre( res ); + +}); + + +app.get( "/:theatreName/:airbaseName", ( req, res ) => { + + const airbases = getAirbasesData( req.params.theatreName ); + + if ( !airbases ) { + sendInvalidTheatre( res ); + return; + } + + const airbaseName = req.params.airbaseName; + + if ( !airbases.hasOwnProperty( airbaseName ) ) { + res.status( 404 ).send( `Unknown airbase name "${airbaseName}". Available options are:\n\t` + Object.keys( airbases ).join( "\n\t" ) ); + } else { + res.status( 200 ).json( airbases[ airbaseName ] ); + } + + +}); + + +app.get( "/:theatreName", ( req, res ) => { + + const airbases = getAirbasesData( req.params.theatreName ); + + if ( !airbases ) { + sendInvalidTheatre( res ); + return; + } + + res.status( 200 ).json( airbases ); + +}); + + + +module.exports = app; \ No newline at end of file diff --git a/client/src/airfields/caucasus.json b/client/src/airfields/caucasus.json new file mode 100644 index 00000000..7c060874 --- /dev/null +++ b/client/src/airfields/caucasus.json @@ -0,0 +1,361 @@ +{ + "airfields": { + "Anapa-Vityazevo": { + "ICAO": "URKA", + "Elevation": "141", + "TACAN": "", + "Runways:": { + "04": { + "Mag Hdg": "034", + "Length": "9000", + "ILS": "" + }, + "22": { + "Mag Hdg": "214", + "Length": "9000", + "ILS": "" + } + } + }, + "Batumi": { + "ICAO": "UGSB", + "Elevation": "33", + "TACAN": "16X", + "Runways": { + "13": { + "Mag Hdg": "119", + "Length": "7500", + "ILS": "" + }, + "31": { + "Mag Hdg": "299", + "Length": "7500", + "ILS": "" + } + } + }, + "Beslan": { + "ICAO": "URMO", + "Elevation": "1722", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "086", + "Length": "9600", + "ILS": "110.50" + }, + "28": { + "Mag Hdg": "266", + "Length": "9600", + "ILS": "" + } + } + }, + "Gelendzhik": { + "ICAO": "URKG", + "Elevation": "72", + "TACAN": "", + "Runways": { + "01": { + "Mag Hdg": "032", + "Length": "5400", + "ILS": "" + }, + "19": { + "Mag Hdg": "212", + "Length": "5400", + "ILS": "" + } + } + }, + "Gudauta": { + "ICAO": "UG23", + "Elevation": "69", + "TACAN": "", + "Runways": { + "15": { + "Mag Hdg": "144", + "Length": "7700", + "ILS": "" + }, + "33": { + "Mag Hdg": "324", + "Length": "7700", + "ILS": "" + } + } + }, + "Kobuleti": { + "ICAO": "UG5X", + "Elevation": "69", + "TACAN": "67X", + "Runways": { + "07": { + "Mag Hdg": "063", + "Length": "7400", + "ILS": "111.50" + }, + "25": { + "Mag Hdg": "243", + "Length": "7400", + "ILS": "" + } + } + }, + "Krasnodar-Center": { + "ICAO": "URKL", + "Elevation": "98", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "079", + "Length": "7700", + "ILS": "" + }, + "27": { + "Mag Hdg": "259", + "Length": "7700", + "ILS": "" + } + } + }, + "Krasnodar-Pashkovsky": { + "ICAO": "URKK", + "Elevation": "112", + "TACAN": "", + "Runways": { + "05": { + "Mag Hdg": "039", + "Length": "9600", + "ILS": "" + }, + "23": { + "Mag Hdg": "219", + "Length": "9600", + "ILS": "" + } + } + }, + "Krymsk": { + "ICAO": "URKW", + "Elevation": "66", + "TACAN": "", + "Runways": { + "04": { + "Mag Hdg": "032", + "Length": "8000", + "ILS": "" + }, + "22": { + "Mag Hdg": "212", + "Length": "8000", + "ILS": "" + } + } + }, + "Kutaisi": { + "ICAO": "UGKO", + "Elevation": "148", + "TACAN": "44X", + "Runways": { + "07": { + "Mag Hdg": "067'", + "Length": "7700", + "ILS": "109.75" + }, + "25": { + "Mag Hdg": "247", + "Length": "7700", + "ILS": "" + } + } + }, + "Maykop-Khanskaya": { + "ICAO": "URKH", + "Elevation": "591", + "TACAN": "", + "Runways": { + "04": { + "Mag Hdg": "031", + "Length": "10100", + "ILS": "" + }, + "22": { + "Mag Hdg": "211", + "Length": "10100", + "ILS": "" + } + } + }, + "Mineralnye Vody": { + "ICAO": "URMM", + "Elevation": "1050", + "TACAN": "", + "Runways": { + "12": { + "Mag Hdg": "108", + "Length": "12700", + "ILS": "111.70" + }, + "30": { + "Mag Hdg": "288", + "Length": "12700", + "ILS": "109.30" + } + } + }, + "Mozdok": { + "ICAO": "XRMF", + "Elevation": "507", + "TACAN": "", + "Runways": { + "08": { + "Mag Hdg": "075", + "Length": "9400", + "ILS": "" + }, + "26": { + "Mag Hdg": "255", + "Length": "9400", + "ILS": "" + } + } + }, + "Nalchick": { + "ICAO": "URMN", + "Elevation": "1411", + "TACAN": "", + "Runways": { + "06": { + "Mag Hdg": "048'", + "Length": "7000", + "ILS": "" + }, + "24": { + "Mag Hdg": "228", + "Length": "7000", + "ILS": "110.50" + } + } + }, + "Novorossiysk": { + "ICAO": "URKN", + "Elevation": "131", + "TACAN": "", + "Runways": { + "04": { + "Mag Hdg": "034", + "Length": "5400", + "ILS": "" + }, + "22": { + "Mag Hdg": "214", + "Length": "5400", + "ILS": "" + } + } + }, + "Senaki-Kolkhi": { + "ICAO": "UGKS", + "Elevation": "43", + "TACAN": "31X", + "Runways": { + "09": { + "Mag Hdg": "088'", + "Length": "7400", + "ILS": "108.90" + }, + "27": { + "Mag Hdg": "268", + "Length": "7400", + "ILS": "" + } + } + }, + "Sochi-Adler": { + "ICAO": "URSS", + "Elevation": "98", + "TACAN": "", + "Runways": { + "06": { + "Mag Hdg": "055", + "Length": "9700", + "ILS": "111.10" + }, + "27": { + "Mag Hdg": "235", + "Length": "9700", + "ILS": "" + } + } + }, + "Tbilisi-Lochini": { + "ICAO": "UGTB", + "Elevation": "1574", + "TACAN": "25X", + "Runways": { + "13": { + "Mag Hdg": "121", + "Length": "9300", + "ILS": "110.30" + }, + "31": { + "Mag Hdg": "301", + "Length": "9300", + "ILS": "108.90" + } + } + }, + "Soganlug": { + "ICAO": "UG24", + "Elevation": "1500", + "TACAN": "25X", + "Runways": { + "14": { + "Mag Hdg": "125", + "Length": "6500", + "ILS": "" + }, + "32": { + "Mag Hdg": "305", + "Length": "6500", + "ILS": "" + } + } + }, + "Sukhumi-Babushara": { + "ICAO": "UGSS", + "Elevation": "43", + "TACAN": "", + "Runways": { + "12": { + "Mag Hdg": "109", + "Length": "11400", + "ILS": "" + }, + "30": { + "Mag Hdg": "289", + "Length": "11400", + "ILS": "" + } + } + }, + "Vaziani": { + "ICAO": "UG27", + "Elevation": "1524", + "TACAN": "22X", + "Runways": { + "13": { + "Mag Hdg": "129", + "Length": "7700", + "ILS": "108.75" + }, + "31": { + "Mag Hdg": "309", + "Length": "7700", + "ILS": "108.75" + } + } + } + } +} diff --git a/client/src/airfields/marianas.json b/client/src/airfields/marianas.json new file mode 100644 index 00000000..3451c203 --- /dev/null +++ b/client/src/airfields/marianas.json @@ -0,0 +1,170 @@ +{ + "airfields": { + "Andersen_AFB": { + "ICAO": "PGUA", + "Elevation": "606", + "TACAN": "54X", + "Runways:": { + "06L": { + "Mag Hdg": "066", + "Length": "10300", + "ILS": "109.30" + }, + "24R": { + "Mag Hdg": "246", + "Length": "10300", + "ILS": "109.35" + }, + "06R": { + "Mag Hdg": "066", + "Length": "10900", + "ILS": "110.10" + }, + "24l": { + "Mag Hdg": "246", + "Length": "10900", + "ILS": "110.15" + } + } + }, + "Antonio_B._Won_Pat_Int_Airport": { + "ICAO": "PGUM", + "Elevation": "255", + "TACAN": "105X", + "Runways:": { + "06L": { + "Mag Hdg": "066", + "Length": "10600", + "ILS": "110.30" + }, + "24R": { + "Mag Hdg": "245", + "Length": "10600", + "ILS": "" + }, + "06R": { + "Mag Hdg": "066", + "Length": "8600", + "ILS": "110.90" + }, + "24L": { + "Mag Hdg": "245", + "Length": "8600", + "ILS": "" + } + } + }, + "North_West_Field": { + "ICAO": "", + "Elevation": "522", + "TACAN": "", + "Runways:": { + "06": { + "Mag Hdg": "063", + "Length": "4500", + "ILS": "" + }, + "24": { + "Mag Hdg": "243", + "Length": "4500", + "ILS": "" + } + } + }, + "Olf_Orote_Field": { + "ICAO": "", + "Elevation": "94", + "TACAN": "", + "Runways:": { + "07": { + "Mag Hdg": "067", + "Length": "3500", + "ILS": "" + }, + "25": { + "Mag Hdg": "247", + "Length": "3500", + "ILS": "" + } + } + }, + "Pagan_Airstrip": { + "ICAO": "", + "Elevation": "49", + "TACAN": "", + "Runways:": { + "11": { + "Mag Hdg": "112", + "Length": "1800", + "ILS": "" + }, + "29": { + "Mag Hdg": "292", + "Length": "1800", + "ILS": "" + } + } + }, + "Rota_Int": { + "ICAO": "PGRO", + "Elevation": "569", + "TACAN": "", + "Runways:": { + "09": { + "Mag Hdg": "092", + "Length": "6600", + "ILS": "" + }, + "27": { + "Mag Hdg": "272", + "Length": "6600", + "ILS": "" + } + } + }, + "Saipan_Int": { + "ICAO": "PGSN", + "Elevation": "213", + "TACAN": "", + "Runways:": { + "06": { + "Mag Hdg": "068", + "Length": "6200", + "ILS": "" + }, + "24": { + "Mag Hdg": "248", + "Length": "6200", + "ILS": "" + }, + "07": { + "Mag Hdg": "068", + "Length": "10600", + "ILS": "109.90" + }, + "25": { + "Mag Hdg": "248", + "Length": "10600", + "ILS": "" + } + } + }, + "Tinian_Int": { + "ICAO": "PGWT", + "Elevation": "284", + "TACAN": "", + "Runways:": { + "08": { + "Mag Hdg": "079", + "Length": "8200", + "ILS": "" + }, + "26": { + "Mag Hdg": "259", + "Length": "8200", + "ILS": "" + } + } + } + } +} \ No newline at end of file diff --git a/client/src/airfields/nevada.json b/client/src/airfields/nevada.json new file mode 100644 index 00000000..e94d1d4d --- /dev/null +++ b/client/src/airfields/nevada.json @@ -0,0 +1,413 @@ +{ + "airfields": { + "BeattyAirport": { + "ICAO": "KBTY", + "Elevation": "3173", + "TACAN": "94X", + "Runways:": { + "16": { + "Mag Hdg": "168", + "Length": "5500", + "ILS": "" + }, + "34": { + "Mag Hdg": "348", + "Length": "5500", + "ILS": "" + } + } + }, + "BoulderCityAirport": { + "ICAO": "KBVU", + "Elevation": "2205", + "TACAN": "114X", + "Runways:": { + "09": { + "Mag Hdg": "087", + "Length": "4400", + "ILS": "" + }, + "27": { + "Mag Hdg": "267", + "Length": "4400", + "ILS": "" + }, + "15": { + "Mag Hdg": "153", + "Length": "3700", + "ILS": "" + }, + "33": { + "Mag Hdg": "333", + "Length": "3700", + "ILS": "" + } + } + }, + "Creech": { + "ICAO": "KINS", + "Elevation": "3126", + "TACAN": "87X", + "Runways:": { + "08": { + "Mag Hdg": "080", + "Length": "8700", + "ILS": "108.70" + }, + "26": { + "Mag Hdg": "260", + "Length": "8700", + "ILS": "" + }, + "13": { + "Mag Hdg": "134", + "Length": "4700", + "ILS": "" + }, + "31": { + "Mag Hdg": "314", + "Length": "4700", + "ILS": "" + } + } + }, + "EchoBayAirport": { + "ICAO": "0L9", + "Elevation": "1549", + "TACAN": "", + "Runways:": { + "06": { + "Mag Hdg": "066", + "Length": "3300", + "ILS": "" + }, + "24": { + "Mag Hdg": "246", + "Length": "3300", + "ILS": "" + } + } + }, + "groom": { + "ICAO": "KXTA", + "Elevation": "4495", + "TACAN": "18X", + "Runways:": { + "14L": { + "Mag Hdg": "145", + "Length": "11700", + "ILS": "" + }, + "32R": { + "Mag Hdg": "325", + "Length": "11700", + "ILS": "109.30" + }, + "14R (CLOSED)": { + "Mag Hdg": "145", + "Length": "17800", + "ILS": "" + }, + "32L (CLOSED)": { + "Mag Hdg": "325", + "Length": "17800", + "ILS": "" + } + } + }, + "HendersonExecutiveAirport": { + "ICAO": "KHND", + "Elevation": "2493", + "TACAN": "", + "Runways:": { + "17L": { + "Mag Hdg": "168", + "Length": "4600", + "ILS": "" + }, + "35R": { + "Mag Hdg": "348", + "Length": "4600", + "ILS": "" + }, + "17R": { + "Mag Hdg": "168", + "Length": "6100", + "ILS": "" + }, + "35L": { + "Mag Hdg": "348", + "Length": "6100", + "ILS": "" + } + } + }, + "JeanAirport": { + "ICAO": "", + "Elevation": "2825", + "TACAN": "", + "Runways:": { + "02L": { + "Mag Hdg": "020", + "Length": "4500", + "ILS": "" + }, + "20R": { + "Mag Hdg": "200", + "Length": "4500", + "ILS": "" + }, + "02R": { + "Mag Hdg": "020", + "Length": "3600", + "ILS": "" + }, + "20L": { + "Mag Hdg": "200", + "Length": "3600", + "ILS": "" + } + } + }, + "LasVegas": { + "ICAO": "KLAS", + "Elevation": "2178", + "TACAN": "116X", + "Runways:": { + "01L": { + "Mag Hdg": "013", + "Length": "8000", + "ILS": "" + }, + "19R": { + "Mag Hdg": "193", + "Length": "8000", + "ILS": "" + }, + "01R": { + "Mag Hdg": "013", + "Length": "8000", + "ILS": "" + }, + "19L": { + "Mag Hdg": "193", + "Length": "8000", + "ILS": "" + }, + "07L": { + "Mag Hdg": "078", + "Length": "10600", + "ILS": "" + }, + "25R": { + "Mag Hdg": "258", + "Length": "10600", + "ILS": "110.30" + }, + "07R": { + "Mag Hdg": "078", + "Length": "10100", + "ILS": "" + }, + "25L": { + "Mag Hdg": "258", + "Length": "10100", + "ILS": "" + } + } + }, + "LaughlinAirport": { + "ICAO": "KIFP", + "Elevation": "673", + "TACAN": "", + "Runways:": { + "16": { + "Mag Hdg": "164", + "Length": "7100", + "ILS": "" + }, + "34": { + "Mag Hdg": "344", + "Length": "7100", + "ILS": "" + } + } + }, + "LincolnCountyAirport": { + "ICAO": "", + "Elevation": "4816", + "TACAN": "", + "Runways:": { + "17": { + "Mag Hdg": "170", + "Length": "4500", + "ILS": "" + }, + "35": { + "Mag Hdg": "350", + "Length": "4500", + "ILS": "" + } + } + }, + "MesquiteAirport": { + "ICAO": "67L", + "Elevation": "1859", + "TACAN": "", + "Runways:": { + "01": { + "Mag Hdg": "017", + "Length": "5000", + "ILS": "" + }, + "19": { + "Mag Hdg": "197", + "Length": "5000", + "ILS": "" + } + } + }, + "MinahAirport_3Q0": { + "ICAO": "", + "Elevation": "4560", + "TACAN": "", + "Runways:": { + "13": { + "Mag Hdg": "140", + "Length": "4100", + "ILS": "" + }, + "31": { + "Mag Hdg": "320", + "Length": "4100", + "ILS": "" + } + } + }, + "nellis": { + "ICAO": "KLSV", + "Elevation": "1849", + "TACAN": "12X", + "Runways:": { + "03L": { + "Mag Hdg": "029", + "Length": "9800", + "ILS": "" + }, + "21R": { + "Mag Hdg": "209", + "Length": "9800", + "ILS": "" + }, + "03R": { + "Mag Hdg": "029", + "Length": "9800", + "ILS": "" + }, + "21L": { + "Mag Hdg": "209", + "Length": "9800", + "ILS": "109.10" + } + } + }, + "NorthLasVegasAirport": { + "ICAO": "KVGT", + "Elevation": "2228", + "TACAN": "", + "Runways:": { + "07": { + "Mag Hdg": "076", + "Length": "4900", + "ILS": "" + }, + "25": { + "Mag Hdg": "256", + "Length": "4900", + "ILS": "" + }, + "12L": { + "Mag Hdg": "122", + "Length": "3800", + "ILS": "110.70" + }, + "30R": { + "Mag Hdg": "302", + "Length": "3800", + "ILS": "109.10" + }, + "12R": { + "Mag Hdg": "122", + "Length": "4600", + "ILS": "" + }, + "30L": { + "Mag Hdg": "302", + "Length": "4600", + "ILS": "" + } + } + }, + "PahuteMesaAirstrip": { + "ICAO": "", + "Elevation": "5059", + "TACAN": "", + "Runways:": { + "18": { + "Mag Hdg": "182", + "Length": "5500", + "ILS": "" + }, + "36": { + "Mag Hdg": "002", + "Length": "5500", + "ILS": "" + } + } + }, + "TonopahAirport": { + "ICAO": "KTPH", + "Elevation": "5390", + "TACAN": "119X", + "Runways:": { + "11": { + "Mag Hdg": "113", + "Length": "5600", + "ILS": "" + }, + "29": { + "Mag Hdg": "293", + "Length": "5600", + "ILS": "" + }, + "15": { + "Mag Hdg": "153", + "Length": "6800", + "ILS": "" + }, + "33": { + "Mag Hdg": "333", + "Length": "6800", + "ILS": "" + } + } + }, + "TonopathAFB": { + "ICAO": "KTNX", + "Elevation": "5535", + "TACAN": "77X", + "Runways:": { + "14": { + "Mag Hdg": "145", + "Length": "11700", + "ILS": "108.30" + }, + "32": { + "Mag Hdg": "325", + "Length": "11700", + "ILS": "111.70" + } + } + } + } +} \ No newline at end of file diff --git a/client/src/airfields/persiangulf.json b/client/src/airfields/persiangulf.json new file mode 100644 index 00000000..81c3be69 --- /dev/null +++ b/client/src/airfields/persiangulf.json @@ -0,0 +1,567 @@ +{ + "airfields": { + "Abu_Dhabi_International_Airport": { + "ICAO": "OMAA", + "Elevation": "92", + "TACAN": "", + "Runways:": { + "13L": { + "Mag Hdg": "127", + "Length": "13100", + "ILS": "" + }, + "13R": { + "Mag Hdg": "127", + "Length": "13200", + "ILS": "" + }, + "31L": { + "Mag Hdg": "307", + "Length": "13100", + "ILS": "" + }, + "31R": { + "Mag Hdg": "307", + "Length": "13200", + "ILS": "" + } + } + }, + "Ai_Ain_International_Airport": { + "ICAO": "OMAL", + "Elevation": "814", + "TACAN": "", + "Runways:": { + "01": { + "Mag Hdg": "006", + "Length": "12800", + "ILS": "" + }, + "19": { + "Mag Hdg": "186", + "Length": "12800", + "ILS": "" + } + } + }, + "abu_musa_airport": { + "ICAO": "OIBA", + "Elevation": "16", + "TACAN": "", + "Runways:": { + "08": { + "Mag Hdg": "082", + "Length": "7800", + "ILS": "" + }, + "26": { + "Mag Hdg": "262", + "Length": "7800", + "ILS": "" + } + } + }, + "Dhafra_AFB": { + "ICAO": "OMAM", + "Elevation": "52", + "TACAN": "96X", + "Runways:": { + "13L": { + "Mag Hdg": "126", + "Length": "11700", + "ILS": "111.10" + }, + "31R": { + "Mag Hdg": "306", + "Length": "11700", + "ILS": "109.10" + }, + "13R": { + "Mag Hdg": "16", + "Length": "11700", + "ILS": "108.70" + }, + "31L": { + "Mag Hdg": "306", + "Length": "11700", + "ILS": "108.70" + } + } + }, + "Al_Maktoum_International_Airport": { + "ICAO": "OMDW", + "Elevation": "125", + "TACAN": "", + "Runways:": { + "12": { + "Mag Hdg": "120", + "Length": "14400", + "ILS": "111.75" + }, + "30": { + "Mag Hdg": "300", + "Length": "14400", + "ILS": "109.75" + } + } + }, + "Minhad_AFB": { + "ICAO": "OMDM", + "Elevation": "190", + "TACAN": "99X", + "Runways:": { + "09": { + "Mag Hdg": "088", + "Length": "12600", + "ILS": "110.70" + }, + "27": { + "Mag Hdg": "268", + "Length": "12600", + "ILS": "110.75" + } + } + }, + "Al_Bateen_Airport": { + "ICAO": "OMAD", + "Elevation": "12", + "TACAN": "", + "Runways:": { + "13": { + "Mag Hdg": "127", + "Length": "7000", + "ILS": "" + }, + "31": { + "Mag Hdg": "307", + "Length": "7000", + "ILS": "" + } + } + }, + "Bandar_Abbas_airfield": { + "ICAO": "OIKB", + "Elevation": "29", + "TACAN": "78X", + "Runways:": { + "03L": { + "Mag Hdg": "25", + "Length": "11000", + "ILS": "" + }, + "21R": { + "Mag Hdg": "205", + "Length": "10000", + "ILS": "" + }, + "03R": { + "Mag Hdg": "25", + "Length": "11700", + "ILS": "" + }, + "21L": { + "Mag Hdg": "205", + "Length": "11700", + "ILS": "109.90" + } + } + }, + "Bandar_Lengeh_Airport": { + "ICAO": "OIBL", + "Elevation": "82", + "TACAN": "", + "Runways:": { + "08": { + "Mag Hdg": "079", + "Length": "7900", + "ILS": "" + }, + "26": { + "Mag Hdg": "259", + "Length": "7900", + "ILS": "" + } + } + }, + "Bandar_e_Jask_airfield": { + "ICAO": "OIZJ", + "Elevation": "26", + "TACAN": "110X", + "Runways:": { + "06": { + "Mag Hdg": "059", + "Length": "7300", + "ILS": "" + }, + "24": { + "Mag Hdg": "239", + "Length": "7300", + "ILS": "" + } + } + }, + "Dubai_International_Airport": { + "ICAO": "OMDB", + "Elevation": "16", + "TACAN": "", + "Runways:": { + "12L": { + "Mag Hdg": "120", + "Length": "11400", + "ILS": "110.10" + }, + "30R": { + "Mag Hdg": "300", + "Length": "11400", + "ILS": "110.90" + }, + "12R": { + "Mag Hdg": "120", + "Length": "11400", + "ILS": "109.50" + }, + "30L": { + "Mag Hdg": "300", + "Length": "11400", + "ILS": "111.30" + } + } + }, + "Fujarirah_AFB": { + "ICAO": "OMFJ", + "Elevation": "121", + "TACAN": "", + "Runways:": { + "11": { + "Mag Hdg": "111", + "Length": "9700", + "ILS": "" + }, + "29": { + "Mag Hdg": "291", + "Length": "9700", + "ILS": "111.50" + } + } + }, + "Havadarya_AFB": { + "ICAO": "OIKP", + "Elevation": "52", + "TACAN": "47X", + "Runways:": { + "08": { + "Mag Hdg": "077", + "Length": "7200", + "ILS": "108.90" + }, + "26": { + "Mag Hdg": "257", + "Length": "7200", + "ILS": "" + } + } + }, + "Jiroft_airfield": { + "ICAO": "OIKJ", + "Elevation": "2664", + "TACAN": "", + "Runways:": { + "13": { + "Mag Hdg": "125", + "Length": "9600", + "ILS": "" + }, + "31": { + "Mag Hdg": "305", + "Length": "9600", + "ILS": "" + } + } + }, + "Kerman_AFB": { + "ICAO": "OIKK", + "Elevation": "5745", + "TACAN": "97X", + "Runways:": { + "16": { + "Mag Hdg": "155", + "Length": "12400", + "ILS": "" + }, + "34": { + "Mag Hdg": "335", + "Length": "12400", + "ILS": "" + } + } + }, + "Khasab_AFB": { + "ICAO": "OOKB", + "Elevation": "102", + "TACAN": "", + "Runways:": { + "01": { + "Mag Hdg": "012", + "Length": "8000", + "ILS": "" + }, + "19": { + "Mag Hdg": "192", + "Length": "8000", + "ILS": "110.30" + } + } + }, + "Kish_International_Airport": { + "ICAO": "OIBK", + "Elevation": "115", + "TACAN": "112X", + "Runways:": { + "10": { + "Mag Hdg": "094", + "Length": "11700", + "ILS": "" + }, + "28": { + "Mag Hdg": "274", + "Length": "11700", + "ILS": "" + }, + "09R": { + "Mag Hdg": "094", + "Length": "11700", + "ILS": "" + }, + "27L": { + "Mag Hdg": "274", + "Length": "11700", + "ILS": "" + } + } + }, + "Lar_airbase": { + "ICAO": "OISL", + "Elevation": "2635", + "TACAN": "", + "Runways:": { + "09": { + "Mag Hdg": "088", + "Length": "10100", + "ILS": "" + }, + "27": { + "Mag Hdg": "268", + "Length": "10100", + "ILS": "" + } + } + }, + "Lavan_Island_Airport": { + "ICAO": "OIBV", + "Elevation": "75", + "TACAN": "", + "Runways:": { + "11": { + "Mag Hdg": "110", + "Length": "8600", + "ILS": "" + }, + "29": { + "Mag Hdg": "290", + "Length": "8600", + "ILS": "" + } + } + }, + "Liwa_Airbase": { + "ICAO": "OMLW", + "Elevation": "400", + "TACAN": "121X", + "Runways:": { + "13": { + "Mag Hdg": "130", + "Length": "11600", + "ILS": "" + }, + "31": { + "Mag Hdg": "310", + "Length": "11600", + "ILS": "" + } + } + }, + "Qeshm_Airport": { + "ICAO": "OIKQ", + "Elevation": "26", + "TACAN": "", + "Runways:": { + "05": { + "Mag Hdg": "047", + "Length": "13600", + "ILS": "" + }, + "23": { + "Mag Hdg": "227", + "Length": "13600", + "ILS": "" + } + } + }, + "Ras_Ai_Khaimah_International_Airport": { + "ICAO": "OMRK", + "Elevation": "330", + "TACAN": "", + "Runways:": { + "17": { + "Mag Hdg": "163", + "Length": "12000", + "ILS": "" + }, + "35": { + "Mag Hdg": "343", + "Length": "12000", + "ILS": "" + } + } + }, + "Sas_Ai_Nakheel_Airport": { + "ICAO": "OMNK", + "Elevation": "10", + "TACAN": "", + "Runways:": { + "16": { + "Mag Hdg": "160", + "Length": "6000", + "ILS": "" + }, + "34": { + "Mag Hdg": "340", + "Length": "6000", + "ILS": "" + } + } + }, + "Sharjah_International_Airport": { + "ICAO": "OMSJ", + "Elevation": "26", + "TACAN": "", + "Runways:": { + "12L": { + "Mag Hdg": "121", + "Length": "10500", + "ILS": "108.55" + }, + "30R": { + "Mag Hdg": "301", + "Length": "10500", + "ILS": "111.95" + }, + "12R": { + "Mag Hdg": "121", + "Length": "10500", + "ILS": "" + }, + "30L": { + "Mag Hdg": "301", + "Length": "10500", + "ILS": "" + } + } + }, + "Shiraz_AFB": { + "ICAO": "OISS", + "Elevation": "4879", + "TACAN": "94X", + "Runways:": { + "11L": { + "Mag Hdg": "113", + "Length": "14000", + "ILS": "" + }, + "29R": { + "Mag Hdg": "293", + "Length": "14000", + "ILS": "" + }, + "11R": { + "Mag Hdg": "113", + "Length": "13800", + "ILS": "" + }, + "29L": { + "Mag Hdg": "293", + "Length": "13800", + "ILS": "108.50" + } + } + }, + "Sir_Abu_Nuayr": { + "ICAO": "OMSN", + "Elevation": "26", + "TACAN": "", + "Runways:": { + "10": { + "Mag Hdg": "097", + "Length": "2300", + "ILS": "" + }, + "28": { + "Mag Hdg": "277", + "Length": "2300", + "ILS": "" + } + } + }, + "Sirri_Island_AFB": { + "ICAO": "OIBS", + "Elevation": "20", + "TACAN": "", + "Runways:": { + "12": { + "Mag Hdg": "125", + "Length": "7900", + "ILS": "" + }, + "30": { + "Mag Hdg": "305", + "Length": "7900", + "ILS": "" + } + } + }, + "Tunb_Islab_AFB": { + "ICAO": "OIGI", + "Elevation": "43", + "TACAN": "", + "Runways:": { + "03": { + "Mag Hdg": "025", + "Length": "6200", + "ILS": "" + }, + "21": { + "Mag Hdg": "205", + "Length": "6200", + "ILS": "" + } + } + }, + "Tonb_e_Kochak_Airport": { + "ICAO": "OITK", + "Elevation": "16", + "TACAN": "89X", + "Runways:": { + "08": { + "Mag Hdg": "079", + "Length": "2500", + "ILS": "" + }, + "26": { + "Mag Hdg": "259", + "Length": "2500", + "ILS": "" + } + } + } + } +} diff --git a/client/src/airfields/syria.json b/client/src/airfields/syria.json new file mode 100644 index 00000000..b48949b7 --- /dev/null +++ b/client/src/airfields/syria.json @@ -0,0 +1,995 @@ +{ + "airfields": { + "Abu_al-Duhur": { + "ICAO": "OS57", + "Elevation": "820", + "TACAN": "", + "Runways:": { + "09": { + "Mag Hdg": "088", + "Length": "9200", + "ILS": "" + }, + "27": { + "Mag Hdg": "268", + "Length": "9200", + "ILS": "" + } + } + }, + "Adana": { + "ICAO": "LTAF", + "Elevation": "56", + "TACAN": "", + "Runways": { + "05": { + "Mag Hdg": "050", + "Length": "8800", + "ILS": "108.70" + }, + "23": { + "Mag Hdg": "230", + "Length": "8800", + "ILS": "" + } + } + }, + "Akrotiri": { + "ICAO": "LCRA", + "Elevation": "69", + "TACAN": "107X", + "Runways": { + "10": { + "Mag Hdg": "106", + "Length": "8800", + "ILS": "" + }, + "28": { + "Mag Hdg": "286", + "Length": "8800", + "ILS": "109.70" + } + } + }, + "Al_Qusayr": { + "ICAO": "OS70", + "Elevation": "1729", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "096", + "Length": "9500", + "ILS": "" + }, + "28": { + "Mag Hdg": "276", + "Length": "9500", + "ILS": "" + } + } + }, + "Dumayr": { + "ICAO": "OS61", + "Elevation": "2067", + "TACAN": "", + "Runways": { + "06": { + "Mag Hdg": "060", + "Length": "9500", + "ILS": "" + }, + "24": { + "Mag Hdg": "240", + "Length": "9500", + "ILS": "" + } + } + }, + "Aleppo": { + "ICAO": "OSAP", + "Elevation": "1254", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "092", + "Length": "9200", + "ILS": "" + }, + "27": { + "Mag Hdg": "272", + "Length": "9200", + "ILS": "" + } + } + }, + "An_Nasiriyah": { + "ICAO": "OSAP", + "Elevation": "1254", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "092", + "Length": "9200", + "ILS": "" + }, + "27": { + "Mag Hdg": "272", + "Length": "9200", + "ILS": "" + } + } + }, + "At_Tanf": { + "ICAO": "", + "Elevation": "2329", + "TACAN": "", + "Runways": "" + }, + "Bassel_Al-Assad": { + "ICAO": "OSLK", + "Elevation": "92", + "TACAN": "", + "Runways": { + "17L": { + "Mag Hdg": "173", + "Length": "7900", + "ILS": "" + }, + "35R": { + "Mag Hdg": "353", + "Length": "7900", + "ILS": "" + }, + "17R": { + "Mag Hdg": "173", + "Length": "8900", + "ILS": "109.10" + }, + "35L": { + "Mag Hdg": "353", + "Length": "8900", + "ILS": "" + } + } + }, + "Beirut": { + "ICAO": "OLBA", + "Elevation": "39", + "TACAN": "", + "Runways": { + "03": { + "Mag Hdg": "030", + "Length": "7000", + "ILS": "110.70" + }, + "21": { + "Mag Hdg": "210", + "Length": "7000", + "ILS": "" + }, + "16": { + "Mag Hdg": "164", + "Length": "10300", + "ILS": "110.10" + }, + "34": { + "Mag Hdg": "344", + "Length": "10300", + "ILS": "" + }, + "17": { + "Mag Hdg": "174", + "Length": "7600", + "ILS": "109.50" + }, + "35": { + "Mag Hdg": "354", + "Length": "7600", + "ILS": "" + } + } + }, + "Damascus": { + "ICAO": "OSDI", + "Elevation": "2008", + "TACAN": "", + "Runways": { + "05L": { + "Mag Hdg": "045", + "Length": "11600", + "ILS": "" + }, + "23R": { + "Mag Hdg": "225", + "Length": "11600", + "ILS": "109.90" + }, + "05R": { + "Mag Hdg": "045", + "Length": "11600", + "ILS": "111.10" + }, + "23L": { + "Mag Hdg": "225", + "Length": "11600", + "ILS": "" + } + } + }, + "Deir_ez_Zor": { + "ICAO": "OSDZ", + "Elevation": "713", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "104", + "Length": "9500", + "ILS": "" + }, + "28": { + "Mag Hdg": "284", + "Length": "9500", + "ILS": "" + } + } + }, + "Ercan": { + "ICAO": "LCEN", + "Elevation": "312", + "TACAN": "", + "Runways": { + "11": { + "Mag Hdg": "110", + "Length": "8700", + "ILS": "" + }, + "29": { + "Mag Hdg": "290", + "Length": "8700", + "ILS": "108.30" + } + } + }, + "Eyn_Shemer": { + "ICAO": "LLES", + "Elevation": "110", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "095", + "Length": "4000", + "ILS": "" + }, + "27": { + "Mag Hdg": "2750", + "Length": "4000", + "ILS": "" + } + } + }, + "Gaziantep": { + "ICAO": "LTAJ", + "Elevation": "2290", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "100", + "Length": "9100", + "ILS": "" + }, + "28": { + "Mag Hdg": "280", + "Length": "9100", + "ILS": "109.10" + } + } + }, + "Gazipasa": { + "ICAO": "LTFG", + "Elevation": "130", + "TACAN": "", + "Runways": { + "08": { + "Mag Hdg": "080", + "Length": "7500", + "ILS": "108.50" + }, + "26": { + "Mag Hdg": "260", + "Length": "7500", + "ILS": "" + } + } + }, + "Gecitkale": { + "ICAO": "LCGK", + "Elevation": "148", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "088", + "Length": "9100", + "ILS": "108.50" + }, + "27": { + "Mag Hdg": "268", + "Length": "9100", + "ILS": "" + } + } + }, + "H3": { + "ICAO": "", + "Elevation": "2583", + "TACAN": "", + "Runways": { + "06": { + "Mag Hdg": "059", + "Length": "9800", + "ILS": "" + }, + "24": { + "Mag Hdg": "239", + "Length": "9800", + "ILS": "" + }, + "11": { + "Mag Hdg": "107", + "Length": "9500", + "ILS": "" + }, + "29": { + "Mag Hdg": "287", + "Length": "9500", + "ILS": "" + } + } + }, + "H3_Northwest": { + "ICAO": "", + "Elevation": "2582", + "TACAN": "", + "Runways": { + "12": { + "Mag Hdg": "117", + "Length": "8000", + "ILS": "" + }, + "30": { + "Mag Hdg": "297", + "Length": "8000", + "ILS": "" + } + } + }, + "H3_Southwest": { + "ICAO": "", + "Elevation": "2671", + "TACAN": "", + "Runways": { + "12": { + "Mag Hdg": "116", + "Length": "8000", + "ILS": "" + }, + "30": { + "Mag Hdg": "296", + "Length": "8000", + "ILS": "" + } + } + }, + "H4": { + "ICAO": "OJHR", + "Elevation": "2257", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "098", + "Length": "8000", + "ILS": "" + }, + "28": { + "Mag Hdg": "278", + "Length": "8000", + "ILS": "" + } + } + }, + "Haifa": { + "ICAO": "LLHA", + "Elevation": "20", + "TACAN": "", + "Runways": { + "16": { + "Mag Hdg": "157", + "Length": "3300", + "ILS": "" + }, + "34": { + "Mag Hdg": "337", + "Length": "3300", + "ILS": "" + } + } + }, + "Hama": { + "ICAO": "OS58", + "Elevation": "984", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "095", + "Length": "8600", + "ILS": "" + }, + "27": { + "Mag Hdg": "275", + "Length": "8600", + "ILS": "" + } + } + }, + "Hatay": { + "ICAO": "LTDA", + "Elevation": "253", + "TACAN": "", + "Runways": { + "04": { + "Mag Hdg": "039", + "Length": "9600", + "ILS": "108.90" + }, + "22": { + "Mag Hdg": "219", + "Length": "9600", + "ILS": "" + } + } + }, + "Incirlik": { + "ICAO": "LTAG", + "Elevation": "230", + "TACAN": "21X", + "Runways": { + "05": { + "Mag Hdg": "049", + "Length": "9600", + "ILS": "109.30" + }, + "23": { + "Mag Hdg": "229", + "Length": "9500", + "ILS": "111.70" + } + } + }, + "Jirah": { + "ICAO": "OS62", + "Elevation": "1173", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "095", + "Length": "9600", + "ILS": "" + }, + "28": { + "Mag Hdg": "275", + "Length": "9600", + "ILS": "" + } + } + }, + "Khalkhalah": { + "ICAO": "OS69", + "Elevation": "2418", + "TACAN": "", + "Runways": { + "07": { + "Mag Hdg": "071", + "Length": "9500", + "ILS": "" + }, + "25": { + "Mag Hdg": "251", + "Length": "9500", + "ILS": "" + }, + "15": { + "Mag Hdg": "146", + "Length": "8300", + "ILS": "" + }, + "33": { + "Mag Hdg": "326", + "Length": "8300", + "ILS": "" + } + } + }, + "Kharab_Ishk": { + "ICAO": "", + "Elevation": "1416", + "TACAN": "", + "Runways": "" + }, + "King_Hussein": { + "ICAO": "OJMF", + "Elevation": "2205", + "TACAN": "", + "Runways": { + "13": { + "Mag Hdg": "127", + "Length": "9500", + "ILS": "" + }, + "31": { + "Mag Hdg": "307", + "Length": "9500", + "ILS": "" + } + } + }, + "Kingsfield": { + "ICAO": "LCRE", + "Elevation": "276", + "TACAN": "", + "Runways": { + "06": { + "Mag Hdg": "058", + "Length": "3300", + "ILS": "" + }, + "24": { + "Mag Hdg": "238", + "Length": "3300", + "ILS": "" + } + } + }, + "Kiryat_Shmona": { + "ICAO": "LLKS", + "Elevation": "360", + "TACAN": "", + "Runways": { + "03": { + "Mag Hdg": "033", + "Length": "3500", + "ILS": "" + }, + "21": { + "Mag Hdg": "213", + "Length": "3500", + "ILS": "" + } + } + }, + "Kuweires": { + "ICAO": "OS66", + "Elevation": "1201", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "095", + "Length": "7700", + "ILS": "" + }, + "28": { + "Mag Hdg": "275", + "Length": "7700", + "ILS": "" + } + } + }, + "Lakatamia": { + "ICAO": "", + "Elevation": "758", + "TACAN": "", + "Runways": "" + }, + "Larnaca": { + "ICAO": "LCRE", + "Elevation": "16", + "TACAN": "", + "Runways": { + "04": { + "Mag Hdg": "043", + "Length": "8800", + "ILS": "" + }, + "22": { + "Mag Hdg": "223", + "Length": "8800", + "ILS": "110.30" + } + } + }, + "Marj_Al_Sultan": { + "ICAO": "", + "Elevation": "2008", + "TACAN": "", + "Runways": "" + }, + "Marj_Ruhayyil": { + "ICAO": "OS63", + "Elevation": "2161", + "TACAN": "", + "Runways": { + "06L": { + "Mag Hdg": "059", + "Length": "9400", + "ILS": "" + }, + "24R": { + "Mag Hdg": "239", + "Length": "9400", + "ILS": "" + }, + "06R": { + "Mag Hdg": "059", + "Length": "8400", + "ILS": "" + }, + "24L": { + "Mag Hdg": "239", + "Length": "8400", + "ILS": "" + } + } + }, + "Megiddo": { + "ICAO": "LLMG", + "Elevation": "180", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "088", + "Length": "6200", + "ILS": "" + }, + "27": { + "Mag Hdg": "268", + "Length": "6200", + "ILS": "" + } + } + }, + "Mezzeh": { + "ICAO": "OS67", + "Elevation": "2387", + "TACAN": "", + "Runways": { + "06": { + "Mag Hdg": "056", + "Length": "8800", + "ILS": "" + }, + "24": { + "Mag Hdg": "236", + "Length": "8800", + "ILS": "" + } + } + }, + "Minakh": { + "ICAO": "OS71", + "Elevation": "1614", + "TACAN": "", + "Runways": { + "10": { + "Mag Hdg": "096", + "Length": "4500", + "ILS": "" + }, + "28": { + "Mag Hdg": "276", + "Length": "4500", + "ILS": "" + } + } + }, + "Naqoura": { + "ICAO": "", + "Elevation": "381", + "TACAN": "", + "Runways": "" + }, + "Palmyra": { + "ICAO": "OSPR", + "Elevation": "1325", + "TACAN": "", + "Runways": { + "08": { + "Mag Hdg": "079", + "Length": "9200", + "ILS": "" + }, + "26": { + "Mag Hdg": "259", + "Length": "9200", + "ILS": "" + } + } + }, + "Paphos": { + "ICAO": "LCPH", + "Elevation": "40", + "TACAN": "79X", + "Runways": { + "11": { + "Mag Hdg": "109", + "Length": "8600", + "ILS": "" + }, + "29": { + "Mag Hdg": "289", + "Length": "8600", + "ILS": "108.90" + } + } + }, + "Qabr_al_Sitt": { + "ICAO": "", + "Elevation": "2135", + "TACAN": "", + "Runways": "" + }, + "Ramat_David": { + "ICAO": "LLRD", + "Elevation": "146", + "TACAN": "84X", + "Runways": { + "09": { + "Mag Hdg": "084", + "Length": "7600", + "ILS": "" + }, + "27": { + "Mag Hdg": "264", + "Length": "7600", + "ILS": "" + }, + "11": { + "Mag Hdg": "105", + "Length": "7700", + "ILS": "" + }, + "29": { + "Mag Hdg": "285", + "Length": "7700", + "ILS": "" + }, + "15": { + "Mag Hdg": "141", + "Length": "7700", + "ILS": "" + }, + "33": { + "Mag Hdg": "321", + "Length": "7700", + "ILS": "111.10" + } + } + }, + "Rayak": { + "ICAO": "OLRA", + "Elevation": "3035", + "TACAN": "", + "Runways": { + "04": { + "Mag Hdg": "042", + "Length": "9400", + "ILS": "" + }, + "22": { + "Mag Hdg": "222", + "Length": "9400", + "ILS": "" + } + } + }, + "Rene_Mouawad": { + "ICAO": "OLKA", + "Elevation": "23", + "TACAN": "", + "Runways": { + "06": { + "Mag Hdg": "058", + "Length": "9000", + "ILS": "" + }, + "24": { + "Mag Hdg": "238", + "Length": "9000", + "ILS": "" + } + } + }, + "Rosh_Pina": { + "ICAO": "LLIB", + "Elevation": "914", + "TACAN": "", + "Runways": { + "05": { + "Mag Hdg": "049", + "Length": "3200", + "ILS": "" + }, + "23": { + "Mag Hdg": "229", + "Length": "3200", + "ILS": "" + }, + "15": { + "Mag Hdg": "147", + "Length": "2900", + "ILS": "" + }, + "33": { + "Mag Hdg": "327", + "Length": "2900", + "ILS": "" + } + } + }, + "Ruwayshid": { + "ICAO": "", + "Elevation": "2980", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "091", + "Length": "7000", + "ILS": "" + }, + "27": { + "Mag Hdg": "271", + "Length": "7000", + "ILS": "" + } + } + }, + "Sanliurfa": { + "ICAO": "LTCS", + "Elevation": "2703", + "TACAN": "", + "Runways": { + "04": { + "Mag Hdg": "036", + "Length": "12900", + "ILS": "" + }, + "22": { + "Mag Hdg": "216", + "Length": "12900", + "ILS": "" + } + } + }, + "Sayqal": { + "ICAO": "OS68", + "Elevation": "2274", + "TACAN": "", + "Runways": { + "05": { + "Mag Hdg": "055", + "Length": "7600", + "ILS": "" + }, + "23": { + "Mag Hdg": "235", + "Length": "7600", + "ILS": "" + }, + "08": { + "Mag Hdg": "085", + "Length": "9500", + "ILS": "" + }, + "26": { + "Mag Hdg": "265", + "Length": "9500", + "ILS": "" + } + } + }, + "Shayrat": { + "ICAO": "OS65", + "Elevation": "2638", + "TACAN": "", + "Runways": { + "11": { + "Mag Hdg": "107", + "Length": "9300", + "ILS": "" + }, + "29": { + "Mag Hdg": "287", + "Length": "9300", + "ILS": "" + } + } + }, + "Tabqa": { + "ICAO": "OS59", + "Elevation": "1099", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "088", + "Length": "9300", + "ILS": "" + }, + "27": { + "Mag Hdg": "268", + "Length": "9300", + "ILS": "" + } + } + }, + "Taftanaz": { + "ICAO": "", + "Elevation": "", + "TACAN": "", + "Runways": "" + }, + "Tal_Siman": { + "ICAO": "", + "Elevation": "", + "TACAN": "", + "Runways": "" + }, + "thalah": { + "ICAO": "OS60", + "Elevation": "2414", + "TACAN": "", + "Runways": { + "05": { + "Mag Hdg": "053", + "Length": "9500", + "ILS": "" + }, + "23": { + "Mag Hdg": "233", + "Length": "9500", + "ILS": "" + } + } + }, + "Tiyas": { + "ICAO": "OS72", + "Elevation": "1798", + "TACAN": "", + "Runways": { + "09": { + "Mag Hdg": "085", + "Length": "9500", + "ILS": "" + }, + "27": { + "Mag Hdg": "265", + "Length": "9500", + "ILS": "" + } + } + }, + "Wujah_Al_Hajar": { + "ICAO": "Z190", + "Elevation": "641", + "TACAN": "", + "Runways": { + "02": { + "Mag Hdg": "024", + "Length": "4800", + "ILS": "" + }, + "20": { + "Mag Hdg": "204", + "Length": "4800", + "ILS": "" + } + } + } + } +} diff --git a/client/src/atc/atc.ts b/client/src/atc/atc.ts index e85c1ee2..1597ca7e 100644 --- a/client/src/atc/atc.ts +++ b/client/src/atc/atc.ts @@ -1,3 +1,5 @@ +import { getMissionData } from ".."; +import { getConnected } from "../server/server"; import { ATCBoard } from "./atcboard"; import { ATCBoardGround } from "./board/ground"; import { ATCBoardTower } from "./board/tower"; @@ -48,6 +50,10 @@ class ATCDataHandler { this.#updateInterval = window.setInterval( () => { + if ( !getConnected() ) { + return; + } + const aBoardIsVisible = this.#atc.getBoards().some( board => board.boardIsVisible() ); if ( aBoardIsVisible ) { @@ -135,7 +141,7 @@ export class ATC { getMissionDateTime() : Date { - return new Date( this.getMissionStartDateTime().getTime() + this.getMissionElapsedSeconds() ); + return new Date( getMissionData().getNowDate() ); } diff --git a/client/src/atc/atcboard.ts b/client/src/atc/atcboard.ts index 79586def..959874ab 100644 --- a/client/src/atc/atcboard.ts +++ b/client/src/atc/atcboard.ts @@ -2,9 +2,10 @@ import { Dropdown } from "../controls/dropdown"; import { zeroAppend } from "../other/utils"; import { ATC } from "./atc"; import { Unit } from "../units/unit"; -import { getUnitsManager } from ".."; +import { getMissionData, getUnitsManager } from ".."; import Sortable from "sortablejs"; import { FlightInterface } from "./atc"; +import { getConnected } from "../server/server"; export interface StripBoardStripInterface { "id": string, @@ -84,6 +85,10 @@ export abstract class ATCBoard { window.setInterval( () => { + + if ( !getConnected() ) { + return; + } this.updateClock(); }, 1000 ); @@ -410,6 +415,10 @@ export abstract class ATCBoard { this.#updateInterval = window.setInterval( () => { + if ( !getConnected() ) { + return; + } + this.update(); }, this.#updateIntervalDelay ); @@ -446,8 +455,12 @@ export abstract class ATCBoard { updateClock() { - const now = this.#atc.getMissionDateTime(); - this.#clockElement.innerText = now.toLocaleTimeString(); + const missionTime = this.#atc.getMissionDateTime().getTime(); + const timeDiff = new Date().getTime() - getMissionData().getUpdateTime(); + + const nowDate = new Date( missionTime + timeDiff ); + + this.#clockElement.innerText = nowDate.toLocaleTimeString(); } diff --git a/client/src/features/featureswitches.ts b/client/src/features/featureswitches.ts index ee237b01..a7cf1c30 100644 --- a/client/src/features/featureswitches.ts +++ b/client/src/features/featureswitches.ts @@ -1,7 +1,7 @@ export interface FeatureSwitchInterface { - "defaultEnabled": boolean, // default on/off state (if allowed by masterSwitch) + "defaultEnabled": boolean, // default on/off state (if allowed by forceState) + "forceState": number, // -1 don't force; 0 force off; 1 force on "label": string, - "masterSwitch": boolean, // on/off regardless of user preference "name": string, "onEnabled"?: CallableFunction, "options"?: object, @@ -13,8 +13,8 @@ class FeatureSwitch { // From config param defaultEnabled; + forceState = -1; label; - masterSwitch; name; onEnabled; removeArtifactsIfDisabled = true; @@ -26,10 +26,10 @@ class FeatureSwitch { constructor(config: FeatureSwitchInterface) { this.defaultEnabled = config.defaultEnabled; - this.label = config.label; - this.masterSwitch = config.masterSwitch; - this.name = config.name; - this.onEnabled = config.onEnabled; + this.forceState = config.forceState; + this.label = config.label; + this.name = config.name; + this.onEnabled = config.onEnabled; this.userPreference = this.getUserPreference(); @@ -47,10 +47,14 @@ class FeatureSwitch { isEnabled() { - if (!this.masterSwitch) { + if ( this.forceState === 0 ) { return false; } + if ( this.forceState === 1 ) { + return true; + } + return this.userPreference; } @@ -62,37 +66,37 @@ export class FeatureSwitches { new FeatureSwitch({ "defaultEnabled": false, + "forceState": -1, "label": "AIC", - "masterSwitch": true, "name": "aic" }), new FeatureSwitch({ "defaultEnabled": false, + "forceState": -1, "label": "AI Formations", - "masterSwitch": true, "name": "ai-formations", "removeArtifactsIfDisabled": false }), new FeatureSwitch({ "defaultEnabled": false, + "forceState": 1, "label": "ATC", - "masterSwitch": true, "name": "atc" }), new FeatureSwitch({ "defaultEnabled": false, + "forceState": -1, "label": "Force show unit control panel", - "masterSwitch": true, "name": "forceShowUnitControlPanel" }), new FeatureSwitch({ "defaultEnabled": true, + "forceState": -1, "label": "Show splash screen", - "masterSwitch": true, "name": "splashScreen" }) diff --git a/client/src/index.ts b/client/src/index.ts index ac14f5c6..e4760d01 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -98,6 +98,7 @@ function readConfig(config: any) { } function setupEvents() { + /* Generic clicks */ document.addEventListener("click", (ev) => { if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) { diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 2471719e..5e05358a 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -9,6 +9,16 @@ export class MissionHandler #airbases : {[name: string]: Airbase} = {}; #theatre : string = ""; + #airbaseData : { [name: string]: object } = {}; + + // Time + #date : any; + #elapsedTime : any; + #startTime : any; + #time : any; + + #updateTime : any; + constructor() { @@ -32,8 +42,29 @@ export class MissionHandler } } + + if ("mission" in data) + { + if (data.mission != null && data.mission.theatre != this.#theatre) + { + this.#theatre = data.mission.theatre; + getMap().setTheatre(this.#theatre); + + getInfoPopup().setText("Map set to " + this.#theatre); + } + } + + if ("airbases" in data) { +/* + console.log( Object.values( data.airbases ).sort( ( a:any, b:any ) => { + const aVal = a.callsign.toLowerCase(); + const bVal = b.callsign.toLowerCase(); + + return aVal > bVal ? 1 : -1; + }) ); +//*/ for (let idx in data.airbases) { var airbase = data.airbases[idx] @@ -65,7 +96,30 @@ export class MissionHandler getInfoPopup().setText("Map set to " + this.#theatre); } + + if ( "date" in data.mission ) { + this.#date = data.mission.date; + } + + if ( "elapsedTime" in data.mission ) { + this.#elapsedTime = data.mission.elapsedTime; + } + + if ( "startTime" in data.mission ) { + this.#startTime = data.mission.startTime; + } + + if ( "time" in data.mission ) { + this.#time = data.mission.time; + } + } + + + if ( "time" in data ) { + this.#updateTime = data.time; + } + } getBullseyes() @@ -73,6 +127,41 @@ export class MissionHandler return this.#bullseyes; } + getDate() { + return this.#date; + } + + + getNowDate() { + + const date = this.getDate(); + const time = this.getTime(); + + if ( !date ) { + return new Date(); + } + + let year = date.Year; + let month = date.Month - 1; + + if ( month < 0 ) { + month = 11; + year--; + } + + return new Date( year, month, date.Day, time.h, time.m, time.s ); + } + + + getTime() { + return this.#time; + } + + + getUpdateTime() { + return this.#updateTime; + } + #onAirbaseClick(e: any) { getMap().showAirbaseContextMenu(e, e.sourceTarget); diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index b9ce860c..2e8bf910 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -504,7 +504,6 @@ export class Unit extends CustomMarker { } delete(explosion: boolean) { - // TODO: add confirmation popup deleteUnit(this.ID, explosion); } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index ba3004a5..ffbc3e62 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -450,9 +450,18 @@ export class UnitsManager { } /***********************************************/ - #onKeyDown(event: KeyboardEvent) { - if (!keyEventWasInInput(event) && event.key === "Delete") { - this.selectedUnitsDelete(); + #onKeyUp(event: KeyboardEvent) { + if (!keyEventWasInInput(event) && event.key === "Delete" ) { + + const selectedUnits = this.getSelectedUnits(); + const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => { + return unit.getMissionData().flags.Human === true; + }); + + if ( !selectionContainsAHuman || confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) { + this.selectedUnitsDelete(); + } + } } diff --git a/client/views/atc/addflight.ejs b/client/views/atc/addflight.ejs index 818869f0..97e1ae5b 100644 --- a/client/views/atc/addflight.ejs +++ b/client/views/atc/addflight.ejs @@ -1,5 +1,5 @@
- +
\ No newline at end of file diff --git a/client/views/atc/board.ejs b/client/views/atc/board.ejs index 007b91f2..58d3dfcc 100644 --- a/client/views/atc/board.ejs +++ b/client/views/atc/board.ejs @@ -1,9 +1,10 @@ -
+

<%= boardType %>

+ <%- include('addflight.ejs') %>
@@ -18,8 +19,4 @@
- -
\ No newline at end of file diff --git a/scripts/parrotSpeak.lua b/scripts/parrotSpeak.lua new file mode 100644 index 00000000..a24057a4 --- /dev/null +++ b/scripts/parrotSpeak.lua @@ -0,0 +1,184 @@ +--------------------------------THIS FIRST BIT IS THE SRS CODE BLOCK------------------- +-------------------- first 4 things need to be set correctly for the server, they are for ours, we don't do the google creds yet + +STTS = {} +-- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER +STTS.DIRECTORY = "C:\\Users\\Administrator\\Desktop\\DCS\\SRS Refugees" + +STTS.SRS_PORT = 5002 -- LOCAL SRS PORT - DEFAULT IS 5002 +STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" + +-- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING +STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" + +local random = math.random +function STTS.uuid() + local template ='yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) + return string.format('%x', v) + end) +end + +function STTS.round(x, n) + n = math.pow(10, n or 0) + x = x * n + if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end + return x / n +end + +function STTS.getSpeechTime(length,speed,isGoogle) + -- Function returns estimated speech time in seconds + + -- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word + -- so 5 chars * 100wpm = 500 characters per min = 8.3 chars per second + -- so lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec + -- map function: (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + + local maxRateRatio = 3 + + speed = speed or 1.0 + isGoogle = isGoogle or false + + local speedFactor = 1.0 + if isGoogle then + speedFactor = speed + else + if speed ~= 0 then + speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1/speedFactor + end + end + + local wpm = math.ceil(100 * speedFactor) + local cps = math.floor((wpm * 5)/60) + + if type(length) == "string" then + length = string.len(length) + end + + return math.ceil(length/cps) +end + +function STTS.TextToSpeech(message,freqs,modulations, volume,name, coalition,point, speed,gender,culture,voice, googleTTS ) + if os == nil or io == nil then + env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ") + return + end + + speed = speed or 1 + gender = gender or "female" + culture = culture or "" + voice = voice or "" + + + message = message:gsub("\"","\\\"") + + local cmd = string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs, modulations, coalition,STTS.SRS_PORT, name ) + + if voice ~= "" then + cmd = cmd .. string.format(" -V \"%s\"",voice) + else + + if culture ~= "" then + cmd = cmd .. string.format(" -l %s",culture) + end + + if gender ~= "" then + cmd = cmd .. string.format(" -g %s",gender) + end + end + + if googleTTS == true then + cmd = cmd .. string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS) + end + + if speed ~= 1 then + cmd = cmd .. string.format(" -s %s",speed) + end + + if volume ~= 1.0 then + cmd = cmd .. string.format(" -v %s",volume) + end + + if point and type(point) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL(point) + + lat = STTS.round(lat,4) + lon = STTS.round(lon,4) + alt = math.floor(alt) + + cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + end + + cmd = cmd ..string.format(" -t \"%s\"",message) + + if string.len(cmd) > 255 then + local filename = os.getenv('TMP') .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" + local script = io.open(filename,"w+") + script:write(cmd .. " && exit" ) + script:close() + cmd = string.format("\"%s\"",filename) + timer.scheduleFunction(os.remove, filename, timer.getTime() + 1) + end + + if string.len(cmd) > 255 then + env.info("[DCS-STTS] - cmd string too long") + env.info("[DCS-STTS] TextToSpeech Command :\n" .. cmd.."\n") + end + os.execute(cmd) + + return STTS.getSpeechTime(message,speed,googleTTS) + +end + +function STTS.PlayMP3(pathToMP3,freqs,modulations, volume,name, coalition,point ) + + local cmd = string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs, modulations, coalition,STTS.SRS_PORT, name, volume ) + + if point and type(point) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL(point) + + lat = STTS.round(lat,4) + lon = STTS.round(lon,4) + alt = math.floor(alt) + + cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + end + + env.info("[DCS-STTS] MP3/OGG Command :\n" .. cmd.."\n") + os.execute(cmd) + +end + +------------------------THIS BIT IS THE CODE YOU'D RUN IN GAME + +tts = {} +tts.words = "All players, all players, AO update in 5 Magic to all players AO update as follows Weather over North ranges is good, recommending full up war. altimeter 3 0 decimal 1 2 , flare restrictions above 5000,in the MOA's and burnout by 100 ft in western ranges. Chaff below 20,000 for all playersAir picture is multiple groups bandits forming a north south CAP 60 miles north west of bullseye, no SAM,manpad or triple A. All units are approved to start moving into tracks for exercise start, exercise commences in 5 minutes" + +tts.atis = "All players, all players, AO update in 5 Magic to all players AO update as follows Weather over North ranges is good, recommending full up war. altimeter 3 0 decimal 1 2 , flare restrictions above 5000,in the MOA's and burnout by 100 ft in western ranges. Chaff below 20,000 for all playersAir picture is multiple groups bandits forming a north south CAP 60 miles north west of bullseye, no SAM,manpad or triple A. All units are approved to start moving into tracks for exercise start, exercise commences in 5 minutes" + +function tts.notify(message, displayFor) + trigger.action.outText(message, displayFor) +end + +function tts.normal () + STTS.TextToSpeech(tts.words,"251","AM","1.0","SRS",2) +end + +function tts.russian () + STTS.TextToSpeech(tts.words,"251","AM","1.0","SRS",2,null,1,"female","ru-RU","Microsoft Irina Desktop") +end + + +do + longRangeShots = missionCommands.addSubMenu("Crash checks") + missionCommands.addCommand ("Speak", longRangeShots, tts.normal) + missionCommands.addCommand ("Speak russian", longRangeShots, tts.russian) + +end + +tts.notify("crashTest.lua loaded", 2) +