263 lines
5.0 KiB
HTML
263 lines
5.0 KiB
HTML
<!doctype html>
|
|
|
|
<html lang="da">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
|
|
<title>GPS demo</title>
|
|
|
|
<link rel="stylesheet"
|
|
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
|
|
<style>
|
|
|
|
html,body{
|
|
height:100%;
|
|
margin:0;
|
|
}
|
|
|
|
#map{
|
|
height:100%;
|
|
}
|
|
|
|
.hud{
|
|
position:absolute;
|
|
top:10px;
|
|
left:10px;
|
|
z-index:1000;
|
|
background:white;
|
|
padding:10px;
|
|
border-radius:8px;
|
|
border:1px solid #ccc;
|
|
font-family:sans-serif;
|
|
}
|
|
|
|
.car{
|
|
width:24px;
|
|
height:24px;
|
|
transform-origin:12px 12px;
|
|
}
|
|
|
|
.car svg{
|
|
width:24px;
|
|
height:24px;
|
|
}
|
|
|
|
.car-label{
|
|
position:relative;
|
|
top:-2px;
|
|
left:26px;
|
|
font-size:12px;
|
|
background:white;
|
|
padding:2px 6px;
|
|
border-radius:10px;
|
|
border:1px solid #ccc;
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="map"></div>
|
|
|
|
<div class="hud">
|
|
<button id="play">Play</button>
|
|
<button id="pause">Pause</button>
|
|
<button id="reset">Reset</button>
|
|
</div>
|
|
|
|
<script>
|
|
|
|
/* --------- DATA --------- */
|
|
|
|
const FRAMES=[
|
|
|
|
{"Bil 1":{lat:55.6761,lon:12.5683},"Bil 2":{lat:55.6748,lon:12.5650},"Bil 3":{lat:55.6780,lon:12.5600},"Bil 4":{lat:55.6728,lon:12.5710},"Bil 5":{lat:55.6795,lon:12.5845}},
|
|
{"Bil 1":{lat:55.6762,lon:12.5720},"Bil 2":{lat:55.6760,lon:12.5670},"Bil 3":{lat:55.6772,lon:12.5603},"Bil 4":{lat:55.6736,lon:12.5698},"Bil 5":{lat:55.6793,lon:12.5815}},
|
|
{"Bil 1":{lat:55.6763,lon:12.5758},"Bil 2":{lat:55.6772,lon:12.5692},"Bil 3":{lat:55.6765,lon:12.5610},"Bil 4":{lat:55.6746,lon:12.5688},"Bil 5":{lat:55.6790,lon:12.5784}},
|
|
{"Bil 1":{lat:55.6764,lon:12.5798},"Bil 2":{lat:55.6785,lon:12.5716},"Bil 3":{lat:55.6758,lon:12.5620},"Bil 4":{lat:55.6757,lon:12.5682},"Bil 5":{lat:55.6786,lon:12.5753}},
|
|
{"Bil 1":{lat:55.6766,lon:12.5837},"Bil 2":{lat:55.6796,lon:12.5744},"Bil 3":{lat:55.6752,lon:12.5634},"Bil 4":{lat:55.6769,lon:12.5682},"Bil 5":{lat:55.6781,lon:12.5722}},
|
|
{"Bil 1":{lat:55.6768,lon:12.5875},"Bil 2":{lat:55.6804,lon:12.5776},"Bil 3":{lat:55.6747,lon:12.5652},"Bil 4":{lat:55.6781,lon:12.5688},"Bil 5":{lat:55.6776,lon:12.5692}},
|
|
{"Bil 1":{lat:55.6770,lon:12.5912},"Bil 2":{lat:55.6810,lon:12.5808},"Bil 3":{lat:55.6744,lon:12.5673},"Bil 4":{lat:55.6792,lon:12.5700},"Bil 5":{lat:55.6771,lon:12.5662}},
|
|
{"Bil 1":{lat:55.6772,lon:12.5948},"Bil 2":{lat:55.6812,lon:12.5842},"Bil 3":{lat:55.6742,lon:12.5696},"Bil 4":{lat:55.6800,lon:12.5716},"Bil 5":{lat:55.6766,lon:12.5634}},
|
|
{"Bil 1":{lat:55.6775,lon:12.5982},"Bil 2":{lat:55.6811,lon:12.5877},"Bil 3":{lat:55.6742,lon:12.5720},"Bil 4":{lat:55.6806,lon:12.5736},"Bil 5":{lat:55.6761,lon:12.5608}},
|
|
{"Bil 1":{lat:55.6778,lon:12.6016},"Bil 2":{lat:55.6808,lon:12.5910},"Bil 3":{lat:55.6744,lon:12.5742},"Bil 4":{lat:55.6809,lon:12.5758},"Bil 5":{lat:55.6756,lon:12.5584}}
|
|
|
|
];
|
|
|
|
/* --------- FARVER --------- */
|
|
|
|
const COLORS={
|
|
"Bil 1":"#ff0000",
|
|
"Bil 2":"#00c853",
|
|
"Bil 3":"#2979ff",
|
|
"Bil 4":"#2979ff",
|
|
"Bil 5":"#2979ff"
|
|
};
|
|
|
|
/* --------- KORT --------- */
|
|
|
|
const map=L.map("map").setView([55.6761,12.5683],13);
|
|
|
|
L.tileLayer(
|
|
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
{attribution:"© OpenStreetMap"}
|
|
).addTo(map);
|
|
|
|
/* --------- IKON --------- */
|
|
|
|
function carIcon(label,color){
|
|
|
|
return L.divIcon({
|
|
className:"",
|
|
iconSize:[1,1],
|
|
iconAnchor:[12,12],
|
|
html:`
|
|
<div class="car" data-role="car">
|
|
<svg viewBox="0 0 24 24">
|
|
<path
|
|
d="M12 2 L20 22 L12 18 L4 22 Z"
|
|
fill="${color}"
|
|
stroke="#ffffff"
|
|
stroke-width="1.5"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="car-label">${label}</div>
|
|
`
|
|
});
|
|
|
|
}
|
|
|
|
/* --------- BEARING --------- */
|
|
|
|
function bearing(p0,p1){
|
|
|
|
const dy=p1.lat-p0.lat;
|
|
const dx=p1.lon-p0.lon;
|
|
|
|
const a=Math.atan2(dx,dy)*180/Math.PI;
|
|
|
|
return (a+360)%360;
|
|
|
|
}
|
|
|
|
/* --------- ROTATION --------- */
|
|
|
|
function setHeading(marker,deg){
|
|
|
|
const el=marker.getElement();
|
|
if(!el)return;
|
|
|
|
const car=el.querySelector("[data-role=car]");
|
|
car.style.transform=`rotate(${deg}deg)`;
|
|
|
|
}
|
|
|
|
/* --------- MARKERS --------- */
|
|
|
|
const markers={};
|
|
const cars=Object.keys(FRAMES[0]);
|
|
|
|
for(const name of cars){
|
|
|
|
const p=FRAMES[0][name];
|
|
|
|
markers[name]=L.marker(
|
|
[p.lat,p.lon],
|
|
{icon:carIcon(name,COLORS[name])}
|
|
).addTo(map);
|
|
|
|
}
|
|
|
|
/* --------- ANIMATION --------- */
|
|
|
|
let frame=0;
|
|
let start=performance.now();
|
|
let running=true;
|
|
|
|
const STEP=1500;
|
|
|
|
function lerp(a,b,t){
|
|
return a+(b-a)*t;
|
|
}
|
|
|
|
/* smooth GPS-agtig easing */
|
|
|
|
function smooth(t){
|
|
return t*t*(3-2*t);
|
|
}
|
|
|
|
function tick(now){
|
|
|
|
if(!running)return;
|
|
|
|
const next=frame+1;
|
|
|
|
if(next>=FRAMES.length)return;
|
|
|
|
let t=(now-start)/STEP;
|
|
|
|
if(t>1)t=1;
|
|
|
|
t=smooth(t);
|
|
|
|
for(const name of cars){
|
|
|
|
const p0=FRAMES[frame][name];
|
|
const p1=FRAMES[next][name];
|
|
|
|
const lat=lerp(p0.lat,p1.lat,t);
|
|
const lon=lerp(p0.lon,p1.lon,t);
|
|
|
|
const hdg=bearing(p0,p1);
|
|
|
|
const m=markers[name];
|
|
|
|
m.setLatLng([lat,lon]);
|
|
setHeading(m,hdg);
|
|
|
|
}
|
|
|
|
if(t>=1){
|
|
|
|
frame++;
|
|
start=now;
|
|
|
|
}
|
|
|
|
requestAnimationFrame(tick);
|
|
|
|
}
|
|
|
|
requestAnimationFrame(tick);
|
|
|
|
/* --------- UI --------- */
|
|
|
|
play.onclick=()=>{
|
|
running=true;
|
|
requestAnimationFrame(tick);
|
|
};
|
|
|
|
pause.onclick=()=>{
|
|
running=false;
|
|
};
|
|
|
|
reset.onclick=()=>{
|
|
frame=0;
|
|
start=performance.now();
|
|
running=true;
|
|
requestAnimationFrame(tick);
|
|
};
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|