mapboxgl.accessToken = config.mapboxKey; const owmKey = config.owmKey var map = new mapboxgl.Map({ container: 'map', style: { "glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf", "light": { "anchor": "viewport", "color": "white", "intensity": 0.1 }, 'version': 8, 'sources': { 'default-background': { 'type': 'raster', 'tiles': [ //'https://map1.vis.earthdata.nasa.gov/wmts-webmerc/VIIRS_CityLights_2012/default//GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpg' //'https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}' //'https://stamen-tiles-b.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg' 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' ], 'tileSize': 256 } }, 'layers': [{ id: 'background', type: 'background', paint: { 'background-color': '#ffffff' } }, { 'id': 'default-background', 'type': 'raster', 'source': 'default-background', 'minzoom': 0, 'maxzoom': 18 } ] }, center: [0, 20], zoom: 2, attributionControl: false }); //prevent world duplication map.setRenderWorldCopies(status === 'false') map.on('load', function () { //displaying clouds from OpenWeatherMap free API not enough // map.addSource('clouds', { // 'type': 'raster', // 'tiles': [ // 'https://tile.openweathermap.org/map/clouds_new/{z}/{x}/{y}.png?appid='+owmKey+'' // ], // 'tileSize': 256 // }); // map.addLayer({ // 'id': 'clouds', // 'type': 'raster', // 'source': 'clouds', // 'minzoom': 0, // 'maxzoom': 10, // 'paint':{ // 'raster-opacity':0.5 // } // }); //loading icons and colors, too lazy to make a loop, to change. map.loadImage('src/img/plane-FFE419.png', function (error, image) { if (error) throw error; map.addImage('plane-FFE419', image); }) map.loadImage('src/img/plane-66FF00.png', function (error, image) { if (error) throw error; map.addImage('plane-66FF00', image); }) map.loadImage('src/img/plane-FF5555.png', function (error, image) { if (error) throw error; map.addImage('plane-FF5555', image); }) var colors = ["FFE419","66FF00","FF5555"] //get now in GMT+0 var nowGMTSeconds = moment.tz(new Date(), "Europe/London").unix() //get the planes from firestore db.collection("planes").where("deliverySecondsServer", ">", nowGMTSeconds).get().then((querySnapshot) => { //change query to get only the ones who are still flying querySnapshot.forEach((doc) => { var id = doc.id var data = doc.data() //creating arcs var line = turf.lineString([data.startCoordo,data.destCoordo]) var lineDistance = turf.length(line); var arc = []; var steps = 500; for (var i = 0; i < lineDistance; i += lineDistance / steps) { var segment = turf.along(line, i); arc.push(segment.geometry.coordinates); } line.geometry.coordinates = arc; //random color var randomColor = colors[Math.floor(Math.random() * colors.length)]; map.addSource('route-'+id+'', { 'type': 'geojson', 'data': line }); map.addLayer({ 'id': 'route-'+id+'', 'source': 'route-'+id+'', 'type': 'line', 'paint': { 'line-width': 2, 'line-color': '#'+randomColor } }); map.setLayoutProperty('route-'+id+'', 'visibility', 'none'); //creating and animating planes // calculating area already travelled var dateStart = moment(data.sentDate[0]) var dateEnd = moment(data.deliveryDate[0]) var dateNow = moment(new Date()) var totalSeconds = dateEnd.diff(dateStart,'seconds'); var travelledSeconds = dateNow.diff(dateStart,'seconds') var travelRatio = travelledSeconds/totalSeconds var lineDistance = turf.length(line); var currentPosition = turf.along(line, lineDistance*travelRatio,{units: 'kilometers'}) var plane = turf.point(currentPosition.geometry.coordinates,data) //calculating bearing based on two points, one just before and one just after var positionBefore = turf.along(line, (lineDistance*travelRatio)-0.00001,{units: 'kilometers'}) var positionAfter = turf.along(line, (lineDistance*travelRatio)+0.00001,{units: 'kilometers'}) plane.properties.bearing = turf.bearing( positionBefore, positionAfter ); map.addSource('plane-'+id+'', { 'type': 'geojson', 'data': plane }); map.addLayer({ 'id': 'plane-'+id+'', 'source': 'plane-'+id+'', 'type': 'symbol', 'layout': { 'icon-image': 'plane-'+randomColor+'', 'icon-rotate': ['get', 'bearing'], 'icon-size':0.05, 'icon-rotation-alignment': 'map', 'icon-allow-overlap': true, 'icon-ignore-placement': true } }); //animating function animate(){ var dateStart = moment(data.sentDate[0]) var dateEnd = moment(data.deliveryDate[0]) var dateNow = moment(new Date()) var totalSeconds = dateEnd.diff(dateStart,'seconds'); var travelledSeconds = dateNow.diff(dateStart,'seconds') var travelRatio = travelledSeconds/totalSeconds var lineDistance = turf.length(line); var currentPosition = turf.along(line, lineDistance*travelRatio,{units: 'kilometers'}) var plane = turf.point(currentPosition.geometry.coordinates,data) //calculating bearing based on two points, one just before and one just after var positionBefore = turf.along(line, (lineDistance*travelRatio)-0.00001,{units: 'kilometers'}) var positionAfter = turf.along(line, (lineDistance*travelRatio)+0.00001,{units: 'kilometers'}) plane.properties.bearing = turf.bearing( positionBefore, positionAfter ); map.getSource('plane-'+id+'').setData(plane); var nowGMTSeconds = moment.tz(new Date(), "Europe/London").unix() //removing the plane from map if arrived if (data.deliverySecondsServer < nowGMTSeconds){ map.setLayoutProperty('plane-'+id+'', 'visibility', 'none'); } requestAnimationFrame(animate) } // uncomment to animate below, still some clipping issues //animate() //on click on a plane: get popup + display route map.on('click', 'plane-'+id+'', function (e) { var coordinates = e.features[0].geometry.coordinates.slice(); var prop = e.features[0].properties; var sentDate = JSON.parse(prop.sentDate)[0] var deliveryDate = JSON.parse(prop.deliveryDate)[0] while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; } //displying route map.setLayoutProperty('route-'+id+'', 'visibility', 'visible'); var popup = new mapboxgl.Popup() .setLngLat(coordinates) .setHTML("Origine: "+prop.startName+"
"+sentDate+"
Destination: "+prop.destName+"
"+deliveryDate+"") .addTo(map); //removing route popup.on('close', function(){ map.setLayoutProperty('route-'+id+'', 'visibility', 'none'); }); }); // Change the cursor to a pointer when the mouse is over the places layer. map.on('mouseenter', 'plane-'+id+'', function () { map.getCanvas().style.cursor = 'pointer'; }); // Change it back to a pointer when it leaves. map.on('mouseleave', 'plane-'+id+'', function () { map.getCanvas().style.cursor = ''; }); //adapt icon size to zoom level map.on('zoom', function() { map.setLayoutProperty('plane-'+id+'', 'icon-size', (map.getZoom())*0.025); }); //URL Parameter : zooming to ID and opening popup when url in format avion-poe.me?plane=id var queryString = window.location.search; queryString = queryString.substring(7) if(id == queryString){ map.setLayoutProperty('route-'+id+'', 'visibility', 'visible'); var bboxTravel = turf.bbox(line); var bounds = [[bboxTravel[0],bboxTravel[1]],[bboxTravel[2],bboxTravel[3]]] map.fitBounds(bounds, { padding: 65, speed: 0.5, // make the flying slow curve: 0.8, // change the speed at which it zooms out // This can be any easing function: it takes a number between // 0 and 1 and returns another number between 0 and 1. easing: function( t) { return t; }, }); var sentDate = plane.properties.sentDate[0] var deliveryDate = plane.properties.deliveryDate[0] var popup = new mapboxgl.Popup() .setLngLat(currentPosition.geometry.coordinates) .setHTML("Origine: "+plane.properties.startName+"
"+sentDate+"
Destination: "+plane.properties.destName+"
"+deliveryDate+"") .addTo(map); //removing route popup.on('close', function(){ map.setLayoutProperty('route-'+id+'', 'visibility', 'none'); }); } }); }); })