Skip to content

Commit

Permalink
It's all broken - trust nothing. But at least its not on my laptop an…
Browse files Browse the repository at this point in the history
…ymore
  • Loading branch information
Kahn committed Jul 28, 2021
1 parent 54fd50f commit e8799fb
Show file tree
Hide file tree
Showing 15 changed files with 24,935 additions and 41 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
npm-debug.log
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM node:14

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production

# Bundle app source
COPY . .

EXPOSE 8080
CMD [ "node", "server.js" ]
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## TODO

## Future
Show ATC polys and state from controllers API

### Current stats
In FIR now

### Stored stats
Traffic heatmap (DB needed)

## Credits
CC BY-SA 3.0
https://commons.wikimedia.org/wiki/File:Plane_font_awesome.svg
184 changes: 184 additions & 0 deletions api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import * as turf from '@turf/turf'
import { xml2js } from 'xml-js';
import dms2dec from 'dms2dec';
import fs from 'fs';
import fetch from 'node-fetch';
import NodeCache from 'node-cache';

const cache = new NodeCache( { stdTTL: 15, checkperiod: 30 } );

export function clearCache(){
console.log('Clearing cache')
cache.flushAll();
return true;
}

export async function getPilots(){
console.log(cache.getStats());
try{
var firs = await getFIRBoundaries('https://raw.githubusercontent.com/vatSys/australia-dataset/master/Maps/FIR_BOUNDARIES.xml');
firLineToPoly(firs);
var vatsimData = await getVatsimData('https://data.vatsim.net/v3/vatsim-data.json');
}catch(err){
console.log(err)
return false;
}
try{
return pilotsInFIR(firs, vatsimData);
}catch(err){
console.log(err)
return false;
}
}

function pilotsInFIR(fir, data){
var pilotsInFIR = [];
data.pilots.forEach(function(pilot) {
var ppos = turf.point(
[(pilot.longitude), (pilot.latitude)],
{ pilot }
);
if(pointInFIR(ppos, fir) == true){
pilotsInFIR.push(ppos);
}
});
return turf.featureCollection(pilotsInFIR);
}

function checkHTTPStatus(res) {
if (res.ok) { // res.status >= 200 && res.status < 300
return res;
} else {
throw HTTPError(res.statusText);
}
}

async function getVatsimData (url) {
var data = cache.get("vatsimData");
if (data == undefined) {
console.log("getVatsimData cache miss")
const res = await fetch(url)
.then(checkHTTPStatus)
.then(res => res.json())
.then( data => {
return data;
})
.catch(err => console.log(err));
data = res;
console.log(`Cached vatsimData len ${Object.keys(data).length}`)
cache.set("vatsimData", data, 15);
}else{
console.log(`vatsimData len ${Object.keys(data).length}`)
console.log("getVatsimData cache hit")
}
return data;
}

async function getFIRBoundaries (url) {
// Extract vatSys firObjs into line arrays
var firObjs = [];
let xml;
var data = cache.get("xml");
if (data == undefined) {
console.log("getFIRBoundaries cache miss")
try{
// Download and parse XML
const res = await fetch(url)
.then(checkHTTPStatus)
.then(res => res.text())
.then( data => {
return data;
})
.catch(err => console.log(err));
xml = await xml2js(res, {compact: true, spaces: 4});
}catch(err){
throw ParserError(err);
}

// Handle single Line Map
if(Array.isArray(xml.Maps.Map.Line)){
xml.Maps.Map.Line.forEach(function (line){
firObjs.push(lineToFIR(line))
});
}else{
firObjs.push(lineToFIR(xml.Maps.Map.Line))
}
console.log(`Cached firObjs len ${firObjs.length}`)
cache.set("xml", firObjs, 86400);
}else{
console.log(`firObjs len ${firObjs.length}`)
console.log("getFIRBoundaries cache hit")
}
return firObjs;
}

