js first commit
This commit is contained in:
parent
63de1dd74a
commit
46d23393e3
73
src/app.css
Normal file
73
src/app.css
Normal file
@ -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
|
||||
}
|
||||
|
4
src/config.js
Normal file
4
src/config.js
Normal file
@ -0,0 +1,4 @@
|
||||
var config = {
|
||||
mapboxKey: 'pk.eyJ1IjoibGVvLWlnYSIsImEiOiJjazY2bGV3MTYxMjV3M25sMmdtNWluM2wzIn0.2THSqD6nz9OhE0Xsjnbw1g',
|
||||
owmKey: "39d5c3be709ba4f5b6230cdffdedd2ac",
|
||||
}
|
67
src/geocoder.js
Normal file
67
src/geocoder.js
Normal file
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
266
src/map.js
Normal file
266
src/map.js
Normal file
@ -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("<b>Origine:</b> "+prop.startName+"<br><em>"+sentDate+"</em><br><b>Destination:</b> "+prop.destName+"<br><em>"+deliveryDate+"</em>")
|
||||
.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("<b>Origine:</b> "+plane.properties.startName+"<br><em>"+sentDate+"</em><br><b>Destination:</b> "+plane.properties.destName+"<br><em>"+deliveryDate+"</em>")
|
||||
.addTo(map);
|
||||
//removing route
|
||||
popup.on('close', function(){
|
||||
map.setLayoutProperty('route-'+id+'', 'visibility', 'none');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
111
src/newPlane.js
Normal file
111
src/newPlane.js
Normal file
@ -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);
|
||||
|
||||
})
|
||||
});
|
||||
})
|
Reference in New Issue
Block a user