kort/kort1.html

260 lines
8.5 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 (frames a→b→c)</title>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
></script>
<style>
html, body { height: 100%; margin: 0; }
#map { height: 100%; }
.car { width: 22px; height: 22px; transform-origin: 11px 11px; will-change: transform; filter: drop-shadow(0 1px 1px rgba(0,0,0,.35)); }
.car svg { display:block; }
.car-label {
position: relative; top: -2px; left: 26px;
font: 12px/1.2 system-ui, sans-serif;
background: rgba(255,255,255,0.85);
padding: 2px 6px; border-radius: 10px;
border: 1px solid rgba(0,0,0,0.15);
white-space: nowrap;
}
.hud {
position: absolute; z-index: 999; top: 10px; left: 10px;
background: rgba(255,255,255,0.9);
padding: 10px 12px; border-radius: 10px;
border: 1px solid rgba(0,0,0,0.15);
font: 13px/1.35 system-ui, sans-serif;
}
.hud button { margin-right: 8px; }
.hud small { color: #444; }
</style>
</head>
<body>
<div id="map"></div>
<div class="hud">
<div style="margin-bottom:6px;">
<button id="btnPlay">Play</button>
<button id="btnPause">Pause</button>
<button id="btnReset">Reset</button>
</div>
<div><strong>Data:</strong> 10 frames (a→b→c…) med 5 biler pr. frame</div>
<small>Justér STEP_MS i koden.</small>
</div>
<script>
// ---------- DATA: 10 frames ----------
/**
* FRAMES[k]["Bil 1"] = {lat, lon, headingDeg, speedMps}
* k=0..9 svarer til a..j
*/
const FRAMES = [
{ // a
"Bil 1": {lat:55.6761, lon:12.5683, headingDeg: 90, speedMps:12},
"Bil 2": {lat:55.6748, lon:12.5650, headingDeg: 40, speedMps: 9},
"Bil 3": {lat:55.6780, lon:12.5600, headingDeg:180, speedMps: 8},
"Bil 4": {lat:55.6728, lon:12.5710, headingDeg:330, speedMps: 7},
"Bil 5": {lat:55.6795, lon:12.5845, headingDeg:260, speedMps:11},
},
{ // b
"Bil 1": {lat:55.6762, lon:12.5720, headingDeg: 95, speedMps:13},
"Bil 2": {lat:55.6760, lon:12.5670, headingDeg: 45, speedMps:10},
"Bil 3": {lat:55.6772, lon:12.5603, headingDeg:190, speedMps: 8},
"Bil 4": {lat:55.6736, lon:12.5698, headingDeg:340, speedMps: 8},
"Bil 5": {lat:55.6793, lon:12.5815, headingDeg:255, speedMps:12},
},
{ // c
"Bil 1": {lat:55.6763, lon:12.5758, headingDeg:100, speedMps:14},
"Bil 2": {lat:55.6772, lon:12.5692, headingDeg: 50, speedMps:11},
"Bil 3": {lat:55.6765, lon:12.5610, headingDeg:200, speedMps: 9},
"Bil 4": {lat:55.6746, lon:12.5688, headingDeg:350, speedMps: 8},
"Bil 5": {lat:55.6790, lon:12.5784, headingDeg:250, speedMps:12},
},
{ // d
"Bil 1": {lat:55.6764, lon:12.5798, headingDeg:105, speedMps:14},
"Bil 2": {lat:55.6785, lon:12.5716, headingDeg: 55, speedMps:12},
"Bil 3": {lat:55.6758, lon:12.5620, headingDeg:210, speedMps: 9},
"Bil 4": {lat:55.6757, lon:12.5682, headingDeg: 0, speedMps: 9},
"Bil 5": {lat:55.6786, lon:12.5753, headingDeg:245, speedMps:11},
},
{ // e
"Bil 1": {lat:55.6766, lon:12.5837, headingDeg:110, speedMps:15},
"Bil 2": {lat:55.6796, lon:12.5744, headingDeg: 60, speedMps:12},
"Bil 3": {lat:55.6752, lon:12.5634, headingDeg:220, speedMps:10},
"Bil 4": {lat:55.6769, lon:12.5682, headingDeg: 10, speedMps: 9},
"Bil 5": {lat:55.6781, lon:12.5722, headingDeg:240, speedMps:10},
},
{ // f
"Bil 1": {lat:55.6768, lon:12.5875, headingDeg:115, speedMps:14},
"Bil 2": {lat:55.6804, lon:12.5776, headingDeg: 70, speedMps:11},
"Bil 3": {lat:55.6747, lon:12.5652, headingDeg:230, speedMps:10},
"Bil 4": {lat:55.6781, lon:12.5688, headingDeg: 20, speedMps:10},
"Bil 5": {lat:55.6776, lon:12.5692, headingDeg:235, speedMps:10},
},
{ // g
"Bil 1": {lat:55.6770, lon:12.5912, headingDeg:120, speedMps:13},
"Bil 2": {lat:55.6810, lon:12.5808, headingDeg: 85, speedMps:10},
"Bil 3": {lat:55.6744, lon:12.5673, headingDeg:240, speedMps: 9},
"Bil 4": {lat:55.6792, lon:12.5700, headingDeg: 30, speedMps:10},
"Bil 5": {lat:55.6771, lon:12.5662, headingDeg:230, speedMps: 9},
},
{ // h
"Bil 1": {lat:55.6772, lon:12.5948, headingDeg:125, speedMps:12},
"Bil 2": {lat:55.6812, lon:12.5842, headingDeg:100, speedMps:10},
"Bil 3": {lat:55.6742, lon:12.5696, headingDeg:250, speedMps: 8},
"Bil 4": {lat:55.6800, lon:12.5716, headingDeg: 40, speedMps: 9},
"Bil 5": {lat:55.6766, lon:12.5634, headingDeg:225, speedMps: 9},
},
{ // i
"Bil 1": {lat:55.6775, lon:12.5982, headingDeg:130, speedMps:11},
"Bil 2": {lat:55.6811, lon:12.5877, headingDeg:115, speedMps: 9},
"Bil 3": {lat:55.6742, lon:12.5720, headingDeg:260, speedMps: 8},
"Bil 4": {lat:55.6806, lon:12.5736, headingDeg: 50, speedMps: 8},
"Bil 5": {lat:55.6761, lon:12.5608, headingDeg:220, speedMps: 8},
},
{ // j
"Bil 1": {lat:55.6778, lon:12.6016, headingDeg:135, speedMps:10},
"Bil 2": {lat:55.6808, lon:12.5910, headingDeg:130, speedMps: 8},
"Bil 3": {lat:55.6744, lon:12.5742, headingDeg:270, speedMps: 7},
"Bil 4": {lat:55.6809, lon:12.5758, headingDeg: 60, speedMps: 7},
"Bil 5": {lat:55.6756, lon:12.5584, headingDeg:215, speedMps: 8},
},
];
const STEP_MS = 1500; // varighed fra frame k til k+1
// ---------- Leaflet ----------
const map = L.map('map').setView([55.6761, 12.5683], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
function carDivIcon(label) {
const html = `
<div class="car" data-role="car">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2 L20 22 L12 18 L4 22 Z"></path>
</svg>
</div>
<div class="car-label">${label}</div>
`;
return L.divIcon({ html, className:'', iconSize:[1,1], iconAnchor:[11,11] });
}
function smoothstep(t){ if(t<0)t=0; if(t>1)t=1; return t*t*(3-2*t); }
function lerp(a,b,t){ return a + (b-a)*t; }
function lerpAngleDeg(a,b,t){
let d = ((b - a + 540) % 360) - 180;
return (a + d*t + 360) % 360;
}
function setMarkerHeading(marker, headingDeg) {
const el = marker.getElement();
if (!el) return;
const carEl = el.querySelector('[data-role="car"]');
if (!carEl) return;
carEl.style.transform = `rotate(${headingDeg}deg)`;
}
// ---------- Build markers from frame 0 ----------
const carNames = Object.keys(FRAMES[0]);
const markers = new Map(); // name -> marker
for (const name of carNames) {
const f0 = FRAMES[0][name];
const m = L.marker([f0.lat, f0.lon], { icon: carDivIcon(name) }).addTo(map);
markers.set(name, m);
setMarkerHeading(m, f0.headingDeg);
}
// ---------- Animation state ----------
let playing = false;
let rafId = null;
let frameIdx = 0; // current frame k
let segStartT = performance.now();
function reset() {
playing = false;
if (rafId) cancelAnimationFrame(rafId);
rafId = null;
frameIdx = 0;
segStartT = performance.now();
for (const name of carNames) {
const f0 = FRAMES[0][name];
const m = markers.get(name);
m.setLatLng([f0.lat, f0.lon]);
setMarkerHeading(m, f0.headingDeg);
}
}
function play() {
if (playing) return;
playing = true;
rafId = requestAnimationFrame(tick);
}
function pause() {
playing = false;
if (rafId) cancelAnimationFrame(rafId);
rafId = null;
}
function tick(now) {
if (!playing) return;
const k = frameIdx;
const next = k + 1;
if (next >= FRAMES.length) {
// Stop på sidste frame
playing = false;
return;
}
const aRaw = (now - segStartT) / STEP_MS;
const a = smoothstep(aRaw);
for (const name of carNames) {
const p0 = FRAMES[k][name];
const p1 = FRAMES[next][name];
const lat = lerp(p0.lat, p1.lat, a);
const lon = lerp(p0.lon, p1.lon, a);
const hdg = lerpAngleDeg(p0.headingDeg, p1.headingDeg, a);
const m = markers.get(name);
m.setLatLng([lat, lon]);
setMarkerHeading(m, hdg);
}
if (aRaw >= 1) {
frameIdx++;
segStartT = now;
}
rafId = requestAnimationFrame(tick);
}
// Buttons
document.getElementById('btnPlay').addEventListener('click', play);
document.getElementById('btnPause').addEventListener('click', pause);
document.getElementById('btnReset').addEventListener('click', () => { reset(); play(); });
// Autostart
reset();
play();
</script>
</body>
</html>