function firLineToPoly (firs) {
// Line arrays into turf polys
firs.forEach(function(fir){
var poly = turf.lineToPolygon(turf.lineString(fir.line),{mutate: true});
fir.poly = poly;
// Dump GeoJSON poly for debugging in https://geojson.io
// console.log(JSON.stringify(poly, null, 1));
// fs.writeFile(`./out/${fir.name}.json`, JSON.stringify(poly, null, 1), 'utf8', function(err) {
// if (err) throw err;
// console.log(`./out/${fir.name}.json`);
// }
// );
});
return firs;
}

function lineToFIR (line) {
// Create new JS obj
var fir = {
name: line._attributes.Name,
line: [],
poly: null
}
// Take vatSys lines and parse into line array
// ±DDMMSS.SSSS±DDDMMSS.SSSS
// (?<latD>[+-][0-9]{2})(?<latM>[0-9]{2})(?<latS>[0-9]{2}\.[0-9]{3})(?<lonD>[+-][0-9]{3})(?<lonM>[0-9]{2})(?<lonS>[0-9]{2}\.[0-9]{3})
// const re = new RegExp(/(?<lat>[+-][0-9]{6}\.[0-9]{3})(?<lon>[+-][0-9]{7}\.[0-9]{3})/g);
const re = new RegExp(/(?<latRef>[+-])(?<latD>[0-9]{2})(?<latM>[0-9]{2})(?<latS>[0-9]{2}\.[0-9]{3})(?<lonRef>[+-])(?<lonD>[0-9]{3})(?<lonM>[0-9]{2})(?<lonS>[0-9]{2}\.[0-9]{3})/g)
var lines = [...line._text.matchAll(re)];
lines.forEach(function(l){
// Convert vaySys DMS into decimal degrees
// https://github.com/vatSys/xml-tools/blob/master/DotAIPtoXML/DotAIPtoXML/Coordinate.cs#L119
var pos = {
latitude: [
parseInt(l.groups.latD),
parseInt(l.groups.latM),
parseFloat(l.groups.latS)
],
latRef: (l.groups.latRef == '+') ? "N" : "S",
longitude: [
parseInt(l.groups.lonD),
parseInt(l.groups.lonM),
parseFloat(l.groups.lonS)
],
lonRef: (l.groups.lonRef == '+') ? "E" : "W"
}
var [ latitude, longitude ] = dms2dec(pos.latitude,pos.latRef,pos.longitude,pos.lonRef);
// Turf is geoJSON so we continue the stupidity here with long THEN lat.
var a = [longitude, latitude]
// Append extract arrays to fir.line array
fir.line.push(a);
});
return fir
};

// Flights in FIRs
function pointInFIR (point, firs) {
var mappedFIRs = [ "MELBOURNE_FIR", "BRISBANE_FIR" ];
var inFIR = false;
firs.forEach(function(fir){
if(mappedFIRs.includes(fir.name)) {
if(turf.booleanPointInPolygon(point,fir.poly)){
console.log(`Matched in ${fir.name}`)
inFIR = true;
}
};
});
return inFIR;
}

