Traffic Generator

This commit is contained in:
Keith Paterson 2021-01-29 17:03:11 +01:00
parent 9f6a9a0943
commit bd4bdd6b51
8 changed files with 640 additions and 106 deletions

View File

@ -0,0 +1,95 @@
<!--
Copyright 2021 Keith Paterson
This file is part of FG Airports.
FG Airports is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
FG Airports is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with FG Airports. If not, see http://www.gnu.org/licenses/.
-->
<template>
<div>
<div v-for="item in traffic" v-bind:key="item.id">
<div v-if="direction == 0">{{ item.departure.time }} {{ item.callsign }} {{ item.departure.port }} --> {{ item.arrival.port }}</div>
<div v-if="direction == 1">{{ item.arrival.time }} {{ item.callsign }} {{ item.departure.port }} --> {{ item.arrival.port }}</div>
</div>
</div>
</template>
<script lang="js">
import {readTrafficXML} from '../loaders/traffic_loader'
import ParkingItem from './ParkingItem'
const fs = require('fs')
const path = require('path')
export default {
name: 'airline-traffic',
components: {ParkingItem},
props: {airline: Object},
data () {
return {}
},
methods: {
traverseDir (dir, airline) {
var result = []
if (!fs.existsSync(dir)) {
return result
}
var iaco = airline.label
fs.readdirSync(dir).forEach(file => {
let fullPath = path.join(dir, file)
if (fs.lstatSync(fullPath).isDirectory()) {
var children = this.traverseDir(fullPath, airline)
result = result.concat(children)
} else {
if (file.match(new RegExp(`${iaco}.xml`, 'i'))) {
result.push(fullPath)
}
}
})
return result
}
},
computed: {
direction: function () {
return this.$parent.$parent.$parent.$data.direction
},
filename: function () {
var ret = this.traverseDir(this.$store.state.Settings.settings.flightgearDirectory_traffic, this.airline)
if (ret.length > 0) {
return ret[0]
}
},
trafficFile: function () {
return readTrafficXML(this.filename)
},
traffic: function () {
if (this.filename) {
return this.trafficFile.filter(f => f.callsign).filter(f =>
(f.departure.port === this.$store.state.Airports.currentAirport.icao && this.direction === 0) ||
(f.arrival.port === this.$store.state.Airports.currentAirport.icao && this.direction === 1)
)
}
console.debug(this.filename)
},
aircraft: function () {
if (this.filename) {
return this.trafficFile.filter(f => f.registration)
}
console.debug(this.filename)
}
}
}
</script>
<style>
div.row.div {
display: flex;
justify-content: space-between;
}
</style>

View File

