2023-10-11 13:58:09 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<title>Trajectory Prediction Browser Test</title>
|
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
background: black;
|
|
|
|
}
|
|
|
|
|
|
|
|
#field {
|
|
|
|
background: white;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body>
|
|
|
|
<canvas id="field" width="1500" height="1500">
|
|
|
|
|
|
|
|
</canvas>
|
|
|
|
|
2023-10-11 16:35:15 +02:00
|
|
|
<script>
|
|
|
|
// minified https://github.com/joewalnes/reconnecting-websocket
|
|
|
|
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});
|
|
|
|
</script>
|
|
|
|
|
2023-10-11 13:58:09 +02:00
|
|
|
<script>
|
|
|
|
// map the field to coordinates of our dummy tracker
|
|
|
|
const field_range = { x: [-10, 10], y: [-10, 10] }
|
|
|
|
|
|
|
|
// Create WebSocket connection.
|
|
|
|
const trajectory_socket = new WebSocket(`ws://${window.location.hostname}:{{ ws_port }}/ws/trajectory`);
|
|
|
|
const prediction_socket = new WebSocket(`ws://${window.location.hostname}:{{ ws_port }}/ws/prediction`);
|
|
|
|
let is_moving = false;
|
|
|
|
const fieldEl = document.getElementById('field');
|
|
|
|
|
|
|
|
let current_data = {}
|
|
|
|
// Listen for messages
|
|
|
|
prediction_socket.addEventListener("message", (event) => {
|
|
|
|
// console.log("Message from server ", event.data);
|
|
|
|
current_data = JSON.parse(event.data);
|
|
|
|
});
|
|
|
|
|
|
|
|
function getMousePos(canvas, evt) {
|
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
|
return {
|
|
|
|
x: evt.clientX - rect.left,
|
|
|
|
y: evt.clientY - rect.top
|
|
|
|
};
|
|
|
|
}
|
|
|
|
function mouse_coordinates_to_position(coordinates) {
|
|
|
|
const x_range = field_range.x[1] - field_range.x[0]
|
|
|
|
const x = (coordinates.x / fieldEl.clientWidth) * x_range + field_range.x[0]
|
|
|
|
const y_range = field_range.y[1] - field_range.y[0]
|
|
|
|
const y = (coordinates.y / fieldEl.clientWidth) * y_range + field_range.y[0]
|
|
|
|
return { x: x, y: y }
|
|
|
|
}
|
|
|
|
function position_to_canvas_coordinate(position) {
|
|
|
|
const x_range = field_range.x[1] - field_range.x[0]
|
|
|
|
const y_range = field_range.y[1] - field_range.y[0]
|
2023-10-11 16:35:15 +02:00
|
|
|
|
|
|
|
const x = Array.isArray(position) ? position[0] : position.x;
|
|
|
|
const y = Array.isArray(position) ? position[1] : position.y;
|
2023-10-11 13:58:09 +02:00
|
|
|
return {
|
2023-10-11 16:35:15 +02:00
|
|
|
x: (x - field_range.x[0]) * fieldEl.width / x_range,
|
|
|
|
y: (y - field_range.y[0]) * fieldEl.width / y_range,
|
2023-10-11 13:58:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// helper function so we can spread
|
|
|
|
function coord_as_list(coord) {
|
|
|
|
return [coord.x, coord.y]
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let tracker = {}
|
|
|
|
let person_counter = 0
|
|
|
|
|
|
|
|
class Person {
|
|
|
|
constructor(id) {
|
|
|
|
this.id = id;
|
|
|
|
this.history = [];
|
|
|
|
this.prediction = []
|
|
|
|
}
|
|
|
|
|
|
|
|
addToHistory(position) {
|
|
|
|
this.history.push(position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldEl.addEventListener('mousedown', (event) => {
|
|
|
|
person_counter++;
|
|
|
|
tracker[person_counter] = new Person(person_counter);
|
|
|
|
is_moving = true;
|
|
|
|
|
|
|
|
const mousePos = getMousePos(fieldEl, event);
|
|
|
|
const position = mouse_coordinates_to_position(mousePos)
|
|
|
|
tracker[person_counter].addToHistory(position);
|
|
|
|
trajectory_socket.send(JSON.stringify(tracker))
|
|
|
|
});
|
|
|
|
fieldEl.addEventListener('mousemove', (event) => {
|
|
|
|
if (!is_moving) return;
|
|
|
|
const mousePos = getMousePos(fieldEl, event);
|
|
|
|
const position = mouse_coordinates_to_position(mousePos)
|
|
|
|
tracker[person_counter].addToHistory(position);
|
|
|
|
trajectory_socket.send(JSON.stringify(tracker))
|
|
|
|
});
|
|
|
|
document.addEventListener('mouseup', (e) => {
|
|
|
|
is_moving = false;
|
|
|
|
tracker = {}
|
|
|
|
})
|
|
|
|
|
|
|
|
const ctx = fieldEl.getContext("2d");
|
|
|
|
function drawFrame() {
|
|
|
|
ctx.clearRect(0, 0, fieldEl.width, fieldEl.height);
|
|
|
|
ctx.save();
|
2023-10-11 16:35:15 +02:00
|
|
|
|
2023-10-11 13:58:09 +02:00
|
|
|
for (let id in current_data) {
|
|
|
|
const person = current_data[id];
|
|
|
|
if (person.history.length > 1) {
|
|
|
|
const hist = structuredClone(person.history)
|
|
|
|
// draw current position:
|
|
|
|
ctx.beginPath()
|
|
|
|
ctx.arc(
|
|
|
|
...coord_as_list(position_to_canvas_coordinate(hist[hist.length - 1])),
|
|
|
|
5, //radius
|
|
|
|
0, 2 * Math.PI);
|
|
|
|
ctx.fill()
|
2023-10-11 16:35:15 +02:00
|
|
|
|
2023-10-11 13:58:09 +02:00
|
|
|
ctx.beginPath()
|
|
|
|
ctx.lineWidth = 3;
|
|
|
|
ctx.strokeStyle = "#325FA2";
|
|
|
|
|
|
|
|
ctx.moveTo(...coord_as_list(position_to_canvas_coordinate(hist.shift())));
|
|
|
|
for (const position of hist) {
|
|
|
|
ctx.lineTo(...coord_as_list(position_to_canvas_coordinate(position)))
|
|
|
|
}
|
|
|
|
ctx.stroke();
|
|
|
|
}
|
2023-10-11 16:35:15 +02:00
|
|
|
|
|
|
|
if(person.hasOwnProperty('predictions') && person.predictions.length > 0) {
|
|
|
|
// multiple predictions can be sampled
|
|
|
|
person.predictions.forEach((prediction, i) => {
|
|
|
|
ctx.beginPath()
|
|
|
|
ctx.lineWidth = i === 1 ? 3 : 0.2;
|
|
|
|
ctx.strokeStyle = i === 1 ? "#ff0000" : "#ccaaaa";
|
|
|
|
|
|
|
|
// start from current position:
|
|
|
|
ctx.moveTo(...coord_as_list(position_to_canvas_coordinate(person.history[person.history.length - 1])));
|
|
|
|
for (const position of prediction) {
|
|
|
|
ctx.lineTo(...coord_as_list(position_to_canvas_coordinate(position)))
|
|
|
|
}
|
|
|
|
ctx.stroke();
|
|
|
|
});
|
|
|
|
}
|
2023-10-11 13:58:09 +02:00
|
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
window.requestAnimationFrame(drawFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
window.requestAnimationFrame(drawFrame);
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
|
|
|
|
</html>
|