This commit is contained in:
portree_kid 2021-06-28 13:56:05 +02:00
commit cc80726ec7
82 changed files with 7020 additions and 2066 deletions

View File

@ -125,6 +125,18 @@ let rendererConfig = {
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
templateParameters(compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options
},
process,
};
},
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,

View File

@ -97,7 +97,18 @@ let webConfig = {
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
minify: {
templateParameters(compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options
},
process,
};
}, minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true

View File

@ -54,13 +54,9 @@ jobs:
- stage: Deploy
install:
- pwd
- find -name *osx-binaries*
- tar -xzf ${CASHER_DIR}/osx-binaries-fetch.tgz
- find -name flightgear-airports*
# - find -name windows-binaries
# - find -name osx-binaries
# - find -name linux-binaries
# - ls -l | grep "^d"
# - ls -l "C:"
- cp ./Users/travis/build/Portree-Kid/flightgear-airports/osx-binaries/*.dmg build/
- cp ./C:/Users/travis/build/Portree-Kid/flightgear-airports/windows-binaries/*.exe build/
- cp ./linux-binaries/*.AppImage build/
@ -75,12 +71,12 @@ jobs:
skip_cleanup: true
overwrite: true
draft: true
name: '0.0.20'
name: '0.0.33'
provider: releases
api_key:
secure: ECdYNrvCA7+qSnbktJPstwXrluqKZTWDD7K3X/dyoxRO4+ZL/K19r60LXF9g90troWgdZSN3tFxNktkGeIOuTo83Q3B6qjXnKtdW3JhefvztlHQadUIf2p8Ype2spicNAl2629Mwr7ksqJuV0rR0T4zYT1SIFWkuwGYhR+tmS94m6QmnocGPzjset3y23lJ/rczJiiWIkaLrOfeCj5VjWtSaVQEWWlolmuji0qGHvOT/mq1X+jfI9RIlJTpV2ZNYaa3fr+M+8tIreaHDjbKrYAuIA/JzpDxmXtWGnxkxLbDxonp6OhzcJ7z9hpdkg38HKxNTL9Bdv0LpZKCTo2C+0G6yR3kQqXhoIzbfFeTX0G6d7I8oYxL1JGCsrnmU3OyhCLMRcOWL294hhDIUMWveBRNVRGVaOzeXRphO4nnMhGNd4KxwxvRPBMNeguhL/ZBwjcw6puYL3m86UT5iwjMuYvcbEVEDXwHSyi96zjHzolkMUn8hPOIUfgmCOIsqIMMpbCXJqdyha0pu9x1qKrXbF+hQ5JXPvTN8/ZG4dLXhvsZJdD50vZu+GW33vU96c+mwIkCz0CS5t+vuHfdT/OOuBN7HV46q/9WrZMSlz5ZD+A2dY57QVL+CucjMZlVoU/iJhfRpV5XPYvLlYI+tpH0yX5JQqtcRdZJzKsYknwZNj5o=
file_glob: true
file: build/*
on:
branches: '0.0.20'
branches: '0.0.33'
repo: Portree-Kid/flightgear-airports

8
build/installer.nsh Normal file
View File

@ -0,0 +1,8 @@
!macro preInit
SetRegView 64
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES64\flightgear-airports"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES64\flightgear-airports"
SetRegView 32
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES32\flightgear-airports"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES32\flightgear-airports"
!macroend

2044
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,14 @@
{
"name": "flightgear-airports",
"version": "0.0.20",
"version": "0.0.34",
"author": "portree_kid <keith.paterson@gmx.de>",
"description": "An software to design Flightgear groundnets",
"license": "GPL v3",
"license": "GPL-3.0",
"main": "./dist/electron/main.js",
"scripts": {
"build": "node .electron-vue/build.js && electron-builder",
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
"dev": "SET NODE_ENV=development&& node .electron-vue/dev-runner.js",
"e2e": "npm run pack && mocha test/e2e",
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src test",
@ -18,6 +17,7 @@
"pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js",
"pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js",
"test": "npm run unit && npm run e2e",
"mocha": "SET NODE_ENV=mocha&& mocha test/mocha/**/*.js",
"unit": "karma start test/unit/karma.conf.js",
"postinstall": ""
},
@ -70,30 +70,35 @@
]
},
"mac": {
"icon": "build/icons/icon.icns"
"icon": "build/icons/icon.icns",
"publish": []
},
"win": {
"icon": "build/icons/icon.ico"
"icon": "build/icons/icon.ico",
"publish": []
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true
},
"linux": {
"icon": "build/icons",
"target": "AppImage"
"target": "AppImage",
"publish": []
}
},
"dependencies": {
"@turf/intersect": "^6.1.3",
"@turf/turf": "^5.1.6",
"axios": "^0.18.1",
"axios": "^0.21.1",
"coordinate-parser": "^1.0.3",
"dijkstrajs": "^1.0.1",
"electron-debug": "^3.0.1",
"element-ui": "^2.13.2",
"element-ui": "^2.15.1",
"file-url": "^3.0.0",
"fs": "0.0.1-security",
"fs-extra": "^9.0.1",
"geo-coordinates-parser": "^1.2.4",
"geodesy": "^2.2.0",
"idb": "^4.0.5",
@ -101,11 +106,14 @@
"leaflet": "^1.5.1",
"leaflet-editable": "^1.2.0",
"leaflet-polylinedecorator": "^1.6.0",
"leaflet-search": "^2.9.9",
"leaflet-sidebar-v2": "^3.2.1",
"leaflet-textpath": "^1.2.0",
"leaflet.pattern": "^0.1.0",
"lokijs": "^1.5.8",
"mathjs": "^6.2.5",
"path": "^0.12.7",
"tiny-worker": "^2.3.0",
"vue": "^2.5.16",
"vue-electron": "^1.0.6",
"vue-idb": "^0.2.0",
@ -134,7 +142,7 @@
"babel-register": "^6.26.0",
"babili-webpack-plugin": "^0.1.2",
"cfonts": "^2.1.2",
"chai": "^4.1.2",
"chai": "^4.2.0",
"chalk": "^2.4.1",
"copy-webpack-plugin": "^4.5.1",
"cross-env": "^5.1.6",
@ -142,7 +150,7 @@
"del": "^3.0.0",
"devtron": "^1.4.0",
"electron": "^7.2.4",
"electron-builder": "^21.2.0",
"electron-builder": "^22.10.4",
"electron-devtools-installer": "^2.2.4",
"eslint": "^4.19.1",
"eslint-config-standard": "^11.0.0",
@ -168,7 +176,7 @@
"mocha": "^5.2.0",
"multispinner": "^0.2.1",
"node-loader": "^0.6.0",
"node-sass": "^4.9.2",
"node-sass": "^4.14.1",
"require-dir": "^1.0.0",
"sass-loader": "^7.0.3",
"spectron": "^3.8.0",

View File

@ -6,7 +6,7 @@
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
<script>
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, "\\\\") %>')
</script>
<% } %>
</head>

View File

@ -1,6 +1,6 @@
'use strict'
import { app, BrowserWindow } from 'electron'
import { app, BrowserWindow, Menu } from 'electron'
const { ipcMain } = require('electron')
ipcMain.on('OpenDebugger', (event, arg) => {
@ -20,6 +20,7 @@ const winURL = process.env.NODE_ENV === 'development'
: `file://${__dirname}/index.html`
function createWindow () {
Menu.setApplicationMenu(null)
/**
* Initial window options
*/
@ -30,6 +31,7 @@ function createWindow () {
nodeIntegration: true,
nodeIntegrationInWorker: true
},
closable: true,
width: 1000
})
mainWindow.loadURL(winURL)

View File

@ -0,0 +1,67 @@
/*
Copyright 2020 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 */
var L = require('leaflet');
export function checkMapper(o) {
if (o instanceof L.ParkingSpot) {
/*
if( o.box === undefined ) {
debugger;
} */
return {
'index': Number(o['id']),
'_leaflet_id': o._leaflet_id,
'type': 'parking',
'parkingType': o.options.attributes.type,
'name': o.options.attributes.name,
'radius': String(o.options.attributes.radius),
'lat': o._latlng.lat,
'lng': o._latlng.lng,
'box': o.box !== undefined ? o.box.getLatLngs() : null
};
} else if (o instanceof L.RunwayNode) {
console.log(o)
return { 'index': Number(o['glueindex']), '_leaflet_id': o._leaflet_id, 'lat': o._latlng.lat, 'lng': o._latlng.lng, 'type': 'runway' };
} else if (o instanceof L.HoldNode) {
console.log(o)
return { 'index': Number(o['glueindex']), '_leaflet_id': o._leaflet_id, 'type': o.holdPointType };
} else if (o instanceof L.RunwayPolygon) {
return {
'type': 'runway_poly',
'pavement': o.getLatLngs()
}
} else if (o instanceof L.TakeoffPolygon) {
return {
'type': 'takeoffpad_poly',
'pavement': o.getLatLngs()
}
} else if (o instanceof L.Polyline) {
console.log(o)
var latLngs = o.getLatLngs().map(l => ({ lat: l.lat, lng: l.lng, index: l.glueindex }));
if (o.options.attributes===undefined) {
return null;
}
return { 'start': Number(o['begin']), 'end': Number(o['end']), '_leaflet_id': o._leaflet_id, 'type': 'poly', 'direction': o.options.attributes.direction, 'isPushBackRoute': o.options.attributes.isPushBackRoute, latLngs: latLngs };
}
else {
console.log('Unknown Type ')
console.log(typeof o)
}
}
export function groMapper(o) {
if (o instanceof L.Polygon) {
}
}

View File

@ -10,7 +10,9 @@
props: [],
mounted () {
this.aiLayer = aiLayer({url: this.$store.state.Settings.settings.phi_url})
if(this.aiLayer) {
this.aiLayer.addTo(this.$parent.mapObject)
}
},
data () {
return {

View File

@ -0,0 +1,100 @@
<!--
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 :key="airline.label + '-div'">
<div v-for="item in traffic" v-bind:key="airline.label + '-' + item.id + '-innerdiv'">
<div :key="item.id + '-dep'" v-if="direction == 0">{{ item.departure.time }} {{ item.callsign }} {{ item.departure.port }} --> {{ item.arrival.port }} {{ item['required-aircraft'] }} {{ item.flighttype }}</div>
<div :key="item.id + '-arr'" v-if="direction == 1">{{ item.arrival.time }} {{ item.callsign }} {{ item.departure.port }} --> {{ item.arrival.port }} {{ item['required-aircraft'] }} {{ item.flighttype }}</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) {
var aircraftLookup = this.trafficFile.filter(a => a['required-aircraft'])
.reduce((req, acc) => {
req[acc['required-aircraft']] = acc
return req
}, {})
var ret = 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))
.map(obj => ({ ...obj, flighttype: aircraftLookup[obj['required-aircraft']].flighttype }))
.filter((v, i, a) => a.findIndex(t => (t.id === v.id)) === i)
return ret
}
},
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

