diff --git a/package.json b/package.json index 5ed4550..52ce56c 100644 --- a/package.json +++ b/package.json @@ -56,13 +56,16 @@ } }, "dependencies": { + "@turf/turf": "^5.1.6", "axios": "^0.18.0", "electron-debug": "^3.0.1", "element-ui": "^2.12.0", "file-url": "^3.0.0", "font-awesome": "^4.7.0", "fs": "0.0.1-security", + "geo-coordinates-parser": "^1.2.3", "idb": "^4.0.5", + "jquery": "^3.4.1", "leaflet": "^1.5.1", "leaflet-editable": "^1.2.0", "leaflet-sidebar-v2": "^3.2.1", diff --git a/src/renderer/components/EditLayer.vue b/src/renderer/components/EditLayer.vue index de774a0..05aac05 100644 --- a/src/renderer/components/EditLayer.vue +++ b/src/renderer/components/EditLayer.vue @@ -7,26 +7,34 @@ diff --git a/src/renderer/components/FlightgearMap.vue b/src/renderer/components/FlightgearMap.vue index 6adb712..4801d1c 100644 --- a/src/renderer/components/FlightgearMap.vue +++ b/src/renderer/components/FlightgearMap.vue @@ -2,6 +2,7 @@ --> - + OpenStreetMap contributors', marker: L.latLng(47.413220, -1.219482), - airports: this.$store.state.Airports.airports + airports: this.$store.state.Airports.airports, + options: {editable: true} } }, methods: { @@ -60,12 +62,13 @@ }, zoomUpdated (zoom) { this.$store.commit('ZOOM', zoom) + console.log(this.$refs.airportLayer.setVisible(zoom < 12)) }, centerUpdated (center) { this.$store.commit('CENTER', center) }, boundsUpdated (bounds) { - this.bounds = bounds + this.$store.commit('BOUNDS', bounds) } }, computed: { diff --git a/src/renderer/components/LeafletSidebar.vue b/src/renderer/components/LeafletSidebar.vue index 10d67a1..3aaf56a 100644 --- a/src/renderer/components/LeafletSidebar.vue +++ b/src/renderer/components/LeafletSidebar.vue @@ -26,10 +26,6 @@
-

- Scanning -
-

diff --git a/src/renderer/components/RunScan.vue b/src/renderer/components/RunScan.vue index ad7186b..87714da 100644 --- a/src/renderer/components/RunScan.vue +++ b/src/renderer/components/RunScan.vue @@ -1,32 +1,55 @@ diff --git a/src/renderer/loaders/ParkingSpot.js b/src/renderer/loaders/ParkingSpot.js new file mode 100644 index 0000000..0ca8d0a --- /dev/null +++ b/src/renderer/loaders/ParkingSpot.js @@ -0,0 +1,109 @@ +/* eslint-disable */ +const convert = require('geo-coordinates-parser'); +const leaflet = require('leaflet'); +const turf = require('@turf/turf'); +const util = require('util'); + +var $ = require('jquery'); + +L.ParkingSpot = L.Circle.extend({ + id: String, + attributes: Object, + + createDirection: function () { + if (this.direction === undefined ) { + var start = this._latlng; + var options = { units: 'kilometers' }; + var end = turf.destination([start.lat, start.lng], this.attributes.radius / 1000, this.attributes.heading - 180, options); + // Resize, since leaflet is wrong + var rad2 = start.distanceTo(end.geometry.coordinates); + this.setRadius(rad2); + this.editor._resizeLatLng.__vertex.setLatLng(end.geometry.coordinates); + this.direction = L.polyline([start, end.geometry.coordinates]); + this.direction.addTo(map); + + + this.on('editable:drawing:move', function (event) { + this.updateDirectionFromVertex(); + }); + } + }, + // + updateVertexFromDirection: function () { + if (this.editEnabled()) { + var start = this._latlng; + var options = { units: 'kilometers' }; + var end = turf.destination([start.lat, start.lng], this.attributes.radius / 1000, this.attributes.heading - 180, options); + // Resize, since leaflet is wrong + var rad2 = start.distanceTo(end.geometry.coordinates); + this.setRadius(rad2); + if(this.editor._resizeLatLng.__vertex!== undefined){ + this.editor._resizeLatLng.__vertex.setLatLng(end.geometry.coordinates); + } + this.direction.setLatLngs([start, end.geometry.coordinates]); + } + }, + updateDirectionFromVertex: function () { + if (this.editEnabled()) { + var start = this._latlng; + var end = this.editor._resizeLatLng.__vertex.getLatLng(); + var heading = turf.bearing([start.lat, start.lng], [end.lat, end.lng]); + this.attributes.heading = heading + 180; + this.direction.setLatLngs([start, end]); + } + }, + _getLatRadius: function () { + return this._mRadius; + }, + + _getLngRadius: function () { + return this._mRadius; + }, +}); + +var parkingSpot = function (n, layerGroup) { + + //console.log(n.attr('lat') + " " + n.attr('lon')); + 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') }); + circle.on('add', function (event) { + console.log(event); + }); + circle.id = n.attr('index'); + /* + +*/ + //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.log( key + "\t" + value); + + if(isNaN(value)) + circle.attributes[ key ] = value; + else + circle.attributes[ key ] = Number( value); + }); + + + circle.addTo(layerGroup); + // circle.enableEdit(); + circle.on('dblclick', function (event) { L.DomEvent.stop(); circle.toggleEdit(); }); + circle.on('click', function (event) { + console.log("Click : " + event.target); + }); + // circle.createDirection(); + return circle; +} + +module.exports = parkingSpot; \ No newline at end of file diff --git a/src/renderer/loaders/groundnet_loader.js b/src/renderer/loaders/groundnet_loader.js new file mode 100644 index 0000000..e432987 --- /dev/null +++ b/src/renderer/loaders/groundnet_loader.js @@ -0,0 +1,214 @@ +/* eslint-disable */ +const fs = require('fs'); +const path = require('path'); +var xamel = require('xamel'); +const convert = require('geo-coordinates-parser'); + +const markers = require('./markers'); + +const parkingSpot = require('./ParkingSpot.js'); + +const util = require('util'); + +console.log(convert.formats); + +var featureLookup = {}; + +exports.readGroundnetXML = function (fDir, icao) { + try { + layerGroup = L.layerGroup(); + var f = path.join(fDir, icao[0], icao[1], icao[2], icao + '.groundnet.xml'); + + if (f == null || !fs.existsSync(f)) + return; + + 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 parkingNodes = xml.find('groundnet/parkingList/Parking'); + console.log("Parking Nodes" + parkingNodes.length); + + var merged = new Array(); + + var nodesLookup = {}; + + parkingNodes.map(n => { + var circle = parkingSpot(n, layerGroup); + circle.on('editable:drawing:move', function (event) { +// console.log("Follow 1: " + event.sourceTarget ); +// console.log("Follow 2: " + event.sourceTarget._dragStartTarget ); +// console.log("Follow 3: " + event.target.editor._resizeLatLng.__vertex ); +// console.log("Follow 4: " + event.target.editor._resizeLatLng.__vertex._icon ); +// console.log("Follow 5: " + (event.target.editor._resizeLatLng.__vertex === event.sourceTarget) ); +// console.log("Follow 6: " + (event.target.editor._resizeLatLng.__vertex._icon === event.sourceTarget._element) ); +// console.log("Follow 7: " + (event.target.editor._resizeLatLng.__vertex._icon === event.sourceTarget._dragStartTarget) ); +// console.log("Follow 8: " + (event.target.editor._resizeLatLng.__vertex._icon._leaflet_id === event.sourceTarget._dragStartTarget._leaflet_id) ); + + // Is it the edit vertex moving? + if(event.target.editor._resizeLatLng.__vertex._icon !== event.sourceTarget._element){ + follow(event.target.id, event); + } + + }); + nodesLookup[n.attr('index')] = n; + featureLookup[n.attr('index')] = new Array(); + featureLookup[n.attr('index')].push(circle); + + features.push(circle); + }).sort(); + // Get all nodes into the lookup + var taxiNodes = xml.find('groundnet/TaxiNodes/node'); + taxiNodes.map(n => { + //attrs.lat + //console.log(n.attr('lat') + " " + n.attr('lon')); + var latlon = convert(n.attr('lat') + " " + n.attr('lon')); + //console.log(latlon.decimalLatitude); + + nodesLookup[n.attr('index')] = n; + }).sort(); + + + var taxiArcs = xml.find('groundnet/TaxiWaySegments/arc'); + taxiArcs.map(n => { + var begin = nodesLookup[n.attr('begin')]; + var end = nodesLookup[n.attr('end')]; + if (typeof begin !== 'undefined' && typeof end !== 'undefined') { + var bidirectional = false; + if (typeof featureLookup[n.attr('begin')] !== 'undefined') { + featureLookup[n.attr('begin')].forEach(element => { + if (element instanceof L.TaxiwaySegment && element.end === n.attr('begin') && element.begin === n.attr('end')) { + element.bidirectional = true; + bidirectional = true; + } + }); + } + if (typeof featureLookup[n.attr('end')] !== 'undefined') { + featureLookup[n.attr('end')].forEach(element => { + if (element instanceof L.TaxiwaySegment && element.end === n.attr('begin') && element.begin === n.attr('end')) { + element.bidirectional = true; + bidirectional = true; + } + }); + } + if (!bidirectional) { + var beginlatlon = convert(begin.attr('lat') + " " + begin.attr('lon')); + var endlatlon = convert(end.attr('lat') + " " + end.attr('lon')); + const polyline = new L.TaxiwaySegment([[beginlatlon.decimalLatitude, beginlatlon.decimalLongitude], [endlatlon.decimalLatitude, endlatlon.decimalLongitude]], { radius: n.attr('radius') }).addTo(layerGroup); + polyline.begin = begin.attr('index'); + polyline.end = end.attr('index'); + // polyline.enableEdit(); + + polyline.on('dblclick', function (event) { L.DomEvent.stop; polyline.toggleEdit; }); + polyline.on('click', function (event) { + console.log(event); + }); + polyline.on('editable:drawing:move', function (event) { + console.log(event.target); + if (dragIndex >= 0) { + follow(dragIndex, event); + } + }); + var dragIndex = -1; + polyline.on('editable:vertex:dragstart', function (event) { + console.log(event.vertex); + console.log(event.target); + if (typeof featureLookup[event.target.begin] !== 'undefined') { + featureLookup[event.target.begin].forEach(element => { + if (element instanceof L.ParkingSpot) { + dragIndex = event.target.begin; + } + else if (element instanceof L.TaxiwaySegment) { + if (element.getLatLngs()[0].equals(event.vertex.getLatLng())) { + dragIndex = element.begin; + } + if (element.getLatLngs()[element.getLatLngs().length - 1].equals(event.vertex.getLatLng())) { + dragIndex = element.end; + } + } + }); + } + if (typeof featureLookup[event.target.end] !== 'undefined') { + featureLookup[event.target.end].forEach(element => { + if (element instanceof L.ParkingSpot) { + dragIndex = event.target.end; + } + }); + } + }); + polyline.on('editable:vertex:dragend', function (event) { + console.log(event.vertex); + if (dragIndex > 0) { + featureLookup[dragIndex].forEach(element => { + if (element instanceof L.ParkingSpot) { + //element.setLatLng(event); + console.log(element); + } + }); + } + dragIndex = -1; + }); + + + polyline.addTo(layerGroup); + if (typeof featureLookup[polyline.begin] === 'undefined') { + featureLookup[polyline.begin] = new Array(); + } + if (typeof featureLookup[polyline.end] === 'undefined') { + featureLookup[polyline.end] = new Array(); + } + featureLookup[polyline.begin].push(polyline); + featureLookup[polyline.end].push(polyline); + } + } + + }).sort(); + + + return layerGroup; + }); + // var jsonAirports = JSON.parse(geoJSON); + // return jsonAirports; + } catch (error) { + console.error(error); + } + return layerGroup; +} + +follow = function (dragIndex, event) { + featureLookup[dragIndex].forEach(element => { + if(element !== event.target){ + if (element instanceof L.ParkingSpot) { + element.disableEdit(); + element.setLatLng(event.latlng); + // element.enableEdit(); + element.updateVertexFromDirection(); + } + else if (element instanceof L.TaxiwaySegment) { + if (element.begin === dragIndex) { + element.getLatLngs()[0].update(event.latlng); + element.setLatLngs(element.getLatLngs()); + element.updateBeginVertex(event.latlng); + element.updateMiddle(); + } + if (element.end === dragIndex) { + element.getLatLngs()[element.getLatLngs().length - 1].update(event.latlng); + element.setLatLngs(element.getLatLngs()); + element.updateEndVertex(event.latlng); + element.updateMiddle(); + } + + } + } + }); + +} \ No newline at end of file diff --git a/src/renderer/loaders/markers.js b/src/renderer/loaders/markers.js new file mode 100644 index 0000000..9e6e1e9 --- /dev/null +++ b/src/renderer/loaders/markers.js @@ -0,0 +1,33 @@ +/* eslint-disable */ +var L = require('leaflet'); + +L.TaxiwaySegment = L.Polyline.extend({ + begin: String, + end: String, + bidirectional: Boolean, + + updateBeginVertex : function (latlng) { + this._latlngs[0].__vertex.setLatLng(latlng); + }, + updateEndVertex : function (latlng) { + this._latlngs[1].__vertex.setLatLng(latlng); + }, + + updateMiddle: function () { + this._latlngs.forEach(element => { + if(element.__vertex.middleMarker){ + element.__vertex.middleMarker.updateLatLng(); + } + }); + } +}); + + +L.MagneticVertex = L.Editable.VertexMarker.extend({ + updateMiddle: function () { + if (this.middleMarker) this.middleMarker.updateLatLng(); + var next = this.getNext(); + if (next && next.middleMarker) next.middleMarker.updateLatLng(); + } +}); + diff --git a/src/renderer/store/modules/Airports.js b/src/renderer/store/modules/Airports.js index 11c44d4..5385ac7 100644 --- a/src/renderer/store/modules/Airports.js +++ b/src/renderer/store/modules/Airports.js @@ -3,10 +3,11 @@ import idb from '../api/airports'; const ADD_AIRPORT = 'ADD_AIRPORT'; const SET_AIRPORTS = 'SET_AIRPORTS'; +const SET_UNFILTERED_AIRPORTS = 'SET_UNFILTERED_AIRPORTS'; const RESET_AIRPORTS = 'RESET_AIRPORTS'; const state = { - airports: [] + airports: [], unfilteredairports:[] } const mutations = { @@ -17,6 +18,9 @@ const mutations = { SET_AIRPORTS (state, airports) { state.airports = airports; }, + SET_UNFILTERED_AIRPORTS (state, airports) { + state.unfilteredairports = airports; + }, RESET_AIRPORTS (state) { state.airports = []; }, @@ -30,14 +34,17 @@ const actions = { async getAirports(context) { context.commit(RESET_AIRPORTS); let airports = await idb.getAirports(); -// let newAirports = []; -// airports.forEach(c => { -// newAirports.push(c); -// }); context.commit(SET_AIRPORTS, airports .filter(point => typeof point.geometry.coordinates !== "undefined" ) .filter(point => point.properties.flights > 0 )); }, + async getAirportsUnfiltered(context) { + context.commit(RESET_AIRPORTS); + let airports = await idb.getAirports(); + context.commit(SET_UNFILTERED_AIRPORTS, airports + .filter(point => typeof point.geometry.coordinates !== "undefined" ) + .filter(point => point.properties.flights > 0 )); + }, async saveAirport(context, airport) { await idb.saveAirport(airport); }} diff --git a/src/renderer/store/modules/Settings.js b/src/renderer/store/modules/Settings.js index f09b46a..b31e847 100644 --- a/src/renderer/store/modules/Settings.js +++ b/src/renderer/store/modules/Settings.js @@ -1,7 +1,8 @@ const state = { settings: { flightgearDirectory: '.' }, zoom: 14, - center: [47.413220, -1.219482] + center: [47.413220, -1.219482], + bounds: undefined } const mutations = { @@ -17,8 +18,10 @@ const mutations = { }, 'CENTER' (state, center) { state.center = center + }, + 'BOUNDS' (state, bounds) { + state.bounds = bounds } - } const plugins = [] diff --git a/src/renderer/utils/scan.js b/src/renderer/utils/scan.js index 79674a7..efc8d40 100644 --- a/src/renderer/utils/scan.js +++ b/src/renderer/utils/scan.js @@ -8,6 +8,12 @@ // const homedir = require('os').homedir(); // const apt = require('apt.js'); +/** + * Iterates over an array with a async function and await + * @param {*} array The array being iterated over + * @param {*} callback + */ + async function asyncForEach(array, callback) { console.log("AsyncForEach Len " + array.length); for (let index = 0; index < array.length; index++) { @@ -69,34 +75,66 @@ async function scanGroundnetFiles(p, features) { }); } +async function resetAI() { + var promise = new Promise(function (resolve, reject) { + try { + var objectStore = features.transaction("airports", 'readwrite').objectStore("airports"); + + objectStore.openCursor().onsuccess = function (event) { + var cursor = event.target.result; + if (cursor) { + cursor.value.properties.flights = 0 + cursor.value.properties.airlines = []; + console.log("Name for SSN " + cursor.key + " is " + cursor.value.properties.name); + cursor.update(cursor.value); + cursor.continue(); + } + else { + console.log("No more entries!"); + resolve(); + } + }; + } catch (error) { + console.log(error); + reject(error); + } + }); + return promise; +} + async function scanTrafficFiles(p, features) { var promise = new Promise(function (resolve, reject) { try { - console.log('Start Groundnets ' + p); - var files = traverseDir(p); - console.log(files); + console.log('Start Traffic ' + p); + + resetAI().then(f => { + var files = traverseDir(p); + console.log(files); + + asyncForEach(files, async f => { + //await waitFor(5000); + try { + var text = await readAI(f, features); + console.log(text); + resolve(text); + } catch (error) { + console.error(error); + reject(error); + } + }).then(t => { + console.log("Finished"); + features.close(); + resolve(); + this.postMessage('DONE'); + }).catch(reason => { + console.log("Crashed"); + console.log(reason); + features.close() + this.postMessage('DONE'); + }); + } + ); - asyncForEach(files, async f => { - //await waitFor(5000); - try { - var text = await readAI(f, features); - console.log(text); - resolve(text); - } catch (error) { - console.error(error); - reject(error); - } - }).then(t => { - console.log("Finished"); - features.close(); - resolve(); - this.postMessage('DONE'); - }).catch(reason => { - console.log("Crashed"); - console.log(reason); - features.close() - this.postMessage('DONE'); - }); //walkDir(p, f => { readGroundnet(f, features) }); @@ -131,7 +169,7 @@ function scanTrafficIntoDB(p, features) { } }; - walkDir(p, f => { readAI(f, features) }); + //walkDir(p, f => { readAI(f, features) }); console.log("Closing"); features.close(); this.postMessage('DONE'); @@ -182,7 +220,7 @@ function readAI(f, apts) { console.log(path.basename(f)); // Reset var airline = path.basename(f).match('([^.]+)\.xml'); - if (airline == null){ + if (airline == null) { resolve(); return; } @@ -195,7 +233,7 @@ function readAI(f, apts) { // console.log(flights); var dat = tXml(xmlSource); // console.log(dat); - if( !dat || !dat[0] || !dat[0].children || !dat[0].children[0] ) { + if (!dat || !dat[0] || !dat[0].children || !dat[0].children[0]) { resolve(); return; } @@ -224,10 +262,10 @@ function readAI(f, apts) { for (var i = 0; i < merged.length; i++) { counts[merged[i]] = 1 + (counts[merged[i]] || 0); } - + asyncForEach(Object.keys(counts), async key => { console.log(key); - await store(key, airline[0], counts[key]); + await store(key, airline[1], counts[key]); }).then(t => { console.log("Finished"); resolve(); @@ -277,8 +315,10 @@ function store(icao, airline, value) { } feature.properties.flights += value; console.log("Airline " + airline); - feature.properties.airlines.push(airline); - feature.properties.airlines.sort(); + if (!feature.properties.airlines.includes(airline)) { + feature.properties.airlines.push(airline); + feature.properties.airlines.sort(); + } console.log("ICAO : " + feature.properties.icao + " Flights : " + feature.properties.flights); // Create another request that inserts the item back into the database var updateAirportRequest = objectStore.put(feature);