From 7e5ff2dee717be6eddcfa05b1b60e38025b4b441 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Sun, 8 Aug 2021 20:05:36 +1000 Subject: [PATCH] MVP-ish --- TODO.md | 7 +- block.html | 47 ++++ client.js | 173 +++++++------ public/aircraft.png | Bin 6543 -> 0 bytes public/flaticon.com/ga-dark.png | Bin 0 -> 13022 bytes public/flaticon.com/ga-light.png | Bin 0 -> 5481 bytes public/fontawesome/jet-dark.png | Bin 0 -> 10408 bytes public/fontawesome/jet-light.png | Bin 0 -> 3106 bytes public/leader.png | Bin 1561 -> 0 bytes public/map.html | 425 ++++++++++++++++++++++--------- public/output.png | Bin 638 -> 0 bytes 11 files changed, 453 insertions(+), 199 deletions(-) create mode 100644 block.html delete mode 100644 public/aircraft.png create mode 100644 public/flaticon.com/ga-dark.png create mode 100644 public/flaticon.com/ga-light.png create mode 100644 public/fontawesome/jet-dark.png create mode 100644 public/fontawesome/jet-light.png delete mode 100644 public/leader.png delete mode 100644 public/output.png diff --git a/TODO.md b/TODO.md index f68d491..89748b2 100644 --- a/TODO.md +++ b/TODO.md @@ -40,6 +40,9 @@ Traffic heatmap (DB needed) CC BY-SA 3.0 https://commons.wikimedia.org/wiki/File:Plane_font_awesome.svg +public/flaticon.com/ga-*.png +
Icons made by Freepik from www.flaticon.com
+ ## Theme Green 33cc99 A10 @@ -54,4 +57,6 @@ Local search https://docs.mapbox.com/mapbox-gl-js/example/forward-geocode-custom-data/ Progressive taxi? / taxi markup for pilot assist -https://docs.mapbox.com/mapbox-gl-js/example/measure/ \ No newline at end of file +https://docs.mapbox.com/mapbox-gl-js/example/measure/ + +https://docs.mapbox.com/mapbox-gl-js/example/set-popup/ \ No newline at end of file diff --git a/block.html b/block.html new file mode 100644 index 0000000..06d636b --- /dev/null +++ b/block.html @@ -0,0 +1,47 @@ +

Traffic in VATPAC airspace