12 changes: 12 additions & 0 deletions data/test-fir.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Maps>
<Map Type="System" Name="FIR_BDRY" Priority="2">
<Line Name="AUSBOX">
-092400.000+1605400.000
-430000.000+1605400.000
-430000.000+0990000.000
-092400.000+0990000.000
-092400.000+1605400.000
</Line>
</Map>
</Maps>
79 changes: 79 additions & 0 deletions data/test-vatsim-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"general": {
"version": 3,
"reload": 1,
"update": "20210720122320",
"update_timestamp": "2021-07-20T12:23:20.5674914Z",
"connected_clients": 740,
"unique_users": 706
},
"pilots": [
{
"cid": 1524202,
"name": "TEST 1",
"callsign": "TEST-FALSE",
"server": "GERMANY-1",
"pilot_rating": 0,
"latitude": -22.77579,
"longitude": -60.05077,
"altitude": 37313,
"groundspeed": 484,
"transponder": "1572",
"heading": 227,
"qnh_i_hg": 30.23,
"qnh_mb": 1024,
"flight_plan": {
"flight_rules": "I",
"aircraft": "H/B78X/B",
"aircraft_faa": "H/B78X/B",
"aircraft_short": "B78X",
"departure": "LIRF",
"arrival": "SCEL",
"alternate": "EHAM",
"cruise_tas": "557",
"altitude": "40000",
"deptime": "1603",
"enroute_time": "1420",
"fuel_time": "1030",
"remarks": "MSFS DEFAULT GPS. ONLY TEXT ON GROUND USING PACX WITH BOARDING MUSIC. /V/",
"route": "LIRF/NENI6W RF601 RF603 RF609 RF711 NENIG GILIO SUXER LABRO ARI FES NEVEK ERLAM TIXAL VALBA OBOGA MAK LISRA BIDLA OLOMA ADM TEKNA AMLOK SOLNA LAY BIPET IPERA NEMDO SVT AMDOL DIGUN 0535N DAKAP 0140W MICRO ENRUS RODOT XVT TOSAR DIKAX ATERU UMSIL DAKBU ESDER REMEK NEKAN ILPUR EGEXO AKNEL GETRA VINOS QUILI UBRIX ENVOK SIMKOK TBN60 MOLPU D358X D358P UGANO TBN PUMAR IUE15 TEGEB SCEL/ANDES8/17L",
"revision_id": 11
},
"logon_time": "2021-07-19T17:04:22.5391994Z",
"last_updated": "2021-07-20T12:23:17.4437642Z"
},
{
"cid": 1524202,
"name": "TEST 2",
"callsign": "TEST-TRUE",
"server": "GERMANY-1",
"pilot_rating": 0,
"latitude": -26.0000,
"longitude": 138.6500,
"altitude": 37313,
"groundspeed": 484,
"transponder": "1572",
"heading": 227,
"qnh_i_hg": 30.23,
"qnh_mb": 1024,
"flight_plan": {
"flight_rules": "I",
"aircraft": "H/B78X/B",
"aircraft_faa": "H/B78X/B",
"aircraft_short": "B78X",
"departure": "LIRF",
"arrival": "SCEL",
"alternate": "EHAM",
"cruise_tas": "557",
"altitude": "40000",
"deptime": "1603",
"enroute_time": "1420",
"fuel_time": "1030",
"remarks": "MSFS DEFAULT GPS. ONLY TEXT ON GROUND USING PACX WITH BOARDING MUSIC. /V/",
"route": "LIRF/NENI6W RF601 RF603 RF609 RF711 NENIG GILIO SUXER LABRO ARI FES NEVEK ERLAM TIXAL VALBA OBOGA MAK LISRA BIDLA OLOMA ADM TEKNA AMLOK SOLNA LAY BIPET IPERA NEMDO SVT AMDOL DIGUN 0535N DAKAP 0140W MICRO ENRUS RODOT XVT TOSAR DIKAX ATERU UMSIL DAKBU ESDER REMEK NEKAN ILPUR EGEXO AKNEL GETRA VINOS QUILI UBRIX ENVOK SIMKOK TBN60 MOLPU D358X D358P UGANO TBN PUMAR IUE15 TEGEB SCEL/ANDES8/17L",
"revision_id": 11
},
"logon_time": "2021-07-19T17:04:22.5391994Z",
"last_updated": "2021-07-20T12:23:17.4437642Z"
}
]}
24,044 changes: 24,043 additions & 1 deletion data/vatsim-data.json

Large diffs are not rendered by default.

39 changes: 0 additions & 39 deletions index.js

This file was deleted.

Loading

0 comments on commit e8799fb

Please sign in to comment.