@ -58,9 +58,12 @@ You should have received a copy of the GNU General Public License along with FG
const $ = require('jquery');
import 'element-ui/lib/theme-chalk/index.css'
import {removeWip} from '../loaders/groundnet_functions'
import Vue from 'vue'
import { EventBus } from './event-bus.js';
export default {
name: 'airport',
components: { },
props: {airport: Object, editing: Boolean},
mounted () {
this.$forceUpdate();
@ -83,7 +86,14 @@ You should have received a copy of the GNU General Public License along with FG
this.$store.dispatch('removeWip', this.airport.icao);
},
upload() {
let airports = this.$store.state.Airports.airports
.filter(a => a.properties.icao.match(this.airport.icao))
if (airports.length > 0) {
this.$store.commit('CENTER', [airports[0].geometry.coordinates[1], airports[0].geometry.coordinates[0]])
}
Vue.set(this.$parent.$parent.$parent, 'uploadVisible', true)
this.$parent.$parent.$parent.$refs.upload.status()
this.$parent.$parent.$parent.$refs.upload.check()
}
},
computed: {

View File

@ -1,30 +1,117 @@
<!--
Copyright 2020 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 v-if="airport">
<Upload :visible.sync="uploadVisible" ref="upload"></Upload>
<el-dialog
title="Add Airline"
:visible.sync="dialogVisible"
width="20%"
:before-close="handleClose">
width="40%"
:before-close="handleClose"
>
<span>Add an selectable airline to {{ icao }} {{ name }}</span>
<el-input
placeholder="Please input airline"
placeholder="Please input airline(s)"
v-model="airlineCode"
maxlength="3"
></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>
</span>
</el-dialog>
<el-dialog
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>
<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-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-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">
@ -40,21 +127,31 @@
<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"
><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-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>
@ -68,28 +165,67 @@
<el-col :span="8" class="text">{{ upload_date }}</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="Traffic" name="fourth">
<TrafficList></TrafficList>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="js">
import EditButton from './EditButton'
import FileSelect from './FileSelect'
import Frequency from './Frequency'
import ParkingList from './ParkingList'
import TrafficList from './TrafficList'
import Upload from './Upload'
const fs = require('fs')
const path = require('path')
export default {
data () {
return {activeTab: 'first', editing: false, dialogVisible: false, airlineCode: ''}
return {showImportFile: false, activeTab: 'first', editing: false, uploadVisible: false, dialogVisible: false, airlineCode: '', fileImport: null}
},
components: {
Frequency, ParkingList
EditButton, FileSelect, Frequency, ParkingList, TrafficList, Upload
},
methods: {
fileImportFileName (f) {
this.fileImport = f
},
edit () {
this.isEditing = true
this.$emit('edit', true)
},
upload () {
this.uploadVisible = true
this.$refs.upload.status()
this.$refs.upload.check()
},
test () {
this.$parent.$parent.$parent.$refs.editLayer.test()
},
importFile () {
this.showImportFile = false
var fDir = this.$store.state.Settings.settings.airportsDirectory
var fNew = path.join(fDir, this.icao[0], this.icao[1], this.icao[2], this.icao + '.groundnet.new.xml')
var editLayer = this.$parent.$parent.$parent.$refs.editLayer
fs.copyFile(this.fileImport.path, fNew, () => {
editLayer.reload(false)
})
},
setEditing (editing) {
this.editing = editing
},
addAirline () {
this.dialogVisible = false
this.$store.dispatch('addAirline', this.airlineCode)
this.airlineCode.split(/[ ,]/).forEach(element => {
if (element.length === 3) {
this.$store.dispatch('addAirline', element)
}
})
},
addFrequency () {
this.$store.dispatch('addFrequency', {type: 'AWOS', value: 0})
@ -108,6 +244,13 @@ export default {
}
},
computed: {
fileImportName: function () {
if (this.fileImport !== null) {
console.log(this.fileImport)
return this.fileImport.path
}
return 'Please select'
},
icao: function () {
return this.$store.state.Airports.currentAirport.icao
},

View File

@ -0,0 +1,106 @@
<!--
Copyright 2020 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 width="100%" v-if="multiarc">
<!--
airlineCodes: 0
heading: 341.34
index: 13
lat: "N59 52.610885"
lon: "W1 17.855144"
name: "Western_Apron_Hanger"
pushBackRoute: 27
radius: 18
type: "gate"
-->
<el-row>
<el-col :span="7">
<span class="demo-input-label">Name :</span>
</el-col>
<el-col :span="15">
<el-input placeholder="Please input" v-model="name"></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<span class="demo-input-label">Pushback :</span>
</el-col>
<el-col :span="15">
<el-switch v-model="isPushback"></el-switch>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<span class="demo-input-label">Direction :</span>
</el-col>
<el-col :span="15">
<el-select v-model="direction" placeholder="Select">
<el-option
v-for="type in options"
:key="type.value"
:label="type.label"
:value="type.value"
></el-option>
</el-select>
</el-col>
</el-row>
</div>
</template>
<script lang="js">
export default {
computed: {
multiarc: function () {
return this.$store.state.Editable.type === 'multiarc'
},
// ga (general aviation), cargo (cargo), gate (commercial passenger traffic),
// mil-fighter (military fighter), mil-cargo (military transport)
options: function () {
return [{value: 'bi-directional', label: 'bi-directional'},
{value: 'forward', label: 'forward'},
{value: 'backward', label: 'backward'}
]
},
name: {
// getter
get: function () {
return this.$store.state.Editable.data.multiarc.name
},
// setter
set: function (newValue) {
this.$store.commit('SET_EDIT_ARC_NAME', newValue)
}
},
isPushback: {
// getter
get: function () {
return this.$store.state.Editable.data.multiarc.isPushBackRoute === '1' ||
Number(this.$store.state.Editable.data.multiarc.isPushBackRoute) === Number(1)
},
// setter
set: function (newValue) {
this.$store.commit('SET_EDIT_PUSHBACK', newValue ? '1' : '0')
}
},
direction: {
// getter
get: function () {
return this.$store.state.Editable.data.multiarc.direction
},
// setter
set: function (newValue) {
this.$store.commit('SET_EDIT_DIRECTION', newValue)
}
}
}
}
</script>

View File

@ -7,10 +7,23 @@
</div>
</h1>
<div id="panel" width="100%">
<el-row v-if="!results || results.length === 0 "><h3>Check not run</h3></el-row>
<el-row v-for="(result,idx) in results" :key="idx">
<el-col :span="2" v-if="result.id<0"><span class="label"><i class="far fa-check-circle"></i></span></el-col>
<el-col :span="2" v-if="result.id==-1"><span class="label"><i class="far fa-check-circle"></i></span></el-col>
<el-col :span="2" v-if="result.id>=0"><span class="label"><i class="fas fa-exclamation-triangle"></i></span></el-col>
<el-col :span="15"><span class="label">{{ result.message }}</span></el-col>
<el-col :span="2" v-if="result.id==-2"><span class="label"><i class="fas fa-exclamation-triangle"></i></span></el-col>
<el-col :span="15">
<el-popover
placement="top-start"
title="Description"
width="200"
trigger="hover"
v-if="result.message"
:content=result.message[1]
>
<span class="label" slot="reference">{{ result.message[0] }}</span>
</el-popover>
</el-col>
<el-col :span="4" v-if="result.id>=0"><el-button v-on:click="show(result.id)" class="button"><i class="fas fa-bullseye"></i></el-button></el-col>
</el-row>
</div>
@ -53,6 +66,9 @@
padding-left: 10px;
padding-right: 10px;
}
h3 {
text-align: center;
}
.label {
display: flex;
justify-content: left;

View File

@ -3,7 +3,7 @@
<div class="select-button">
...
</div>
<input type="file" @change="handleFileChange" webkitdirectory directory/>
<input name="hiddenDir" type="file" v-on:change="handleFileChange($event)" webkitdirectory directory tabindex="-1"/>
</label>
</template>
@ -20,10 +20,16 @@
methods: {
handleFileChange (e) {
try {
if (e.target.files && e.target.files.length>0) {
var first = e.target.files[0].webkitRelativePath.split("/")[0];
var webkitdirectoryPath = e.target.files[0].path.split(first)[0] + first;
this.$emit('input', webkitdirectoryPath)
}
} catch (error) {
console.error(error)
}
}
}
}
</script>
@ -39,6 +45,8 @@
text-align: center;
font-weight: bold;
width: 28px;
height: 28px;
}
.directory-select > input[type="file"] {

View File

@ -11,34 +11,84 @@ You should have received a copy of the GNU General Public License along with FG
-->
<template>
<div id="EditBar">
<Upload :visible.sync="uploadVisible" ref="upload"></Upload>
<ZoomButton icon="fas fa-th" v-on:click="zoomin" :show="true" tooltip="Zoomin"></ZoomButton>
<ZoomButton icon="fas fa-th-large" v-on:click="zoomout" :show="!editing" tooltip="Zoomout"></ZoomButton>
<!--<ZoomButton icon="far fa-eye-slash" v-on:click="hideAPT" :show='true' tooltip="Hide APT"></ZoomButton>-->
<EditButton icon="fas fa-upload" v-on:click="upload" :show="!editing" tooltip="Upload"></EditButton>
<EditButton icon="fas fa-plane" v-on:click="test" :show="!editing" tooltip="Export"></EditButton>
<EditButton icon="fas fa-edit" v-on:click="edit" :show="!editing" tooltip="Edit"></EditButton>
<EditButton
icon="fas fa-undo"
v-on:click="centerDialogVisible = true"
:show="editing"
tooltip="Undo"
></EditButton>
<el-dialog title="Reload" :visible.sync="centerDialogVisible" width="30%" center>
<span style="center">Reload from last save? You will lose the current edits.</span>
<el-dialog
title="Checking"
width="30%"
center
:visible.sync="checkDialogVisible"
>
<el-container direction="vertical">
<el-progress
:percentage="Number(((progress / max) * 100).toPrecision(3))"
v-if="max > 0"
></el-progress>
</el-container>
</el-dialog>
<el-dialog
title="Revert"
:visible.sync="centerDialogVisible"
width="550px"
center
>
<span>
Please select the Version to revert to.
<el-row v-for="item in saves" :key="item.file">
<el-button @click="revert(item.file)">{{item.mtime}}</el-button>
</el-row>
</span>
<span slot="footer" class="dialog-footer">
<el-button @click="undoFirst">Base version (GIT)</el-button>
<el-button type="primary" @click="undoLast">Last save</el-button>
<el-button type="primary" @click="cancel">Cancel</el-button>
</span>
</el-dialog>
<el-dialog title="Saving" :visible.sync="saveDialogVisible" width="30%" center>
<el-dialog
title="Saving"
:visible.sync="saveDialogVisible"
width="30%"
center
>
<span style="center">Saving..</span>
</el-dialog>
<EditButton icon="fas fa-save" v-on:click="save" :show="editing" tooltip="Save"></EditButton>
<EditButton icon="far fa-check-square" v-on:click="showCheck" :show="editing" tooltip="Check"></EditButton>
<ZoomButton
icon="fas fa-th"
v-on:click="zoomin"
:show="true"
tooltip="Zoomin"
></ZoomButton>
<ZoomButton
icon="fas fa-th-large"
v-on:click="zoomout"
:show="!editing"
tooltip="Zoomout"
></ZoomButton>
<EditButton
icon="fa fa-window-close"
v-on:click="close"
:show="editing"
tooltip="Close/Save Editing"
></EditButton>
<EditButton
icon="fas fa-undo"
v-on:click="openReload"
:show="editing"
tooltip="Revert to Savepoint"
></EditButton>
<EditButton
icon="fas fa-save"
v-on:click="save"
:show="editing"
tooltip="Save"
></EditButton>
<EditButton
icon="far fa-check-square"
v-on:click="showCheck"
:show="editing"
tooltip="Check"
></EditButton>
<EditButton
icon="fas fa-draw-polygon"
v-on:click="drawPolyline"
@ -63,38 +113,39 @@ You should have received a copy of the GNU General Public License along with FG
:show="editing"
tooltip="Draw Parking"
></EditButton>
<EditButton icon="fas fa-trash-alt" v-on:click="deleteFeature" :show="editing" tooltip="Remove"></EditButton>
<el-dialog title="Checking" width="30%" center :visible.sync="checkDialogVisible">
<el-container direction="vertical">
<el-progress :percentage="Number(((progress / max)*100).toPrecision(3))" v-if="max>0"></el-progress>
</el-container>
</el-dialog>
<EditButton
icon="fas fa-trash-alt"
v-on:click="deleteFeature"
:show="editing"
tooltip="Remove"
></EditButton>
</div>
</template>
<script lang="js">
/* eslint-disable */
const path = require('path')
const fs = require('fs');
const mapper = require('../check/mapper');
import {listSaves} from '../loaders/groundnet_loader'
import EditButton from './EditButton'
import ZoomButton from './ZoomButton';
import Upload from './Upload'
import Vue from 'vue'
import fileUrl from 'file-url'
const path = require('path')
const fs = require('fs');
export default {
components: { EditButton, Upload, ZoomButton },
components: { EditButton, ZoomButton },
data () {
return {isEditing: false, uploadVisible: false, centerDialogVisible: false, saveDialogVisible: false, checkDialogVisible: false, checking: false, progress: 0, max: 0, pavementLayerVisible: true}
return {isEditing: false, uploadVisible: false, centerDialogVisible: false, saveDialogVisible: false, checkDialogVisible: false, checking: false, progress: 0, max: 0, pavementLayerVisible: true, saves: [] }
},
created () {
},
methods: {
upload() {
this.uploadVisible = true
this.$refs.upload.status()
this.$refs.upload.check()
cancel () {
this.centerDialogVisible = false
},
zoomout() {
this.$parent.$parent.$refs.editLayer.stopDrawing()
@ -110,42 +161,57 @@ You should have received a copy of the GNU General Public License along with FG
},
edit () {
this.isEditing = true
this.$emit('edit')
this.$emit('edit', true)
},
undoFirst () {
setEditing (editing) {
this.isEditing = editing
},
revert (file) {
this.isEditing = false
this.$emit('edit')
this.$emit('edit', false)
this.centerDialogVisible = false
this.$parent.$parent.$refs.map.mapObject.options.minZoom = 1;
this.$parent.$parent.$refs.editLayer.disableEdit()
this.$parent.$parent.$refs.editLayer.reload(true)
this.$parent.$parent.$refs.towerLayer.disableEdit()
this.$parent.$parent.$refs.thresholdLayer.disableEdit()
this.$parent.$parent.$refs.editLayer.reload(file)
},
undoLast () {
this.isEditing = false
this.$emit('edit')
this.centerDialogVisible = false
this.$parent.$parent.$refs.map.mapObject.options.minZoom = 1;
this.$parent.$parent.$refs.editLayer.disableEdit()
this.$parent.$parent.$refs.editLayer.reload(false)
},
save () {
close () {
this.$parent.$parent.$refs.editLayer.stopDrawing()
this.isEditing = false
this.$emit('edit')
this.$emit('edit', false)
this.$parent.$parent.$refs.map.mapObject.options.minZoom = 1;
Vue.set(this, 'saveDialogVisible', true)
this.$emit('edit')
this.$emit('edit', false)
Vue.nextTick( function () {
setTimeout( this.closeDefered.bind(this), 100);
}, this)
},
closeDefered () {
this.$parent.$parent.$refs.editLayer.save()
this.$parent.$parent.$refs.towerLayer.save()
this.$parent.$parent.$refs.thresholdLayer.save()
this.$parent.$parent.$refs.editLayer.disableEdit()
this.$parent.$parent.$refs.towerLayer.disableEdit()
this.$parent.$parent.$refs.thresholdLayer.disableEdit()
this.rescanCurrentGroundnet()
Vue.set(this, 'saveDialogVisible', false)
},
save () {
Vue.set(this, 'saveDialogVisible', true)
this.$parent.$parent.$refs.editLayer.stopDrawing()
Vue.nextTick( function () {
setTimeout( this.saveDefered.bind(this), 100);
}, this)
},
saveDefered () {
this.$parent.$parent.$refs.editLayer.save()
this.$parent.$parent.$refs.editLayer.disableEdit()
this.scanGroundnets()
this.$parent.$parent.$refs.towerLayer.save()
this.$parent.$parent.$refs.thresholdLayer.save()
this.rescanCurrentGroundnet()
Vue.set(this, 'saveDialogVisible', false)
},
scanGroundnets () {
rescanCurrentGroundnet () {
try {
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/src/renderer/utils/worker.js`
@ -188,10 +254,7 @@ You should have received a copy of the GNU General Public License along with FG
view.scanning = Boolean(workery.checking)
workery.view = view
}
}, 1000)
},
test() {
this.$parent.$parent.$refs.editLayer.test()
}, 500)
},
check () {
try {
@ -200,8 +263,25 @@ You should have received a copy of the GNU General Public License along with FG
? `http://localhost:9080/src/renderer/utils/check.js`
: `file://${process.resourcesPath}/workers/check.js`
console.log('make a check worker: ', path.resolve(__dirname, 'check.js'))
if(!this.$parent.$parent.$refs.pavementLayer.pavement) {
this.max = 0
this.checkDialogVisible = false
this.$message({
type: 'Error',
showClose: true,
message: `Check can't run without pavementlayer since runways aren't known. Is the APT file set correctly?`
})
return
}
const worker = new Worker(winURL)
worker.onerror = function(e) {
worker.terminate()
worker.view.max = 0
worker.view.checkDialogVisible = false
e.preventDefault(); // <-- "Hey browser, I handled it!"
}
console.log(fileUrl('src/renderer/utils/check.js'))
worker.checking = this.checking
@ -211,15 +291,27 @@ You should have received a copy of the GNU General Public License along with FG
worker.progress = 0
// var worker = new Worker(fileUrl('src/renderer/utils/worker.js'))
this.worker = worker
var xml = []
var groundnet = []
this.$parent.$parent.$refs.editLayer.groundnetLayerGroup.eachLayer(l => {
console.log(l)
xml.push(l)
groundnet.push(l)
})
var features = groundnet.map(mapper.checkMapper).filter(n => n)
var pavement = []
this.$parent.$parent.$refs.pavementLayer.pavement.eachLayer(l => {
console.log(l)
pavement.push(l)
})
var thresholds = []
this.$parent.$parent.$refs.thresholdLayer.getLayer().eachLayer(l => {
console.log(l)
thresholds.push(l)
})
var pavementFeatures = pavement.map(mapper.checkMapper).filter(n => n)
//TODO
var thresholdFeatures = thresholds.map(mapper.checkMapper).filter(n => n)
var features = xml.map(this.featuresMapper).filter(n => n)
worker.postMessage(['check', features ] )
worker.postMessage(['check', features.concat(pavementFeatures).concat(thresholdFeatures) ] )
this.pollData()
// the reply
var store = this.$store
@ -274,36 +366,11 @@ You should have received a copy of the GNU General Public License along with FG
Vue.set(this, 'checkDialogVisible', true)
this.check()
},
featuresMapper(o) {
if (o instanceof L.ParkingSpot) {
/*
if( o.box === undefined ) {
debugger;
} */
return { 'index': Number(o['id']),
'_leaflet_id': o._leaflet_id,
'type': 'parking',
'parkingType': o.options.attributes.type,
'name': o.options.attributes.name,
'radius': String(o.options.attributes.radius),
'lat': o._latlng.lat,
'lng': o._latlng.lng,
'box': o.box!==undefined?o.box.getLatLngs():null
};
} else if (o instanceof L.RunwayNode) {
console.log(o)
return { 'index': Number(o['glueindex']), '_leaflet_id': o._leaflet_id, 'type': 'runway' };
} else if (o instanceof L.HoldNode) {
console.log(o)
return { 'index': Number(o['glueindex']), '_leaflet_id': o._leaflet_id, 'type': o.holdPointType };
} else if (o instanceof L.Polyline) {
console.log(o)
//_latlngs[""0""].__vertex.glueindex
var latLngs = o.getLatLngs().map(l => ({lat: l.lat, lng: l.lng, index: l.glueindex}));
return { 'start': Number(o['begin']), 'end': Number(o['end']), '_leaflet_id': o._leaflet_id, 'type': 'poly', 'isPushBackRoute': o.options.attributes.isPushBackRoute, latLngs: latLngs };
} else {
console.log('Unknown Type ')
console.log(typeof o)
openReload: function() {
this.centerDialogVisible = true
var icao = this.$parent.$parent.$refs.editLayer.icao
if (icao !== undefined && icao !== '') {
this.saves = listSaves(this.$store.state.Settings.settings.airportsDirectory, icao).sort((a, b) => a.mtimeMs - b.mtimeMs)
}
}
},

View File

@ -22,6 +22,7 @@ You should have received a copy of the GNU General Public License along with FG
import L2 from 'leaflet-textpath'
import Vue from 'vue'
import { MessageBox } from 'element-ui';
const turf = require('@turf/turf')
@ -38,18 +39,17 @@ You should have received a copy of the GNU General Public License along with FG
components: {},
props: [],
created () {
console.log(LMap)
console.log(LMarker)
console.log(L)
console.log(LEdit)
console.log(L2)
console.log('Created Editlayer')
[LMap, LMarker, L, LEdit, L2]
console.debug('Created Editlayer')
// console.log(LSymbol)
},
mounted () {
this.selectionLayerGroup = L.layerGroup();
this.selectionLayerGroup.addTo(this.$parent.mapObject)
this.$parent.mapObject.createPane('pushback-pane')
this.$parent.mapObject.getPane('pushback-pane').style.zIndex = 512
this.$parent.mapObject.createPane('route-pane')
this.$parent.mapObject.getPane('route-pane').style.zIndex = 511
this.$store.watch(
function (state) {
return state.Editable.data.node;
@ -70,6 +70,16 @@ You should have received a copy of the GNU General Public License along with FG
deep: true //add this if u need to watch object properties change etc.
}
);
this.$store.watch(
function (state) {
return state.Editable.data.multiarc;
},
() => { this.editedMultiArc() }
,
{
deep: true //add this if u need to watch object properties change etc.
}
);
this.$store.watch(
function (state) {
return state.Editable.data.parking;
@ -96,10 +106,16 @@ You should have received a copy of the GNU General Public License along with FG
},
data () {
return {
maxId: 1, icao: String, checking: false, editing: false
maxId: 1, icao: '', checking: false, editing: false
}
},
methods: {
getLayer () {
return this.groundnetLayerGroup;
},
getIdLayerGroup() {
return this.idLayerGroup;
},
getParkings(ring) {
var poly = turf.polygon(ring);
var parkings = []
@ -115,37 +131,46 @@ You should have received a copy of the GNU General Public License along with FG
this.selection = parkings;
return parkings;
},
load (icao, force) {
load (icao, filename) {
if (this.groundnetLayerGroup !== undefined) {
this.groundnetLayerGroup.removeFrom(this.$parent.mapObject)
}
this.$parent.$parent.setIcao(icao)
this.icao = icao
this.groundnetLayerGroup = readGroundnetXML(this.$store.state.Settings.settings.airportsDirectory, icao, force)
var f = '';
if (!filename) {
var f = path.join(this.$store.state.Settings.settings.airportsDirectory, icao[0], icao[1], icao[2], icao + '.groundnet.new.xml')
if (!fs.existsSync(f)) {
f = path.join(this.$store.state.Settings.settings.airportsDirectory, icao[0], icao[1], icao[2], icao + '.groundnet.xml')
}
} else {
f = path.join(this.$store.state.Settings.settings.airportsDirectory, icao[0], icao[1], icao[2], filename)
}
console.info(`Reload from : ${f}`)
this.groundnetLayerGroup = readGroundnetXML(this.$store.state.Settings.settings.airportsDirectory, icao, f)
if (this.groundnetLayerGroup === undefined) {
console.error('ICAO not loaded ' + icao)
console.warn('Groundnet for ICAO not loaded ' + icao)
return
}
if (this.groundnetLayerGroup.getLayers().length === 0) {
console.warn('Groundnet for ICAO not loaded ' + icao)
}
this.groundnetLayerGroup.eachLayer(l => {
if (l instanceof L.TaxiwaySegment) {
l.addListeners()
}
})
/*
this.groundnetLayerGroup.eachLayer(l => {
if (l instanceof L.TaxiwaySegment) {
var decorator = L.polylineDecorator(l, {
pattern: [
// defines a pattern of 10px-wide dashes, repeated every 20px on the line
{offset: 5, repeat: 50, symbol: L.Symbol.arrowHead({pixelSize: 15, pathOptions: {fillOpacity: 1, weight: 0}})}
]
})
decorator.addTo(this.$parent.mapObject)
if (l.updateArrows !== undefined) {
l.updateArrows(this.$store.state.Settings.zoom)
}
if (typeof l.setInteractive === 'function') {
l.setInteractive(false)
}
})
*/
console.log(this.groundnetLayerGroup.maxId)
})
console.debug(`MaxId : ${this.groundnetLayerGroup.maxId}`)
this.buildLookup()
this.groundnetLayerGroup.addTo(this.$parent.mapObject)
this.icao = icao
@ -191,6 +216,11 @@ You should have received a copy of the GNU General Public License along with FG
element.updateRadius(event.wingspan/2)
});
break;
case 'parking-group-type':
this.selection.forEach(element => {
element.updateType(event.parking_type)
});
break;
default:
break;
}
@ -202,9 +232,11 @@ You should have received a copy of the GNU General Public License along with FG
this.$store.commit('SET_EDIT', true)
this.featureLookup = [];
if(!this.groundnetLayerGroup) {
return;
}
this.groundnetLayerGroup.eachLayer(l => {
l.enableEdit()
l.featureLookup = this.featureLookup;
if (typeof l.extensions === 'function') {
l.extensions(this)
@ -212,13 +244,65 @@ You should have received a copy of the GNU General Public License along with FG
if (typeof l.bringToFront === 'function') {
l.bringToFront()
}
if (typeof l.updateStyle === 'function') {
l.updateStyle()
}
if (typeof l.setInteractive === 'function') {
l.setInteractive(true)
}
})
this.$store.dispatch('addWip', {icao: this.icao}); },
this.$store.dispatch('addWip', {icao: this.icao});
},
showTooltips() {
this.groundnetLayerGroup.eachLayer(l => {
if (l instanceof L.Polyline) {
l.getLatLngs().forEach(l => {
if (this.$parent.mapObject.getBounds().contains(l)) {
if (l.__vertex && !l.__vertex.getTooltip()) {
l.__vertex.bindTooltip(l.glueindex, {permanent: true});
}
}
});
}
if (l instanceof L.ParkingSpot) {
if (this.$parent.mapObject.getBounds().contains(l.getLatLng())) {
var parkingHub = l.glueindex + " " + l.options.attributes.name + " " + l.options.attributes.number;
if(l.box) {
l.box.bindTooltip(parkingHub, {permanent: true, direction: 'right'});
} else {
l.bindTooltip(parkingHub, {permanent: true, direction: 'right'});
}
}
}
});
setTimeout(this.closeTooltips.bind(this), 2000);
},
closeTooltips() {
this.groundnetLayerGroup.eachLayer(l => {
if (l instanceof L.Polyline) {
l.getLatLngs().forEach(l => {
if (l.__vertex && l.__vertex.getTooltip()) {
l.__vertex.unbindTooltip();
}
});
}
if (l instanceof L.ParkingSpot) {
if(l.box) {
l.box.unbindTooltip();
} else {
l.unbindTooltip();
}
}
});
},
disableEdit () {
this.editable = false
this.editing = false
this.$store.commit('SET_EDIT', false)
this.groundnetLayerGroup.eachLayer(l => {
if (typeof l.setInteractive === 'function') {
l.setInteractive(false)
}
l.disableEdit()
})
},
@ -240,6 +324,9 @@ You should have received a copy of the GNU General Public License along with FG
console.log('Remove : ' + this.$store.state.Editable.type)
}
},
isOnRunway(latlng) {
return this.$parent.$parent.$refs.pavementLayer.isOnRunway(latlng)
},
findRouteToPushback (index) {
if (this.featureLookup===undefined || this.featureLookup[index]===undefined) {
return
@ -271,7 +358,7 @@ You should have received a copy of the GNU General Public License along with FG
});
},
removeArc (arc) {
console.log(arc);
console.debug('Remove Arc : ' + arc);
var arcLayer = this.groundnetLayerGroup.getLayer(this.$store.state.Editable.index);
arcLayer.removeFrom(this.groundnetLayerGroup);
},
@ -291,8 +378,23 @@ You should have received a copy of the GNU General Public License along with FG
return;
}
if (this.featureLookup===undefined || this.featureLookup[index]===undefined) {
var found = false;
this.groundnetLayerGroup.eachLayer((layer) => {
if (layer instanceof L.Polyline && layer._leaflet_id == index) {
layer.select();
this.$store.dispatch('setCenter', layer.getCenter());
found = true;
} else {
layer.deselect();
}
});
if (found) {
return;
} else {
console.error("Lookup " + index + " failed ");
this.buildLookup()
this.buildLookup();
return;
}
}
if (Number(this.$store.state.Editable.index) >= 0 &&
this.featureLookup[this.$store.state.Editable.index]!==undefined) {
@ -304,8 +406,7 @@ You should have received a copy of the GNU General Public License along with FG
this.featureLookup[index].forEach((element, i) => {
if (element instanceof L.Polyline) {
element._latlngs.forEach((e1, index1) => {
console.log(e1);
if (e1.attributes.index===index) {
if (e1.attributes.index===Number(index)) {
var latlng = {};
latlng.lat = e1.lat;
latlng.lng = e1.lng;
@ -453,17 +554,6 @@ You should have received a copy of the GNU General Public License along with FG
}
});
},
/*
getParkings (){
var parkings = []
this.groundnetLayerGroup.eachLayer(l => {
if (l instanceof L.ParkingSpot) {
parkings.push(l)
}
})
return parkings
},
*/
refreshLookup(index) {
//element.__vertex
this.featureLookup[index] = this.featureLookup[index].filter(item => {
@ -485,9 +575,10 @@ You should have received a copy of the GNU General Public License along with FG
try {
this.featureLookup[index].forEach((element, i) => {
if (element instanceof L.Polyline) {
console.log('Poly : ' + i + ' ' + element.attributes);
console.debug('Poly : ' + i + ' ' + element.attributes);
// Complete poly with be removed
if ( element._latlngs.length <= 3 ) {
console.debug('Remove short ' + element);
if(Number(element.begin) !== index) {
this.featureLookup[Number(element.begin)] = this.featureLookup[Number(element.begin)].filter(item => item !== element);
this.refreshLookup(Number(element.begin))
@ -497,10 +588,11 @@ You should have received a copy of the GNU General Public License along with FG
this.refreshLookup(Number(element.end))
}
element.removeFrom(this.groundnetLayerGroup);
element.removeFrom(this.$parent.mapObject);
}
else {
element.getLatLngs().forEach((e1, index1) => {
console.log(index1 + ' ' + e1);
console.debug('Remove Long' + index1 + ' ' + e1);
if (e1.attributes.index===index) {
var splitOffNodes = element.getLatLngs().splice(index1);
element.editor.refresh();
@ -574,7 +666,7 @@ You should have received a copy of the GNU General Public License along with FG
polyLine.addListeners()
polyLine.on('editable:drawing:end', event => {
console.log(event)
console.debug(event)
event.target.addTo(this.groundnetLayerGroup)
})
},
@ -592,10 +684,10 @@ You should have received a copy of the GNU General Public License along with FG
polyLine.addListeners()
polyLine.on('editable:drawing:end', event => {
console.log(event)
console.debug(event)
event.target.addTo(this.groundnetLayerGroup)
var pt = event.sourceTarget._latlngs[event.sourceTarget._latlngs.length-1];
pt.attributes.holdPointType = 'PushBack'
pt.attributes['holdPointType'] = 'PushBack'
var nIndex = pt.attributes.index
var fa_icon = "<div style='background-color:#4838cc;' class='marker-pin'></div><i class='fas fa-arrows-alt-h'></i>";
const icon = new L.DivIcon({
@ -608,6 +700,7 @@ You should have received a copy of the GNU General Public License along with FG
node.glueindex = nIndex;
node.addTo(this.groundnetLayerGroup);
node.featureLookup = this.featureLookup;
node['holdPointType'] = 'PushBack'
this.featureLookup[nIndex].push(node);
node.addListeners();
node.extensions();
@ -619,7 +712,7 @@ You should have received a copy of the GNU General Public License along with FG
this.featureLookup===undefined) {
return
}
console.log('Edited Parking : ' + this.$store.state.Editable.data.parking)
console.debug('Edited Parking : ' + this.$store.state.Editable.data.parking)
//Notify list
if (this.featureLookup[this.$store.state.Editable.index]===undefined) {
return
@ -638,18 +731,18 @@ You should have received a copy of the GNU General Public License along with FG
},
editedParkings() {
if (this.featureLookup===undefined) {
console.warn("Lookup undefinded");
console.warn("Lookup undefined");
this.buildLookup()
}
if (this.featureLookup===undefined) {
return
}
console.log('Edited Parkings : ' + this.$store.state.Parkings.items)
console.debug('Edited Parkings : ' + this.$store.state.Parkings.items)
this.$store.state.Parkings.items.forEach( newElement => {
console.debug(newElement);
if(this.featureLookup[newElement.index]) {
this.featureLookup[newElement.index].forEach((element,index) => {
if (element instanceof L.ParkingSpot) {
console.debug(element);
element.options.attributes.name = newElement.name
element.options.attributes.number = newElement.number
element.options.attributes.type = newElement.type
@ -677,6 +770,7 @@ You should have received a copy of the GNU General Public License along with FG
!this.editing) {
return;
}
console.debug("Edit Type : " + this.$store.state.type);
var arc = this.groundnetLayerGroup.getLayer(this.$store.state.Editable.index);
if (arc && arc instanceof L.Polyline) {
console.log('Edited Arc : ' + this.$store.state.Editable.index);
@ -684,6 +778,27 @@ You should have received a copy of the GNU General Public License along with FG
arc.updateStyle();
}
},
editedMultiArc() {
if (!this.groundnetLayerGroup ||
this.$store.state.Editable.data.multiarc === undefined ||
this.$store.state.Editable.data.multiarc.ids === undefined ||
this.featureLookup===undefined ||
!this.editing) {
return;
}
console.debug("Edit Type : " + this.$store.state.Editable.data.multiarc.ids + ' ' + String(this.$store.state.Editable.data.multiarc.direction));
this.$store.state.Editable.data.multiarc.ids.forEach(id => {
console.debug(id);
var arc = this.groundnetLayerGroup.getLayer(id);
if (arc && arc instanceof L.Polyline) {
console.log('Edited Arc : ' + this.$store.state.Editable.index);
arc.options.attributes.direction = String(this.$store.state.Editable.data.multiarc.direction)
arc.options.attributes.name = String(this.$store.state.Editable.data.multiarc.name)
arc.options.attributes.isPushBackRoute = Number(this.$store.state.Editable.data.multiarc.isPushBackRoute)
arc.updateStyle();
}
});
},
//Update Node
editedNode() {
if (this.$store.state.Editable.index === undefined ||
@ -757,20 +872,7 @@ You should have received a copy of the GNU General Public License along with FG
})
if (!hasRunwayNode && isOnRunway && latlng !== undefined) {
this.$store.state.Editable.data.node.holdPointType
const icon = new L.DivIcon({
className: 'custom-div-icon',
html: "<div style='background-color:#4838cc;' class='marker-pin'></div><i class='fas fa-plane-departure'></i>",
iconSize: [30, 42],
iconAnchor: [15, 42]
});
const node = new L.RunwayNode(latlng, { icon: icon });
node.glueindex = nIndex;
node.addTo(this.groundnetLayerGroup);
this.featureLookup[nIndex].push(node);
node.featureLookup = this.featureLookup;
node.addListeners();
node.extensions();
this.addRunwayNode(latlng, nIndex)
}
if (!hasHoldPointNode && isHoldPoint) {
var fa_icon = null;
@ -795,6 +897,21 @@ You should have received a copy of the GNU General Public License along with FG
node.extensions();
}
},
addRunwayNode (latlng, nIndex) {
const icon = new L.DivIcon({
className: 'custom-div-icon',
html: "<div style='background-color:#4838cc;' class='marker-pin'></div><i class='fas fa-plane-departure'></i>",
iconSize: [30, 42],
iconAnchor: [15, 42]
});
const node = new L.RunwayNode(latlng, { icon: icon });
node.glueindex = nIndex;
node.addTo(this.groundnetLayerGroup);
this.featureLookup[nIndex].push(node);
node.featureLookup = this.featureLookup;
node.addListeners();
node.extensions();
},
// Finde nearest node
closestLayerSnap (eventLatlng, snap) {
var layers = []
@ -807,9 +924,11 @@ You should have received a copy of the GNU General Public License along with FG
console.warn('No glueindex : ' + latlng.__vertex);
}
let distance = latlng.distanceTo(eventLatlng)
if (distance > 0 && distance < snap) {
if (distance >= 0 && distance < snap && latlng.glueindex >=0) {
layers.push({d: distance, l: layer, latlng: latlng.__vertex.latlng, glueindex: latlng.glueindex})
}
} else {
console.log(latlng);
}
})
} else if (layer instanceof L.RunwayNode) {
@ -841,11 +960,11 @@ You should have received a copy of the GNU General Public License along with FG
this.$parent.mapObject.on('click', this.addParking)
},
removeLayerClick (event) {
console.log(event)
console.debug(event)
this.groundnetLayerGroup.removeLayer(event.target)
},
addParking (event) {
console.log(event.latlng)
console.debug(event.latlng)
if (event.latlng === undefined) {
return
}
@ -855,9 +974,12 @@ You should have received a copy of the GNU General Public License along with FG
circle.glueindex = circle.id
circle.addTo(this.groundnetLayerGroup)
circle.featureLookup = this.featureLookup
circle.addListeners()
circle.enableEdit()
circle.extensions()
circle.addListeners()
circle.updateVertexFromDirection();
circle.updateWheelPos();
circle.updateBox();
if (Number(this.$store.state.Editable.index) >= 0 &&
this.featureLookup[this.$store.state.Editable.index]!==undefined) {
this.featureLookup[this.$store.state.Editable.index].forEach(element => {
@ -874,8 +996,8 @@ You should have received a copy of the GNU General Public License along with FG
this.$parent.mapObject.off('click', this.addParking)
this.$parent.mapObject._container.style.cursor = ''
},
reload (force) {
this.load(this.icao, force)
reload (filename) {
this.load(this.icao, filename)
},
link (index) {
var layers = []
@ -913,9 +1035,11 @@ You should have received a copy of the GNU General Public License along with FG
console.warn('No glueindex : ' + latlng.__vertex);
}
let distance = latlng.distanceTo(centerLatLng)
if (latlng.glueindex !== newIndex && distance < 10) {
if (Number(latlng.glueindex) !== Number(newIndex) && distance < 10) {
nearest.push({d: distance, l: layer, latlng: latlng.__vertex.latlng, glueindex: latlng.glueindex })
}
} else {
console.error("No __Vertex", latlng);
}
})
}
@ -964,11 +1088,10 @@ You should have received a copy of the GNU General Public License along with FG
save () {
var xml = []
this.groundnetLayerGroup.eachLayer(l => {
console.log(l)
//console.debug(l)
xml.push(l)
})
writeGroundnetXML(this.$store.state.Settings.settings.airportsDirectory, this.icao, xml)
this.load(this.icao, false)
},
//Copy to test directory
test() {
@ -979,11 +1102,18 @@ You should have received a copy of the GNU General Public License along with FG
try { fs.mkdirSync(path.join(this.$store.state.Settings.settings.testDirectory, 'Airports', this.icao[0], this.icao[1]), { recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(this.$store.state.Settings.settings.testDirectory, 'Airports', this.icao[0], this.icao[1], this.icao[2]), { recursive: true })} catch (err) { }
try {
fs.copyFileSync(f, fNew)
this.$message({
type: 'info',
message: `Copied to ${fNew}`
});
} catch (error) {
this.$message({
type: 'error',
message: `Copy error : ${error}`
});
}
},
setVisible(visible) {
if (this.layerGroup) {

View File

@ -1,13 +1,11 @@
<template>
<label class="file-select">
<div class="select-button">
<span v-if="value">Selected File: {{value.name}}</span>
<span v-else>Select File</span>
...
</div>
<input type="file" @change="handleFileChange"/>
</label>
</template>
<script>
export default {
props: {

View File

@ -28,10 +28,8 @@ You should have received a copy of the GNU General Public License along with FG
</l-control>
-->
<!--<l-marker :lat-lng="marker"></l-marker>-->
<LeafletSidebar ref="sidebar" @edit="onEditSidebar"></LeafletSidebar>
<LeafletSidebar ref="sidebar" @editParking="onEditSidebar" @edit="onEdit($event)"></LeafletSidebar>
<AiLayer ref="aiLayer"></AiLayer>
<PavementLayer ref="pavementLayer"></PavementLayer>
<ThresholdLayer ref="thresholdLayer"></ThresholdLayer>
<l-layer-group layerType="overlay" name="airports" ref="airportLayer">
<l-circle
v-for="(item, index) in this.$store.state.Airports.airports"
@ -44,31 +42,36 @@ You should have received a copy of the GNU General Public License along with FG
></l-circle>
</l-layer-group>
<EditLayer ref="editLayer"></EditLayer>
<PavementLayer ref="pavementLayer"></PavementLayer>
<ThresholdLayer ref="thresholdLayer"></ThresholdLayer>
<TowerLayer ref="towerLayer"></TowerLayer>
<ToolLayer ref="toolLayer" @select-poly="onSelectedPolygon"></ToolLayer>
<EditBar ref="editBar" @edit="onEdit"></EditBar>
<EditBar ref="editBar" @edit="onEdit($event)"></EditBar>
<ToolBar ref="toolBar"></ToolBar>
</l-map>
</template>
<script lang="js">
import 'leaflet/dist/leaflet.css'
import 'leaflet-search/dist/leaflet-search.src.css'
import '@/assets/button.css'
import { LMap, LTileLayer, LMarker, LCircle, LLayerGroup, LControl } from 'vue2-leaflet'
import { LMap, LTileLayer, LMarker, LCircle, LLayerGroup, LControl, LTooltip } from 'vue2-leaflet'
import LeafletSidebar from './LeafletSidebar'
import AiLayer from './AiLayer'
import EditBar from './EditBar'
import ToolBar from './ToolBar'
import EditLayer from './EditLayer'
import ToolLayer from './ToolLayer'
import TowerLayer from './TowerLayer'
import PavementLayer from './PavementLayer'
import ThresholdLayer from './ThresholdLayer'
import { Loading } from 'element-ui'
import L from 'leaflet'
import { LeafletSearch } from 'leaflet-search'
// https://github.com/KoRiGaN/Vue2Leaflet/issues/103
delete L.Icon.Default.prototype._getIconUrl
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
@ -76,8 +79,76 @@ You should have received a copy of the GNU General Public License along with FG
})
export default {
name: 'flightgear-map',
components: { LMap, LTileLayer, LMarker, LCircle, LeafletSidebar, AiLayer, EditBar, ToolBar, EditLayer, PavementLayer, LLayerGroup, LControl, ThresholdLayer, ToolLayer },
components: { LMap, LTileLayer, LMarker, LCircle, LTooltip, LeafletSidebar, AiLayer, EditBar, ToolBar, EditLayer, TowerLayer, PavementLayer, LLayerGroup, LControl, ThresholdLayer, ToolLayer, LeafletSearch },
props: [],
created () {
this.loadingInstance = null
this.$store.watch(
function (state) {
return state.Loading.icao
},
(newValue, oldValue) => {
// debugger
console.log('setIcaoLoading ' + oldValue + ' => ' + newValue + ' groundnetLoaded ' + this.groundnetLoaded + ' pavementLoaded ' + this.pavementLoaded + ' ' + this.loadingInstance)
if (newValue !== oldValue && newValue !== '') {
this.groundnetLoaded = newValue
if ((this.loadingInstance === null || this.loadingInstance === undefined)) {
this.loadingInstance = Loading.service({ fullscreen: false })
}
}
}
,
{
deep: false,
immediate: true
}
)
this.$store.watch(
function (state) {
return state.Loading.groundnetLoaded
},
(newValue, oldValue) => {
// debugger
console.log('groundnetLoaded ' + oldValue + ' => ' + newValue + ' groundnetLoaded ' + this.groundnetLoaded + ' pavementLoaded ' + this.pavementLoaded + ' ' + this.loadingInstance)
if (newValue !== oldValue) {
this.groundnetLoaded = newValue
if (this.groundnetLoaded &&
this.pavementLoaded &&
this.loadingInstance !== null) {
this.loadingInstance.close()
this.loadingInstance = null
}
}
}
,
{
deep: false,
immediate: true
}
)
this.$store.watch(
function (state) {
return state.Loading.pavementLoaded
},
(newValue, oldValue) => {
console.log('pavementLoaded ' + oldValue + ' => ' + newValue + ' ' + this.groundnetLoaded + ' ' + this.pavementLoaded + ' ' + this.loadingInstance)
if (newValue !== oldValue) {
this.pavementLoaded = newValue
if (this.groundnetLoaded &&
this.pavementLoaded &&
this.loadingInstance !== null) {
this.loadingInstance.close()
this.loadingInstance = null
}
}
}
,
{
deep: false,
immediate: true
}
)
},
mounted () {
this.$store.dispatch('getAirports')
this.$store.subscribe((mutation, state) => {
@ -88,13 +159,14 @@ You should have received a copy of the GNU General Public License along with FG
.filter(feature => this.visible(feature))
.map(feature => feature.properties.icao)
if (airportsToLoad.length > 0 && airportsToLoad[0] !== this.editingAirport && this.zoom > 12) {
let loadingInstance = Loading.service({ fullscreen: true })
this.$store.dispatch('setIcaoLoading', airportsToLoad[0])
this.$nextTick(() => { // Loading should be closed asynchronously
this.$refs.pavementLayer.load(airportsToLoad[0])
this.$refs.editLayer.load(airportsToLoad[0])
this.$refs.thresholdLayer.load(airportsToLoad[0])
loadingInstance.close()
if (this.$refs.towerLayer) {
this.$refs.towerLayer.load(airportsToLoad[0])
}
this.editingAirport = airportsToLoad[0]
})
}
@ -104,13 +176,15 @@ You should have received a copy of the GNU General Public License along with FG
if (this.$refs.airportLayer) {
this.$refs.airportLayer.setVisible(this.zoom < 12)
}
// console.log(this.groundnet)
}
})
},
data () {
return {
loadingInstance: Object,
groundnetLoaded: false,
pavementLoaded: false,
url: 'https://a.tile.openstreetmap.de/{z}/{x}/{y}.png',
attribution: '<A href="https://github.com/Portree-Kid/flightgear-airports" target="_blank">Flightgear Airports ' + require('electron').remote.app.getVersion() +
'</A> <A href="https://www.electronjs.org/" target="_blank">Electron</A> ' +
@ -124,7 +198,6 @@ You should have received a copy of the GNU General Public License along with FG
},
methods: {
ready (e) {
console.log(e)
e.on('layeradd', this.onLayerAdd)
},
onLayerAdd (e) {
@ -140,15 +213,45 @@ You should have received a copy of the GNU General Public License along with FG
if (this.$refs.pavementLayer.getLayer() === e.layer) {
// debugger
var l = this.layersControl._layers.filter(l => l.name === 'APT Layer')
if (l.length > 0 && l[0].layer !== this.$refs.pavementLayer.getLayer()) {
this.layersControl.removeLayer(l[0].layer)
this.layersControl.addOverlay(this.$refs.pavementLayer.getLayer(), 'APT Layer')
}
if (l.length === 0) {
this.layersControl.addOverlay(this.$refs.pavementLayer.getLayer(), 'APT Layer')
}
}
if (this.$refs.thresholdLayer.getLayer() === e.layer) {
if (this.$refs.thresholdLayer !== undefined && this.$refs.thresholdLayer.getLayer() === e.layer) {
l = this.layersControl._layers.filter(l => l.name === 'Threshold Layer')
if (l.length > 0 && l[0].layer !== this.$refs.thresholdLayer.getLayer()) {
this.layersControl.removeLayer(l[0].layer)
this.layersControl.addOverlay(this.$refs.thresholdLayer.getLayer(), 'Threshold Layer')
}
if (l.length === 0) {
this.layersControl.addOverlay(this.$refs.thresholdLayer.getLayer(), 'Threshold Layer')
}
this.$refs.thresholdLayer.zoomUpdated()
}
if (this.$refs.towerLayer !== undefined && this.$refs.towerLayer.getLayer() === e.layer) {
l = this.layersControl._layers.filter(l => l.name === 'Tower Layer')
if (l.length > 0 && l[0].layer !== this.$refs.towerLayer.getLayer()) {
this.layersControl.removeLayer(l[0].layer)
this.layersControl.addOverlay(this.$refs.towerLayer.getLayer(), 'Tower Layer')
}
if (l.length === 0) {
this.layersControl.addOverlay(this.$refs.towerLayer.getLayer(), 'Tower Layer')
}
this.$refs.towerLayer.zoomUpdated()
}
if (this.$refs.editLayer !== undefined && this.searchControl === undefined && this.$refs.editLayer.getLayer() === e.layer) {
this.searchControl = new L.Control.Search({
layer: this.$refs.editLayer.getLayer(),
position: 'topleft',
propertyName: 'searchTerm',
marker: {animate: false},
initial: false
})
this.searchControl.addTo(this.$refs.map.mapObject)
}
},
onSelectedPolygon (ring) {
@ -160,10 +263,17 @@ You should have received a copy of the GNU General Public License along with FG
this.$refs.sidebar.setData(parkings)
},
onEdit (event) {
if (event) {
this.$refs.map.mapObject.options.minZoom = 13
} else {
this.$refs.map.mapObject.options.minZoom = 1
}
this.$refs.editLayer.enableEdit()
this.$refs.toolBar.setEdit(this.$refs.editBar.isEditing)
this.$refs.sidebar.setEditing(this.$refs.editBar.isEditing)
this.$refs.towerLayer.enableEdit()
this.$refs.thresholdLayer.enableEdit()
this.$refs.editBar.setEditing(event)
this.$refs.toolBar.setEditing(event)
this.$refs.sidebar.setEditing(event)
},
onEditSidebar (event) {
this.$refs.editLayer.onEdit(event)
@ -226,6 +336,7 @@ You should have received a copy of the GNU General Public License along with FG
event.target.airport = item
// console.log(event, item)
this.normalStyle(event.target)
event.target.bindTooltip(event.target.airport.properties.icao + ' ' + event.target.airport.properties.name)
},
onClick (event, item) {
console.log(item)
@ -244,13 +355,38 @@ You should have received a copy of the GNU General Public License along with FG
if (zoom !== this.$store.state.Settings.zoom) {
this.$store.dispatch('setZoom', zoom)
this.$refs.airportLayer.setVisible(zoom < 12)
if (this.$refs.towerLayer) {
this.$refs.towerLayer.setVisible(this.zoom >= 12)
}
if (this.$refs.thresholdLayer) {
this.$refs.thresholdLayer.setVisible(this.zoom >= 12)
}
this.$refs.pavementLayer.setVisible(zoom >= 12)
}
if (this.$refs.editLayer.groundnetLayerGroup) {
this.$refs.editLayer.groundnetLayerGroup.eachLayer(function (layer) {
if (layer.updateArrows !== undefined) {
layer.updateArrows(zoom)
}
})
}
if (this.$refs.thresholdLayer) {
this.$refs.thresholdLayer.zoomUpdated()
}
if (this.$refs.towerLayer) {
this.$refs.towerLayer.zoomUpdated()
}
},
async centerUpdated (center) {
if (center !== this.$store.state.Settings.center) {
this.$store.dispatch('setCenter', center)
this.$store.dispatch('setCenter', {lat: Number(center.lat), lng: Number(center.lng)})
this.$refs.airportLayer.setVisible(this.zoom < 12)
if (this.$refs.thresholdLayer) {
this.$refs.thresholdLayer.setVisible(this.zoom >= 12)
}
if (this.$refs.towerLayer) {
this.$refs.towerLayer.setVisible(this.zoom >= 12)
}
this.$refs.pavementLayer.setVisible(this.zoom >= 12)
}
},

View File

@ -1,7 +1,18 @@
<!--
Copyright 2020 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 class="leaflet-sidebar-pane" id="home">
<h1 class="leaflet-sidebar-header">
Help
{{version}}
<div class="leaflet-sidebar-close"><i class="fa fa-caret-left"></i></div>
</h1>
<h2>Setup</h2>
@ -15,22 +26,20 @@
<h2>World view</h2>
<p>
When zoomed out you will see circles. Their size corresponds with the number of flights.
Blue means Ok. Yellow to little parking. Red no groundnet.
Blue means Ok. Yellow to little parking in comparison to the number of flights. Red no groundnet.
</p>
<h2>Edit view</h2>
<p>
</p>
<ul>
<li>Button with ICAO code opens the Airport data in the edit tab.</li>
<li>The top 4 buttons in the button bar are for zooming. </li>
<li>Upload sends the current airport to groundweb.</li>
<li>Edit switches into edit mode</li>
<li>Undo undos all changes or all changes during session</li>
<li>Save, saves the groundnet</li>
<li>Draw taxiline</li>
<li>Check triggers the groundnet check.</li>
<li>Draw bi directional taxiline</li>
<li>Draw uni directional taxiline</li>
<li>Draw pushback.</li>
<li>Add parking</li>
<li>Remove element, removes the currently selected element</li>
<li>Check triggers the groundnet check.</li>
</ul>
</div>
</template>
@ -51,7 +60,9 @@
},
computed: {
version: function () {
return ' Flightgear Airports ' + require('electron').remote.app.getVersion()
}
}
}
</script>

View File

@ -11,6 +11,7 @@ You should have received a copy of the GNU General Public License along with FG
-->
<template>
<div id="sidebar" class="leaflet-sidebar collapsed">
<Upload :visible.sync="uploadVisible" ref="upload"></Upload>
<!-- Nav tabs -->
<div class="leaflet-sidebar-tabs">
<ul role="tablist"> <!-- top aligned tabs -->
@ -37,10 +38,13 @@ You should have received a copy of the GNU General Public License along with FG
<div class="leaflet-sidebar-close"><i class="fa fa-caret-left"></i></div>
</h1>
<ParkingEdit></ParkingEdit>
<ArcEditMulti></ArcEditMulti>
<ArcEdit></ArcEdit>
<ThresholdEdit></ThresholdEdit>
<TowerEdit></TowerEdit>
<NodeEdit></NodeEdit>
<ParkingGroupEdit ref="parkingGroupEdit" @edit="(msg) => $emit('edit', msg)"></ParkingGroupEdit>
<AirportEdit ref="airportEdit"></AirportEdit>
<ParkingGroupEdit ref="parkingGroupEdit" @editParking="(msg) => $emit('editParking', msg)"></ParkingGroupEdit>
<AirportEdit ref="airportEdit" @edit="$emit('edit', $event)"></AirportEdit>
</div>
<!--
<div class="leaflet-sidebar-pane" id="parking">
@ -73,22 +77,32 @@ You should have received a copy of the GNU General Public License along with FG
import L from 'leaflet'
import AirportEdit from './AirportEdit'
import ArcEdit from './ArcEdit'
import TowerEdit from './TowerEdit'
import ArcEditMulti from './ArcEditMulti'
import CheckPanel from './CheckPanel'
import FileSelect from './FileSelect'
import Help from './Help'
import NodeEdit from './NodeEdit'
import ThresholdEdit from './ThresholdEdit'
import ParkingEdit from './ParkingEdit'
import ParkingGroupEdit from './ParkingGroupEdit'
// import ParkingList from './ParkingList'
import RunScan from './RunScan'
import SettingsPanel from './SettingsPanel'
import Search from './Search'
import Upload from './Upload'
import WorkInProgress from './WorkInProgress'
export default {
name: 'leaflet-sidebar',
components: { Help, AirportEdit, ArcEdit, CheckPanel, NodeEdit, ParkingEdit, ParkingGroupEdit, RunScan, FileSelect, SettingsPanel, Search, WorkInProgress },
components: { Help, AirportEdit, ArcEdit, ArcEditMulti, CheckPanel, NodeEdit, ParkingEdit, ParkingGroupEdit, RunScan, TowerEdit, ThresholdEdit, FileSelect, SettingsPanel, Search, Upload, WorkInProgress },
props: [],
created () {
window.addEventListener('keydown', this.doCommand)
},
destroyed () {
window.removeEventListener('keydown', this.doCommand)
},
mounted () {
this.add()
},
@ -96,10 +110,17 @@ You should have received a copy of the GNU General Public License along with FG
this.remove()
},
data () {
return {
return { uploadVisible: false
}
},
methods: {
doCommand (e) {
let cmd = String.fromCharCode(e.keyCode).toLowerCase()
if (e.keyCode === 46 /** DEL */ && e.target.type !== 'text') {
this.$parent.$parent.$refs.editLayer.deleteFeature()
}
console.log(cmd)
},
deferredMountedTo (parent) {
this.sidebar = L.control.sidebar({
autopan: false, // whether to maintain the centered map point when opening the sidebar

View File

@ -36,7 +36,17 @@
</el-select>
</el-col>
</el-row>
<el-row><el-button @click="link"><i class="fas fa-link"></i></el-button></el-row>
<el-row>
<el-popover
placement="top-start"
title="Goto"
width="200"
trigger="hover"
content="Weld/Link nodes"
>
<el-button @click="link" slot="reference"><i class="fas fa-link"></i></el-button>
</el-popover>
</el-row>
</div>
</div>
</template>

View File

@ -16,13 +16,21 @@
<span class="label">Name :</span>
</el-col>
<el-col :span="8">
<el-input placeholder="Name" v-model="name" :disabled="!editing"></el-input>
<el-input
placeholder="Name"
v-model="name"
:disabled="!editing"
></el-input>
</el-col>
<el-col :span="5">
<span class="label">Number :</span>
</el-col>
<el-col :span="7">
<el-input placeholder="Number" v-model="number" :disabled="!editing"></el-input>
<el-input
placeholder="Number"
v-model="number"
:disabled="!editing"
></el-input>
</el-col>
</el-row>
<el-row>
@ -40,7 +48,11 @@
-->
<el-radio-group v-model="wingspan" :disabled="!editing">
<el-tooltip content="PIPER PA-31/CESSNA 404 Titan" placement="top" effect="light">
<el-tooltip
content="PIPER PA-31/CESSNA 404 Titan"
placement="top"
effect="light"
>
<el-radio :label="15">A (7.5)</el-radio>
</el-tooltip>
<el-tooltip
@ -64,13 +76,25 @@
>
<el-radio :label="36">C (18)</el-radio>
</el-tooltip>
<el-tooltip content="B767 Series/AIRBUS A-310" placement="top" effect="light">
<el-radio :label="52">D (27)</el-radio>
<el-tooltip
content="B767 Series/AIRBUS A-310"
placement="top"
effect="light"
>
<el-radio :label="52">D (26)</el-radio>
</el-tooltip>
<el-tooltip content="B777 Series/B787 Series/A330 Family" placement="top" effect="light">
<el-tooltip
content="B777 Series/B787 Series/A330 Family"
placement="top"
effect="light"
>
<el-radio :label="66">E (33)</el-radio>
</el-tooltip>
<el-tooltip content="BOEING 747-8/AIRBUS A-380-800" placement="top" effect="light">
<el-tooltip
content="BOEING 747-8/AIRBUS A-380-800"
placement="top"
effect="light"
>
<el-radio :label="80">F (40)</el-radio>
</el-tooltip>
</el-radio-group>
@ -82,23 +106,41 @@
</el-col>
<el-col :span="17">{{ type }}</el-col>
</el-row>
<el-row v-if="editing">
<el-col :span="7">
<span class="label">Calculate :</span>
</el-col>
<el-col :span="17">
<el-radio-group v-model="calculate" size="small">
<el-radio-button label="Nose Wheel"></el-radio-button>
<el-radio-button label="Center"></el-radio-button>
</el-radio-group>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<span class="label">Coordinates :</span>
</el-col>
<el-col :span="17">
<el-popover
placement="top-start"
placement="bottom-start"
title="E-Mail"
width="200"
trigger="hover"
content="D.DDD, DMS, DM supported"
:disabled="!editing || calculate === 'Center'"
>
<el-input placeholder="Please input" v-model="coordinates" slot="reference" :disabled="!editing"></el-input>
<el-input
placeholder="Please input"
v-model="coordinates"
slot="reference"
:disabled="!editing || calculate === 'Center'"
@focus="coordFocussed = true"
@blur="coordFocussed = false"
></el-input>
</el-popover>
</el-col>
</el-row>
<!--
<el-row>
<el-col :span="7">
<span class="label">Nosewheel Coordinates :</span>
@ -110,33 +152,50 @@
width="200"
trigger="hover"
content="D.DDD, DMS, DM supported"
:disabled="!editing || calculate === 'Nose Wheel'"
>
<el-input placeholder="Please input" v-model="coordinates" slot="reference" :disabled="!editing"></el-input>
<el-input
placeholder="Please input"
v-model="noseCoordinates"
slot="reference"
:disabled="!editing || calculate === 'Nose Wheel'"
@focus="noseCoordFocussed = true"
@blur="noseCoordFocussed = false"
></el-input>
</el-popover>
</el-col>
</el-row>
-->
<el-row>
<el-col :span="7">
<span class="label">Heading :</span>
</el-col>
<el-col :span="17">
<el-col :span="13">
<el-input-number
v-model="heading"
:min="-361"
:max="720"
:step="0.1"
:precision="1"
:disabled="!editing"
:disabled="!editing || calculate === 'Heading'"
@change="headingChange"
></el-input-number>
</el-col>
<el-col :span="4">
<el-button @click="rotate" class="button">
<i class="fas fa-ruler-combined"></i>
</el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<span class="label">Parking Type :</span>
</el-col>
<el-col :span="17">
<el-select v-model="parking_type" placeholder="Select" :disabled="!editing">
<el-select
v-model="parking_type"
placeholder="Select"
:disabled="!editing"
>
<el-option
v-for="type in options"
:key="type.value"
@ -152,7 +211,12 @@
<span class="label">Airline :</span>
</el-col>
<el-col :span="17">
<el-select v-model="airlineCodes" multiple placeholder="Select" :disabled="!editing">
<el-select
v-model="airlineCodes"
multiple
placeholder="Select"
:disabled="!editing"
>
<el-option
v-for="item in airlines"
:key="item.value"
@ -174,14 +238,114 @@
<script lang="js">
/* eslint-disable */
const convert = require('geo-coordinates-parser');
const Coordinates = require('coordinate-parser');
const turf = require('@turf/turf');
const turfOptions = { units: 'kilometers' };
export default {
mounted() {
this.$store.watch(
function (state) {
return state.Editable.data.parking;
},
() => { this.editedParking() }
,
{
deep: true //add this if u need to watch object properties change etc.
}
);
},
methods: {
show (idx) {
this.$parent.$parent.$parent.$refs.editLayer.show(idx)
editedParking() {
this.externalChange = true
this.heading = Number(this.$store.state.Editable.data.parking.heading);
this.externalChange = false
},
rotate() {
var heading = this.$store.state.Editable.data.parking.heading + 90;
while (heading>=360) {
heading -= 360
}
while (heading<0) {
heading += 360
}
this.headingChange(heading);
},
headingChange( newValue ) {
while (newValue>=360) {
newValue -= 360
}
while (newValue<0) {
newValue += 360
}
if ( ( Math.abs( this.$store.state.Editable.data.parking.heading - newValue ) >= 0 && Math.abs( this.$store.state.Editable.data.parking.heading - newValue ) <= 0.1 )
|| !this.externalChange) {
if (Number(this.$store.state.Editable.data.parking.heading) !== newValue) {
this.$store.commit('SET_EDIT_PARKING_HEADING', newValue)
}
if(this.calculate === 'Center') {
// we change center
const noseWheelLatLng = convert(this.$store.state.Editable.data.parking.nosecoords);
const parkingSize = this.validRadii.indexOf(this.$store.state.Editable.data.parking.radius);
if (parkingSize>=0) {
var reverseHeading = this.normalizeAngle(this.$store.state.Editable.data.parking.heading+180);
var newCenter = turf.destination(this.latToTurf(noseWheelLatLng), this.validN2M[parkingSize]/1000, reverseHeading, turfOptions);
this.$store.commit('SET_EDIT_PARKING_COORDS', this.turfToLatLng(newCenter));
}
}
}
},
show (idx) {
this.$parent.$parent.$parent.$refs.editLayer.show(idx)
},
normalizeAngle( angle ) {
if(angle >= 180) {
return angle - 360;
}
if(angle <= -180) {
return angle + 360;
}
return angle;
},
latToTurf (turfPoint) {
return [turfPoint.decimalLongitude, turfPoint.decimalLatitude];
},
turfToLatLng (turfPoint) {
return '' + turfPoint.geometry.coordinates[1].toFixed(6) + ',' + turfPoint.geometry.coordinates[0].toFixed(6);
},
beautify (coordString) {
var a = coordString.split(' ');
if (a.length === 2) {
return '' + Number(a[0]).toFixed(6) + ' ' + Number(a[1]).toFixed(6);
} else {
return coordString;
}
},
calcWheel () {
if(this.calculate === 'Nose Wheel') {
// we change nosewheel
const centerLatLng = convert(this.beautify(this.$store.state.Editable.data.parking.coords));
const parkingSize = this.validRadii.indexOf(this.$store.state.Editable.data.parking.radius);
if (parkingSize>=0) {
var newNoseWheel = turf.destination(this.latToTurf(centerLatLng), this.validN2M[parkingSize]/1000, this.$store.state.Editable.data.parking.heading, turfOptions);
this.$store.commit('SET_EDIT_PARKING_NOSE_COORDS', this.turfToLatLng(newNoseWheel));
}
}
},
calcCenter () {
if (this.calculate === 'Center') {
// we change center
const noseWheelLatLng = convert(this.beautify(this.$store.state.Editable.data.parking.nosecoords));
const parkingSize = this.validRadii.indexOf(this.$store.state.Editable.data.parking.radius);
if (parkingSize>=0) {
var newCenter = turf.destination(this.latToTurf(noseWheelLatLng), this.validN2M[parkingSize]/1000, this.$store.state.Editable.data.parking.heading - 180, turfOptions);
this.$store.commit('SET_EDIT_PARKING_COORDS', this.turfToLatLng(newCenter));
}
}
}
},
data () { return {rotateFocussed: false, externalChange: false, coordFocussed: false, noseCoordFocussed: false, calculateState: 'Nose Wheel', noseWheel: '', validRadii: [7.5, 10, 14, 18, 26, 33, 40], validN2M: [5, 5, 6, 10, 15, 24, 24], heading: 0 }},
computed: {
editing: {
get: function () {
@ -211,7 +375,7 @@
var codes = this.$store.state.Editable.data.parking.airlineCodes
if (Array.isArray(codes)) {
return codes
} else if (codes !== undefined) {
} else if (codes !== undefined && typeof codes === 'string') {
return codes.split(',')
} else {
return []
@ -243,6 +407,19 @@
this.$store.commit('SET_EDIT_PARKING_NUMBER', newValue)
}
},
calculate: {
get: function () {
return this.calculateState;
},
set: function (newValue) {
this.calculateState = newValue;
if (newValue==='Center') {
this.calcCenter();
} else {
this.calcWheel();
}
}
},
coordinates: {
// getter
get: function () {
@ -258,25 +435,34 @@
if( newValue.match(/,/g) !== null && newValue.match(/,/g).length === 3) {
newValue = newValue.replace(', ', ' ').replace(/,/g, '.').replace(' ', ', ');
}
if (this.coordFocussed) {
this.$store.commit('SET_EDIT_PARKING_COORDS', newValue)
}
this.calcWheel();
}
},
heading: {
noseCoordinates: {
// getter
get: function () {
return Number(this.$store.state.Editable.data.parking.heading)
if(this.$store.state.Editable.index!==undefined) {
if(!this.$store.state.Editable.data.parking.nosecoords && this.calculate === 'Nose Wheel') {
this.calcWheel();
}
return this.$store.state.Editable.data.parking.nosecoords;
}
},
// setter
set: function (newValue) {
while (newValue>=360) {
newValue -= 360
if (newValue==='unknown') {
}
while (newValue<0) {
newValue += 360
if( newValue.match(/,/g) !== null && newValue.match(/,/g).length === 3) {
newValue = newValue.replace(', ', ' ').replace(/,/g, '.').replace(' ', ', ');
}
if (Number(this.$store.state.Editable.data.parking.heading) !== newValue) {
this.$store.commit('SET_EDIT_PARKING_HEADING', newValue)
if (this.noseCoordFocussed) {
this.$store.commit('SET_EDIT_PARKING_NOSE_COORDS', newValue);
}
this.calcCenter();
}
},
wingspan: {

View File

@ -74,7 +74,7 @@
<el-col :span="7">
<span class="label">Heading :</span>
</el-col>
<el-col :span="17">
<el-col :span="13">
<el-input-number
v-model="avgHeading" @change="headingChange"
:min="-361"
@ -84,14 +84,18 @@
:disabled="!editing"
></el-input-number>
</el-col>
<el-col :span="4">
<el-button @click="rotate" class="button">
<i class="fas fa-ruler-combined"></i>
</el-button>
</el-col>
</el-row>
<!--
<el-row>
<el-col :span="7">
<span class="label">Parking Type :</span>
</el-col>
<el-col :span="17">
<el-select v-model="parking_type" placeholder="Select" :disabled="!editing">
<el-select v-model="parking_type" @change="typeChange" placeholder="Select" :disabled="!editing">
<el-option
v-for="type in options"
:key="type.value"
@ -102,6 +106,7 @@
</el-select>
</el-col>
</el-row>
<!--
<el-row>
<el-col :span="7">
<span class="label">Airline :</span>
@ -131,17 +136,28 @@ const convert = require('geo-coordinates-parser');
export default {
data () {
return {
data: Object, avgHeading: 5, editing: Boolean, wingspan: 0
data: Object, avgHeading: 5, editing: Boolean, wingspan: 0, parking_type: ''
}
},
methods: {
rotate() {
this.avgHeading = this.avgHeading + 90;
while (this.avgHeading>=360) {
this.avgHeading -= 360
}
while (this.avgHeading<0) {
this.avgHeading += 360
}
this.headingChange(this.avgHeading);
},
show (idx) {
this.$parent.$parent.$parent.$refs.editLayer.show(idx)
},
setData (data) {
this.data = data;
this.setAvgHeading();
this.setAvgType();
},
setEditing(editing) {
this.editing = editing
@ -156,9 +172,20 @@ const convert = require('geo-coordinates-parser');
return r;
}, { sum: 0, count: 0, avg: 0 }).avg);
},
setAvgType() {
if( this.data === null || this.data === undefined) {
return 0
}
var types = this.data.map(parking => parking.options.attributes.type).filter((v, i, a) => a.indexOf(v) === i);
if (types.length == 1) {
this.parking_type = types[0];
} else {
this.parking_type = '';
}
},
wingspanChange( newValue ) {
if ( newValue ) {
this.$emit('edit', {type: 'parking-group-wingspan', wingspan: newValue} )
this.$emit('editParking', {type: 'parking-group-wingspan', wingspan: newValue} )
}
},
headingChange( newValue ) {
@ -169,7 +196,12 @@ const convert = require('geo-coordinates-parser');
newValue += 360
}
if ( newValue ) {
this.$emit('edit', {type: 'parking-group-angle', angle: newValue} )
this.$emit('editParking', {type: 'parking-group-angle', angle: newValue} )
}
},
typeChange( newValue ) {
if ( newValue ) {
this.$emit('editParking', {type: 'parking-group-type', parking_type: newValue} )
}
}
},
@ -256,19 +288,6 @@ const convert = require('geo-coordinates-parser');
{value: 'mil-fighter', label: 'military fighter'},
{value: 'mil-cargo', label: 'military cargo'}
]
},
parking_type: {
// getter
get: function () {
if (this.$store.state.Editable.data.parking.type === undefined) {
return 'none'
}
return this.$store.state.Editable.data.parking.type
},
// setter
set: function (newValue) {
this.$store.commit('SET_EDIT_PARKING_TYPE', newValue)
}
}
}
}

View File

@ -1,6 +1,17 @@
<!--
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>
<el-link v-if="!editing" type="primary" @click="show(parking.index)">{{parking.name}} {{number}} {{parking.type}} {{type}}</el-link>
<el-input @focus="show(parking.index)" v-if="editing" placeholder="Name" v-model="name" class="wide"></el-input>
<el-input @focus="show(parking.index)" v-if="editing" placeholder="Number" v-model="number" class="narrow"></el-input>
<el-select @focus="show(parking.index)" v-if="editing" v-model="parking_type" placeholder="Select">
@ -30,15 +41,19 @@
if (this.editLayer === null) {
this.initLayer()
}
if (this.editLayer) {
return this.editLayer.show(idx)
}
},
initLayer () {
var parent = this.$parent
while (parent.$refs.editLayer === undefined) {
while (parent && !parent.$refs.editLayer) {
parent = parent.$parent
}
if (parent) {
this.editLayer = parent.$refs.editLayer
}
}
},
computed: {
options: function () {
@ -69,7 +84,11 @@
number: {
// getter
get: function () {
if (this.parking.number && this.parking.number !== 'undefined') {
return this.parking.number
} else {
return ''
}
},
// setter
set: function (newValue) {
@ -88,8 +107,36 @@
set: function (newValue) {
this.$store.commit('SET_EDIT_PARKING_ITEM_TYPE', [this.parking.index, newValue])
}
}
},
type: function () {
/**
* Cat Models FG Radii N2M Radii
* B Small Regionals ERJ CRJ ATR 14 6
* C A319 A320 A321 B737 18 10
* D B757, B767 26 15
* E B777 B787 A330 A340 A360 33 24
* F A380 40 24
*/
switch (this.parking.radius * 2) {
case 15:
return 'Piper J-3 Cub/Cessna 172'
case 20:
return 'Beech 200/Cessna 425'
case 28:
return 'BOMBARDIER Regional Jet CRJ-200/DE HAVILLAND CANADA DHC-6'
case 36:
return 'BOEING 737-700/AIRBUS A-320/EMBRAER ERJ 190-100'
case 52:
return 'B767 Series/AIRBUS A-310'
case 66:
return 'B777 Series/B787 Series/A330 Family'
case 80:
return 'BOEING 747-8/AIRBUS A-380-800'
default:
return 'Unknown radius : ' + this.parking.radius
}
}
}
}
</script>

View File

@ -5,6 +5,7 @@
import L from 'leaflet'
import LEdit from 'leaflet-editable/src/Leaflet.Editable.js'
import {readPavement} from '../loaders/pavement_loader'
import * as turf from '@turf/turf'
export default {
name: 'edit-layer',
@ -31,12 +32,19 @@
// Callback for add
readPavement(this.$store.state.Settings.settings.flightgearDirectory_apt, icao, this.read)
},
// Callback called when pavement read
read (layer) {
this.pavement = layer
if (this.pavement) {
this.pavement.on('add', this.onAdd)
this.pavement.addTo(this.$parent.mapObject)
this.visible = true
} else {
this.$message({
type: 'Error',
showClose: true,
message: `Couldn't load pavement from ${this.$store.state.Settings.settings.flightgearDirectory_apt}`
})
}
},
onAdd () {
@ -68,6 +76,21 @@
this.deferredMountedTo(this.$parent.mapObject)
}
},
isOnRunway (latlng) {
var ret = false
this.pavement.eachLayer(l => {
if (l instanceof L.RunwayPolygon) {
console.debug(l)
if (turf.booleanContains(l.turfyRunway, this.latToTurf(latlng))) {
ret = true
}
}
})
return ret
},
latToTurf (turfPoint) {
return turf.point([turfPoint.lng, turfPoint.lat])
},
setVisible (visible) {
if (this.pavement !== undefined) {
if (visible !== this.visible) {

View File

@ -74,6 +74,16 @@
const worker = new Worker(winURL)
console.log(fileUrl('src/renderer/utils/worker.js'))
worker.onerror = function (e) {
console.error(e)
worker.terminate()
worker.scanning = false
worker.view.progress = 0
worker.view.max = 0
worker.view.worker = null
clearInterval(this.polling)
e.preventDefault()
}
worker.scanning = this.scanning
worker.max = this.max
@ -121,6 +131,16 @@
const worker = new Worker(winURL)
console.log(fileUrl('src/renderer/utils/worker.js'))
worker.onerror = function (e) {
console.error(e)
worker.terminate()
worker.scanning = false
worker.view.progress = 0
worker.view.max = 0
worker.view.worker = null
clearInterval(this.polling)
e.preventDefault()
}
worker.scanning = this.scanning
worker.max = this.max
@ -169,6 +189,16 @@
const worker = new Worker(winURL)
console.log(fileUrl('src/renderer/utils/worker.js'))
worker.onerror = function (e) {
console.error(e)
worker.terminate()
worker.scanning = false
worker.view.progress = 0
worker.view.max = 0
worker.view.worker = null
clearInterval(this.polling)
e.preventDefault()
}
this.scanning = true
worker.scanning = this.scanning
worker.max = this.max

View File

@ -16,11 +16,8 @@
<script lang="js">
// import scanner from '../utils/scan'
import fileUrl from 'file-url'
import {Table, TableColumn} from 'element-ui'
const path = require('path')
export default {
name: 'search',
components: { [Table.name]: Table,
@ -36,7 +33,7 @@
},
methods: {
goto (icao) {
console.log(icao)
console.debug('Goto : ' + icao)
let airports = this.$store.state.Airports.airports
.filter(a => a.properties.icao.match(icao))
if (airports.length > 0) {
@ -46,87 +43,11 @@
formatter (row, column) {
console.log('Row ' + row)
return row
},
scanAPT () {
try {
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/src/renderer/utils/worker.js`
: `file://${__dirname}/worker.js`
console.log('make a worker: ', path.resolve(__dirname, 'worker.js'))
const worker = new Worker(winURL)
console.log(fileUrl('src/renderer/utils/worker.js'))
// var worker = new Worker(fileUrl('src/renderer/utils/worker.js'))
worker.postMessage(['scanapt'])
// the reply
var store = this.$store
worker.onmessage = function (e) {
if (e.data === 'DONE') {
console.log('DONE')
store.dispatch('getAirports')
worker.terminate()
}
console.log(e.data)
}
} catch (err) {
console.error(err)
}
},
scanGroundnets () {
try {
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/src/renderer/utils/worker.js`
: `file://${__dirname}/worker.js`
console.log('make a worker: ', path.resolve(__dirname, 'worker.js'))
const worker = new Worker(winURL)
console.log(fileUrl('src/renderer/utils/worker.js'))
worker.postMessage(['scan', this.$store.state.Settings.settings.airportsDirectory])
// the reply
var store = this.$store
worker.onmessage = function (e) {
if (e.data === 'DONE') {
console.log('DONE')
store.dispatch('getAirports')
worker.terminate()
}
console.log(e.data)
}
} catch (err) {
console.error(err)
}
},
scanTraffic () {
// let flightgearDirectory = this.$store.state.settings.flightgearDirectory
try {
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/src/renderer/utils/worker.js`
: `file://${__dirname}/worker.js`
console.log('make a worker: ', path.resolve(__dirname, 'worker.js'))
const worker = new Worker(winURL)
console.log(fileUrl('src/renderer/utils/worker.js'))
worker.postMessage(['scanai', this.$store.state.Settings.settings.flightgearDirectory])
// the reply
var store = this.$store
worker.onmessage = function (e) {
if (e.data === 'DONE') {
console.log('DONE')
store.dispatch('getAirports')
worker.terminate()
}
console.log(e.data)
}
} catch (err) {
console.error(err)
}
}
},
computed: {
searched: function () {
console.log(this.searchterm)
console.debug('Search : ' + this.searchterm)
if (this.searchterm !== this.lastSearchTerm) {
let searchRegex = new RegExp(this.searchterm, 'i')
let result = this.$store.state.Airports.airports
@ -136,10 +57,14 @@
.filter(a => searchRegex.test(a.properties.icao) || searchRegex.test(a.properties.name))
// .map(a => console.log(a.properties))
.map(a => ({ icao: a.properties.icao, name: a.properties.name }))
.filter((v, i, a) => a.findIndex(t => (t.icao === v.icao)) === i)
let icaoResult = result.filter(a => a.icao === this.searchterm).filter((v, i, a) => a.findIndex(t => (t.icao === v.icao)) === i)
if (result !== undefined &&
result.length === 0 &&
icaoResult.length === 0 &&
this.searchterm !== undefined &&
this.searchterm.length >= 3) {
this.searchterm.length >= 3 &&
this.searchterm.length <= 4) {
// Not found so it might have been excluded due to no traffic
this.$store.dispatch('getAirport', this.searchterm)
}
this.lastResult = result

View File

@ -6,54 +6,122 @@
<i class="fa fa-caret-left"></i>
</div>
</h1>
<div id="panel" width="100%">
<el-collapse v-model="activeName" accordion>
<el-collapse-item title="General" name="1">
<el-row>
<el-col :span="12" class="label">Number of saves : </el-col>
<el-col :span="12">
<el-popover
placement="top-start"
title="Saves"
width="200"
trigger="hover"
content="How many previous versions should be kept."
>
<el-input
placeholder="Number of versions"
slot="reference"
v-model="numberOfSaves"
></el-input>
</el-popover>
</el-col>
</el-row>
</el-collapse-item>
<el-collapse-item title="Directories" name="2">
<el-row>
<el-col :span="22" class="label">Airports Directory</el-col>
</el-row>
<el-row>
<el-col :span="20" class="file-label">{{ airports_directory }}</el-col>
<el-col :span="4">
<directory-select @input="airportsDirectorySelect"></directory-select>
</el-col>
</el-row>
<el-row>
<el-col :span="22" class="label">Flightgear Directory</el-col>
</el-row>
<el-row>
<el-col :span="20" class="file-label">{{ flightgear_directory }}</el-col>
<el-col :span="4">
<directory-select @input="flightgearDirectorySelect"></directory-select>
</el-col>
</el-row>
<el-row>
<el-col :span="7" class="label">Traffic Directory</el-col>
<el-col :span="15" class="file-label">{{ Traffic_directory }}</el-col>
<el-col
:span="22"
v-bind:class="{
invalid: !airports_directory_ok,
file_label: airports_directory_ok,
}"
>{{ airports_directory }}</el-col
>
<el-col :span="2">
<el-popover
placement="top-start"
title="E-Mail"
width="200"
trigger="hover"
content="The work directory. Best is a copy from groundweb"
>
<directory-select
@input="airportsDirectorySelect"
slot="reference"
></directory-select>
</el-popover>
</el-col>
</el-row>
<el-row>
<el-col :span="7" class="label">APT File</el-col>
<el-col :span="15" class="file-label">{{ apt_file }}</el-col>
<el-col :span="22" class="label">Flightgear Data Directory</el-col>
</el-row>
<el-row>
<el-col
:span="22"
v-bind:class="{
invalid: !flightgear_directory_ok,
file_label: flightgear_directory_ok,
}"
>{{ flightgear_directory }}</el-col
>
<el-col :span="2">
<el-popover
placement="top-start"
title="E-Mail"
width="200"
trigger="hover"
content="The FGDATA directory."
>
<directory-select
@input="flightgearDirectorySelect"
slot="reference"
></directory-select>
</el-popover>
</el-col>
</el-row>
<el-row>
<el-col :span="7" class="label">Export Directory</el-col>
<el-col :span="15" class="file-label">{{ test_directory }}</el-col>
<el-col :span="22" class="label">Traffic Directory</el-col>
</el-row>
<el-row>
<el-col
:span="22"
v-bind:class="{
invalid: !Traffic_directory_ok,
file_label: Traffic_directory_ok,
}"
>{{ Traffic_directory }}</el-col
>
<el-col :span="2"> </el-col>
</el-row>
<el-row>
<el-col :span="22" class="label">APT File</el-col>
</el-row>
<el-row>
<el-col :span="22" v-bind:class="{ invalid: !apt_file_ok }">{{
apt_file
}}</el-col>
<el-col :span="2"> </el-col>
</el-row>
<el-row>
<el-col :span="7" class="label">Export Directory : </el-col>
<el-col
:span="15"
v-bind:class="{
invalid: !test_directory_ok,
file_label: test_directory_ok,
}"
>{{ test_directory }}</el-col
>
<el-col :span="2">
<directory-select @input="testDirectorySelect"></directory-select>
</el-col>
</el-row>
</el-collapse-item>
<el-collapse-item title="User" name="3">
<el-row>
<el-col :span="22" class="label">Phi Host Url</el-col>
</el-row>
<el-row>
<el-col :span="24" class="label">
<el-input placeholder="Please input a valid Phi URL" v-model="phi_url"></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="7" class="label">Author E-Mail : </el-col>
<el-col :span="17">
<el-popover
@ -63,12 +131,15 @@
trigger="hover"
content="Only used as a committer/author for Github. This e-mail is only visible via GIT."
>
<el-input placeholder="Please input your email" slot="reference" v-model="email"></el-input>
<el-input
placeholder="Please input your email"
slot="reference"
v-model="email"
></el-input>
</el-popover>
</el-col>
</el-row>
<el-row>
<el-col :span="7" class="label">Author Name : </el-col>
<el-col :span="17">
<el-popover
@ -78,10 +149,16 @@
trigger="hover"
content="This is saved to the file and is therefore distributed via Terrasync."
>
<el-input placeholder="Please input your Name" slot="reference" v-model="name"></el-input>
<el-input
placeholder="Please input your Name"
slot="reference"
v-model="name"
></el-input>
</el-popover>
</el-col>
</el-row>
</el-collapse-item>
<el-collapse-item title="Troubleshooting" name="4">
<el-row>
<el-col :span="7">
<span class="label">Scan logging :</span>
@ -114,7 +191,21 @@
</el-popover>
</el-col>
</el-row>
</div>
</el-collapse-item>
<el-collapse-item title="Flightgear" name="5">
<el-row>
<el-col :span="22" class="label">Phi Host Url</el-col>
</el-row>
<el-row>
<el-col :span="24" class="label">
<el-input
placeholder="Please input a valid Phi URL"
v-model="phi_url"
></el-input>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
</div>
</template>
@ -123,19 +214,30 @@
import DirectorySelect from './DirectorySelect'
const { ipcRenderer } = require('electron')
const fs = require('fs')
export default {
name: 'settings-panel',
components: { DirectorySelect, FileSelect },
props: [],
mounted () {
},
data () {
return {
return { ok: true, activeName: '0', scanStoreLogging: false }
},
mounted () {
this.$store.watch(
function (state) {
return state.Settings.settings
},
() => { this.loggingChanged() }
,
{
deep: true // add this if u need to watch object properties change etc.
}
)
},
methods: {
loggingChanged () {
this.scanStoreLogging = this.$store.state.Settings.settings.scanLogging === 1
},
flightgearDirectorySelect: function (flightgearDirectory) {
console.log(flightgearDirectory)
this.$store.commit('FLIGHTGEAR_DIRECTORY', flightgearDirectory)
@ -153,6 +255,16 @@
}
},
computed: {
numberOfSaves: {
// getter
get: function () {
return this.$store.state.Settings.settings.numberOfSaves
},
// setter
set: function (newValue) {
this.$store.commit('SET_NUMBER_OF_SAVES', newValue)
}
},
email: {
// getter
get: function () {
@ -186,25 +298,65 @@
flightgear_directory: function () {
return this.$store.state.Settings.settings.flightgearDirectory
},
flightgear_directory_ok: function () {
try {
fs.accessSync(this.$store.state.Settings.settings.flightgearDirectory)
return true
} catch (error) {
return false
}
},
AI_directory: function () {
return this.$store.state.Settings.settings.flightgearDirectory_ai
},
Traffic_directory: function () {
return this.$store.state.Settings.settings.flightgearDirectory_traffic
},
Traffic_directory_ok: function () {
try {
fs.accessSync(this.$store.state.Settings.settings.flightgearDirectory_traffic)
return true
} catch (error) {
return false
}
},
apt_file: function () {
return this.$store.state.Settings.settings.flightgearDirectory_apt
},
apt_file_ok: function () {
try {
fs.accessSync(this.$store.state.Settings.settings.flightgearDirectory_apt)
return true
} catch (error) {
return false
}
},
airports_directory: function () {
return this.$store.state.Settings.settings.airportsDirectory
},
airports_directory_ok: function () {
try {
fs.accessSync(this.$store.state.Settings.settings.airportsDirectory)
return true
} catch (error) {
return false
}
},
test_directory: function () {
return this.$store.state.Settings.settings.testDirectory
},
test_directory_ok: function () {
try {
fs.accessSync(this.$store.state.Settings.settings.testDirectory)
return true
} catch (error) {
return false
}
},
scanLogging: {
// getter
get: function () {
return this.$store.state.Settings.settings.scanLogging === 1
return this.scanStoreLogging
},
// setter
set: function (newValue) {
@ -223,10 +375,14 @@
border-radius: 4px;
}
.label {
padding: 10px;
padding: 5px;
font-weight: bold;
}
.file-label {
padding: 10px;
.file_label {
padding: 5px;
}
.invalid {
padding: 5px;
background-color: red;
}
</style>

View File

@ -0,0 +1,95 @@
<!--
Copyright 2020 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 width="100%" v-if="threshold">
<div>
<el-row>
<el-col :span="7">
<span class="label">Runway :</span>
</el-col>
<el-col :span="17">
<el-input
placeholder="Please input"
v-model="runway"
:disabled="true"
></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<span class="label">Displacement :</span>
</el-col>
<el-col :span="17">
<el-input-number
v-model="displacement"
:disabled="!editing"
></el-input-number>
</el-col>
</el-row>
</div>
</div>
</template>
<script lang="js">
/* eslint-disable */
const Coordinates = require('coordinate-parser');
import {writeTowerXML} from '../loaders/tower_writer'
export default {
/*
methods: {
updateIsOnRunway (value) {
this.$store.commit('SET_EDIT_ISONRUNWAY', value)
}
},
*/
data () {
return {
coordFocussed: false
}
},
methods: {
save () {
var o = {latitude: this.latitude, longitude: this.longitude, height: this.height};
writeTowerXML(this.$store.state.Settings.settings.airportsDirectory, this.$parent.$parent.$parent.icao, o)
}
},
computed: {
editing: {
get: function () {
return this.$parent.$parent.$parent.$refs.editLayer.editing
}
},
threshold: function () {
return this.$store.state.Editable.type === 'threshold'
},
//<rwy>07L</rwy>
//<hdg-deg>68.77</hdg-deg>
//<displ-m>0.0</displ-m>
//<stopw-m>160.0</stopw-m>
runway: function () {
return this.$store.state.Editable.data.threshold.runway;
},
displacement: {
set: function (newValue) {
this.$store.dispatch('setDisplacement', newValue);
},
get: function () {
return this.$store.state.Editable.data.threshold.displacement;
}
}
}
}
</script>

View File

@ -3,8 +3,10 @@
<script lang="js">
import { LMap, LMarker } from 'vue2-leaflet'
import L from 'leaflet'
import leafletPattern from 'leaflet.pattern'
import LEdit from 'leaflet-editable/src/Leaflet.Editable.js'
import {readThresholdXML} from '../loaders/threshold_loader'
import {writeThresholdXML} from '../loaders/threshold_writer'
export default {
name: 'edit-layer',
@ -12,10 +14,19 @@
created () {
},
mounted () {
console.log(LMap)
console.log(LMarker)
console.log(L)
console.log(LEdit)
console.debug(LMap, LMarker, L, LEdit, leafletPattern)
this.$store.watch(
function (state) {
return state.Editable.data.threshold
},
() => { this.editedThreshold() }
,
{
deep: true
}
)
var stripes = new L.StripePattern({color: 'yellow'})
stripes.addTo(this.$parent.mapObject)
},
beforeDestroy () {
this.remove()
@ -25,12 +36,37 @@
}
},
methods: {
editedThreshold () {
if (this.$store.state.Editable.data.threshold) {
var rwy = this.$store.state.Editable.data.threshold.runway
var displacement = this.$store.state.Editable.data.threshold.displacement
this.layerGroup.eachLayer(l => {
if (l instanceof L.Threshold) {
if (l.rwy === rwy) {
l.setDisplacement(displacement)
}
}
})
}
},
getLayer () {
return this.layerGroup
},
load (icao) {
this.$parent.mapObject.createPane('threshold-pane')
this.$parent.mapObject.getPane('threshold-pane').style.zIndex = 550
if (this.layerGroup) {
this.layerGroup.removeFrom(this.$parent.mapObject)
}
var stripes = new L.StripePattern({color: 'yellow'})
stripes.addTo(this.$parent.mapObject)
// Callback for add
this.layerGroup = readThresholdXML(this.$store.state.Settings.settings.airportsDirectory, icao, this.read)
this.layerGroup = readThresholdXML(this.$store.state.Settings.settings.airportsDirectory, icao, this.read, stripes)
if (!this.layerGroup) {
console.warn('Threshold for ICAO not loaded ' + icao)
return
}
this.layerGroup.addTo(this.$parent.mapObject)
this.visible = true
this.icao = icao
@ -47,6 +83,24 @@
this.deferredMountedTo(this.$parent.mapObject)
}
},
enableEdit () {
if (this.layerGroup) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.Threshold) {
l.setInteractive(true)
}
})
}
},
disableEdit () {
if (this.layerGroup) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.Threshold) {
l.setInteractive(false)
}
})
}
},
setVisible (visible) {
if (this.layerGroup !== undefined) {
if (visible !== this.visible) {
@ -58,13 +112,42 @@
this.visible = visible
}
}
},
save () {
if (this.layerGroup) {
var list = {}
this.layerGroup.eachLayer(l => {
if (l instanceof L.Threshold) {
var latitude = l.originLatLng[0].toFixed(6)
var longitude = l.originLatLng[1].toFixed(6)
if (list[l.index] === undefined) {
list[l.index] = []
}
var o = {latitude: latitude, longitude: longitude, index: l.index, rwy: l.rwy, heading: l.heading, displacement: l.displacement, stopw_m: l.stopw_m}
list[l.index].push(o)
}
})
writeThresholdXML(this.$store.state.Settings.settings.airportsDirectory, this.icao, list)
}
},
zoomUpdated () {
if (this.layerGroup) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.Threshold) {
l.updateIcon(this.$parent.mapObject)
}
})
}
}
},
computed: {
edit: function () {
console.log('Zoom : ' + this.$store.state.Settings.zoom)
if (this.$store.state.Settings.zoom > 12) {
console.log()
console.log('Zoom above 12')
}
}
}

View File

@ -11,6 +11,12 @@ You should have received a copy of the GNU General Public License along with FG
-->
<template>
<div id="ToolBar">
<ToolButton
icon="far fa-eye"
v-on:click="showTooltips"
:show="editing"
tooltip="Show Tooltips"
></ToolButton>
<ToolButton
icon="fas fa-draw-polygon"
v-on:click="drawPolyline"
@ -42,7 +48,10 @@ You should have received a copy of the GNU General Public License along with FG
this.$parent.$parent.$refs.toolLayer.stopDrawing()
this.$parent.$parent.$refs.toolLayer.drawPolyline()
},
setEdit (edit) {
showTooltips () {
this.$parent.$parent.$refs.editLayer.showTooltips()
},
setEditing (edit) {
this.isEditing = edit;
if(!this.isEditing) {
this.$parent.$parent.$refs.toolLayer.stopDrawing()

View File

@ -6,6 +6,7 @@
import ToolControl from '../leaflet/ToolControl.js'
export default {
name: 'edit-bar',
components: { ToolControl, L },
props: {
icon: String,
show: Boolean,
@ -22,8 +23,6 @@
}
},
mounted () {
console.debug(L)
console.debug(ToolControl)
this.add()
},
beforeDestroy () {

View File

@ -56,8 +56,38 @@ You should have received a copy of the GNU General Public License along with FG
},
drawPolyline () {
var polyLine = this.$parent.mapObject.editTools.startPolygon(undefined, {color: 'green'})
var layerGroup = this.toolLayerGroup;
polyLine.addTo(this.toolLayerGroup)
polyLine.on('click', event => {
polyLine.removeFrom(layerGroup);
});
polyLine.on('editable:drawing:end', event => {
console.debug('editable:drawing:end', event)
var latLngs = event.target.getLatLngs()[0].map( latLng => ([latLng.lat, latLng.lng]));
// turf rings must start/end with the same point
latLngs.push(latLngs[0]);
var longest = 0;
var angleLongest = 0;
latLngs.forEach((item, index, arr) => {
if (index > 0) {
var angle = turf.bearing(turf.point([arr[index-1][1], arr[index-1][0]]), turf.point([arr[index][1], arr[index][0]])) + 180;
var dist = turf.distance( turf.point(arr[index-1]), turf.point(arr[index]));
if (dist>longest) {
longest = dist;
angleLongest = angle;
}
}
});
event.target.bindTooltip(angleLongest.toFixed(2) + '°', {permanent: true})
var ring = [latLngs];
this.$emit('select-poly', ring);
})
polyLine.on('editable:vertex:dragend', event => {
console.debug(event)
var latLngs = event.target.getLatLngs()[0].map( latLng => ([latLng.lat, latLng.lng]));
@ -77,14 +107,7 @@ You should have received a copy of the GNU General Public License along with FG
}
}
});
var point1 = turf.point([-75.343, 39.984]);
var point2 = turf.point([-75.534, 39.123]);
var bearing = turf.bearing(point1, point2);
console.log(bearing);
event.target.bindTooltip(angleLongest.toFixed(2) + '°', {permanent: true})
event.target.setTooltipContent(angleLongest.toFixed(2) + '°')
var ring = [latLngs];

View File

@ -0,0 +1,93 @@
<template>
<div width="100%" v-if="tower">
<div>
<el-row>
<el-col :span="7">
<span class="label">Latitude :</span>
</el-col>
<el-col :span="17">
<el-input placeholder="Please input" v-model="latitude" :disabled="true"
@focus="coordFocussed = true"
@blur="coordFocussed = false"></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<span class="label">Longitude :</span>
</el-col>
<el-col :span="17">
<el-input placeholder="Please input" v-model="longitude" :disabled="true"
@focus="coordFocussed = true"
@blur="coordFocussed = false"></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<span class="label">Height :</span>
</el-col>
<el-col :span="17">
<el-input-number placeholder="Please input" @change="handleChange" v-model="height" :disabled="!editing" :step="0.01"
@focus="coordFocussed = true"
@blur="coordFocussed = false"></el-input-number>
</el-col>
</el-row>
</div>
</div>
</template>
<script lang="js">
/* eslint-disable */
const Coordinates = require('coordinate-parser');
import {writeTowerXML} from '../loaders/tower_writer'
export default {
/*
methods: {
updateIsOnRunway (value) {
this.$store.commit('SET_EDIT_ISONRUNWAY', value)
}
},
*/
data () {
return {
coordFocussed: false
}
},
methods: {
save () {
var o = {latitude: this.latitude, longitude: this.longitude, height: this.height};
writeTowerXML(this.$store.state.Settings.settings.airportsDirectory, this.$parent.$parent.$parent.icao, o)
},
handleChange (newValue) {
this.$store.dispatch('setTowerHeight', newValue);
}
},
computed: {
editing: {
get: function () {
return this.$parent.$parent.$parent.$refs.editLayer.editing
}
},
tower: function () {
return this.$store.state.Editable.type === 'tower'
},
// {index: 39, lat: "N58 27.343", lon: "W03 5.153", isOnRunway: 0, holdPointType: "none"}
latitude: function () {
return this.$store.state.Editable.data.tower.coords.latitude;
},
longitude: function () {
return this.$store.state.Editable.data.tower.coords.longitude;
},
height: {
get: function () {
return this.$store.state.Editable.data.tower.height;
},
set: function (newValue) {
this.$store.dispatch('setTowerHeight', newValue);
}
}
}
}
</script>

View File

@ -0,0 +1,154 @@
<!--
Copyright 2020 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></template>
<script lang="js">
import { LMap, LMarker } from 'vue2-leaflet'
import L from 'leaflet'
import LEdit from 'leaflet-editable/src/Leaflet.Editable.js'
import {readTowerXML} from '../loaders/tower_loader'
import {writeTowerXML} from '../loaders/tower_writer'
export default {
name: 'tower-layer',
props: [],
created () {
console.debug([LMap, LMarker, L, LEdit])
},
mounted () {
this.$store.watch(
function (state) {
return state.Editable.data.tower
},
() => { this.editedTower() }
,
{
deep: true
}
)
},
beforeDestroy () {
this.remove()
},
data () {
return {
}
},
methods: {
editedTower () {
if (this.$store.state.Editable.data.tower) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.TowerMarker) {
l.setTowerHeight(this.$store.state.Editable.data.tower.height)
}
})
}
},
getLayer () {
return this.layerGroup
},
load (icao) {
this.$parent.mapObject.createPane('tower-pane')
this.$parent.mapObject.getPane('tower-pane').style.zIndex = 550
if (this.layerGroup !== undefined) {
this.layerGroup.removeFrom(this.$parent.mapObject)
}
// Callback for add
this.layerGroup = readTowerXML(this.$store.state.Settings.settings.airportsDirectory, icao, this.read)
if (!this.layerGroup) {
console.warn('Tower for ICAO not loaded ' + icao)
return
}
this.layerGroup.addTo(this.$parent.mapObject)
this.visible = true
this.icao = icao
},
deferredMountedTo (parent) {
},
remove () {
if (this.layerGroup) {
this.$parent.removeLayer(this.layerGroup)
}
},
add () {
if (this.$parent._isMounted) {
this.deferredMountedTo(this.$parent.mapObject)
}
},
enableEdit () {
if (this.layerGroup) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.TowerMarker) {
l.setInteractive(true)
}
})
}
},
disableEdit () {
if (this.layerGroup) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.TowerMarker) {
l.setInteractive(false)
}
})
}
},
save () {
if (this.layerGroup) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.TowerMarker) {
var latitude = l.getLatLng().lat.toFixed(6)
var longitude = l.getLatLng().lng.toFixed(6)
var height = l.elev_m
var o = {latitude: latitude, longitude: longitude, height: height}
writeTowerXML(this.$store.state.Settings.settings.airportsDirectory, l.icao, o)
}
})
}
},
setVisible (visible) {
if (this.layerGroup !== undefined) {
if (visible !== this.visible) {
if (visible) {
this.layerGroup.addTo(this.$parent.mapObject)
} else {
this.layerGroup.removeFrom(this.$parent.mapObject)
}
this.visible = visible
}
}
},
zoomUpdated () {
if (this.layerGroup) {
this.layerGroup.eachLayer(l => {
if (l instanceof L.TowerMarker) {
l.updateIcon(this.$parent.mapObject)
}
})
}
}
},
computed: {
edit: function () {
console.log('Zoom : ' + this.$store.state.Settings.zoom)
if (this.$store.state.Settings.zoom > 12) {
console.log()
}
}
}
}
</script>
<style scoped lang="scss">
</style>

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.filter((v, i, a) => a.indexOf(v) === i)
}
}
}
</script>
<style>
div.row.div {
display: flex;
justify-content: space-between;
}
</style>

View File

@ -1,15 +1,31 @@
<!--
Copyright 2020 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>
<el-dialog :title="title" :visible.sync="visible" width="30%" center>
<el-dialog :title.sync="title" :visible.sync="visible" width="30%" center>
<span v-if="max>0">
<el-progress :percentage="Number(((progress / max)*100).toPrecision(3))" v-if="max>0"></el-progress>
</span>
<span v-if="results.length>0" style="color: red">{{results.length}} Errors please correct first</span><br/>
<span style="center">E-Mail : {{this.$store.state.Settings.settings.email}}</span><br/>
<span style="center"><el-checkbox v-model="gplv2">I agree to release the groundnet under GPL v2</el-checkbox></span><br/>
<span style="center" v-if="message">{{message}}</span><br/>
<span style="error" v-if="error">{{message}}</span><br/>
<span class="center">E-Mail : {{this.$store.state.Settings.settings.email}}</span><br/>
<span class="center"><el-checkbox v-model="gplv2" class="center">I agree to release the groundnet under GPL v2</el-checkbox></span><br/>
<span :class="textClass" v-if="message">{{message}}</span><br/>
<el-button @click="handleOkClicked('twr')" :disabled="!tower_comittable" >Upload Tower</el-button>
<el-button @click="handleOkClicked('groundnet')" :disabled="!groundnet_comittable" >Upload Groundnet</el-button>
<el-button @click="handleOkClicked('threshold')" :disabled="!threshold_comittable" >Upload Threshold</el-button>
<span slot="footer" class="dialog-footer">
<el-button @click="upload" :disabled="!comittable">Ok</el-button>
<el-button @click="closeClicked">{{buttonText}}</el-button>
</span>
</el-dialog>
</template>
@ -21,40 +37,91 @@
import axios from 'axios'
const fs = require('fs')
const path = require('path')
const mapper = require('../check/mapper');
export default {
name: 'upload',
props: [],
mounted () {
this.$store.watch(
function (state) {
return state.Loading.groundnetLoaded;
},
() => { if(this.$store.state.Loading.groundnetLoaded &&
this.$store.state.Loading.pavementLoaded &&
this.visible) this.check() }
,
{
deep: false
}
);
this.$store.watch(
function (state) {
return state.Loading.pavementLoaded;
},
() => { if(this.$store.state.Loading.groundnetLoaded &&
this.$store.state.Loading.pavementLoaded &&
this.visible) this.check() }
,
{
deep: false
}
);
},
data () {
return {
gplv2: false, message: null, error: false, progress: 0, max: 0, azure: false
gplv2: false, message: null, error: false, progress: 0, max: 0, azure: false, success: false, uploading: false, buttonText: 'Ok'
}
},
methods: {
reqListener(e) {
try {
if(JSON.parse(e.srcElement.response).status==='OK') {
this.message = null;
this.azure = true;
this.error = false;
} else {
this.message = 'Azure down';
}
} catch (error) {
console.error(error);
}
},
status () {
this.azure = false;
var xhr = new XMLHttpRequest();
var parent = this.$parent;
this.message = 'Checking for Groundweb health'
xhr.open('GET', 'http://groundweb.azurewebsites.net/groundnets/status', true);
xhr.onreadystatechange = function () {
if (xhr.status !== 200){
parent.$refs.upload.message = 'Azure down';
parent.$refs.upload.error = true;
console.error(xhr);
}
}
xhr.addEventListener("load", this.reqListener);
try {
xhr.send();
} catch (err) {
console.error(err);
this.error = true;
}
},
upload () {
closeClicked () {
Vue.set(this.$parent, 'uploadVisible', false)
return;
},
handleOkClicked (type) {
this.uploading = true;
var f = path.join(this.$store.state.Settings.settings.airportsDirectory,
this.icao[0],
this.icao[1],
this.icao[2],
this.icao + '.groundnet.new.xml');
this.icao + `.${type}.new.xml`);
if (f == null || !fs.existsSync(f)) {
this.message = 'File doesn\'t exist';
@ -72,25 +139,35 @@
var formData = new FormData();
formData.append("gpl", this.gplv2 )
formData.append("user_email", this.$store.state.Settings.settings.email)
formData.append('groundnet', blob, this.icao + '.groundnet.xml');
formData.append('groundnet', blob, this.icao + `.${type}.xml`);
var parent = this.$parent;
var messageField = this.message;
// action after uploading happens
xhr.onreadystatechange = function () {
if (xhr.status !== 200){
parent.$refs.upload.message = 'Upload Error'
parent.$refs.upload.error = true;
console.error(xhr);
}
}
xhr.onload = function(e) {
console.log("File uploading completed! ");
console.log(e);
parent.$refs.upload.uploading = false
if (e.srcElement.status===500) {
parent.$refs.upload.message == e.srcElement.statusText
} else if(JSON.parse(e.srcElement.response).message.match('[A-Z0-9]* Imported Successfully')) {
Vue.set(parent, 'uploadVisible', false)
parent.$refs.upload.success = true
parent.$refs.upload.message = `${type} Uploaded Successfully`
parent.$store.commit('UPLOAD_WIP', parent.$store.state.Airports.currentAirport.icao)
} else if(JSON.parse(e.srcElement.response).message === 'XML Errors') {
var response = JSON.parse(e.srcElement.response);
if (response.validationErrors) {
parent.$refs.upload.message = 'XML Errors : \n';
response.validationErrors.forEach(element => {
parent.$refs.upload.message += element.message + '\r\n';
parent.$refs.upload.message += element.message + '\n';
});
}
} else if(JSON.parse(e.srcElement.response) !== undefined) {
@ -116,45 +193,22 @@
view.scanning = Boolean(workery.checking)
workery.view = view
}
}, 1000)
},
featuresMapper(o) {
if (o instanceof L.ParkingSpot) {
return { 'index': Number(o['id']),
'_leaflet_id': o._leaflet_id,
'type': 'parking',
'parkingType': o.options.attributes.type,
'name': o.options.attributes.name,
'radius': String(o.options.attributes.radius),
'lat': o._latlng.lat,
'lng': o._latlng.lng,
'box': o.box!==undefined?o.box.getLatLngs():null
};
} else if (o instanceof L.RunwayNode) {
console.log(o)
return { 'index': Number(o['glueindex']), '_leaflet_id': o._leaflet_id, 'type': 'runway' };
} else if (o instanceof L.HoldNode) {
console.log(o)
return { 'index': Number(o['glueindex']), '_leaflet_id': o._leaflet_id, 'type': o.holdPointType };
} else if (o instanceof L.Polyline) {
console.log(o)
var latLngs = o.getLatLngs().map(l => ({lat: l.lat, lng: l.lng, index: l.attributes.index}));
return { 'start': Number(o['begin']), 'end': Number(o['end']), '_leaflet_id': o._leaflet_id, 'type': 'poly', 'isPushBackRoute': o.options.attributes.isPushBackRoute, latLngs: latLngs };
} else {
console.log('Unknown Type ')
console.log(typeof o)
}
}, 500)
},
check () {
try {
if(!(this.$store.state.Loading.groundnetLoaded &&
this.$store.state.Loading.pavementLoaded)) {
return
}
this.scanning = true
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/src/renderer/utils/check.js`
: `file://${process.resourcesPath}/workers/check.js`
console.log('make a check worker: ', path.resolve(__dirname, 'check.js'))
console.debug('make a check worker: ', path.resolve(__dirname, 'check.js'))
const worker = new Worker(winURL)
console.log(fileUrl('src/renderer/utils/check.js'))
console.debug(fileUrl('src/renderer/utils/check.js'))
worker.checking = this.checking
worker.max = this.max
@ -163,15 +217,32 @@
worker.progress = 0
// var worker = new Worker(fileUrl('src/renderer/utils/worker.js'))
this.worker = worker
var xml = []
this.$parent.$parent.$parent.$refs.editLayer.groundnetLayerGroup.eachLayer(l => {
var groundnet = []
if (!this.editLayer().groundnetLayerGroup) {
this.message = 'Groundnet not visible'
}
if (!this.pavementLayer().pavement) {
this.message = 'Pavement not visible'
}
this.editLayer().groundnetLayerGroup.eachLayer(l => {
console.log(l)
xml.push(l)
if (l instanceof L.Polyline) {
l._latlngs[0].glueindex = this.begin;
l._latlngs.slice(-1)[0].glueindex = this.end;
l.extensions(this)
}
groundnet.push(l)
})
var features = groundnet.map(mapper.checkMapper).filter(n => n)
var pavement = []
this.pavementLayer().pavement.eachLayer(l => {
console.log(l)
pavement.push(l)
})
var features2 = pavement.map(mapper.checkMapper).filter(n => n)
var features = xml.map(this.featuresMapper).filter(n => n)
worker.postMessage(['check', features ] )
worker.postMessage(['check', features.concat(features2) ] )
this.pollData()
// the reply
var store = this.$store
@ -181,7 +252,6 @@
this.max = 4
} else if (e.data[0] === 'DONE') {
console.log('DONE')
store.dispatch('setResults', e.data[1])
worker.terminate()
worker.view.max = 0
worker.view.checkDialogVisible = false
@ -200,6 +270,24 @@
} catch (err) {
console.error(err)
}
},
editLayer () {
var parent = this.$parent;
while (!parent.icao||parent.$refs.editLayer==undefined) {
parent = parent.$parent;
if (parent.icao&&parent.$refs.editLayer!==undefined) {
return parent.$refs.editLayer;
}
}
},
pavementLayer () {
var parent = this.$parent;
while (!parent.icao||parent.$refs.pavementLayer==undefined) {
parent = parent.$parent;
if (parent.icao&&parent.$refs.pavementLayer!==undefined) {
return parent.$refs.pavementLayer;
}
}
}
},
computed: {
@ -213,28 +301,65 @@
Vue.set(this.$parent, 'uploadVisible', newValue)
}
},
textClass: function () {
return !this.error?'centermessage':'error'
},
title: function () {
return `Upload ${this.$parent.$parent.$parent.icao} to groundweb.`
return `Upload ${this.icao} to groundweb.`
},
icao: {
get: function () {
return this.$parent.$parent.$parent.icao
var parent = this.$parent;
while (!parent.icao) {
parent = parent.$parent;
if (parent.icao) {
return parent.icao;
}
}
return this.$store.state.Airports.currentAirport.icao
},
set: function (newValue) {
console.error('ICAO being set ' + newValue);
}
},
tower_comittable: function () {
var f = path.join(this.$store.state.Settings.settings.airportsDirectory,
this.icao[0],
this.icao[1],
this.icao[2],
this.icao + '.twr.new.xml');
return fs.existsSync(f) && this.gplv2 && this.max === 0 && this.azure && !this.uploading;
},
groundnet_comittable: function () {
var f = path.join(this.$store.state.Settings.settings.airportsDirectory,
this.icao[0],
this.icao[1],
this.icao[2],
this.icao + '.groundnet.new.xml');
return fs.existsSync(f) && this.$store.state.Check.results.filter(a => a.id>=0).length === 0 && this.gplv2 && this.max === 0 && this.azure && !this.uploading
},
threshold_comittable: function () {
var f = path.join(this.$store.state.Settings.settings.airportsDirectory,
this.icao[0],
this.icao[1],
this.icao[2],
this.icao + '.threshold.new.xml');
return fs.existsSync(f) && this.gplv2 && this.max === 0 && this.azure && !this.uploading
},
comittable: function () {
return this.$store.state.Check.results.filter(a => a.id>=0).length === 0 && this.gplv2 && this.max === 0 && this.azure
return this.$store.state.Check.results.filter(a => a.id>=0).length === 0 && this.gplv2 && this.max === 0 && this.azure && !this.uploading
},
results: function () {
return this.$store.state.Check.results.filter(a => a.id>=0)
}
}
},
}
</script>
<style scoped lang="scss">
.center { text-align: center; vertical-align: middle;}
.error { text-align: center; background-color: red;}
.el-dialog__body {padding: 10px;}
.center { text-align: center; vertical-align: middle; padding: 5px; font-size: 12pt; font-weight: normal}
.centermessage { text-align: left; vertical-align: middle; padding: 5px; font-size: 12pt; font-weight: normal; white-space: pre-line;}
.error { text-align: center; color: red; padding: 5px; font-size: 12pt; font-weight: normal;}
.el-dialog--center .el-dialog__body { padding: 5px;}
</style>

View File

@ -0,0 +1,14 @@
/*
Copyright 2020 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/.
*/
import Vue from 'vue'
export const EventBus = new Vue()

View File

@ -0,0 +1,41 @@
/*
Copyright 2020 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 turf = require('@turf/turf')
L.RunwayPolygon = L.Polygon.extend({
turfyRunway: [],
setTurfy: function (runwayPoints) {
var latLngs = runwayPoints.map(this.turfToLatLng);
latLngs.push(latLngs[0]);
this.turfyRunway = turf.polygon([latLngs]);
},
turfToLatLng: function (turfPoint) {
return [turfPoint.lng, turfPoint.lat];
}
});
var runwayPoly = function (runwayPoints) {
var runwayPoly = new L.RunwayPolygon(runwayPoints);
runwayPoly.setStyle({ color: 'grey', fillColor: 'grey', opacity: 0.5, fillOpacity: 0.5, interactive: false });
runwayPoly.setTurfy(runwayPoints);
console.debug(runwayPoints);
return runwayPoly;
}
module.exports = runwayPoly;

View File

@ -0,0 +1,41 @@
/*
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 turf = require('@turf/turf')
L.TakeoffPolygon = L.Polygon.extend({
turfyRunway: [],
setTurfy: function (padPoints) {
var latLngs = padPoints.map(this.turfToLatLng);
latLngs.push(latLngs[0]);
this.turfyRunway = turf.polygon([latLngs]);
},
turfToLatLng: function (turfPoint) {
return [turfPoint.lng, turfPoint.lat];
}
});
var takeoffPadPoly = function (padPoints) {
var takeoffPadPoly = new L.TakeoffPolygon(padPoints);
takeoffPadPoly.setStyle({ color: 'black', fillColor: '', opacity: 1.0, fillOpacity: 0.0, interactive: false });
takeoffPadPoly.setTurfy(padPoints);
console.debug(padPoints);
return takeoffPadPoly;
}
module.exports = takeoffPadPoly;

View File

@ -0,0 +1,25 @@
/*
Copyright 2020 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 */
L.TaxiwayPolygon = L.Polygon.extend({
});
var taxiwayPoly = function (runwayPoints) {
var taxiwayPoly = new L.TaxiwayPolygon(runwayPoints);
taxiwayPoly.setStyle({ color: 'grey', fillColor: 'grey', opacity: 0.3, fillOpacity: 0.3, interactive: false });
return taxiwayPoly;
}
module.exports = taxiwayPoly;

View File

@ -95,6 +95,7 @@ var holdNode = function (n, layerGroup) {
});
const node = new L.HoldNode([latlon.decimalLatitude, latlon.decimalLongitude], { icon: icon });
node.glueindex = n.attr('index');
node.feature = { properties: { searchTerm: n.attr('index')}};
node.holdPointType = n.attr('holdPointType');
node.addTo(layerGroup);
node.addListeners();

View File

@ -96,6 +96,10 @@ L.ParkingSpot = L.Circle.extend({
this.updateWheelPos();
this.updateBox();
},
updateType(type) {
this.options.attributes.type = type;
this.deselect();
},
// Update the direction vertex from the direction
updateVertexFromDirection() {
if (this.editEnabled()) {
@ -137,6 +141,7 @@ L.ParkingSpot = L.Circle.extend({
if(this.frontWheel!==undefined) {
this.frontWheel.setLatLng(this.turfToLatLng(frontWheelEnd));
return this.turfToLatLng(frontWheelEnd);
}
}
},
@ -190,10 +195,13 @@ L.ParkingSpot = L.Circle.extend({
}
event.target._parkingSpot.select();
});
if(this.editor && this.editor.editLayer) {
this.box.addTo(this.editor.editLayer);
}
}
if(this.box!==undefined) {
var latlngs = [leftBack, rightBack, rightMiddle, rightIntermediate, rightFront, leftFront, leftIntermediate, leftMiddle].map(l => this.turfToLatLng(l));
console.debug(latlngs);
this.box.setLatLngs(latlngs);
}
}
@ -216,26 +224,59 @@ L.ParkingSpot = L.Circle.extend({
if(this.direction) {
this.direction.setStyle(style);
this.frontWheel.setStyle(style);
}
var wheelPos = this.updateWheelPos();
if(wheelPos) {
store.default.dispatch('setParkingNoseCoords', wheelPos.lat.toFixed(6) + ' ' + wheelPos.lng.toFixed(6));
}
this.updateBox();
if(this.box) {
this.box.setStyle(style);
}
}
this.updateWheelPos();
this.updateBox();
},
deselect() {
var style = {};
if(this.options.attributes.type == 'ga') {
style['color'] = 'green';
} else if(this.options.attributes.type == 'cargo') {
style['color'] = 'yellow';
} else if(this.options.attributes.type == 'gate') {
style['color'] = '#3388ff';
} else if(this.options.attributes.type == 'mil-fighter') {
style['color'] = 'red';
} else if(this.options.attributes.type == 'mil-cargo') {
style['color'] = 'DarkRed';
} else {
style['color'] = '#3388ff';
}
this.setStyle(style);
if(this.direction) {
this.direction.setStyle(style);
this.frontWheel.setStyle(style);
if(this.box) {
this.box.setStyle(style);
}
}
this.updateWheelPos();
this.updateBox();
if(this.box) {
this.box.setStyle(style);
}
},
setInteractive(interactive) {
if (interactive) {
if(this.direction&&this.direction._path) {
L.DomUtil.addClass(this.direction._path, 'leaflet-interactive');
}
if(this.box&&this.box._path) {
L.DomUtil.addClass(this.box._path, 'leaflet-interactive');
}
} else {
if(this.direction&&this.direction._path) {
L.DomUtil.removeClass(this.direction._path, 'leaflet-interactive');
}
if(this.box&&this.box._path) {
L.DomUtil.removeClass(this.box._path, 'leaflet-interactive');
}
}
},
addListeners: function () {
this.on('editable:drawing:move', function (event) {
@ -257,14 +298,15 @@ L.ParkingSpot = L.Circle.extend({
}
});
this.on('add', function (event) {
console.log(event);
console.debug(event);
event.target.updateBox();
if(event.target.box !== undefined) {
event.target.box.addTo(event.target._map);
}
event.target.setInteractive(false);
});
this.on('remove', function (event) {
console.log(event);
console.debug(event);
if(event.target.box !== undefined) {
event.target.box.removeFrom(event.target._map);
}
@ -276,7 +318,8 @@ L.ParkingSpot = L.Circle.extend({
console.debug("DragEnd Parking : ", event);
store.default.dispatch('setParking', event.target.options.attributes);
store.default.dispatch('setParkingCoords', event.target.getLatLng().lat.toFixed(6) + ' ' + event.target.getLatLng().lng.toFixed(6));
event.target.updateWheelPos();
var wheelPos = event.target.updateWheelPos();
store.default.dispatch('setParkingNoseCoords', wheelPos.lat.toFixed(6) + ' ' + wheelPos.lng.toFixed(6));
event.target.updateBox();
/*
store.default.dispatch('setParkingHeading', this.options.attributes.heading)
@ -330,10 +373,6 @@ L.ParkingSpot = L.Circle.extend({
}
});
}
store.default.dispatch('setParking', this.options.attributes);
store.default.dispatch('setParkingCoords', this.getLatLng().lat.toFixed(6) + ' ' + this.getLatLng().lng.toFixed(6));
this.select();
},
turfToLatLng: function (turfPoint) {
@ -413,12 +452,14 @@ var parkingSpot = function (n, layerGroup) {
var latlon = convert(n.attr('lat') + " " + n.attr('lon'));
//console.log(latlon.decimalLatitude);
//console.log(convert(n.attr('lat') + " " + n.attr('lon')).decimalLongitude);
const circle = new L.ParkingSpot([latlon.decimalLatitude, latlon.decimalLongitude], { radius: n.attr('radius'), attributes: {} });
circle.on('editable:enable', function (event) {
const parking = new L.ParkingSpot([latlon.decimalLatitude, latlon.decimalLongitude], { radius: n.attr('radius'), attributes: {} });
parking.on('editable:enable', function (event) {
// event.target.createDirection();
});
circle.id = n.attr('index');
circle.glueindex = n.attr('index');
parking.id = n.attr('index');
parking.glueindex = n.attr('index');
parking.feature = { properties: { searchTerm: n.attr('index') + ' ' + n.attr('name')}};
/*
<Parking index="2"
type="gate"
@ -434,17 +475,18 @@ airlineCodes="VIR,KAL,DAL,KLM" />
//circle.attributes = { type: n.attr('type'), name: n.attr('name'), radius: Number(n.attr('radius')), airlineCodes: n.attr('airlineCodes'), heading: Number(n.attr('heading')) };
$.each( n.attrs, function( key, value ) {
console.debug( '$', circle.id, key , value);
console.debug( '$', parking.id, key , value);
if(isNaN(value))
circle.options.attributes[ key ] = value;
parking.options.attributes[ key ] = value;
else
circle.options.attributes[ key ] = Number( value);
parking.options.attributes[ key ] = Number( value);
});
circle.addListeners();
parking.addListeners();
circle.addTo(layerGroup);
return circle;
parking.addTo(layerGroup);
parking.deselect();
return parking;
}
module.exports = parkingSpot;

View File

@ -26,6 +26,20 @@ L.RunwayNode = L.Marker.extend({
store.default.dispatch('setRunway', event.target.options.attributes);
}
});
this.on('add', function (event) {
event.target.setInteractive(false);
});
},
setInteractive(interactive) {
if (interactive) {
if(this._icon) {
L.DomUtil.addClass(this._icon, 'leaflet-interactive');
}
} else {
if(this._icon) {
L.DomUtil.removeClass(this._icon, 'leaflet-interactive');
}
}
},
select() {
try {
@ -106,6 +120,8 @@ var runwayNode = function (n, layerGroup) {
});
var node = new L.RunwayNode([latlon.decimalLatitude, latlon.decimalLongitude], { icon: icon, attributes: {} });
node.glueindex = n.attr('index');
node.feature = n.attr('index');
$.each( n.attrs, function( key, value ) {
if(isNaN(value))
node.options.attributes[ key ] = value;

View File

@ -3,19 +3,25 @@ const Vue = require('vue');
var L = require('leaflet');
const store = require('../store');
const util = require('util');
const assign = require('core-js/fn/object/assign');
exports.extendTaxiSegment = function (taxiwaySegment) {
const extendTaxiSegment = function (taxiwaySegment) {
taxiwaySegment.__proto__.begin;
taxiwaySegment.__proto__.end;
taxiwaySegment.__proto__.bidirectional;
taxiwaySegment.__proto__.updateBeginVertex = function (latlng) {
if (this._latlngs[0].__vertex) {
this._latlngs[0].__vertex.setLatLng(latlng);
this.selectVertex(Number(this._latlngs[0].glueindex));
}
};
taxiwaySegment.__proto__.updateEndVertex = function (latlng) {
if (this._latlngs[1].__vertex) {
this._latlngs[1].__vertex.setLatLng(latlng);
this.selectVertex(Number(this._latlngs[1].glueindex));
}
};
@ -33,6 +39,7 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
this.editLayer = editLayer;
this._latlngs[0].glueindex = this.begin;
this._latlngs.slice(-1)[0].glueindex = this.end;
if (this.featureLookup) {
if (typeof this.featureLookup[this.begin] === 'undefined') {
this.featureLookup[this.begin] = new Array();
}
@ -42,15 +49,16 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
this.featureLookup[this.begin].push(this);
this.featureLookup[this.end].push(this);
this.bidirectional = true;
}
};
taxiwaySegment.__proto__.select = function () {
this.options.attributes.selected = true;
this.updateStyle();
};
taxiwaySegment.__proto__.selectVertex = function () {
taxiwaySegment.__proto__.selectVertex = function (index) {
this.getLatLngs().forEach(element => {
if (Number(element.glueindex) === store.default.state.Editable.index) {
if (element.__vertex._icon != null) {
if (Number(element.glueindex) === index) {
if (element.__vertex !== undefined && element.__vertex._icon != null) {
element.__vertex.__proto__.deselect = function () {
if (this._icon != null) {
this._icon.style.setProperty('background-color', 'white');
@ -119,7 +127,8 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
});
}
event.target.select();
console.log("Click : " + event.target);
console.debug("Click : " + util.inspect(event.originalEvent));
if (!event.originalEvent.ctrlKey) {
if (store.default.state.Editable.data.arc === undefined ||
store.default.state.Editable.data.arc !== event.target.options.attributes) {
if (event.target.options.attributes === undefined) {
@ -132,29 +141,138 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
event.target.options.attributes.selected = true;
store.default.dispatch('setArc', event.target.options.attributes);
}
} else {
var arcs = event.target.expandArc(event.target.options.attributes);
var multiarc = { name: '', index: 900719925474099, ids: [], isPushBackRoute: null, direction: null };
if (store.default.state.Editable.data.multiarc === undefined ||
store.default.state.Editable.data.multiarc !== event.target.options.attributes) {
if (event.target.options.attributes === undefined) {
event.target.options.attributes = {};
}
event.target.options.attributes.index = event.target._leaflet_id;
this.editLayer.featureLookup[event.target._leaflet_id] = [];
this.featureLookup[event.target._leaflet_id].push(this);
event.target.options.attributes.selected = true;
//multiarc.name = JSON.parse(JSON.stringify(event.target.options.attributes.name));
//multiarc.isPushBackRoute = JSON.parse(JSON.stringify(event.target.options.attributes.isPushBackRoute));
//multiarc.direction = JSON.parse(JSON.stringify(event.target.options.attributes.direction));
if (event.target.options.attributes.name !== undefined) {
multiarc.name = assign(event.target.options.attributes.name);
}
if (event.target.options.attributes.isPushBackRoute) {
multiarc.isPushBackRoute = assign(event.target.options.attributes.isPushBackRoute);
} else {
multiarc.isPushBackRoute = false;
}
multiarc.direction = assign(event.target.options.attributes.direction);
this.editLayer.featureLookup[900719925474099] = [];
multiarc.ids = [];
//TODO
store.default.dispatch('setMultiArc', multiarc);
}
var editLayer = this.editLayer;
arcs.forEach(id => {
console.debug(id);
var arc = editLayer.groundnetLayerGroup.getLayer(id);
if (arc && arc instanceof L.Polyline) {
editLayer.featureLookup[900719925474099].push(arc);
arc.select();
}
});
store.default.dispatch('setMultiArcIds', arcs);
}
});
this.on('editable:drawing:move', function (event) {
if (dragIndex >= 0) {
this.selectVertex(dragIndex);
console.log('GlueDrag : ' + dragIndex + '\t' + event.target.dragIndex);
this.follow(dragIndex, event);
}
});
this.on('editable:middlemarker:mousedown', event => {
console.debug('editable:middlemarker:mousedown');
});
this.on('editable:vertex:new', event => {
console.log(event)
console.debug('editable:vertex:new ' + event.vertex.getIndex() + '\t' + event.vertex.getLastIndex() + '\t');
// Find nearest node
let isOnRunway = this.editLayer.isOnRunway(event.latlng)
let closest = this.editLayer.closestLayerSnap(event.latlng, 5)
let taxiwaySegment = event.latlng.__vertex.editor.feature;
let taxiwaySegment = event.vertex.editor.feature;
if (taxiwaySegment.options.attributes === undefined) {
taxiwaySegment.options.attributes = { direction: 'bi-directional' };
}
var isOnRunwayNum = 0;
if (isOnRunway) {
isOnRunwayNum = 1;
}
taxiwaySegment.updateStyle();
if (event.vertex.getIndex() !== 0 && event.vertex.getIndex() !== event.vertex.getLastIndex()) {
var x = taxiwaySegment.getLatLngs().filter(l => l === event.vertex.latlng);
// Somehow the latlng is not in our Segment!?
if (taxiwaySegment.getLatLngs().length < 3 && x.length === 0) {
var fixed = taxiwaySegment.getLatLngs();
fixed.splice(1, 0, event.vertex.latlng);
taxiwaySegment.setLatLngs(fixed);
}
var nextIndex = ++taxiwaySegment.editLayer.groundnetLayerGroup.maxId;
var splitOffNodes = taxiwaySegment.getLatLngs().splice(-1);
var remainingNodes = taxiwaySegment.getLatLngs();
if (remainingNodes.length <= 1 || !remainingNodes[1]) {
console.error('Not enough remaining nodes', remainingNodes);
}
splitOffNodes.unshift(L.latLng(remainingNodes[1].lat, remainingNodes[1].lng, remainingNodes[1].alt));
remainingNodes[1]['glueindex'] = nextIndex;
remainingNodes[1].attributes = { index: nextIndex, isOnRunway: isOnRunwayNum };
taxiwaySegment.options.attributes.end = nextIndex;
splitOffNodes[0]['glueindex'] = nextIndex;
splitOffNodes[0].attributes = { index: nextIndex, isOnRunway: isOnRunwayNum };
taxiwaySegment.setLatLngs(remainingNodes);
//taxiwaySegment.editor.refresh();
//taxiwaySegment.editor.reset();
if (splitOffNodes.length > 1) {
var polyline = new L.Polyline(splitOffNodes, { attributes: {} });
polyline.addTo(taxiwaySegment.editLayer.$parent.$parent.$refs.map.mapObject);
polyline.addTo(taxiwaySegment.editLayer.groundnetLayerGroup);
extendTaxiSegment(polyline);
polyline.addListeners();
polyline.setEditlayer(taxiwaySegment.editLayer);
polyline.enableEdit(taxiwaySegment.editLayer.$parent.$parent.$refs.map.mapObject);
polyline.editor.refresh();
//polyline.editor.reset();
polyline.featureLookup = this.featureLookup;
polyline.feature = { properties: {searchTerm: 'Arc ' + nextIndex + '-' + taxiwaySegment.end}};
polyline.options.attributes.name = taxiwaySegment.options.attributes.name;
polyline.options.attributes.direction = taxiwaySegment.options.attributes.direction;
polyline.options.attributes.isPushBackRoute = taxiwaySegment.options.attributes.isPushBackRoute;
polyline.options.attributes.begin = nextIndex;
polyline.options.attributes.end = taxiwaySegment.end;
polyline.updateStyle();
polyline.begin = nextIndex;
polyline.end = taxiwaySegment.end;
taxiwaySegment.end = nextIndex;
this.editLayer.featureLookup[nextIndex] = [];
this.featureLookup[nextIndex].push(taxiwaySegment);
this.featureLookup[nextIndex].push(polyline);
} else {
console.error('SplitoffNodes Short ', splitOffNodes);
}
} else {
// Glue to another node
if (closest) {
event.latlng['glueindex'] = Number(closest.glueindex);
event.latlng.__vertex.setLatLng(closest.latlng);
event.latlng.attributes = { index: event.latlng.glueindex, isOnRunway: 0 };
event.latlng.attributes = { index: event.latlng.glueindex, isOnRunway: isOnRunwayNum };
// Push Vertex to lookup
this.editLayer.featureLookup[event.latlng.glueindex].push(event.latlng.__vertex);
if (isOnRunwayNum == 1) {
this.editLayer.addRunwayNode(event.latlng, event.latlng['glueindex'])
}
if (taxiwaySegment.options.attributes.begin === undefined) {
taxiwaySegment.options.attributes.begin = event.latlng['glueindex']
} else {
@ -164,13 +282,16 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
taxiwaySegment.begin = closest.glueindex;
}
taxiwaySegment.end = closest.glueindex;
console.log(`Closest : ${closest}`)
console.debug(`Closest : ${closest}`)
} else {
event.vertex.latlng['glueindex'] = ++this.editLayer.groundnetLayerGroup.maxId;
event.vertex.latlng.attributes = { index: event.vertex.latlng.glueindex, isOnRunway: 0 };
event.vertex.latlng.attributes = { index: event.vertex.latlng.glueindex, isOnRunway: isOnRunwayNum };
this.editLayer.featureLookup[event.vertex.latlng.glueindex] = [];
this.editLayer.featureLookup[event.vertex.latlng.glueindex].push(event.vertex);
this.editLayer.featureLookup[event.vertex.latlng.glueindex].push(taxiwaySegment);
if (isOnRunwayNum == 1) {
this.editLayer.addRunwayNode(event.latlng, event.vertex.latlng['glueindex'])
}
// taxiwaySegment.editor.refresh();
//taxiwaySegment.editor.reset();
if (taxiwaySegment.options.attributes.begin === undefined) {
@ -183,13 +304,22 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
taxiwaySegment.end = Number(event.vertex.latlng.glueindex);
}
}
}
//this.splitShape(taxiwaySegment.getLatLngs(), )
});
this.on('editable:vertex:deleted', event => {
console.log('editable:vertex:deleted' + event)
console.debug('editable:vertex:deleted')
});
this.on('editable:vertex:mousedown', event => {
console.debug('editable:vertex:mousedown')
event.layer.editor.map.fire('mousedown', event);
});
this.on('editable:vertex:click', event => {
console.debug('editable:vertex:click')
});
this.on('editable:vertex:rawclick', event => {
console.debug('editable:vertex:rawclick')
event.cancel()
console.log(event)
});
this.on('editable:vertex:clicked', function (event) {
if (Number(store.default.state.Editable.index) >= 0 &&
@ -211,7 +341,7 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
} else {
this.editLayer.featureLookup[event.vertex.latlng.glueindex].forEach
store.default.dispatch('setNode', event.vertex.latlng)
this.selectVertex()
this.selectVertex(store.default.state.Editable.index)
}
}
});
@ -223,9 +353,30 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
return;
console.log("Drag Start : ", event.vertex.latlng.glueindex);
dragIndex = event.vertex.latlng.glueindex;
if (Number(store.default.state.Editable.index) >= 0 &&
this.featureLookup[store.default.state.Editable.index] !== undefined) {
this.featureLookup[store.default.state.Editable.index].forEach(element => {
if (element.deselect !== undefined) {
element.deselect();
}
});
}
if (!this.editor.map.editTools.drawing()) {
var hold = this.featureLookup[event.vertex.latlng.glueindex].filter(n => n instanceof L.HoldNode);
if (hold.length > 0) {
hold[0].select();
}
var parking = this.featureLookup[event.vertex.latlng.glueindex].filter(n => n instanceof L.ParkingSpot);
if (parking.length > 0) {
parking[0].selectParking();
} else {
this.selectVertex(Number(dragIndex))
}
}
});
this.on('editable:vertex:dragend', function (event) {
console.log("Dragend : ", event.vertex);
try {
if (dragIndex > 0) {
event.target.featureLookup[dragIndex].forEach(element => {
if (element instanceof L.ParkingSpot) {
@ -235,6 +386,9 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
});
}
dragIndex = -1;
if (!event.vertex.latlng.glueindex) {
console.error('GlueIndex not found : ', event.vertex);
}
var parking = this.featureLookup[event.vertex.latlng.glueindex].filter(n => n instanceof L.ParkingSpot);
if (parking.length > 0) {
parking[0].selectParking();
@ -253,13 +407,66 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
var lines = this.featureLookup[event.vertex.latlng.glueindex].filter(n => n instanceof L.Polyline);
Vue.default.nextTick(function () {
lines.forEach(line => {
line.selectVertex()
line.selectVertex(store.default.state.Editable.index)
});
})
}
} catch (error) {
console.error(error);
console.error(event.vertex.latlng.glueindex);
}
});
};
taxiwaySegment.__proto__.expandArc = function (attributes) {
var isPushBackRoute = attributes.isPushBackRoute;
var ids = [];
var walkedNodes = [];
var segmentIds = [];
console.debug('start Walk');
ids = ids.concat(this.walkPushbackRoute(attributes.begin, walkedNodes, isPushBackRoute));
ids = ids.concat(this.walkPushbackRoute(attributes.end, walkedNodes, isPushBackRoute));
return ids;
}
taxiwaySegment.__proto__.walkPushbackRoute = function (index, walkedNodes, isPushBackRoute) {
console.debug('Walk Level');
walkedNodes.push(index)
var segmentIds = [];
var polyLines = this.featureLookup[index].filter(n => n instanceof L.Polyline);
if (polyLines === undefined || polyLines.length > 2) {
console.debug('Walk ' + index + '\t' + polyLines.length);
return;
}
polyLines.forEach(l => {
segmentIds.push(l._leaflet_id);
console.debug('Walk Next ' + index + '\t' +
(walkedNodes.indexOf(index) < 0) + '\t' +
l.begin + '\t' +
(walkedNodes.indexOf(Number(l.begin)) < 0) + '\t' +
l.end + '\t' +
(walkedNodes.indexOf(Number(l.end)) < 0) + '\t' +
l.options.attributes.direction + '\t' +
l.options.attributes.begin + '\t' +
l.options.attributes.end);
console.debug('Walk isPushBackRoute ' + l.options.attributes.isPushBackRoute + '\t' + isPushBackRoute + '\t' + (l.options.attributes.isPushBackRoute === isPushBackRoute));
if (l.options.attributes.isPushBackRoute === isPushBackRoute) {
console.debug(Number(l.begin) === index && walkedNodes.indexOf(Number(l.end)) < 0);
if (Number(l.begin) === index && walkedNodes.indexOf(Number(l.end)) < 0) {
console.debug('Walk forward ' + l.options.attributes.direction);
segmentIds = segmentIds.concat(this.walkPushbackRoute(Number(l.end), walkedNodes, isPushBackRoute))
}
console.debug(Number(l.end) === index && walkedNodes.indexOf(Number(l.begin)) < 0);
if (Number(l.end) === index && walkedNodes.indexOf(Number(l.begin)) < 0) {
console.debug('Walk backward ' + l.options.attributes.direction);
segmentIds = segmentIds.concat(this.walkPushbackRoute(Number(l.begin), walkedNodes, isPushBackRoute))
}
}
});
return segmentIds;
}
/**
*
*/
@ -313,8 +520,58 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
})
};
taxiwaySegment.__proto__.updateArrows = function (zoom) {
if (this._map === null) {
return;
}
if (this.options.attributes.direction === 'forward') {
this.setText(null);
if (zoom <= 16) {
this.setText(' >', { repeat: true, offset: 6, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 15px serif;' } })
} else if (zoom <= 19) {
this.setText(' > ', { repeat: true, offset: 7, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 20px serif;' } })
} else {
this.setText(' > ', { repeat: true, offset: 10, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 30px serif;' } })
}
} else if (this.options.attributes.direction === 'backward') {
this.setText(null);
if (zoom <= 16) {
this.setText(' <', { repeat: true, offset: 6, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 15px serif;' } })
} else if (zoom <= 19) {
this.setText(' < ', { repeat: true, offset: 7, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 20px serif;' } })
} else {
this.setText(' < ', { repeat: true, offset: 10, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 30px serif;' } })
}
} else {
this.setText(null);
}
}
taxiwaySegment.__proto__.setInteractive = function (interactive) {
if (this.getLayers) {
this.getLayers().forEach(layer => {
layer.setInteractive(interactive);
});
return;
}
if (!this._path) {
return;
}
this.options.interactive = interactive;
if (interactive) {
L.DomUtil.addClass(this._path, 'leaflet-interactive');
} else {
L.DomUtil.removeClass(this._path, 'leaflet-interactive');
}
};
taxiwaySegment.__proto__.updateStyle = function () {
var style = {};
if (!this.options.attributes) {
return;
}
if (this.options.attributes.selected) {
style.color = 'red';
} else if (this.options.attributes.isPushBackRoute) {
@ -323,6 +580,11 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
else {
style.color = '#3388ff';
}
if (this.editEnabled()) {
style.interactive = true;
} else {
style.interactive = false;
}
this.setStyle(style);
if (this._map !== null) {
if (this.options.attributes.direction === 'forward') {
@ -330,7 +592,7 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
this.setText(' > ', { repeat: true, offset: 10, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 30px serif;' } })
} else if (this.options.attributes.direction === 'backward') {
this.setText(null);
this.setText(' < ', { repeat: true, offset: 10, attributes: { fill: 'red', style: 'vertical-align: bottom; font-weight: bold; font: bold 30px serif;' } })
this.setText(' < ', { repeat: true, offset: 10, attributes: { fill: 'red', style: 'vertical-align: bottom; vertical-align: bottom; font-weight: bold; font: bold 30px serif;' } })
} else {
this.setText(null);
}
@ -338,3 +600,5 @@ exports.extendTaxiSegment = function (taxiwaySegment) {
}
};
};
exports.extendTaxiSegment = extendTaxiSegment;

View File

@ -1,141 +1,164 @@
/* eslint-disable */
const convert = require('geo-coordinates-parser');
const leaflet = require('leaflet');
const turf = require('@turf/turf');
const util = require('util');
const store = require('../store');
var $ = require('jquery');
L.Threshold = L.Circle.extend({
select() {
var style = {};
style['color'] = 'red';
this.setStyle(style);
},
addListeners: function () {
this.on('editable:drawing:move', function (event) {
console.log("Move : ", event);
console.log("Move : ", event.latlng);
// Is it the edit vertex (Middle) moving?
if(event.target.editor._resizeLatLng.__vertex._icon !== event.sourceTarget._element){
event.target.setLatLng(event.latlng);
event.target.updateVertexFromDirection();
this.follow(event.target.id, event);
}
else if(event.target.editor._resizeLatLng.__vertex._icon === event.sourceTarget._element) {
event.target.updateDirectionFromVertex();
event.target.updateVertexFromDirection();
}
});
/*
this.on('editable:vertex:drag', function (event) {
console.log("Drag : ", event);
});
Copyright 2020 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/.
*/
this.on('click', function (event) {
console.log("Click : " + event.target);
store.default.dispatch('setParking', event.target.options.attributes);
this.select();
this.unwatch = store.default.watch(
function (state) {
return state.Editable.data.parking;
/* eslint-disable */
const convert = require('geo-coordinates-parser');
const fs = require('fs');
const path = require('path');
const store = require('../store');
const turf = require('@turf/turf');
/**http://wiki.openstreetmap.org/wiki/Zoom_levels*/
L.Threshold = L.Marker.extend({
heading: 0,
displacement: 0,
stopw_m: 0,
originLatLng: null,
rwy: '',
interactive: false,
stripSVG: function(fName) {
var rx = /<\s*svg[^>]*>([\s\S]*)<\s*\/svg[^>]*>/gm;
var svg = fs.readFileSync(path.join(__static, '/', fName), 'utf8');
var svg2 = rx.exec(svg);
return svg2[0];
},
() => {
if (event.target instanceof L.Threshold) {
event.target.setStyle({color : '#3388ff'});
this.unwatch();
updateIcon : function(map) {
console.debug(`Lat Lng Threshold ${this.getLatLng()}`);
if(map !== null) {
var metersPP = this.metersPerPixel(map.getCenter().lat, map.getZoom());
console.debug('Old Meters per pixel ' + this._metersPP);
console.debug('New Meters per pixel ' + metersPP);
if(this._metersPP != metersPP) {
var pixelSize = (this.iconSize/2) / metersPP;
var scale = pixelSize/this.iconSize;
var offset = 0;//-(this.iconSize/2);
this.setIcon(L.divIcon({
iconSize: 64,
className: 'threshold-marker-icon',
html: `<div style=\'transform: translateX(${offset}px) translateY(${offset}px) scale(${scale}) rotate(${this.heading}deg); border: 1px red\'>${this.svg}</div>`,
}));
this.setInteractive(this.interactive);
this.update(this.getLatLng());
this.setLatLng(this.getLatLng());
this._metersPP = metersPP;
}
}
,
{
deep: true //add this if u need to watch object properties change etc.
}
);
});
this.on('editable:vertex:clicked', function (event) {
console.log(this.featureLookup[event.vertex.glueindex]);
if(event.target.editor._resizeLatLng.__vertex._icon !== event.sourceTarget._element){
event.vertex._icon.style['background-color'] = 'red';
store.default.dispatch('setParking', event.target.options.attributes);
this.unwatch = store.default.watch(
function (state) {
return state.Editable.data.parking;
},
() => {
event.target.setStyle({color : '#3388ff'});
this.unwatch();
setInteractive(interactive) {
if (interactive) {
if(this._icon) {
L.DomUtil.addClass(this._icon, 'leaflet-interactive');
}
,
{
deep: true //add this if u need to watch object properties change etc.
} else {
if(this._icon) {
L.DomUtil.removeClass(this._icon, 'leaflet-interactive');
}
);
}
});
this.on('editable:disable', function (event) {
event.target.removeDirection();
});
this.interactive = interactive;
},
metersPerPixel: function (latitude, zoomLevel) {
var earthCircumference = 40075017;
var latitudeRadians = latitude * (Math.PI / 180);
return earthCircumference * Math.cos(latitudeRadians) / Math.pow(2, zoomLevel + 8);
},
updateStyle: function () {
pixelValue: function (latitude, meters, zoomLevel) {
return meters / metersPerPixel(latitude, zoomLevel);
},
setOrigin (originLatLng) {
this.originLatLng = originLatLng;
},
setHeading (heading) {
this.heading = Number(heading);
},
setRunway (rwy) {
this.rwy = Number(rwy);
},
setStopW (stopw_m) {
this.stopw_m = stopw_m;
},
setDisplacement(displacement) {
const turfOptions = { units: 'kilometers' };
this.displacement = Number(displacement);
var newPos = turf.destination([this.originLatLng[1], this.originLatLng[0]], displacement/1000, this.normalizeAngle(this.heading), turfOptions);
var newValue = {lat: newPos.geometry.coordinates[1].toFixed(6),
lng: newPos.geometry.coordinates[0].toFixed(6) };
console.debug(`Threshold Old : ${this.originLatLng} -> ${this.turfToLatLng(newPos)}`);
this.setLatLng(newValue);
},
normalizeAngle( angle ) {
if(angle >= 180) {
return angle - 360;
}
if(angle <= -180) {
return angle + 360;
}
return angle;
},
latToTurf (turfPoint) {
return [turfPoint.lng, turfPoint.lat];
},
latLngToTurf (turfPoint) {
return [turfPoint.decimalLongitude, turfPoint.decimalLatitude];
},
turfToLatLng: function (turfPoint) {
return {lat: turfPoint.geometry.coordinates[1], lng: turfPoint.geometry.coordinates[0]};
},
extensions: function (editLayer) {
this.createDirection();
if (typeof this.featureLookup[this.id] === 'undefined') {
this.featureLookup[this.id] = [];
return '' + turfPoint.geometry.coordinates[1].toFixed(6) + ',' + turfPoint.geometry.coordinates[0].toFixed(6);
}
this.featureLookup[this.id].push(this);
},
_getLatRadius: function () {
return this._mRadius;
},
_getLngRadius: function () {
return this._mRadius;
},
});
var threshold = function (n, layerGroup) {
//console.log(n.attr('lat') + " " + n.attr('lon'));
var latlon = convert(n.find('lat/text()').text() + " " + n.find('lon/text()').text());
//console.log(latlon.decimalLatitude);
//console.log(convert(n.attr('lat') + " " + n.attr('lon')).decimalLongitude);
const circle = new L.Threshold([latlon.decimalLatitude, latlon.decimalLongitude], { radius: 10, attributes: {} });
circle.on('editable:enable', function (event) {
// event.target.createDirection();
L.Threshold.addInitHook(function(){
this.svg = this.stripSVG('FGA_THR.svg');
this.iconSize = 500;
this.on('click', function (event) {
console.debug("Click Threshold : ", event);
store.default.dispatch('setThreshold', {rwy: event.target.rwy, displacement: event.target.displacement});
});
this.on('add', function (event) {
event.target.setInteractive(false);
});
});
//Builds a marker for a threshold
/*
<Parking index="2"
type="gate"
name="A6"
number=""
lat="N44 52.799"
lon="W93 11.947"
heading="-147.51"
radius="18"
pushBackRoute="541"
airlineCodes="VIR,KAL,DAL,KLM" />
<threshold>
<lon>13.517142</lon>
<lat>52.380125</lat>
<rwy>07L</rwy>
<hdg-deg>68.77</hdg-deg>
<displ-m>0.0</displ-m>
<stopw-m>160.0</stopw-m>
</threshold>
*/
//circle.attributes = { type: n.attr('type'), name: n.attr('name'), radius: Number(n.attr('radius')), airlineCodes: n.attr('airlineCodes'), heading: Number(n.attr('heading')) };
var threshold = function (n, options) {
var latlon = convert(n.find('lat/text()').text() + " " + n.find('lon/text()').text());
var rwy = n.find('rwy/text()').text();
var heading = n.find('hdg-deg/text()').text();
var displ_m = n.find('displ-m/text()').text();
var stopw_m = n.find('stopw-m/text()').text();
$.each( n.attrs, function( key, value ) {
console.log( '$', circle.id, key , value);
if(isNaN(value))
circle.options.attributes[ key ] = value;
else
circle.options.attributes[ key ] = Number( value);
});
circle.addTo(layerGroup);
return circle;
var marker = new L.Threshold([latlon.decimalLatitude, latlon.decimalLongitude],
{pane: 'threshold-pane'});
marker.setOrigin([latlon.decimalLatitude, latlon.decimalLongitude]);
marker.setHeading(heading);
marker.setDisplacement(displ_m);
marker.setRunway(rwy);
marker.setStopW(stopw_m);
return marker;
}
module.exports = threshold;

View File

@ -0,0 +1,116 @@
/*
Copyright 2020 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 convert = require('geo-coordinates-parser');
const fs = require('fs');
const path = require('path');
const store = require('../store');
/**http://wiki.openstreetmap.org/wiki/Zoom_levels*/
L.TowerMarker = L.Marker.extend({
options: {
zIndexOffset: 10000, draggable: 'true'
},
stripSVG: function(fName) {
var rx = /<\s*svg[^>]*>([\s\S]*)<\s*\/svg[^>]*>/gm;
var svg = fs.readFileSync(path.join(__static, '/', fName), 'utf8');
var svg2 = rx.exec(svg);
return svg2[0];
},
updateIcon : function(map) {
console.debug(`Lat Lng Tower ${this.getLatLng()}`);
if(map !== null) {
var metersPP = this.metersPerPixel(map.getCenter().lat, map.getZoom());
console.debug('Old Meters per pixel ' + this._metersPP);
console.debug('New Meters per pixel ' + metersPP);
if(this._metersPP != metersPP) {
var pixelSize = this.iconSize / metersPP;
var scale = pixelSize/this.iconSize;
var offset = 0;//-(this.iconSize/2);
this.setIcon(L.divIcon({
iconSize: 32,
className: 'threshold-marker-icon',
html: `<div style=\'transform: translateX(${offset}px) translateY(${offset}px) scale(${scale}); border: 1px red\'>${this.svg}</div>`,
}));
this.setInteractive(this.interactive);
this.update(this.getLatLng());
console.debug();
this.setLatLng(this.getLatLng());
this._metersPP = metersPP;
}
}
},
setInteractive(interactive) {
if (interactive) {
if(this._icon) {
L.DomUtil.addClass(this._icon, 'leaflet-interactive');
}
} else {
if(this._icon) {
L.DomUtil.removeClass(this._icon, 'leaflet-interactive');
}
}
this.interactive = interactive;
},
metersPerPixel: function (latitude, zoomLevel) {
var earthCircumference = 40075017;
var latitudeRadians = latitude * (Math.PI / 180);
return earthCircumference * Math.cos(latitudeRadians) / Math.pow(2, zoomLevel + 8);
},
pixelValue: function (latitude, meters, zoomLevel) {
return meters / metersPerPixel(latitude, zoomLevel);
},
setTowerHeight: function (height) {
this.elev_m = height;
}
});
L.TowerMarker.addInitHook(function(){
this.svg = this.stripSVG('tower.svg');
this.iconSize = 32;
this.on('click', function (event) {
store.default.dispatch('setTowerCoords',
event.target.getLatLng().lat.toFixed(6) + ' ' +
event.target.getLatLng().lng.toFixed(6) + ' ' +
event.target.elev_m);
store.default.dispatch('setTowerHeight', event.target.elev_m );
});
this.on('dragstart', function (event) {
console.debug("Drag Tower : ", event);
});
this.on('dragend', function (event) {
console.debug("DragEnd Tower : ", event);
store.default.dispatch('setTowerCoords',
event.target.getLatLng().lat.toFixed(6) + ' ' +
event.target.getLatLng().lng.toFixed(6) + ' ' +
event.target.elev_m);
store.default.dispatch('setTowerHeight', event.target.elev_m );
});
this.on('add', function (event) {
event.target.setInteractive(false);
});
});
//Builds a marker for a ai or multiplayer aircraft
var tower = function (n, options) {
var latlon = convert(n.find('lat/text()').text() + " " + n.find('lon/text()').text());
var marker = new L.TowerMarker([latlon.decimalLatitude, latlon.decimalLongitude], {pane: 'tower-pane'});
marker.elev_m = n.find('elev-m/text()').text();
return marker;
}
module.exports = tower;

View File

@ -29,21 +29,38 @@ function addFrequencies (type, value) {
}
exports.addFeature = function (feature) {
featureLookup[feature.id] = new Array();
featureLookup[feature.id] = [];
}
exports.readGroundnetXML = function (fDir, icao, force) {
exports.listSaves = function (fDir, icao) {
var directory = path.join(fDir, icao[0], icao[1], icao[2]);
var files = fs.readdirSync(directory);
var ret = files
.filter(f => f.includes(icao) )
.filter(f => f.includes('groundnet') )
.map(f => {
try {
var layerGroup = L.layerGroup();
layerGroup.maxId = 0;
var f = path.join(fDir, icao[0], icao[1], icao[2], icao + '.groundnet.xml');
var fNew = path.join(fDir, icao[0], icao[1], icao[2], icao + '.groundnet.new.xml');
if (f == null || (!fs.existsSync(f) && force)|| (!fs.existsSync(f) && !fs.existsSync(fNew) ))
return layerGroup;
if (fNew != null && fs.existsSync(fNew) && !force) {
f = fNew;
var fileDate = fs.lstatSync(path.join(directory, f));
return {file: f, mtime: `${fileDate.mtime}`, mtimeMs: `${fileDate.mtimeMs}`};
} catch (error) {
console.error(error);
}
});
ret.forEach( f => {
console.debug(f);
});
return ret;
}
exports.readGroundnetXML = function (fDir, icao, f) {
try {
store.default.dispatch('setGroundnetLoaded', false);
var layerGroup = L.layerGroup();
layerGroup.minId = 9999999999;
layerGroup.maxId = 0;
if (f == null || (!fs.existsSync(f) ))
return layerGroup;
var features = new Array();
@ -84,7 +101,7 @@ exports.readGroundnetXML = function (fDir, icao, force) {
store.default.dispatch('setFrequencies', frequencies);
var parkingNodes = xml.find('groundnet/parkingList/Parking');
console.log("Parking Nodes" + parkingNodes.length);
console.debug("Parking Nodes length" + parkingNodes.length);
var merged = new Array();
@ -97,15 +114,16 @@ exports.readGroundnetXML = function (fDir, icao, force) {
nodesLookup[n.attr('index')] = n;
featureLookup[n.attr('index')] = new Array();
featureLookup[n.attr('index')].push(circle);
layerGroup.minId = Math.min(layerGroup.minId, Number(n.attr('index')))
layerGroup.maxId = Math.max(layerGroup.maxId, Number(n.attr('index')))
features.push(circle);
}).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 - p2.number
return p1.number?p1.number.localeCompare(p2.number):-1;
} else {
return p1.name.localeCompare(p2.name)
}}));
@ -123,7 +141,9 @@ exports.readGroundnetXML = function (fDir, icao, force) {
}
//console.log(latlon.decimalLatitude);
layerGroup.minId = Math.min(layerGroup.minId, Number(n.attr('index')))
layerGroup.maxId = Math.max(layerGroup.maxId, Number(n.attr('index')))
console.debug(`Min Id : ${layerGroup.minId} Max Id : ${layerGroup.maxId} `);
nodesLookup[n.attr('index')] = n;
if (n.attr('isOnRunway') === '1') {
@ -174,7 +194,13 @@ exports.readGroundnetXML = function (fDir, icao, force) {
if (!bidirectional) {
var beginlatlon = convert(beginNode.attr('lat') + " " + beginNode.attr('lon'));
var endlatlon = convert(endNode.attr('lat') + " " + endNode.attr('lon'));
var polyline = new L.Polyline([[beginlatlon.decimalLatitude, beginlatlon.decimalLongitude], [endlatlon.decimalLatitude, endlatlon.decimalLongitude]], { attributes: {} }).addTo(layerGroup);
var pane = 'route-pane';
if(n.attr('isPushBackRoute') === '1') {
pane = 'pushback-pane';
}
var polyline = new L.Polyline([[beginlatlon.decimalLatitude, beginlatlon.decimalLongitude], [endlatlon.decimalLatitude, endlatlon.decimalLongitude]], { pane: pane, attributes: {} }).addTo(layerGroup);
extendTaxiSegment(polyline);
polyline.addListeners();
polyline._latlngs[0].attributes = {};
@ -208,6 +234,8 @@ exports.readGroundnetXML = function (fDir, icao, force) {
polyline.begin = beginNode.attr('index');
polyline.end = endNode.attr('index');
polyline.feature = { properties: { searchTerm: 'Arc ' + beginNode.attr('index') + '-' + endNode.attr('index')}};
// polyline.enableEdit();
// polyline.on('dblclick', function (event) { L.DomEvent.stop; polyline.toggleEdit; });
@ -224,7 +252,7 @@ exports.readGroundnetXML = function (fDir, icao, force) {
}
}
}).sort();
store.default.dispatch('setGroundnetLoaded', true);
return layerGroup;
});

View File

@ -18,6 +18,11 @@ var featureLookup = [];
var parkings = [];
var pushBackNodeLookup = [];
/**
* Walk nodes until the pushback node is found.
* @param {*} index
*/
function findRouteToPushback (index) {
if (featureLookup===undefined) {
return
@ -25,6 +30,7 @@ function findRouteToPushback (index) {
var walkedNodes = [index]
var pushBackNodes = []
walkPushbackRoute(index, walkedNodes, pushBackNodes)
return pushBackNodes
}
@ -51,23 +57,48 @@ function walkPushbackRoute (index, walkedNodes, pushBackNodes) {
});
}
/**
*
* @param {*} fDir The directory
* @param {*} icao
* @param {*} featureList
*/
exports.writeGroundnetXML = function (fDir, icao, featureList) {
try {
try { fs.mkdirSync(path.join(fDir), { recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0]),{ recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0], icao[1]), { recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0], icao[1], icao[2]), { recursive: true })} catch (err) { }
var fileNames = [];
for (let index = 1; index <= store.default.state.Settings.settings.numberOfSaves; index++) {
fileNames.push(path.join(fDir, icao[0], icao[1], icao[2], icao + `.groundnet.bak.${index}.xml`));
}
var f = path.join(fDir, icao[0], icao[1], icao[2], icao + '.groundnet.new.xml');
var fBak = path.join(fDir, icao[0], icao[1], icao[2], icao + '.groundnet.bak.xml');
if( fs.existsSync(f) ) {
fs.copyFileSync(f, fBak);
var previous = '';
fileNames.reverse().forEach(fBak => {
if (fs.existsSync(fBak) && previous !== '') {
console.debug( `Copy ${fBak} to ${previous}`);
fs.copyFileSync(fBak, previous);
}
previous = fBak;
});
fs.copyFileSync(f, previous);
}
if (f == null)
return;
pushBackNodeLookup = [];
console.debug(featureList);
var parkings = featureList.map(mapParkings).filter(n => n);
var runwayNodes = featureList.map(mapRunwayNodes).filter(n => n);
var holdNodes = featureList.map(mapHoldPoint).filter(n => n);
holdNodes.forEach(n => {
pushBackNodeLookup[n['@index']] = n;
});
@ -76,17 +107,19 @@ exports.writeGroundnetXML = function (fDir, icao, featureList) {
var arcList = [];
var frequencies = [];
var version = new Date().toUTCString() + ' by FlightgearAirports';
var version = new Date().toUTCString() + ' by FlightgearAirports ' + require('electron').remote.app.getVersion();
var name = store.default.state.Settings.settings.name;
featureLookup = [];
// Loaded segments
featureList.filter(o => o instanceof L.TaxiwaySegment).filter(n => n).forEach(element => {
var begin = mapBeginNode(element);
if(begin['@index']==="")
console.warn("Begin missing");
nodes[begin['@index']] = begin;
var end = mapEndNode(element);
if(end['@index']==="")
console.warn("End missing");
nodes[end['@index']] = end;
});
// New segments
@ -117,8 +150,13 @@ exports.writeGroundnetXML = function (fDir, icao, featureList) {
arcList.push(arc);
featureLookup[latlng.glueindex][startIndex] = arc;
}
if (currentArc.direction === '' || !currentArc.direction) {
console.error( "Arc without direction " + util.inspect(currentArc) );
}
}
startIndex = latlng.glueindex;
} else {
console.error( "LatLng without glueindex " + util.inspect(latlng) );
}
});
});
@ -129,7 +167,7 @@ exports.writeGroundnetXML = function (fDir, icao, featureList) {
});
// delete the parkings
// Find the index of the pushback node
parkings.forEach(n => {
nodes[n['@index']] = null;
var pushBackNode = findRouteToPushback(Number(n['@index']))[0];
@ -142,6 +180,7 @@ exports.writeGroundnetXML = function (fDir, icao, featureList) {
arcList = arcList.filter(a => a['@begin'] !== a['@end']);
nodes.sort((p, p2) => { return p['@index'] - p2['@index'] });
//console.debug(util.inspect(nodes));
var uniqueNodes = nodes.filter((v, i, a) => a.indexOf(v) === i);
var approachList = store.default.state.Frequencies.items.filter(f => f.type === 'APPROACH').map(mapFrequency);
@ -158,7 +197,54 @@ exports.writeGroundnetXML = function (fDir, icao, featureList) {
var unicomList = store.default.state.Frequencies.items.filter(f => f.type === 'UNICOM').map(mapFrequency);
var xmlObj = { groundnet: { version: version, name: name,
var gapStart = -1;
var gapEnd = -1;
do {
gapStart = -1;
gapEnd = -1;
var allIds = parkings.map(n => Number(n['@index']))
.concat(uniqueNodes.map(n => Number(n['@index'])))
.sort((a, b) => a - b);
allIds.forEach((element, index, array) => {
if (index > 0 && array[index-1] + 1 != element && gapStart == -1 ) {
gapStart = array[index-1];
gapEnd = element;
}
});
var gap = gapEnd - gapStart -1;
if ( gap >= 0 ) {
parkings = parkings.map(n => {
if (n['@index']>gapStart) {
n['@index'] = String(n['@index'] - gap);
}
if (n['@pushbackRoute']>gapStart) {
n['@pushbackRoute'] = String(n['@pushbackRoute'] - gap);
}
return n;
});
uniqueNodes = uniqueNodes.map(n => {
if (n['@index']>gapStart) {
n['@index'] = String(n['@index'] - gap);
}
return n;
});
arcList = arcList.map(n => {
if (n['@begin']>gapStart) {
n['@begin'] = String(n['@begin'] - gap);
}
if (n['@end']>gapStart) {
n['@end'] = String(n['@end'] - gap);
}
return n;
});
}
} while( gapStart > 0 && gapEnd > 0);
var xmlObj = { groundnet: { version: 1, fgaversion: version, name: name,
'frequencies': { APPROACH: approachList, DEPARTURE: departureList, AWOS: awosList, CLEARANCE: clearanceList, GROUND: groundList, TOWER: towerList, UNICOM: unicomList },
parkingList: { Parking: parkings }, TaxiNodes: { node: uniqueNodes }, TaxiWaySegments: { arc: arcList } } };
@ -181,12 +267,20 @@ var mapParkings = function (o) {
var lat = convertLat(o.getLatLng());
var lon = convertLon(o.getLatLng());
// <Parking index="0" type="gate" name="GA_Parking" lat="S9 25.739923" lon="E160 2.927602" heading="67" radius="44" airlineCodes="" />
var parking = { '@index': String(o['id']), '@type': o.options.attributes.type, '@name': o.options.attributes.name, '@lat': lat, '@lon': lon, '@heading': Number(o.options.attributes.heading%360).toFixed(1), '@radius': String(o.options.attributes.radius) };
var parking = { '@index': String(o['id']), '@type': o.options.attributes.type, '@lat': lat, '@lon': lon, '@heading': Number(o.options.attributes.heading%360).toFixed(1), '@radius': String(o.options.attributes.radius) };
if (o.options.attributes.name !== '' && o.options.attributes.name !== 'undefined' && o.options.attributes.name !== undefined) {
parking['@name'] = o.options.attributes.name;
}
if( o.options.attributes.airlineCodes) {
console.debug(o.options.attributes.airlineCodes);
parking['@airlineCodes'] = o.options.attributes.airlineCodes;
}
if(o.options.attributes.number) {
if(o.options.attributes.number !== undefined &&
typeof o.options.attributes.number === 'number' || (
typeof o.options.attributes.number === 'string' &&
o.options.attributes.number.trim() !== ''
)
) {
console.debug(o.options.attributes.number);
parking['@number'] = o.options.attributes.number;
}
@ -212,13 +306,17 @@ var mapRunwayNodes = function (o) {
var mapHoldPoint = function (o) {
if (o instanceof L.HoldNode) {
if( o['holdPointType'] === undefined )
{
console.error('Oh dear ' + o);
}
return { '@index': String(o['glueindex']), '@holdPointType': o['holdPointType'] };
}
}
var mapBeginNode = function (o) {
if (o instanceof L.TaxiwaySegment) {
console.debug(o);
console.debug('Map Begin : ', o['begin']);
// <Parking index="0" type="gate" name="GA_Parking" lat="S9 25.739923" lon="E160 2.927602" heading="67" radius="44" airlineCodes="" />
return { '@index': String(o['begin']), '@lat': convertLat(o._latlngs[0]), '@lon': convertLon(o._latlngs[0]), '@isOnRunway': '0', '@type': 'begin' };
}
@ -226,7 +324,7 @@ var mapBeginNode = function (o) {
var mapEndNode = function (o) {
if (o instanceof L.TaxiwaySegment) {
console.debug(o);
console.debug('Map End : ', o['end']);
// <Parking index="0" type="gate" name="GA_Parking" lat="S9 25.739923" lon="E160 2.927602" heading="67" radius="44" airlineCodes="" />
return { '@index': String(o['end']), '@lat': convertLat(o._latlngs[1]), '@lon': convertLon(o._latlngs[1]), '@isOnRunway': '0', '@type': 'end' };
}
@ -241,31 +339,33 @@ var mapVertexNode = function (l) {
}
var convertLat = function (latlng) {
console.debug(latlng.lat);
//console.debug(latlng.lat);
var NS = latlng.lat > 0 ? 'N' : 'S';
var deg = mathjs.floor(mathjs.abs(latlng.lat));
var min = (mathjs.abs(latlng.lat) - deg) * 60;
// console.debug(NS + deg + " " + min);
return NS + String(deg).padStart(2, '0') + " " + mathjs.round(min, 3);
return NS + String(deg).padStart(2, '0') + " " + mathjs.round(min, 6);
}
var convertLon = function (latlng) {
console.debug(latlng.lng);
//console.debug(latlng.lng);
var NS = latlng.lng > 0 ? 'E' : 'W';
var deg = mathjs.floor(mathjs.abs(latlng.lng));
var min = (mathjs.abs(latlng.lng) - deg) * 60;
// console.debug(NS + deg + " " + min);
return NS + String(deg).padStart(2, '0') + " " + mathjs.round(min, 3);
return NS + String(deg).padStart(2, '0') + " " + mathjs.round(min, 6);
}
var styleArc = function (attributes, arc) {
console.debug(attributes);
//console.debug(attributes);
if(attributes !== undefined){
if (attributes.isPushBackRoute !== undefined && Number(attributes.isPushBackRoute) === 1 ) {
arc['@isPushBackRoute'] = "1";
} else {
arc['@isPushBackRoute'] = "0";
}
if ( attributes.name !== '' && attributes.name !== 'undefined') {
arc['@name'] = attributes.name;
}
}
}

View File

@ -1,10 +1,25 @@
/*
Copyright 2020 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 lineReader = require('readline');
const zlib = require('zlib');
// const geodesy = require('geodesy');
const LatLonEllipsoidal = require('geodesy/latlon-ellipsoidal-vincenty.js').default;
const fs = require('fs');
const store = require('../store');
const buildRunwayPoly = require('../leaflet/Runway.js');
const buildTaxiwayPoly = require('../leaflet/Taxiway.js');
/**
*
* @param {*} line
@ -253,14 +268,24 @@ function createLineString(currentFeature, layerGroup) {
}
}
module.exports.readPavement = function (f, icao, cb) {
module.exports.readPavement = function (f, icao, callback) {
console.log(f);
var pavementLayerGroup = L.layerGroup();
var currentFeature;
store.default.dispatch('setPavementLoaded', false);
if (!fs.existsSync(f)) {
store.default.dispatch('setPavementLoaded', true);
callback();
return;
}
try {
fs.accessSync(f, fs.constants.R_OK);
lineReader.createInterface({
input: fs.createReadStream(f).pipe(zlib.createGunzip())
}).on('line', function (line) {
try {
var fields = line.split(/[ ]+/);
// var fields = line.match('([0-9]+)');
if (fields == null)
@ -275,13 +300,25 @@ module.exports.readPavement = function (f, icao, cb) {
}
// console.log('Ignored:', line);
}
} catch (error) {
console.error('Error reading : ' + line + error);
}
}).on('error', function (err) {
store.default.dispatch('setPavementLoaded', true);
console.error(err);
lr.close();
callback();
}).on('close', function () {
store.default.dispatch('setPavementLoaded', true);
console.log("End");
cb(pavementLayerGroup);
callback(pavementLayerGroup);
});
} catch (err) {
console.error('no access!');
store.default.dispatch('setPavementLoaded', true);
callback();
return;
}
}
module.exports.debug = false;
@ -304,22 +341,14 @@ var scanMethods = {
// APTDAT 715 Segment
10: (line, icao, layerGroup) => {
if (module.exports.isScanning) {
//var marker = new L.marker([line[1], line[2]], { title: '10 Point', color: 'fuchsia' });
//marker.bindTooltip(String(line), { className: "my-label", offset: [0, 0] });
//marker.addTo(layerGroup);
var pointMiddle = new LatLonEllipsoidal(Number(line[1]), Number(line[2]));
var point1 = pointMiddle.destinationPoint(line[5] / 6.562, line[4]);
var point2 = pointMiddle.destinationPoint(line[5] / 6.562, line[4] - 180);
//var runwayPoly2 = new L.Polygon([point1, point2]);
//var marker2 = new L.marker(point2, { title: '10 Point 2', color: 'fuchsia' });
//marker2.bindTooltip(String(line), { className: "my-label", offset: [0, 0] });
//marker2.addTo(layerGroup);
//runwayPoly2.setStyle({ color: 'red', interactive: false });
//runwayPoly2.addTo(layerGroup);
var runwayPoints = [];
var bearing = point1.initialBearingTo(point2);
// Width in ft
var runwayWidth = Number(line[8]) / 3.281;
runwayPoints.push(point1.destinationPoint(runwayWidth / 2, (bearing + 90)));
@ -327,8 +356,7 @@ var scanMethods = {
runwayPoints.push(point2.destinationPoint(runwayWidth / 2, (bearing - 90)));
runwayPoints.push(point1.destinationPoint(runwayWidth / 2, (bearing - 90)));
var runwayPoly = new L.Polygon(runwayPoints);
runwayPoly.setStyle({ color: 'grey', fillColor: 'grey', opacity: 0.5, fillOpacity: 0.5, interactive: false });
var runwayPoly = buildTaxiwayPoly(runwayPoints);
runwayPoly.addTo(layerGroup);
}
},
@ -350,8 +378,7 @@ var scanMethods = {
runwayPoints.push(point2.destinationPoint(runwayWidth / 2, (bearing - 90)));
runwayPoints.push(point1.destinationPoint(runwayWidth / 2, (bearing - 90)));
var runwayPoly = new L.Polygon(runwayPoints);
runwayPoly.setStyle({ color: 'grey', interactive: false });
var runwayPoly = buildRunwayPoly(runwayPoints);
runwayPoly.addTo(layerGroup);
var displacedEnd1 = point1.destinationPoint(Number(line[20]), bearing)

View File

@ -1,19 +1,22 @@
/* eslint-disable */
const fs = require('fs');
const path = require('path');
var xamel = require('xamel');
const xamel = require('xamel');
const convert = require('geo-coordinates-parser');
const LatLonEllipsoidal = require('geodesy/latlon-ellipsoidal-vincenty.js').default;
const store = require('../store');
const util = require('util');
const takeoffPadPoly = require('../leaflet/TakeoffPad.js');
const threshold = require('./Threshold.js');
var $ = require('jquery');
exports.readThresholdXML = function (fDir, icao, force) {
exports.readThresholdXML = function (fDir, icao, force, stripes) {
try {
var layerGroup = L.layerGroup();
layerGroup.maxId = 0;
@ -40,18 +43,39 @@ exports.readThresholdXML = function (fDir, icao, force) {
throw err;
}
var thresholdNodes = xml.find('PropertyList/runway/threshold');
console.log("Threshold Nodes" + thresholdNodes.length);
var runwayNodes = xml.find('PropertyList/runway');
console.log("Threshold Nodes" + runwayNodes.length);
var merged = new Array();
var nodesLookup = {};
featureLookup = [];
var index = 0;
runwayNodes.map(r => {
var thresholds = r.find('threshold');
thresholds.map(n => {
var icon = threshold(n);
icon.index = index;
icon.addTo(layerGroup);
// Width in m
var runwayWidth = 20;
var latlon = convert(n.find('lat/text()').text() + " " + n.find('lon/text()').text());
var displ_m = Number(n.find('displ-m/text()').text());
var pointMiddle = new LatLonEllipsoidal(latlon.decimalLatitude, latlon.decimalLongitude);
var heading = Number(n.find('hdg-deg/text()').text());
var point1 = pointMiddle.destinationPoint(displ_m, heading);
var point2 = pointMiddle.destinationPoint(displ_m + 80, heading);
thresholdNodes.map(n => {
var circle = threshold(n, layerGroup);
features.push(circle);
var runwayPoints = [];
runwayPoints.push(point1.destinationPoint(runwayWidth / 2, (heading + 90)));
runwayPoints.push(point2.destinationPoint(runwayWidth / 2, (heading + 90)));
runwayPoints.push(point2.destinationPoint(runwayWidth / 2, (heading - 90)));
runwayPoints.push(point1.destinationPoint(runwayWidth / 2, (heading - 90)));
var runwayPoly = takeoffPadPoly(runwayPoints);
runwayPoly.addTo(layerGroup);
}
)
index+=1;
}).sort();
return layerGroup;

View File

@ -0,0 +1,120 @@
/*
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 writeThresholdXML = function (fDir, icao, coordinates) {
try {
try { fs.mkdirSync(path.join(fDir), { recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0]),{ recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0], icao[1]), { recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0], icao[1], icao[2]), { recursive: true })} catch (err) { }
var f = path.join(fDir, icao[0], icao[1], icao[2], icao + '.threshold.new.xml');
var fBak = path.join(fDir, icao[0], icao[1], icao[2], icao + '.threshold.bak.xml');
if( fs.existsSync(f) ) {
fs.copyFileSync(f, fBak);
}
if (f == null)
return;
var xmlObj = { PropertyList: { runway: map(coordinates) } };
xmlString = builder.create(xmlObj).end({ pretty: true });
fs.writeFileSync(f, xmlString);
console.debug(xmlString);
} catch (error) {
console.error(error);
}
return;
}
var map = function (o) {
console.debug(o);
/**
<?xml version='1.0' encoding='ISO-8859-1'?>
<PropertyList>
<runway>
<threshold>
<lon>13.485025</lon>
<lat>52.367689</lat>
<rwy>07</rwy>
<hdg-deg>68.75</hdg-deg>
<displ-m>0.0</displ-m>
<stopw-m>300.0</stopw-m>
</threshold>
<threshold>
<lon>13.526097</lon>
<lat>52.377431</lat>
<rwy>25</rwy>
<hdg-deg>248.78</hdg-deg>
<displ-m>0.0</displ-m>
<stopw-m>300.0</stopw-m>
</threshold>
</runway>
<runway>
<threshold>
<lon>13.517142</lon>
<lat>52.380125</lat>
<rwy>07L</rwy>
<hdg-deg>68.77</hdg-deg>
<displ-m>0.0</displ-m>
<stopw-m>160.0</stopw-m>
</threshold>
<threshold>
<lon>13.554253</lon>
<lat>52.388919</lat>
<rwy>25R</rwy>
<hdg-deg>248.80</hdg-deg>
<displ-m>0.0</displ-m>
<stopw-m>300.0</stopw-m>
</threshold>
</runway>
</PropertyList>
*/
var ret = [];
Object.keys(o).forEach(key => {
ret.push({ threshold: mapThreshold(o[key])});
});
return ret;
}
/**
* <latitude>58.106924</latitude>
<longitude>6.610419</longitude>
<index>0</index>
<rwy>14</rwy>
<heading>131.16</heading>
<displacement>273</displacement>
<stopw_m>0.0</stopw_m>
* @param {*} t
*/
var mapThreshold = function (t) {
return t.map( t => {return {lon: t.longitude, lat: t.latitude, rwy: t.rwy, 'hdg-deg': t.heading, 'displ-m': t.displacement, 'stopw-m': t.stopw_m}});
}
export { writeThresholdXML };

View File

@ -0,0 +1,78 @@
/*
Copyright 2020 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 convert = require('geo-coordinates-parser');
const store = require('../store');
const util = require('util');
const tower = require('./Tower.js');
var $ = require('jquery');
exports.readTowerXML = function (fDir, icao, force) {
try {
var layerGroup = L.layerGroup();
layerGroup.maxId = 0;
var f = path.join(fDir, icao[0], icao[1], icao[2], icao + '.twr.xml');
var fNew = path.join(fDir, icao[0], icao[1], icao[2], icao + '.twr.new.xml');
if (f == null || !fs.existsSync(f))
return;
if (fNew != null && fs.existsSync(fNew) && !force) {
f = fNew;
}
var features = new Array();
// map.on("editable:vertex:new", function (event) {
// log.console("Add vertex " + event);
// });
var xmlGroundnet = fs.readFileSync(f, 'utf8').toString();
xamel.parse(xmlGroundnet, function (err, xml) {
console.debug("parsed " + path.basename(f));
if (err !== null) {
console.error("Error in " + airline);
throw err;
}
var towerNodes = xml.find('PropertyList/tower/twr');
console.log("Towers " + towerNodes.length);
towerNodes.map(n => {
var towerIcon = tower(n, layerGroup);
towerIcon['icao'] = icao
towerIcon.addTo(layerGroup);
/*
//DEBUG Code
var latlon = convert(n.find('lat/text()').text() + " " + n.find('lon/text()').text());
var marker = new L.Circle([latlon.decimalLatitude, latlon.decimalLongitude], 5);
marker.addTo(layerGroup);
*/
features.push(towerIcon);
}).sort();
return layerGroup;
});
// var jsonAirports = JSON.parse(geoJSON);
// return jsonAirports;
} catch (error) {
console.error(error);
}
return layerGroup;
};

View File

@ -0,0 +1,63 @@
/*
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 featureLookup = [];
var parkings = [];
var pushBackNodeLookup = [];
exports.writeTowerXML = function (fDir, icao, coordinates) {
try {
try { fs.mkdirSync(path.join(fDir), { recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0]),{ recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0], icao[1]), { recursive: true })} catch (err) { }
try { fs.mkdirSync(path.join(fDir, icao[0], icao[1], icao[2]), { recursive: true })} catch (err) { }
var f = path.join(fDir, icao[0], icao[1], icao[2], icao + '.twr.new.xml');
var fBak = path.join(fDir, icao[0], icao[1], icao[2], icao + '.twr.bak.xml');
if( fs.existsSync(f) ) {
fs.copyFileSync(f, fBak);
}
if (f == null)
return;
var xmlObj = { PropertyList: { tower: { twr: map(coordinates)} } };
xmlString = builder.create(xmlObj).end({ pretty: true });
fs.writeFileSync(f, xmlString);
console.debug(xmlString);
} catch (error) {
console.error(error);
}
return;
}
var map = function (o) {
console.debug(o);
/**
<lon>-1.6286902</lon>
<lat>59.53396633</lat>
<elev-m>3.05</elev-m>
*/
return { lon: o.longitude, lat: o.latitude, 'elev-m': o.height};
}

View File

@ -0,0 +1,123 @@
/*
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(),
'required-aircraft': params.find('required-aircraft').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

@ -14,6 +14,10 @@ Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.config.errorHandler = (err, vm, info) => {
console.error(err)
}
/* eslint-disable no-new */
new Vue({
components: { App },

View File

@ -264,5 +264,5 @@ function mapSGPropertyNode(node) {
}));
export function aiLayer(options) {
return new L.AILayer(null, options);
return undefined //new L.AILayer(null, options);
}

View File

@ -1,15 +1,28 @@
/**
Copyright 2020 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/.
*/
import Vue from 'vue'
const state = {
type: 'none',
index: 'none',
editing: false,
data: {airports: {}, parking: {}, arc: {}, node: {}, runway: {}}
data: {airports: {}, parking: {}, arc: {}, multiarc: {}, node: {}, runway: {}, threshold: {}, tower: {}}
}
const SET_EDIT_AIRPORT = 'SET_EDIT_AIRPORT'
const SET_EDIT_PARKING = 'SET_EDIT_PARKING'
const SET_EDIT_ARC = 'SET_EDIT_ARC'
const SET_EDIT_MULTI_ARC = 'SET_EDIT_MULTI_ARC'
const SET_EDIT_RUNWAY = 'SET_EDIT_RUNWAY'
const mutations = {
@ -25,7 +38,6 @@ const mutations = {
state.type = 'airport'
},
SET_EDIT_PARKING (state, parking) {
Vue.set(state, 'data', {})
var p = Object.assign({}, parking)
Vue.set(state.data, 'parking', p)
Vue.set(state, 'index', p.index)
@ -35,15 +47,11 @@ const mutations = {
if (node === undefined) {
return
}
if (!state.data || state.type !== 'node') {
Vue.set(state, 'data', {})
}
Vue.set(state.data, 'node', node)
Vue.set(state, 'index', node.index)
Vue.set(state, 'type', 'node')
},
SET_EDIT_RUNWAY (state, runway) {
Vue.set(state, 'data', {})
Vue.set(state.data, 'node', runway)
Vue.set(state, 'index', runway.index)
Vue.set(state, 'type', 'runway')
@ -52,9 +60,6 @@ const mutations = {
if (arc === undefined) {
return
}
if (!state.data || state.type !== 'arc') {
Vue.set(state, 'data', {})
}
Vue.set(state.data, 'arc', arc)
if (state.data.arc.name === undefined) {
Vue.set(state.data.arc, 'name', '')
@ -62,6 +67,31 @@ const mutations = {
Vue.set(state, 'index', arc.index)
Vue.set(state, 'type', 'arc')
},
SET_EDIT_MULTI_ARC (state, arc) {
if (arc === undefined) {
return
}
Vue.set(state.data.multiarc, 'isPushBackRoute', arc.isPushBackRoute)
Vue.set(state.data.multiarc, 'direction', arc.direction)
if (state.data.multiarc.name === undefined) {
Vue.set(state.data.multiarc, 'name', '')
}
Vue.set(state, 'index', arc.index)
Vue.set(state, 'type', 'multiarc')
},
'SET_EDIT_MULTI_ARC_IDS' (state, arcs) {
if (arcs === undefined) {
return
}
if (!state.data || state.type !== 'multiarc') {
return
}
if (state.data.multiarc.ids === undefined) {
state.data.multiarc.ids = []
}
state.data.multiarc.ids = state.data.multiarc.ids.concat(arcs.filter(n => n).filter((v, i, a) => a.indexOf(v) === i))
},
'SET_EDIT_PARKING_NAME' (state, parkingName) {
Vue.set(state.data.parking, 'name', parkingName)
},
@ -90,14 +120,29 @@ const mutations = {
'SET_EDIT_PARKING_COORDS' (state, coords) {
Vue.set(state.data.parking, 'coords', coords)
},
'SET_EDIT_PARKING_NOSE_COORDS' (state, coords) {
Vue.set(state.data.parking, 'nosecoords', coords)
},
'SET_EDIT_ARC_NAME' (state, arcName) {
if (state.type === 'arc') {
Vue.set(state.data.arc, 'name', arcName)
} else {
Vue.set(state.data.multiarc, 'name', arcName)
}
},
'SET_EDIT_PUSHBACK' (state, isPushBackRoute) {
Vue.set(state.data.arc, 'isPushBackRoute', isPushBackRoute)
if (state.type === 'arc') {
Vue.set(state.data.arc, 'isPushBackRoute', Number(isPushBackRoute))
} else {
Vue.set(state.data.multiarc, 'isPushBackRoute', Number(isPushBackRoute))
}
},
'SET_EDIT_DIRECTION' (state, direction) {
if (state.type === 'arc') {
Vue.set(state.data.arc, 'direction', direction)
} else {
Vue.set(state.data.multiarc, 'direction', direction)
}
},
'SET_EDIT_HOLDPOINTTYPE' (state, holdPointType) {
Vue.set(state.data.node, 'holdPointType', holdPointType)
@ -105,6 +150,28 @@ const mutations = {
'SET_EDIT_NODE_COORDS' (state, coords) {
Vue.set(state.data.node, 'coords', coords)
},
'SET_EDIT_TOWER_COORDS' (state, coords) {
state.type = 'tower'
if (!state.data.tower) {
state.data.tower = {}
}
if (!state.data.tower.coords) {
state.data.tower.coords = {}
}
Vue.set(state.data.tower.coords, 'latitude', coords.split(' ')[0])
Vue.set(state.data.tower.coords, 'longitude', coords.split(' ')[1])
},
'SET_EDIT_TOWER_HEIGHT' (state, height) {
Vue.set(state.data.tower, 'height', height)
},
'SET_EDIT_THRESHOLD_COORDS' (state, threshold) {
state.type = 'threshold'
Vue.set(state.data.threshold, 'runway', threshold.rwy)
Vue.set(state.data.threshold, 'displacement', threshold.displacement)
},
'SET_EDIT_THRESHOLD_DISPLACEMENT' (state, displacement) {
Vue.set(state.data.threshold, 'displacement', displacement)
},
'SET_EDIT_ISONRUNWAY' (state, isOnRunway) {
Vue.set(state.data.node, 'isOnRunway', isOnRunway)
}
@ -129,12 +196,33 @@ const actions = {
async setParkingCoords (context, coords) {
context.commit('SET_EDIT_PARKING_COORDS', coords)
},
async setParkingNoseCoords (context, coords) {
context.commit('SET_EDIT_PARKING_NOSE_COORDS', coords)
},
async setArc (context, arc) {
context.commit(SET_EDIT_ARC, arc)
},
async setMultiArc (context, arc) {
context.commit(SET_EDIT_MULTI_ARC, arc)
},
async setMultiArcIds (context, arc) {
context.commit('SET_EDIT_MULTI_ARC_IDS', arc)
},
async setNode (context, node) {
context.commit('SET_EDIT_NODE', node.attributes)
context.commit('SET_EDIT_NODE_COORDS', node.lat.toFixed(6) + ' ' + node.lng.toFixed(6))
},
async setTowerCoords (context, node) {
context.commit('SET_EDIT_TOWER_COORDS', node)
},
async setTowerHeight (context, height) {
context.commit('SET_EDIT_TOWER_HEIGHT', height)
},
async setThreshold (context, node) {
context.commit('SET_EDIT_THRESHOLD_COORDS', node)
},
async setDisplacement (context, displacement) {
context.commit('SET_EDIT_THRESHOLD_DISPLACEMENT', displacement)
}
}

View File

@ -0,0 +1,51 @@
/*
Copyright 2020 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/.
*/
import Vue from 'vue'
const state = { icao: '', groundnetLoaded: false, pavementLoaded: false }
const mutations = {
SET_ICAO_LOADING (state, icao) {
Vue.set(state, 'icao', icao)
},
SET_GROUNDNET_LOADED (state, loaded) {
Vue.set(state, 'groundnetLoaded', loaded)
},
SET_PAVEMENT_LOADED (state, loaded) {
Vue.set(state, 'pavementLoaded', loaded)
}
}
const actions = {
async setIcaoLoading (context, p) {
context.commit('SET_ICAO_LOADING', p)
},
async setGroundnetLoaded (context, p) {
if (typeof p !== 'boolean') {
console.error('Not Boolean')
}
context.commit('SET_GROUNDNET_LOADED', p)
},
async setPavementLoaded (context, p) {
if (typeof p !== 'boolean') {
console.error('Not Boolean')
}
context.commit('SET_PAVEMENT_LOADED', p)
}
}
export default {
state,
mutations,
actions
}

View File

@ -1,3 +1,15 @@
/*
Copyright 2020 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/.
*/
import Vue from 'vue'
const state = { items: [] }
@ -31,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)

View File

@ -1,6 +1,9 @@
/* eslint-disable */
const path = require('path');
const fs = require('fs');
const state = {
settings: { flightgearDirectory: '.', testDirectory: '.', email: 'flightgearairports@example.org', name: 'unknown', phi_url: 'http://localhost:8080' },
settings: { numberOfSaves: 1, flightgearDirectory: '.', testDirectory: '.', email: 'flightgearairports@example.org', name: 'unknown', phi_url: 'http://localhost:8080' },
zoom: 14,
center: [47.413220, -1.219482],
bounds: undefined,
@ -10,10 +13,21 @@ const state = {
const mutations = {
'DELETE_INDEXED_DB'() { },
'FLIGHTGEAR_DIRECTORY'(state, flightgearDirectory) {
try {
fs.accessSync(flightgearDirectory)
state.settings.flightgearDirectory = flightgearDirectory
state.settings.flightgearDirectory_ai = flightgearDirectory + '/data/AI'
state.settings.flightgearDirectory_traffic = flightgearDirectory + '/data/AI/Traffic'
state.settings.flightgearDirectory_apt = flightgearDirectory + '/data/Airports/apt.dat.gz'
} catch (err) {
try {
fs.accessSync(flightgearDirectory.replace(/\.App/, ''))
state.settings.flightgearDirectory = flightgearDirectory.replace(/\.App/, '')
} catch (error) {
console.warn(error)
}
}
state.settings.flightgearDirectory_ai = flightgearDirectory + path.sep + 'AI'
state.settings.flightgearDirectory_traffic = path.join(flightgearDirectory, 'AI', 'Traffic');
state.settings.flightgearDirectory_apt = path.join(flightgearDirectory, 'Airports', 'apt.dat.gz');
},
'AIPORTS_DIRECTORY'(state, airportsDirectory) {
state.settings.airportsDirectory = airportsDirectory
@ -36,6 +50,9 @@ const mutations = {
'SET_NAME'(state, name) {
state.settings.name = name
},
'SET_NUMBER_OF_SAVES'(state, numberOfSaves) {
state.settings.numberOfSaves = numberOfSaves
},
'SET_PHI_URL'(state, phi_url) {
state.settings.phi_url = phi_url
},

View File

@ -8,9 +8,14 @@ const fs = require('fs');
//debugger;
var turf;
var check_msg;
if (process.env.NODE_ENV === 'development') {
importScripts('../../../node_modules/dijkstrajs/dijkstra.js');
turf = require('./node_modules/@turf/turf');
} else if (process.env.NODE_ENV === 'mocha') {
importScripts('../../../node_modules/dijkstrajs/dijkstra.js');
turf = require('../../../node_modules/@turf/turf')
} else {
importScripts('dijkstra.js');
turf = require('@turf/turf')
@ -38,22 +43,36 @@ onmessage = function (event) {
).catch(result => {
console.error('Crashed');
console.error(result);
postMessage(['DONE', []]);
postMessage(['DONE', [{ id: -1, message: ['Crashed', result] }]]);
});
}
};
/**
* Implements the checks of the groundnet
* @param {*} data
*/
async function checkGroundnet(data) {
var promise = new Promise(function (resolve, reject) {
try {
const fName = process.env.NODE_ENV === 'development'
? './src/renderer/utils/check_msg.json'
: path.join(`${process.resourcesPath}`, 'workers', 'check_msg.json');
check_msg = JSON.parse(fs.readFileSync(fName, 'utf8').toString());
//debugger;
var parkings = data.map(mapParkings).filter(n => n !== undefined);
var runwayNodes = data.map(mapRunwayNodes).filter(n => n !== undefined);
var runwayNodeIDs = data.map(mapRunwayNodeId).filter(n => n !== undefined);
var runwayNodes = data.map(mapRunwayNode).filter(n => n !== undefined);
var pushbackNodes = data.map(mapPushbackNodes).filter(n => n !== undefined);
var edges = data.map(mapEdges).filter(n => n !== undefined);
this.max = 4 * parkings.length * runwayNodes.length +
3 * parkings.length;
var normalNodes = data.map(mapEdges).filter(n => n !== undefined)
.flatMap(m => m.latLngs).filter(n => runwayNodeIDs.indexOf(Number(n.index)) < 0);
var takeoffPads = data.map(mapTakeoffPads).filter(n => n !== undefined);
this.max = 30;
this.postMessage(['max', this.max]);
var boxes = {};
@ -65,62 +84,100 @@ async function checkGroundnet(data) {
boxes[element.index].push(boxes[element.index][0]);
}
});
var graph = {};
var directionalGraph = {};
var bidirectionalGraph = {};
console.debug(parkings);
parkings.forEach(element => {
graph[element] = {};
directionalGraph[element] = {};
bidirectionalGraph[element] = {};
});
runwayNodes.forEach(element => {
graph[element] = {};
runwayNodeIDs.forEach(element => {
directionalGraph[element] = {};
bidirectionalGraph[element] = {};
});
var notOkNodes = [];
//debugger;
console.debug(edges);
if (edges === undefined) {
resolve([{ id: -1, message: check_msg.NO_EDGES }]);
}
this.postMessage(['progress', 1]);
//debugger;
if (takeoffPads.length === 0) {
resolve([{ id: -1, message: check_msg.NO_RUNWAYS }]);
}
this.postMessage(['progress', 1]);
edges.forEach(edge => {
graph[edge.start] = {};
graph[edge.end] = {};
directionalGraph[edge.start] = {};
bidirectionalGraph[edge.start] = {};
directionalGraph[edge.end] = {};
bidirectionalGraph[edge.end] = {};
if (edge.latLngs !== undefined) {
// Check if there are segments > 2km
edge.latLngs.forEach((latLng, index, arr) => {
if (index > 0) {
var d = distance([arr[index - 1].lng, arr[index - 1].lat], [latLng.lng, latLng.lat]);
if (d > 2000) {
notOkNodes.push({ id: Number(arr[index-1].index), message: `Start of long route ${d.toFixed(2)}` });
notOkNodes.push({ id: Number(arr[index].index), message: `End of long route ${d.toFixed(2)}` });
notOkNodes.push({ id: Number(arr[index - 1].index), message: check_msg.LONG_ROUTE_START });
notOkNodes.push({ id: Number(arr[index].index), message: check_msg.LONG_ROUTE_END });
}
//console.log(d);
}
});
}
});
this.postMessage(['progress', 1]);
this.postMessage(['progress', 1]);
// Add edges to graphs
edges.forEach(element => {
var node1 = graph[element.start];
var node1 = directionalGraph[element.start];
var node2 = directionalGraph[element.end];
if (element.direction === undefined) {
notOkNodes.push({ id: Number(element._leaflet_id), message: check_msg.EDGE_MISSING_DIRECTION });
}
if (element.direction === 'bi-directional' || element.direction === 'forward') {
node1[Number(element.end)] = 1;
var node2 = graph[element.end];
}
if (element.direction === 'bi-directional' || element.direction === 'backward') {
node2[Number(element.start)] = 1;
}
var node3 = bidirectionalGraph[element.start];
var node4 = bidirectionalGraph[element.end];
node3[Number(element.end)] = 1;
node4[Number(element.start)] = 1;
});
this.postMessage(['progress', 1]);
var isLegitEnd = function (v) {
if( Object.keys(graph[v]).length <= 1 ) {
if (bidirectionalGraph[v] === undefined) {
debugger;
}
if (Object.keys(bidirectionalGraph[v]).length <= 1) {
return true;
}
return Object.keys(graph[v]).filter( v => runwayNodes[v]).length === 0;
return Object.keys(bidirectionalGraph[v]).filter(v => runwayNodeIDs[v]).length === 0;
}
//debugger;
runwayNodes = runwayNodes.filter(
runwayNodeIDs = runwayNodeIDs.filter(
(v, i) => isLegitEnd(v)
);
// Check if there is a route from every parking to every runway node
var okNodes = [];
logger('info', graph);
logger('info', directionalGraph);
parkings.forEach(parkingNode => {
runwayNodes.forEach(runwayNode => {
var ok = checkRoute(graph, parkingNode, runwayNode);
runwayNodeIDs.forEach(runwayNode => {
var ok = checkRoute(directionalGraph, parkingNode, runwayNode);
if (ok) {
okNodes.push(parkingNode);
okNodes.push(runwayNode);
} else {
console.log(`No route from Parking ${parkingNode} to Runwaynode ${runwayNode}`);
}
this.postMessage(['progress', 1]);
});
});
// Build pushback graph
// Build pushback directionalGraph
var noPushbackGraph = {};
parkings.forEach(element => {
noPushbackGraph[element] = {};
@ -144,16 +201,10 @@ async function checkGroundnet(data) {
var okPushbacks = [];
// Check pushback
var multiplePushbackRoutes = {};
//debugger;
parkings.forEach(parkingNode => {
pushbackNodes.forEach(pushbackNode => {
var numRoutes = checkRoute(noPushbackGraph, parkingNode, pushbackNode);
if (numRoutes === 0) {
/*
if(parkingNode===14) {
debugger;
}
*/
if (multiplePushbackRoutes[parkingNode] === undefined &&
Object.keys(noPushbackGraph[parkingNode]) > 0) {
// Only when there is a edge leaving
@ -172,71 +223,111 @@ async function checkGroundnet(data) {
multiplePushbackRoutes[parkingNode].push(pushbackNode);
}
}
});
});
var notConnectedToPushback = pushbackNodes.map(
id => {
var normalRoutes = bidirectionalGraph[id];
var pushbackRoutes = noPushbackGraph[id];
if (Object.keys(pushbackRoutes).length < 1)
return { id: id, message: check_msg.PUSHBACK_NOT_CONNECTED }
}).filter(n => n !== undefined);
this.postMessage(['progress', 1]);
var multipleTaxiRoutes = pushbackNodes.map(
id => {
var normalRoutes = bidirectionalGraph[id];
var pushbackRoutes = noPushbackGraph[id];
if (normalRoutes !== undefined) {
var nonPushbackRoutes = Object.keys(normalRoutes).filter(r => pushbackRoutes[r] === undefined);
if (nonPushbackRoutes.length > 1)
return { id: id, message: check_msg.TO_MANY_PUSHBACK_TAXI_ROUTES }
}
}).filter(n => n !== undefined);
this.postMessage(['progress', 1]);
var pushbackExitNotBidirectional = pushbackNodes.map(
id => {
var normalRoutes = bidirectionalGraph[id];
var pushbackRoutes = noPushbackGraph[id];
if (normalRoutes !== undefined) {
var nonPushbackRoutes = Object.keys(normalRoutes).filter(r => pushbackRoutes[r] === undefined);
if(nonPushbackRoutes.length > 0) {
var returnRoute = Object.keys(bidirectionalGraph[nonPushbackRoutes[0]]).map(id => Number(id)).filter(retId =>id === retId);
if (returnRoute.length === 0)
return { id: id, message: check_msg.PUSHBACK_EXIT_NOT_BIDRECTIONAL }
}
}
}).filter(n => n !== undefined);
this.postMessage(['progress', 1]);
});
});
var rogueHoldPoints = pushbackNodes.map(
id => {
var routes = noPushbackGraph[id];
if (Object.keys(routes).length < 1)
return { id: id, message: 'Unconnected Pushbacknode' }
return { id: id, message: check_msg.UNCONNECTED_PUSHBACK }
/*
else if(Object.keys(routes).length>1)
return { id: id, message: 'Multiple connected pushback node' }
*/
}
).filter(n => n !== undefined);
this.postMessage(['progress', 1]);
var wrongPushbackRoutes = parkings.filter(
function (e) {
//debugger;
return this[e] != undefined && this[e].length != 1;
}
, multiplePushbackRoutes).map(
id => {
var endPoints = multiplePushbackRoutes[id];
if (endPoints.length < 1)
return { id: id, message: 'No way to pushback holdpoint' }
return { id: id, message: check_msg.NO_WAY_TO_HOLDPOINT }
else
return { id: id, message: 'Multiple connected pushback points' }
return { id: id, message: check_msg.MULTIPLE_PUSHBACK }
}
);
wrongPushbackRoutes =wrongPushbackRoutes.concat(multiplePushbackRoutes);
this.postMessage(['progress', 1]);
okNodes = okNodes.filter((v, i) => okNodes.indexOf(v) === i);
var notOkNodesParkings = parkings.filter(
(v, i) => okNodes.indexOf(v) < 0
).map(
id => { return { id: id, message: 'No way from parking to each runway' } }
id => { return { id: id, message: check_msg.NO_RUNWAY_ROUTE } }
);
var notOkNodesRunways = runwayNodes.filter(
this.postMessage(['progress', 1]);
var notOkNodesRunways = runwayNodeIDs.filter(
(v, i) => okNodes.indexOf(v) < 0
).map(
id => { return { id: id, message: 'No way from runway to each parking' } }
id => { return { id: id, message: check_msg.NO_PARKING_ROUTE } }
);
this.postMessage(['progress', 1]);
if (parkings.length === 0) {
notOkNodes.push({ id: 0, message: 'No parkings' });
notOkNodes.push({ id: -2, message: check_msg.NO_PARKINGS });
}
if (runwayNodes.length === 0) {
notOkNodes.push({ id: 0, message: 'No Runwaynodes' });
this.postMessage(['progress', 1]);
if (runwayNodeIDs.length === 0) {
notOkNodes.push({ id: -2, message: check_msg.NO_RUNWAY_NODES });
}
var allEnds = Object.entries(graph).filter(
this.postMessage(['progress', 1]);
var allEnds = Object.entries(bidirectionalGraph).filter(
(v, i) => Object.keys(v[1]).length <= 1
);
// Ends that are not on Runway and not a Parking or Pushback
var allLegitimateEndNodes = parkings.concat(runwayNodes).concat(pushbackNodes);
var allLegitimateEndNodes = parkings.concat(runwayNodeIDs).concat(pushbackNodes);
var danglingEnds = allEnds.filter(
(v, i) => allLegitimateEndNodes.indexOf(Number(v[0])) < 0
).map(
v => { return { id: Number(v[0]), message: 'Node not a legimate end' } }
v => { return { id: Number(v[0]), message: check_msg.NOT_LEGIT_END } }
);
this.postMessage(['progress', 1]);
var parkingNodes = data.map(mapParkingNode).filter(n => n !== undefined);
var overlappingParkings = [];
parkingNodes.forEach(parkingNode => {
if (boxes[parkingNode.index] === undefined) {
overlappingParkings.push({ id: parkingNode.index, message: 'Unknown radius' });
overlappingParkings.push({ id: parkingNode.index, message: check_msg.UNKNOWN_RADIUS });
}
});
// Check for intersecting radii
@ -248,7 +339,6 @@ async function checkGroundnet(data) {
[parkingNode1.lng, parkingNode1.lat]);
if (d < parkingNode.radius + parkingNode1.radius + 10) {
// If bigger circles intersect we should check the boxes
//debugger;
if (boxes[parkingNode.index] !== null && boxes[parkingNode1.index] !== null &&
boxes[parkingNode.index] !== undefined && boxes[parkingNode1.index] !== undefined) {
var poly1 = turf.polygon([boxes[parkingNode.index]]);
@ -257,69 +347,98 @@ async function checkGroundnet(data) {
var intersection = turf.intersect(poly1, poly2);
if (intersection !== null) {
overlappingParkings.push({ id: parkingNode.index, message: 'Overlapping parkings' });
overlappingParkings.push({ id: parkingNode.index, message: check_msg.OVERLAPPING_PARKINGS });
}
}
}
}
});
});
this.postMessage(['progress', 1]);
});
});
var invalidParkings = [];
// Check for name
parkingNodes.forEach(parkingNode => {
if (!parkingNode.name || /^\s*$/.test(parkingNode.name)) {
invalidParkings.push({ id: parkingNode.index, message: 'Name empty' });
this.postMessage(['progress', 1]);
invalidParkings.push({ id: parkingNode.index, message: check_msg.NAME_EMPTY });
}
if (!parkingNode.type) {
invalidParkings.push({ id: parkingNode.index, message: 'Parking type empty' });
this.postMessage(['progress', 1]);
invalidParkings.push({ id: parkingNode.index, message: check_msg.TYPE_EMPTY });
}
if (['ga', 'cargo', 'gate', 'mil-fighter', 'mil-cargo'].indexOf(parkingNode.parkingType) < 0) {
//debugger;
invalidParkings.push({ id: parkingNode.index, message: `Parking type ${parkingNode.parkingType} not valid` });
this.postMessage(['progress', 1]);
invalidParkings.push({ id: parkingNode.index, message: check_msg.PARKING_TYPE_INVALID });
}
});
this.postMessage(['progress', 1]);
this.postMessage(['progress', 1]);
//Check for dual pushback/runway nodes
runwayNodes.forEach(runwayNode => {
runwayNodeIDs.forEach(runwayNode => {
if (pushbackNodes.indexOf(runwayNode) >= 0) {
notOkNodes.push({ id: runwayNode, message: 'Dual runway/ pushback node' });
notOkNodes.push({ id: runwayNode, message: check_msg.DUAL_PUSHBACK });
}
});
this.postMessage(['progress', 1]);
//Check if runwaynodes are on runway
runwayNodes.forEach(runwayNode => {
// debugger;
if (takeoffPads.filter(r => turf.booleanContains(r, latToTurf(runwayNode))).length === 0) {
notOkNodes.push({ id: runwayNode.index, message: check_msg.RUNWAY_NODE_NOT_ON_RUNWAY });
}
});
this.postMessage(['progress', 1]);
//Check if nodes no normal nodes are on runway
normalNodes.forEach(normalNode => {
//debugger;
if (takeoffPads.filter(r => turf.booleanContains(r, latToTurf(normalNode))).length > 0) {
notOkNodes.push({ id: normalNode.index, message: check_msg.NON_RUNWAYNODE_ON_RUNWAY });
}
});
this.postMessage(['progress', 1]);
var doubleEdges = edges.filter((v, i, a) => a.findIndex(t => (t.start === v.start && t.end === v.end) ) !== i);
doubleEdges.forEach(e => {
notOkNodes.push({ id: e.id, message: check_msg.DOUBLE_EDGE });
});
// debugger;
notOkNodes = notOkNodes.concat(invalidParkings);
if (invalidParkings.length === 0) {
notOkNodes.push({id:-1, message: 'Parkings valid'});
notOkNodes.push({ id: -1, message: check_msg.PARKINGS_VALID });
}
notOkNodes = notOkNodes.concat(overlappingParkings);
if (overlappingParkings.length === 0) {
notOkNodes.push({id:-1, message: 'No parkings overlapping'});
notOkNodes.push({ id: -1, message: check_msg.NO_OVERLAPPING_PARKINGS });
}
notOkNodes = notOkNodes.concat(danglingEnds);
if (danglingEnds.length === 0) {
notOkNodes.push({id:-1, message: 'No invalid ends'});
notOkNodes.push({ id: -1, message: check_msg.NO_INVALID_ENDS });
}
notOkNodes = notOkNodes.concat(notOkNodesParkings).concat(rogueHoldPoints);
if (notOkNodesParkings.length === 0 && rogueHoldPoints === 0) {
notOkNodes.push({id:-1, message: 'Routes from parkings OK'});
notOkNodes.push({ id: -1, message: check_msg.ROUTES_FROM_PARKINGS_OK });
}
notOkNodes = notOkNodes.concat(notOkNodesRunways);
if (notOkNodesRunways.length === 0) {
notOkNodes.push({id:-1, message: 'Routes from runways OK'});
notOkNodes.push({ id: -1, message: check_msg.ROUTES_FROM_RUNWAYS_OK });
}
notOkNodes = notOkNodes.concat(wrongPushbackRoutes);
if (wrongPushbackRoutes.length===0) {
notOkNodes.push({id:-1, message: 'Pushback routes OK'});
notOkNodes = notOkNodes.concat(notConnectedToPushback);
notOkNodes = notOkNodes.concat(multipleTaxiRoutes);
notOkNodes = notOkNodes.concat(pushbackExitNotBidirectional);
if (wrongPushbackRoutes.length === 0 &&
notConnectedToPushback.length === 0 &&
multipleTaxiRoutes.length === 0 &&
pushbackExitNotBidirectional.length === 0
) {
notOkNodes.push({ id: -1, message: check_msg.PUSHBACK_ROUTES_OK });
}
// check1(graph);
// check2();
// this.postMessage(['progress', 1]);
resolve(notOkNodes);
} catch (error) {
reject(error);
}
@ -327,9 +446,9 @@ async function checkGroundnet(data) {
return promise;
}
function checkRoute(graph, from, to) {
function checkRoute(directionalGraph, from, to) {
try {
var pathD = this.dijkstra.find_path(graph, from, to);
var pathD = this.dijkstra.find_path(directionalGraph, from, to);
if (pathD.length > 0) {
console.log(pathD);
return pathD.length;
@ -341,8 +460,8 @@ function checkRoute(graph, from, to) {
}
}
function check1(graph) {
var graph1 = {
function check1(directionalGraph) {
var directionalGraph1 = {
a: { b: 1, d: 1 },
b: { a: 1, c: 1, e: 1 },
c: { b: 1, f: 1 },
@ -353,7 +472,7 @@ function check1(graph) {
h: { e: 1, g: 1, i: 1 },
i: { f: 1, h: 1 }
};
var path = this.dijkstra.find_path(graph, 'a', 'i');
var path = this.dijkstra.find_path(directionalGraph, 'a', 'i');
console.log(path);
}
@ -385,18 +504,44 @@ var mapBoxes = function (o) {
return { index: o.index };
}
var mapRunwayNodes = function (o) {
var mapRunwayNodeId = function (o) {
if (o.type === 'runway')
return o.index;
console.debug(o);
}
var mapRunwayNode = function (o) {
if (o.type === 'runway') {
return { index: o.index, lat: o.lat, lng: o.lng };
}
}
var mapTakeoffPads = function (o) {
if (o.type === 'takeoffpad_poly') {
var pts = o.pavement[0].map(latLngToArray);
pts.push(pts[0]);
return turf.polygon([pts]);
}
}
var mapEdges = function (o) {
if (o.type === 'poly')
// debugger;
return {
start: o.start, end: o.end, isPushBackRoute: o.isPushBackRoute !== undefined &&
o.isPushBackRoute !== 0, latLngs: o.latLngs
id: o._leaflet_id, start: o.start, end: o.end, isPushBackRoute: o.isPushBackRoute !== undefined &&
o.isPushBackRoute !== 0, direction: o.direction, latLngs: o.latLngs
};
console.debug(o);
}
var latToTurf = function (turfPoint) {
return turf.point([turfPoint.lng, turfPoint.lat]);
};
var latLngToArray = function (turfPoint) {
//debugger;
return [turfPoint.lng, turfPoint.lat];
};
var turfToLatLng = function (turfPoint) {
return '' + turfPoint.geometry.coordinates[1].toFixed(6) + ',' + turfPoint.geometry.coordinates[0].toFixed(6);
};

View File

@ -0,0 +1,33 @@
{
"LONG_ROUTE_START" : ["Start of long route", "Route segments of >2km are Taxidraw artefacts"],
"LONG_ROUTE_END": ["End of long route", "Route segments of >2km are Taxidraw artefacts"],
"EDGE_MISSING_DIRECTION": ["Edge missing direction", "Each edge must have a direction (forward, backward, bi-directional)"],
"NO_RUNWAY_ROUTE": ["No way from parking to each runway", "There must be a route from each parking to each runway."],
"NO_PARKING_ROUTE": ["No way from runway to each parking", "There must be a route from each runway to each parking."],
"NO_PARKINGS": ["No parkings", ""],
"NO_RUNWAY_NODES": ["No Runwaynodes", "Fine for parking only"],
"NOT_LEGIT_END": ["Node not a legimate end", "Taxiroutes must end either at a parking or on a runway"],
"UNKNOWN_RADIUS" :["Unknown radius", "Radii must be one from the list"],
"OVERLAPPING_PARKINGS" :["Overlapping parkings", "Parkings must not overlap"],
"NAME_EMPTY" :["Name empty", "Name of parking must not be empty"],
"TYPE_EMPTY" :["Parking type empty", "The parking type must not be empty"],
"PARKING_TYPE_INVALID" :["Parking type not valid", "The type of parking must be one of ()"],
"DUAL_PUSHBACK": ["Dual runway/ pushback node", "A runway node can not be a hold node at the same time"],
"RUNWAY_NODE_NOT_ON_RUNWAY" : ["Runwaynode not in takeoff pad", ""],
"NON_RUNWAYNODE_ON_RUNWAY": ["Non Runwaynode in takeoff pad", ""],
"PARKINGS_VALID": ["Parkings valid", "All Ok"],
"NO_OVERLAPPING_PARKINGS": ["No parkings overlapping", "Parking positions may not overlap. Reduce the radius or move."],
"NO_INVALID_ENDS": ["No invalid ends", ""],
"ROUTES_FROM_PARKINGS_OK": ["Routes from parkings OK", ""],
"ROUTES_FROM_RUNWAYS_OK": ["Routes from runways OK", ""],
"PUSHBACK_ROUTES_OK": ["Pushback routes OK", ""],
"UNCONNECTED_PUSHBACK": ["Unconnected Pushbacknode", ""],
"NO_WAY_TO_HOLDPOINT":["No way to pushback holdpoint", "There is no route from the parking to the pushback hold point-"],
"MULTIPLE_PUSHBACK": ["Multiple connected pushback points", "There are more than one possible pushback holdpoint routes."],
"PUSHBACK_NOT_CONNECTED": ["Pushback Holding Point not Connected to Pushback Route", ""],
"TO_MANY_PUSHBACK_TAXI_ROUTES": ["Too many Taxi routes from Pushback Holding Point", "There must be only one pushback point reachable from the parking."],
"PUSHBACK_EXIT_NOT_BIDRECTIONAL": ["Pushback Holding Point Exit route is not bidirectional", ""],
"NO_EDGES": ["No Edges", "No checks are run if there are no edges present"],
"NO_RUNWAYS": ["No Runways", "No checks are run if there are no runways present. APT layer visible?"],
"DOUBLE_EDGE": ["No Double Edges", "This edge is doubled"]
}

View File

@ -1,29 +1,40 @@
var util = require("util");
/* eslint-disable no-unused-vars */
var util = require('util');
const d = new Date();
const fName = 'scan_' + d.getFullYear()
+ d.getMonth()
+ d.getDay()
+ d.getHours()
+ d.getMinutes()
+ d.getSeconds()
+ d.getMilliseconds() + '.log';
const fName = 'scan_' + d.getFullYear() +
d.getMonth() +
d.getDay() +
d.getHours() +
d.getMinutes() +
d.getSeconds() +
d.getMilliseconds() + '.log';
var logStream = null;
var loggerInit = function (logging) {
if (logging) {
logStream = require('fs').createWriteStream( fName, {autoClose: true});
try {
const homedir = require('os').homedir();
const logFileName = require('path').join(homedir, fName);
logStream = require('fs').createWriteStream( logFileName, {autoClose: true});
} catch (error) {
console.error('Logging not possible ' + error);
}
}
}
var logger = function (level, msg, o) {
var d = new Date();
try {
if (logStream !== null) {
logStream.write(d.toUTCString() + '|' + level + ' | ' + msg + '\r\n');
}
if (o != undefined && logStream!==null) {
logStream.write( util.inspect(o,{depth: 2}) + '\r\n');
}
} catch (error) {
console.error('Logging not possible ' + error);
}
}

View File

@ -16,6 +16,8 @@ You should have received a copy of the GNU General Public License along with FG
* @param {*} callback
*/
const { Debugger } = require("electron");
async function asyncForEach(array, callback) {
logger('info', "AsyncForEach Len " + array.length);
for (let index = 0; index < array.length; index++) {
@ -46,7 +48,7 @@ async function scanGroundnetFiles(p, features) {
try {
logger('info', 'Start Groundnets ' + p);
var files = traverseDir(p);
this.postMessage(['max', files.length]);
this.postMessage(['max', files.length*2]);
logger('info', files);
asyncForEach(files, async f => {
@ -184,6 +186,9 @@ function scanTrafficIntoDB(p, features) {
function traverseDir(dir) {
var result = [];
if(!fs.existsSync(dir)) {
return result;
}
fs.readdirSync(dir).forEach(file => {
let fullPath = path.join(dir, file);
if (fs.lstatSync(fullPath).isDirectory()) {
@ -239,26 +244,57 @@ function readAI(f, apts) {
resolve();
return;
}
const aircraftLookup = {};
dat.trafficlist.aircraft.map(n => {
try {
if(aircraftLookup[n['required-aircraft']] === undefined) {
aircraftLookup[n['required-aircraft']] = [];
}
aircraftLookup[n['required-aircraft']].push(n.airline);
aircraftLookup[n['required-aircraft']] = aircraftLookup[n['required-aircraft']].filter((v, i, a) => a.indexOf(v) === i);
} catch (error) {
reject(error);
}
//debugger;
});
logger('info', 'Traffic', dat.trafficlist.flight);
logger('info', "Departure flights " + dat.trafficlist.flight.length);
var merged = new Array();
// Flat list. Each flight departing or landing counts as one.
var merged = [];
var airports = {};
dat.trafficlist.flight.map(n => {
merged.push(n.departure.port);
merged.push(n.arrival.port);
if(airports[n.departure.port] === undefined) {
airports[n.departure.port] = [];
}
if(airports[n.arrival.port] === undefined) {
airports[n.arrival.port] = [];
}
airports[n.departure.port] = airports[n.departure.port].concat(aircraftLookup[n['required-aircraft']]);
airports[n.departure.port] = airports[n.departure.port].filter((v, i, a) => a.indexOf(v) === i)
airports[n.arrival.port] = airports[n.arrival.port].concat(aircraftLookup[n['required-aircraft']]);
airports[n.arrival.port] = airports[n.arrival.port].filter((v, i, a) => a.indexOf(v) === i)
}).sort();
//debugger;
var counts = {};
for (var i = 0; i < merged.length; i++) {
counts[merged[i]] = 1 + (counts[merged[i]] || 0);
}
asyncForEach(Object.keys(counts), async key => {
logger('info', key);
await store(key, airline[1], counts[key]);
asyncForEach(Object.keys(counts), async icao => {
logger('info', icao);
await store(icao, airports[icao], counts[icao]);
}).then(t => {
logger('info', "Finished");
resolve();
@ -290,7 +326,7 @@ function readAI(f, apts) {
* @param {*} value
*/
function store(icao, airline, value) {
function store(icao, airlines, value) {
var promise = new Promise(function (resolve, reject) {
logger('info', "Airport " + icao + " has " + value + " new flights");
// Make a request to get a record by key from the object store
@ -300,18 +336,18 @@ function store(icao, airline, value) {
var objectStoreRequest = index.get(icao);
objectStoreRequest.onsuccess = function (event) {
logger('info', 'Stored ', event);
logger('info', 'Store Request', event);
var feature = objectStoreRequest.result;
if (!feature) {
feature = createFeature(icao);
}
feature.properties.flights += value;
logger('info', "Airline : ", airline);
if (!feature.properties.airlines.includes(airline)) {
feature.properties.airlines.push(airline);
feature.properties.airlines.sort();
}
logger('info', "ICAO : " + feature.properties.icao + " Flights : " + feature.properties.flights);
logger('info', "Airlines : ", JSON.stringify(airlines));
//debugger;
feature.properties.airlines = feature.properties.airlines.concat(airlines);
feature.properties.airlines = feature.properties.airlines.filter((v, i, a) => a.indexOf(v) === i)
feature.properties.airlines.sort()
// Create another request that inserts the item back into the database
var updateAirportRequest = objectStore.put(feature);
@ -320,16 +356,16 @@ function store(icao, airline, value) {
// When this new request succeeds, run the displayData() function again to update the display
updateAirportRequest.onsuccess = function (event) {
logger('info', "Stored", event);
logger('info', "Updated Success", event);
resolve();
};
updateAirportRequest.onerror = function (event) {
logger('info', "Error storing ", event);
logger('info', "Error updating ", event);
reject(event);
};
};
objectStoreRequest.onerror = function (event) {
logger('info', "Error " + event);
logger('info', "Error reading" + event);
reject(event);
};
});
@ -345,6 +381,7 @@ function store(icao, airline, value) {
async function readGroundnet(f, features) {
var promise = new Promise(function (resolve, reject) {
try {
var thisPostMessage = this.postMessage;
var filename = path.basename(f).match('^([^.]+)\\.([^.]+)(\\.new)?\\.([^.]+)');
if (filename == null) {
resolve("File didn't match");
@ -439,12 +476,10 @@ async function readGroundnet(f, features) {
}
if(filename [3] === '.new') {
feature['properties']['wipgroundnet'] = nodes && nodes.node ? nodes.node.length : 0;
//debugger;
feature['properties']['wipparking'] = parkingnodes && parkingnodes.Parking ? parkingnodes.Parking.length : 0;
} else {
feature['properties']['groundnet'] = nodes && nodes.node ? nodes.node.length : 0;
//debugger;
feature['properties']['parking'] = parkingnodes && parkingnodes.Parking ? parkingnodes.Parking.length : 0;
}
}
@ -459,6 +494,7 @@ async function readGroundnet(f, features) {
// report on the success of the transaction completing, when everything is done
transaction.oncomplete = function (event) {
logger('info', 'Write Transaction complete ' + event);
thisPostMessage(['progress', 1]);
resolve("Stored " + filename[1]);
};
@ -481,7 +517,7 @@ async function readGroundnet(f, features) {
};
}
objectStoreRequest.onerror = function (event) {
logger('info', "Read Errpr : " + event);
logger('info', "Read Error : " + event);
resolve(event);
}
}

2
static/FGA_ACT_A_GA.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="150mm" height="150mm" version="1.1" viewBox="0 0 150 150" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-1.6394e-5 -147)"><g transform="translate(-512.5 -153)"><path d="m587.5 347.75c-0.32736 0-0.75863 2.023-0.97757 3.043 0 0-8.7023-0.47356-8.7023 0.14107 0 0.61461 8.733 0.36873 8.733 0.36873 0.0812 0.14151 0.22163 0.15914 0.368 0.18528v0.21306c-0.57176 0.16009-1.4739 0.31231-2.5099 0.41186-1.036 0.0995-1.1332 0.93371-1.1332 0.93371-0.70455 4.8918-0.96276 10.026-1.2071 14.632-0.012 0.0118-0.0165 1.6e-4 -0.0369 9e-3l-20.112 2e-3 -27.221 1.9869c-2.1208 0.16639-2.2014 1.5529-2.2014 3.2272v8.0906l29.858 3.5196 19.67 0.0146c1.2567 9.6974 3.6713 28.109 3.6713 28.109l-12.803 2.1075c-0.75926 0.18181-1.8746 0.23703-2.1181 1.0313-0.3756 1.9795-0.35641 4.2182-0.24741 6.0605 0.0369 0.68858 0.16248 1.2114 0.8672 1.354l13.439 2.401c0.39078 0.0865 0.49893-0.13326 0.58873-0.48422l1.3408-4.2669s-0.081 7.3888 0.73272 9.0111c0.81369-1.6224 0.73271-9.0111 0.73271-9.0111l1.3412 4.2669c0.0898 0.35096 0.19795 0.57077 0.58874 0.48422l13.439-2.401c0.70472-0.14253 0.83032-0.6654 0.8672-1.354 0.10899-1.8424 0.12819-4.081-0.24741-6.0605-0.24351-0.79425-1.3589-0.84947-2.1181-1.0313l-12.803-2.1075s2.4149-18.412 3.6716-28.109l19.67-0.0146 29.859-3.5196v-8.0906c5.8e-4 -1.6743-0.081-3.0608-2.2018-3.2272l-27.221-1.9869-20.112-2e-3c-0.0204-9e-3 -0.0249 3e-3 -0.0369-9e-3 -0.2443-4.6057-0.50251-9.7399-1.2071-14.632 0 0-0.0972-0.83417-1.1332-0.93371-1.036-0.0996-1.9381-0.25177-2.5099-0.41186v-0.21307c0.14636-0.0262 0.28682-0.0438 0.368-0.18528 0 0 8.733 0.24589 8.733-0.36873s-8.7023-0.14106-8.7023-0.14106c-0.21894-1.0201-0.65058-3.0431-0.97793-3.0431zm0 9.0766c0.34605 0 0.71407-1.3e-4 0.71408 0.5394v2.36c0 0.54212-0.36803 0.54196-0.71408 0.54196-0.34606 0-0.7192 1.6e-4 -0.7192-0.54196v-2.36c-1e-5 -0.53953 0.37314-0.5394 0.7192-0.5394zm-12.73 16.475c0.34605 0 0.71407-1.3e-4 0.71407 0.5394v2.36c0 0.54211-0.36802 0.54196-0.71407 0.54196-0.34606 0-0.7192 1.5e-4 -0.7192-0.54196v-2.36c0-0.53953 0.37314-0.5394 0.7192-0.5394zm25.474 0c0.34606 0 0.71408-1.3e-4 0.71408 0.5394v2.36c0 0.54211-0.36802 0.54196-0.71408 0.54196-0.34605 0-0.71919 1.5e-4 -0.71919-0.54196v-2.36c-1e-5 -0.53953 0.37314-0.5394 0.71919-0.5394z" fill="#3296ff"/><circle cx="587.5" cy="375" r="74.595" fill="none" stroke="#3296ff" stroke-linecap="square" stroke-linejoin="round" stroke-width=".811" style="paint-order:normal"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="280mm" height="282.66mm" version="1.1" viewBox="0 0 280 282.66" xmlns="http://www.w3.org/2000/svg"><g transform="translate(35 -7.171)"><g transform="translate(-7.6294e-6 7.3444)"><g transform="matrix(4.5516 0 0 4.5516 -372.53 -527.39)" fill="#3296ff"><path transform="matrix(.26458 0 0 .26458 -7.9725e-5 4.7925e-5)" d="m396.53 449.99c-6.703 0-12.091 16.285-12.119 29.002l-0.17578 54.354-2.8828 7.209-14.512-0.18945c0.6477-4.764 1.2976-11.043-1.4473-15.457l-0.0469-1.3477 14.395 0.83985 0.16797-1.9668-14.578 0.97461c0-1.4239-1.0254-4.1602-2.3457-4.1602-1.4366 0-2.2472 2.4804-2.4375 4.1211l-14.088-1.1582-4e-3 2.4609 14.047-0.9668-0.1914 1.3164c-2.3109 4.8272-1.8613 10.199-0.92774 15.412-46.681 3.307-70.422 5.1355-71.252 5.2051-0.82972 0.0695-1.8463 0.90433-1.9707 2.293-0.0556 0.59954-0.16605 0.90065-0.33008 0.90039-0.16479-1e-3 -0.35633 0.0276-0.57422 0.082-0.21808 0.0533-0.43767 0.32575-0.6582 0.81641-0.67616 2.9741-0.27076 5.9302-0.0977 8.7734 0.0552 0.10885 12.16 0.73137 36.314 1.8672 24.156 1.1349 36.316 1.7029 36.48 1.7031 0.16403 3.7e-4 6.0384 0.17301 24.84 0.20703l2.123 5.0859c-0.0302 21.998 0.0965 58.62 7.0059 86.129-15.285 2.3628-23.055 3.6526-23.314 3.8594-2.0877 2.7596-2.3172 6.879-1.8203 9.998 0.0556 1e-3 4.4001 0.33632 13.033 1.0059l13.115 0.92578c0.10923-0.0544 1.4785-2.0469 1.4785-2.0469 0.21947 1.7517 0.2277 5.2639 2.7734 5.2676h2e-3c2.5457-4e-3 2.552-3.5159 2.7715-5.2676 0 0 1.3693 1.9924 1.4785 2.0469l13.115-0.92578c8.6331-0.66954 12.978-1.0047 13.033-1.0059 0.49693-3.119 0.26734-7.2384-1.8203-9.998-0.25928-0.20674-8.0293-1.4966-23.314-3.8594 6.9093-27.509 7.0361-64.131 7.0059-86.129l2.123-5.0859c18.801-0.034 24.678-0.20666 24.842-0.20703 0.16404-2.3e-4 12.323-0.56825 36.479-1.7031 24.155-1.1358 36.259-1.7583 36.314-1.8672 0.17311-2.8432 0.5785-5.7993-0.0976-8.7734-0.22035-0.49066-0.44013-0.76312-0.65821-0.81641-0.21807-0.0544-0.40943-0.0832-0.57421-0.082-0.16404 2.6e-4 -0.27257-0.30085-0.32813-0.90039-0.12435-1.3886-1.141-2.2234-1.9707-2.293-0.82972-0.0695-24.573-1.8981-71.254-5.2051 0.93358-5.2134 1.3832-10.585-0.92773-15.412l-0.19141-1.3164 14.047 0.9668-4e-3 -2.4609-14.088 1.1582c-0.19049-1.6407-1.0009-4.1211-2.4375-4.1211-1.3203 0-2.3457 2.7363-2.3457 4.1602l-14.576-0.97461 0.16602 1.9668 14.395-0.83985-0.0469 1.3477c-2.7449 4.4137-2.095 10.693-1.4473 15.457l-14.512 0.18945-2.8809-7.209-0.17578-54.354c0-12.782-5.4181-29.002-12.121-29.002zm-2.207 19.031h1.6133v4.8184h-1.6133zm2.8008 0h1.6133v4.8184h-1.6133zm-20.578 81.467h2.7774v7.1152h-2.7774zm3.8789 0h2.7773v7.1152h-2.7773zm29.434 0h2.7774v7.1152h-2.7774zm3.8789 0h2.7773v7.1152h-2.7773z" fill="#3296ff"/></g><circle cx="105" cy="139.83" r="139.18" fill="none" stroke="#3296ff" stroke-linecap="square" stroke-linejoin="round" stroke-width="1.6438" style="paint-order:normal"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="280mm" height="311.54mm" version="1.1" viewBox="0 0 280 311.54" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-6.4969e-6 14.543)"><circle cx="140" cy="141.28" r="139.36" fill="none" stroke="#3296ff" stroke-linecap="square" stroke-linejoin="round" stroke-miterlimit="4.3333" stroke-width="1.2759" style="paint-order:normal"/><path d="m152.63 24.911c-0.0267-25.744-10.66-39.454-12.634-39.454h1e-5c-1.9741 0-12.608 13.71-12.634 39.454l0.0182 79.957-1.6403 1.3481s-84.993 49.186-89.398 51.752-4.5756 5.1873-4.5756 5.1873-7.5179 14.841-7.7484 15.302c-0.23052 0.4615-0.27144 1.1507-0.27144 1.1507l0.1078 2.3443s6.6825-7.4591 6.8393-7.6432c0.1568-0.18415 0.32861-0.34095 0.7442-0.33769 0.41558 3e-3 1.0215 0.0362 1.4196-0.32729 0.93562-1.1932 2.1371-2.6676 3.9093-3.1924 1.7722-0.52482 39.634-11.689 59.608-17.581l31.015-1.8624v47.86l-0.95849 1.5429h-1.3832c-0.24979-1.7755-0.85068-1.9326-0.85068-1.9326l-11.633 0.12468s-1.2585 0.79803-1.2585 6.6315c0 10.609 1.3118 13.973 1.3118 13.973s0.30131 0.16754 1.2416 0.16754l2.5625 10.064s0.46106 0.0701 0.9494 0.0701l1.7637 5.2665 0.71822-0.0351 1.3923-5.3548c0.55067 0 0.99225-0.16884 0.99225-0.16884l0.0714-0.44288 9.2212 6.0198 5.3548 27.175s-33.731 22.555-34.623 23.158c-0.89108 0.60283-1.4159 2.6639-1.565 4.3768l-0.65199 7.4952 38.872-12.307 1.0494 5.3873 1.1104-5.3873 38.871 12.307-0.65198-7.4952c-0.14909-1.7129-0.67393-3.774-1.565-4.3768-0.8911-0.60285-34.623-23.158-34.623-23.158l5.3561-27.175 9.2199-6.0198 0.0714 0.44288s0.4429 0.16884 0.99356 0.16884l1.391 5.3548 0.71822 0.0351 1.765-5.2665c0.48833 0 0.9481-0.0701 0.9481-0.0701l2.5625-10.064c0.9403 0 1.2416-0.16754 1.2416-0.16754s1.3118-3.3645 1.3118-13.973c0-5.8335-1.2585-6.6315-1.2585-6.6315l-11.633-0.12468s-0.6009 0.15707-0.8507 1.9326h-1.3832l-0.95719-1.5429v-47.86l31.013 1.8624c19.974 5.8921 57.836 17.057 59.608 17.581 1.7722 0.5248 2.9749 1.9992 3.9106 3.1924 0.39808 0.36352 1.0027 0.33031 1.4182 0.32729 0.4156-3e-3 0.58741 0.15355 0.74421 0.33769 0.1568 0.18415 6.8393 7.6432 6.8393 7.6432l0.10781-2.3443s-0.0397-0.68921-0.27015-1.1507c-0.23051-0.4615-7.7497-15.302-7.7497-15.302s-0.16886-2.6209-4.5743-5.1873c-4.4054-2.5664-89.399-51.752-89.399-51.752l-1.639-1.3481 0.0169-79.957m-34.454 111.65c0.78911 0 1.6055 0.23981 1.6053 1.2949v6.738c2.3e-4 0.93982-0.81617 1.2949-1.6053 1.2949-0.78909 0-1.6053-0.35505-1.6053-1.2949v-6.738c0-1.0551 0.81618-1.2949 1.6053-1.2949zm5.0002 0c0.78909 0 1.6055 0.23981 1.6053 1.2949v6.738c2.4e-4 0.93982-0.81619 1.2949-1.6053 1.2949s-1.6053-0.35505-1.6053-1.2949v-6.738c0-1.0551 0.81618-1.2949 1.6053-1.2949zm33.577 0c0.78909 0 1.6053 0.23981 1.6053 1.2949v6.738c0 0.93982-0.81618 1.2949-1.6053 1.2949-0.7891 0-1.6055-0.35505-1.6053-1.2949v-6.738c-2.4e-4 -1.055 0.81618-1.2949 1.6053-1.2949zm5.0002 0c0.78909 1e-5 1.6053 0.2398 1.6053 1.2949v6.738c0 0.93982-0.81618 1.2949-1.6053 1.2949s-1.6055-0.35505-1.6053-1.2949v-6.738c-2.4e-4 -1.0551 0.81619-1.2949 1.6053-1.2949z" fill="#3296ff"/></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

3
static/FGA_ACT_C.svg Normal file
View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="360mm" height="369.09mm" version="1.1" viewBox="0 0 360 369.09" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g transform="translate(75 36.046)"><g transform="translate(-65.5 -28.331)"><path d="m170.5-0.026086c-7.0846 9.35e-6 -19.026 24.031-19.062 58.205l0.0362 54.388-1.9176 10.818s-0.10598 1.4555-0.99653 1.9124-25.868 13.39-25.868 13.39c0.89235-6.9871 2.094-27.847-0.25947-27.847h-15.911c-3.162 1e-5 -2.337 31.287-0.34733 31.287h1.0188l1.163 3.968-94.45 48.717c-4.8538 2.6647-11.085 6.7435-12.179 12.611l-1.7264 14.181v2.7208l1.8819-8.6075c0.26207-1.101 1.0968-1.7912 2.1833-2.0856l48.418-14.553 1.5222 5.4184 1.2943-6.2934 32.906-10.202 1.2591 4.6033 1.4023-5.4184-0.02378-0.0238 17.489-5.4184h13.63v0.0238l1.1754 4.6751 1.1268-4.6989 27.248-0.014 0.0594 84.392c0.23612 14.268 3.6729 28.282 6.066 42.28 0 0-0.48489 3.4026-4.915 6.2216 0 0-36.324 23.321-38.756 24.91-2.4318 1.5884-3.5142 3.8978-3.5142 8.6897l0.0961 5.1067 52.194-11.616c1.3933 6.3589 5.2092 17.812 5.3703 18.305 0.16114 0.49226 0.88696 1.043 0.88696 1.043s0.44043 0.31323 0.75567 0.31323h1.486c0.31524 0 0.75567-0.31323 0.75567-0.31323s0.72582-0.55079 0.88695-1.043c0.16114-0.49225 3.977-11.946 5.3703-18.305l52.194 11.616 0.0956-5.1067c0-4.7918-1.0819-7.1013-3.5137-8.6897s-38.756-24.91-38.756-24.91c-4.4301-2.8189-4.915-6.2216-4.915-6.2216 2.3931-13.998 5.8299-28.012 6.066-42.28l0.06-84.392 27.248 0.014 1.1273 4.6989 1.1748-4.6751v-0.0238h13.63l17.49 5.4184-0.0238 0.0238 1.4023 5.4184 1.2586-4.6033 32.906 10.202 1.2948 6.2934 1.5222-5.4184 48.418 14.553c1.0865 0.29433 1.9212 0.98458 2.1833 2.0856l1.8824 8.6075v-2.7208l-1.7263-14.181c-1.0936-5.8671-7.3253-9.946-12.179-12.611l-94.45-48.717 1.163-3.968h1.0188c1.9896 0 2.8147-31.287-0.34734-31.287h-15.911c-2.3535 0-1.1518 20.86-0.25947 27.847 0 0-24.977-12.933-25.867-13.39-0.89055-0.45697-0.99705-1.9124-0.99705-1.9124l-1.9176-10.818 0.0362-54.388c-0.03593-34.174-11.977-58.205-19.062-58.205zm-2.373 45.661c0.8389 4.8e-5 1.0544 0.39604 1.0544 1.0673v3.692c0 0.68329-0.21552 1.0792-1.0544 1.0792-0.8389-6e-6 -1.0441-0.39594-1.0441-1.0792v-3.692c0-0.6713 0.20519-1.0673 1.0441-1.0673zm4.7454 0c0.8389 4.8e-5 1.0446 0.39604 1.0446 1.0673v3.692c0 0.68329-0.2057 1.0792-1.0446 1.0792-0.8389-6e-6 -1.0539-0.39594-1.0539-1.0792v-3.692c0-0.6713 0.215-1.0673 1.0539-1.0673zm-46.369 122.62c0.87915-2e-3 1.44 0.52759 1.44 1.4746v5.0349c0 0.82604-0.55805 1.4757-1.44 1.4757-0.88197 0-1.4379-0.64964-1.4379-1.4757v-5.0349c-2e-5 -0.93514 0.55878-1.4724 1.4379-1.4746zm87.992 0c0.87916 2e-3 1.4385 0.5395 1.4385 1.4746v5.0349c0 0.82604-0.55649 1.4757-1.4385 1.4757s-1.44-0.64964-1.44-1.4757v-5.0349c5e-5 -0.94705 0.56085-1.4766 1.44-1.4746zm-81.29 0.0476c0.87915-2e-3 1.4394 0.52759 1.4395 1.4746v5.0349c0 0.82603-0.55752 1.4757-1.4395 1.4757s-1.4385-0.64964-1.4385-1.4757v-5.0349c-2e-5 -0.93514 0.5593-1.4724 1.4385-1.4746zm74.589 0c0.87915 2e-3 1.4385 0.5395 1.4384 1.4746v5.0349c0 0.82603-0.55649 1.4757-1.4384 1.4757-0.88197 0-1.44-0.64964-1.44-1.4757v-5.0349c5e-5 -0.94705 0.56085-1.4766 1.44-1.4746z" fill="#3296ff" fill-rule="evenodd"/><circle cx="170.5" cy="172.29" r="179.31" fill="none" stroke="#3296ff" stroke-linecap="square" stroke-linejoin="round" stroke-miterlimit="3" stroke-width="1.371" style="paint-order:normal"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

2
static/FGA_ACT_D.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="5203mm" height="5463.5mm" version="1.1" viewBox="0 0 5203 5463.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(2496.5 2583.2)"><path d="m103.62-2580.1c-130.08 0-255.75 434.93-255.75 588.23l-0.125 1240.4s-55.621 87.016-114.96 127.88c-59.342 40.86-294.12 203.78-294.12 203.78l12.788-24.751s2e-4 -204.6 0-255.75c-1e-4 -51.151-12.788-63.938-25.575-63.938-12.787 1e-4 -204.6 0-204.6 0-25.575 0-38.363 12.788-38.363 63.938 0 0-0.399 257.75 0 281.33 0.399 23.577 12.788 51.151 12.788 51.151h25.575l-0.62501 98.405s-1405.1 961.43-1444.4 988.54c-39.261 27.115-51.15 42.459-51.15 63.938v217.39l575.44-242.96 869.56-306.9 179.03-51.151 498.74-62.464s-0.736 1167.2-0.02 1290.1c0.706 122.92 63.489 432.48 63.489 432.48l-702.87 539.38-38.363 51.15v166.24l869.78-212.97 12.563 46.73 25.575 89.513 12.788 102.3 12.788 38.363 12.788-38.363 12.788-102.3 25.575-89.513 12.563-46.73 869.78 212.97v-166.24l-38.363-51.15-702.87-539.38s62.783-309.56 63.488-432.48c0.711-122.92-0.05-1290.1-0.05-1290.1l498.77 62.464 179.03 51.151 869.56 306.9 575.44 242.96v-217.39c0-21.479-11.889-36.823-51.151-63.938-39.261-27.115-1444.4-988.54-1444.4-988.54l-0.60001-98.405h25.575s12.389-27.573 12.788-51.151c0.399-23.577 0-281.33 0-281.33 0-51.15-12.788-63.938-38.363-63.938 0 0-191.81 1e-4 -204.6 0-12.788-1e-4 -25.575 12.788-25.575 63.938-2e-4 51.151 0 255.75 0 255.75l12.788 24.751s-234.77-162.92-294.12-203.78c-59.342-40.86-114.99-127.88-114.99-127.88l-0.104-1240.4c-1e-4 -153.3-125.68-588.23-255.75-588.23zm-38.363 409.2c6.347 2e-4 12.787 5.5342 12.787 11.064v67.809c0 5.4289-6.4405 10.865-12.787 10.864-6.3906 2e-4 -12.788-5.4355-12.788-10.864v-67.809c1e-4 -5.5302 6.3971-11.013 12.788-11.064zm76.726 0c6.3906 0.05 12.788 5.5342 12.788 11.064v67.809c-1e-4 5.4289-6.397 10.865-12.788 10.864-6.347 2e-4 -12.788-5.4355-12.788-10.864v-67.809c0-5.5302 6.4406-11.064 12.788-11.064zm-575.44 2186.7h25.575c6.347 2.01e-4 12.788 6.393 12.788 12.788v76.726c0 6.3929-6.4406 12.788-12.788 12.788h-25.575c-6.3906 1e-4 -12.787-6.3947-12.788-12.788v-76.726c1e-4 -6.3947 6.397-12.736 12.788-12.788zm115.09 0h25.575c6.347 2.01e-4 12.788 6.393 12.788 12.788v76.726c0 6.3929-6.4406 12.788-12.788 12.788h-25.575c-6.3906 1e-4 -12.787-6.3947-12.788-12.788v-76.726c2e-4 -6.3947 6.3971-12.736 12.788-12.788zm818.41 0h25.575c6.3907 0.05 12.788 6.393 12.788 12.788v76.726c-2e-4 6.3929-6.397 12.788-12.788 12.788h-25.575c-6.347 1e-4 -12.788-6.3947-12.788-12.788v-76.726c0-6.3947 6.4406-12.787 12.788-12.788zm115.09 0h25.575c6.3906 0.05 12.788 6.393 12.788 12.788v76.726c-1e-4 6.3929-6.3971 12.788-12.788 12.788h-25.575c-6.347 1e-4 -12.788-6.3947-12.788-12.788v-76.726c0-6.3947 6.4406-12.787 12.788-12.788zm-1048.6 140.66h25.575c6.347 1e-4 12.788 6.3929 12.788 12.787v76.726c0 6.3929-6.4406 12.788-12.788 12.788h-25.575c-6.3906 1e-4 -12.787-6.3947-12.788-12.788v-76.726c1e-4 -6.3946 6.397-12.736 12.788-12.787zm115.09 0h25.575c6.347 1e-4 12.788 6.3929 12.788 12.787v76.726c0 6.3929-6.4406 12.788-12.788 12.788h-25.575c-6.3906 1e-4 -12.787-6.3947-12.788-12.788v-76.726c2e-4 -6.3946 6.3971-12.736 12.788-12.787zm818.41 0h25.575c6.3907 0.05 12.788 6.3929 12.788 12.787v76.726c-2e-4 6.3929-6.397 12.788-12.788 12.788h-25.575c-6.347 1e-4 -12.788-6.3947-12.788-12.788v-76.726c0-6.3946 6.4406-12.787 12.788-12.787zm115.09 0h25.575c6.3906 0.05 12.788 6.3929 12.788 12.787v76.726c-1e-4 6.3929-6.3971 12.788-12.788 12.788h-25.575c-6.347 1e-4 -12.788-6.3947-12.788-12.788v-76.726c0-6.3946 6.4406-12.787 12.788-12.787z" fill="#3296ff" fill-rule="evenodd"/><circle cx="105" cy="135.8" r="2582.6" fill="none" stroke="#3296ff" stroke-linecap="square" stroke-linejoin="round" stroke-width="37.746" style="paint-order:normal"/></g></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

2
static/FGA_ACT_E.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="660mm" height="735.62mm" version="1.1" viewBox="0 0 660 735.62" xmlns="http://www.w3.org/2000/svg"><g transform="translate(227.07 219.31)"><g transform="translate(-220.16 -219.35)"><path d="m323.98 0.041596c-12.621 1.1404-32.35 58.556-32.35 95.304v172.91l-46.402 31.844c1.7124-7.7226 3.623-25.137-0.66208-43.984h-33.315c-3.6679 12.285-5.2993 33.094 0.0391 51.47l3.3026 0.0898 3.0428 11.261c-67.538 46.949-135.82 92.856-204.08 138.75-2.0153 1.3847-2.119 1.8573-2.119 1.8573l-11.437 22.93v8.1344l26.342-17.935 71.254-28.565 34-11.466 0.69527 3.201 1.5605-3.992 51.247-17.319 1.0976 3.6346 1.041-4.3845 29.414-9.9624 21.011-3.2655 0.82027 6e-3 1.3632 4.7322 1.7772-4.7341 50.009-0.0273s-0.0109 126.27 0 148.94c0.0156 32.451 7.5281 73.581 8.5445 79.039s3.3484 11.282-3.1483 16.589c-6.4967 5.3071-71.763 58.406-77.1 62.77-5.3362 4.364-4.9704 10.253-4.7556 11.913s1.9276 15.884 1.9276 15.884l98.173-36.949 7.8414 28.559 1.7675 4e-3 7.8414-28.559 98.171 36.949s1.7128-14.223 1.9276-15.884 0.58251-7.5514-4.7536-11.915c-5.3362-4.3639-70.605-57.461-77.102-62.768-6.4967-5.3071-4.1647-11.131-3.1483-16.589s8.5308-46.587 8.5464-79.039c0.0109-22.668 0-148.94 0-148.94l50.007 0.0273 1.7773 4.7341 1.3632-4.7322 0.82222-8e-3 21.011 3.2674 29.413 9.9624 1.041 4.3826 1.0976-3.6326 51.249 17.319 1.5605 3.992 0.69331-3.201 34.002 11.464 71.252 28.567 26.344 17.935-2e-3 -8.1344-11.435-22.93s-0.1057-0.47265-2.121-1.8573c-68.258-45.89-136.54-91.8-204.08-138.75l3.0409-11.259 3.3045-0.0918c5.3383-18.376 3.705-39.185 0.0371-51.47h-33.315c-4.2851 18.847-2.3726 36.264-0.66013 43.986l-46.402-31.846v-172.91c0-36.748-19.762-94.167-32.383-95.308zm-4.3806 54.566h2.0546c0.8995-7e-6 1.2851 0.38391 1.2851 1.2812v7.5582c-4e-5 0.89669-0.38558 1.2812-1.2851 1.2812h-2.0546c-0.89949-9e-6 -1.2246-0.38449-1.2206-1.2812v-7.5582c-4e-3 -0.89728 0.32117-1.2812 1.2206-1.2812zm6.6422 0h2.0546c0.8995-7e-6 1.2851 0.38391 1.2851 1.2812v7.5582c-4e-5 0.89669-0.38558 1.2812-1.2851 1.2812h-2.0546c-0.89948-9e-6 -1.2246-0.38449-1.2206-1.2812v-7.5582c-4e-3 -0.89728 0.32117-1.2812 1.2206-1.2812zm43.543 294.97h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89668-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89676 0.38559-1.2813 1.2851-1.2812zm13.968 0h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89668-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89958-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89676 0.38559-1.2813 1.2851-1.2812zm-122 0.0488h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89667-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-8e-5 -0.89675 0.38559-1.2813 1.2851-1.2812zm13.97 0h2.5057c0.89955-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89667-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89675 0.38558-1.2813 1.2851-1.2812zm94.048 13.818h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89668-0.38552 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89675 0.38559-1.2813 1.2851-1.2812zm13.97 0h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89668-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89675 0.38559-1.2813 1.2851-1.2812zm-122 0.0488h2.5057c0.89955-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89667-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89675 0.38559-1.2813 1.2851-1.2812zm13.97 0h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89667-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89675 0.38558-1.2813 1.2851-1.2812zm97.866 6.2614h8.8667zm-108.03 0.0488h8.8667zm104.23 7.5406h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89667-0.38552 1.2792-1.2851 1.2792h-2.5057c-0.89957-5e-5 -1.2852-0.38261-1.2851-1.2792v-8.8394c-7e-5 -0.89675 0.38559-1.2813 1.2851-1.2812zm13.97 0h2.5057c0.89954-6e-5 1.2831 0.38451 1.2831 1.2812v8.8394c2e-5 0.89667-0.38357 1.2792-1.2831 1.2792h-2.5057c-0.89957-5e-5 -1.2852-0.38261-1.2851-1.2792v-8.8394c-7e-5 -0.89675 0.38559-1.2813 1.2851-1.2812zm-122 0.0488h2.5057c0.89955-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c1e-5 0.89667-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89675 0.38559-1.2813 1.2851-1.2812zm13.97 0h2.5057c0.89954-6e-5 1.2851 0.38451 1.2851 1.2812v8.8394c2e-5 0.89667-0.38553 1.2812-1.2851 1.2812h-2.5057c-0.89957-5e-5 -1.2852-0.38456-1.2851-1.2812v-8.8394c-7e-5 -0.89675 0.38558-1.2813 1.2851-1.2812z" fill="#3296ff"/><circle cx="323.1" cy="368.73" r="328.11" fill="none" stroke="#3296ff" stroke-width="3.7896" style="paint-order:normal"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

17
static/FGA_ACT_F.svg Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="211.67mm" height="211.67mm" version="1.1" viewBox="0 0 211.67 211.67" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(953.33 -995.17)">
<path d="m-742.19 1101c0 27.929-11.095 54.714-30.844 74.463-19.749 19.749-46.534 30.844-74.463 30.844-27.929 0-54.714-11.095-74.463-30.844-19.749-19.749-30.844-46.534-30.844-74.463 0-27.929 11.095-54.714 30.844-74.463 19.749-19.749 46.534-30.844 74.463-30.844 27.929 0 54.714 11.095 74.463 30.844 19.749 19.749 30.844 46.534 30.844 74.463z" fill="none" stroke="#3296ff" stroke-linejoin="round" stroke-width="1.0531"/>
<path d="m-847.5 1004.7c-7.4097 0-9.4794 27.242-9.4556 35.842v13.103l-1.2407 2.0809-2.6718 3.1116-5.7373 4.7856-15.217 12.445c0.26327-2.4217 0.81772-10.57-0.18346-11.848l-8.6782 0.072c-0.70678 0.8906-1.6381 10.501 0.13788 15.728h1.1029l0.38829 1.823-21.816 16.071c0.5494-2.3946 1.2496-11.314 0.25043-13.182l-8.8583 0.014c-1.0492 4.8589-1.1533 10.88-0.0459 15.696l1.2043 0.04 0.50012 2.5271s-28.608 21.118-31.045 22.91c-2.4373 1.7913-3.8489 5.5589-3.8729 8.2254-0.0241 2.6665-0.0401 5.2116-0.0401 5.2116l35.659-15.492 0.65645 4.4375 1.1948-5.2253 9.3938-3.722 0.4792 5.7045 1.5361-6.4857 9.1575-3.7024 0.83374 5.6914 1.1619-6.4791 8.8095-3.6302 0.53827 2.4748 1.0438-3.1115 9.1247-2.9869 0.64327 2.4748 0.93875-2.9147 12.853-3.4791 0.32163 5.0153 1.4704 10.352 0.033 19.858c0.17362 4.5575 0.64186 9.1069 1.287 13.621l1.477 6.7877c0.23946 1.6304-0.74861 3.3195-1.8528 4.424 0 0-27.318 20.921-28.298 21.678-0.98046 0.7565-1.2339 2.0863-1.2339 2.0863l-2.2479 10.338 25.84-9.5902 11.73-4.0193 2.1403 6.7961 0.58694 6.9848 0.58694-6.9848 2.1403-6.7961 11.73 4.0193 25.84 9.5902-2.2479-10.338s-0.25346-1.3298-1.2339-2.0863c-0.98047-0.7566-28.298-21.678-28.298-21.678-1.1042-1.1045-2.0923-2.7936-1.8528-4.4239l1.477-6.7877c0.64515-4.5145 1.1134-9.0639 1.287-13.621l0.033-19.858 1.4705-10.352 0.32163-5.0153 12.853 3.4791 0.93876 2.9147 0.64326-2.4748 9.1247 2.9869 1.0438 3.1115 0.53826-2.4748 8.8095 3.6302 1.1619 6.4791 0.83375-5.6914 9.1575 3.7024 1.5361 6.4857 0.4792-5.7045 9.3938 3.722 1.1948 5.2253 0.65644-4.4375 35.659 15.492s-0.016-2.5452-0.0401-5.2117-1.4357-6.4341-3.8729-8.2254c-2.4373-1.7912-31.045-22.91-31.045-22.91l0.50013-2.5271 1.2043-0.04c1.1074-4.816 1.0033-10.837-0.0459-15.696l-8.8583-0.014c-0.99921 1.8687-0.29898 10.788 0.25042 13.182l-21.816-16.071 0.3883-1.8229h1.1029c1.776-5.2277 0.84466-14.838 0.13787-15.728l-8.6782-0.072c-1.0012 1.2776-0.44672 9.4259-0.18345 11.848l-15.217-12.445-5.7373-4.7856-2.6718-3.1116-1.2407-2.0809v-13.103c0.0237-8.6006-2.046-35.842-9.4556-35.842zm-2.0804 12.256h1.3916v3.5317h-1.3916zm2.8227 0h1.4048v3.5317h-1.4048zm-19.766 73.142h1.3982v3.5317h-1.3982zm3.5777 0h1.3917v3.5317h-1.3917zm29.501 0h1.3916v3.5317h-1.3916zm3.5645 0h1.4048v3.5317h-1.4048zm-36.643 4.2932h1.3982v3.5251h-1.3982zm3.5777 0h1.3917v3.5251h-1.3917zm29.501 0h1.3916v3.5251h-1.3916zm3.5645 0h1.4048v3.5251h-1.4048zm-27.426 2.0481h1.3983v3.5186h-1.3983zm14.015 0h1.4048v3.5186h-1.4048zm-9.919 0.01h1.3983v3.5251h-1.3983zm14.015 0h1.4048v3.5251h-1.4048zm-18.112 4.4901h1.3983v3.5251h-1.3983zm4.0962 0h1.3983v3.5251h-1.3983zm9.919 0h1.4048v3.5251h-1.4048zm4.0963 0h1.4048v3.5251h-1.4048zm-18.112 4.5033h1.3983v3.5316h-1.3983zm4.0962 0h1.3983v3.5316h-1.3983zm9.919 0h1.4048v3.5316h-1.4048zm4.0963 0h1.4048v3.5316h-1.4048z" fill="#3296ff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

89
static/FGA_THR.svg Normal file
View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
height="64"
version="1.1"
viewBox="0 0 64 64"
id="svg16"
sodipodi:docname="FGA_THR.svg"
inkscape:version="0.92.1 r15371">
<metadata
id="metadata22">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs20" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1147"
id="namedview18"
showgrid="false"
inkscape:zoom="4"
inkscape:cx="-31.957369"
inkscape:cy="57.205794"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg16"
units="px" />
<g
transform="matrix(0.48381287,0,0,0.48380165,-18.79736,-39.8415)"
id="g14">
<g
transform="matrix(1.0265,0,0,1.0265,-2.7864,-3.9408)"
id="g12"
style="fill-rule:evenodd">
<path
transform="matrix(0.26458,0,0,0.26458,38.854,82.354)"
d="m 294.7,489.09 -44.15,-72.041 -44.803,72.281 c 14.546,2.7042 29.306,4.1868 44.131,4.209 15.063,-0.066 30.057,-1.6154 44.822,-4.4492 z"
style="paint-order:normal"
id="path2"
inkscape:connector-curvature="0" />
<path
transform="matrix(0.26458,0,0,0.26458,38.854,82.354)"
d="M 318.85,483.12 250.557,371.68 181.289,483.43 c 1.3922,0.41046 2.7334,0.92574 4.1367,1.3125 6.7072,1.8521 13.5,3.3069 20.328,4.5762 l 44.803,-72.281 44.15,72.041 c 6.3642,-1.2214 12.699,-2.6112 18.957,-4.3398 1.7592,-0.47721 3.4438,-1.1087 5.1855,-1.623 z"
style="fill:#fffc00;paint-order:normal"
id="path4"
inkscape:connector-curvature="0" />
<path
transform="matrix(0.26458,0,0,0.26458,38.854,82.354)"
d="m 7.793,274.15 c 9.8503,98.716 78.612,181.32 173.49,209.29 l 69.268,-111.75 68.293,111.44 c 94.698,-27.966 163.41,-110.38 173.36,-208.98 z"
style="paint-order:normal"
id="path6"
inkscape:connector-curvature="0" />
<path
d="m 105,84.064 a 64.436,64.436 0 0 0 -6.9536,0.45217 v 63.984 H 91.068 v -62.829 a 64.436,64.436 0 0 0 -3.4644,0.79478 v 62.034 h -6.9784 v -59.598 a 64.436,64.436 0 0 0 -3.4644,1.5503 v 58.048 h -6.9784 v -54.219 a 64.436,64.436 0 0 0 -3.4644,2.4774 v 51.742 h -6.9784 v -45.829 a 64.436,64.436 0 0 0 -3.4644,3.7192 v 42.11 h -6.9784 v -32.27 a 64.436,64.436 0 0 0 -3.4644,6.8011 v 25.469 H 40.564 a 64.436,64.436 0 0 0 0.08992,2.5476 h 128.65 a 64.436,64.436 0 0 0 0.12764,-2.5476 h -5.2684 v -25.336 a 64.436,64.436 0 0 0 -3.4644,-6.8952 v 32.232 h -6.9784 v -42.046 a 64.436,64.436 0 0 0 -3.4644,-3.7445 v 45.79 h -6.9784 v -51.738 a 64.436,64.436 0 0 0 -3.4644,-2.4035 v 54.141 h -6.9784 v -58.023 a 64.436,64.436 0 0 0 -3.4644,-1.5885 v 59.611 h -6.9784 v -61.978 a 64.436,64.436 0 0 0 -3.4644,-0.8847 v 62.863 h -6.9784 v -64.035 a 64.436,64.436 0 0 0 -6.9536,-0.40049 z"
style="paint-order:normal"
id="path8"
inkscape:connector-curvature="0" />
<path
d="m 86.789,198.81 18.357,-29.616 18.149,29.615 h 7.3544 l -25.503,-41.615 -25.796,41.617 h 7.4388"
style="fill:#fffc00;paint-order:normal"
id="path10"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

146
static/tower.svg Normal file
View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="FGA Tower Icon.svg"
viewBox="0 0 248.26501 249.26443"
height="32"
width="32"
id="svg833"
version="1.1">
<metadata
id="metadata839">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs837" />
<sodipodi:namedview
inkscape:current-layer="layer1"
inkscape:window-maximized="1"
inkscape:window-y="-9"
inkscape:window-x="-9"
inkscape:cy="131.18309"
inkscape:cx="130.09189"
inkscape:zoom="2.8284272"
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
showgrid="false"
id="namedview835"
inkscape:window-height="1361"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff"
inkscape:document-rotation="0"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-bbox="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-global="true" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Background" />
<g
transform="translate(7.706459,7.9160461)"
inkscape:label="Headset"
id="layer1"
inkscape:groupmode="layer">
<g
id="g962">
<circle
r="124.1325"
cy="116.21645"
cx="116.42604"
id="path1622"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="sccsccs"
d="m 88.489103,214.89893 c 0,-5.21922 4.23103,-9.45026 9.450184,-9.45033 l 36.986813,-6e-5 c 5.20585,0 9.43688,4.23103 9.43688,9.45026 0,5.21922 -4.23103,9.45026 -9.43688,9.45012 l -36.986813,7e-5 c -5.219154,2e-4 -9.450184,-4.23083 -9.450184,-9.45006 z"
style="fill:#000000;fill-opacity:1;stroke:#00a8b2;stroke-width:0.0994765;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path849-5" />
<g
style="fill:#000000;fill-opacity:1"
transform="translate(-3.4175518e-4)"
id="g1992">
<path
id="path849-5-5"
style="fill:#000000;fill-opacity:1;stroke:#00a8b2;stroke-width:0.150978;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 226.14123,79.658933 c -7.92132,0 -14.34375,6.421246 -14.34375,14.322266 v 56.134771 c 1e-4,7.92121 6.42243,14.34377 14.34375,14.34375 1.77942,0 3.47461,-0.33935 5.04687,-0.93164 a 124.1325,124.1325 0 0 0 9.29492,-43.01367 v -8.59961 a 124.1325,124.1325 0 0 0 -4.2207,-28.076179 c -2.59403,-2.582096 -6.16991,-4.179688 -10.12109,-4.179688 z" />
<path
id="path849-5-5-8"
style="fill:#000000;fill-opacity:1;stroke:#00a8b2;stroke-width:0.150978;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 6.711542,79.658933 c -3.950201,0 -7.5252386,1.596784 -10.1191409,4.177734 A 124.1325,124.1325 0 0 0 -7.6302551,111.8855 v 8.66211 a 124.1325,124.1325 0 0 0 9.2949219,42.98047 c 1.5722702,0.59229 3.2674492,0.93164 5.0468752,0.93164 7.92132,0 14.34406,-6.42254 14.34375,-14.34375 V 93.981199 c 2.1e-4,-7.901011 -6.42245,-14.322266 -14.34375,-14.322266 z" />
</g>
<g
id="g916"
style="stroke:#00989a;stroke-opacity:1">
<path
id="rect1605"
style="fill:none;fill-opacity:1;stroke:#00989a;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 53.844354,143.81714 v 79.60352 a 124.1325,124.1325 0 0 0 62.582026,16.92773 124.1325,124.1325 0 0 0 63.28907,-17.34766 v -79.18359 z" />
<polygon
style="fill:#00989a;fill-opacity:1;stroke:#00989a;stroke-opacity:1"
id="polygon986"
points="89.486,243.876 71.941,139.481 26.677,139.481 61.767,243.876 "
transform="matrix(0.75835957,0,0,0.75835957,6.0968618,-47.492855)" />
<polygon
style="fill:#00989a;fill-opacity:1;stroke:#00989a;stroke-opacity:1"
id="polygon988"
points="151.484,139.481 151.484,243.876 189.314,243.876 206.859,139.481 "
transform="matrix(0.75835957,0,0,0.75835957,6.0968618,-47.492855)" />
<polygon
style="fill:#00989a;fill-opacity:1;stroke:#00989a;stroke-opacity:1"
id="polygon990"
points="84.109,139.481 101.654,243.876 139.484,243.876 139.484,139.481 "
transform="matrix(0.75835957,0,0,0.75835957,6.0968618,-47.492855)" />
<polygon
style="fill:#00989a;fill-opacity:1;stroke:#00989a;stroke-opacity:1"
id="polygon992"
points="201.482,243.876 229.201,243.876 264.291,139.481 219.027,139.481 "
transform="matrix(0.75835957,0,0,0.75835957,6.0968618,-47.492855)" />
<rect
y="42.483017"
x="44.615604"
height="9.8302555"
width="143.62088"
id="rect1605-7"
style="fill:#00989a;fill-opacity:1;stroke:#00989a;stroke-width:2.50089;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<path
sodipodi:nodetypes="cc"
id="path1761"
d="m 221.22133,163.59357 c -16.80353,25.19128 -43.642,43.64367 -76.66957,51.11645"
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path913"
d="M 6.8856252,81.848629 C 21.611214,33.687043 66.066978,0.7936943 116.42946,0.79583065 c 50.36251,0.00213 94.81547,32.89923835 109.537,81.06206135"
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

18
test/mocha/index.js Normal file
View File

@ -0,0 +1,18 @@
'use strict'
// Set BABEL_ENV to use proper env config
process.env.BABEL_ENV = 'test'
// Enable use of ES6+ on required files
require('babel-register')({
ignore: /node_modules/
})
// Attach Chai APIs to global scope
const { expect, should, assert } = require('chai')
global.expect = expect
global.should = should
global.assert = assert
// Require all JS files in `./specs` for Mocha to consume
require('require-dir')('./specs')

View File

@ -0,0 +1,228 @@
var assert = require('chai').assert;
var Worker = require("tiny-worker");
process.chdir('src/renderer/utils');
describe("Test Check", function () {
describe("Routing Checks", function () {
it("Not legitimate End", function (done) {
var data = [
{ 'start': 1, 'end': 2, '_leaflet_id': 1, 'type': 'poly', 'isPushBackRoute': true }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === 1).filter(m => m.message === 'Node not a legimate end'), 1);
assert.lengthOf(e.data[1].filter(m => m.id === 2).filter(m => m.message === 'Node not a legimate end'), 1);
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 0);
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
it("Legitimate End Runway", function (done) {
var data = [
{ 'start': 1, 'end': 2, '_leaflet_id': 1, 'type': 'poly', 'isPushBackRoute': true, 'direction': 'bi-directional' },
{ 'index': 1, '_leaflet_id': 2, 'type': 'runway' }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === 1).filter(m => m.message === 'No way from runway to each parking'), 1);
assert.lengthOf(e.data[1].filter(m => m.id === 2).filter(m => m.message === 'Node not a legimate end'), 1);
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 0);
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
it("From Parking to Runway bi-directional", function (done) {
var data = [
{ 'start': 1, 'end': 2, '_leaflet_id': 1, 'type': 'poly', 'isPushBackRoute': true, direction: 'bi-directional' },
{ 'index': 1, '_leaflet_id': 2, 'type': 'runway' },
{ 'index': 2, '_leaflet_id': 3, 'name': 'name', parkingType: 'gate', 'type': 'parking' }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === -1).filter(m => m.message === 'Routes from runways OK'), 1);
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 1);
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
it("From Parking to Runway forward OK", function (done) {
var data = [
{ 'start': 1, 'end': 2, '_leaflet_id': 1, 'type': 'poly', 'isPushBackRoute': true, direction: 'forward' },
{ 'index': 1, '_leaflet_id': 3, 'name': 'name', parkingType: 'gate', 'type': 'parking' },
{ 'index': 2, '_leaflet_id': 2, 'type': 'runway' }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === -1).filter(m => m.message === 'Routes from runways OK'), 1);
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 1);
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
it("From Parking to Runway forward Not Ok", function (done) {
var data = [
{ 'start': 1, 'end': 2, '_leaflet_id': 1, 'type': 'poly', 'isPushBackRoute': true, direction: 'forward' },
{ 'index': 2, '_leaflet_id': 3, 'name': 'name', parkingType: 'gate', 'type': 'parking' },
{ 'index': 1, '_leaflet_id': 2, 'type': 'runway' }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === -1).filter(m => m.message === 'Routes from runways OK'), 0);
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 1);
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
it("From Parking to Runway reverse OK", function (done) {
var data = [
{ 'start': 1, 'end': 2, '_leaflet_id': 1, 'type': 'poly', 'isPushBackRoute': true, direction: 'backward' },
{ 'index': 2, '_leaflet_id': 3, 'name': 'name', parkingType: 'gate', 'type': 'parking' },
{ 'index': 1, '_leaflet_id': 2, 'type': 'runway' }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === -1).filter(m => m.message === 'Routes from runways OK'), 1);
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 1);
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
it("From Parking to Runway reverse Not OK", function (done) {
var data = [
{ 'start': 1, 'end': 2, '_leaflet_id': 1, 'type': 'poly', 'isPushBackRoute': true, direction: 'backward' },
{ 'index': 1, '_leaflet_id': 3, 'name': 'name', parkingType: 'gate', 'type': 'parking' },
{ 'index': 2, '_leaflet_id': 2, 'type': 'runway' }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === -1).filter(m => m.message === 'Routes from runways OK'), 0, 'No Routes from Runway');
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 1, 'No invalid ends');
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
});
describe("Routing Checks", function () {
it("Runway Node not on Runway", function (done) {
var data = [
{ 'index': 2, '_leaflet_id': 2, 'type': 'runway' },
{ 'name': 'R29', 'type': 'runway_area', 'polygon': [] }
];
var worker = new Worker('check.js');
worker.onmessage = function (e) {
if (e.data === 'checkStarted') {
} else if (e.data[0] === 'DONE') {
console.log('DONE')
worker.terminate()
if (e.data[1].length === 0) {
done('Crashed')
} else {
console.log(e.data[1]);
console.log(e.data[1].filter(m => m.id === 1));
assert.lengthOf(e.data[1].filter(m => m.id === -1).filter(m => m.message === 'Routes from runways OK'), 0, 'No Routes from Runway');
assert.lengthOf(e.data[1].filter(m => m.message === 'No invalid ends'), 1, 'No invalid ends');
done()
}
} else if (e.data.length > 0) {
}
};
worker.postMessage(['check', data]);
});
});
describe("", function () {
});
});

View File

@ -1,13 +0,0 @@
import Vue from 'vue'
Vue.config.devtools = false
Vue.config.productionTip = false
// require all test files (files that ends with .spec.js)
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)
// require all src files except main.js for coverage.
// you can also change this to match only the subset of files that
// you want coverage for.
const srcContext = require.context('../../src/renderer', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)

View File

@ -1,62 +0,0 @@
'use strict'
const path = require('path')
const merge = require('webpack-merge')
const webpack = require('webpack')
const baseConfig = require('../../.electron-vue/webpack.renderer.config')
const projectRoot = path.resolve(__dirname, '../../src/renderer')
// Set BABEL_ENV to use proper preset config
process.env.BABEL_ENV = 'test'
let webpackConfig = merge(baseConfig, {
devtool: '#inline-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"testing"'
})
]
})
// don't treat dependencies as externals
delete webpackConfig.entry
delete webpackConfig.externals
delete webpackConfig.output.libraryTarget
// apply vue option to apply isparta-loader on js
webpackConfig.module.rules
.find(rule => rule.use.loader === 'vue-loader').use.options.loaders.js = 'babel-loader'
module.exports = config => {
config.set({
browsers: ['visibleElectron'],
client: {
useIframe: false
},
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
},
customLaunchers: {
'visibleElectron': {
base: 'Electron',
flags: ['--show']
}
},
frameworks: ['mocha', 'chai'],
files: ['./index.js'],
preprocessors: {
'./index.js': ['webpack', 'sourcemap']
},
reporters: ['spec', 'coverage'],
singleRun: true,
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true
}
})
}

View File

@ -1,13 +0,0 @@
import Vue from 'vue'
import LandingPage from '@/components/LandingPage'
describe('LandingPage.vue', () => {
it('should render correct contents', () => {
const vm = new Vue({
el: document.createElement('div'),
render: h => h(LandingPage)
}).$mount()
expect(vm.$el.querySelector('.title').textContent).to.contain('Welcome to your new project!')
})
})