+
+ \ No newline at end of file diff --git a/client.js b/client.js index ee16bbd..43ebc14 100644 --- a/client.js +++ b/client.js @@ -3,7 +3,9 @@ import fetch from 'node-fetch'; import { xml2js } from 'xml-js'; import dms2dec from 'dms2dec'; import { lineToPolygon, lineString } from '@turf/turf'; +import bunyan from 'bunyan'; +var log = bunyan.createLogger({name: "vatsim-map"}) const cache = new NodeCache( { stdTTL: 15, checkperiod: 30 } ); /** @@ -14,7 +16,7 @@ const cache = new NodeCache( { stdTTL: 15, checkperiod: 30 } ); */ export function clearCache(){ - console.log('Clearing cache') + log.info('Clearing cache') cache.flushAll(); return true; } @@ -36,12 +38,20 @@ export async function getVatsimData (url) { .then( data => { return data; }) - .catch(err => console.log(err)); + .catch(err => log.error(err)); data = res; - console.log(`cache:set url:${url} keys:${Object.keys(data).length}`) + log.info({ + cache: 'set', + url: url, + keys: Object.keys(data).length + }) cache.set("vatsimData", data, 15); }else{ - console.log(`cache:get url:${url} keys:${Object.keys(data).length}`) + log.info({ + cache: 'get', + url: url, + keys: Object.keys(data).length + }) } return data; } @@ -55,12 +65,20 @@ export async function getVatsimAFV (url) { .then( data => { return data; }) - .catch(err => console.log(err)); + .catch(err => log.error(err)); data = res; - console.log(`cache:set url:${url} keys:${Object.keys(data).length}`) + log.info({ + cache: 'set', + url: url, + keys: Object.keys(data).length + }); cache.set("vatsimAFV", data, 15); }else{ - console.log(`cache:get url:${url} keys:${Object.keys(data).length}`) + log.info({ + cache: 'get', + url: url, + keys: Object.keys(data).length + }) } return data; } @@ -82,20 +100,27 @@ export async function getLineFeatures (url) { .then( data => { return data; }) - .catch(err => console.log(err)); + .catch(err => log.error(err)); data = await xml2js(res, {compact: true, spaces: 4}); }catch(err){ throw Error(err); } var features = xmlToFeatures(data); - console.log(features); - console.log(`cache:set url:${url} objs:${features.length}`) + log.info({ + cache: 'set', + url: url, + objs: data.length + }) cache.set(url, features, 86400); return features; }else{ - console.log(`cache:get url:${url} objs:${data.length}`) + log.info({ + cache: 'get', + url: url, + objs: data.length + }) return data; } } @@ -107,70 +132,72 @@ function xmlToFeatures (data) { // (?[+-][0-9]{2})(?[0-9]{2})(?[0-9]{2}\.[0-9]{3})(?[+-][0-9]{3})(?[0-9]{2})(?[0-9]{2}\.[0-9]{3}) const re = new RegExp(/(?[+-])(?[0-9]{2})(?[0-9]{2})(?[0-9]{2}\.[0-9]{3})(?[+-])(?[0-9]{3})(?[0-9]{2})(?[0-9]{2}\.[0-9]{3})/g) - // Handle Maps - FIR Boundaries - try{ - data.Maps.Map.Line.forEach(function(obj){ - var lineStringArr = []; - var matches = [...obj._text.matchAll(re)]; - matches.forEach(function(match){ - // Convert vaySys DMS into decimal degrees - // https://github.com/vatSys/xml-tools/blob/master/DotAIPtoXML/DotAIPtoXML/Coordinate.cs#L119 - var pos = { - latitude: [ - parseInt(match.groups.latD), - parseInt(match.groups.latM), - parseFloat(match.groups.latS) - ], - latRef: (match.groups.latRef == '+') ? "N" : "S", - longitude: [ - parseInt(match.groups.lonD), - parseInt(match.groups.lonM), - parseFloat(match.groups.lonS) - ], - lonRef: (match.groups.lonRef == '+') ? "E" : "W" - } - var [ latitude, longitude ] = dms2dec(pos.latitude,pos.latRef,pos.longitude,pos.lonRef); - // Turf is geoJSON so we continue the stupidity here with long THEN lat. - lineStringArr.push([longitude, latitude]); - }); - polys.push(lineToPolygon(lineString(lineStringArr),{mutate: true, properties: {name: obj._attributes.Name}})); - }) - }catch(err){ - console.log(err); + if(data.Maps.Map.Line == undefined){ + // Handle volumes + try{ + data.Volumes.Boundary.forEach(function(obj){ + var lineStringArr = []; + var matches = [...obj._text.matchAll(re)]; + matches.forEach(function(match){ + // Convert vaySys DMS into decimal degrees + // https://github.com/vatSys/xml-tools/blob/master/DotAIPtoXML/DotAIPtoXML/Coordinate.cs#L119 + var pos = { + latitude: [ + parseInt(match.groups.latD), + parseInt(match.groups.latM), + parseFloat(match.groups.latS) + ], + latRef: (match.groups.latRef == '+') ? "N" : "S", + longitude: [ + parseInt(match.groups.lonD), + parseInt(match.groups.lonM), + parseFloat(match.groups.lonS) + ], + lonRef: (match.groups.lonRef == '+') ? "E" : "W" + } + var [ latitude, longitude ] = dms2dec(pos.latitude,pos.latRef,pos.longitude,pos.lonRef); + // Turf is geoJSON so we continue the stupidity here with long THEN lat. + lineStringArr.push([longitude, latitude]); + }); + polys.push(lineToPolygon(lineString(lineStringArr),{mutate: true, properties: {name: obj._attributes.Name}})); + }) + }catch(err){ + log.error(err); + } + }else{ + // Handle Maps - FIR Boundaries + try{ + data.Maps.Map.Line.forEach(function(obj){ + var lineStringArr = []; + var matches = [...obj._text.matchAll(re)]; + matches.forEach(function(match){ + // Convert vaySys DMS into decimal degrees + // https://github.com/vatSys/xml-tools/blob/master/DotAIPtoXML/DotAIPtoXML/Coordinate.cs#L119 + var pos = { + latitude: [ + parseInt(match.groups.latD), + parseInt(match.groups.latM), + parseFloat(match.groups.latS) + ], + latRef: (match.groups.latRef == '+') ? "N" : "S", + longitude: [ + parseInt(match.groups.lonD), + parseInt(match.groups.lonM), + parseFloat(match.groups.lonS) + ], + lonRef: (match.groups.lonRef == '+') ? "E" : "W" + } + var [ latitude, longitude ] = dms2dec(pos.latitude,pos.latRef,pos.longitude,pos.lonRef); + // Turf is geoJSON so we continue the stupidity here with long THEN lat. + lineStringArr.push([longitude, latitude]); + }); + polys.push(lineToPolygon(lineString(lineStringArr),{mutate: true, properties: {name: obj._attributes.Name}})); + }) + }catch(err){ + log.error(err); + } } - // Handle volumes - try{ - data.Volumes.Boundary.forEach(function(obj){ - var lineStringArr = []; - var matches = [...obj._text.matchAll(re)]; - matches.forEach(function(match){ - // Convert vaySys DMS into decimal degrees - // https://github.com/vatSys/xml-tools/blob/master/DotAIPtoXML/DotAIPtoXML/Coordinate.cs#L119 - var pos = { - latitude: [ - parseInt(match.groups.latD), - parseInt(match.groups.latM), - parseFloat(match.groups.latS) - ], - latRef: (match.groups.latRef == '+') ? "N" : "S", - longitude: [ - parseInt(match.groups.lonD), - parseInt(match.groups.lonM), - parseFloat(match.groups.lonS) - ], - lonRef: (match.groups.lonRef == '+') ? "E" : "W" - } - var [ latitude, longitude ] = dms2dec(pos.latitude,pos.latRef,pos.longitude,pos.lonRef); - // Turf is geoJSON so we continue the stupidity here with long THEN lat. - lineStringArr.push([longitude, latitude]); - }); - polys.push(lineToPolygon(lineString(lineStringArr),{mutate: true, properties: {name: obj._attributes.Name}})); - }) - }catch(err){ - console.log(err); - } - return polys }; diff --git a/public/aircraft.png b/public/aircraft.png deleted file mode 100644 index e67f3b8b93596a3b1f590e109d8b8439fb873dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6543 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 diff --git a/public/flaticon.com/ga-dark.png b/public/flaticon.com/ga-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..41059ac4487875859046bf2a2e02e6aa1702d4e8 GIT binary patch literal 13022 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 literal 0 HcmV?d00001 diff --git a/public/flaticon.com/ga-light.png b/public/flaticon.com/ga-light.png new file mode 100644 index 0000000000000000000000000000000000000000..b7cb6e3acc30cfaad57254c171bbed04585b5e7d GIT binary patch literal 5481 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 + @@ -62,145 +58,323 @@ if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]); }); return result; - } + } var theme = findGetParameter('theme'); if(theme == 'dark'){ map.setStyle(styleDark); } - //options.localGeocoder Function? A function accepting the query string which performs local geocoding to supplement results from the Mapbox Geocoding API. Expected to return an Array of GeoJSON Features in the Carmen GeoJSON format. - function forwardGeocoder(query) { - // var response = await fetch(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/pilots`); - // var json = await response.json(); - var xmlhttp = new XMLHttpRequest(); - xmlhttp.onreadystatechange = function() { - if (this.readyState == 4 && this.status == 200) { - var json = JSON.parse(this.responseText); - // console.log(json.features.length) - var matchingFeatures = []; - for (var i = 0; i < json.features.length; i++) { - var feature = json.features[i]; - // Handle queries with different capitalization - // than the source data by calling toLowerCase(). - if ( - feature.properties.pilot.callsign - .toLowerCase() - .search(query.toLowerCase()) !== -1 - ) { - // Add a tree emoji as a prefix for custom - // data results using carmen geojson format: - // https://github.com/mapbox/carmen/blob/master/carmen-geojson.md - feature['place_name'] = '🌲 ' + feature.properties.pilot.callsign; - feature['center'] = feature.geometry.coordinates; - matchingFeatures.push(feature); + // //options.localGeocoder Function? A function accepting the query string which performs local geocoding to supplement results from the Mapbox Geocoding API. Expected to return an Array of GeoJSON Features in the Carmen GeoJSON format. + // function forwardGeocoder(query) { + // // var response = await fetch(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/pilots`); + // // var json = await response.json(); + // var xmlhttp = new XMLHttpRequest(); + // xmlhttp.onreadystatechange = function() { + // if (this.readyState == 4 && this.status == 200) { + // var json = JSON.parse(this.responseText); + // // console.log(json.features.length) + // var matchingFeatures = []; + // for (var i = 0; i < json.features.length; i++) { + // var feature = json.features[i]; + // // Handle queries with different capitalization + // // than the source data by calling toLowerCase(). + // if ( + // feature.properties.pilot.callsign + // .toLowerCase() + // .search(query.toLowerCase()) !== -1 + // ) { + // // Add a tree emoji as a prefix for custom + // // data results using carmen geojson format: + // // https://github.com/mapbox/carmen/blob/master/carmen-geojson.md + // feature['place_name'] = '🌲 ' + feature.properties.pilot.callsign; + // feature['center'] = feature.geometry.coordinates; + // matchingFeatures.push(feature); + // } + // } + // console.log(matchingFeatures) + // return matchingFeatures; + // } + // }; + // xmlhttp.open("GET", `${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/pilots`, false); + // xmlhttp.send(); + // } + + // // Add the search control to the map. + // map.addControl( + // new MapboxGeocoder({ + // accessToken: mapboxgl.accessToken, + // mapboxgl: mapboxgl, + // localGeocoder: forwardGeocoder, + // localGeocoderOnly: true, + // zoom: 14, + // placeholder: 'Find aircraft' + // }) + // ); + + + // async function getATCSectors() { + // try{ + // var response = await fetch(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/atc/sectors`); + // var json = await response.json(); + // map.addSource('sectors', { + // 'type': 'geojson', + // 'data': json + // }); + // // Add a new layer to visualize the polygon. + // map.addLayer({ + // 'id': 'sectors', + // 'type': 'fill', + // 'source': 'sectors', // reference the data source + // 'layout': {}, + // 'paint': { + // 'fill-color': '#0080ff', // blue color fill + // 'fill-opacity': 0.1 + // } + // }); + // // Add a black outline around the polygon. + // map.addLayer({ + // 'id': 'outline', + // 'type': 'line', + // 'source': 'sectors', + // 'layout': {}, + // 'paint': { + // 'line-color': '#000', + // 'line-width': 1 + // } + // }); + // // // Add sector labels + // // json.features.forEach(function(sector){ + // // console.log(sector) + // // var marker = new mapboxgl.Marker() + // // .setLngLat(turf.center({geojson: sector})) + // // .addTo(map); + // // }); + + // }catch(err){ + // throw Error(err); + // } + // }; + + // var refresh = setInterval(setPilotMarkers, 1000); + // var drawn = false; + + async function setPilotMarkers () { + console.log(`setPilotMarkers`); + var response = await fetch(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/pilots`); + var json = await response.json(); + try{ + // Marker approach + for (const marker of json.features) { + console.log(marker); + // Create popup for each Marker + const popup = new mapboxgl.Popup({ offset: 25 }).setText( + `Callsign: ${marker.properties.pilot.callsign} + Pilot: ${marker.properties.pilot.name}` + ); + // Regex callsigns + // Anything using a VH callsign or three leters get GA + const re = new RegExp(/^VH-[A-Z]{3}$|^VH[A-Z]{3}$|^[A-Z]{3}$/); + const gaIcon = re.test(marker.properties.pilot.callsign); + console.log(`gaIcon re ${gaIcon}`); + console.log(marker.properties.pilot.callsign) + // Create a DOM element for each marker. + const el = document.createElement('div'); + // const width = 20; + // const height = 20; + el.className = 'marker'; + if(gaIcon == true){ + el.style.width = `17px`; + el.style.height = `17px`; + if(theme=="dark"){ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/flaticon.com/ga-dark.png)`; + }else{ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/flaticon.com/ga-light.png)`; + } + }else{ + el.style.width = `20px`; + el.style.height = `20px`; + if(theme=="dark"){ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/fontawesome/jet-dark.png)`; + }else{ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/fontawesome/jet-light.png)`; } } - console.log(matchingFeatures) - return matchingFeatures; - } - }; - xmlhttp.open("GET", `${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/pilots`, false); - xmlhttp.send(); - } - // Add the search control to the map. - map.addControl( - new MapboxGeocoder({ - accessToken: mapboxgl.accessToken, - mapboxgl: mapboxgl, - localGeocoder: forwardGeocoder, - localGeocoderOnly: true, - zoom: 14, - placeholder: 'Find aircraft' - }) - ); + el.style.backgroundSize = '100%'; + el.addEventListener('click', () => { + window.alert(marker.properties.message); + }); + + // Add markers to the map. + new mapboxgl.Marker(el) + .setLngLat(marker.geometry.coordinates) + .setPopup(popup) + // Icon sets are rotated 45 + .setRotation(marker.properties.pilot.heading - 45) + .addTo(map); + // Layer approach - async function getATCSectors() { - try{ - var response = await fetch(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/atc/sectors`); - var json = await response.json(); - map.addSource('sectors', { + // Add a symbol layer + if(theme=="dark"){ + var mapLayer = map.getLayer('aircraft'); + + if(typeof mapLayer !== 'undefined') { + // Remove map layer & source. + map.removeLayer('aircraft').removeSource('aircraft'); + } + + map.addSource('aircraft', { 'type': 'geojson', + 'attribution': 'vatsim-map', 'data': json }); - // Add a new layer to visualize the polygon. - map.addLayer({ - 'id': 'sectors', - 'type': 'fill', - 'source': 'sectors', // reference the data source - 'layout': {}, - 'paint': { - 'fill-color': '#0080ff', // blue color fill - 'fill-opacity': 0.1 - } - }); - // Add a black outline around the polygon. - map.addLayer({ - 'id': 'outline', - 'type': 'line', - 'source': 'sectors', - 'layout': {}, - 'paint': { - 'line-color': '#000', - 'line-width': 1 + map.addLayer({ + 'id': 'aircraft', + 'type': 'symbol', + 'source': 'aircraft', + 'layout': { + // 'icon-image': 'custom-marker', + // 'icon-size': 0.08, + // 'icon-rotate': [ 'get', 'heading', ['object', ['get', 'pilot']]], + // 'icon-allow-overlap': true, + // 'icon-ignore-placement': true, + 'text-field': ['format', ['get', 'callsign', ['object', ['get', 'pilot']]], { 'text-color': '#FFF'}], + 'text-font': [ + 'Open Sans Semibold', + 'Arial Unicode MS Bold' + ], + 'text-offset': [1, 1], + // 'text-anchor': 'bottom', + 'text-variable-anchor': ["top", "bottom", "left"], + 'text-allow-overlap': true, + 'text-ignore-placement': true + } + }); + }else{ + var mapLayer = map.getLayer('aircraft'); + + if(typeof mapLayer !== 'undefined') { + // Remove map layer & source. + map.removeLayer('aircraft').removeSource('aircraft'); + } + + map.addSource('aircraft', { + 'type': 'geojson', + 'attribution': 'vatsim-map', + 'data': json + }); + + map.addLayer({ + 'id': 'aircraft', + 'type': 'symbol', + 'source': 'aircraft', + 'layout': { + // 'icon-image': 'custom-marker', + // 'icon-size': 0.08, + // 'icon-rotate': [ 'get', 'heading', ['object', ['get', 'pilot']]], + // 'icon-allow-overlap': true, + // 'icon-ignore-placement': true, + 'text-field': ['format', ['get', 'callsign', ['object', ['get', 'pilot']]], { 'text-color': '#000'}], + 'text-font': [ + 'Open Sans Semibold', + 'Arial Unicode MS Bold' + ], + 'text-offset': [1, 1], + // 'text-anchor': 'bottom', + 'text-variable-anchor': ["top", "bottom", "left"], + 'text-allow-overlap': true, + 'text-ignore-placement': true + } + }); + } + } - }); - // // Add sector labels - // json.features.forEach(function(sector){ - // console.log(sector) - // var marker = new mapboxgl.Marker() - // .setLngLat(turf.center({geojson: sector})) - // .addTo(map); - // }); - }catch(err){ - throw Error(err); + console.log(err) } }; - var refresh = setInterval(setPilotsLayer, 15000); - var drawn = false; async function setPilotsLayer () { var response = await fetch(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/v1/pilots`); var json = await response.json(); if(drawn == false){ try{ - map.addSource('aircraft', { - 'type': 'geojson', - 'attribution': 'vatsim-map', - 'data': json - }); - // Add a symbol layer - map.addLayer({ - 'id': 'aircraft', - 'type': 'symbol', - 'source': 'aircraft', - 'layout': { - 'icon-image': 'custom-marker', - 'icon-size': 0.08, - 'icon-rotate': [ 'get', 'heading', ['object', ['get', 'pilot']]], - 'icon-allow-overlap': true, - 'icon-ignore-placement': true, - 'text-field': ['format', ['get', 'callsign', ['object', ['get', 'pilot']]], { 'text-color': '#000000'}], - 'text-font': [ - 'Open Sans Semibold', - 'Arial Unicode MS Bold' - ], - 'text-offset': [1, 1], - // 'text-anchor': 'bottom', - 'text-variable-anchor': ["top", "bottom", "left"], - 'text-allow-overlap': true, - 'text-ignore-placement': true + // Layer approach + // map.addSource('aircraft', { + // 'type': 'geojson', + // 'attribution': 'vatsim-map', + // 'data': json + // }); + // // Add a symbol layer + // map.addLayer({ + // 'id': 'aircraft', + // 'type': 'symbol', + // 'source': 'aircraft', + // 'layout': { + // 'icon-image': 'custom-marker', + // 'icon-size': 0.08, + // 'icon-rotate': [ 'get', 'heading', ['object', ['get', 'pilot']]], + // 'icon-allow-overlap': true, + // 'icon-ignore-placement': true, + // 'text-field': ['format', ['get', 'callsign', ['object', ['get', 'pilot']]], { 'text-color': '#000000'}], + // 'text-font': [ + // 'Open Sans Semibold', + // 'Arial Unicode MS Bold' + // ], + // 'text-offset': [1, 1], + // // 'text-anchor': 'bottom', + // 'text-variable-anchor': ["top", "bottom", "left"], + // 'text-allow-overlap': true, + // 'text-ignore-placement': true + // } + // }); + + // Marker approach + for (const marker of json.features) { + // Regex callsigns + // Anything using a VH callsign or three leters get GA + const re = new RegExp('VH-[A-Z]{3}|VH[A-Z]{3}|[A-Z]{3}') + const gaIcon = re.test(marker.callsign); + console.log(`gaIcon re ${gaIcon}`); + // Create a DOM element for each marker. + const el = document.createElement('div'); + const width = 20; + const height = 20; + el.className = 'marker'; + if(gaIcon){ + if(theme=="dark"){ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/flaticon.com/ga-dark.png)`; + }else{ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/flaticon.com/ga-light.png)`; + } + }else{ + if(theme=="dark"){ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/fontawesome/jet-dark.png)`; + }else{ + el.style.backgroundImage = `url(${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/fontawesome/jet-light.png)`; + } + } + el.style.width = `${width}px`; + el.style.height = `${height}px`; + el.style.backgroundSize = '100%'; + + el.addEventListener('click', () => { + window.alert(marker.properties.message); + }); + + // Add markers to the map. + new mapboxgl.Marker(el) + .setLngLat(marker.geometry.coordinates) + .addTo(map); } - }); drawn = true; }catch(err){ console.log(err) } }else{ try{ - map.getSource('aircraft').setData(json); + // map.getSource('aircraft').setData(json); }catch(err){ console.log(err) } @@ -208,17 +382,18 @@ }; map.on('load', function () { + console.log(`map.on load`); // Add an image to use as a custom marker - map.loadImage( - `${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/aircraft.png`, - function (error, image) { - if (error) throw error; - map.addImage('custom-marker', image); - } - ); + // map.loadImage( + // `${window.location.protocol}//${window.location.hostname}:${window.location.port}/static/fontawesome/jet-light.png`, + // function (error, image) { + // if (error) throw error; + // map.addImage('custom-marker', image); + // } + // ); - getATCSectors(); - setPilotsLayer(); + // getATCSectors(); + setPilotMarkers(); }); diff --git a/public/output.png b/public/output.png deleted file mode 100644 index 74fa8f0ed794db64b46395c6efdf024bcb742a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 638 zcmezW?=?dJgCB!C11|#?gA|x#00R);lfi?*g&_zcz>uf}VI(SXFt9=;5BW-uRv z@|b`+To@b~LKrF-7#ZZ0q!{WLQdJ?6DM~Q8W6UP1j6it^c**dPVS(yyhBBaCB2cwZ wsZnZ(g+QpP2*U#gS)i*K8I~(7W-ev0gZsdf!4;U!To_g}I58wJyacB-03<~jfdBvi