From 46d23393e3e490957d9a0e1c301eb2f4bcbed92a Mon Sep 17 00:00:00 2001 From: leomartine Date: Fri, 19 Mar 2021 14:01:44 +0000 Subject: [PATCH] js first commit --- src/app.css | 73 +++++++++++++ src/config.js | 4 + src/geocoder.js | 67 ++++++++++++ src/map.js | 266 ++++++++++++++++++++++++++++++++++++++++++++++++ src/newPlane.js | 111 ++++++++++++++++++++ 5 files changed, 521 insertions(+) create mode 100644 src/app.css create mode 100644 src/config.js create mode 100644 src/geocoder.js create mode 100644 src/map.js create mode 100644 src/newPlane.js diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..f2ad8c5 --- /dev/null +++ b/src/app.css @@ -0,0 +1,73 @@ +body { + margin: 0; + padding: 0; + font-family: 'PT Mono', monospace; + /* font-family: 'Special Elite', cursive !important */ +} + +#map { + position: absolute; + top: 0; + bottom: 0; + width: 100%; +} + +.footer { + position: absolute; + bottom: 0; + width: 100%; + /* Set the fixed height of the footer here */ + height: 30px; + line-height: 30px; /* Vertically center the text there */ + background-color: #f5f5f5; + font-size: 12px +} + +/* removing footer on mobile */ +@media screen and (max-width: 400px) { + .footer { + display:none + } +} + +/* big modal */ +.modal-lg { + max-width:1140px !important; +} + +.mapboxgl-map{ + font-family: 'PT Mono', monospace; + /* font-family: 'Special Elite', cursive !important */ +} + +.mapboxgl-ctrl-logo{ + display: none !important; +} + + +/* paper plane animation */ +#canvas3d{ + display:none; + transition: all .3s; + position:fixed; + height:100vh; + width:100vw; + background-color: rgba(0,0,0,0); + z-index:9999; + margin-top: 50vh; /* poussé de la moitié de hauteur de viewport */ + transform: translateY(-50%) translateX(-50%); /* tiré de la moitié de sa propre hauteur */ + margin-left: 50vw; /* poussé de la moitié de hauteur de viewport */ +} + +/* qill text editor */ +#messageTextArea { + min-height: 200px; + border-radius: 0px 0px .25em .25em !important; + border-color: #ced4da !important +} + +.ql-toolbar{ + border-radius: .25em .25em 0px 0px !important; + border-color: #ced4da !important +} + diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..c84ea25 --- /dev/null +++ b/src/config.js @@ -0,0 +1,4 @@ +var config = { + mapboxKey: 'pk.eyJ1IjoibGVvLWlnYSIsImEiOiJjazY2bGV3MTYxMjV3M25sMmdtNWluM2wzIn0.2THSqD6nz9OhE0Xsjnbw1g', + owmKey: "39d5c3be709ba4f5b6230cdffdedd2ac", +} \ No newline at end of file diff --git a/src/geocoder.js b/src/geocoder.js new file mode 100644 index 0000000..2c1c5cb --- /dev/null +++ b/src/geocoder.js @@ -0,0 +1,67 @@ +let langGeocoder = "fr" + +// Expediteur +var expeLocList = [] +var expeLoc; +$('#expeGeocoderPhoton').typeahead({ + source: function (query, process) { + expeLocList = [] + var url = "https://photon.komoot.io/api?q="+query+"&lang="+langGeocoder+"&limit=4&osm_tag=place:city&osm_tag=place:town&osm_tag=place:village&osm_tag=place:municipality"; + var res = [] + return $.getJSON(url,function(data){ + for (var i in data.features){ + res.push(data.features[i].properties.name+" ("+data.features[i].properties.state+", "+data.features[i].properties.country+")") + expeLocList.push({ + name: data.features[i].properties.name, + center: data.features[i].geometry.coordinates, + fullname: data.features[i].properties.name+" ("+data.features[i].properties.state+", "+data.features[i].properties.country+")" + }) + } + return process(res) + }) + }, + matcher: function(item){ //needed when spelling not exact + return true + }, + + afterSelect: function (obj) { + for (var i in expeLocList){ + if (expeLocList[i].fullname == obj){ + expeLoc = expeLocList[i] + } + } + } +}) + +//Destinataire +var destLocList = [] +var destLoc; +$('#destGeocoderPhoton').typeahead({ + source: function (query, process) { + destLocList = [] + var url = "https://photon.komoot.io/api?q="+query+"&lang="+langGeocoder+"&limit=4&osm_tag=place:city&osm_tag=place:town&osm_tag=place:village&osm_tag=place:municipality"; + var res = [] + return $.getJSON(url,function(data){ + for (var i in data.features){ + res.push(data.features[i].properties.name+" ("+data.features[i].properties.state+", "+data.features[i].properties.country+")") + destLocList.push({ + name: data.features[i].properties.name, + center: data.features[i].geometry.coordinates, + fullname: data.features[i].properties.name+" ("+data.features[i].properties.state+", "+data.features[i].properties.country+")" + }) + } + return process(res) + }) + }, + matcher: function(item){ //needed when spelling not exact + return true + }, + + afterSelect: function (obj) { + for (var i in destLocList){ + if (destLocList[i].fullname == obj){ + destLoc = destLocList[i] + } + } + } +}) \ No newline at end of file diff --git a/src/map.js b/src/map.js new file mode 100644 index 0000000..e6a34fa --- /dev/null +++ b/src/map.js @@ -0,0 +1,266 @@ +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'); + }); + } + }); + }); +}) \ No newline at end of file diff --git a/src/newPlane.js b/src/newPlane.js new file mode 100644 index 0000000..e751c59 --- /dev/null +++ b/src/newPlane.js @@ -0,0 +1,111 @@ +// init text editor in modal +var quill = new Quill('#messageTextArea', { + modules: { + toolbar: [ + [{ 'size': ['small', false, 'large'] }], + [{ 'font': [] }], + ['bold', 'italic', 'underline','strike'], + [{ 'color': [] }], + ] + }, + placeholder: '', + theme: 'snow' // or 'bubble', +}); + +$("#sendNewPlane").on('click',function(){ + + //hiding navbar + $('.navbar-collapse').collapse('hide'); + //hiding modal + $("#newPlaneModal").modal('hide') + + var line = turf.lineString([expeLoc.center,destLoc.center]) //create line between start and end + var length = turf.length(line) //get length in Km + var speed = 30 //speed in km/h + var time = length/speed //time to delivery in hours + + + //get the local time with TZ for starting point + $.getJSON('https://api.mapbox.com/v4/examples.4ze9z6tv/tilequery/'+expeLoc.center[0]+','+expeLoc.center[1]+'.json?access_token='+mapboxgl.accessToken+'',function(startTime){ + var expeTimezone = startTime.features[0].properties.TZID; + var sentDate = moment.tz(new Date(), expeTimezone); + sentDate = [sentDate.format(),expeTimezone] + + //get the local time with TZ for end point + $.getJSON('https://api.mapbox.com/v4/examples.4ze9z6tv/tilequery/'+destLoc.center[0]+','+destLoc.center[1]+'.json?access_token='+mapboxgl.accessToken+'',function(endTime){ + var destTimezone = endTime.features[0].properties.TZID; + var endDate = moment.tz(new Date(), destTimezone); + var deliveryDate = endDate.add(time, 'hours') // adding hours + deliveryDate = [endDate.format(),destTimezone] + + //storing the endDate in GMT+0 in seconds for the query + var deliverySecondsServer = moment.tz(new Date(), "Europe/London").add(time, 'hours').unix() + + var publicMessage = $("#publicMessage").prop("checked") + var message = $("#messageTextArea").val() + + var data = { + 'message':message, + 'public':publicMessage, + 'expeMail':$("#expeMail").val(), + 'destMail':$("#destMail").val(), + 'expeName':expeLoc.name, + 'destName':destLoc.name, + 'expeCoordo':expeLoc.center, + 'destCoordo':destLoc.center, + 'sentDate': sentDate, + 'deliveryDate':deliveryDate, + 'deliverySecondsServer':deliverySecondsServer, + } + //sending confirm mail + + //sending notif mail + + //scheduling mail + + //adding the plane + var planes = db.collection('planes') + if (!publicMessage){ //removing message from DB if not public + data.message = '' + } + planes.add({ + public: data.public, + message:data.message, + destName: data.destName, + destCoordo: data.destCoordo, + startName: data.expeName, + startCoordo: data.expeCoordo, + sentDate: data.sentDate, + deliveryDate: data.deliveryDate, + deliverySecondsServer: data.deliverySecondsServer + }); + + // creating image for plane, need to link it to the inputs tog et entered values. + var canvas = document.getElementById("blankCanvas"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgb(255,255,255)"; + ctx.fillRect(0,0,400,400); + ctx.fillStyle = "rgb(0,0,0)"; + ctx.font = "9px Courier"; + ctx.fillText("Message: .- / -- --- -. / ... . -. ... --..-- /",10,60); + ctx.fillText("Expediteur: leo.martine@yahoo.fr - Annemasse",10,300); + ctx.fillText("Destinataire: leo.martine@yahoo.fr - Lyon",10,320); + $("#front").attr('src',document.getElementById("blankCanvas").toDataURL()) + + + //display and animate plane + $("#canvas3d").css('display','block') + animePlane() + //moving plane at the end + setInterval(function(){ + $("#canvas3d").css('transition','transform 2500ms ease-in-out') + $("#canvas3d").css('transform','translate(50vw, -150vh)') + },4400); + //reloading page (to change) + setInterval(function(){ + window.location.href = "http://127.0.0.1:8887/?plane=1FTMbm9V9eindbwoJ9uW"; + },5500); + + }) + }); +}) \ No newline at end of file