@ -16,12 +16,13 @@ You should have received a copy of the GNU General Public License along with FG
title="Add Airline"
:visible.sync="dialogVisible"
width="40%"
:before-close="handleClose">
:before-close="handleClose"
>
<span>Add an selectable airline to {{ icao }} {{ name }}</span>
<el-input
placeholder="Please input airline(s)"
v-model="airlineCode"
></el-input>
<el-input
placeholder="Please input airline(s)"
v-model="airlineCode"
></el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="addAirline">Confirm</el-button>
@ -31,116 +32,143 @@ You should have received a copy of the GNU General Public License along with FG
title="Import File"
:visible.sync="showImportFile"
width="20%"
:before-close="handleClose">
<span>Beware wip will be overwritten</span>
<el-row>
<el-col :span="20">
<el-input
placeholder="Please input file"
v-model="fileImportName">
</el-input>
</el-col>
<el-col :span="4">
<file-select @input="fileImportFileName"></file-select>
</el-col>
</el-row>
:before-close="handleClose"
>
<span>Beware wip will be overwritten</span>
<el-row>
<el-col :span="20">
<el-input placeholder="Please input file" v-model="fileImportName">
</el-input>
</el-col>
<el-col :span="4">
<file-select @input="fileImportFileName"></file-select>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="showImportFile = false">Cancel</el-button>
<el-button type="primary" @click="importFile">Confirm</el-button>
</span>
</el-dialog>
<h1 class="leaflet-sidebar-header">{{ icao }} {{ name }}</h1>
<div width="100%" >
<el-row>
<el-popover
placement="top-start"
title="Description"
width="50"
trigger="hover"
content="Edit"
>
<el-button @click="edit" v-if="!editing" slot="reference"><i class="fas fa-edit"></i></el-button>
</el-popover>
<el-popover
placement="top-start"
title="Description"
width="200"
trigger="hover"
content="Import groundnet"
>
<el-button @click="showImportFile = true" v-if="!editing" slot="reference"><i class="fas fa-file-import"></i></el-button>
</el-popover>
<el-popover
placement="top-start"
title="Description"
width="220"
trigger="hover"
content="Export groundnet to export directory"
>
<el-button @click="test" v-if="!editing" slot="reference"><i class="fas fa-file-export"></i></el-button>
</el-popover>
<el-popover
placement="top-start"
title="Description"
width="200"
trigger="hover"
content="Upload Airport"
>
<el-button @click="upload" v-if="!editing" slot="reference"><i class="fas fa-upload"></i></el-button>
</el-popover>
</el-row>
<el-row>
<el-col :span="7"><span class="label"> Airlines :</span></el-col>
<el-col :span="15">
<el-tag v-for="item in airlines" :key="item.value">{{item.value}}</el-tag>
</el-col>
<el-col :span="2">
<el-button @click="dialogVisible = true" v-if="editing" ><i class="fas fa-plus"></i></el-button>
</el-col>
</el-row>
<div width="100%">
<el-row>
<el-popover
placement="top-start"
title="Description"
width="50"
trigger="hover"
content="Edit"
>
<el-button @click="edit" v-if="!editing" slot="reference"
><i class="fas fa-edit"></i
></el-button>
</el-popover>
<el-popover
placement="top-start"
title="Description"
width="200"
trigger="hover"
content="Import groundnet"
>
<el-button
@click="showImportFile = true"
v-if="!editing"
slot="reference"
><i class="fas fa-file-import"></i
></el-button>
</el-popover>
<el-popover
placement="top-start"
title="Description"
width="220"
trigger="hover"
content="Export groundnet to export directory"
>
<el-button @click="test" v-if="!editing" slot="reference"
><i class="fas fa-file-export"></i
></el-button>
</el-popover>
<el-popover
placement="top-start"
title="Description"
width="200"
trigger="hover"
content="Upload Airport"
>
<el-button @click="upload" v-if="!editing" slot="reference"
><i class="fas fa-upload"></i
></el-button>
</el-popover>
</el-row>
<el-row>
<el-col :span="7"><span class="label"> Airlines :</span></el-col>
<el-col :span="15">
<el-tag v-for="item in airlines" :key="item.value">{{
item.value
}}</el-tag>
</el-col>
<el-col :span="2">
<el-button @click="dialogVisible = true" v-if="editing"
><i class="fas fa-plus"></i
></el-button>
</el-col>
</el-row>
</div>
<el-tabs v-model="activeTab" >
<el-tab-pane label="Frequencies" name="first">
<div>
<el-row v-for="f in frequencyList" :key="f.index">
<Frequency :frequency="f"></Frequency>
</el-row>
<el-button @click="addFrequency" v-if="editing" >Add</el-button>
<el-tabs v-model="activeTab">
<el-tab-pane label="Frequencies" name="first">
<div>
<el-row v-for="f in frequencyList" :key="f.index">
<Frequency :frequency="f"></Frequency>
</el-row>
<el-button @click="addFrequency" v-if="editing">Add</el-button>
</div>
</el-tab-pane>
<el-tab-pane label="Parkings" name="second">
<ParkingList></ParkingList>
</el-tab-pane>
<el-tab-pane label="Statistics" name="third">
<el-row><el-col :span="8"><span class="label">Traffic :</span></el-col></el-row>
</el-tab-pane>
<el-tab-pane label="Parkings" name="second">
<ParkingList></ParkingList>
</el-tab-pane>
<el-tab-pane label="Statistics" name="third">
<el-row
><el-col :span="8"
><span class="label">Traffic :</span></el-col
></el-row
>
<el-row>
<el-col :span="8">Flights :</el-col>
<el-col :span="4">{{ flights }}</el-col>
<el-col :span="8"></el-col>
<el-col :span="4"></el-col>
</el-row>
<el-row><el-col :span="16"><span class="label">GIT/Terrasync :</span></el-col></el-row>
<el-row
><el-col :span="16"
><span class="label">GIT/Terrasync :</span></el-col
></el-row
>
<el-row>
<el-col :span="8">Parking Positions :</el-col>
<el-col :span="4">{{ parking }}</el-col>
<el-col :span="8">Groundnet Nodes :</el-col>
<el-col :span="4">{{groundnet}}</el-col>
<el-col :span="4">{{ groundnet }}</el-col>
</el-row>
<el-row><el-col :span="8"><span class="label">Work :</span></el-col></el-row>
<el-row
><el-col :span="8"><span class="label">Work :</span></el-col></el-row
>
<el-row v-if="wip">
<el-col :span="8">Work Parking Positions :</el-col>
<el-col :span="4">{{ wipparking }}</el-col>
<el-col :span="8">Work Groundnet Nodes :</el-col>
<el-col :span="4">{{wipgroundnet}}</el-col>
<el-col :span="4">{{ wipgroundnet }}</el-col>
</el-row>
<el-row v-if="wip">
<el-col :span="4">Saved :</el-col>
<el-col :span="8" class="text">{{date}}</el-col>
<el-col :span="8" class="text">{{ date }}</el-col>
<el-col :span="4">Uploaded :</el-col>
<el-col :span="8" class="text">{{upload_date}}</el-col>
<el-col :span="8" class="text">{{ upload_date }}</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="Traffic" name="fourth">
<TrafficList></TrafficList>
</el-tab-pane>
</el-tabs>
</div>
</template>
@ -149,6 +177,7 @@ You should have received a copy of the GNU General Public License along with FG
import FileSelect from './FileSelect'
import Frequency from './Frequency'
import ParkingList from './ParkingList'
import TrafficList from './TrafficList'
import Upload from './Upload'
const fs = require('fs')
@ -159,7 +188,7 @@ export default {
return {showImportFile: false, activeTab: 'first', editing: false, uploadVisible: false, dialogVisible: false, airlineCode: '', fileImport: null}
},
components: {
EditButton, FileSelect, Frequency, ParkingList, Upload
EditButton, FileSelect, Frequency, ParkingList, TrafficList, Upload
},
methods: {
fileImportFileName (f) {
@ -291,10 +320,10 @@ export default {
</script>
<style lang="scss" scoped>
.el-row {
padding: 0em;
margin-bottom: 5px;
}
.el-row {
padding: 0em;
margin-bottom: 5px;
}
.label {
display: flex;
justify-content: left;

View File

@ -1,3 +1,14 @@
<!--
Copyright 2021 Keith Paterson
This file is part of FG Airports.
FG Airports is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
FG Airports is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with FG Airports. If not, see http://www.gnu.org/licenses/.
-->
<template>
<div>
<el-link v-if="!editing" type="primary" @click="show(parking.index)">{{parking.name}} {{number}} {{parking.type}}</el-link>

View File

@ -0,0 +1,97 @@
<!--
Copyright 2021 Keith Paterson
This file is part of FG Airports.
FG Airports is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
FG Airports is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with FG Airports. If not, see http://www.gnu.org/licenses/.
-->
<template>
<div>
<el-radio-group v-model="direction">
<el-radio :label="departure"
><i class="fas fa-plane-departure"></i
></el-radio>
<el-radio :label="arrival"><i class="fas fa-plane-arrival"></i></el-radio>
</el-radio-group>
<el-collapse v-model="activeName" accordion ref="collapse">
<el-collapse-item
v-for="a in airlines"
v-bind:key="a.index"
class="row"
:title="a.label"
:name="a.label"
>
<AirlineItem :airline="a" ref="airline"></AirlineItem>
</el-collapse-item>
</el-collapse>
<el-popover
placement="top-start"
title="Add Test Traffic"
width="200"
trigger="hover"
content="Generate Testtraffic"
>
<el-button @click="generate" class="button" slot="reference">
<i class="fas fa-notes-medical"></i>
</el-button>
</el-popover>
</div>
</template>
<script lang="js">
import AirlineItem from './AirlineItem'
import {writeTrafficXML} from '../loaders/traffic_writer'
export default {
name: 'traffic-list',
components: {AirlineItem},
props: [],
data () {
return {
activeName: '',
departure: 0,
arrival: 1,
direction: 0,
activeIndex: '1',
activeIndex2: '1'
}
},
methods: {
generate () {
// .filter((f) => f.$children[0])
var aircraft = this.$refs.airline.flatMap((f) => {
console.debug(f.aircraft)
return f.aircraft
}
).filter(f => f)
writeTrafficXML(this.$store.state.Settings.settings.flightgearDirectory_traffic, this.$store.state.Parkings.items, aircraft)
}
},
computed: {
airlines: function () {
var airlineCodes = []
if (this.$store.state.Airports.currentAirport !== undefined && this.$store.state.Airports.currentAirport.airlines) {
var storedairlineCodes = this.$store.state.Airports.currentAirport.airlines
storedairlineCodes.forEach(element => {
airlineCodes.push({value: element, label: element})
})
}
return airlineCodes
}
}
}
</script>
<style>
div.row.div {
display: flex;
justify-content: space-between;
}
</style>

View File

@ -120,7 +120,7 @@ exports.readGroundnetXML = function (fDir, icao, f) {
}).sort();
store.default.dispatch('setParkings', parkingNodes.map(
p => ({index: Number(p.attrs.index), name: String(p.attrs.name), number: String(p.attrs.number), type: String(p.attrs.type)}
p => ({index: Number(p.attrs.index), radius: Number(p.attrs.radius), name: String(p.attrs.name), number: String(p.attrs.number), type: String(p.attrs.type)}
)).sort((p1, p2) => {
if (p1.name === p2.name) {
return p1.number?p1.number.localeCompare(p2.number):-1;

View File

@ -0,0 +1,122 @@
/*
Copyright 2021 Keith Paterson
This file is part of FG Airports.
FG Airports is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
FG Airports is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with FG Airports. If not, see http://www.gnu.org/licenses/.
*/
/* eslint-disable */
const fs = require('fs');
const path = require('path');
var xamel = require('xamel');
const store = require('../store');
const util = require('util');
exports.readTrafficXML = function (f) {
try {
var ret = [];
var xmlTraffic = fs.readFileSync(f, 'utf8').toString();
xamel.parse(xmlTraffic, function (err, xml) {
console.debug("parsed " + path.basename(f));
if (err !== null) {
console.error("Error in " + airline);
throw err;
}
var requiredAircraft = xml.find('trafficlist/aircraft');
console.log("Aircraft " + requiredAircraft.length);
ret.concat(requiredAircraft);
var flights = xml.find('trafficlist/flight');
console.log("Flights " + flights.length);
ret.concat(flights);
console.log(ret.length);
ret = ret.concat(flights.map(flightMapper)).concat(requiredAircraft.map(aircraftMapper))
return ret;
});
return ret;
} catch (error) {
console.error(error);
}
};
/*
* <flight>
<callsign>Hebridean_1047</callsign>
<required-aircraft>HBR_BN_2</required-aircraft>
<fltrules>VFR</fltrules>
<departure>
<port>EGPU</port>
<time>4/14:50:00</time>
</departure>
<cruise-alt>50</cruise-alt>
<arrival>
<port>EGEO</port>
<time>4/15:50:00</time>
</arrival>
<repeat>WEEK</repeat>
</flight>
*/
function flightMapper(params) {
return {
id: `${btoa(buildId(params))}`,
callsign: params.find('callsign').text(),
arrival: {
port: params.find('arrival/port').text(),
time: params.find('arrival/time').text()
},
departure: {
port: params.find('departure/port').text(),
time: params.find('departure/time').text()
}
};
}
function buildId(params) {
return `${params.find('callsign').text()}_`+
`${params.find('arrival/port').text()}_`+
`${params.find('arrival/time').text()}_`+
`${params.find('departure/port').text()}_` +
`${params.find('departure/time').text()}`;
}
/*
<aircraft>
<model>Aircraft/BN-2/BN-2-Hebridean.xml</model>
<livery>HBR</livery>
<airline>HBR</airline>
<home-port>EGEO</home-port>
<required-aircraft>HBR_BN_2</required-aircraft>
<actype>BN2</actype>
<offset>0</offset>
<radius>8</radius>
<flighttype>gate</flighttype>
<performance-class>turboprop_transport</performance-class>
<registration>G-HEBO</registration>
<heavy>false</heavy>
</aircraft>
*/
function aircraftMapper(params) {
return {
model: params.find('model').text(),
livery: params.find('livery').text(),
airline: params.find('airline').text(),
'home-port': params.find('home-port').text(),
'required-aircraft': params.find('required-aircraft').text(),
actype: params.find('actype').text(),
offset: params.find('offset').text(),
radius: params.find('radius').text(),
flighttype: params.find('flighttype').text(),
'performance-class': params.find('performance-class').text(),
'registration': params.find('registration').text(),
'heavy': params.find('heavy').text(),
};
}

View File

@ -0,0 +1,180 @@
/*
Copyright 2021 Keith Paterson
This file is part of FG Airports.
FG Airports is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
FG Airports is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with FG Airports. If not, see http://www.gnu.org/licenses/.
*/
/* eslint-disable */
const fs = require('fs');
const path = require('path');
const store = require('../store');
const util = require('util');
const mathjs = require('mathjs');
var builder = require('xmlbuilder');
var parkingStats = (acc, cur) => {
if (!acc[cur.radius]) {
acc[cur.radius] = { count: 0, radius: cur.radius }
}
acc[cur.radius].count += 1
return acc
};
/**
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<trafficlist xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="traffic.xsd">
<aircraft>
<model>Aircraft/BN-2/BN-2-Hebridean.xml</model>
<livery>HBR</livery>
<airline>HBR</airline>
<home-port>EGEO</home-port>
<required-aircraft>HBR_BN_2</required-aircraft>
<actype>BN2</actype>
<offset>0</offset>
<radius>8</radius>
<flighttype>gate</flighttype>
<performance-class>turboprop_transport</performance-class>
<registration>G-HEBS</registration>
<heavy>false</heavy>
</aircraft>
<flight>
<callsign>Hebridean_1047</callsign>
<required-aircraft>HBR_BN_2</required-aircraft>
<fltrules>VFR</fltrules>
<departure>
<port>EGPU</port>
<time>4/14:50:00</time>
</departure>
<cruise-alt>50</cruise-alt>
<arrival>
<port>EGEO</port>
<time>4/15:50:00</time>
</arrival>
<repeat>WEEK</repeat>
</flight>
</trafficlist>
*/
var writeTrafficXML = function (fDir, parkings, aircraft) {
try {
var icao = store.default.state.Airports.currentAirport.icao;
var aircraftList = aircraft;
try { fs.mkdirSync(path.join(fDir), { recursive: true }) } catch (err) { }
try { fs.mkdirSync(path.join(fDir, 'TST'), { recursive: true }) } catch (err) { }
var f = path.join(fDir, 'TST', icao + '.xml');
var parkingData = parkings.reduce(parkingStats, {});
/*
*
<flight>
<callsign>Hebridean_1001</callsign>
<required-aircraft>HBR_BN_2</required-aircraft>
<fltrules>VFR</fltrules>
<departure>
<port>EGEO</port>
<time>2/14:10:00</time>
</departure>
<cruise-alt>50</cruise-alt>
<arrival>
<port>EGEY</port>
<time>2/15:20:00</time>
</arrival>
<repeat>WEEK</repeat>
</flight>
*/
var flightMapper = function (pStat) {
var ret = [];
var blockSize = Math.min( pStat[1].count/6, 6);
for (let index = 0; index < pStat[1].count; index++) {
var aircraft = this[index];
var minutes = `${Math.floor(index/blockSize)}`.padStart(2, '0');
var seconds = `${index}`.padStart(2, '0');
for (let weekday = 0; weekday < 7; weekday++) {
ret.push({
callsign: `Test_${index}_${weekday}`,
'required-aircraft': aircraft['required-aircraft'],
fltrules: 'VFR',
departure: {
port: icao,
time: `${weekday}/12:${minutes}:${seconds}`
},
'cruise-alt': 50,
arrival: {
port: icao,
time: `${weekday}/13:${minutes}:${seconds}`
},
repeat: 'WEEK'
});
}
}
return ret;
}
/*
<aircraft>
<model>Aircraft/BN-2/BN-2-Hebridean.xml</model>
<livery>HBR</livery>
<airline>HBR</airline>
<home-port>EGEO</home-port>
<required-aircraft>HBR_BN_2</required-aircraft>
<actype>BN2</actype>
<offset>0</offset>
<radius>8</radius>
<flighttype>gate</flighttype>
<performance-class>turboprop_transport</performance-class>
<registration>G-HEBO</registration>
<heavy>false</heavy>
</aircraft>
*/
var aircraftMapper = function (pStat) {
var ret = [];
if (typeof this === 'undefined') {
return;
}
var possibleAircraft = this.filter(a => a.radius <= pStat[1].radius);
for (let index = 0; index < pStat[1].count; index++) {
var aircraft = possibleAircraft[Math.floor(Math.random() * possibleAircraft.length)];
aircraft['required-aircraft'] = `GG-${index}`;
aircraft.registration = `GG-${index}`;
aircraft['home-port'] = icao;
ret.push(aircraft);
}
return ret;
}
var aircraftList = Object.entries(parkingData).flatMap(aircraftMapper, aircraft).sort();
var flightList = Object.entries(parkingData).flatMap(flightMapper, aircraftList).sort();
var xmlObj = { trafficList: { aircraft: aircraftList, flight: flightList } };
var xmlString = builder.create(xmlObj).end({ pretty: true });
fs.writeFileSync(f, xmlString);
console.debug(xmlString);
} catch (error) {
console.error(error);
}
return;
}
export { writeTrafficXML as writeTrafficXML };

View File

@ -43,7 +43,7 @@ const mutations = {
const actions = {
async addParking (context, p) {
context.commit('ADD_FREADD_PARKINGQUENCY', p)
context.commit('ADD_PARKING', p)
},
async updatedParking (context, p) {
context.commit('UPDATE_PARKING', p)